# This file was automatically generated by SWIG (https://www.swig.org).
# Version 4.5.0
#
# Do not make changes to this file unless you know what you are doing - modify
# the SWIG interface file instead.

"""pyagrum module"""

import typing
# ## recursive import for typehints annotation
import pyagrum
# ## end of added by passForType (pyAgrum)

# Import the low-level C/C++ module
if getattr(globals().get("__spec__"), "parent", None) or __package__ or "." in __name__:
    from . import _pyagrum
else:
    import _pyagrum

import builtins as __builtin__

def _swig_repr(self):
    try:
        strthis = "proxy of " + self.this.__repr__()
    except __builtin__.Exception:
        strthis = ""
    return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,)


def _swig_setattr_nondynamic_instance_variable(set):
    def set_instance_attr(self, name, value):
        if name == "this":
            set(self, name, value)
        elif name == "thisown":
            self.this.own(value)
        elif hasattr(self, name) and isinstance(getattr(type(self), name), property):
            set(self, name, value)
        else:
            raise AttributeError("You cannot add instance attributes to %s" % self)
    return set_instance_attr


def _swig_setattr_nondynamic_class_variable(set):
    def set_class_attr(cls, name, value):
        if hasattr(cls, name) and not isinstance(getattr(cls, name), property):
            set(cls, name, value)
        else:
            raise AttributeError("You cannot add class attributes to %s" % cls)
    return set_class_attr


class _SwigNonDynamicMeta(type):
    """Meta class to enforce nondynamic attributes (no new attributes) for a class"""
    __setattr__ = _swig_setattr_nondynamic_class_variable(type.__setattr__)


import weakref

class SwigPyIterator(object):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args, **kwargs):
        raise AttributeError("No constructor defined - class is abstract")
    __repr__ = _swig_repr
    __swig_destroy__ = _pyagrum.delete_SwigPyIterator

    def value(self) -> object:
        return _pyagrum.SwigPyIterator_value(self)

    def incr(self, n: int=1) -> "swig::SwigPyIterator *":
        return _pyagrum.SwigPyIterator_incr(self, n)

    def decr(self, n: int=1) -> "swig::SwigPyIterator *":
        return _pyagrum.SwigPyIterator_decr(self, n)

    def distance(self, x: "SwigPyIterator") -> "ptrdiff_t":
        return _pyagrum.SwigPyIterator_distance(self, x)

    def equal(self, x: "SwigPyIterator") -> bool:
        return _pyagrum.SwigPyIterator_equal(self, x)

    def copy(self) -> "swig::SwigPyIterator *":
        return _pyagrum.SwigPyIterator_copy(self)

    def __next__(self) -> object:
        return _pyagrum.SwigPyIterator___next__(self)

    def previous(self) -> object:
        return _pyagrum.SwigPyIterator_previous(self)

    def advance(self, n: "ptrdiff_t") -> "swig::SwigPyIterator *":
        return _pyagrum.SwigPyIterator_advance(self, n)

    def __eq__(self, x: "SwigPyIterator") -> bool:
        return _pyagrum.SwigPyIterator___eq__(self, x)

    def __ne__(self, x: "SwigPyIterator") -> bool:
        return _pyagrum.SwigPyIterator___ne__(self, x)

    def __iadd__(self, n: "ptrdiff_t") -> "swig::SwigPyIterator &":
        return _pyagrum.SwigPyIterator___iadd__(self, n)

    def __isub__(self, n: "ptrdiff_t") -> "swig::SwigPyIterator &":
        return _pyagrum.SwigPyIterator___isub__(self, n)

    def __add__(self, n: "ptrdiff_t") -> "swig::SwigPyIterator *":
        return _pyagrum.SwigPyIterator___add__(self, n)

    def __sub__(self, *args) -> "ptrdiff_t":
        return _pyagrum.SwigPyIterator___sub__(self, *args)
    def __iter__(self):
        return self

# Register SwigPyIterator in _pyagrum:
_pyagrum.SwigPyIterator_swigregister(SwigPyIterator)
import numpy 
class JunctionTreeGenerator(object):
    r"""

    JunctionTreeGenerator is use to generate junction tree or binary junction tree from Bayesian networks.

    JunctionTreeGenerator() -> JunctionTreeGenerator
        default constructor

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def junctionTree(self, *args) -> "pyagrum.JunctionTree":
        r"""

        Computes the junction tree for its parameters. If the first parameter is a graph, the heurisitcs assume that all the node have the same domain size (2). If given, the heuristic takes into account the partial order for its elimination order.

        Parameters
        ----------
        g : pyagrum.UndiGraph
            a undirected graph

        dag : pyagrum.DAG
            a dag

        bn : pyagrum.BayesNet
            a BayesianNetwork

        partial_order: list of list of int
            a partial order among the nodeIDs

        Returns
        -------
        pyagrum.CliqueGraph
            the current junction tree.

        """
        return _pyagrum.JunctionTreeGenerator_junctionTree(self, *args)

    def eliminationOrder(self, *args) -> list[int]:
        r"""

        Computes the elimination for its parameters. If the first parameter is a graph, the heurisitcs assume that all the node have the same domain size (2). If given, the heuristic takes into account the partial order for its elimination order.

        Parameters
        ----------
        g : pyagrum.UndiGraph
            a undirected graph

        dag : pyagrum.DAG
            a dag

        bn : pyagrum.BayesNet
            a BayesianNetwork

        partial_order: list of list of int
            a partial order among the nodeIDs

        Returns
        -------
        pyagrum.CliqueGraph
            the current elimination order.

        """
        return _pyagrum.JunctionTreeGenerator_eliminationOrder(self, *args)

    def binaryJoinTree(self, *args) -> "pyagrum.JunctionTree":
        r"""

        Computes the binary joint tree for its parameters. If the first parameter is a graph, the heurisitcs assume that all the node have the same domain size (2). If given, the heuristic takes into account the partial order for its elimination order.

        Parameters
        ----------
        g : pyagrum.UndiGraph
            a undirected graph

        dag : pyagrum.DAG
            a dag

        bn : pyagrum.BayesNet
            a BayesianNetwork

        partial_order: list of list of int
            a partial order among the nodeIDs

        Returns
        -------
        pyagrum.CliqueGraph
            the current binary joint tree

        """
        return _pyagrum.JunctionTreeGenerator_binaryJoinTree(self, *args)

    def __init__(self):
        r"""

        JunctionTreeGenerator is use to generate junction tree or binary junction tree from Bayesian networks.

        JunctionTreeGenerator() -> JunctionTreeGenerator
            default constructor

        """
        _pyagrum.JunctionTreeGenerator_swiginit(self, _pyagrum.new_JunctionTreeGenerator())
    __swig_destroy__ = _pyagrum.delete_JunctionTreeGenerator

# Register JunctionTreeGenerator in _pyagrum:
_pyagrum.JunctionTreeGenerator_swigregister(JunctionTreeGenerator)
class GumException(Exception):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.GumException_swiginit(self, _pyagrum.new_GumException(*args))
    __swig_destroy__ = _pyagrum.delete_GumException

    def what(self) -> str:
        r"""

        Returns
        -------
        str
            the error message

        """
        return _pyagrum.GumException_what(self)

    def errorContent(self) -> str:
        r"""

        Returns
        -------
        str
            the error content

        """
        return _pyagrum.GumException_errorContent(self)

    def errorType(self) -> str:
        r"""

        Returns
        -------
        str
            the error type

        """
        return _pyagrum.GumException_errorType(self)

    def errorCallStack(self) -> str:
        r"""

        Returns
        -------
        str
            the error call stack

        """
        return _pyagrum.GumException_errorCallStack(self)

# Register GumException in _pyagrum:
_pyagrum.GumException_swigregister(GumException)
cvar = _pyagrum.cvar
_static_Set_end_ = cvar._static_Set_end_
_static_Set_end_safe_ = cvar._static_Set_end_safe_
_Set_end_ = cvar._Set_end_
_Set_end_safe_ = cvar._Set_end_safe_


def _createMsg_(filename: str, function: str, line: int, msg: str) -> str:
    return _pyagrum._createMsg_(filename, function, line, msg)
class FatalError(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.FatalError_swiginit(self, _pyagrum.new_FatalError(*args))
    __swig_destroy__ = _pyagrum.delete_FatalError

# Register FatalError in _pyagrum:
_pyagrum.FatalError_swigregister(FatalError)
class NotImplementedYet(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.NotImplementedYet_swiginit(self, _pyagrum.new_NotImplementedYet(*args))
    __swig_destroy__ = _pyagrum.delete_NotImplementedYet

# Register NotImplementedYet in _pyagrum:
_pyagrum.NotImplementedYet_swigregister(NotImplementedYet)
class IteratorError(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.IteratorError_swiginit(self, _pyagrum.new_IteratorError(*args))
    __swig_destroy__ = _pyagrum.delete_IteratorError

# Register IteratorError in _pyagrum:
_pyagrum.IteratorError_swigregister(IteratorError)
class UndefinedIteratorValue(IteratorError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.UndefinedIteratorValue_swiginit(self, _pyagrum.new_UndefinedIteratorValue(*args))
    __swig_destroy__ = _pyagrum.delete_UndefinedIteratorValue

# Register UndefinedIteratorValue in _pyagrum:
_pyagrum.UndefinedIteratorValue_swigregister(UndefinedIteratorValue)
class UndefinedIteratorKey(IteratorError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.UndefinedIteratorKey_swiginit(self, _pyagrum.new_UndefinedIteratorKey(*args))
    __swig_destroy__ = _pyagrum.delete_UndefinedIteratorKey

# Register UndefinedIteratorKey in _pyagrum:
_pyagrum.UndefinedIteratorKey_swigregister(UndefinedIteratorKey)
class NullElement(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.NullElement_swiginit(self, _pyagrum.new_NullElement(*args))
    __swig_destroy__ = _pyagrum.delete_NullElement

# Register NullElement in _pyagrum:
_pyagrum.NullElement_swigregister(NullElement)
class UndefinedElement(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.UndefinedElement_swiginit(self, _pyagrum.new_UndefinedElement(*args))
    __swig_destroy__ = _pyagrum.delete_UndefinedElement

# Register UndefinedElement in _pyagrum:
_pyagrum.UndefinedElement_swigregister(UndefinedElement)
class SizeError(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.SizeError_swiginit(self, _pyagrum.new_SizeError(*args))
    __swig_destroy__ = _pyagrum.delete_SizeError

# Register SizeError in _pyagrum:
_pyagrum.SizeError_swigregister(SizeError)
class ArgumentError(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.ArgumentError_swiginit(self, _pyagrum.new_ArgumentError(*args))
    __swig_destroy__ = _pyagrum.delete_ArgumentError

# Register ArgumentError in _pyagrum:
_pyagrum.ArgumentError_swigregister(ArgumentError)
class InvalidArgumentsNumber(ArgumentError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.InvalidArgumentsNumber_swiginit(self, _pyagrum.new_InvalidArgumentsNumber(*args))
    __swig_destroy__ = _pyagrum.delete_InvalidArgumentsNumber

# Register InvalidArgumentsNumber in _pyagrum:
_pyagrum.InvalidArgumentsNumber_swigregister(InvalidArgumentsNumber)
class InvalidArgument(ArgumentError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.InvalidArgument_swiginit(self, _pyagrum.new_InvalidArgument(*args))
    __swig_destroy__ = _pyagrum.delete_InvalidArgument

# Register InvalidArgument in _pyagrum:
_pyagrum.InvalidArgument_swigregister(InvalidArgument)
class IOError(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.IOError_swiginit(self, _pyagrum.new_IOError(*args))
    __swig_destroy__ = _pyagrum.delete_IOError

# Register IOError in _pyagrum:
_pyagrum.IOError_swigregister(IOError)
class FormatNotFound(IOError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.FormatNotFound_swiginit(self, _pyagrum.new_FormatNotFound(*args))
    __swig_destroy__ = _pyagrum.delete_FormatNotFound

# Register FormatNotFound in _pyagrum:
_pyagrum.FormatNotFound_swigregister(FormatNotFound)
class OperationNotAllowed(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.OperationNotAllowed_swiginit(self, _pyagrum.new_OperationNotAllowed(*args))
    __swig_destroy__ = _pyagrum.delete_OperationNotAllowed

# Register OperationNotAllowed in _pyagrum:
_pyagrum.OperationNotAllowed_swigregister(OperationNotAllowed)
class NotFound(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.NotFound_swiginit(self, _pyagrum.new_NotFound(*args))
    __swig_destroy__ = _pyagrum.delete_NotFound

# Register NotFound in _pyagrum:
_pyagrum.NotFound_swigregister(NotFound)
class OutOfBounds(ArgumentError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.OutOfBounds_swiginit(self, _pyagrum.new_OutOfBounds(*args))
    __swig_destroy__ = _pyagrum.delete_OutOfBounds

# Register OutOfBounds in _pyagrum:
_pyagrum.OutOfBounds_swigregister(OutOfBounds)
class DuplicateElement(ArgumentError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.DuplicateElement_swiginit(self, _pyagrum.new_DuplicateElement(*args))
    __swig_destroy__ = _pyagrum.delete_DuplicateElement

# Register DuplicateElement in _pyagrum:
_pyagrum.DuplicateElement_swigregister(DuplicateElement)
class DuplicateLabel(ArgumentError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.DuplicateLabel_swiginit(self, _pyagrum.new_DuplicateLabel(*args))
    __swig_destroy__ = _pyagrum.delete_DuplicateLabel

# Register DuplicateLabel in _pyagrum:
_pyagrum.DuplicateLabel_swigregister(DuplicateLabel)
class GraphError(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.GraphError_swiginit(self, _pyagrum.new_GraphError(*args))
    __swig_destroy__ = _pyagrum.delete_GraphError

# Register GraphError in _pyagrum:
_pyagrum.GraphError_swigregister(GraphError)
class HedgeException(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.HedgeException_swiginit(self, _pyagrum.new_HedgeException(*args))
    __swig_destroy__ = _pyagrum.delete_HedgeException

# Register HedgeException in _pyagrum:
_pyagrum.HedgeException_swigregister(HedgeException)
class NoNeighbour(GraphError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.NoNeighbour_swiginit(self, _pyagrum.new_NoNeighbour(*args))
    __swig_destroy__ = _pyagrum.delete_NoNeighbour

# Register NoNeighbour in _pyagrum:
_pyagrum.NoNeighbour_swigregister(NoNeighbour)
class NoParent(GraphError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.NoParent_swiginit(self, _pyagrum.new_NoParent(*args))
    __swig_destroy__ = _pyagrum.delete_NoParent

# Register NoParent in _pyagrum:
_pyagrum.NoParent_swigregister(NoParent)
class NoChild(GraphError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.NoChild_swiginit(self, _pyagrum.new_NoChild(*args))
    __swig_destroy__ = _pyagrum.delete_NoChild

# Register NoChild in _pyagrum:
_pyagrum.NoChild_swigregister(NoChild)
class InvalidEdge(GraphError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.InvalidEdge_swiginit(self, _pyagrum.new_InvalidEdge(*args))
    __swig_destroy__ = _pyagrum.delete_InvalidEdge

# Register InvalidEdge in _pyagrum:
_pyagrum.InvalidEdge_swigregister(InvalidEdge)
class InvalidArc(GraphError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.InvalidArc_swiginit(self, _pyagrum.new_InvalidArc(*args))
    __swig_destroy__ = _pyagrum.delete_InvalidArc

# Register InvalidArc in _pyagrum:
_pyagrum.InvalidArc_swigregister(InvalidArc)
class InvalidNode(GraphError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.InvalidNode_swiginit(self, _pyagrum.new_InvalidNode(*args))
    __swig_destroy__ = _pyagrum.delete_InvalidNode

# Register InvalidNode in _pyagrum:
_pyagrum.InvalidNode_swigregister(InvalidNode)
class DefaultInLabel(GraphError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.DefaultInLabel_swiginit(self, _pyagrum.new_DefaultInLabel(*args))
    __swig_destroy__ = _pyagrum.delete_DefaultInLabel

# Register DefaultInLabel in _pyagrum:
_pyagrum.DefaultInLabel_swigregister(DefaultInLabel)
class InvalidDirectedCycle(GraphError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.InvalidDirectedCycle_swiginit(self, _pyagrum.new_InvalidDirectedCycle(*args))
    __swig_destroy__ = _pyagrum.delete_InvalidDirectedCycle

# Register InvalidDirectedCycle in _pyagrum:
_pyagrum.InvalidDirectedCycle_swigregister(InvalidDirectedCycle)
class InvalidPartiallyDirectedCycle(GraphError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.InvalidPartiallyDirectedCycle_swiginit(self, _pyagrum.new_InvalidPartiallyDirectedCycle(*args))
    __swig_destroy__ = _pyagrum.delete_InvalidPartiallyDirectedCycle

# Register InvalidPartiallyDirectedCycle in _pyagrum:
_pyagrum.InvalidPartiallyDirectedCycle_swigregister(InvalidPartiallyDirectedCycle)
class CPTError(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.CPTError_swiginit(self, _pyagrum.new_CPTError(*args))
    __swig_destroy__ = _pyagrum.delete_CPTError

# Register CPTError in _pyagrum:
_pyagrum.CPTError_swigregister(CPTError)
class ScheduleMultiDimError(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.ScheduleMultiDimError_swiginit(self, _pyagrum.new_ScheduleMultiDimError(*args))
    __swig_destroy__ = _pyagrum.delete_ScheduleMultiDimError

# Register ScheduleMultiDimError in _pyagrum:
_pyagrum.ScheduleMultiDimError_swigregister(ScheduleMultiDimError)
class AbstractScheduleMultiDim(ScheduleMultiDimError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.AbstractScheduleMultiDim_swiginit(self, _pyagrum.new_AbstractScheduleMultiDim(*args))
    __swig_destroy__ = _pyagrum.delete_AbstractScheduleMultiDim

# Register AbstractScheduleMultiDim in _pyagrum:
_pyagrum.AbstractScheduleMultiDim_swigregister(AbstractScheduleMultiDim)
class UnknownScheduleMultiDim(ScheduleMultiDimError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.UnknownScheduleMultiDim_swiginit(self, _pyagrum.new_UnknownScheduleMultiDim(*args))
    __swig_destroy__ = _pyagrum.delete_UnknownScheduleMultiDim

# Register UnknownScheduleMultiDim in _pyagrum:
_pyagrum.UnknownScheduleMultiDim_swigregister(UnknownScheduleMultiDim)
class DuplicateScheduleMultiDim(ScheduleMultiDimError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.DuplicateScheduleMultiDim_swiginit(self, _pyagrum.new_DuplicateScheduleMultiDim(*args))
    __swig_destroy__ = _pyagrum.delete_DuplicateScheduleMultiDim

# Register DuplicateScheduleMultiDim in _pyagrum:
_pyagrum.DuplicateScheduleMultiDim_swigregister(DuplicateScheduleMultiDim)
class ScheduleOperationError(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.ScheduleOperationError_swiginit(self, _pyagrum.new_ScheduleOperationError(*args))
    __swig_destroy__ = _pyagrum.delete_ScheduleOperationError

# Register ScheduleOperationError in _pyagrum:
_pyagrum.ScheduleOperationError_swigregister(ScheduleOperationError)
class UnknownScheduleOperation(ScheduleOperationError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.UnknownScheduleOperation_swiginit(self, _pyagrum.new_UnknownScheduleOperation(*args))
    __swig_destroy__ = _pyagrum.delete_UnknownScheduleOperation

# Register UnknownScheduleOperation in _pyagrum:
_pyagrum.UnknownScheduleOperation_swigregister(UnknownScheduleOperation)
class UnavailableScheduleOperation(ScheduleOperationError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.UnavailableScheduleOperation_swiginit(self, _pyagrum.new_UnavailableScheduleOperation(*args))
    __swig_destroy__ = _pyagrum.delete_UnavailableScheduleOperation

# Register UnavailableScheduleOperation in _pyagrum:
_pyagrum.UnavailableScheduleOperation_swigregister(UnavailableScheduleOperation)
class UnexecutedScheduleOperation(ScheduleOperationError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.UnexecutedScheduleOperation_swiginit(self, _pyagrum.new_UnexecutedScheduleOperation(*args))
    __swig_destroy__ = _pyagrum.delete_UnexecutedScheduleOperation

# Register UnexecutedScheduleOperation in _pyagrum:
_pyagrum.UnexecutedScheduleOperation_swigregister(UnexecutedScheduleOperation)
class IncompatibleEvidence(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.IncompatibleEvidence_swiginit(self, _pyagrum.new_IncompatibleEvidence(*args))
    __swig_destroy__ = _pyagrum.delete_IncompatibleEvidence

# Register IncompatibleEvidence in _pyagrum:
_pyagrum.IncompatibleEvidence_swigregister(IncompatibleEvidence)
class FactoryError(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.FactoryError_swiginit(self, _pyagrum.new_FactoryError(*args))
    __swig_destroy__ = _pyagrum.delete_FactoryError

# Register FactoryError in _pyagrum:
_pyagrum.FactoryError_swigregister(FactoryError)
class FactoryInvalidState(FactoryError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.FactoryInvalidState_swiginit(self, _pyagrum.new_FactoryInvalidState(*args))
    __swig_destroy__ = _pyagrum.delete_FactoryInvalidState

# Register FactoryInvalidState in _pyagrum:
_pyagrum.FactoryInvalidState_swigregister(FactoryInvalidState)
class TypeError(FactoryError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.TypeError_swiginit(self, _pyagrum.new_TypeError(*args))
    __swig_destroy__ = _pyagrum.delete_TypeError

# Register TypeError in _pyagrum:
_pyagrum.TypeError_swigregister(TypeError)
class WrongClassElement(FactoryError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.WrongClassElement_swiginit(self, _pyagrum.new_WrongClassElement(*args))
    __swig_destroy__ = _pyagrum.delete_WrongClassElement

# Register WrongClassElement in _pyagrum:
_pyagrum.WrongClassElement_swigregister(WrongClassElement)
class PRMTypeError(FactoryError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.PRMTypeError_swiginit(self, _pyagrum.new_PRMTypeError(*args))
    __swig_destroy__ = _pyagrum.delete_PRMTypeError

# Register PRMTypeError in _pyagrum:
_pyagrum.PRMTypeError_swigregister(PRMTypeError)
class LearningError(GumException):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.LearningError_swiginit(self, _pyagrum.new_LearningError(*args))
    __swig_destroy__ = _pyagrum.delete_LearningError

# Register LearningError in _pyagrum:
_pyagrum.LearningError_swigregister(LearningError)
class IncompatibleScorePrior(LearningError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.IncompatibleScorePrior_swiginit(self, _pyagrum.new_IncompatibleScorePrior(*args))
    __swig_destroy__ = _pyagrum.delete_IncompatibleScorePrior

# Register IncompatibleScorePrior in _pyagrum:
_pyagrum.IncompatibleScorePrior_swigregister(IncompatibleScorePrior)
class PossiblyIncompatibleScorePrior(LearningError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.PossiblyIncompatibleScorePrior_swiginit(self, _pyagrum.new_PossiblyIncompatibleScorePrior(*args))
    __swig_destroy__ = _pyagrum.delete_PossiblyIncompatibleScorePrior

# Register PossiblyIncompatibleScorePrior in _pyagrum:
_pyagrum.PossiblyIncompatibleScorePrior_swigregister(PossiblyIncompatibleScorePrior)
class DatabaseError(LearningError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.DatabaseError_swiginit(self, _pyagrum.new_DatabaseError(*args))
    __swig_destroy__ = _pyagrum.delete_DatabaseError

# Register DatabaseError in _pyagrum:
_pyagrum.DatabaseError_swigregister(DatabaseError)
class MissingVariableInDatabase(LearningError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.MissingVariableInDatabase_swiginit(self, _pyagrum.new_MissingVariableInDatabase(*args))
    __swig_destroy__ = _pyagrum.delete_MissingVariableInDatabase

# Register MissingVariableInDatabase in _pyagrum:
_pyagrum.MissingVariableInDatabase_swigregister(MissingVariableInDatabase)
class MissingValueInDatabase(LearningError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.MissingValueInDatabase_swiginit(self, _pyagrum.new_MissingValueInDatabase(*args))
    __swig_destroy__ = _pyagrum.delete_MissingValueInDatabase

# Register MissingValueInDatabase in _pyagrum:
_pyagrum.MissingValueInDatabase_swigregister(MissingValueInDatabase)
class UnknownLabelInDatabase(LearningError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.UnknownLabelInDatabase_swiginit(self, _pyagrum.new_UnknownLabelInDatabase(*args))
    __swig_destroy__ = _pyagrum.delete_UnknownLabelInDatabase

# Register UnknownLabelInDatabase in _pyagrum:
_pyagrum.UnknownLabelInDatabase_swigregister(UnknownLabelInDatabase)
class SyntaxError(IOError):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.SyntaxError_swiginit(self, _pyagrum.new_SyntaxError(*args))

    def col(self) -> int:
        r"""

        Returns
        -------
        int
            the indice of the colonne of the error

        """
        return _pyagrum.SyntaxError_col(self)

    def line(self) -> int:
        r"""

        Returns
        -------
        int
            the indice of the line of the error

        """
        return _pyagrum.SyntaxError_line(self)

    def filename(self) -> str:
        r"""

        Return the name of the file in which the syntax error occurred.

        Returns
        -------
        str
            the name of the file containing the syntax error

        """
        return _pyagrum.SyntaxError_filename(self)
    __swig_destroy__ = _pyagrum.delete_SyntaxError

# Register SyntaxError in _pyagrum:
_pyagrum.SyntaxError_swigregister(SyntaxError)

def setNumberOfThreads(number: int) -> None:
    r"""

    To aNone spare cycles (less then 100% CPU occupied), use more threads than logical processors (x2 is a good all-around value).

    Returns
    -------
    number : int
      the number of threads to be used

    """
    return _pyagrum.setNumberOfThreads(number)

def isOMP() -> bool:
    r"""

    Returns
    -------
    bool
      True if OMP has been set at compilation, False otherwise

    """
    return _pyagrum.isOMP()

def dispatchRangeToThreads(beg: int, end: int, nb_threads: int) -> tuple[tuple[int, int], ...]:
    return _pyagrum.dispatchRangeToThreads(beg, end, nb_threads)

def randomValue(max: int=2) -> int:
    r"""

    Returns
    -------
    int
      a value randomly drawn (0 or 1)

    """
    return _pyagrum.randomValue(max)

def randomProba() -> float:
    r"""

    Returns
    -------
    float
        a random number between 0 and 1 included (i.e. a proba).

    """
    return _pyagrum.randomProba()

def randomGeneratorSeed() -> int:
    r"""

    Returns
    -------
    int
      a randomly generated seed

    """
    return _pyagrum.randomGeneratorSeed()

def currentRandomGeneratorValue() -> int:
    return _pyagrum.currentRandomGeneratorValue()

def initRandom(seed: int=0) -> None:
    r"""

    Initialize random generator seed. If `seed=0`, the generator is initialized from the current time in ms. `seed!=0` allows to fix the generator in a certain stage and then allows to repeat the same pseudo-random numbers sequence.

    Parameters
    ----------
    seed : int
      the seed used to initialize the random generator (0 if using time)

    """
    return _pyagrum.initRandom(seed)
VarType_DISCRETIZED = _pyagrum.VarType_DISCRETIZED
VarType_LABELIZED = _pyagrum.VarType_LABELIZED
VarType_INTEGER = _pyagrum.VarType_INTEGER
VarType_NUMERICAL = _pyagrum.VarType_NUMERICAL
VarType_RANGE = _pyagrum.VarType_RANGE
VarType_CONTINUOUS = _pyagrum.VarType_CONTINUOUS
class Variable(object):
    r"""

    Abstract class used by DiscreteVariable.

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args, **kwargs):
        raise AttributeError("No constructor defined - class is abstract")
    __repr__ = _swig_repr
    __swig_destroy__ = _pyagrum.delete_Variable

    def clone(self) -> "pyagrum.Variable":
        r"""

        Copy factory

        Returns
        -------
        pyagrum.DiscreteVariable
            a pointer on a new copy of this

        """
        return _pyagrum.Variable_clone(self)

    def __eq__(self, aRV: "pyagrum.Variable") -> bool:
        return _pyagrum.Variable___eq__(self, aRV)

    def setName(self, theValue: str) -> None:
        r"""

        sets the name of the variable.

        Parameters
        ----------
        theValue : str
            the new description of the variable

        """
        return _pyagrum.Variable_setName(self, theValue)

    def name(self) -> str:
        r"""

        Returns
        -------
        str
            the name of the variable

        """
        return _pyagrum.Variable_name(self)

    def setDescription(self, theValue: str) -> None:
        r"""

        set the description of the variable.

        Parameters
        ----------
        theValue : str
            the new description of the variable

        """
        return _pyagrum.Variable_setDescription(self, theValue)

    def description(self) -> str:
        r"""

        Returns
        -------
        str
            the description of the variable

        """
        return _pyagrum.Variable_description(self)

    def varType(self) -> int:
        return _pyagrum.Variable_varType(self)

    def domain(self) -> str:
        return _pyagrum.Variable_domain(self)

# Register Variable in _pyagrum:
_pyagrum.Variable_swigregister(Variable)
class DiscreteVariable(Variable):
    r"""

    DiscreteVariable is the (abstract) base class for discrete random variables.

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args, **kwargs):
        raise AttributeError("No constructor defined - class is abstract")
    __swig_destroy__ = _pyagrum.delete_DiscreteVariable

    def clone(self) -> "pyagrum.DiscreteVariable":
        r"""

        Returns
        -------
          pyagrum.DiscreteVariable
            a copy of the DiscreteVariable

        """
        return _pyagrum.DiscreteVariable_clone(self)

    def empty(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the domain size < 2

        """
        return _pyagrum.DiscreteVariable_empty(self)

    def domainSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of modalities in the variable domain

        """
        return _pyagrum.DiscreteVariable_domainSize(self)

    def labels(self) -> tuple[str, ...]:
        r"""

        Returns
        -------
        tuple
            a tuple containing the labels

        """
        return _pyagrum.DiscreteVariable_labels(self)

    def numerical(self, indice: int) -> float:
        r"""

        Parameters
        ----------
        index : int
            an index

        Returns
        -------
        float
            the numerical representation of the value at the given index

        """
        return _pyagrum.DiscreteVariable_numerical(self, indice)

    def isEmpirical(self) -> bool:
        return _pyagrum.DiscreteVariable_isEmpirical(self)

    def isNumerical(self) -> bool:
        r"""

        Returns True if the variable has a meaningful numerical representation, i.e. it is not a LabelizedVariable.

        Returns
        -------
        bool
            True for DiscretizedVariable, IntegerVariable, NumericalDiscreteVariable and RangeVariable; False for LabelizedVariable.

        """
        return _pyagrum.DiscreteVariable_isNumerical(self)

    def closestIndex(self, val: float) -> int:
        r"""

        For numerical discrete variables (all except pyagrum.LabelizedVariable), this method returns the index of the closest value to a given float value in the variable's domain.

        Parameters
        ----------
        val : float
          the value for which we want to find the closest index
        Returns
        -------
        int
          the index of the closest value to `value` in the variable's domain

        Raises
        ------
        pyagrum.NotImplementedYet
          if the variable is not numerical discrete (i.e., if it is a pyagrum.LabelizedVariable).

        """
        return _pyagrum.DiscreteVariable_closestIndex(self, val)

    def closestLabel(self, val: float) -> str:
        r"""

        For numerical discrete variables (all except pyagrum.LabelizedVariable), this method returns the label of the closest value to a given float value in the variable's domain.

        Parameters
        ----------
        val : float
          the value for which we want to find the closest index
        Returns
        -------
        str
          the label of the closest value to `value` in the variable's domain

        Raises
        ------
        pyagrum.InvalidArgument
          if the variable is not numerical discrete (i.e., if it is a pyagrum.LabelizedVariable).

        """
        return _pyagrum.DiscreteVariable_closestLabel(self, val)

    def varType(self) -> int:
        r"""

        returns the type of variable

        Returns
        -------
        int :
            the type of the variable.

            0: DiscretizedVariable, 1: LabelizedVariable, 2: IntegerVariable, 3: NumericalDiscreteVariable, 4: RangeVariable

        """
        return _pyagrum.DiscreteVariable_varType(self)

    def toFast(self) -> str:
        r"""

        Return the variable description in fast syntax (e.g. ``name[val1|val2|...]``).

        Returns
        -------
        str
            fast-syntax string representation

        """
        return _pyagrum.DiscreteVariable_toFast(self)

    def index(self, label: str) -> int:
        r"""

        Parameters
        ----------
        label : str
            a label

        Returns
        -------
        int
            the index of the label

        """
        return _pyagrum.DiscreteVariable_index(self, label)

    def label(self, i: int) -> str:
        r"""

        Parameters
        ----------
        i : int
            the index of the label we wish to return

        Returns
        -------
        str
            the label at the given index

        Raises
        ------
        pyagrum.OutOfBounds
            If the variable does not contain the label

        """
        return _pyagrum.DiscreteVariable_label(self, i)

    def toStringWithDescription(self) -> str:
        r"""

        Returns
        -------
        str
            a description of the variable

        """
        return _pyagrum.DiscreteVariable_toStringWithDescription(self)

    def domain(self) -> str:
        r"""

        Returns
        -------
        str
            the domain of the variable

        """
        return _pyagrum.DiscreteVariable_domain(self)

    def stype(self) -> str:
        r"""

        Returns
        -------
        str
            a description of its type

        """
        return _pyagrum.DiscreteVariable_stype(self)

    def __repr__(self) -> str:
        return _pyagrum.DiscreteVariable___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.DiscreteVariable___str__(self)

    def __iter__(self):
      """
      Iterate over the labels of the variable

      Yield
      -----
      tuple[int,str]
        The index of the label and its value
      """
      for i in range(self.domainSize()):
        yield i,self.label(i)

    def __hash__(self):
        return hash(self.name()+self.domain())

    def __getitem__(self,label):   # adding the y() function here
        return self.index(label)

    ###########
    # shortcuts for readonly API from derived classes
    ###########
    # Labelized
    def posLabel(self,s:str)->int:
      """
      Returns the index of the label `s` in the variable's domain (if the variable is a pyagrum.LabelizedVariable)

      Raises
      ------
        NotImplementedError
          If the variable is not a pyagrum.LabelizedVariable
        NotFoundError
          If the variable does not contain the label `s`

      Returns
      -------
        int
          the index of the label `s` in the variable's domain
      """
      try:
        return self.asLabelizedVar().posLabel(s)
      except pyagrum.OperationNotAllowed:
         raise NotImplementedError(f"posLabel not implemented for {self}")
    def isLabel(self,s:str)->bool:
      """
      returns whether s is a label of the variable

      Raises
      ------
        NotImplementedError
          If the variable is not a pyagrum.LabelizedVariable

      Returns
      -------
        bool
          True if s is a label of the variable, False otherwise
      """
      try:
        return self.asLabelizedVar().isLabel(s)
      except pyagrum.OperationNotAllowed:
         raise NotImplementedError(f"isLabel not implemented for {self}")
    ###########
    # Range
    def belongs(self,x:int)->bool:
      """
      returns whether x belongs to the variable domain (if the variable is a pyagrum.RangeVariable)

      Parameters
      ----------
      x : int
          the value for which we want to know if it belongs to the variable domain

      Raises
      ------
        NotImplementedError
          If the variable is not a pyagrum.RangeVariable

      Returns
      -------
        bool
          True if x belongs to the variable domain, False otherwise
      """
      try:
        return self.asRangeVar().belongs(x)
      except pyagrum.OperationNotAllowed:
         raise NotImplementedError(f"belongs not implemented for {self}")
    def minVal(self)->int:
      """
      returns the minimum value of the variable domain (if the variable is a pyagrum.RangeVariable)

      Raises
      ------
        NotImplementedError
          If the variable is not a pyagrum.RangeVariable

      Returns
      -------
        int
          the minimum value of the variable domain
      """
      try:
        return self.asRangeVar().minVal()
      except pyagrum.OperationNotAllowed:
         raise NotImplementedError(f"minVal not implemented for {self}")
    def maxVal(self)->int:
      """
      returns the maximum value of the variable domain (if the variable is a pyagrum.RangeVariable)

      Raises
      ------
        NotImplementedError
          If the variable is not a pyagrum.RangeVariable

      Returns
      -------
        int
          the maximum value of the variable domain
      """
      try:
        return self.asRangeVar().maxVal()
      except pyagrum.OperationNotAllowed:
         raise NotImplementedError(f"maxVal- not implemented for {self}")
    ###########
    # NumericalDiscreteVariable and/or IntegerVariable
    def numericalDomain(self)->tuple[float,...]:
      """
      Returns the numerical domain of the variable (if the variable is a pyagrum.NumericalDiscreteVariable)

      Raises
      ------
        NotImplementedError
          If the variable is not a pyagrum.NumericalDiscreteVariable

      Returns
      -------
        tuple[float,...]
          the numerical domain of the variable
      """
      try:
        return self.asNumericalDiscreteVar().numericalDomain()
      except pyagrum.OperationNotAllowed:
         raise NotImplementedError(f"numericalDomain not implemented for {self}")

    def integerDomain(self)->tuple[int,...]:
      """
      Returns the integer domain of the variable (if the variable is a pyagrum.integerVariable)

      Raises
      ------
        NotImplementedError
          If the variable is not a pyagrum.IntegerVariable

      Returns
      -------
        tuple[int,...]
          the integer domain of the variable
      """
      try:
        return self.asIntegerVar().integerDomain()
      except pyagrum.OperationNotAllowed:
        raise NotImplementedError(f"isValue not implemented for {self}")

    def isValue(self,x:float)->bool:
      """
      Returns whether x is a value for the variable

      Parameters
      ----------
      x : float
          the value for which we want to know if it is a value for the variable

      Raises
      ------
        NotImplementedError
          If the variable is not a pyagrum.NumericalDiscreteVariable or a pyagrum.IntegerVariable

      Returns
      -------
        bool
          whether x is a value for the variable, i.e. whether x belongs to the variable domain
      """
      try:
        return self.asNumericalDiscreteVar().isValue(x)
      except pyagrum.OperationNotAllowed:
        try:
          return self.asIntegerVar().isValue(x)
        except pyagrum.OperationNotAllowed:
          raise NotImplementedError(f"isValue not implemented for {self}")

    ###########
    # DiscretizedVariable
    def isTick(self,x:float):
      """
      Returns whether x is a tick for the variable

      Parameters
      ----------
      x : float
          the value for which we want to know if it is a tick for the variable

      Raises
      ------
        NotImplementedError
          If the variable is not a pyagrum.DiscretizedVariable

      Returns
      -------
        bool
          whether x is a tick for the variable, i.e. whether x belongs to the variable's domain
      """
      try:
        return self.asDiscretizedVar().isTick(x)
      except pyagrum.OperationNotAllowed:
        raise NotImplementedError(f"isTick not implemented for {self}")
    def ticks(self)->tuple[float,...]:
      """
      Returns the ticks of the variable

      Raises
      ------
        NotImplementedError
          If the variable is not a pyagrum.DiscretizedVariable

      Returns
      -------
        tuple[float,...]
          the ticks of the variable, i.e. the values that belong to the variable's domain
      """
      try:
        return self.asDiscretizedVar().ticks()
      except pyagrum.OperationNotAllowed:
        raise NotImplementedError(f"ticks not implemented for {self}")
    def isEmpirical(self)->bool:
      """
      Returns whether the variable is empirical (if the variable is a pyagrum.DiscretizedVariable). I.e. whether its limits (max and min) are defined by data and accept any values below or above these limits, contrary to non-empirical discretized variables for which the limits are fixed and values outside of these limits are not accepted)

      Raises
      ------
        NotImplementedError
          If the variable is not a pyagrum.DiscretizedVariable

      Returns
      -------
        bool
          whether the variable is empirical
      """
      try:
        return self.asDiscretizedVar().isEmpirical()
      except pyagrum.OperationNotAllowed:
        raise NotImplementedError(f"isEmpirical not implemented for {self}")
    def tick(self,x:int):
      """
      Returns the x-th tick of the variable (if the variable is a pyagrum.DiscretizedVariable)

      Parameters
      ----------
      x : int
          the index of the tick we want to get

      Raises
      ------
        NotImplementedError
          If the variable is not a pyagrum.DiscretizedVariable
        pyagrum.OutOfBounds
          If x is not a valid index for the ticks of the variable

      Returns
      -------
        float
          the x-th tick of the variable
      """
      try:
        return self.asDiscretizedVar().tick(x)
      except pyagrum.OperationNotAllowed:
        raise NotImplementedError(f"tick not implemented for {self}")
    def draw(self,x):
      """
      Draw a random float value from the x-th interval of the variable (only for pyagrum.DiscretizedVariable).

      Parameters
      ----------
      x : int
          the index of the interval from which to draw

      Returns
      -------
      float
          a random value drawn uniformly from the x-th interval

      Raises
      ------
      NotImplementedError
          if the variable is not a pyagrum.DiscretizedVariable
      pyagrum.OutOfBounds
          if x is not a valid interval index
      """
      try:
        return self.asDiscretizedVar().draw(x)
      except pyagrum.OperationNotAllowed :
        raise NotImplementedError(f"draw not implemented for {self}")


    def asLabelizedVar(self) -> "pyagrum.LabelizedVariable":
        r"""

        Tries to cast the variable as a `pyagrum.LabelizedVar`.

        Raises
        ------
        pyagrum.OperationNotAllowed if this is not possible

        Returns
        -------
        pyagrum.LabelizedVar
          the variable as a pyagrum.LabelizedVar

        """
        return _pyagrum.DiscreteVariable_asLabelizedVar(self)

    def asRangeVar(self) -> "pyagrum.RangeVariable":
        r"""

        Tries to cast the variable as a `pyagrum.RangeVar`.

        Raises
        ------
        pyagrum.OperationNotAllowed if this is not possible

        Returns
        -------
        pyagrum.RangeVar
          the variable as a pyagrum.RangeVar

        """
        return _pyagrum.DiscreteVariable_asRangeVar(self)

    def asIntegerVar(self) -> "pyagrum.IntegerVariable":
        r"""

        Tries to cast the variable as a `pyagrum.IntegerVar`.

        Raises
        ------
        pyagrum.OperationNotAllowed if this is not possible

        Returns
        -------
        pyagrum.IntegerVar
          the variable as a pyagrum.IntegerVar

        """
        return _pyagrum.DiscreteVariable_asIntegerVar(self)

    def asNumericalDiscreteVar(self) -> "pyagrum.NumericalDiscreteVariable":
        r"""

        Tries to cast the variable as a `pyagrum.NumericalDiscreteVar`.

        Raises
        ------
        pyagrum.OperationNotAllowed if this is not possible

        Returns
        -------
        pyagrum.NumericalDiscreteVar
          the variable as a pyagrum.NumericalDiscreteVar

        """
        return _pyagrum.DiscreteVariable_asNumericalDiscreteVar(self)

    def asDiscretizedVar(self) -> "pyagrum.DiscretizedVariable":
        r"""

        Tries to cast the variable as a `pyagrum.DiscretizedVar`.

        Raises
        ------
        pyagrum.OperationNotAllowed if this is not possible

        Returns
        -------
        pyagrum.DiscretizedVar
          the variable as a pyagrum.DiscretizedVar

        """
        return _pyagrum.DiscreteVariable_asDiscretizedVar(self)

# Register DiscreteVariable in _pyagrum:
_pyagrum.DiscreteVariable_swigregister(DiscreteVariable)
class LabelizedVariable(DiscreteVariable):
    r"""

    LabelizedVariable is a discrete random variable with a customizable sequence of labels.

    LabelizedVariable(aName, aDesc='', nbrLabel=2) -> LabelizedVariable
        Parameters:
            - **aName** (str) -- the name of the variable
            - **aDesc** (str) -- the (optional) description of the variable
            - **nbrLabel** (int) -- the number of labels to create (2 by default)

    LabelizedVariable(aName, aDesc='', labels) -> LabelizedVariable
        Parameters:
            - **aName** (str) -- the name of the variable
            - **aDesc** (str) -- the (optional) description of the variable
            - **labels** (list of str) -- the labels to create

    LabelizedVariable(aLDRV) -> LabelizedVariable
        Parameters:
            - **aLDRV** (*pyagrum.LabelizedVariable*) -- The pyagrum.LabelizedVariable that will be copied

    Examples
    --------
    >>> import pyagrum as gum
    >>> # creating a variable with 3 labels : '0', '1' and '2'
    >>> va=pyagrum.LabelizedVariable('a','a labelized variable',3)
    >>> print(va)
    a:Labelized(<0,1,2>)
    >>> va.addLabel('foo')
    (pyagrum.LabelizedVariable@0x7fc4c840dd90) a:Labelized(<0,1,2,foo>)
    >>> va.changeLabel(1,'bar')
    >>> print(va)
    a:Labelized(<0,bar,2,foo>)
    >>> vb=pyagrum.LabelizedVariable('b','b',0).addLabel('A').addLabel('B').addLabel('C')
    >>> print(vb)
    b:Labelized(<A,B,C>)
    >>> vb.labels()
    ('A', 'B', 'C')
    >>> vb.isLabel('E')
    False
    >>> vb.label(2)
    'C'
    >>> vc=pyagrum.LabelizedVariable('b','b',['one','two','three'])
    >>> vc
    (pyagrum.LabelizedVariable@0x7fc4c840c130) b:Labelized(<one,two,three>)

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args):
        _pyagrum.LabelizedVariable_swiginit(self, _pyagrum.new_LabelizedVariable(*args))
    __swig_destroy__ = _pyagrum.delete_LabelizedVariable

    def clone(self) -> "pyagrum.LabelizedVariable":
        r"""

        Returns
        -------
        pyagrum.LabelizedVariable
            a copy of the LabelizedVariable

        """
        return _pyagrum.LabelizedVariable_clone(self)

    def index(self, label: str) -> int:
        r"""

        Parameters
        ----------
        label : str
            a label

        Returns
        -------
        int
            the index of the label

        """
        return _pyagrum.LabelizedVariable_index(self, label)

    def isLabel(self, aLabel: str) -> bool:
        r"""

        Indicates whether the variable already has the label passed in argument

        Parameters
        ----------
        aLabel : str
            the label to be tested

        Returns
        -------
        bool
            True if the label already exists

        """
        return _pyagrum.LabelizedVariable_isLabel(self, aLabel)

    def addLabel(self,*args):
        """
        Add a label with a new index (we assume that we will NEVER remove a label).

        Parameters
        ----------
        aLabel : str
            the label to be added to the labelized variable

        Returns
        -------
        pyagrum.LabelizedVariable
            the labelized variable

        Raises
        ------
          pyagrum.DuplicateElement
            If the variable already contains the label
        """
        _pyagrum.LabelizedVariable_addLabel(self,*args)
        return self



    def changeLabel(self, pos: int, aLabel: str) -> None:
        r"""

        Change the label at the specified index

        Parameters
        ----------
        pos : int
            the index of the label to be changed
        aLabel : str
            the label to be added to the labelized variable

        Raises
        ------
        pyagrum.DuplicateElement
          If the variable already contains the new label
        pyagrum.OutOfBounds
          If the index is greater than the size of the variable

        """
        return _pyagrum.LabelizedVariable_changeLabel(self, pos, aLabel)

    def eraseLabels(self) -> None:
        r"""

        Erase all the labels from the variable.

        """
        return _pyagrum.LabelizedVariable_eraseLabels(self)

    def label(self, i: int) -> str:
        r"""

        Parameters
        ----------
        i : int
            the index of the label we wish to return

        Returns
        -------
        str
            the label at the given index

        Raises
        ------
        pyagrum.OutOfBounds
            If the variable does not contain the label

        """
        return _pyagrum.LabelizedVariable_label(self, i)

    def posLabel(self, label: str) -> int:
        r"""

        Return the position (index) of a label in the variable's domain.

        Parameters
        ----------
        label : str
            the label to look up

        Returns
        -------
        int
            the index of the label in the domain

        Raises
        ------
        pyagrum.NotFound
            if the label does not exist in the domain

        """
        return _pyagrum.LabelizedVariable_posLabel(self, label)

    def numerical(self, index: int) -> float:
        r"""

        Parameters
        ----------
        index : int
            an index

        Returns
        -------
        float
            the numerical representation of the value at the given index

        """
        return _pyagrum.LabelizedVariable_numerical(self, index)

    def closestIndex(self, val: float) -> int:
        r"""

        For numerical discrete variables (all except pyagrum.LabelizedVariable), this method returns the index of the closest value to a given float value in the variable's domain.

        Parameters
        ----------
        val : float
          the value for which we want to find the closest index
        Returns
        -------
        int
          the index of the closest value to `value` in the variable's domain

        Raises
        ------
        pyagrum.NotImplementedYet
          if the variable is not numerical discrete (i.e., if it is a pyagrum.LabelizedVariable).

        """
        return _pyagrum.LabelizedVariable_closestIndex(self, val)

    def domainSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of modalities in the variable domain

        """
        return _pyagrum.LabelizedVariable_domainSize(self)

    def varType(self) -> int:
        r"""

        returns the type of variable

        Returns
        -------
        int :
            the type of the variable.

            0: DiscretizedVariable, 1: LabelizedVariable, 2: IntegerVariable, 3: NumericalDiscreteVariable, 4: RangeVariable

        """
        return _pyagrum.LabelizedVariable_varType(self)

    def domain(self) -> str:
        r"""

        Returns
        -------
        str
            the domain of the variable as a string

        """
        return _pyagrum.LabelizedVariable_domain(self)

    def stype(self) -> str:
        r"""

        Returns
        -------
        str
            a description of its type

        """
        return _pyagrum.LabelizedVariable_stype(self)

    def toFast(self) -> str:
        r"""

        Return the variable description in fast syntax (e.g. ``name[val1|val2|...]``).

        Returns
        -------
        str
            fast-syntax string representation

        """
        return _pyagrum.LabelizedVariable_toFast(self)

    def __repr__(self) -> str:
        return _pyagrum.LabelizedVariable___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.LabelizedVariable___str__(self)

# Register LabelizedVariable in _pyagrum:
_pyagrum.LabelizedVariable_swigregister(LabelizedVariable)
class RangeVariable(DiscreteVariable):
    r"""

    RangeVariable represents a variable with a range of integers as domain.

    RangeVariable(aName, aDesc,minVal, maxVal) -> RangeVariable
        Parameters:
            - **aName** (*str*) -- the name of the variable
            - **aDesc** (*str*) -- the description of the variable
            - **minVal** (int) -- the minimal integer of the interval
            - **maxVal** (int) -- the maximal integer of the interval

    RangeVariable(aName, aDesc='') -> RangeVariable
        Parameters:
            - **aName** (*str*) -- the name of the variable
            - **aDesc** (*str*) -- the description of the variable

        By default ``minVal=0`` and ``maxVal=1``

    RangeVariable(aRV) -> RangeVariable
        Parameters:
            - **aDV** (*RangeVariable*) -- the pyagrum.RangeVariable that will be copied

    Examples
    --------
    >>> import pyagrum as gum
    >>> vI=pyagrum.RangeVariable('I','I in [4,10]',4,10)
    >>> print(vI)
    I:Range([4,10])
    >>> vI.maxVal()
    10
    >>> vI.belongs(1)
    False
    >>> # where is the value 5 ?
    >>> vI.index('5')
    1
    >>> vI.labels()
    ('4', '5', '6', '7', '8', '9', '10')

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args):
        _pyagrum.RangeVariable_swiginit(self, _pyagrum.new_RangeVariable(*args))
    __swig_destroy__ = _pyagrum.delete_RangeVariable

    def clone(self) -> "pyagrum.RangeVariable":
        r"""

        Returns
        -------
        pyagrum.RangeVariable
            a copy of the RangeVariable

        """
        return _pyagrum.RangeVariable_clone(self)

    def domainSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of modalities in the variable domain

        """
        return _pyagrum.RangeVariable_domainSize(self)

    def varType(self) -> int:
        r"""

        returns the type of variable

        Returns
        -------
        int :
            the type of the variable.

            0: DiscretizedVariable, 1: LabelizedVariable, 2: IntegerVariable, 3: NumericalDiscreteVariable, 4: RangeVariable

        """
        return _pyagrum.RangeVariable_varType(self)

    def toFast(self) -> str:
        r"""

        Return the variable description in fast syntax (e.g. ``name[min,max]``).

        Returns
        -------
        str
            fast-syntax string representation

        """
        return _pyagrum.RangeVariable_toFast(self)

    def label(self, index: int) -> str:
        r"""

        Parameters
        ----------
        indice : int
          the index of the label we wish to return

        Returns
        -------
        str
          the indice-th label

        Raises
        ------
        pyagrum.OutOfBounds
          If the variable does not contain the label

        """
        return _pyagrum.RangeVariable_label(self, index)

    def numerical(self, index: int) -> float:
        r"""

        Parameters
        ----------
        index : int
            an index

        Returns
        -------
        float
            the numerical representation of the value at the given index

        """
        return _pyagrum.RangeVariable_numerical(self, index)

    def minVal(self) -> int:
        r"""

        Returns
        -------
        long :
          the lower bound of the variable

        """
        return _pyagrum.RangeVariable_minVal(self)

    def setMinVal(self, minVal: int) -> None:
        r"""

        Set a new value of the lower bound

        Parameters
        ----------
        minVal : long
          The new value of the lower bound

        Warnings
        --------
        An error should be raised if the value is higher than the upper bound.

        """
        return _pyagrum.RangeVariable_setMinVal(self, minVal)

    def maxVal(self) -> int:
        r"""

        Returns
        -------
        long :
          the upper bound of the variable.

        """
        return _pyagrum.RangeVariable_maxVal(self)

    def setMaxVal(self, maxVal: int) -> None:
        r"""

        Set a new value of the upper bound

        Parameters
        ----------
        maxVal : long
          The new value of the upper bound

        Warnings
        --------
        An error should be raised if the value is lower than the lower bound.

        """
        return _pyagrum.RangeVariable_setMaxVal(self, maxVal)

    def belongs(self, val: int) -> bool:
        r"""

        Parameters
        ----------
        val : long
          the value to be tested

        Returns
        -------
        bool:
          True if the value in parameters belongs to the variable's interval.

        """
        return _pyagrum.RangeVariable_belongs(self, val)

    def index(self, arg2: str) -> int:
        r"""

        Parameters
        ----------
        arg2 : str
          a label

        Returns
        -------
        int
          the indice of the label

        """
        return _pyagrum.RangeVariable_index(self, arg2)

    def closestIndex(self, val: float) -> int:
        r"""

        For numerical discrete variables (all except pyagrum.LabelizedVariable), this method returns the index of the closest value to a given float value in the variable's domain.

        Parameters
        ----------
        val : float
          the value for which we want to find the closest index
        Returns
        -------
        int
          the index of the closest value to `value` in the variable's domain

        Raises
        ------
        pyagrum.NotImplementedYet
          if the variable is not numerical discrete (i.e., if it is a pyagrum.LabelizedVariable).

        """
        return _pyagrum.RangeVariable_closestIndex(self, val)

    def domain(self) -> str:
        r"""

        Returns
        -------
        str
            the domain of the variable

        """
        return _pyagrum.RangeVariable_domain(self)

    def stype(self) -> str:
        r"""

        Returns
        -------
        str
            a description of its type

        """
        return _pyagrum.RangeVariable_stype(self)

    def __repr__(self) -> str:
        return _pyagrum.RangeVariable___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.RangeVariable___str__(self)

# Register RangeVariable in _pyagrum:
_pyagrum.RangeVariable_swigregister(RangeVariable)
class IntegerVariable(DiscreteVariable):
    r"""

    IntegerVariable is a discrete random variable with a customizable sequence of int.

    IntegerVariable(aName, aDesc='', values=None) -> IntegerVariable
        Parameters:
            - **aName** (str) -- the name of the variable
            - **aDesc** (str) -- the (optional) description of the variable
            - **values** (list of int) -- the values to create

    IntegerVariable(aIDRV) -> IntegerVariable
        Parameters:
            - **aIDRV** (*pyagrum.IntegerVariable*) -- The pyagrum.IntegerVariable that will be copied

    Examples
    --------
    >>> import pyagrum as gum
    >>> # creating a variable with 3 values : 1,34,142
    >>> va=pyagrum.IntegerVariable('a','a integer variable',[1,34,142])
    >>> print(va)
    a:Integer(<1,34,142>)
    >>> va.addValue(25)
    (pyagrum.IntegerVariable@000001E4F5D07490) a:Integer(<1,25,34,142>)
    >>> va.changeValue(34,43)
    >>> print(va)
    a:Integer(<1,25,43,142>)
    >>> vb=pyagrum.IntegerVariable('b','b').addValue(34).addValue(142).addValue(1)
    >>> print(vb)
    b:Integer(<1,34,142>)
    >>> vb.labels()
    ('1', '34', '142')

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args):
        _pyagrum.IntegerVariable_swiginit(self, _pyagrum.new_IntegerVariable(*args))

    def clone(self) -> "pyagrum.IntegerVariable":
        r"""

        Returns
        -------
          pyagrum.DiscreteVariable
            a copy of the DiscreteVariable

        """
        return _pyagrum.IntegerVariable_clone(self)
    __swig_destroy__ = _pyagrum.delete_IntegerVariable

    def domainSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of modalities in the variable domain

        """
        return _pyagrum.IntegerVariable_domainSize(self)

    def varType(self) -> int:
        r"""

        returns the type of variable

        Returns
        -------
        int :
            the type of the variable.

            0: DiscretizedVariable, 1: LabelizedVariable, 2: IntegerVariable, 3: NumericalDiscreteVariable, 4: RangeVariable

        """
        return _pyagrum.IntegerVariable_varType(self)

    def toFast(self) -> str:
        r"""

        Return the variable description in fast syntax (e.g. ``name{v1|v2|...}``).

        Returns
        -------
        str
            fast-syntax string representation

        """
        return _pyagrum.IntegerVariable_toFast(self)

    def index(self, label: str) -> int:
        r"""

        Parameters
        ----------
        label : str
            a label

        Returns
        -------
        int
            the index of the label

        """
        return _pyagrum.IntegerVariable_index(self, label)

    def closestIndex(self, val: float) -> int:
        r"""

        For numerical discrete variables (all except pyagrum.LabelizedVariable), this method returns the index of the closest value to a given float value in the variable's domain.

        Parameters
        ----------
        val : float
          the value for which we want to find the closest index
        Returns
        -------
        int
          the index of the closest value to `value` in the variable's domain

        Raises
        ------
        pyagrum.NotImplementedYet
          if the variable is not numerical discrete (i.e., if it is a pyagrum.LabelizedVariable).

        """
        return _pyagrum.IntegerVariable_closestIndex(self, val)

    def label(self, index: int) -> str:
        r"""

        Parameters
        ----------
        i : int
            the index of the label we wish to return

        Returns
        -------
        str
            the label at the given index

        Raises
        ------
        pyagrum.OutOfBounds
            If the variable does not contain the label

        """
        return _pyagrum.IntegerVariable_label(self, index)

    def numerical(self, index: int) -> float:
        r"""

        Parameters
        ----------
        index : int
            an index

        Returns
        -------
        float
            the numerical representation of the value at the given index

        """
        return _pyagrum.IntegerVariable_numerical(self, index)

    def domain(self) -> str:
        r"""

        Returns
        -------
        str
            the domain of the variable

        """
        return _pyagrum.IntegerVariable_domain(self)

    def stype(self) -> str:
        r"""

        Returns
        -------
        str
            a description of its type

        """
        return _pyagrum.IntegerVariable_stype(self)

    def integerDomain(self) -> tuple[int, ...]:
        r"""

        Returns
        -------
        tuple
            the tuple of integer values that form the domain of this variable

        """
        return _pyagrum.IntegerVariable_integerDomain(self)

    def addValue(self,*args):
        """
        Add a value to the list of values for the variable.

        Parameters
        ----------
        value : int
            the new value

        Returns
        -------
        pyagrum.IntegerVariable
            the Integer variable

        Raises
        ------
          pyagrum.DuplicateElement
            If the variable already contains the value
        """
        _pyagrum.IntegerVariable_addValue(self,*args)
        return self



    def isValue(self, value: int) -> bool:
        r"""

        Parameters
        ----------
        value: int
            the value to look at.

        Returns
        -------
        bool
            True if the value is in the domain.

        """
        return _pyagrum.IntegerVariable_isValue(self, value)

    def changeValue(self, old_value: int, new_value: int) -> None:
        r"""

        Parameters
        ----------
        old_value : int
            the value to be changed

        new_value : int
            the new value

        """
        return _pyagrum.IntegerVariable_changeValue(self, old_value, new_value)

    def eraseValue(self, value: int) -> None:
        r"""

        Parameters
        ----------
        value: int
            the value to erase. If the value is not in the domain, the function does nothing (no exception raised)

        """
        return _pyagrum.IntegerVariable_eraseValue(self, value)

    def eraseValues(self) -> None:
        r"""

        Remove all the domain.

        """
        return _pyagrum.IntegerVariable_eraseValues(self)

    def closestLabel(self, val: float) -> str:
        r"""

        For numerical discrete variables (all except pyagrum.LabelizedVariable), this method returns the label of the closest value to a given float value in the variable's domain.

        Parameters
        ----------
        val : float
          the value for which we want to find the closest index
        Returns
        -------
        str
          the label of the closest value to `value` in the variable's domain

        Raises
        ------
        pyagrum.InvalidArgument
          if the variable is not numerical discrete (i.e., if it is a pyagrum.LabelizedVariable).

        """
        return _pyagrum.IntegerVariable_closestLabel(self, val)

    def __repr__(self) -> str:
        return _pyagrum.IntegerVariable___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.IntegerVariable___str__(self)

# Register IntegerVariable in _pyagrum:
_pyagrum.IntegerVariable_swigregister(IntegerVariable)
class NumericalDiscreteVariable(DiscreteVariable):
    r"""

    NumericalDiscreteVariable is a discrete random variable with a customizable sequence of float.

    NumericalDiscreteVariable(aName, aDesc='', values=None) -> NumericalDiscreteVariable
        Parameters:
            - **aName** (str) -- the name of the variable
            - **aDesc** (str) -- the (optional) description of the variable
            - **values** (list of float) -- the values to create equivalent to *fast* syntax `{v1|v2|v3|...|vn}`

    NumericalDiscreteVariable(aName, aDesc='', first, last, nbr) -> NumericalDiscreteVariable
        Parameters:
            - **aName** (str) -- the name of the variable
            - **aDesc** (str) -- the (optional) description of the variable
            - **first** (float) -- specify a list of floats from `first` to `last` in `nbr` steps.
            - **last** (float) --
            - **nbr** (int)  -- equivalent to fast syntax `{first:last:nbr}`

    NumericalDiscreteVariable(aNDRV) -> NumericalDiscreteVariable
        Parameters:
            - **aNDRV** (*pyagrum.NumericalDiscreteVariable*) -- The pyagrum.NumericalDiscreteVariable that will be copied

    Examples
    --------
    >>> import pyagrum as gum
    >>> # creating a variable with 3 values : 1.5,3.14,1.42
    >>> va=pyagrum.NumericalDiscreteVariable('a','a numerica variable',[1.5,3.14,1.42])
    >>> print(va)
    a:NumericalDiscrete({1.42|1.5|3.14})
    >>> va.addValue(2.01)
    (pyagrum.NumericalDiscreteVariable@0x55ea157b8d60) a:NumericalDiscrete({1.42|1.5|2.01|3.14})
    >>> va.changeValue(3.14,3.1415)
    >>> print(va)
    a:NumericalDiscrete({1.42|1.5|2.01|3.1415})
    >>> vb=pyagrum.NumericalDiscreteVariable('b','b').addValue(3.14).addValue(1.42).addValue(1.5)
    >>> print(vb)
    b:NumericalDiscrete({1.42|1.5|3.14})
    >>> vb.labels()
    ('1.42', '1.5', '3.14')
    >>>> vc=pyagrum.NumericalDiscreteVariable('c','c',1.2,3.8,5)
    >>> print(vc)
    c:NumericalDiscrete({1.2|1.85|2.5|3.15|3.8})

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args):
        _pyagrum.NumericalDiscreteVariable_swiginit(self, _pyagrum.new_NumericalDiscreteVariable(*args))

    def clone(self) -> "pyagrum.NumericalDiscreteVariable":
        r"""

        Returns
        -------
          pyagrum.DiscreteVariable
            a copy of the DiscreteVariable

        """
        return _pyagrum.NumericalDiscreteVariable_clone(self)
    __swig_destroy__ = _pyagrum.delete_NumericalDiscreteVariable

    def domainSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of modalities in the variable domain

        """
        return _pyagrum.NumericalDiscreteVariable_domainSize(self)

    def varType(self) -> int:
        r"""

        returns the type of variable

        Returns
        -------
        int :
            the type of the variable.

            0: DiscretizedVariable, 1: LabelizedVariable, 2: IntegerVariable, 3: NumericalDiscreteVariable, 4: RangeVariable

        """
        return _pyagrum.NumericalDiscreteVariable_varType(self)

    def toFast(self) -> str:
        r"""

        Return the variable description in fast syntax (e.g. ``name{v1|v2|...}``).

        Returns
        -------
        str
            fast-syntax string representation

        """
        return _pyagrum.NumericalDiscreteVariable_toFast(self)

    def index(self, label: str) -> int:
        r"""

        Parameters
        ----------
        label : str
            a label

        Returns
        -------
        int
            the index of the label

        """
        return _pyagrum.NumericalDiscreteVariable_index(self, label)

    def closestIndex(self, val: float) -> int:
        r"""

        For numerical discrete variables (all except pyagrum.LabelizedVariable), this method returns the index of the closest value to a given float value in the variable's domain.

        Parameters
        ----------
        val : float
          the value for which we want to find the closest index
        Returns
        -------
        int
          the index of the closest value to `value` in the variable's domain

        Raises
        ------
        pyagrum.NotImplementedYet
          if the variable is not numerical discrete (i.e., if it is a pyagrum.LabelizedVariable).

        """
        return _pyagrum.NumericalDiscreteVariable_closestIndex(self, val)

    def label(self, index: int) -> str:
        r"""

        Parameters
        ----------
        i : int
            the index of the label we wish to return

        Returns
        -------
        str
            the label at the given index

        Raises
        ------
        pyagrum.OutOfBounds
            If the variable does not contain the label

        """
        return _pyagrum.NumericalDiscreteVariable_label(self, index)

    def numerical(self, index: int) -> float:
        r"""

        Parameters
        ----------
        index : int
            an index

        Returns
        -------
        float
            the numerical representation of the value at the given index

        """
        return _pyagrum.NumericalDiscreteVariable_numerical(self, index)

    def domain(self) -> str:
        r"""

        Returns
        -------
        str
            the domain of the variable

        """
        return _pyagrum.NumericalDiscreteVariable_domain(self)

    def stype(self) -> str:
        r"""

        Returns
        -------
        str
            a description of its type

        """
        return _pyagrum.NumericalDiscreteVariable_stype(self)

    def numericalDomain(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple[float,...]
            the tuple of float values that form the domain of this variable

        """
        return _pyagrum.NumericalDiscreteVariable_numericalDomain(self)

    def addValue(self,*args):
        """
        Add a value to the list of values for the variable.

        Parameters
        ----------
        value : double
            the new value

        Returns
        -------
        pyagrum.IntegerVariable
            the Integer variable

        Raises
        ------
          pyagrum.DuplicateElement
            If the variable already contains the value
        """
        _pyagrum.NumericalDiscreteVariable_addValue(self,*args)
        return self



    def isValue(self, value: float) -> bool:
        r"""

        Parameters
        ----------
        value: int
            the value to look at.

        Returns
        -------
        bool
            True if the value is in the domain.

        """
        return _pyagrum.NumericalDiscreteVariable_isValue(self, value)

    def changeValue(self, old_value: float, new_value: float) -> None:
        r"""

        Parameters
        ----------
        old_value : int
            the value to be changed

        new_value : int
            the new value

        """
        return _pyagrum.NumericalDiscreteVariable_changeValue(self, old_value, new_value)

    def eraseValue(self, value: float) -> None:
        r"""

        Parameters
        ----------
        value: int
            the value to erase. If the value is not in the domain, the function does nothing (no exception raised)

        """
        return _pyagrum.NumericalDiscreteVariable_eraseValue(self, value)

    def eraseValues(self) -> None:
        r"""

        Remove all the domain.

        """
        return _pyagrum.NumericalDiscreteVariable_eraseValues(self)

    def closestLabel(self, val: float) -> str:
        r"""

        For numerical discrete variables (all except pyagrum.LabelizedVariable), this method returns the label of the closest value to a given float value in the variable's domain.

        Parameters
        ----------
        val : float
          the value for which we want to find the closest index
        Returns
        -------
        str
          the label of the closest value to `value` in the variable's domain

        Raises
        ------
        pyagrum.InvalidArgument
          if the variable is not numerical discrete (i.e., if it is a pyagrum.LabelizedVariable).

        """
        return _pyagrum.NumericalDiscreteVariable_closestLabel(self, val)

    def __repr__(self) -> str:
        return _pyagrum.NumericalDiscreteVariable___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.NumericalDiscreteVariable___str__(self)

# Register NumericalDiscreteVariable in _pyagrum:
_pyagrum.NumericalDiscreteVariable_swigregister(NumericalDiscreteVariable)
class IDiscretizedVariable(DiscreteVariable):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args, **kwargs):
        raise AttributeError("No constructor defined - class is abstract")
    __repr__ = _swig_repr
    __swig_destroy__ = _pyagrum.delete_IDiscretizedVariable

    def clone(self) -> "pyagrum.DiscretizedVariable":
        r"""

        Returns
        -------
          pyagrum.DiscreteVariable
            a copy of the DiscreteVariable

        """
        return _pyagrum.IDiscretizedVariable_clone(self)

    def isEmpirical(self) -> bool:
        r"""

        Check whether the variable uses relaxed (empirical) tick bounds.

        Returns
        -------
        bool
            True if the variable is in empirical mode

        """
        return _pyagrum.IDiscretizedVariable_isEmpirical(self)

    def setEmpirical(self, state: bool) -> None:
        r"""

        Enable or disable empirical (relaxed bounds) mode for the variable.

        Parameters
        ----------
        state : bool
            True to enable empirical mode, False to disable

        """
        return _pyagrum.IDiscretizedVariable_setEmpirical(self, state)

    def draw(self, indice: int) -> float:
        r"""

        Draw a random index from the variable's domain.

        Returns
        -------
        int
            the index of the drawn value

        """
        return _pyagrum.IDiscretizedVariable_draw(self, indice)

# Register IDiscretizedVariable in _pyagrum:
_pyagrum.IDiscretizedVariable_swigregister(IDiscretizedVariable)
class Edge(object):
    r"""

    pyagrum.Edge is the representation of an arc between two nodes represented by int : the first and the second.

    Edge(aN1,aN2) -> Edge
        Parameters:
            - **aN1** (int) -- the nodeId of the first node
            - **aN2** (int) -- the nodeId of the secondnode

    Edge(src) -> Edge
        Parameters:
            - **src** (*yAgrum.Edge*) -- the Edge to copy

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.Edge_swiginit(self, _pyagrum.new_Edge(*args))
    __swig_destroy__ = _pyagrum.delete_Edge

    def other(self, id: int) -> int:
        r"""

        Parameters
        ----------
        id : int
          the nodeId of one of the nodes of the Edge


        Returns
        -------
        int
          the nodeId of the other node

        """
        return _pyagrum.Edge_other(self, id)

    def first(self) -> int:
        r"""

        Returns
        -------
        int
          the nodeId of the first node of the arc (the tail)

        """
        return _pyagrum.Edge_first(self)

    def second(self) -> int:
        r"""

        Returns
        -------
        int
          the nodeId of the second node of the arc (the head)

        """
        return _pyagrum.Edge_second(self)

    def __eq__(self, src: "pyagrum.Edge") -> bool:
        return _pyagrum.Edge___eq__(self, src)

# Register Edge in _pyagrum:
_pyagrum.Edge_swigregister(Edge)
class Arc(object):
    r"""

    pyagrum.Arc is the representation of an arc between two nodes represented by int : the head and the tail.

    Arc(tail, head) -> Arc
        Parameters:
            - **tail** (int) -- the tail
            - **head** (int) -- the head

    Arc(src) -> Arc
        Parameters:
            - **src** (*Arc*) -- the pyagrum.Arc to copy

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.Arc_swiginit(self, _pyagrum.new_Arc(*args))
    __swig_destroy__ = _pyagrum.delete_Arc

    def tail(self) -> int:
        r"""

        Returns
        -------
        int
          the id of the tail node

        """
        return _pyagrum.Arc_tail(self)

    def head(self) -> int:
        r"""

        Returns
        -------
        int
          the id of the head node

        """
        return _pyagrum.Arc_head(self)

    def other(self, id: int) -> int:
        r"""

        Parameters
        ----------
        id : int
          the nodeId of the head or the tail


        Returns
        -------
        int
          the nodeId of the other node

        """
        return _pyagrum.Arc_other(self, id)

    def first(self) -> int:
        r"""

        Returns
        -------
        int
          the nodeId of the first node of the arc (the tail)

        """
        return _pyagrum.Arc_first(self)

    def second(self) -> int:
        r"""

        Returns
        -------
        int
          the nodeId of the second node of the arc (the head)

        """
        return _pyagrum.Arc_second(self)

    def __eq__(self, src: "pyagrum.Arc") -> bool:
        return _pyagrum.Arc___eq__(self, src)

# Register Arc in _pyagrum:
_pyagrum.Arc_swigregister(Arc)
class UndiGraph(object):
    r"""

    UndiGraph represents an Undirected Graph.

    UndiGraph() -> UndiGraph
        default constructor

    UndiGraph(src) -> UndiGraph
        Parameters!
            - **src** (*UndiGraph*) -- the pyagrum.UndiGraph to copy


    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args):
        _pyagrum.UndiGraph_swiginit(self, _pyagrum.new_UndiGraph(*args))
    __swig_destroy__ = _pyagrum.delete_UndiGraph

    @staticmethod
    def completeGraph(n: int) -> "pyagrum.UndiGraph":
        r"""

        Create a complete undirected graph with n nodes.

        Parameters
        ----------
        n : int
            number of nodes

        Returns
        -------
        UndiGraph
            graph where every pair of distinct nodes is connected by an edge, with nodes 0..n-1

        """
        return _pyagrum.UndiGraph_completeGraph(n)

    def __eq__(self, g: "pyagrum.UndiGraph") -> bool:
        return _pyagrum.UndiGraph___eq__(self, g)

    def eraseNode(self, id: int) -> None:
        r"""

        Erase the node and all the adjacent edges.

        Parameters
        ----------
        id : int
          the id of the node

        """
        return _pyagrum.UndiGraph_eraseNode(self, id)

    def clear(self) -> None:
        r"""

        Remove all the nodes and edges from the graph.

        """
        return _pyagrum.UndiGraph_clear(self)

    def toDot(self) -> str:
        r"""

        Returns
        -------
        str
            a friendly display of the graph in DOT format

        """
        return _pyagrum.UndiGraph_toDot(self)

    def hasUndirectedCycle(self) -> bool:
        r"""

        Checks whether the graph contains cycles.

        Returns
        -------
        bool
            True if the graph contains a cycle

        """
        return _pyagrum.UndiGraph_hasUndirectedCycle(self)

    def partialUndiGraph(self, nodes: list[int]) -> "pyagrum.UndiGraph":
        r"""

        Parameters
        ----------
        nodesSet : Set
            The set of nodes composing the partial graph

        Returns
        -------
        pyagrum.UndiGraph
            The partial graph formed by the nodes given in parameter

        """
        return _pyagrum.UndiGraph_partialUndiGraph(self, nodes)

    def chainComponents(self) -> dict[int,int]:
        r"""

        Return the chain components (connected components) of the graph.

        Each node is mapped to the id of its component root (an arbitrarily chosen node from the same component).

        Returns
        -------
        dict[int, int]
            mapping node id → component root id

        """
        return _pyagrum.UndiGraph_chainComponents(self)

    def connectedComponents(self) -> dict[int,int]:
        r"""

        Returns the connected components of the graph.

        Each node is mapped to the id of its component root (an arbitrarily chosen
        node from the same component).

        Returns
        -------
        dict[int, int]
            mapping node id → component root id

        See Also
        --------
        connectedComponentsList : returns a dict[int, set[int]] grouping nodes by component
        connectedComponentsCount : returns the number of components

        """
        return _pyagrum.UndiGraph_connectedComponents(self)

    def undirectedPath(self, node1: int, node2: int) -> list[int] | None:
        r"""

        Return a shortest undirected path between two nodes, or None if no path exists.

        Parameters
        ----------
        node1 : int
            id of the first node
        node2 : int
            id of the second node

        Returns
        -------
        list[int] or None
            ordered list of node ids along the path, or None if the nodes are disconnected

        """
        return _pyagrum.UndiGraph_undirectedPath(self, node1, node2)

    def hasUndirectedPath(self, *args) -> bool:
        r"""

        Check whether two nodes are connected by an undirected path.

        Parameters
        ----------
        n1 : int
            id of the first node
        n2 : int
            id of the second node

        Returns
        -------
        bool
            True if a path exists between n1 and n2

        """
        return _pyagrum.UndiGraph_hasUndirectedPath(self, *args)

    def __repr__(self) -> str:
        return _pyagrum.UndiGraph___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.UndiGraph___str__(self)

    def nodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
            the set of ids

        """
        return _pyagrum.UndiGraph_nodes(self)

    def connectedComponentsList(self):
      """ connected components as a dict of sets

      Returns
      -------
      dict(int, set[int])
        dict of connected components (as sets of nodeIds) keyed by an arbitrary root nodeId per component.

      """
      cc = self.connectedComponents()
      result = {}
      for node, root in cc.items():
        if root not in result:
          result[root] = set()
        result[root].add(node)
      return result

    def connectedComponentsCount(self):
      """ number of connected components

      Returns
      -------
      int
        the number of connected components in the graph.

      """
      return len(set(self.connectedComponents().values()))

    def adjacencyMatrix(self):
      """ adjacency matrix from a graph/graphical models

      Compute the adjacency matrix of a pyAgrum's graph or graphical models
      (more generally an object that has `nodes`, `children`/`parents` or `neighbours` methods)

      Returns
      -------
      numpy.ndarray
        adjacency matrix (as numpy.ndarray) with nodeId as key.

      """
      nodes=self.nodes()
      n=self.size()
      am=numpy.zeros((n,n)).astype(int)

      for node in nodes:
          if hasattr(self,'children'):
              for children in self.children(node):
                  am[node,children]=1
          if hasattr(self,'neighbours'):
              for neighbour in self.neighbours(node):
                  am[node,neighbour]=1
      return am


    def nameFromId(self, id: int) -> str:
        r"""

        Return the name of a node, or its id as a string if no name was set.

        Parameters
        ----------
        id : int
            the node id

        Returns
        -------
        str
            the name associated with the node, or ``str(id)`` if the node has no name

        """
        return _pyagrum.UndiGraph_nameFromId(self, id)

    def idFromName(self, name: str) -> int | None:
        r"""

        Return the id of the node with the given name, or None if no such name exists.

        Parameters
        ----------
        name : str
            the name to look up

        Returns
        -------
        int or None
            the node id, or None if the name is not found

        """
        return _pyagrum.UndiGraph_idFromName(self, name)

    def setName(self, id: int, name: str) -> None:
        r"""

        Assign a name to a node.

        If the node already has a name, it is replaced. The name must not already be
        used by another node.

        Parameters
        ----------
        id : int
            the node id
        name : str
            the name to assign

        Raises
        ------
        pyagrum.InvalidNode
            If the node does not exist.
        pyagrum.DuplicateElement
            If the name is already used by a different node.

        """
        return _pyagrum.UndiGraph_setName(self, id, name)

    def hasName(self, id: int) -> bool:
        r"""

        Check whether a node has an explicitly assigned name.

        Parameters
        ----------
        id : int
            the node id

        Returns
        -------
        bool
            True if a name has been assigned to this node via setName

        """
        return _pyagrum.UndiGraph_hasName(self, id)

    def addNodes(self, n: int) -> set[int]:
        r"""

        Add n nodes.

        Parameters
        ----------
        n : int
          the number of nodes to add.

        Returns
        -------
        Set of int
          the new ids

        """
        return _pyagrum.UndiGraph_addNodes(self, n)

    def __iter__(self):
      """
      Iterate over the nodes of the graph

      Yield
      -----
      int
        The index of the node
      """
      for i in self.nodes():
        yield i

    def __getstate__(self):
        state=dict()
        if hasattr(self,'arcs'):
            state['arcs']=self.arcs()
        if hasattr(self,'edges'):
          state['edges']=self.edges()
        return state

    def __setstate__(self,state):
        self.__init__()
        if 'arcs' in state:
            for x,y in state['arcs']:
              if not self.existsNode(x):
                self.addNodeWithId(x)
              if not self.existsNode(y):
                self.addNodeWithId(y)
              self.addArc(x,y)
        if 'edges' in state:
            for x,y in state['edges']:
              if not self.existsNode(x):
                self.addNodeWithId(x)
              if not self.existsNode(y):
                self.addNodeWithId(y)
              self.addEdge(x,y)
        return self


    def edges(self) -> set[tuple[int,int]]:
        r"""

        Returns
        -------
        List
          the list of the edges

        """
        return _pyagrum.UndiGraph_edges(self)

    def neighbours(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id : int
            the id of the checked node

        Returns
        -------
        Set
            The set of edges adjacent to the given node

        """
        return _pyagrum.UndiGraph_neighbours(self, id)

    def addNode(self) -> int:
        r"""

        Returns
        -------
        int
          the new NodeId

        """
        return _pyagrum.UndiGraph_addNode(self)

    def addNodeWithId(self, id: int) -> None:
        r"""

        Add a node by choosing a new NodeId.

        Parameters
        ----------
        id : int
          The id of the new node

        Raises
        ------
          pyagrum.DuplicateElement
            If the given id is already used

        """
        return _pyagrum.UndiGraph_addNodeWithId(self, id)

    def existsNode(self, id: int) -> bool:
        r"""

        Check if a node with a certain id exists in the graph.

        Parameters
        ----------
        id : int
            the checked id

        Returns
        -------
        bool
            True if the node exists

        """
        return _pyagrum.UndiGraph_existsNode(self, id)

    def size(self) -> int:
        r"""

        Returns
        -------
        int
            the number of nodes in the graph

        """
        return _pyagrum.UndiGraph_size(self)

    def empty(self) -> bool:
        r"""

        Check if the graph is empty.

        Returns
        -------
        bool
            True if the graph is empty

        """
        return _pyagrum.UndiGraph_empty(self)

    def addEdge(self, *args) -> None:
        r"""

        Insert a new edge into the graph.

        Parameters
        ----------
        n1 : int
          the id of one node of the new inserted edge
        n2 : int
          the id of the other node of the new inserted edge

        Raises
        ------
          pyagrum.InvalidNode
            If n1 or n2 does not belong to the graph nodes.

        """
        return _pyagrum.UndiGraph_addEdge(self, *args)

    def eraseEdge(self, n1: int, n2: int) -> None:
        r"""

        Erase the edge between n1 and n2.

        Parameters
        ----------
        n1 : int
          the id of the tail node
        n2 : int
          the id of the head node

        """
        return _pyagrum.UndiGraph_eraseEdge(self, n1, n2)

    def existsEdge(self, n1: int, n2: int) -> bool:
        r"""

        Check if an edge exists between n1 and n2.

        Parameters
        ----------
        n1 : int
          the id of one extremity of the edge
        n2 : int
          the id of the other extremity if tge edge

        Returns
        -------
        bool
            True if the arc exists

        """
        return _pyagrum.UndiGraph_existsEdge(self, n1, n2)

    def sizeEdges(self) -> int:
        r"""

        Returns
        -------
        int
            the number of edges in the graph

        """
        return _pyagrum.UndiGraph_sizeEdges(self)

    def emptyEdges(self) -> bool:
        r"""

        Check if the graph doesn't contains edges.

        Returns
        -------
        bool
            True if the graph doesn't contains edges

        """
        return _pyagrum.UndiGraph_emptyEdges(self)

    def eraseNeighbours(self, n: int) -> None:
        r"""

        Erase all the edges adjacent to a given node.

        Parameters
        ----------
        n : int
          the id of the node

        """
        return _pyagrum.UndiGraph_eraseNeighbours(self, n)

# Register UndiGraph in _pyagrum:
_pyagrum.UndiGraph_swigregister(UndiGraph)
emptyNodeSet = cvar.emptyNodeSet

class DiGraph(object):
    r"""

    DiGraph represents a Directed Graph.

    DiGraph() -> DiGraph
        default constructor

    DiGraph(src) -> DiGraph
        Parameters:
            - **src** (*pyagrum.DiGraph*) -- the digraph to copy

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args):
        _pyagrum.DiGraph_swiginit(self, _pyagrum.new_DiGraph(*args))
    __swig_destroy__ = _pyagrum.delete_DiGraph

    @staticmethod
    def completeGraph(n: int) -> "pyagrum.DiGraph":
        r"""

        Create a complete directed graph with n nodes.

        Parameters
        ----------
        n : int
            number of nodes

        Returns
        -------
        DiGraph
            directed graph where every ordered pair (i, j) with i≠j has arc i→j, with nodes 0..n-1

        """
        return _pyagrum.DiGraph_completeGraph(n)

    def __eq__(self, g: "DiGraph") -> bool:
        return _pyagrum.DiGraph___eq__(self, g)

    def eraseNode(self, id: int) -> None:
        r"""

        Erase the node and all the related arcs.

        Parameters
        ----------
        id : int
            the id of the node

        """
        return _pyagrum.DiGraph_eraseNode(self, id)

    def clear(self) -> None:
        r"""

        Remove all the nodes and arcs from the graph.

        """
        return _pyagrum.DiGraph_clear(self)

    def toDot(self) -> str:
        r"""

        Returns
        -------
        str
            a friendly display of the graph in DOT format

        """
        return _pyagrum.DiGraph_toDot(self)

    def topologicalOrder(self) -> list[int]:
        r"""

        Returns
        -------
        List
            the list of the nodes Ids in a topological order

        Raises
        ------
        pyagrum.InvalidDirectedCycle
          If this graph contains cycles

        """
        return _pyagrum.DiGraph_topologicalOrder(self)

    def hasDirectedPath(self, _from: int, to: int) -> bool:
        r"""

        Check if a directedpath exists between from and to.

        Parameters
        ----------
        from : int
            the id of the first node of the (possible) path
        to : int
            the id of the last node of the (possible) path

        Returns
        -------
        bool
            True if the directed path exists

        """
        return _pyagrum.DiGraph_hasDirectedPath(self, _from, to)

    def directedPath(self, node1: int, node2: int) -> list[int] | None:
        r"""

        Return a shortest directed path from node1 to node2, or None if no such path exists.

        Parameters
        ----------
        node1 : int
            id of the source node
        node2 : int
            id of the destination node

        Returns
        -------
        list[int] or None
            ordered list of node ids along the directed path, or None if node2 is unreachable from node1

        """
        return _pyagrum.DiGraph_directedPath(self, node1, node2)

    def directedUnorientedPath(self, node1: int, node2: int) -> list[int] | None:
        r"""

        Return a shortest path from node1 to node2 ignoring arc orientation, or None if no path exists.

        Parameters
        ----------
        node1 : int
            id of the source node
        node2 : int
            id of the destination node

        Returns
        -------
        list[int] or None
            ordered list of node ids (arcs may be traversed in either direction), or None if unreachable

        """
        return _pyagrum.DiGraph_directedUnorientedPath(self, node1, node2)

    def family(self, *args) -> list[int]:
        r"""

        Return the family of a node: the node itself plus all its parents.

        Parameters
        ----------
        norid : int
            id of the node

        Returns
        -------
        set[int]
            {norid} ∪ parents(norid)

        """
        return _pyagrum.DiGraph_family(self, *args)

    def connectedComponents(self) -> dict[int,int]:
        r"""

        Returns the weakly connected components of the graph.

        Each node is mapped to the id of its component root (an arbitrarily chosen
        node from the same component).

        Returns
        -------
        dict[int, int]
            mapping node id → component root id

        See Also
        --------
        connectedComponentsList : returns a dict[int, set[int]] grouping nodes by component
        connectedComponentsCount : returns the number of components

        """
        return _pyagrum.DiGraph_connectedComponents(self)

    def __repr__(self) -> str:
        return _pyagrum.DiGraph___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.DiGraph___str__(self)

    def nodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
            the set of ids

        """
        return _pyagrum.DiGraph_nodes(self)

    def connectedComponentsList(self):
      """ connected components as a dict of sets

      Returns
      -------
      dict(int, set[int])
        dict of connected components (as sets of nodeIds) keyed by an arbitrary root nodeId per component.

      """
      cc = self.connectedComponents()
      result = {}
      for node, root in cc.items():
        if root not in result:
          result[root] = set()
        result[root].add(node)
      return result

    def connectedComponentsCount(self):
      """ number of connected components

      Returns
      -------
      int
        the number of connected components in the graph.

      """
      return len(set(self.connectedComponents().values()))

    def adjacencyMatrix(self):
      """ adjacency matrix from a graph/graphical models

      Compute the adjacency matrix of a pyAgrum's graph or graphical models
      (more generally an object that has `nodes`, `children`/`parents` or `neighbours` methods)

      Returns
      -------
      numpy.ndarray
        adjacency matrix (as numpy.ndarray) with nodeId as key.

      """
      nodes=self.nodes()
      n=self.size()
      am=numpy.zeros((n,n)).astype(int)

      for node in nodes:
          if hasattr(self,'children'):
              for children in self.children(node):
                  am[node,children]=1
          if hasattr(self,'neighbours'):
              for neighbour in self.neighbours(node):
                  am[node,neighbour]=1
      return am


    def nameFromId(self, id: int) -> str:
        r"""

        Return the name of a node, or its id as a string if no name was set.

        Parameters
        ----------
        id : int
            the node id

        Returns
        -------
        str
            the name associated with the node, or ``str(id)`` if the node has no name

        """
        return _pyagrum.DiGraph_nameFromId(self, id)

    def idFromName(self, name: str) -> int | None:
        r"""

        Return the id of the node with the given name, or None if no such name exists.

        Parameters
        ----------
        name : str
            the name to look up

        Returns
        -------
        int or None
            the node id, or None if the name is not found

        """
        return _pyagrum.DiGraph_idFromName(self, name)

    def setName(self, id: int, name: str) -> None:
        r"""

        Assign a name to a node.

        If the node already has a name, it is replaced. The name must not already be
        used by another node.

        Parameters
        ----------
        id : int
            the node id
        name : str
            the name to assign

        Raises
        ------
        pyagrum.InvalidNode
            If the node does not exist.
        pyagrum.DuplicateElement
            If the name is already used by a different node.

        """
        return _pyagrum.DiGraph_setName(self, id, name)

    def hasName(self, id: int) -> bool:
        r"""

        Check whether a node has an explicitly assigned name.

        Parameters
        ----------
        id : int
            the node id

        Returns
        -------
        bool
            True if a name has been assigned to this node via setName

        """
        return _pyagrum.DiGraph_hasName(self, id)

    def addNodes(self, n: int) -> set[int]:
        r"""

        Add a set of n nodes.

        Parameters
        ----------
        n : int
          the number of nodes to add.

        Returns
        -------
        Set of int
          the new ids

        """
        return _pyagrum.DiGraph_addNodes(self, n)

    def __iter__(self):
      """
      Iterate over the nodes of the graph

      Yield
      -----
      int
        The index of the node
      """
      for i in self.nodes():
        yield i

    def __getstate__(self):
        state=dict()
        if hasattr(self,'arcs'):
            state['arcs']=self.arcs()
        if hasattr(self,'edges'):
          state['edges']=self.edges()
        return state

    def __setstate__(self,state):
        self.__init__()
        if 'arcs' in state:
            for x,y in state['arcs']:
              if not self.existsNode(x):
                self.addNodeWithId(x)
              if not self.existsNode(y):
                self.addNodeWithId(y)
              self.addArc(x,y)
        if 'edges' in state:
            for x,y in state['edges']:
              if not self.existsNode(x):
                self.addNodeWithId(x)
              if not self.existsNode(y):
                self.addNodeWithId(y)
              self.addEdge(x,y)
        return self


    def arcs(self) -> set[tuple[int,int]]:
        r"""

        Returns the set of arcs in the graph.

        Returns
        -------
        Set
            the set of the arcs

        """
        return _pyagrum.DiGraph_arcs(self)

    def parents(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id :
            The id of the child node

        Returns
        -------
        Set
            the set of the parents ids.

        """
        return _pyagrum.DiGraph_parents(self, id)

    def children(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id : int
          the id of the parent

        Returns
        -------
        Set
            the set of all the children

        """
        return _pyagrum.DiGraph_children(self, id)

    def descendants(self, *args) -> list[int]:
        r"""

        give the set of nodeid of descendants of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the descendants of node `norid`.

        """
        return _pyagrum.DiGraph_descendants(self, *args)

    def ancestors(self, *args) -> list[int]:
        r"""

        give the set of nodeid of ancestors of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the ancestors of node `norid`.

        """
        return _pyagrum.DiGraph_ancestors(self, *args)

    def addNode(self) -> int:
        r"""

        Returns
        -------
        int
          the new NodeId

        """
        return _pyagrum.DiGraph_addNode(self)

    def addNodeWithId(self, id: int) -> None:
        r"""

        Add a node by choosing a new NodeId.

        Parameters
        ----------
        id : int
          The id of the new node

        Raises
        ------
          pyagrum.DuplicateElement
          If the given id is already used

        """
        return _pyagrum.DiGraph_addNodeWithId(self, id)

    def existsNode(self, id: int) -> bool:
        r"""

        Check if a node with a certain id exists in the graph.

        Parameters
        ----------
        id : int
            the checked id

        Returns
        -------
        bool
            True if the node exists

        """
        return _pyagrum.DiGraph_existsNode(self, id)

    def size(self) -> int:
        r"""

        Returns
        -------
        int
            the number of nodes in the graph

        """
        return _pyagrum.DiGraph_size(self)

    def empty(self) -> bool:
        r"""

        Check if the graph is empty.

        Returns
        -------
        bool
            True if the graph is empty

        """
        return _pyagrum.DiGraph_empty(self)

    def addArc(self, *args) -> None:
        r"""

        Add an arc from tail to head.

        Parameters
        ----------
        tail : int
          the id of the tail node
        head : int
          the id of the head node

        Raises
        ------
          pyagrum.InvalidNode
            If head or tail does not belong to the graph nodes.

        """
        return _pyagrum.DiGraph_addArc(self, *args)

    def eraseArc(self, n1: int, n2: int) -> None:
        r"""

        Erase the arc between n1 and n2.

        Parameters
        ----------
        n1 : int
            the id of the tail node
        n2 : int
            the id of the head node

        """
        return _pyagrum.DiGraph_eraseArc(self, n1, n2)

    def existsArc(self, n1: int, n2: int) -> bool:
        r"""

        Check if an arc exists between n1 and n2.

        Parameters
        ----------
        n1 : int
            the id of the tail node
        n2 : int
            the id of the head node

        Returns
        -------
        bool
            True if the arc exists

        """
        return _pyagrum.DiGraph_existsArc(self, n1, n2)

    def eraseParents(self, n: int) -> None:
        r"""

        Erase the arcs coming to the node.

        Parameters
        ----------
        n : int
            the id of the child node

        """
        return _pyagrum.DiGraph_eraseParents(self, n)

    def eraseChildren(self, n: int) -> None:
        r"""

        Erase the arcs heading through the node's children.

        Parameters
        ----------
        n : int
            the id of the parent node

        """
        return _pyagrum.DiGraph_eraseChildren(self, n)

    def sizeArcs(self) -> int:
        r"""

        Returns
        -------
        int
            the number of arcs in the graph

        """
        return _pyagrum.DiGraph_sizeArcs(self)

    def emptyArcs(self) -> bool:
        r"""

        Check if the graph doesn't contains arcs.

        Returns
        -------
        bool
            True if the graph doesn't contains arcs

        """
        return _pyagrum.DiGraph_emptyArcs(self)

# Register DiGraph in _pyagrum:
_pyagrum.DiGraph_swigregister(DiGraph)
class DAG(DiGraph):
    r"""

    DAG represents a Directed Graph.

    DAG() -> DAG
        default constructor

    DAG(src) -> DAG
        Parameters:
            - **src** (*pyagrum.DAG*) -- the DAG to copy

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args):
        _pyagrum.DAG_swiginit(self, _pyagrum.new_DAG(*args))
    __swig_destroy__ = _pyagrum.delete_DAG

    def moralGraph(self) -> "pyagrum.UndiGraph":
        r"""

        Returns the moral graph of the DAG, formed by adding edges between all pairs of nodes that have a common child, and then making all edges in the graph undirected.

        Returns
        -------
        pyagrum.UndiGraph
            The moral graph

        """
        return _pyagrum.DAG_moralGraph(self)

    def moralizedAncestralGraph(self, nodes: list[int]) -> "pyagrum.UndiGraph":
        r"""

        Compute the moralized ancestral graph of the nodes from the DAG.

        Parameters
        ----------
        nodes : int | sequence of int
          a sequence of node ids (int) or a single node id (int)
        Returns
        -------
        pyagrum.UndiGraph
          the moralized ancestral graph of the nodes from the DAG.

        """
        return _pyagrum.DAG_moralizedAncestralGraph(self, nodes)

    def minimalCondSet(self, *args) -> list[int]:
        r"""

        Return a minimal conditioning set of a target given source nodes in the DAG.

        Parameters
        ----------
        target : int | str | list[int|str]
            the target node id(s) or name(s)
        soids : list[int|str]
            the list of source node ids or names

        Returns
        -------
        set[int]
            the minimal conditioning set (as node ids)

        """
        return _pyagrum.DAG_minimalCondSet(self, *args)

    def __repr__(self) -> str:
        return _pyagrum.DAG___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.DAG___str__(self)

    def dSeparation(self, *args) -> bool:
        r"""

        Check if the sets of nodes X and Y are d-separated (by the set of nodes Z if given) in the DAG.

        Parameters
        ----------
        X : int | sequence of int
          a sequence of node ids (int) or a single node id (int)
        Y : int | sequence of int
          a sequence of node ids (int) or a single node id (int)
        Z : int | sequence of int (optional)
          a sequence of node ids (int) or a single node id (int)
        Returns
        -------
        bool
          True if X and Y are d-separated (by Z if given), False otherwise.

        """
        return _pyagrum.DAG_dSeparation(self, *args)

    def addNodes(self, n: int) -> set[int]:
        r"""

        Add a set of n nodes.

        Parameters
        ----------
        n : int
          the number of nodes to add.

        Returns
        -------
        Set of int
          the new ids

        """
        return _pyagrum.DAG_addNodes(self, n)

    def __iter__(self):
      """
      Iterate over the nodes of the graph

      Yield
      -----
      int
        The index of the node
      """
      for i in self.nodes():
        yield i

    def __getstate__(self):
        state=dict()
        if hasattr(self,'arcs'):
            state['arcs']=self.arcs()
        if hasattr(self,'edges'):
          state['edges']=self.edges()
        return state

    def __setstate__(self,state):
        self.__init__()
        if 'arcs' in state:
            for x,y in state['arcs']:
              if not self.existsNode(x):
                self.addNodeWithId(x)
              if not self.existsNode(y):
                self.addNodeWithId(y)
              self.addArc(x,y)
        if 'edges' in state:
            for x,y in state['edges']:
              if not self.existsNode(x):
                self.addNodeWithId(x)
              if not self.existsNode(y):
                self.addNodeWithId(y)
              self.addEdge(x,y)
        return self


    def arcs(self) -> set[tuple[int,int]]:
        r"""

        Returns the set of arcs in the graph.

        Returns
        -------
        Set
            the set of the arcs

        """
        return _pyagrum.DAG_arcs(self)

    def parents(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id : int
            The id of the child node

        Returns
        -------
        Set
            the set of the parents ids.

        """
        return _pyagrum.DAG_parents(self, id)

    def children(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id : int
          the id of the parent

        Returns
        -------
        Set
            the set of all the children

        """
        return _pyagrum.DAG_children(self, id)

    def descendants(self, id: int) -> list[int]:
        r"""

        give the set of nodeid of descendants of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the descendants of node `norid`.

        """
        return _pyagrum.DAG_descendants(self, id)

    def ancestors(self, id: int) -> list[int]:
        r"""

        give the set of nodeid of ancestors of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the ancestors of node `norid`.

        """
        return _pyagrum.DAG_ancestors(self, id)

    def addArc(self, *args) -> None:
        r"""

        Add an arc from tail to head.

        Parameters
        ----------
        tail : int
          the id of the tail node
        head : int
          the id of the head node

        Raises
        ------
          pyagrum.InvalidNode
            If head or tail does not belong to the graph nodes.
          pyagrum.CycleDetected
            If a cycle is detected


        """
        return _pyagrum.DAG_addArc(self, *args)

    def eraseArc(self, n1: int, n2: int) -> None:
        r"""

        Erase the arc between n1 and n2.

        Parameters
        ----------
        n1 : int
            the id of the tail node
        n2 : int
            the id of the head node

        """
        return _pyagrum.DAG_eraseArc(self, n1, n2)

    def existsArc(self, n1: int, n2: int) -> bool:
        r"""

        Check if an arc exists between n1 and n2.

        Parameters
        ----------
        n1 : int
            the id of the tail node
        n2 : int
            the id of the head node

        Returns
        -------
        bool
            True if the arc exists

        """
        return _pyagrum.DAG_existsArc(self, n1, n2)

    def eraseParents(self, n: int) -> None:
        r"""

        Erase the arcs coming to the node.

        Parameters
        ----------
        n : int
            the id of the child node

        """
        return _pyagrum.DAG_eraseParents(self, n)

    def eraseChildren(self, n: int) -> None:
        r"""

        Erase the arcs heading to the node's children.

        Parameters
        ----------
        n : int
            the id of the parent node

        """
        return _pyagrum.DAG_eraseChildren(self, n)

    def sizeArcs(self) -> int:
        r"""

        Returns
        -------
        int
            the number of arcs in the graph

        """
        return _pyagrum.DAG_sizeArcs(self)

    def emptyArcs(self) -> bool:
        r"""

        Check if the graph doesn't contains arcs.

        Returns
        -------
        bool
            True if the graph doesn't contains arcs

        """
        return _pyagrum.DAG_emptyArcs(self)

# Register DAG in _pyagrum:
_pyagrum.DAG_swigregister(DAG)
class MixedGraph(UndiGraph, DiGraph):
    r"""

    MixedGraph represents a graph with both arcs and edges.

    MixedGraph() -> MixedGraph
        default constructor

    MixedGraph(src) -> MixedGraph
        Parameters:
            - **src** (*pyagrum.MixedGraph*) --the MixedGraph to copy

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args):
        _pyagrum.MixedGraph_swiginit(self, _pyagrum.new_MixedGraph(*args))
    __swig_destroy__ = _pyagrum.delete_MixedGraph

    def __eq__(self, g: "MixedGraph") -> bool:
        return _pyagrum.MixedGraph___eq__(self, g)

    def eraseNode(self, node: int) -> None:
        r"""

        Erase the node and all the related arcs and edges.

        Parameters
        ----------
        id : int
            the id of the node

        """
        return _pyagrum.MixedGraph_eraseNode(self, node)

    def clear(self) -> None:
        r"""

        Remove all the nodes and edges from the graph.

        """
        return _pyagrum.MixedGraph_clear(self)

    def hasMixedOrientedPath(self, node1: int, node2: int) -> bool:
        r"""

        Check if there is an oriented path from node1 to node2 in the mixed graph (following arc directions).

        Parameters
        ----------
        node1 : int
            the id of the start node
        node2 : int
            the id of the end node

        Returns
        -------
        bool
            True if such a path exists

        """
        return _pyagrum.MixedGraph_hasMixedOrientedPath(self, node1, node2)

    def toDot(self) -> str:
        r"""

        Returns
        -------
        str
            a friendly display of the graph in DOT format

        """
        return _pyagrum.MixedGraph_toDot(self)

    def chainComponent(self, node: int) -> list[int]:
        r"""

        Return the chain component containing a given node.

        The chain component of a node in a mixed graph is the set of nodes reachable via undirected edges from that node.

        Parameters
        ----------
        id : int
            the id of the node

        Returns
        -------
        set[int]
            the set of node ids in the same chain component

        """
        return _pyagrum.MixedGraph_chainComponent(self, node)

    def chainComponents(self) -> dict[int,int]:
        r"""

        Return the chain components of the graph.

        Each node is mapped to the id of its component root (an arbitrarily chosen node from the same component).

        Returns
        -------
        dict[int, int]
            mapping node id → component root id

        """
        return _pyagrum.MixedGraph_chainComponents(self)

    def connectedComponents(self) -> dict[int,int]:
        r"""

        Returns the weakly connected components of the mixed graph (following both
        arcs and undirected edges in both directions).

        Each node is mapped to the id of its component root (an arbitrarily chosen
        node from the same component).

        Returns
        -------
        dict[int, int]
            mapping node id → component root id

        See Also
        --------
        connectedComponentsList : returns a dict[int, set[int]] grouping nodes by component
        connectedComponentsCount : returns the number of components

        """
        return _pyagrum.MixedGraph_connectedComponents(self)

    def __repr__(self) -> str:
        return _pyagrum.MixedGraph___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.MixedGraph___str__(self)

    def addNodes(self, n: int) -> set[int]:
        r"""

        Add n nodes.

        Parameters
        ----------
        n : int
          the number of nodes to add.

        Returns
        -------
        Set of int
          the new ids

        """
        return _pyagrum.MixedGraph_addNodes(self, n)

    def __iter__(self):
      """
      Iterate over the nodes of the graph

      Yield
      -----
      int
        The index of the node
      """
      for i in self.nodes():
        yield i

    def __getstate__(self):
        state=dict()
        if hasattr(self,'arcs'):
            state['arcs']=self.arcs()
        if hasattr(self,'edges'):
          state['edges']=self.edges()
        return state

    def __setstate__(self,state):
        self.__init__()
        if 'arcs' in state:
            for x,y in state['arcs']:
              if not self.existsNode(x):
                self.addNodeWithId(x)
              if not self.existsNode(y):
                self.addNodeWithId(y)
              self.addArc(x,y)
        if 'edges' in state:
            for x,y in state['edges']:
              if not self.existsNode(x):
                self.addNodeWithId(x)
              if not self.existsNode(y):
                self.addNodeWithId(y)
              self.addEdge(x,y)
        return self


    def arcs(self) -> set[tuple[int,int]]:
        r"""

        Returns the set of arcs in the graph.

        Returns
        -------
        set
            the set of the arcs

        """
        return _pyagrum.MixedGraph_arcs(self)

    def parents(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id : int
            the id of the child node

        Returns
        -------
        Set
            the set of parent node ids

        """
        return _pyagrum.MixedGraph_parents(self, id)

    def children(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id : int
            the id of the parent node

        Returns
        -------
        Set
            the set of all the children ids

        """
        return _pyagrum.MixedGraph_children(self, id)

    def descendants(self, id: int) -> list[int]:
        r"""

        give the set of nodeid of descendants of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the descendants of node `norid`.

        """
        return _pyagrum.MixedGraph_descendants(self, id)

    def ancestors(self, id: int) -> list[int]:
        r"""

        give the set of nodeid of ancestors of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the ancestors of node `norid`.

        """
        return _pyagrum.MixedGraph_ancestors(self, id)

    def edges(self) -> set[tuple[int,int]]:
        r"""

        Returns
        -------
        List
            the list of the edges

        """
        return _pyagrum.MixedGraph_edges(self)

    def neighbours(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id : int
            the id of the checked node

        Returns
        -------
        Set
            the set of node ids linked to the given node by an edge

        """
        return _pyagrum.MixedGraph_neighbours(self, id)

    def boundary(self, id: int) -> list[int]:
        r"""

        Boundary are neighbours (not oriented), children and parents

        Parameters
        ----------
        id : int
            the id of the node

        Returns
        -------
        set
            the set of node ids.

        """
        return _pyagrum.MixedGraph_boundary(self, id)

    def mixedOrientedPath(self, node1: int, node2: int) -> list[int] | None:
        r"""

        Parameters
        ----------
        node1 : int
            the id form which the path begins
        node2 : int
            the id to witch the path ends

        Returns
        -------
        List
             a path from node1 to node2, using edges and/or arcs (following the direction of the arcs). If no path is found, the returned list is empty.

        """
        return _pyagrum.MixedGraph_mixedOrientedPath(self, node1, node2)

    def mixedUnorientedPath(self, node1: int, node2: int) -> list[int] | None:
        r"""

        Parameters
        ----------
        node1 : int
            the id from which the path begins
        node2 : int
            the id to which the path ends

        Returns
        -------
        List
             a path from node1 to node2, using edges and/or arcs (not necessarily following the direction of the arcs). If no path is found, the list is empty.


        """
        return _pyagrum.MixedGraph_mixedUnorientedPath(self, node1, node2)

    def connectedComponentsList(self):
      """ connected components as a dict of sets

      Returns
      -------
      dict(int, set[int])
        dict of connected components (as sets of nodeIds) keyed by an arbitrary root nodeId per component.

      """
      cc = self.connectedComponents()
      result = {}
      for node, root in cc.items():
        if root not in result:
          result[root] = set()
        result[root].add(node)
      return result

    def connectedComponentsCount(self):
      """ number of connected components

      Returns
      -------
      int
        the number of connected components in the graph.

      """
      return len(set(self.connectedComponents().values()))


    def addNode(self) -> int:
        r"""

        Returns
        -------
        int
          the new NodeId

        """
        return _pyagrum.MixedGraph_addNode(self)

    def addNodeWithId(self, id: int) -> None:
        r"""

        Add a node by choosing a new NodeId.

        Parameters
        ----------
        id : int
          The id of the new node

        Raises
        ------
          pyagrum.DuplicateElement
            If the given id is already used

        """
        return _pyagrum.MixedGraph_addNodeWithId(self, id)

    def existsNode(self, id: int) -> bool:
        r"""

        Check if a node with a certain id exists in the graph.

        Parameters
        ----------
        id : int
            the checked id

        Returns
        -------
        bool
            True if the node exists

        """
        return _pyagrum.MixedGraph_existsNode(self, id)

    def size(self) -> int:
        r"""

        Returns
        -------
        int
            the number of nodes in the graph

        """
        return _pyagrum.MixedGraph_size(self)

    def empty(self) -> bool:
        r"""

        Check if the graph is empty.

        Returns
        -------
        bool
            True if the graph is empty

        """
        return _pyagrum.MixedGraph_empty(self)

    def addEdge(self, n1: int, n2: int) -> None:
        r"""

        Insert a new edge into the graph.

        Parameters
        ----------
        n1 : int
          the id of one node of the new inserted edge
        n2 : int
          the id of the other node of the new inserted edge

        Raises
        ------
          pyagrum.InvalidNode
            If n1 or n2 does not belong to the graph nodes.

        """
        return _pyagrum.MixedGraph_addEdge(self, n1, n2)

    def eraseEdge(self, n1: int, n2: int) -> None:
        r"""

        Erase the edge between n1 and n2.

        Parameters
        ----------
        n1 : int
          the id of the tail node
        n2 : int
          the id of the head node

        """
        return _pyagrum.MixedGraph_eraseEdge(self, n1, n2)

    def existsEdge(self, n1: int, n2: int) -> bool:
        r"""

        Check if an edge exists between n1 and n2.

        Parameters
        ----------
        n1 : int
          the id of one extremity of the edge
        n2 : int
          the id of the other extremity if tge edge

        Returns
        -------
        bool
            True if the arc exists

        """
        return _pyagrum.MixedGraph_existsEdge(self, n1, n2)

    def sizeEdges(self) -> int:
        r"""

        Returns
        -------
        int
            the number of edges in the graph

        """
        return _pyagrum.MixedGraph_sizeEdges(self)

    def emptyEdges(self) -> bool:
        r"""

        Check if the graph doesn't contains edges.

        Returns
        -------
        bool
            True if the graph doesn't contains edges

        """
        return _pyagrum.MixedGraph_emptyEdges(self)

    def eraseNeighbours(self, n: int) -> None:
        r"""

        Erase all the edges adjacent to a given node.

        Parameters
        ----------
        n : int
          the id of the node

        """
        return _pyagrum.MixedGraph_eraseNeighbours(self, n)

    def addArc(self, n1: int, n2: int) -> None:
        r"""

        Add an arc from tail to head.

        Parameters
        ----------
        tail : int
            the id of the tail node
        head : int
            the id of the head node

        Raises
        ------
        pyagrum.InvalidNode
            If head or tail does not belong to the graph nodes.

        """
        return _pyagrum.MixedGraph_addArc(self, n1, n2)

    def eraseArc(self, n1: int, n2: int) -> None:
        r"""

        Erase the arc between n1 and n2.

        Parameters
        ----------
        n1 : int
            the id of the tail node
        n2 : int
            the id of the head node

        """
        return _pyagrum.MixedGraph_eraseArc(self, n1, n2)

    def existsArc(self, n1: int, n2: int) -> bool:
        r"""

        Check if an arc exists between n1 and n2.

        Parameters
        ----------
        n1 : int
            the id of the tail node
        n2 : int
            the id of the head node

        Returns
        -------
        bool
            True if the arc exists

        """
        return _pyagrum.MixedGraph_existsArc(self, n1, n2)

    def eraseParents(self, n: int) -> None:
        r"""

        Erase the arcs coming to the node.

        Parameters
        ----------
        n : int
            the id of the child node

        """
        return _pyagrum.MixedGraph_eraseParents(self, n)

    def eraseChildren(self, n: int) -> None:
        r"""

        Erase the arcs heading through the node's children.

        Parameters
        ----------
        n : int
            the id of the parent node

        """
        return _pyagrum.MixedGraph_eraseChildren(self, n)

    def sizeArcs(self) -> int:
        r"""

        Returns
        -------
        int
            the number of arcs in the graph

        """
        return _pyagrum.MixedGraph_sizeArcs(self)

    def emptyArcs(self) -> bool:
        r"""

        Check if the graph doesn't contains arcs.

        Returns
        -------
        bool
            True if the graph doesn't contains arcs

        """
        return _pyagrum.MixedGraph_emptyArcs(self)

# Register MixedGraph in _pyagrum:
_pyagrum.MixedGraph_swigregister(MixedGraph)
class PDAG(MixedGraph):
    r"""

    PDAG represents a graph with both arcs and edges.

    PDAG() -> PDAG
        default constructor

    PDAG(src) -> PDAG
        Parameters:
            - **src** (*pyagrum.PDAG*) --the PDAG to copy

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args):
        _pyagrum.PDAG_swiginit(self, _pyagrum.new_PDAG(*args))
    __swig_destroy__ = _pyagrum.delete_PDAG

    def moralGraph(self) -> "pyagrum.UndiGraph":
        r"""

        Returns the moral graph of the PDAG, formed by adding edges between all pairs of nodes that have a common child, and then making all edges in the graph undirected.

        Returns
        -------
        pyagrum.UndiGraph
            The moral graph

        """
        return _pyagrum.PDAG_moralGraph(self)

    def moralizedAncestralGraph(self, nodes: list[int]) -> "pyagrum.UndiGraph":
        r"""

        Compute the moralized ancestral graph of the nodes from the DAG.

        Parameters
        ----------
        nodes : int | sequence of int
          a sequence of node ids (int) or a single node id (int)
        Returns
        -------
        pyagrum.UndiGraph
          the moralized ancestral graph of the nodes from the DAG.

        """
        return _pyagrum.PDAG_moralizedAncestralGraph(self, nodes)

    def hasMixedReallyOrientedPath(self, n1: int, n2: int) -> bool:
        r"""

        Check if there is a strictly oriented path from node1 to node2 (all arcs, no edges).

        Parameters
        ----------
        node1 : int
            the start node id
        node2 : int
            the end node id

        Returns
        -------
        bool
            True if such a path exists

        """
        return _pyagrum.PDAG_hasMixedReallyOrientedPath(self, n1, n2)

    def toDot(self) -> str:
        r"""

        Returns
        -------
        str
            a friendly display of the graph in DOT format

        """
        return _pyagrum.PDAG_toDot(self)

    def __repr__(self) -> str:
        return _pyagrum.PDAG___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.PDAG___str__(self)

    def cSeparation(self, *args) -> bool:
        r"""

        Check if the sets of nodes X and Y are c-separated (by the set of nodes Z if given) in the PDAG.

        Parameters
        ----------
        X : int | sequence of int
          a sequence of node ids (int) or a single node id (int)
        Y : int | sequence of int
          a sequence of node ids (int) or a single node id (int)
        Z : int | sequence of int (optional)
          a sequence of node ids (int) or a single node id (int)
        Returns
        -------
        bool
          True if X and Y are c-separated (by Z if given), False otherwise.

        """
        return _pyagrum.PDAG_cSeparation(self, *args)

    def addNodes(self, n: int) -> set[int]:
        r"""

        Add n new nodes to the graph.

        Parameters
        ----------
        n : int
            the number of nodes to add

        """
        return _pyagrum.PDAG_addNodes(self, n)

    def __iter__(self):
      """
      Iterate over the nodes of the graph

      Yield
      -----
      int
        The index of the node
      """
      for i in self.nodes():
        yield i

    def __getstate__(self):
        state=dict()
        if hasattr(self,'arcs'):
            state['arcs']=self.arcs()
        if hasattr(self,'edges'):
          state['edges']=self.edges()
        return state

    def __setstate__(self,state):
        self.__init__()
        if 'arcs' in state:
            for x,y in state['arcs']:
              if not self.existsNode(x):
                self.addNodeWithId(x)
              if not self.existsNode(y):
                self.addNodeWithId(y)
              self.addArc(x,y)
        if 'edges' in state:
            for x,y in state['edges']:
              if not self.existsNode(x):
                self.addNodeWithId(x)
              if not self.existsNode(y):
                self.addNodeWithId(y)
              self.addEdge(x,y)
        return self


    def arcs(self) -> set[tuple[int,int]]:
        r"""

        Returns the set of arcs in the graph.

        Returns
        -------
        set
            the set of the arcs

        """
        return _pyagrum.PDAG_arcs(self)

    def parents(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id : int
            the id of the child node

        Returns
        -------
        Set
            the set of parent node ids

        """
        return _pyagrum.PDAG_parents(self, id)

    def children(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id : int
            the id of the parent node

        Returns
        -------
        Set
            the set of all children ids

        """
        return _pyagrum.PDAG_children(self, id)

    def descendants(self, id: int) -> list[int]:
        r"""

        give the set of nodeid of descendants of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the descendants of node `norid`.

        """
        return _pyagrum.PDAG_descendants(self, id)

    def ancestors(self, id: int) -> list[int]:
        r"""

        give the set of nodeid of ancestors of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the ancestors of node `norid`.

        """
        return _pyagrum.PDAG_ancestors(self, id)

    def edges(self) -> set[tuple[int,int]]:
        r"""

        Returns
        -------
        List
            the list of the edges

        """
        return _pyagrum.PDAG_edges(self)

    def neighbours(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id : int
            the id of the checked node

        Returns
        -------
        Set
            the set of node ids linked by an edge to the given node

        """
        return _pyagrum.PDAG_neighbours(self, id)

    def boundary(self, id: int) -> list[int]:
        r"""

        Boundary of a node: neighbours (via edges), children, and parents.

        Parameters
        ----------
        id : int
            the id of the node

        Returns
        -------
        set
            the set of adjacent node ids

        """
        return _pyagrum.PDAG_boundary(self, id)

    def mixedOrientedPath(self, node1: int, node2: int) -> list[int] | None:
        r"""

        Parameters
        ----------
        node1 : int
            the id form which the path begins
        node2 : int
            the id to witch the path ends

        Returns
        -------
        List
             a path from node1 to node2, using edges and/or arcs (following the direction of the arcs). If no path is found, the returned list is empty.

        """
        return _pyagrum.PDAG_mixedOrientedPath(self, node1, node2)

    def mixedUnorientedPath(self, node1: int, node2: int) -> list[int] | None:
        r"""

        Parameters
        ----------
        node1 : int
            the id from which the path begins
        node2 : int
            the id to which the path ends

        Returns
        -------
        List
             a path from node1 to node2, using edges and/or arcs (not necessarily following the direction of the arcs). If no path is found, the list is empty.


        """
        return _pyagrum.PDAG_mixedUnorientedPath(self, node1, node2)

    def connectedComponentsList(self):
      """ connected components as a dict of sets

      Returns
      -------
      dict(int, set[int])
        dict of connected components (as sets of nodeIds) keyed by an arbitrary root nodeId per component.

      """
      cc = self.connectedComponents()
      result = {}
      for node, root in cc.items():
        if root not in result:
          result[root] = set()
        result[root].add(node)
      return result

    def connectedComponentsCount(self):
      """ number of connected components

      Returns
      -------
      int
        the number of connected components in the graph.

      """
      return len(set(self.connectedComponents().values()))


    def addNode(self) -> int:
        r"""

        Add a new node to the graph and return its id.

        Returns
        -------
        int
            the id of the new node

        """
        return _pyagrum.PDAG_addNode(self)

    def addNodeWithId(self, id: int) -> None:
        r"""

        Add a node with a specific id.

        Parameters
        ----------
        id : int
            the id of the new node

        Raises
        ------
        pyagrum.DuplicateElement
            if a node with this id already exists

        """
        return _pyagrum.PDAG_addNodeWithId(self, id)

    def existsNode(self, id: int) -> bool:
        r"""

        Check whether a node exists.

        Parameters
        ----------
        id : int
            the node id to check

        Returns
        -------
        bool
            True if the node exists

        """
        return _pyagrum.PDAG_existsNode(self, id)

    def size(self) -> int:
        r"""

        Returns
        -------
        int
            the number of nodes in the graph

        """
        return _pyagrum.PDAG_size(self)

    def empty(self) -> bool:
        r"""

        Check if the graph has no nodes.

        Returns
        -------
        bool
            True if there are no nodes in the graph

        """
        return _pyagrum.PDAG_empty(self)

    def addEdge(self, *args) -> None:
        r"""

        Insert a new edge into the graph.

        Parameters
        ----------
        n1 : int
          the id of one node of the new inserted edge
        n2 : int
          the id of the other node of the new inserted edge

        Raises
        ------
          pyagrum.InvalidNode
            If n1 or n2 does not belong to the graph nodes.

        """
        return _pyagrum.PDAG_addEdge(self, *args)

    def eraseEdge(self, n1: int, n2: int) -> None:
        r"""

        Remove an edge from the graph.

        Parameters
        ----------
        n1 : int
            one endpoint of the edge
        n2 : int
            the other endpoint

        Raises
        ------
        pyagrum.InvalidEdge
            if the edge does not exist

        """
        return _pyagrum.PDAG_eraseEdge(self, n1, n2)

    def existsEdge(self, n1: int, n2: int) -> bool:
        r"""

        Check whether an edge exists between two nodes.

        Parameters
        ----------
        n1 : int
            one endpoint
        n2 : int
            the other endpoint

        Returns
        -------
        bool
            True if the edge exists

        """
        return _pyagrum.PDAG_existsEdge(self, n1, n2)

    def sizeEdges(self) -> int:
        r"""

        Returns
        -------
        int
            the number of edges in the graph

        """
        return _pyagrum.PDAG_sizeEdges(self)

    def emptyEdges(self) -> bool:
        r"""

        Check if the graph has no edges.

        Returns
        -------
        bool
            True if the graph contains no edges

        """
        return _pyagrum.PDAG_emptyEdges(self)

    def eraseNeighbours(self, n: int) -> None:
        r"""

        Erase all edges adjacent to a given node.

        Parameters
        ----------
        id : int
            the id of the node

        """
        return _pyagrum.PDAG_eraseNeighbours(self, n)

    def addArc(self, *args) -> None:
        r"""

        Add an arc from tail to head.

        Parameters
        ----------
        tail : int
          the id of the tail node
        head : int
          the id of the head node

        Raises
        ------
          pyagrum.InvalidNode
            If head or tail does not belong to the graph nodes.

          PyAgrum.InvalidDirectedCycle
            if the arc would create a (mixed) cycle.

        """
        return _pyagrum.PDAG_addArc(self, *args)

    def eraseArc(self, n1: int, n2: int) -> None:
        r"""

        Remove an arc from the graph.

        Parameters
        ----------
        tail : int
            the id of the tail node
        head : int
            the id of the head node

        Raises
        ------
        pyagrum.InvalidArc
            if the arc does not exist

        """
        return _pyagrum.PDAG_eraseArc(self, n1, n2)

    def existsArc(self, n1: int, n2: int) -> bool:
        r"""

        Check whether an arc exists between two nodes.

        Parameters
        ----------
        tail : int
            the id of the tail node
        head : int
            the id of the head node

        Returns
        -------
        bool
            True if the arc (tail, head) exists

        """
        return _pyagrum.PDAG_existsArc(self, n1, n2)

    def eraseParents(self, n: int) -> None:
        r"""

        Erase all arcs incoming to a node from its parents.

        Parameters
        ----------
        id : int
            the id of the node

        """
        return _pyagrum.PDAG_eraseParents(self, n)

    def eraseChildren(self, n: int) -> None:
        r"""

        Erase all arcs from a node to its children.

        Parameters
        ----------
        id : int
            the id of the node

        """
        return _pyagrum.PDAG_eraseChildren(self, n)

    def sizeArcs(self) -> int:
        r"""

        Returns
        -------
        int
            the number of arcs in the graph

        """
        return _pyagrum.PDAG_sizeArcs(self)

    def emptyArcs(self) -> bool:
        r"""

        Check if the graph has no arcs.

        Returns
        -------
        bool
            True if the graph contains no arcs

        """
        return _pyagrum.PDAG_emptyArcs(self)

# Register PDAG in _pyagrum:
_pyagrum.PDAG_swigregister(PDAG)
EdgeMark_Circle = _pyagrum.EdgeMark_Circle
EdgeMark_Tail = _pyagrum.EdgeMark_Tail
EdgeMark_Arrowhead = _pyagrum.EdgeMark_Arrowhead
class PAG(UndiGraph):
    r"""

    PAG represents a Partial Ancestral Graph, the output of the FCI algorithm.

    A PAG is an undirected graph whose edges carry endpoint marks. Each edge
    between nodes x and y has two marks, one at each endpoint:

    - ``EdgeMark_Circle`` (o): uncertain endpoint
    - ``EdgeMark_Tail`` (-): definite non-ancestor (tail)
    - ``EdgeMark_Arrowhead`` (>): definite ancestor (arrowhead)

    PAG() -> PAG
        default constructor

    PAG(src) -> PAG
        Parameters:
            - **src** (*pyagrum.PAG*) -- the PAG to copy

    Typically obtained via ``BNLearner.learnPAG()`` after calling ``useFCI()``.

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.PAG_swiginit(self, _pyagrum.new_PAG(*args))
    __swig_destroy__ = _pyagrum.delete_PAG

    def clearEdges(self) -> None:
        r"""

        Remove all edges and their endpoint marks from the PAG.

        Nodes are preserved; only edges and their associated marks are removed.

        """
        return _pyagrum.PAG_clearEdges(self)

    def eraseNode(self, id: int) -> None:
        r"""

        Erase the node and all the adjacent edges.

        Parameters
        ----------
        id : int
          the id of the node

        """
        return _pyagrum.PAG_eraseNode(self, id)

    def clear(self) -> None:
        r"""

        Remove all the nodes and edges from the graph.

        """
        return _pyagrum.PAG_clear(self)

    def isArrowhead(self, src: int, dst: int) -> bool:
        r"""

        Return True if the mark at dst (seen from src) is an arrowhead.

        Parameters
        ----------
        src : int
        dst : int

        Returns
        -------
        bool

        """
        return _pyagrum.PAG_isArrowhead(self, src, dst)

    def isTail(self, src: int, dst: int) -> bool:
        r"""

        Return True if the mark at dst (seen from src) is a tail.

        Parameters
        ----------
        src : int
        dst : int

        Returns
        -------
        bool

        """
        return _pyagrum.PAG_isTail(self, src, dst)

    def isCircle(self, src: int, dst: int) -> bool:
        r"""

        Return True if the mark at dst (seen from src) is a circle.

        Parameters
        ----------
        src : int
        dst : int

        Returns
        -------
        bool

        """
        return _pyagrum.PAG_isCircle(self, src, dst)

    def isDefinitelyDirected(self, x: int, y: int) -> bool:
        r"""

        Return True if the edge x-y is definitely directed from x to y.

        An edge is definitely directed from x to y when x has a tail and y has an arrowhead.

        Parameters
        ----------
        x : int
        y : int

        Returns
        -------
        bool

        """
        return _pyagrum.PAG_isDefinitelyDirected(self, x, y)

    def isBidirected(self, x: int, y: int) -> bool:
        r"""

        Return True if both endpoints of edge (x, y) are arrowheads (bidirected edge).

        Parameters
        ----------
        x : int
        y : int

        Returns
        -------
        bool

        """
        return _pyagrum.PAG_isBidirected(self, x, y)

    def isDefCollider(self, x: int, z: int, y: int) -> bool:
        r"""

        Return True if z is a definite collider on the path x-z-y.

        z is a definite collider when both marks at z (from x and from y) are arrowheads.

        Parameters
        ----------
        x : int
        z : int
        y : int

        Returns
        -------
        bool

        """
        return _pyagrum.PAG_isDefCollider(self, x, z, y)

    def toMixedGraph(self) -> "pyagrum.MixedGraph":
        r"""

        Convert the PAG to a MixedGraph by interpreting definite edge orientations.

        - Definitely directed edges become arcs.
        - Bidirected edges become two arcs (one in each direction).
        - Undirected or circle edges become undirected edges.

        Returns
        -------
        pyagrum.MixedGraph

        """
        return _pyagrum.PAG_toMixedGraph(self)

    def toDot(self) -> str:
        r"""

        Return a Graphviz dot representation of the PAG.

        Endpoint marks are rendered as:
        - Circle: ``odot``
        - Tail: ``none``
        - Arrowhead: ``normal``

        Returns
        -------
        str
            dot-format string

        """
        return _pyagrum.PAG_toDot(self)

    def addNode(self) -> int:
        r"""

        Add a new node to the PAG and return its id.

        Returns
        -------
        int
            the new NodeId

        """
        return _pyagrum.PAG_addNode(self)

    def addNodes(self, n: int) -> list[int]:
        r"""

        Add n new nodes to the PAG.

        Parameters
        ----------
        n : int
            number of nodes to add

        Returns
        -------
        list of int
            the new NodeIds

        """
        return _pyagrum.PAG_addNodes(self, n)

    def addNodeWithId(self, id: int) -> None:
        r"""

        Add a node with a chosen id.

        Parameters
        ----------
        id : int
            the id of the new node

        Raises
        ------
        pyagrum.DuplicateElement
            if the given id is already used

        """
        return _pyagrum.PAG_addNodeWithId(self, id)

    def existsNode(self, id: int) -> bool:
        r"""

        Check whether a node with the given id exists in the PAG.

        Parameters
        ----------
        id : int
            the id to check

        Returns
        -------
        bool
            True if the node exists

        """
        return _pyagrum.PAG_existsNode(self, id)

    def size(self) -> int:
        r"""

        Return the number of nodes in the PAG.

        Returns
        -------
        int
            number of nodes

        """
        return _pyagrum.PAG_size(self)

    def empty(self) -> bool:
        r"""

        Check whether the PAG has no nodes.

        Returns
        -------
        bool
            True if the PAG contains no nodes

        """
        return _pyagrum.PAG_empty(self)

    def eraseEdge(self, *args) -> None:
        r"""

        Erase the edge between n1 and n2.

        Parameters
        ----------
        n1 : int
          the id of the tail node
        n2 : int
          the id of the head node

        """
        return _pyagrum.PAG_eraseEdge(self, *args)

    def existsEdge(self, n1: int, n2: int) -> bool:
        r"""

        Check whether an edge exists between two nodes.

        Parameters
        ----------
        n1 : int
            id of one endpoint
        n2 : int
            id of the other endpoint

        Returns
        -------
        bool
            True if the edge exists

        """
        return _pyagrum.PAG_existsEdge(self, n1, n2)

    def sizeEdges(self) -> int:
        r"""

        Return the number of edges in the PAG.

        Returns
        -------
        int
            number of edges

        """
        return _pyagrum.PAG_sizeEdges(self)

    def emptyEdges(self) -> bool:
        r"""

        Check whether the PAG has no edges.

        Returns
        -------
        bool
            True if the PAG contains no edges

        """
        return _pyagrum.PAG_emptyEdges(self)

    def eraseNeighbours(self, n: int) -> None:
        r"""

        Remove all edges adjacent to a given node (and their marks).

        Parameters
        ----------
        n : int
            id of the node

        """
        return _pyagrum.PAG_eraseNeighbours(self, n)

    def addEdge(self, *args) -> None:
        r"""

        Add an edge between two nodes with specified endpoint marks.

        Signatures:

          ``addEdge(x, y)``
              Adds a Circle-Circle edge (both endpoints uncertain).

          ``addEdge(x, y, markAtX, markAtY)``
              Adds an edge with explicit marks. ``markAtX`` is the mark on x's side
              (visible from y), ``markAtY`` is the mark on y's side (visible from x).

        Parameters
        ----------
        x : int
            id of the first node
        y : int
            id of the second node
        markAtX : int, optional
            mark at the x endpoint (``EdgeMark_Circle``, ``EdgeMark_Tail``, or ``EdgeMark_Arrowhead``)
        markAtY : int, optional
            mark at the y endpoint (``EdgeMark_Circle``, ``EdgeMark_Tail``, or ``EdgeMark_Arrowhead``)

        """
        return _pyagrum.PAG_addEdge(self, *args)

    def markAt(self, *args) -> int:
        r"""

        Return the endpoint mark on the dst side of edge (src, dst).

        The mark at dst indicates what the edge says about dst from src's perspective:
        ``marks_[Arc(src,dst)]`` = mark at the dst endpoint.

        Parameters
        ----------
        src : int
            id of the source node
        dst : int
            id of the destination node

        Returns
        -------
        int
            ``EdgeMark_Circle`` (0), ``EdgeMark_Tail`` (1), or ``EdgeMark_Arrowhead`` (2)

        """
        return _pyagrum.PAG_markAt(self, *args)

    def setMarkAt(self, *args) -> None:
        r"""

        Set the endpoint mark on the dst side of edge (src, dst).

        Parameters
        ----------
        src : int
            id of the source node
        dst : int
            id of the destination node
        m : int
            new mark: ``EdgeMark_Circle`` (0), ``EdgeMark_Tail`` (1), or ``EdgeMark_Arrowhead`` (2)

        """
        return _pyagrum.PAG_setMarkAt(self, *args)

    def reorientAllWith(self, *args) -> None:
        r"""

        Set all endpoint marks in the PAG to the given mark.

        Parameters
        ----------
        m : int
            mark to set everywhere: ``EdgeMark_Circle`` (0), ``EdgeMark_Tail`` (1), or ``EdgeMark_Arrowhead`` (2)

        """
        return _pyagrum.PAG_reorientAllWith(self, *args)

# Register PAG in _pyagrum:
_pyagrum.PAG_swigregister(PAG)
class CliqueGraph(UndiGraph):
    r"""

    CliqueGraph represents a Clique Graph.

    CliqueGraph() -> CliqueGraph
        default constructor

    CliqueGraph(src) -> CliqueGraph
        Parameter
            - **src** (*pyagrum.CliqueGraph*) -- the CliqueGraph to copy

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.CliqueGraph_swiginit(self, _pyagrum.new_CliqueGraph(*args))
    __swig_destroy__ = _pyagrum.delete_CliqueGraph

    def addEdge(self, first: int, second: int) -> None:
        r"""

        Insert a new edge into the graph.

        Parameters
        ----------
        n1 : int
          the id of one node of the new inserted edge
        n2 : int
          the id of the other node of the new inserted edge

        Raises
        ------
          pyagrum.InvalidNode
            If n1 or n2 does not belong to the graph nodes.

        """
        return _pyagrum.CliqueGraph_addEdge(self, first, second)

    def eraseEdge(self, edge: "pyagrum.Edge") -> None:
        r"""

        Erase the edge between n1 and n2.

        Parameters
        ----------
        n1 : int
          the id of the tail node
        n2 : int
          the id of the head node

        """
        return _pyagrum.CliqueGraph_eraseEdge(self, edge)

    def clearEdges(self) -> None:
        r"""

        Remove all edges and their separators

        """
        return _pyagrum.CliqueGraph_clearEdges(self)

    def addNode(self, *args) -> int:
        r"""

        Returns
        -------
        int
          the new NodeId

        """
        return _pyagrum.CliqueGraph_addNode(self, *args)

    def eraseNode(self, node: int) -> None:
        r"""

        Erase the node and all the adjacent edges.

        Parameters
        ----------
        id : int
          the id of the node

        """
        return _pyagrum.CliqueGraph_eraseNode(self, node)

    def clear(self) -> None:
        r"""

        Remove all the nodes and edges from the graph.

        """
        return _pyagrum.CliqueGraph_clear(self)

    def container(self, idNode: int) -> int:
        r"""

        Parameters
        ----------
        idNode : int
          the id of the node

        Returns
        -------
        int
          the id of a clique containing the node

        Raises
        ------
        pyagrum.NotFound
          If no clique contains idNode

        """
        return _pyagrum.CliqueGraph_container(self, idNode)

    def setClique(self, idClique: int, new_clique: list[int]) -> None:
        r"""

        changes the set of nodes included into a given clique

        Parameters
        ----------
        idClique : int
          the id of the clique
        new_clique : set
          the new set of nodes to be included in the clique

        Raises
        ------
        pyagrum.NotFound
          If idClique is not a clique of the graph

        """
        return _pyagrum.CliqueGraph_setClique(self, idClique, new_clique)

    def addToClique(self, clique_id: int, node_id: int) -> None:
        r"""

        Change the set of nodes included into a given clique and returns the new set

        Parameters
        ----------
        clique_id : int
          the id of the clique
        node_id : int
          the id of the node

        Raises
        ------
          pyagrum.NotFound
          If clique_id does not exist
          pyagrum.DuplicateElement
          If clique_id set already contains the ndoe

        """
        return _pyagrum.CliqueGraph_addToClique(self, clique_id, node_id)

    def eraseFromClique(self, clique_id: int, node_id: int) -> None:
        r"""

        Remove a node from a clique

        Parameters
        ----------
        clique_id : int
          the id of the clique
        node_id : int
          the id of the node

        Raises
        ------
        pyagrum.NotFound
          If clique_id does not exist

        """
        return _pyagrum.CliqueGraph_eraseFromClique(self, clique_id, node_id)

    def containerPath(self, node1: int, node2: int) -> list[int]:
        r"""

        Parameters
        ----------
        node1 : int
          the id of one node
        node2 : int
          the id of the other node

        Returns
        -------
        List
          a path from a clique containing node1 to a clique containing node2

        Raises
        ------
        pyagrum.NotFound
          If such path cannot be found

        """
        return _pyagrum.CliqueGraph_containerPath(self, node1, node2)

    def hasRunningIntersection(self) -> bool:
        r"""

        Returns
        -------
        bool
          True if the running intersection property holds

        """
        return _pyagrum.CliqueGraph_hasRunningIntersection(self)

    def isJoinTree(self) -> bool:
        r"""

        Returns
        -------
        bool
          True if the graph is a join tree

        """
        return _pyagrum.CliqueGraph_isJoinTree(self)

    def toDot(self) -> str:
        r"""

        Returns
        -------
        str
            a friendly display of the graph in DOT format

        """
        return _pyagrum.CliqueGraph_toDot(self)

    def __map_str__(self, *args) -> str:
        return _pyagrum.CliqueGraph___map_str__(self, *args)

    def __eq__(self, _from: "CliqueGraph") -> bool:
        return _pyagrum.CliqueGraph___eq__(self, _from)

    def clique(self, clique: int) -> list[int]:
        r"""

        Parameters
        ----------
        idClique : int
          the id of the clique

        Returns
        -------
        set
          The set of nodes included in the clique

        Raises
        ------
        pyagrum.NotFound
          If the clique does not belong to the clique graph

        """
        return _pyagrum.CliqueGraph_clique(self, clique)

    def separator(self, cliq1: int, cliq2: int) -> list[int]:
        r"""

        Parameters
        ----------
        edge : pyagrum.Edge
          the edge to be checked
        clique1 : int
            one extremity of the edge
        clique : int
          the other extremity of the edge

        Returns
        -------
        set
          the separator included in a given edge

        Raises
        ------
        pyagrum.NotFound
          If the edge does not belong to the clique graph

        """
        return _pyagrum.CliqueGraph_separator(self, cliq1, cliq2)

    def toDotWithNames(self,bn):
        """
        Parameters
        ----------
        bn : pyagrum.BayesNet
        a Bayesian network

        Returns
        -------
        str
          a friendly display of the graph in DOT format where ids have been changed according to their correspondance in the BN
        """
        def local_nameFromId(m):
          return " ".join([bn.variable(int(n)).name()
                           for n in m.group().split("-")])
        import re
        m = re.compile(r'(?<=label=\")\d+[\-\d+]*')
        return m.sub(local_nameFromId,self.toDot())


# Register CliqueGraph in _pyagrum:
_pyagrum.CliqueGraph_swigregister(CliqueGraph)
class MeekRules(object):
    r"""

    MeekRules class applies the Meek rules to a mixed graph or PDAG (Partially Directed Acyclic Graph) in order to propagate orientation constraints and obtain a CPDAG (Completed Partially Directed Acyclic Graph) or a DAG (Directed Acyclic Graph).

    The Meek Rules help complete the orientation of edges in a mixed graph, resulting in a CPDAG that reflects the possible causal relationships among variables while accounting for the observed conditional independencies in the data. The CPDAG is a more refined and directed version of the original PDAG (Partially Directed Acyclic Graph) obtained from the data.

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self):
        _pyagrum.MeekRules_swiginit(self, _pyagrum.new_MeekRules())
    __swig_destroy__ = _pyagrum.delete_MeekRules

    def propagate(self, mg: "MixedGraph") -> "pyagrum.MixedGraph":
        r"""

        Apply Meek rules R1–R4 to orient edges in a mixed graph.

        Parameters
        ----------
        mg : pyagrum.MixedGraph
            the mixed graph to orient (arcs and undirected edges)

        Returns
        -------
        pyagrum.MixedGraph
            the partially oriented graph (may still contain undirected edges or double-headed arcs)

        """
        return _pyagrum.MeekRules_propagate(self, mg)

    def propagateToCPDAG(self, mg: "MixedGraph") -> "pyagrum.PDAG":
        r"""

        Propagates orientation constraints in the graph according to the Meek rules. The Meek rules are applied iteratively until no more orientation constraints can be propagated. And then arbitratily resolves double orientations. This method returns a `pyagrum.PDAG` with the properties of CPDAG.

        The arbitrary resolution of double orientations is not deterministic. It depends on the order of the edges in the graph. `pyagrum.MeekRules.Choices` returns the list of the arbitrary choices made by the last execution of `pyagrum.MeekRules.progagatesToCPDAG`.

        Parameters
        ----------
        mg : pyagrum.MixedGraph
            The graph to be completed.

        Returns
        -------
        pyagrum.PDAG
            The completed `pyagrum.PDAG` (CPDAG).

        """
        return _pyagrum.MeekRules_propagateToCPDAG(self, mg)

    def propagateToDAG(self, mg: "MixedGraph") -> "pyagrum.DAG":
        r"""

        Calls `propagateToCPDAG` and then orients the remaining edges arbitrarily according to some heuristics. `pyagrum.MeekRules.Choices` returns the list of the arbitrary choices made by the last execution of `pyagrum.MeekRules.progagatesToDAG`.

        Parameters
        ----------
        mg : `pyagrum.MixedGraph` or `pyagrum.PDAG`
            The graph to be completed.

        Returns
        -------
        `pyagrum.DAG`
            The completed `pyagrum.DAG`.

        """
        return _pyagrum.MeekRules_propagateToDAG(self, mg)

    def choices(self) -> list[tuple[int,int]]:
        r"""

        Returns the list of the arbitrary choices made by the last execution of `pyagrum.MeekRules.progagatesToCPDAG` or `pyagrum.MeekRules.progagatesToDAG`.

        Returns
        -------
        list of tuple
            The list of the arbitrary choices made by the last execution of `pyagrum.MeekRules.progagatesToCPDAG` or `pyagrum.MeekRules.progagatesToDAG`. Each tuple represents a `pyagrum.Edge`.

        """
        return _pyagrum.MeekRules_choices(self)

# Register MeekRules in _pyagrum:
_pyagrum.MeekRules_swigregister(MeekRules)
class Instantiation(object):
    r"""

    Class for assigning/browsing values to tuples of discrete variables.

    Instantiation is designed to assign values to tuples of variables and to efficiently loop over values of subsets of variables.

    Instantiation() -> Instantiation
        default constructor

    Instantiation(aI) -> Instantiation
        Parameters:
          - **aI** (*pyagrum.Instantiation*) -- the Instantiation we copy

    Returns
    -------
    pyagrum.Instantiation
        An empty tuple or a copy of the one in parameters

    Instantiation is subscriptable therefore values can be easily accessed/modified.

    Examples
    --------
    >>> ## Access the value of A in an instantiation aI
    >>> valueOfA = aI['A']
    >>> ## Modify the value
    >>> aI['A'] = newValueOfA

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args):
        _pyagrum.Instantiation_swiginit(self, _pyagrum.new_Instantiation(*args))
    __swig_destroy__ = _pyagrum.delete_Instantiation

    def nbrDim(self) -> int:
        r"""

        Returns
        -------
        int
            The number of variables in the Instantiation.

        """
        return _pyagrum.Instantiation_nbrDim(self)

    def add(self, v: "pyagrum.DiscreteVariable") -> None:
        r"""

        Adds a new variable in the Instantiation.

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
          The new variable added to the Instantiation

        Raises
        ------
        DuplicateElement
          If the variable is already in this Instantiation


        """
        val = _pyagrum.Instantiation_add(self, v)

        return self


        return val


    def erase(self, *args) -> None:
        r"""

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
          The variable to be removed from this Instantiation.

        Raises
        ------
        NotFound
          If v does not belong to this Instantiation.

        """
        return _pyagrum.Instantiation_erase(self, *args)

    def clear(self) -> None:
        r"""

        Erase all variables from an Instantiation.

        """
        return _pyagrum.Instantiation_clear(self)

    def domainSize(self) -> int:
        r"""

        Returns
        -------
        int
            The product of the variable's domain size in the Instantiation.

        """
        return _pyagrum.Instantiation_domainSize(self)

    def pos(self, v: "pyagrum.DiscreteVariable") -> int:
        r"""

        Returns
        -------
        int
           the position of the variable v.

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
            the variable for which its position is return.

        Raises
        ------
        NotFound
          If v does not belong to the instantiation.

        """
        return _pyagrum.Instantiation_pos(self, v)

    def val(self, *args) -> int:
        r"""

        Parameters
        ----------
        i : int
            The index of the variable.
        var : pyagrum.DiscreteVariable
            The variable the value of which we wish to know

        Returns
        -------
        int
            the current value of the variable.

        Raises
        ------
        NotFound
          If the element cannot be found.

        """
        return _pyagrum.Instantiation_val(self, *args)

    def variable(self, *args) -> "pyagrum.DiscreteVariable":
        r"""

        Parameters
        ----------
        i : int
          The index of the variable

        Returns
        -------
        pyagrum.DiscreteVariable
          the variable at position i in the tuple.

        Raises
        ------
        NotFound
          If the element cannot be found.

        """
        return _pyagrum.Instantiation_variable(self, *args)

    def chgVal(self, *args) -> "pyagrum.Instantiation":
        r"""

        Assign newval to v (or to the variable at position varPos) in the Instantiation.

        Parameters
        ----------
        v : pyagrum.DiscreteVariable or string
          The variable whose value is assigned (or its name)
        varPos : int
          The index of the variable whose value is assigned in the tuple of variables of the Instantiation
        newval : int or string
          The index of the value assigned (or its name)

        Returns
        -------
        pyagrum.Instantiation
            The modified instantiation

        Raises
        ------
        NotFound
          If variable v does not belong to the instantiation.
        OutOfBounds
          If newval is not a possible value for the variable.

        """
        return _pyagrum.Instantiation_chgVal(self, *args)

    def setVals(self, i: "pyagrum.Instantiation") -> "pyagrum.Instantiation":
        r"""

        Assign the values from i in the Instantiation.

        Parameters
        ----------
        i : pyagrum.Instantiation
          An Instantiation in which the new values are searched

        Returns
        -------
        pyagrum.Instantiation
          a reference to the instantiation

        """
        return _pyagrum.Instantiation_setVals(self, i)

    def contains(self, *args) -> bool:
        r"""

        Indicates whether a given variable belongs to the Instantiation.

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
            The variable for which the test is made.

        Returns
        -------
        bool :
            True if the variable is in the Instantiation.

        """
        return _pyagrum.Instantiation_contains(self, *args)

    def variablesSequence(self) -> list[object]:
        r"""

        Returns
        -------
        List
            the sequence of DiscreteVariable of this instantiation.

        """
        return _pyagrum.Instantiation_variablesSequence(self)

    def empty(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the instantiation is empty.

        """
        return _pyagrum.Instantiation_empty(self)

    def inOverflow(self) -> bool:
        r"""

        Returns
        -------
        bool
          True if the current value of the tuple is correct

        """
        return _pyagrum.Instantiation_inOverflow(self)

    def unsetOverflow(self) -> None:
        r"""

        Removes the flag overflow.

        """
        return _pyagrum.Instantiation_unsetOverflow(self)

    def unsetEnd(self) -> None:
        r"""

        Alias for unsetOverflow().

        """
        return _pyagrum.Instantiation_unsetEnd(self)

    def end(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the Instantiation reached the end.

        """
        return _pyagrum.Instantiation_end(self)

    def rend(self) -> bool:
        r"""

        Returns
        -------
        bool:
          True if the Instantiation reached the rend.

        """
        return _pyagrum.Instantiation_rend(self)

    def inc(self) -> None:
        r"""

        Operator ++.

        """
        return _pyagrum.Instantiation_inc(self)

    def dec(self) -> None:
        r"""

        Operator --.

        """
        return _pyagrum.Instantiation_dec(self)

    def incIn(self, i: "pyagrum.Instantiation") -> None:
        r"""

        Operator ++ for the variables in i.

        Parameters
        ----------
        i : pyagrum.Instantiation
            The set of variables to increment in this Instantiation.

        """
        return _pyagrum.Instantiation_incIn(self, i)

    def decIn(self, i: "pyagrum.Instantiation") -> None:
        r"""

        Operator -- for the variables in i.

        Parameters
        ----------
        i : pyagrum.Instantiation
          The set of variables to decrement in this Instantiation

        """
        return _pyagrum.Instantiation_decIn(self, i)

    def incOut(self, i: "pyagrum.Instantiation") -> None:
        r"""

        Operator ++ for the variables not in i.

        Parameters
        ----------
        i : Instantiation
            The set of variable to not increment in this Instantiation.

        """
        return _pyagrum.Instantiation_incOut(self, i)

    def decOut(self, i: "pyagrum.Instantiation") -> None:
        r"""

        Operator -- for the variables not in i.

        Parameters
        ----------
        i : pyagrum.Instantiation
          The set of variables to not decrement in this Instantiation.

        """
        return _pyagrum.Instantiation_decOut(self, i)

    def incNotVar(self, v: "pyagrum.DiscreteVariable") -> None:
        r"""

        Operator ++ for vars which are not v.

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
            The variable not to increment in this Instantiation.

        """
        return _pyagrum.Instantiation_incNotVar(self, v)

    def decNotVar(self, v: "pyagrum.DiscreteVariable") -> None:
        r"""

        Operator -- for vars which are not v.

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
          The variable not to decrement in this Instantiation.

        """
        return _pyagrum.Instantiation_decNotVar(self, v)

    def incVar(self, v: "pyagrum.DiscreteVariable") -> None:
        r"""

        Operator ++ for variable v only.

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
            The variable to increment in this Instantiation.

        Raises
        ------
        NotFound
          If variable v does not belong to the Instantiation.

        """
        return _pyagrum.Instantiation_incVar(self, v)

    def decVar(self, v: "pyagrum.DiscreteVariable") -> None:
        r"""

        Operator -- for variable v only.

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
         The variable to decrement in this Instantiation.

        Raises
        ------
        NotFound
          If variable v does not belong to the Instantiation.

        """
        return _pyagrum.Instantiation_decVar(self, v)

    def setFirst(self) -> None:
        r"""

        Assign the first values to the tuple of the Instantiation.

        """
        return _pyagrum.Instantiation_setFirst(self)

    def setLast(self) -> None:
        r"""

        Assign the last values in the Instantiation.

        """
        return _pyagrum.Instantiation_setLast(self)

    def setFirstIn(self, i: "pyagrum.Instantiation") -> None:
        r"""

        Assign the first values in the Instantiation for the variables in i.

        Parameters
        ----------
        i : pyagrum.Instantiation
          The variables to which their first value is assigned in this Instantiation.

        """
        return _pyagrum.Instantiation_setFirstIn(self, i)

    def setLastIn(self, i: "pyagrum.Instantiation") -> None:
        r"""

        Assign the last values in the Instantiation for the variables in i.

        Parameters
        ----------
        i : pyagrum.Instantiation
            The variables to which their last value is assigned in this Instantiation.

        """
        return _pyagrum.Instantiation_setLastIn(self, i)

    def setFirstOut(self, i: "pyagrum.Instantiation") -> None:
        r"""

        Assign the first values in the Instantiation for the variables not in i.

        Parameters
        ----------
        i : pyagrum.Instantiation
            The variable that will not be set to their first value in this Instantiation.

        """
        return _pyagrum.Instantiation_setFirstOut(self, i)

    def setLastOut(self, i: "pyagrum.Instantiation") -> None:
        r"""

        Assign the last values in the Instantiation for the variables not in i.

        Parameters
        ----------
        i : pyagrum.Instantiation
            The variables that will not be set to their last value in this Instantiation.

        """
        return _pyagrum.Instantiation_setLastOut(self, i)

    def setFirstNotVar(self, v: "pyagrum.DiscreteVariable") -> None:
        r"""

        Assign the first values to variables different of v.

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
          The variable that will not be set to its first value in this Instantiation.

        """
        return _pyagrum.Instantiation_setFirstNotVar(self, v)

    def setLastNotVar(self, v: "pyagrum.DiscreteVariable") -> None:
        r"""

        Assign the last values to variables different of v.

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
            The variable that will not be set to its last value in this Instantiation.

        """
        return _pyagrum.Instantiation_setLastNotVar(self, v)

    def setFirstVar(self, v: "pyagrum.DiscreteVariable") -> None:
        r"""

        Assign the first value in the Instantiation for var v.

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
            The variable that will be set to its first value in this Instantiation.

        """
        return _pyagrum.Instantiation_setFirstVar(self, v)

    def setLastVar(self, v: "pyagrum.DiscreteVariable") -> None:
        r"""

        Assign the last value in the Instantiation for var v.

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
          The variable that will be set to its last value in this Instantiation.

        """
        return _pyagrum.Instantiation_setLastVar(self, v)

    def __eq__(self, other: "pyagrum.Instantiation") -> bool:
        return _pyagrum.Instantiation___eq__(self, other)

    def __iadd__(self, depl: int) -> "pyagrum.Instantiation":
        return _pyagrum.Instantiation___iadd__(self, depl)

    def __isub__(self, depl: int) -> "pyagrum.Instantiation":
        return _pyagrum.Instantiation___isub__(self, depl)

    def hamming(self) -> int:
        r"""

        Returns
        -------
        int
          the hamming distance of this instantiation.

        """
        return _pyagrum.Instantiation_hamming(self)

    def reorder(self, *args) -> None:
        r"""

        Reorder vars of this instantiation giving the order in v (or i).

        Parameters
        ----------
        i : pyagrum.Instantiation
          The sequence of variables with which to reorder this Instantiation.
        v : list
            The new order of variables for this Instantiation.

        """
        return _pyagrum.Instantiation_reorder(self, *args)

    def __repr__(self) -> str:
        return _pyagrum.Instantiation___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.Instantiation___str__(self)

    def setMutable(self) -> None:
        r"""

        Detach this Instantiation from its master, making it independently mutable.

        After this call, the Instantiation is no longer a slave and can be freely modified.

        """
        return _pyagrum.Instantiation_setMutable(self)

    def isMutable(self) -> bool:
        r"""

        Check whether this Instantiation is currently a slave of another object (i.e., not mutable).

        Returns
        -------
        bool
            True if the Instantiation is a slave (not independently mutable)

        """
        return _pyagrum.Instantiation_isMutable(self)

    def todict(self, withLabels: bool=False) -> dict[str, int | str]:
        r"""

        Create a dictionary `{variable_name:value}` from an instantiation

        Parameters
        ----------
        withLabels : boolean
            The value will be a label (string) if True. It will be a position (int) if False. Default is False

        Returns
        -------
        dict
            The dictionary

        """
        return _pyagrum.Instantiation_todict(self, withLabels)

    def _cppfromdict(self, dict: object) -> None:
        return _pyagrum.Instantiation__cppfromdict(self, dict)

    def fromdict(self, dict: object) -> None:
      r"""

      Change the values in an instantiation from a dictionary `{variable_name:value}` where value can be a position (int) or a label (string).

      If a variable_name does not occur in the instantiation, nothing is done.

      Warnings
      --------
          OutOfBounds raised if a value cannot be found.

      """
      self._cppfromdict(dict)
      return self

    def __setitem__(self,key,item):
      self.chgVal(key,item)

    def __getitem__(self,key):
      return self.val(self.variable(key))

    def variablesSequence(self):
      """
      Returns
      -------
      list
          a list containing the sequence of variables
      """
      varlist = []
      for i in range(0, self.nbrDim()):
          varlist.append(self.variable(i))
      return varlist

    def addVarsFromModel(self,model,names):
      r"""
      From a graphical model, add all the variable whose names are in the iterable

      Parameters
      ----------
      model : pyagrum.GraphicalModel
      a (discrete) graphical model such as Bayesian network, Markov random field, Influence Diagram, etc.

      names : iterable of strings
      a list/set/etc of names of variables (as string)

      Returns
      -------
      pyagrum.Instantiation
      the current instantiation (self) in order to chain methods.
      """
      for name in names:
        self.add(model.variable(name))
      return self

    def loopIn(self):
        """
        Generator to iterate on an Instantiation.

        Yield an pyagrum.Instantiation (copy of self) that iterates over all the possible values for the Instantiation.

        Examples
        --------
        >>> import pyagrum as gum
        >>> bn=pyagrum.fastBN("A[3]->B[3]<-C[3]")
        >>> I=pyagrum.Instantiation(bn.cpt("B"))
        >>> for i in I.loopIn():
              print(i)
              print(bn.cpt("B").get(i))
              bn.cpt("B").set(i,0.3)
        """
        J=pyagrum.Instantiation(self)
        J.setFirst()
        while not J.end():
            yield(J)
            J.inc()
        J.setLast()
        return


# Register Instantiation in _pyagrum:
_pyagrum.Instantiation_swigregister(Instantiation)
GUM_DEFAULT_ITERATOR_NUMBER = _pyagrum.GUM_DEFAULT_ITERATOR_NUMBER
class GraphicalModel(object):
    r"""

    Abstract class for all PGM (associating set of variables and a graph).

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args, **kwargs):
        raise AttributeError("No constructor defined - class is abstract")
    __repr__ = _swig_repr
    __swig_destroy__ = _pyagrum.delete_GraphicalModel

    def property(self, name: str) -> str:
        r"""

        Returns the value associated to this property.

        Properties are a way to keep some (name,value) together with de model.

        Parameters
        ----------
        name : str
          the name of the property

        Raises
        ------
        NotFound
          if no name property is found

        Returns
        -------
        str
          The value associated to this name

        """
        return _pyagrum.GraphicalModel_property(self, name)

    def propertyWithDefault(self, name: str, byDefault: str) -> str:
        r"""

        Returns the value associated to this property or the default value if there is no such property.

        Properties are a way to keep some information (name,value) together with de model.

        Parameters
        ----------
        name : str
          the name of the property
        byDefault: str
          the value by default if no property has been found.

        Returns
        -------
        str
          The value associated to this name or the value by default.

        """
        return _pyagrum.GraphicalModel_propertyWithDefault(self, name, byDefault)

    def setProperty(self, name: str, value: str) -> None:
        r"""

        Create or change the couple (name,value) in the properties.

        Properties are a way to keep some information (name,value) together with de model.

        Parameters
        ----------
        name : str
          the name of the property
        value: str
          the value of the property.

        """
        return _pyagrum.GraphicalModel_setProperty(self, name, value)

    def properties(self) -> tuple[str, ...]:
        r"""

        Return the keys of all metadata properties of the model.

        Returns
        -------
        tuple[str, ...]
            tuple of property names (use :meth:`property` to retrieve a value by key)

        """
        return _pyagrum.GraphicalModel_properties(self)

    def existsProperty(self, name: str) -> bool:
        r"""

        Check whether a property key exists in the model's metadata.

        Parameters
        ----------
        name : str
            the property name

        Returns
        -------
        bool
            True if the property exists

        """
        return _pyagrum.GraphicalModel_existsProperty(self, name)

    def updateMetaData(self) -> None:
        r"""

        Update the model's built-in metadata (version, creation date, last modification date).

        This method is called automatically by writers before saving the model to a file.

        """
        return _pyagrum.GraphicalModel_updateMetaData(self)

    def variableNodeMap(self) -> "pyagrum.VariableNodeMap":
        r"""

        Return the variable-to-node mapping of the model.

        Returns
        -------
        pyagrum.VariableNodeMap
            the internal variable-to-node bijection

        """
        return _pyagrum.GraphicalModel_variableNodeMap(self)

    def size(self) -> int:
        return _pyagrum.GraphicalModel_size(self)

    def empty(self) -> bool:
        r"""

        Check if there are some variables in the model.

        Returns
        -------
        bool
            True if there is no variable in the model.

        """
        return _pyagrum.GraphicalModel_empty(self)

    def exists(self, *args) -> bool:
        r"""

        Check if a node with this name or id exists

        Parameters
        ----------
        norid: str|int
          name or id of the searched node

        Returns
        -------
        bool
            True if there is a node with such a name or id

        """
        return _pyagrum.GraphicalModel_exists(self, *args)

    def names(self, *args) -> tuple[str, ...]:
        r"""

        Set of names of variables in the model

        Returns
        -------
        set
            The names of the graph variables

        """
        return _pyagrum.GraphicalModel_names(self, *args)

    def ids(self, names: tuple[str, ...]) -> list[int]:
        r"""

        List of ids for a list of names of variables in the model

        Parameters
        ----------
        lov : list of str
          List of variable names

        Returns
        -------
        list of int
            The ids for the list of names of the graph variables

        """
        return _pyagrum.GraphicalModel_ids(self, names)

    def nodeset(self, names: tuple[str, ...]) -> list[int]:
        r"""

        Set of ids for a list of names of variables in the model

        Parameters
        ----------
        lov : list of str
          List of variable names

        Returns
        -------
        set
            The set of ids for the list of names of the graph variables

        """
        return _pyagrum.GraphicalModel_nodeset(self, names)

    def variables(self, *args) -> "pyagrum.VariableSet":
        r"""

        Return the set of variables corresponding to a list of names or a set of node ids.

        Parameters
        ----------
        args : list[str] or set[int]
            variable names or node ids

        Returns
        -------
        pyagrum.VariableSet
            the set of corresponding variables

        """
        return _pyagrum.GraphicalModel_variables(self, *args)

    def nodes(self) -> set[int]:
        return _pyagrum.GraphicalModel_nodes(self)

    def completeInstantiation(self) -> "pyagrum.Instantiation":
        r"""

        Give an instantiation over all the variables of the model

        Returns
        -------
        pyagrum.Instantiation
          a complete Instantiation for the model

        """
        return _pyagrum.GraphicalModel_completeInstantiation(self)

    def variable(self, id: int) -> "pyagrum.DiscreteVariable":
        return _pyagrum.GraphicalModel_variable(self, id)

    def nodeId(self, var: "pyagrum.DiscreteVariable") -> int:
        r"""

        Return the node id of a variable.

        Parameters
        ----------
        var : pyagrum.DiscreteVariable
            the variable

        Returns
        -------
        int
            the node id of the variable

        Raises
        ------
        pyagrum.NotFound
            if the variable does not exist in the model

        """
        return _pyagrum.GraphicalModel_nodeId(self, var)

    def idFromName(self, name: str) -> int:
        r"""

        Return the node id of a variable given its name.

        Parameters
        ----------
        name : str
            the name of the variable

        Returns
        -------
        int
            the node id of the variable

        Raises
        ------
        pyagrum.NotFound
            if no variable with this name exists in the model

        """
        return _pyagrum.GraphicalModel_idFromName(self, name)

    def variableFromName(self, name: str) -> "pyagrum.DiscreteVariable":
        r"""

        Return the variable with the given name.

        Parameters
        ----------
        name : str
            the name of the variable

        Returns
        -------
        pyagrum.DiscreteVariable
            the variable

        Raises
        ------
        pyagrum.NotFound
            if no variable with this name exists in the model

        """
        return _pyagrum.GraphicalModel_variableFromName(self, name)

    def log10DomainSize(self) -> float:
        r"""

        returns the log10 of the domain size of the model defined as the product of the domain sizes of the variables in the model.

        Returns
        -------
        float
            the log10 domain size.

        """
        return _pyagrum.GraphicalModel_log10DomainSize(self)

    def isIndependent(self, *args) -> bool:
        r"""

        check if nodes X and nodes Y are independent given nodes Z

        Parameters
        ----------
        X : str|int|list of str|int
              a list of of nodeIds or names
        Y : str|int|list of str|int
              a list of of nodeIds or names
        Z : str|int|list of str|int
              a list of of nodeIds or names

        Raises
        ------
        InvalidArgument
          if X and Y share variables

        Returns
        -------
        bool
          True if X and Y are independent given Z in the model

        """
        return _pyagrum.GraphicalModel_isIndependent(self, *args)

    @staticmethod
    def spaceCplxToString(dSize: float, dim: int, usedMem: int) -> str:
        r"""

        Return a human-readable string summarising the space complexity of a graphical model.

        Parameters
        ----------
        dSize : float
            log10 of the joint domain size
        dim : int
            number of independent parameters
        usedMem : int
            memory footprint in bytes

        Returns
        -------
        str
            a string of the form ``'domainSize: X, dim: Y, mem: Z'``

        """
        return _pyagrum.GraphicalModel_spaceCplxToString(dSize, dim, usedMem)

    def family(self, *args) -> list[int]:
        return _pyagrum.GraphicalModel_family(self, *args)

# Register GraphicalModel in _pyagrum:
_pyagrum.GraphicalModel_swigregister(GraphicalModel)
_static_list_end_safe_ = cvar._static_list_end_safe_
_static_list_end_ = cvar._static_list_end_
_list_end_safe_ = cvar._list_end_safe_
_list_end_ = cvar._list_end_

class DiscreteGraphicalModel(GraphicalModel):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args, **kwargs):
        raise AttributeError("No constructor defined - class is abstract")
    __repr__ = _swig_repr
    __swig_destroy__ = _pyagrum.delete_DiscreteGraphicalModel

    def variableNodeMap(self) -> "pyagrum.VariableNodeMap":
        r"""

        Return the variable-to-node mapping of the model.

        Returns
        -------
        pyagrum.VariableNodeMap
            the internal variable-to-node bijection

        """
        return _pyagrum.DiscreteGraphicalModel_variableNodeMap(self)

    def variable(self, id: int) -> "pyagrum.DiscreteVariable":
        return _pyagrum.DiscreteGraphicalModel_variable(self, id)

    def nodeId(self, var: "pyagrum.DiscreteVariable") -> int:
        r"""

        Return the node id of a variable.

        Parameters
        ----------
        var : pyagrum.DiscreteVariable
            the variable

        Returns
        -------
        int
            the node id of the variable

        Raises
        ------
        pyagrum.NotFound
            if the variable does not exist in the model

        """
        return _pyagrum.DiscreteGraphicalModel_nodeId(self, var)

    def idFromName(self, name: str) -> int:
        r"""

        Return the node id of a variable given its name.

        Parameters
        ----------
        name : str
            the name of the variable

        Returns
        -------
        int
            the node id of the variable

        Raises
        ------
        pyagrum.NotFound
            if no variable with this name exists in the model

        """
        return _pyagrum.DiscreteGraphicalModel_idFromName(self, name)

    def variableFromName(self, name: str) -> "pyagrum.DiscreteVariable":
        r"""

        Return the variable with the given name.

        Parameters
        ----------
        name : str
            the name of the variable

        Returns
        -------
        pyagrum.DiscreteVariable
            the variable

        Raises
        ------
        pyagrum.NotFound
            if no variable with this name exists in the model

        """
        return _pyagrum.DiscreteGraphicalModel_variableFromName(self, name)

# Register DiscreteGraphicalModel in _pyagrum:
_pyagrum.DiscreteGraphicalModel_swigregister(DiscreteGraphicalModel)
class DAGmodel(DiscreteGraphicalModel):
    r"""

    Abstract class used by IBayesNet and InfluenceDiagram.

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr
    __swig_destroy__ = _pyagrum.delete_DAGmodel

    def __init__(self, *args):
        _pyagrum.DAGmodel_swiginit(self, _pyagrum.new_DAGmodel(*args))

    def dag(self) -> "pyagrum.DAG":
        r"""

        Returns
        -------
        pyagrum.DAG
            a constant reference to the dag of this BayesNet.

        """
        val = _pyagrum.DAGmodel_dag(self)

        from pyagrum import DAG
        val = DAG(val) # copying the DAG


        return val


    def size(self) -> int:
        r"""

        Returns
        -------
        int
            the number of nodes in the graph

        """
        return _pyagrum.DAGmodel_size(self)

    def sizeArcs(self) -> int:
        r"""

        Returns
        -------
        int
            the number of arcs in the graph

        """
        return _pyagrum.DAGmodel_sizeArcs(self)

    def nodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
            the set of ids

        """
        return _pyagrum.DAGmodel_nodes(self)

    def exists(self, *args) -> bool:
        r"""

        Check if a node with this name or id exists

        Parameters
        ----------
        norid: str|int
          name or id of the searched node

        Returns
        -------
        bool
            True if there is a node with such a name or id

        """
        return _pyagrum.DAGmodel_exists(self, *args)

    def arcs(self) -> set[tuple[int,int]]:
        r"""

        Returns
        -------
        list
            The lisf of arcs in the graph

        """
        return _pyagrum.DAGmodel_arcs(self)

    def existsArc(self, *args) -> bool:
        r"""

        Check if an arc exists

        Parameters
        ----------
        tail : str|int
          the name or id of the tail of the arc

        head : str|int
          the name or the id of the head of the arc

        Returns
        -------
        bool
          True if `tail->head` is an arc.

        """
        return _pyagrum.DAGmodel_existsArc(self, *args)

    def parents(self, *args) -> list[int]:
        return _pyagrum.DAGmodel_parents(self, *args)

    def family(self, *args) -> list[int]:
        r"""

        give the set of parents of a node and the node

        Parameters
        ----------
        norid : str|int
          the node

        Returns
        -------
        set
          the set of nodeId of the family of the node `norid`

        """
        return _pyagrum.DAGmodel_family(self, *args)

    def children(self, *args) -> list[int]:
        return _pyagrum.DAGmodel_children(self, *args)

    def descendants(self, *args) -> list[int]:
        r"""

        give the set of nodeid of descendants of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the descendants of node `norid`.

        """
        return _pyagrum.DAGmodel_descendants(self, *args)

    def ancestors(self, *args) -> list[int]:
        r"""

        give the set of nodeid of ancestors of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the ancestors of node `norid`.

        """
        return _pyagrum.DAGmodel_ancestors(self, *args)

    def moralizedAncestralGraph(self, *args) -> "pyagrum.UndiGraph":
        r"""

        build a UndiGraph by moralizing the Ancestral Graph of a list of nodes

        Parameters
        ----------
        nodes : str|int|list of str|int
          the list of of nodeIds or names

        Warnings
        --------
          pyagrum.UndiGraph only knows NodeId. Hence the moralized ancestral graph does not include the names of the variables.graph

        Returns
        -------
        pyagrum.UndiGraph
          the moralized ancestral graph of the nodes

        """
        return _pyagrum.DAGmodel_moralizedAncestralGraph(self, *args)

    def isIndependent(self, *args) -> bool:
        r"""

        check if nodes X and nodes Y are independent given nodes Z

        Parameters
        ----------
        X : str|int|list of str|int
              a list of of nodeIds or names
        Y : str|int|list of str|int
              a list of of nodeIds or names
        Z : str|int|list of str|int
              a list of of nodeIds or names

        Raises
        ------
        InvalidArgument
          if X and Y share variables

        Returns
        -------
        bool
          True if X and Y are independent given Z in the model

        """
        return _pyagrum.DAGmodel_isIndependent(self, *args)

    def moralGraph(self) -> "pyagrum.UndiGraph":
        r"""

        Returns the moral graph of the BayesNet, formed by adding edges between all pairs of nodes that have a common child, and then making all edges in the graph undirected.

        Returns
        -------
        pyagrum.UndiGraph
            The moral graph

        """
        return _pyagrum.DAGmodel_moralGraph(self)

    def topologicalOrder(self) -> list[int]:
        r"""

        Returns
        -------
        List
            the list of the nodes Ids in a topological order

        Raises
        ------
        pyagrum.InvalidDirectedCycle
            If this graph contains cycles

        """
        return _pyagrum.DAGmodel_topologicalOrder(self)

    def connectedComponents(self) -> dict[int,int]:
        r"""

        Return the weakly connected components of the graph.

        Each node is mapped to the id of its component root (an arbitrarily chosen node from the same component).

        Returns
        -------
        dict[int, int]
            mapping node id → component root id

        See Also
        --------
        connectedComponentsList : returns a dict[int, set[int]] grouping nodes by component
        connectedComponentsCount : returns the number of components

        """
        return _pyagrum.DAGmodel_connectedComponents(self)

    def hasSameStructure(self, other: "pyagrum.DAGmodel") -> bool:
        r"""

        Parameters
        ----------
        pyagrum.DAGmodel
            a direct acyclic model

        Returns
        -------
        bool
            True if all the named node are the same and all the named arcs are the same

        """
        return _pyagrum.DAGmodel_hasSameStructure(self, other)

    def minimalCondSet(self, *args) -> list[int]:
        r"""

        Return a minimal conditioning set of a target node (or set of nodes) given a set of source nodes in the DAG model.

        A minimal conditioning set is a subset of the source nodes that d-separates the targets from the remaining sources.

        Parameters
        ----------
        target : int | str | list[int|str]
            the target node id(s) or name(s)
        soids : list[int|str]
            the list of source node ids or names

        Returns
        -------
        set[int]
            the minimal conditioning set (as node ids)

        """
        return _pyagrum.DAGmodel_minimalCondSet(self, *args)

# Register DAGmodel in _pyagrum:
_pyagrum.DAGmodel_swigregister(DAGmodel)
class UGmodel(DiscreteGraphicalModel):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr
    __swig_destroy__ = _pyagrum.delete_UGmodel

    def __init__(self, *args):
        _pyagrum.UGmodel_swiginit(self, _pyagrum.new_UGmodel(*args))

    def graph(self) -> "pyagrum.UndiGraph":
        r"""

        Return the underlying undirected graph.

        Returns
        -------
        pyagrum.UndiGraph
            the underlying graph

        """
        return _pyagrum.UGmodel_graph(self)

    def size(self) -> int:
        r"""

        Return the number of nodes (variables) in the graphical model.

        Returns
        -------
        int
            the number of nodes

        """
        return _pyagrum.UGmodel_size(self)

    def sizeEdges(self) -> int:
        r"""

        Return the number of edges in the model.

        Returns
        -------
        int
            the number of edges

        """
        return _pyagrum.UGmodel_sizeEdges(self)

    def nodes(self) -> set[int]:
        return _pyagrum.UGmodel_nodes(self)

    def exists(self, *args) -> bool:
        r"""

        Check if a node with this name or id exists

        Parameters
        ----------
        norid: str|int
          name or id of the searched node

        Returns
        -------
        bool
            True if there is a node with such a name or id

        """
        return _pyagrum.UGmodel_exists(self, *args)

    def edges(self) -> set[tuple[int,int]]:
        return _pyagrum.UGmodel_edges(self)

    def existsEdge(self, *args) -> bool:
        r"""

        Check whether an edge exists between two nodes.

        Parameters
        ----------
        n1 : int | str
            one endpoint (id or name)
        n2 : int | str
            the other endpoint (id or name)

        Returns
        -------
        bool
            True if the edge exists

        """
        return _pyagrum.UGmodel_existsEdge(self, *args)

    def neighbours(self, *args) -> list[int]:
        return _pyagrum.UGmodel_neighbours(self, *args)

    def isIndependent(self, *args) -> bool:
        r"""

        check if nodes X and nodes Y are independent given nodes Z

        Parameters
        ----------
        X : str|int|list of str|int
              a list of of nodeIds or names
        Y : str|int|list of str|int
              a list of of nodeIds or names
        Z : str|int|list of str|int
              a list of of nodeIds or names

        Raises
        ------
        InvalidArgument
          if X and Y share variables

        Returns
        -------
        bool
          True if X and Y are independent given Z in the model

        """
        return _pyagrum.UGmodel_isIndependent(self, *args)

    def hasSameStructure(self, other: "pyagrum.UGmodel") -> bool:
        r"""

        Check whether this model has the same undirected structure as another UGmodel.

        Parameters
        ----------
        other : pyagrum.MarkovRandomField
            the model to compare with

        Returns
        -------
        bool
            True if the undirected structures are identical

        """
        return _pyagrum.UGmodel_hasSameStructure(self, other)

    def connectedComponents(self) -> dict[int,int]:
        r"""

        Return the connected components of the undirected model.

        Each node is mapped to the id of its component root.

        Returns
        -------
        dict[int, int]
            mapping node id → component root id

        """
        return _pyagrum.UGmodel_connectedComponents(self)

    def family(self, *args) -> list[int]:
        r"""

        Return the family of a node: the node itself plus all its neighbours.

        Parameters
        ----------
        id : int
            the node id

        Returns
        -------
        set[int]
            the node and all its neighbours

        """
        return _pyagrum.UGmodel_family(self, *args)

# Register UGmodel in _pyagrum:
_pyagrum.UGmodel_swigregister(UGmodel)
class ApproximationScheme(object):
    r"""

    Used to parametrize stopping criteria in approximate inference or learning algorithm.

    ApproximationScheme(verbosity=False) -> ApproximationScheme
        Parameters:
          - **verbosity** (*bool*) -- to keep (or not) tracks of the learning process (history of epsilons)

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, verbosity: bool=False):
        _pyagrum.ApproximationScheme_swiginit(self, _pyagrum.new_ApproximationScheme(verbosity))
    __swig_destroy__ = _pyagrum.delete_ApproximationScheme

    def setEpsilon(self, eps: float) -> None:
        r"""

        Parameters
        ----------
        eps : float
            the epsilon we want to use

        Raises
        ------
        pyagrum.OutOfBounds
            If eps<0

        """
        return _pyagrum.ApproximationScheme_setEpsilon(self, eps)

    def epsilon(self) -> float:
        r"""

        Returns
        -------
        float
            the value of epsilon

        """
        return _pyagrum.ApproximationScheme_epsilon(self)

    def disableEpsilon(self) -> None:
        r"""

        Disable epsilon as a stopping criterion.

        """
        return _pyagrum.ApproximationScheme_disableEpsilon(self)

    def enableEpsilon(self) -> None:
        r"""

        Enable epsilon as a stopping criterion.

        """
        return _pyagrum.ApproximationScheme_enableEpsilon(self)

    def isEnabledEpsilon(self) -> bool:
        r"""

        Returns
        -------
        bool
          True if epsilon is used as a stopping criterion.

        """
        return _pyagrum.ApproximationScheme_isEnabledEpsilon(self)

    def setMinEpsilonRate(self, rate: float) -> None:
        r"""

        Parameters
        ----------
        rate : float
            the minimal epsilon rate

        """
        return _pyagrum.ApproximationScheme_setMinEpsilonRate(self, rate)

    def minEpsilonRate(self) -> float:
        r"""

        Returns
        -------
        float
            the value of the minimal epsilon rate

        """
        return _pyagrum.ApproximationScheme_minEpsilonRate(self)

    def disableMinEpsilonRate(self) -> None:
        r"""

        Disable a min epsilon rate as a stopping criterion.

        """
        return _pyagrum.ApproximationScheme_disableMinEpsilonRate(self)

    def enableMinEpsilonRate(self) -> None:
        r"""

        Enable a min epsilon rate as a stopping criterion.

        """
        return _pyagrum.ApproximationScheme_enableMinEpsilonRate(self)

    def isEnabledMinEpsilonRate(self) -> bool:
        r"""

        Returns
        -------
        bool
          True if epsilon rate is used as a stopping criterion

        """
        return _pyagrum.ApproximationScheme_isEnabledMinEpsilonRate(self)

    def setMaxIter(self, max: int) -> None:
        r"""

        Parameters
        ----------
        max : int
            the maximum number of iteration

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1

        """
        return _pyagrum.ApproximationScheme_setMaxIter(self, max)

    def maxIter(self) -> int:
        r"""

        Returns
        -------
        int
            the criterion on number of iterations

        """
        return _pyagrum.ApproximationScheme_maxIter(self)

    def disableMaxIter(self) -> None:
        r"""

        Disable max iterations as a stopping criterion.

        """
        return _pyagrum.ApproximationScheme_disableMaxIter(self)

    def enableMaxIter(self) -> None:
        r"""

        Enable max iterations as a stopping criterion.

        """
        return _pyagrum.ApproximationScheme_enableMaxIter(self)

    def isEnabledMaxIter(self) -> bool:
        r"""

        Returns
        -------
        bool
          True if max iterations is used as a stopping criterion

        """
        return _pyagrum.ApproximationScheme_isEnabledMaxIter(self)

    def setMaxTime(self, timeout: float) -> None:
        r"""

        Parameters
        ----------
        tiemout : float
            stopping criterion on timeout (in seconds)

        Raises
        ------
        pyagrum.OutOfBounds
            If timeout<=0.0

        """
        return _pyagrum.ApproximationScheme_setMaxTime(self, timeout)

    def maxTime(self) -> float:
        r"""

        Returns
        -------
        float
            the timeout(in seconds)

        """
        return _pyagrum.ApproximationScheme_maxTime(self)

    def currentTime(self) -> float:
        r"""

        Returns
        -------
        float
            get the current running time in second (float)

        """
        return _pyagrum.ApproximationScheme_currentTime(self)

    def disableMaxTime(self) -> None:
        r"""

        Disable max time as a stopping criterion.

        """
        return _pyagrum.ApproximationScheme_disableMaxTime(self)

    def enableMaxTime(self) -> None:
        r"""

        Enable max time as a stopping criterion.

        """
        return _pyagrum.ApproximationScheme_enableMaxTime(self)

    def isEnabledMaxTime(self) -> bool:
        r"""

        Returns
        -------
        bool
          True if max time is used as a stopping criterion

        """
        return _pyagrum.ApproximationScheme_isEnabledMaxTime(self)

    def setPeriodSize(self, p: int) -> None:
        r"""

        Parameters
        ----------
        p : int
            number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.ApproximationScheme_setPeriodSize(self, p)

    def periodSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.ApproximationScheme_periodSize(self)

    def setVerbosity(self, v: bool) -> None:
        r"""

        Parameters
        ----------
        v : bool
                verbosity

        """
        return _pyagrum.ApproximationScheme_setVerbosity(self, v)

    def verbosity(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the verbosity is enabled

        """
        return _pyagrum.ApproximationScheme_verbosity(self)

    def stateApproximationScheme(self) -> int:
        r"""

        Returns
        -------
        int
          the state of the approximation scheme

        """
        return _pyagrum.ApproximationScheme_stateApproximationScheme(self)

    def nbrIterations(self) -> int:
        r"""

        Returns
        -------
        int
            the number of iterations

        """
        return _pyagrum.ApproximationScheme_nbrIterations(self)

    def history(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple
            the scheme history

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the scheme did not performed or if verbosity is set to false

        """
        return _pyagrum.ApproximationScheme_history(self)

    def initApproximationScheme(self) -> None:
        r"""

        Initiate the approximation scheme.

        """
        return _pyagrum.ApproximationScheme_initApproximationScheme(self)

    def startOfPeriod(self) -> bool:
        r"""

        Returns
        -------
        bool
          True if it is a start of a period

        """
        return _pyagrum.ApproximationScheme_startOfPeriod(self)

    def updateApproximationScheme(self, incr: int=1) -> None:
        r"""

        Update the approximation scheme.

        """
        return _pyagrum.ApproximationScheme_updateApproximationScheme(self, incr)

    def remainingBurnIn(self) -> int:
        r"""

        Returns
        -------
        int
          the number of remaining burn in

        """
        return _pyagrum.ApproximationScheme_remainingBurnIn(self)

    def stopApproximationScheme(self) -> None:
        r"""

        Stop the approximation scheme.

        """
        return _pyagrum.ApproximationScheme_stopApproximationScheme(self)

    def continueApproximationScheme(self, error: float) -> bool:
        r"""

        Continue the approximation scheme.

        Parameters
        ----------
        error : float

        """
        return _pyagrum.ApproximationScheme_continueApproximationScheme(self, error)

# Register ApproximationScheme in _pyagrum:
_pyagrum.ApproximationScheme_swigregister(ApproximationScheme)

def fastVariable(*args) -> "pyagrum.DiscreteVariable":
    r"""

    Use *fast* syntax to add a variable in the BayesNet.

    Raises
    ------
    pyagrum.NotAllowed

    Parameters
    ----------
    fast_description: str
      string following *fast* syntax description
    default_nbrmod: int
      nbr of modality if fast_description does not indicate it.
      `default_nbrmod=1` is the way to create a variable with only one value (for instance for reward in influence diagram).

    Examples
    --------
    >>> print(pyagrum.fastVariable('A{On|Off|Defun}'))
    A:Labelized({On|Off|Defun})
    >>> print(pyagrum.fastVariable('A{3.14|0|1.15}'))
    A:NumericalDiscrete({0|1.15|3.14})
    >>> print(pyagrum.fastVariable('A{1.2:5.2:5}}'))
    A:NumericalDiscrete({1.2|2.2|3.2|4.2|5.2})
    >>> print(pyagrum.fastVariable('A{1|3|9}'))
    A:Integer({1|3|9})
    >>> print(pyagrum.fastVariable('A[4,6]'))
    A:Range([4,6])
    >>> print(pyagrum.fastVariable('A[5]'))
    A:Range([0,4])
    >>> print(pyagrum.fastVariable('A[4,6,10]'))
    A:Discretized(<[4;6[,[6;10]>)
    >>> print(pyagrum.fastVariable('A[1:6:5]'))
    A:Discretized(<[1;2[,[2;3[,[3;4[,[4;5[,[5;6]>)



    """
    return _pyagrum.fastVariable(*args)

def fastDiGraph(desc: str) -> "pyagrum.DiGraph":
    r"""

    Build a graph from a dot-like syntax : ``'A->B->C;B->E'``.

    ``'->'`` denotes a directed arc, ``'<-'`` a directed arc in the other direction
    (e.g. ``'A->B<-C'`` means both A and C point to B), ``'-'`` an undirected edge,
    ``';'`` separates independent chains. If every node token in the description is
    a non-negative integer, those integers are used directly as node ids ;
    otherwise every token -- including numeric-looking ones -- is used as a node
    name.

    Note
    ----
    A single ``'-'`` (not ``'--'``) is used for edges on purpose : ``pyagrum.fastMRF``
    already uses ``'--'`` to list the variables of a single factor (a clique), a
    different construct from a chain of pairwise edges. ``'A--B'`` is therefore
    rejected here rather than silently misread.

    Raises
    ------
    pyagrum.InvalidArc
      If the description requires a directed arc but the target graph type does
      not support arcs, or if the description is malformed.
    pyagrum.InvalidEdge
      If the description requires an undirected edge but the target graph type
      does not support edges.

    Parameters
    ----------
    desc : str
      the string containing the dot-like specification

    Returns
    -------
    pyagrum.DiGraph or pyagrum.UndiGraph or pyagrum.MixedGraph or pyagrum.DAG or pyagrum.PDAG

    Examples
    --------
    >>> pyagrum.fastDiGraph('A->B->C;B->E')
    >>> pyagrum.fastUndiGraph('A-B-C')
    >>> pyagrum.fastMixedGraph('A->B-C')
    >>> pyagrum.fastDAG('A->B->C')
    >>> pyagrum.fastPDAG('A->B-C')

    """
    return _pyagrum.fastDiGraph(desc)

def fastUndiGraph(desc: str) -> "pyagrum.UndiGraph":
    r"""

    Build a graph from a dot-like syntax : ``'A->B->C;B->E'``.

    ``'->'`` denotes a directed arc, ``'<-'`` a directed arc in the other direction
    (e.g. ``'A->B<-C'`` means both A and C point to B), ``'-'`` an undirected edge,
    ``';'`` separates independent chains. If every node token in the description is
    a non-negative integer, those integers are used directly as node ids ;
    otherwise every token -- including numeric-looking ones -- is used as a node
    name.

    Note
    ----
    A single ``'-'`` (not ``'--'``) is used for edges on purpose : ``pyagrum.fastMRF``
    already uses ``'--'`` to list the variables of a single factor (a clique), a
    different construct from a chain of pairwise edges. ``'A--B'`` is therefore
    rejected here rather than silently misread.

    Raises
    ------
    pyagrum.InvalidArc
      If the description requires a directed arc but the target graph type does
      not support arcs, or if the description is malformed.
    pyagrum.InvalidEdge
      If the description requires an undirected edge but the target graph type
      does not support edges.

    Parameters
    ----------
    desc : str
      the string containing the dot-like specification

    Returns
    -------
    pyagrum.DiGraph or pyagrum.UndiGraph or pyagrum.MixedGraph or pyagrum.DAG or pyagrum.PDAG

    Examples
    --------
    >>> pyagrum.fastDiGraph('A->B->C;B->E')
    >>> pyagrum.fastUndiGraph('A-B-C')
    >>> pyagrum.fastMixedGraph('A->B-C')
    >>> pyagrum.fastDAG('A->B->C')
    >>> pyagrum.fastPDAG('A->B-C')

    """
    return _pyagrum.fastUndiGraph(desc)

def fastMixedGraph(desc: str) -> "pyagrum.MixedGraph":
    r"""

    Build a graph from a dot-like syntax : ``'A->B->C;B->E'``.

    ``'->'`` denotes a directed arc, ``'<-'`` a directed arc in the other direction
    (e.g. ``'A->B<-C'`` means both A and C point to B), ``'-'`` an undirected edge,
    ``';'`` separates independent chains. If every node token in the description is
    a non-negative integer, those integers are used directly as node ids ;
    otherwise every token -- including numeric-looking ones -- is used as a node
    name.

    Note
    ----
    A single ``'-'`` (not ``'--'``) is used for edges on purpose : ``pyagrum.fastMRF``
    already uses ``'--'`` to list the variables of a single factor (a clique), a
    different construct from a chain of pairwise edges. ``'A--B'`` is therefore
    rejected here rather than silently misread.

    Raises
    ------
    pyagrum.InvalidArc
      If the description requires a directed arc but the target graph type does
      not support arcs, or if the description is malformed.
    pyagrum.InvalidEdge
      If the description requires an undirected edge but the target graph type
      does not support edges.

    Parameters
    ----------
    desc : str
      the string containing the dot-like specification

    Returns
    -------
    pyagrum.DiGraph or pyagrum.UndiGraph or pyagrum.MixedGraph or pyagrum.DAG or pyagrum.PDAG

    Examples
    --------
    >>> pyagrum.fastDiGraph('A->B->C;B->E')
    >>> pyagrum.fastUndiGraph('A-B-C')
    >>> pyagrum.fastMixedGraph('A->B-C')
    >>> pyagrum.fastDAG('A->B->C')
    >>> pyagrum.fastPDAG('A->B-C')

    """
    return _pyagrum.fastMixedGraph(desc)

def fastDAG(desc: str) -> "pyagrum.DAG":
    r"""

    Build a graph from a dot-like syntax : ``'A->B->C;B->E'``.

    ``'->'`` denotes a directed arc, ``'<-'`` a directed arc in the other direction
    (e.g. ``'A->B<-C'`` means both A and C point to B), ``'-'`` an undirected edge,
    ``';'`` separates independent chains. If every node token in the description is
    a non-negative integer, those integers are used directly as node ids ;
    otherwise every token -- including numeric-looking ones -- is used as a node
    name.

    Note
    ----
    A single ``'-'`` (not ``'--'``) is used for edges on purpose : ``pyagrum.fastMRF``
    already uses ``'--'`` to list the variables of a single factor (a clique), a
    different construct from a chain of pairwise edges. ``'A--B'`` is therefore
    rejected here rather than silently misread.

    Raises
    ------
    pyagrum.InvalidArc
      If the description requires a directed arc but the target graph type does
      not support arcs, or if the description is malformed.
    pyagrum.InvalidEdge
      If the description requires an undirected edge but the target graph type
      does not support edges.

    Parameters
    ----------
    desc : str
      the string containing the dot-like specification

    Returns
    -------
    pyagrum.DiGraph or pyagrum.UndiGraph or pyagrum.MixedGraph or pyagrum.DAG or pyagrum.PDAG

    Examples
    --------
    >>> pyagrum.fastDiGraph('A->B->C;B->E')
    >>> pyagrum.fastUndiGraph('A-B-C')
    >>> pyagrum.fastMixedGraph('A->B-C')
    >>> pyagrum.fastDAG('A->B->C')
    >>> pyagrum.fastPDAG('A->B-C')

    """
    return _pyagrum.fastDAG(desc)

def fastPDAG(desc: str) -> "pyagrum.PDAG":
    r"""

    Build a graph from a dot-like syntax : ``'A->B->C;B->E'``.

    ``'->'`` denotes a directed arc, ``'<-'`` a directed arc in the other direction
    (e.g. ``'A->B<-C'`` means both A and C point to B), ``'-'`` an undirected edge,
    ``';'`` separates independent chains. If every node token in the description is
    a non-negative integer, those integers are used directly as node ids ;
    otherwise every token -- including numeric-looking ones -- is used as a node
    name.

    Note
    ----
    A single ``'-'`` (not ``'--'``) is used for edges on purpose : ``pyagrum.fastMRF``
    already uses ``'--'`` to list the variables of a single factor (a clique), a
    different construct from a chain of pairwise edges. ``'A--B'`` is therefore
    rejected here rather than silently misread.

    Raises
    ------
    pyagrum.InvalidArc
      If the description requires a directed arc but the target graph type does
      not support arcs, or if the description is malformed.
    pyagrum.InvalidEdge
      If the description requires an undirected edge but the target graph type
      does not support edges.

    Parameters
    ----------
    desc : str
      the string containing the dot-like specification

    Returns
    -------
    pyagrum.DiGraph or pyagrum.UndiGraph or pyagrum.MixedGraph or pyagrum.DAG or pyagrum.PDAG

    Examples
    --------
    >>> pyagrum.fastDiGraph('A->B->C;B->E')
    >>> pyagrum.fastUndiGraph('A-B-C')
    >>> pyagrum.fastMixedGraph('A->B-C')
    >>> pyagrum.fastDAG('A->B->C')
    >>> pyagrum.fastPDAG('A->B-C')

    """
    return _pyagrum.fastPDAG(desc)

def randomDistribution(n: int) -> tuple[float, ...]:
    r"""

    Parameters
    ----------
    n : int
      The number of modalities for the ditribution.

    Returns
    -------
    a random discrete distribution.

    """
    return _pyagrum.randomDistribution(n)
class DiscretizedVariable(IDiscretizedVariable):
    r"""

    DiscretizedVariable is a discrete random variable with a set of `ticks` defining intervals.

    DiscretizedVariable(aName, aDesc ,ticks=None,is_empirical=False) -> pyagrum.DiscretizedVariable Parameters:

            - **aName** (``str``) -- the name of the variable
            - **aDesc** (``str``) -- the description of the variable
            - **ticks** (``list[float]``) -- the list of ticks to add
            - **is_empirical** (``bool``) -- if False, raise an error if a value is out of bound.


    DiscretizedVariable(aDDRV) -> DiscretizedVariable
        Parameters:

            - **aDDRV** (:class:`pyagrum.DiscretizedVariable`) -- the pyagrum.DiscretizedVariable that will be copied

    Examples
    --------
    >>> import pyagrum as gum
    >>> vX=pyagrum.DiscretizedVariable('X','X has been discretized').addTick(1).addTick(2).addTick(3).addTick(3.1415)
    >>> print(vX)
    X:Discretized(<[1;2[,[2;3[,[3;3.1415]>)
    >>> vX.isTick(4)
    False
    >>> vX.labels()
    ('[1;2[', '[2;3[', '[3;3.1415]')
    >>> # where is the real value 2.5 ?
    >>> vX.index('2.5')
    1

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args):
        _pyagrum.DiscretizedVariable_swiginit(self, _pyagrum.new_DiscretizedVariable(*args))
    __swig_destroy__ = _pyagrum.delete_DiscretizedVariable

    def clone(self) -> "pyagrum.DiscretizedVariable":
        r"""

        Returns
        -------
        pyagrum.DiscretizedVariable
            a copy of the DiscretizedVariable

        """
        return _pyagrum.DiscretizedVariable_clone(self)

    def varType(self) -> int:
        r"""

        returns the type of variable

        Returns
        -------
        int :
            the type of the variable.

            0: DiscretizedVariable, 1: LabelizedVariable, 2: IntegerVariable, 3: NumericalDiscreteVariable, 4: RangeVariable

        """
        return _pyagrum.DiscretizedVariable_varType(self)

    def isTick(self, aTick: float) -> bool:
        r"""

        Parameters
        ----------
        aTick : float
            the Tick to be tested

        Returns
        -------
        bool :
            True if the Tick already exists

        """
        return _pyagrum.DiscretizedVariable_isTick(self, aTick)

    def addTick(self,*args):
        """
        Parameters
        ----------
        aTick : double
            the Tick to be added

        Returns
        -------
        pyagrum.DiscretizedVariable
            the discretized variable

        Raises
        ------
          pyagrum.DefaultInLabel
            If the tick is already defined
        """
        _pyagrum.DiscretizedVariable_addTick(self,*args)
        return self



    def eraseTicks(self) -> None:
        r"""

        erase all the Ticks

        """
        return _pyagrum.DiscretizedVariable_eraseTicks(self)

    def label(self, i: int) -> str:
        r"""

        Parameters
        ----------
        i : int
            the index of the label we wish to return

        Returns
        -------
        str
            the label at the given index

        Raises
        ------
        pyagrum.OutOfBounds
            If the variable does not contain the label

        """
        return _pyagrum.DiscretizedVariable_label(self, i)

    def numerical(self, indice: int) -> float:
        r"""

        Parameters
        ----------
        index : int
            an index

        Returns
        -------
        float
            the numerical representation of the value at the given index

        """
        return _pyagrum.DiscretizedVariable_numerical(self, indice)

    def draw(self, indice: int) -> float:
        r"""

        Allow to draw a value in the i-th interval of the discretized variable.1

        Parameters
        ----------
        i : int
            the index of the interval to draw

        Returns
        -------
        float :
            the value randomly drawn in the i-th interval

        """
        return _pyagrum.DiscretizedVariable_draw(self, indice)

    def index(self, *args) -> int:
        r"""

        Parameters
        ----------
        label : str
            a label

        Returns
        -------
        int
            the index of the label

        """
        return _pyagrum.DiscretizedVariable_index(self, *args)

    def domainSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of modalities in the variable domain

        """
        return _pyagrum.DiscretizedVariable_domainSize(self)

    def domain(self) -> str:
        r"""

        Returns
        -------
        str
            the domain of the variable as a string

        """
        return _pyagrum.DiscretizedVariable_domain(self)

    def stype(self) -> str:
        r"""

        Returns
        -------
        str
            a description of its type

        """
        return _pyagrum.DiscretizedVariable_stype(self)

    def tick(self, i: int) -> float:
        r"""

        Indicate the index of the Tick

        Parameters
        ----------
        i : int
            the index of the Tick

        Returns
        -------
        aTick : float
            the i-th Tick

        Raises
        ------
        pyagrum.NotFound
            If the index is greater than the number of Ticks

        """
        return _pyagrum.DiscretizedVariable_tick(self, i)

    def ticks(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple :
            a tuple containing all the Ticks

        """
        return _pyagrum.DiscretizedVariable_ticks(self)

    def closestIndex(self, val: float) -> int:
        r"""

        For numerical discrete variables (all except pyagrum.LabelizedVariable), this method returns the index of the closest value to a given float value in the variable's domain.

        Parameters
        ----------
        val : float
          the value for which we want to find the closest index
        Returns
        -------
        int
          the index of the closest value to `value` in the variable's domain

        Raises
        ------
        pyagrum.NotImplementedYet
          if the variable is not numerical discrete (i.e., if it is a pyagrum.LabelizedVariable).

        """
        return _pyagrum.DiscretizedVariable_closestIndex(self, val)

    def toFast(self) -> str:
        r"""

        Return the variable description in fast syntax (e.g. ``name[v1:v2:v3]``).

        Returns
        -------
        str
            fast-syntax string representation

        """
        return _pyagrum.DiscretizedVariable_toFast(self)

    def __repr__(self) -> str:
        return _pyagrum.DiscretizedVariable___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.DiscretizedVariable___str__(self)

# Register DiscretizedVariable in _pyagrum:
_pyagrum.DiscretizedVariable_swigregister(DiscretizedVariable)
class MultiDimContainer(object):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args, **kwargs):
        raise AttributeError("No constructor defined - class is abstract")
    __repr__ = _swig_repr
    __swig_destroy__ = _pyagrum.delete_MultiDimContainer

    def set(self, i: "pyagrum.Instantiation", value: float) -> None:
        return _pyagrum.MultiDimContainer_set(self, i, value)

    def get(self, i: "pyagrum.Instantiation") -> float:
        return _pyagrum.MultiDimContainer_get(self, i)

    def fill(self, d: float) -> None:
        return _pyagrum.MultiDimContainer_fill(self, d)

    def populate(self, *args) -> None:
        return _pyagrum.MultiDimContainer_populate(self, *args)

    def copyFrom(self, *args) -> None:
        return _pyagrum.MultiDimContainer_copyFrom(self, *args)

    def extractFrom(self, src: "pyagrum.Tensor", mask: "pyagrum.Instantiation") -> None:
        return _pyagrum.MultiDimContainer_extractFrom(self, src, mask)

    def content(self, *args) -> "pyagrum.Tensor":
        return _pyagrum.MultiDimContainer_content(self, *args)

    def getMasterRef(self, *args) -> "pyagrum.Tensor":
        return _pyagrum.MultiDimContainer_getMasterRef(self, *args)

    def copy(self, src: "pyagrum.Tensor") -> None:
        return _pyagrum.MultiDimContainer_copy(self, src)

    def toString(self, *args) -> str:
        return _pyagrum.MultiDimContainer_toString(self, *args)

    def __eq__(self, p: "pyagrum.Tensor") -> bool:
        return _pyagrum.MultiDimContainer___eq__(self, p)

    def apply(self, f: "std::function< float (float) >") -> None:
        return _pyagrum.MultiDimContainer_apply(self, f)

    def reduce(self, f: "std::function< float (float,float) >", base: float) -> float:
        return _pyagrum.MultiDimContainer_reduce(self, f, base)

    def beginMultipleChanges(self) -> None:
        return _pyagrum.MultiDimContainer_beginMultipleChanges(self)

    def endMultipleChanges(self, *args) -> None:
        return _pyagrum.MultiDimContainer_endMultipleChanges(self, *args)

# Register MultiDimContainer in _pyagrum:
_pyagrum.MultiDimContainer_swigregister(MultiDimContainer)
class Tensor(object):
    r"""

    Class representing a tensor.

    Tensor() -> Tensor
        default constructor

    Tensor(src) -> Tensor
        Parameters:
            - **src** (``pyagrum.Tensor``) -- the Tensor to copy

    Tensor(v1,v2, ...) -> Tensor
        Parameters:
            - v1,v2... (``pyagrum.DiscreteVariable``) -- the variables to be added to the tensor

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    @staticmethod
    def deterministicTensor(*args) -> "pyagrum.Tensor":
        r"""

        This static method generates a Tensor representing a deterministic function of a :class:`pyagrum.DiscreteVariable`) such as a hard evidence.

        Parameters
        ----------
        var : pyagrum.DiscreteVariable
          the variable to use
        value: int | str
          the indice or the label of the value for the variable

        Returns
        -------
        pyagrum.Tensor
          The representation of the deterministic function as a :class:`pyagrum.Tensor`.

        """
        return _pyagrum.Tensor_deterministicTensor(*args)

    @staticmethod
    def uniformTensor(var: "pyagrum.DiscreteVariable") -> "pyagrum.Tensor":
        r"""

        Create a uniform tensor over a single discrete variable (all values equal to 1/domainSize).

        Parameters
        ----------
        var : pyagrum.DiscreteVariable
            the variable defining the domain

        Returns
        -------
        Tensor
            uniform tensor over var

        """
        return _pyagrum.Tensor_uniformTensor(var)

    def __init__(self, *args):

        vars=[] # checking for python contructor with a list of variables
        if all(isinstance(v, pyagrum.DiscreteVariable) for v in args):
          vars=[x for x in args]
          args=[]


        _pyagrum.Tensor_swiginit(self, _pyagrum.new_Tensor(*args))

        self._list_vars=list()

        for v in vars:
          self.add(v)



    __swig_destroy__ = _pyagrum.delete_Tensor

    def random(self) -> "pyagrum.Tensor":
        r"""

        Fill the tensor with random values uniformly drawn from [0,1].

        Returns
        -------
        Tensor
            self (in-place modification)

        """
        val = _pyagrum.Tensor_random(self)

        return self


        return val


    def randomDistribution(self) -> "pyagrum.Tensor":
        r"""

        Fill the tensor as a random probability distribution (values are non-negative and sum to 1).

        Returns
        -------
        Tensor
            self (in-place modification)

        """
        val = _pyagrum.Tensor_randomDistribution(self)

        return self


        return val


    def randomCPT(self) -> "pyagrum.Tensor":
        r"""

        Fill the tensor as a random conditional probability table (each conditional distribution sums to 1).

        Returns
        -------
        Tensor
            self (in-place modification)

        """
        val = _pyagrum.Tensor_randomCPT(self)

        return self


        return val


    def noising(self, alpha: float) -> "pyagrum.Tensor":
        r"""

        Add noise to a CPT by mixing it with a random CPT: ``(1 - alpha) * self + alpha * randomCPT()``.

        Parameters
        ----------
        alpha : float
            noise level, must be in [0, 1]

        Returns
        -------
        Tensor
            self (in-place modification)

        """
        val = _pyagrum.Tensor_noising(self, alpha)

        return self


        return val


    def isNonZeroMap(self) -> "pyagrum.Tensor":
        r"""

        Returns
        -------
        pyagrum.Tensor
          a boolean-like tensor using the predicate `isNonZero`.

        """
        return _pyagrum.Tensor_isNonZeroMap(self)

    def sum(self) -> float:
        r"""

        Returns
        -------
        float :
          the sum of all elements in the Tensor

        """
        return _pyagrum.Tensor_sum(self)

    def product(self) -> float:
        r"""

        Returns
        -------
        float
          the product of all elements in the Tensor

        """
        return _pyagrum.Tensor_product(self)

    def max(self) -> float:
        r"""

        Returns
        -------
        float
          the maximum of all elements in the Tensor

        """
        return _pyagrum.Tensor_max(self)

    def min(self) -> float:
        r"""

        Returns
        -------
        float
          the min of all elements in the Tensor

        """
        return _pyagrum.Tensor_min(self)

    def maxNonOne(self) -> float:
        r"""

        Returns
        -------
        float
          the maximum of non one elements in the Tensor

        Raises
        ------
        pyagrum.NotFound
          If all value == 1.0

        """
        return _pyagrum.Tensor_maxNonOne(self)

    def minNonZero(self) -> float:
        r"""

        Returns
        -------
        float
          the min of non zero elements in the Tensor

        Raises
        ------
        pyagrum.NotFound
          If all value == 0.0

        """
        return _pyagrum.Tensor_minNonZero(self)

    def findAll(self, v: float) -> list[dict[str,int]]:
        r"""

        Find all the position of a value in the Tensor.

        Parameters
        ----------
        v : float
          the value to find

        Returns
        -------
        list[dict[str,int]]
          a list of all the instantiations (as python dictionary) where the value is found

        """
        return _pyagrum.Tensor_findAll(self, v)

    def entropy(self) -> float:
        r"""

        Returns
        -------
        float
          the entropy of the tensor

        """
        return _pyagrum.Tensor_entropy(self)

    def mean(self) -> float:
        r"""

        Compute the mean of a univariate numerical discrete distribution.

        Raises
        ------
        pyagrum.ArgumentError
          If the tensor is not a 1-dimensional distribution (sum != 1) or if the variable is not numerical.

        Returns
        -------
        float
          the mean of the distribution

        """
        return _pyagrum.Tensor_mean(self)

    def variance(self) -> float:
        r"""

        Compute the variance of a univariate numerical discrete distribution.

        Raises
        ------
        pyagrum.ArgumentError
          If the tensor is not a 1-dimensional distribution (sum != 1) or if the variable is not numerical.

        Returns
        -------
        float
          the variance of the distribution

        """
        return _pyagrum.Tensor_variance(self)

    def stdDev(self) -> float:
        r"""

        Compute the standard deviation of a univariate numerical discrete distribution.

        Raises
        ------
        pyagrum.ArgumentError
          If the tensor is not a 1-dimensional distribution (sum != 1) or if the variable is not numerical.

        Returns
        -------
        float
          the standard deviation of the distribution

        """
        return _pyagrum.Tensor_stdDev(self)

    def reorganize(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a new Tensor with another order.

        Returns
        -------
        varnames : list
          a list of the var names in the new order

        Returns
        -------
        pyagrum.Tensor
          a reference to the modified tensor

        """
        return _pyagrum.Tensor_reorganize(self, *args)

    def putFirst(self, varname: str) -> "pyagrum.Tensor":
        r"""

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
            The variable for which the index should be 0.

        Returns
        -------
        pyagrum.Tensor
          a reference to the modified tensor

        Raises
        ------
        pyagrum.InvalidArgument
          If the var is not in the tensor

        """
        return _pyagrum.Tensor_putFirst(self, varname)

    def fillWith(self, *args) -> "pyagrum.Tensor":
        r"""

        Automatically fills the tensor with v.

        Parameters
        ----------
        v : number or list of values or pyagrum.Tensor or numpy.ndarray
            a value or a list/pyagrum.Tensor/ndarray containing the values to fill the Tensor with.

        mapping : list|tuple|dict

        Warnings
        --------
            - if `v` is a list, the size of the list must be the size of the tensor

            - if `v` is a ref:pyagrum.Tensor, it must contain variables with exactly the same names and labels but not necessarily the same variables. If

            - If the second argument `mapping` is given, `mapping` explains how to map the variables of the tensor source to the variables of the tensor destination.

            - If `mapping` is a sequence, the order follows the same order as `destination.names`. If `mapping` is a dict, the keys are the names in the destination and the values are the names in the source.

            - if `v` is a ``numpy.ndarray``, it must be C-contiguous, dtype ``float64``, and its total size must equal the domain size. If it is N-D, its shape must equal ``tuple(reversed(self.shape))``. Copies the data in a single ``memcpy``.

        Returns
        -------
        pyagrum.Tensor
              a reference to the modified tensor

        Raises
        ------
        pyagrum.SizeError
          If v size's does not matches the domain size.
        pyagrum.ArgumentError
          If anything wrong with the arguments.

        """

        if len(args)==1:
          if isinstance(args[0], numpy.ndarray):
            arr = args[0]
            if arr.ndim > 1:
              expected = tuple(reversed(self.shape))
              if arr.shape != expected:
                raise ValueError(f"[pyAgrum] numpy array shape {arr.shape} does not match Tensor shape {expected}")
            self._fillWithNpArray(arr)
            return self
        if len(args)>1:
          d=args[1]
          if isinstance(d, dict):
            if set(d.keys())==set(self.names):
              return self.fillWith(args[0],[d[s] for s in self.names])
            else:
              raise pyagrum.ArgumentError(f"[pyAgrum] keys in dict {tuple(d.keys())} does not match the Tensor's variables {self.names}")


        val = _pyagrum.Tensor_fillWith(self, *args)

        return self


        return val


    def abs(self) -> "pyagrum.Tensor":
        r"""

        Apply abs on every element of the container

        Returns
        -------
        pyagrum.Tensor
            a reference to the modified tensor.

        """
        val = _pyagrum.Tensor_abs(self)

        return self


        return val


    def sq(self) -> "pyagrum.Tensor":
        r"""

        Square all the values in the Tensor

        """
        val = _pyagrum.Tensor_sq(self)

        return self


        return val


    def log2(self) -> "pyagrum.Tensor":
        r"""

        log2 all the values in the Tensor

        Warnings
        --------
        When the Tensor contains 0 or negative values, no exception are raised but `-inf` or `nan` values are assigned.

        """
        val = _pyagrum.Tensor_log2(self)

        return self


        return val


    def sgn(self) -> "pyagrum.Tensor":
        r"""

        Apply the sign function element-wise in place: -1 if value < 0, 0 if value == 0, 1 if value > 0.

        Returns
        -------
        Tensor
            self (in-place modification)

        """
        val = _pyagrum.Tensor_sgn(self)

        return self


        return val


    def new_abs(self) -> "pyagrum.Tensor":
        r"""

        Return a new tensor with the absolute value applied to each element.

        Returns
        -------
        Tensor
            new tensor

        """
        return _pyagrum.Tensor_new_abs(self)

    def new_sq(self) -> "pyagrum.Tensor":
        r"""

        Return a new tensor with each element squared.

        Returns
        -------
        Tensor
            new tensor

        """
        return _pyagrum.Tensor_new_sq(self)

    def new_log2(self) -> "pyagrum.Tensor":
        r"""

        Return a new tensor with the base-2 logarithm applied to each element.

        Returns
        -------
        Tensor
            new tensor

        """
        return _pyagrum.Tensor_new_log2(self)

    def new_sgn(self) -> "pyagrum.Tensor":
        r"""

        Return a new tensor with the sign function applied element-wise: -1 if value < 0, 0 if value == 0, 1 if value > 0.

        Returns
        -------
        Tensor
            new tensor

        """
        return _pyagrum.Tensor_new_sgn(self)

    def normalize(self) -> "pyagrum.Tensor":
        r"""

        Normalize the Tensor (do nothing if sum is 0)

        Returns
        -------
        pyagrum.Tensor
          a reference to the normalized Tensor

        """
        val = _pyagrum.Tensor_normalize(self)

        return self


        return val


    def KL(self, p: "Tensor") -> float:
        r"""

        Check the compatibility and compute the Kullback-Leibler divergence between the tensor and `p`.

        Parameters
        ----------
        p : pyagrum.Tensor
          the tensor from which we want to calculate the divergence.

        Returns
        -------
        float
          The value of the divergence

        Raises
        ------
          pyagrum.InvalidArgument
            If p is not compatible with the tensor (dimension, variables)
          pyagrum.FatalError
            If a zero is found in p or the tensor and not in the other.

        """
        return _pyagrum.Tensor_KL(self, p)

    def normalizeAsCPT(self, *args) -> "pyagrum.Tensor":
        r"""

        Normalize the Tensor as a CPT

        Returns
        -------
        pyagrum.Tensor
          a reference to the normalized Tensor

        Raises
        ------
        pyagrum.FatalError
          If some distribution sums to 0

        """
        val = _pyagrum.Tensor_normalizeAsCPT(self, *args)

        return self


        return val


    def scale(self, v: float) -> "pyagrum.Tensor":
        r"""

        Create a new tensor multiplied by v.

        Parameters
        ----------
        v : float
          a multiplier

        Returns
        -------
          a reference to the modified tensor

        """
        val = _pyagrum.Tensor_scale(self, v)

        return self


        return val


    def translate(self, v: float) -> "pyagrum.Tensor":
        r"""

        Create a new tensor added with v.

        Parameters
        ----------
        v : float
          The value to be added

        Returns
        -------
          a reference to the modified tensor

        """
        val = _pyagrum.Tensor_translate(self, v)

        return self


        return val


    def inverse(self) -> "pyagrum.Tensor":
        r"""

        Invert each value of the tensor in place (replace x by 1/x for each element).

        Returns
        -------
        Tensor
            self (in-place modification)

        """
        val = _pyagrum.Tensor_inverse(self)

        return self


        return val


    def draw(self) -> int:
        r"""

        draw a value using the tensor as a probability table.

        Returns
        -------
        int
          the index of the drawn value

        """
        return _pyagrum.Tensor_draw(self)

    def memoryFootprint(self) -> int:
        r"""

        get the size (in byte) of the Tensor representation in memory

        Returns
        -------
        int
          the size in byte of the representation of the Tensor in memory.

        """
        return _pyagrum.Tensor_memoryFootprint(self)

    def __add__(self, *args) -> "pyagrum.Tensor":
        return _pyagrum.Tensor___add__(self, *args)

    def __sub__(self, *args) -> "pyagrum.Tensor":
        return _pyagrum.Tensor___sub__(self, *args)

    def __mul__(self, *args) -> "pyagrum.Tensor":
        return _pyagrum.Tensor___mul__(self, *args)

    def __truediv__(self, *args):
        return _pyagrum.Tensor___truediv__(self, *args)



    def __iadd__(self, *args) -> "pyagrum.Tensor":
        return _pyagrum.Tensor___iadd__(self, *args)

    def __imul__(self, *args) -> "pyagrum.Tensor":
        return _pyagrum.Tensor___imul__(self, *args)

    def __isub__(self, *args) -> "pyagrum.Tensor":
        return _pyagrum.Tensor___isub__(self, *args)

    def __itruediv__(self, *args):
        return _pyagrum.Tensor___itruediv__(self, *args)
    __idiv__ = __itruediv__



    def isEvidence(self) -> bool:
        r"""

        Check whether the tensor represents a likelihood (soft evidence) over a single variable.

        A tensor is an evidence if it has exactly one variable, all values are in [0, 1], and the sum is positive.

        Returns
        -------
        bool
            True if the tensor is a valid likelihood vector

        """
        return _pyagrum.Tensor_isEvidence(self)

    def __or__(self, p2: "Tensor") -> "pyagrum.Tensor":
        return _pyagrum.Tensor___or__(self, p2)

    def __and__(self, p2: "Tensor") -> "pyagrum.Tensor":
        return _pyagrum.Tensor___and__(self, p2)

    def __invert__(self) -> "pyagrum.Tensor":
        return _pyagrum.Tensor___invert__(self)

    @staticmethod
    def evEq(v: "pyagrum.DiscreteVariable", val: float) -> "pyagrum.Tensor":
        r"""

        This static method generates a Tensor representing an observation where a quasi-continuous variable
        (a :class:`pyagrum.DiscretizedVariable` with many ticks) takes a specific given value.

        Notes
        -----
          - see also :meth:`BayesNet.evEq`
          - see also :meth:`Tensor.evGt`, :meth:`Tensor.evLt`, :meth:`Tensor.evIn`

        Examples
        --------
        >>> A=pyagrum.fastVariable('A[0:10:20]')
        >>> p=pyagrum.Tensor.evEq(A,5)
        >>> p
        (pyagrum.Tensor@000001D7FAB06AD0)
          A                                                                                                                                                                                                    |
        [0;0.5[  |[0.5;1[  |[1;1.5[  |[1.5;2[  |[2;2.5[  |[2.5;3[  |[3;3.5[  |[3.5;4[  |[4;4.5[  |[4.5;5[  |[5;5.5[  |[5.5;6[  |[6;6.5[  |[6.5;7[  |[7;7.5[  |[7.5;8[  |[8;8.5[  |[8.5;9[  |[9;9.5[  |[9.5;10] |
        ---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
         0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 1.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  |

        Parameters
        ----------
        var : pyagrum.DiscretizedVariable
          the variable to use
        value: float

        Returns
        -------
        pyagrum.Tensor
          The representation of the evidence as a :class:`~pyagrum.Tensor`.

        """
        return _pyagrum.Tensor_evEq(v, val)

    @staticmethod
    def evIn(v: "pyagrum.DiscreteVariable", val1: float, val2: float) -> "pyagrum.Tensor":
        r"""

        This static method generates a Tensor representing an observation where a quasi-continuous variable
        (a :class:`pyagrum.DiscretizedVariable` with many ticks) takes a value between the 2 paramerts (min,max)

        Notes
        -----
          - see also :meth:`BayesNet.evIn`
          - see also :meth:`Tensor.evEq`, :meth:`Tensor.evLt`, :meth:`Tensor.evGt`

        Parameters
        ----------
        var : pyagrum.DiscretizedVariable
          the variable to use
        valueMin: float
          the minimum value
        valueMax: float
          the maximum value

        Returns
        -------
        pyagrum.Tensor
          The representation of the evidence as a :class:`~pyagrum.Tensor`.

        """
        return _pyagrum.Tensor_evIn(v, val1, val2)

    @staticmethod
    def evGt(v: "pyagrum.DiscreteVariable", val: float) -> "pyagrum.Tensor":
        r"""

        This static method generates a Tensor representing an observation where a quasi-continuous variable
        (a :class:`pyagrum.DiscretizedVariable` with many ticks) takes a value greater than the parameter.

        Notes
        -----
          - see also :meth:`BayesNet.evGt`
          - see also :meth:`Tensor.evEq`, :meth:`Tensor.evLt`, :meth:`Tensor.evIn`

        Examples
        --------
        >>> A=pyagrum.fastVariable('A[0:10:20]')
        >>> p=pyagrum.Tensor.evGt(A,5)
        >>> p
        (pyagrum.Tensor@000001D7FAB06AD0)
          A                                                                                                                                                                                                    |
        [0;0.5[  |[0.5;1[  |[1;1.5[  |[1.5;2[  |[2;2.5[  |[2.5;3[  |[3;3.5[  |[3.5;4[  |[4;4.5[  |[4.5;5[  |[5;5.5[  |[5.5;6[  |[6;6.5[  |[6.5;7[  |[7;7.5[  |[7.5;8[  |[8;8.5[  |[8.5;9[  |[9;9.5[  |[9.5;10] |
        ---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
         0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 1.0000  | 1.0000  | 1.0000  | 1.0000  | 1.0000  | 1.0000  | 1.0000  | 1.0000  | 1.0000  | 1.0000  |

        Parameters
        ----------
        var : pyagrum.DiscretizedVariable
          the variable to use
        value: float

        Returns
        -------
        pyagrum.Tensor
          The representation of the evidence as a :class:`~pyagrum.Tensor`.

        """
        return _pyagrum.Tensor_evGt(v, val)

    @staticmethod
    def evLt(v: "pyagrum.DiscreteVariable", val: float) -> "pyagrum.Tensor":
        r"""

        This static method generates a Tensor representing an observation where a quasi-continuous variable
        (a :class:`pyagrum.DiscretizedVariable` with many ticks) takes a value less than the parameter.

        Notes
        -----
          - see also :meth:`BayesNet.evLt`
          - see also :meth:`Tensor.evEq`, :meth:`Tensor.evGt`, :meth:`Tensor.evIn`

        Parameters
        ----------
        var : pyagrum.DiscretizedVariable
          the variable to use
        value: float

        Returns
        -------
        pyagrum.Tensor
          The representation of the evidence as a :class:`~pyagrum.Tensor`.

        """
        return _pyagrum.Tensor_evLt(v, val)

    def __repr__(self) -> str:
        return _pyagrum.Tensor___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.Tensor___str__(self)

    def expectedValue(self, *args) -> float:
        r"""

        Calculate the mathematical expected value of a (joint) random variable using the given function as an argument.

        Parameters
        ----------
        func : function(dict)->float
            A function that takes a single argument, representing the value of a python representation of a `pyagrum.Instantiation` (as a dictionary), and returns a float.

        Warnings
        --------
        The `pyagrum.Tensor` is assumed to contain a joint distribution.

        Examples
        --------
        >>> def log2cptA(x):
        ...   return -math.log2(bn.cpt('A')[x])
        >>> entropy_of_A=bn.cpt('A').expectedValue(log2cptA) # OK it A has no parents.

        Returns
        -------
        float
            The mathematical expected value of the random variable calculated using the given function as an argument.

        """
        return _pyagrum.Tensor_expectedValue(self, *args)

    def extract(self, *args) -> "pyagrum.Tensor":
        r"""

        create a new Tensor extracted from self given a partial instantiation.

        Parameters
        ----------
        inst : pyagrum.instantiation
          a partial instantiation
        dict : dict
          a dictionnary containing values for some discrete variables.

        Warnings
        --------
            if the dictionnary contains a key that is not the name of a variable in the `pyagrum.Tensor`,
            this key is just not used without notification. Then `pyagrum.Tensor.extract` concerns
            only the variables that  both are in the Tensor and in the dictionnary.

        Returns
        -------
        pyagrum.Tensor
          the new Tensor

        """
        return _pyagrum.Tensor_extract(self, *args)

    def sumOut(self, *args) -> "pyagrum.Tensor":
        r"""

        Projection using sum as operation.

        Parameters
        ----------
        varnames : set
          the set of vars to eliminate

        Returns
        -------
        pyagrum.Tensor
          the projected Tensor

        Raises
        ------
        pyagrum.InvalidArgument
          If varnames contains only one variable that does not exist in the Tensor

        """
        return _pyagrum.Tensor_sumOut(self, *args)

    def prodOut(self, *args) -> "pyagrum.Tensor":
        r"""

        Projection using multiplication as operation.

        Parameters
        ----------
        varnames : set
          the set of vars to eliminate

        Returns
        -------
        pyagrum.Tensor
          the projected Tensor

        Raises
        ------
        pyagrum.InvalidArgument
          If varnames contains only one variable that does not exist in the Tensor

        """
        return _pyagrum.Tensor_prodOut(self, *args)

    def maxOut(self, *args) -> "pyagrum.Tensor":
        r"""

        Projection using `max` as operation.

        Parameters
        ----------
        varnames : set
          the set of vars to eliminate

        Returns
        -------
        pyagrum.Tensor
          the projected Tensor

        Raises
        ------
        pyagrum.InvalidArgument
          If varnames contains only one variable that does not exist in the Tensor

        """
        return _pyagrum.Tensor_maxOut(self, *args)

    def minOut(self, *args) -> "pyagrum.Tensor":
        r"""

        Projection using `min` as operation.

        Parameters
        ----------
        varnames : set
          the set of vars to eliminate

        Returns
        -------
        pyagrum.Tensor
          the projected Tensor

        Warnings
        --------
        InvalidArgument raised if varnames contains only one variable that does not exist in the Tensor

        """
        return _pyagrum.Tensor_minOut(self, *args)

    def sumIn(self, *args) -> "pyagrum.Tensor":
        r"""

        Projection using sum as operation.

        Parameters
        ----------
        varnames : set
          the set of vars to keep

        Returns
        -------
        pyagrum.Tensor
          the projected Tensor

        """
        return _pyagrum.Tensor_sumIn(self, *args)

    def prodIn(self, *args) -> "pyagrum.Tensor":
        r"""

        Projection using multiplication as operation.

        Parameters
        ----------
        varnames : set
          the set of vars to keep

        Returns
        -------
        pyagrum.Tensor
          the projected Tensor

        """
        return _pyagrum.Tensor_prodIn(self, *args)

    def maxIn(self, *args) -> "pyagrum.Tensor":
        r"""

        Projection using `max` as operation.

        Parameters
        ----------
        varnames : set
          the set of vars to keep

        Returns
        -------
        pyagrum.Tensor
          the projected Tensor

        """
        return _pyagrum.Tensor_maxIn(self, *args)

    def minIn(self, *args) -> "pyagrum.Tensor":
        r"""

        Projection using `min` as operation.

        Parameters
        ----------
        varnames : set
          the set of vars to keep

        Returns
        -------
        pyagrum.Tensor
          the projected Tensor

        """
        return _pyagrum.Tensor_minIn(self, *args)

    def argmin(self) -> tuple[list["pyagrum.Instantiation"], float]:
        r"""

        Returns
        -------
        tuple
          the list of positions of the min and the min of all elements in the Tensor

        """
        return _pyagrum.Tensor_argmin(self)

    def argmax(self) -> tuple[list["pyagrum.Instantiation"], float]:
        r"""

        Returns
        -------
        tuple
          the list of positions of the max and the max of all elements in the Tensor

        """
        return _pyagrum.Tensor_argmax(self)

    def __eq__(self, *args) -> bool:
        return _pyagrum.Tensor___eq__(self, *args)

    def __ne__(self, b: "Tensor") -> bool:
        return _pyagrum.Tensor___ne__(self, b)

    def _as_nparray_raw(self, self_pyobj: object) -> "numpy.ndarray":
        return _pyagrum.Tensor__as_nparray_raw(self, self_pyobj)

    def _toarray_raw(self) -> "numpy.ndarray":
        return _pyagrum.Tensor__toarray_raw(self)

    def _fillWithNpArray(self, arr: object) -> None:
        return _pyagrum.Tensor__fillWithNpArray(self, arr)

    def __radd__(self,other):
      return self.__add__(other)

    def __rmul__(self,other):
      return self.__mul__(other)

    def __rsub__(self,other):
      return (self*-1)+other

    def __rfloordiv__(self,other):
      return Tensor(self).inverse().scale(other)

    def __rtruediv__(self,other):
      return Tensor(self).inverse().scale(other)

    def __rdiv__(self,other):
      return Tensor(self).inverse().scale(other)

    def __neg__(self):
      return -1*self

    def __abs__(self):
      return Tensor(self).abs()

    def loopIn(self):
      """
      Generator to iterate inside a Tensor.

      Yield an pyagrum.Instantiation that iterates over all the possible values for the pyagrum.Tensor

      Examples
      --------
      >>> import pyagrum as gum
      >>> bn=pyagrum.fastBN("A[3]->B[3]<-C[3]")
      >>> for i in bn.cpt("B").loopIn():
            print(i)
            print(bn.cpt("B").get(i))
            bn.cpt("B").set(i,0.3)
      """
      i=Instantiation(self)
      i.setFirst()
      while not i.end():
        yield i
        i.inc()
      return

    def fillFromExpression(self,s_fn):
      """
      Automatically fills the tensor with the evaluation of the expression s_fn (no matter if is a CPT or not).

      The symbolic expression s_fn gives a value for each parameters of the Tensor

      Examples
      --------
      >>> import pyagrum as gum
      >>> bn=pyagrum.fastBN('A[3]->B[3]<-C[3]')
      >>> bn.cpt('B').fillFromFunction('(B+A+C)/2')

      Parameters
      ----------
      s_fn : str
          a symbolic expression using the name of the variables of the Tensor and giving a value to the first variable of the Tensor. This evaluation is done in a context that inclides 'math' module.

      Warning
      -------
          The expression may have any numerical values, but will be then transformed to the closest correct value for the range of the variable.


      Returns
      -------
      pyagrum.Tensor
            a reference to the modified tensor
      """
      import math
      forbidden=frozenset(['__import__','__class__'])

      code=float(s_fn) if isinstance(s_fn, (int, float)) else compile(s_fn,"<string>","eval")
      if not isinstance(s_fn, (int, float)):
        if forbidden & set(code.co_names):
          raise InvalidArgument("[pyAgrum] '__import__' is not allowed in the expression '"+s_fn+"'")

      if isinstance(s_fn, (int, float)):
        self.fillWith(float(s_fn))
        return self

      arr=numpy.empty(self.domainSize(), dtype=numpy.float64)
      for idx,I in enumerate(self.loopIn()):
        vars={self.variable(i).name():self.variable(i).numerical(I.val(i)) for i in range(self.nbrDim())}
        arr[idx]=eval(code,{'math':math},vars)
      self.fillWith(arr)
      return self

    def fillFromFunction(self,s_fn):
      """
      Automatically fills the tensor as a deterministic CPT with the evaluation of the expression s_fn.

      The symbolic expression s_fn gives a value for the first variable, depending on the following variables.
      The computed CPT is deterministic.

      Examples
      --------
      >>> import pyagrum as gum
      >>> bn=pyagrum.fastBN('A[3]->B[3]<-C[3]')
      >>> bn.cpt('B').fillFromFunction('(A+C)/2')

      Parameters
      ----------
      s_fn : str
          a symbolic expression using the name of the second and following variables of the Tensor and giving a value to the first variable of the Tensor. This evaluation is done in a context that inclides 'math' module.

      Warning
      -------
          The expression may have any numerical values, but will be then transformed to the closest correct value for the range of the variable.


      Returns
      -------
      pyagrum.Tensor
            a reference to the modified tensor

      Raises
      ------
        pyagrum.InvalidArgument
          If the first variable is Labelized.
      """
      import math
      forbidden=frozenset(['__import__','__class__'])

      if self.variable(0).varType() == pyagrum.VarType_LABELIZED:
        raise InvalidArgument("[pyAgrum] The variable "+self.variable(0).name()+" is a LabelizedVariable")

      self.fillWith(0)
      I = Instantiation(self)
      code=float(s_fn) if isinstance(s_fn, (int, float)) else compile(s_fn,"<string>","eval")
      if not isinstance(s_fn, (int, float)):
        if forbidden & set(code.co_names):
          raise InvalidArgument("[pyAgrum] '__import__' is not allowed in the expression '"+s_fn+"'")

      I.setFirst()
      while not I.end():
        vars={self.variable(i).name():self.variable(i).numerical(I.val(i)) for i in range(1,self.nbrDim())}
        res=s_fn if isinstance(s_fn, (int, float)) else eval(code,{'math':math},vars)
        pos=self.variable(0).closestIndex(res)
        I.chgVal(0,pos)
        self.set(I,1)
        I.incNotVar(self.variable(0))

      self.normalizeAsCPT()
      return self


    def fillFromDistribution(self,distribution,**s_fns):
      """
      Automatically fills the tensor as a familly of distributions whose parameters are found using evaluation of the expressions s_fns.

      The symbolic expressions s_fns gives a value for the named parameters of the distributions.

      Examples
      --------
      >>> import scipy.stats as stats
      >>> import pyagrum as gum
      >>> bn=pyagrum.fastBN('A[10]->B[10]<-C[10]')
      >>> bn.cpt("B").fillFromDistribution(stats.norm,loc="(A+C)/2",scale=1)

      Parameters
      ----------
      s_fns : a list of named arguments (str)
          the named arguments with an evaluation of the expressions in s_fns are passed as argument for the chosen distribution.

      Returns
      -------
      pyagrum.Tensor
            a reference to the modified tensor

      Raises
      ------
        pyagrum.InvalidArgument
          If the first variable is Labelized.
      """
      import math

      forbidden=frozenset(['__import__','__class__'])

      var=self.variable(0)
      var_ds=var.domainSize()
      if var.varType() == pyagrum.VarType_LABELIZED:
        raise InvalidArgument("[pyAgrum] The variable "+self.variable(0).name()+" is a LabelizedVariable")

      codes={k:float(s_fns[k]) if isinstance(s_fns[k], (int, float)) else compile(s_fns[k],"<string>","eval") for k in s_fns.keys()}
      for _,code in codes.items():
        if not isinstance(code, (int, float)):
          if forbidden & set(code.co_names):
            raise InvalidArgument("[pyAgrum] '__import__' is not allowed in the expression '"+code+"'")

      if hasattr(distribution,'pdf'):
        d=distribution.pdf
      elif hasattr(distribution,'pmf'):
        d=distribution.pmf
      else:
        raise InvalidArgument("[pyAgrum] The distribution must have a pdf or a pmf method")

      Xs=numpy.array([var.numerical(i) for i in range(var_ds)], dtype=numpy.float64)

      I=pyagrum.Instantiation()
      for i in range(1,self.nbrDim()):
        I.add(self.variable(i))

      args={}
      fnkeys=set(s_fns.keys())
      for k in s_fns.keys():
          if isinstance(s_fns[k], (int, float)):
              args[k]=float(s_fns[k])
              fnkeys.remove(k)

      arr=numpy.empty(self.domainSize(), dtype=numpy.float64)
      for pos,J in enumerate(I.loopIn()):
          vars={J.variable(i).name():J.variable(i).numerical(J.val(i)) for i in range(J.nbrDim())}
          for k in fnkeys:
              args[k]=eval(codes[k],{'math':math},vars)
          arr[pos*var_ds:(pos+1)*var_ds]=d(Xs,**args)
      self.fillWith(arr)
      self.normalizeAsCPT()
      return self

    def toVarsIn(self,p):
      """
      Create a copy of the Tensor with the same variables as in p.

      Warning
      -------
      p is a pyAgrum's object that can refer to variables through a method `p.variable(name:str)`. For instance, a Potential, an Instantiation or a Graphical Model (Bayesian Network,...).

      Examples
      --------
        >>> import pyagrum as gum
        >>> bn1=pyagrum.fastBN('A[3]->B[3]<-C[3]')
        >>> bn2=pyagrum.fastBN('A[3]<-B[3]<-C[3]')
        >>> # bn1.cpt('A')+bn2.cpt('A') # does not work since the vars 'A' in bn1 and bn2 are not the same.
        >>> bn1.cpt('A').toVars(bn2)+bn2.cpt('A') # OK

      Returns
      -------
        pyagrum.Tensor
            a copy of the Potential with the same variables as p.
      """
      res=pyagrum.Tensor()
      for i in self.names:
        res.add(p.variable(i))
      res.fillWith(self)
      return res

    def variablesSequence(self):
        """
        Returns
        -------
        list
            a list containing the sequence of variables
        """
        varlist = []
        for i in range(0, self.nbrDim()):
            varlist.append(self.variable(i))
        return varlist

    def __prepareIndices__(self,ind):
      """
      From an indice (dict or tuple), returns a triple (inst, loopvars, varlist) where
      varlist is [loopvars.variable(k-1) for k in range(loopvars.nbrDim(), 0, -1)].
      """
      from numbers import Number

      loopvars=Instantiation(self)
      loopvars.setMutable()

      inst=Instantiation(self)
      inst.setFirst()

      if isinstance(ind, (Number,slice)):
        i = tuple([ind])
      else:
        i = ind

      if isinstance(i,dict):
          for nam in self.names:
              if nam in i:
                  inst.chgVal(nam,i[nam])
                  loopvars.erase(nam)
      elif isinstance(i,tuple):
          vn=[n for n in reversed(self.names)]
          if len(i)>self.nbrDim():
              raise KeyError("Too many values in '"+str(i)+"' for '"+str(self)+"'")
          for k,v in enumerate(i):
              if not isinstance(v,slice):
                  nam=vn[k]
                  inst.chgVal(nam,v)
                  loopvars.erase(nam)
      else:
          raise ValueError("No subscript using '"+str(i)+"'")
      varlist=[loopvars.variable(k-1) for k in range(loopvars.nbrDim(),0,-1)]
      return inst,loopvars,varlist

    def __getitem__(self, id):
      if isinstance(id,Instantiation):
          return self.get(id)

      inst,loopvars,varlist=self.__prepareIndices__(id)

      if loopvars.nbrDim()==0:
          return self.get(inst)

      if loopvars.nbrDim()==self.nbrDim():
        try:
          return self.as_nparray()
        except RuntimeError:
          pass

      names=[v.name() for v in varlist]
      tab=numpy.zeros(tuple(v.domainSize() for v in varlist))
      while not inst.end():
          indice=[inst.val(name) for name in names]
          tab[tuple(indice)]=self.get(inst)
          inst.incIn(loopvars)
      return tab

    def __setitem__(self, id, value):
      from numbers import Number
      if isinstance(id,Instantiation):
          self.set(id,value)
          return

      inst,loopvars,varlist=self.__prepareIndices__(id)

      if loopvars.nbrDim()==0:
          self.set(inst,value)
          return

      if loopvars.nbrDim() == self.nbrDim() and not isinstance(value, dict):
          if isinstance(value, list):
              value = numpy.array(value, dtype=numpy.float64)
          self.fillWith(value)
          return

      if isinstance(value,Number):
        while not inst.end():
            self.set(inst,value)
            inst.incIn(loopvars)
      else:
        if isinstance(value,list):
            value=numpy.array(value, dtype=numpy.float64)
        elif isinstance(value,dict):
            if loopvars.nbrDim()>1:
                raise ArgumentError("The value can be a dict only when specifying 1D-marginal.")
            var=loopvars.variable(0)
            for label in var.labels():
                if label not in value:
                  raise ArgumentError(f"The label '{label}' can not be found in the value.")
            for label in value.keys():
                if not var.isLabel(label):
                  raise ArgumentError(f"The label '{label}' can not be found in variable {var}.")
            value=numpy.array([value[item] for item in var.labels()])
        else:
            if not isinstance(value,numpy.ndarray):
                raise ArgumentError(f"{value} is not a correct value for a tensor.")

        shape=tuple(v.domainSize() for v in varlist)
        if value.shape!=shape:
          raise IndexError("Shape of '"+str(value)+"' is not '"+str(shape)+"'")

        names=[v.name() for v in varlist]
        while not inst.end():
            indice = tuple([inst.val(name) for name in names])
            self.set(inst,float(value[indice]))
            inst.incIn(loopvars)

    def __iter__(self):
        """
        Iterate over the data of the Tensor

        Yield
        -----
        tuple[pyagrum.Instantiation,float]
          The instantiation and the value in the Tensor
        """
        for i in self.loopIn():
            yield i,self.get(i)

    def tolist(self):
        """
        Returns
        -------
        list
            the tensor as a list
        """
        return self.__getitem__({}).tolist()

    def toarray(self):
        """
        Return a copy of the Tensor's data as a shaped numpy array.

        The shape follows the Tensor's variable order: the last variable added
        varies fastest (C order), so the shape is ``tuple(reversed(self.shape))``.

        Returns
        -------
        numpy.ndarray
            New float64 array; modifying it does not affect the Tensor.

        Examples
        --------
        >>> t = pyagrum.Tensor().add(pyagrum.LabelizedVariable("a","",2)).add(pyagrum.LabelizedVariable("b","",3))
        >>> t.fillWith([1,2,3,4,5,6])
        >>> t.toarray()          # shape (3, 2)
        array([[1., 2.],
               [3., 4.],
               [5., 6.]])
        """
        arr = self._toarray_raw()
        if self.nbrDim() > 0:
            arr.shape = tuple(reversed(self.shape))
        return arr

    def as_nparray(self):
        """
        Return a zero-copy numpy view over the Tensor's internal data buffer.

        The returned array shares memory with the Tensor: modifying one modifies
        the other. The Tensor is kept alive as long as the view exists.

        The shape follows the same convention as :meth:`toarray`:
        ``tuple(reversed(self.shape))``.

        Returns
        -------
        numpy.ndarray
            float64 view, shape ``tuple(reversed(self.shape))``, C-contiguous.

        Examples
        --------
        >>> t = pyagrum.Tensor().add(pyagrum.LabelizedVariable("a","",2)).add(pyagrum.LabelizedVariable("b","",3))
        >>> t.fillWith([1,2,3,4,5,6])
        >>> v = t.as_nparray()   # shape (3, 2), shares buffer with t
        >>> v[0, 0] = 99
        >>> t[{"a": 0, "b": 0}]  # 99.0
        99.0
        """
        arr = self._as_nparray_raw(self)
        if self.nbrDim() > 0:
            arr = arr.reshape(tuple(reversed(self.shape)))
        return arr

    def topandas(self):
        """
        Returns
        -------
        pandas.DataFrame
           the tensor as an pandas.DataFrame
        """
        import pandas as pd
        varnames = list(reversed(self.names))
        data = []
        pname = ""
        for inst in self.loopIn():
            d = {k:v for k,v in reversed(inst.todict(True).items())}
            d[pname] = self.get(inst)
            d[pname], d[varnames[-1]] = d[varnames[-1]], d[pname]
            data.append(d)
        cols = varnames[:-1] + [pname]
        return pd.DataFrame(data).set_index(cols).unstack(pname)

    def tolatex(self):
        """
        Render object to a LaTeX tabular.

        Requires to include `booktabs` package in the LaTeX document.

        Returns
        -------
        str
         the tensor as LaTeX string
        """
        return self.topandas().to_latex()

    def toclipboard(self,**kwargs):
        """
        Write a text representation of the tensor to the system clipboard.

        The clipboard content can be pasted into a spreadsheet application.

        Parameters
        ----------
        **kwargs
            Additional keyword arguments passed to :meth:`pandas.DataFrame.to_clipboard`.
        """
        return self.topandas().to_clipboard()

    @property
    def names(self):
        """
        Returns
        -------
        list
            a list containing the name of each variables in the tensor

        Warnings
        --------
            listed in the reverse order of the enumeration order of the variables.
        """
        return tuple([self.variable(i).name() for i in range(self.nbrDim())])

    @property
    def shape(self):
        """
        Returns
        -------
        list
            a list containing the dimensions of each variables in the tensor

        Warnings
        --------
            `p.shape` and `p[:].shape` list the dimensions in different order
        """
        return tuple([self.variable(i).domainSize() for i in range(self.nbrDim())])


    def get(self, i: "pyagrum.Instantiation") -> float:
        r"""

        Parameters
        ----------
        i : pyagrum.Instantiation
          an Instantiation

        Returns
        -------
        float
          the value in the Tensor at the position given by the instantiation

        """
        return _pyagrum.Tensor_get(self, i)

    def set(self, i: "pyagrum.Instantiation", value: float) -> None:
        r"""

        Change the value pointed by i

        Parameters
        ----------
        i : pyagrum.Instantiation
          The Instantiation to be changed
        value : float
          The new value of the Instantiation

        """
        return _pyagrum.Tensor_set(self, i, value)

    def empty(self) -> bool:
        r"""

        Returns
        -------
        bool
            Returns true if no variable is in the tensor.

        """
        return _pyagrum.Tensor_empty(self)

    def pos(self, v: "pyagrum.DiscreteVariable") -> int:
        r"""

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
            The variable for which the index is returned.

        Returns
        -------
        int
            the index of a variable.

        Raises
        ------
        pyagrum.NotFound
          If v is not in this multidimensional matrix.

        """
        return _pyagrum.Tensor_pos(self, v)

    def contains(self, v: "pyagrum.DiscreteVariable") -> bool:
        r"""

        Check whether a variable is in the tensor.

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
            the variable to check.

        Returns
        -------
        bool
            True if the var is in the tensor

        """
        return _pyagrum.Tensor_contains(self, v)

    def variable(self, *args) -> "pyagrum.DiscreteVariable":
        r"""

        Parameters
        ----------
        i : int
          An index of this multidimensional matrix.

        Returns
        -------
          the variable at the ith index

        Raises
        ------
        pyagrum.NotFound
          If i does not reference a variable in this multidimensional matrix.

        """
        return _pyagrum.Tensor_variable(self, *args)

    def remove(self, var: "pyagrum.DiscreteVariable") -> None:
        r"""

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
            The variable to be removed

        Returns
        -------
        pyagrum.Tensor
          a reference to the modified tensor

        Warnings
        --------
        IndexError raised if the var is not in the tensor

        """
        val = _pyagrum.Tensor_remove(self, var)

        self._list_vars.remove(var)


        return val


    def add(self, v: "pyagrum.DiscreteVariable") -> None:
        r"""

        Add a discrete variable to the tensor.

        Parameters
        ----------
        v : pyagrum.DiscreteVariable
          the var to be added

        Raises
        ------
        pyagrum.DuplicateElement
          If the variable is already in this Tensor.
        pyagrum.InvalidArgument
          If the variable is empty.

        Returns
        -------
        pyagrum.Tensor
            a reference to the modified tensor.

        """
        val = _pyagrum.Tensor_add(self, v)

        self._list_vars.append(v)
        return self


        return val


    def domainSize(self) -> int:
        r"""

        Compute the size of the domain of the Tensor, i.e., the product of the domain sizes of the variables in the Tensor.

        Returns
        -------
        int
          the size of the domain of the Tensor (the number of values it can take)

        """
        return _pyagrum.Tensor_domainSize(self)

    def nbrDim(self, *args) -> int:
        r"""

        Returns
        -------
        int
          the number of vars in the multidimensional container.

        """
        return _pyagrum.Tensor_nbrDim(self, *args)

# Register Tensor in _pyagrum:
_pyagrum.Tensor_swigregister(Tensor)
class PairMPE(object):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.PairMPE_swiginit(self, _pyagrum.new_PairMPE(*args))
    first = property(_pyagrum.PairMPE_first_get, _pyagrum.PairMPE_first_set)
    second = property(_pyagrum.PairMPE_second_get, _pyagrum.PairMPE_second_set)
    def __len__(self):
        return 2
    def __repr__(self):
        return str((self.first, self.second))
    def __getitem__(self, index): 
        if not (index % 2):
            return self.first
        else:
            return self.second
    def __setitem__(self, index, val):
        if not (index % 2):
            self.first = val
        else:
            self.second = val
    __swig_destroy__ = _pyagrum.delete_PairMPE

# Register PairMPE in _pyagrum:
_pyagrum.PairMPE_swigregister(PairMPE)

def statsObj() -> None:
    r"""

    Print debug statistics on aGrUM objects.

    Only active in debug build mode (GUM_DEBUG_MODE). No output in release mode.

    """
    return _pyagrum.statsObj()

def getNumberOfThreads() -> int:
    r"""

    Return the current number of threads used in parallel regions.

    This is equal to getMaxNumberOfThreads() unless the user has called setNumberOfThreads().

    Returns
    -------
    int
        the current thread count for parallel regions

    """
    return _pyagrum.getNumberOfThreads()

def getMaxNumberOfThreads() -> int:
    r"""

    Return the maximum number of threads available (equal to the number of logical processors by default).

    Returns
    -------
    int
        the maximum number of threads

    """
    return _pyagrum.getMaxNumberOfThreads()

def getNumberOfLogicalProcessors() -> int:
    r"""

    Return the number of logical processors available on the system.

    Returns
    -------
    int
        the number of logical processors

    """
    return _pyagrum.getNumberOfLogicalProcessors()

import warnings

# seed is chosen randomly :)
initRandom(0)

def _update_config_core():
# hook to control some parameters for core params
  setNumberOfThreads(config.typed['core', 'default_maxNumberOfThreads'])

# configuration object
from .config import PyAgrumConfiguration
config = PyAgrumConfiguration()
config.add_hook(_update_config_core)
config.run_hooks()

try:
# load custom configuration if any
  config.load()
except FileNotFoundError:
  pass


def log2(p):
  """Compute p.log2() in a new Tensor without modifying p

  Parameters
  ----------
  p : pyagrum.Tensor
    The tensor on which to apply log2 function

  Returns
  -------
    a pyagrum.Tensor
  """
  return Tensor(p).log2()


def fastGraph(desc: str):
  """
  Build a graph from a dot-like syntax such as ``'A->B->C;B-D<-E;'``, picking a concrete
  graph type from a quick read of `desc`.

  If `desc` only contains directed arcs (``'->'``/``'<-'``), a :class:`pyagrum.DAG` is
  attempted first, falling back to a :class:`pyagrum.DiGraph` if that would create a
  directed cycle. If `desc` mixes arcs and undirected edges (``'-'``), a
  :class:`pyagrum.PDAG` is attempted first, falling back to a :class:`pyagrum.MixedGraph`
  on the same condition. If `desc` only contains undirected edges, a
  :class:`pyagrum.UndiGraph` is returned.

  For explicit control over the returned type, use :func:`pyagrum.fastDiGraph`,
  :func:`pyagrum.fastUndiGraph`, :func:`pyagrum.fastMixedGraph`, :func:`pyagrum.fastDAG`,
  or :func:`pyagrum.fastPDAG` directly.

  Parameters
  ----------
  desc : str
    the string containing the dot-like specification

  Returns
  -------
  pyagrum.DAG or pyagrum.DiGraph or pyagrum.PDAG or pyagrum.MixedGraph or pyagrum.UndiGraph
  """
  import re

  is_arc = ("->" in desc) or ("<-" in desc)
  is_edge = re.search(r"(?<!<)-(?!>)", desc) is not None

  if not is_arc:
    return fastUndiGraph(desc)

  if is_edge:
    try:
      return fastPDAG(desc)
    except InvalidDirectedCycle:
      return fastMixedGraph(desc)

  try:
    return fastDAG(desc)
  except InvalidDirectedCycle:
    return fastDiGraph(desc)


import os.path as ospath

def _gum_pickle_load(filename):
  """Load a model from a pickle file."""
  import pickle
  with open(filename, "rb") as f:
    return pickle.load(f)

def _gum_pickle_save(model, filename):
  """Save a model to a pickle file."""
  import pickle
  with open(filename, "wb") as f:
    pickle.dump(model, f, pickle.HIGHEST_PROTOCOL)

def _gum_set_name_property(model, filename):
  """Set the 'name' property from the filename stem if not already set."""
  model.setProperty("name", model.propertyWithDefault("name", ospath.splitext(ospath.basename(filename))[0]))



####################################################################################
def Potential(*args, **kwargs):
  warnings.warn("** pyAgrum : The class pyagrum.Potential is deprecated since `pyAgrum>=2.0.0`. A pyagrum.Tensor will be returned instead."
                , DeprecationWarning, stacklevel=2)
  return Tensor(*args, **kwargs)


class PythonBNListener(object):
    r"""

    Listener for Bayesian Network's modifications. This listener is notified when the structure of the BN is changed.

    PythonBNListener(bn:pyagrum.BayesNet,vnm:pyagrum.VariableNodeMap) -> PythonBNListener
        default constructor

    Notes
    -----
        This class est mainly automatically instantiated using the method pyagrum.BayesNet.addStructureListener.

    Parameters
    ----------
    bn : BaysNet
        The bayes net to listen to
    vnm : VarNodeMap
        A translation unit between id of node and name of variable (usually : bn.variableNodeMap()).

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, bn: "pyagrum.BayesNet", vnm: "pyagrum.VariableNodeMap"):
        r"""

        Listener for Bayesian Network's modifications. This listener is notified when the structure of the BN is changed.

        PythonBNListener(bn:pyagrum.BayesNet,vnm:pyagrum.VariableNodeMap) -> PythonBNListener
            default constructor

        Notes
        -----
            This class est mainly automatically instantiated using the method pyagrum.BayesNet.addStructureListener.

        Parameters
        ----------
        bn : BaysNet
            The bayes net to listen to
        vnm : VarNodeMap
            A translation unit between id of node and name of variable (usually : bn.variableNodeMap()).

        """
        _pyagrum.PythonBNListener_swiginit(self, _pyagrum.new_PythonBNListener(bn, vnm))
    __swig_destroy__ = _pyagrum.delete_PythonBNListener

    def whenNodeAdded(self, source: object, id: int) -> None:
        r"""

        Callback called when a node is added to the Bayesian network.

        Parameters
        ----------
        id : int
            the id of the new node
        name : str
            the name of the new node

        """
        return _pyagrum.PythonBNListener_whenNodeAdded(self, source, id)

    def whenNodeDeleted(self, arg2: object, id: int) -> None:
        r"""

        Callback called when a node is removed from the Bayesian network.

        Parameters
        ----------
        id : int
            the id of the deleted node

        """
        return _pyagrum.PythonBNListener_whenNodeDeleted(self, arg2, id)

    def whenArcAdded(self, arg2: object, src: int, dst: int) -> None:
        r"""

        Callback called when an arc is added to the Bayesian network.

        Parameters
        ----------
        src : int
            the tail node id
        dst : int
            the head node id

        """
        return _pyagrum.PythonBNListener_whenArcAdded(self, arg2, src, dst)

    def whenArcDeleted(self, arg2: object, src: int, dst: int) -> None:
        r"""

        Callback called when an arc is removed from the Bayesian network.

        Parameters
        ----------
        src : int
            the tail node id
        dst : int
            the head node id

        """
        return _pyagrum.PythonBNListener_whenArcDeleted(self, arg2, src, dst)

    def setWhenArcAdded(self, pyfunc: object) -> None:
        r"""

        Add the listener in parameter to the list of existing ones for adding an arc.

        Parameters
        ----------
        pyfunc : lambda expression
            a function (i:int,j:int) called when when an arc (i,j) is added

        """
        return _pyagrum.PythonBNListener_setWhenArcAdded(self, pyfunc)

    def setWhenArcDeleted(self, pyfunc: object) -> None:
        r"""

        Add the listener in parameter to the list of existing ones for deleting an arc.

        Parameters
        ----------
        pyfunc : lambda expression
            a function (i:int,j:int) called when when an arc (i,j) is removed

        """
        return _pyagrum.PythonBNListener_setWhenArcDeleted(self, pyfunc)

    def setWhenNodeAdded(self, pyfunc: object) -> None:
        r"""

        Add the listener in parameter to the list of existing ones for adding a node.

        Parameters
        ----------
        pyfunc : lambda expression
            a function (i:int,s:str) called when a node of id i and name s is added.

        """
        return _pyagrum.PythonBNListener_setWhenNodeAdded(self, pyfunc)

    def setWhenNodeDeleted(self, pyfunc: object) -> None:
        r"""

        Add the listener in parameter to the list of existing ones for deleting an arc.

        Parameters
        ----------
        pyfunc : lambda expression
            a function (i:int) called when a node of id i and name s is removed.

        """
        return _pyagrum.PythonBNListener_setWhenNodeDeleted(self, pyfunc)

# Register PythonBNListener in _pyagrum:
_pyagrum.PythonBNListener_swigregister(PythonBNListener)
class PythonLoadListener(object):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def whenLoading(self, buffer: object, percent: int) -> None:
        r"""

        Callback invoked during loading to report progress.

        Parameters
        ----------
        filename : str
            the file being loaded
        pct : int
            the current loading progress percentage

        """
        return _pyagrum.PythonLoadListener_whenLoading(self, buffer, percent)

    def setPythonListener(self, l: object) -> bool:
        r"""



        """
        return _pyagrum.PythonLoadListener_setPythonListener(self, l)

    def __init__(self):
        _pyagrum.PythonLoadListener_swiginit(self, _pyagrum.new_PythonLoadListener())
    __swig_destroy__ = _pyagrum.delete_PythonLoadListener

# Register PythonLoadListener in _pyagrum:
_pyagrum.PythonLoadListener_swigregister(PythonLoadListener)

def _fillLoadListeners_(py_listener: "std::vector< PythonLoadListener,std::allocator< PythonLoadListener > > &", l: object) -> int:
    return _pyagrum._fillLoadListeners_(py_listener, l)
class PythonApproximationListener(object):
    r"""

    Parameters
    ----------
    algo : IApproximationSchemeConfiguration
        an approxmation scheme

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, algo: "IApproximationSchemeConfiguration"):
        r"""

        Parameters
        ----------
        algo : IApproximationSchemeConfiguration
            an approxmation scheme

        """
        _pyagrum.PythonApproximationListener_swiginit(self, _pyagrum.new_PythonApproximationListener(algo))
    __swig_destroy__ = _pyagrum.delete_PythonApproximationListener

    def whenProgress(self, src: object, step: int, error: float, duration: float) -> None:
        r"""

        Callback invoked at each iteration step of the approximation algorithm.

        Parameters
        ----------
        pct : float
            the current progress percentage
        error : float
            the current error estimate
        time : float
            the elapsed time

        """
        return _pyagrum.PythonApproximationListener_whenProgress(self, src, step, error, duration)

    def whenStop(self, src: object, message: str) -> None:
        r"""

        Callback invoked when the approximation algorithm stops.

        Parameters
        ----------
        message : str
            a message describing the stopping reason

        """
        return _pyagrum.PythonApproximationListener_whenStop(self, src, message)

    def setWhenProgress(self, pyfunc: object) -> None:
        r"""

        Parameters
        ----------
        pyfunc
            the function to execute

        """
        return _pyagrum.PythonApproximationListener_setWhenProgress(self, pyfunc)

    def setWhenStop(self, pyfunc: object) -> None:
        r"""

        Parameters
        ----------
        pyfunc
            the function to execute

        """
        return _pyagrum.PythonApproximationListener_setWhenStop(self, pyfunc)

# Register PythonApproximationListener in _pyagrum:
_pyagrum.PythonApproximationListener_swigregister(PythonApproximationListener)
class PythonDatabaseGeneratorListener(object):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, notif: "BNDatabaseGenerator"):
        _pyagrum.PythonDatabaseGeneratorListener_swiginit(self, _pyagrum.new_PythonDatabaseGeneratorListener(notif))
    __swig_destroy__ = _pyagrum.delete_PythonDatabaseGeneratorListener

    def whenProgress(self, src: object, step: int, duration: float) -> None:
        r"""

        Callback invoked at each progress step during database generation.

        Parameters
        ----------
        src : object
            the source of the notification
        step : int
            the current progress step
        duration : float
            elapsed time in seconds

        """
        return _pyagrum.PythonDatabaseGeneratorListener_whenProgress(self, src, step, duration)

    def whenStop(self, src: object, message: str) -> None:
        r"""

        Callback invoked when the database generation stops.

        Parameters
        ----------
        src : object
            the source of the notification
        message : str
            a message describing the stop condition

        """
        return _pyagrum.PythonDatabaseGeneratorListener_whenStop(self, src, message)

    def setWhenProgress(self, pyfunc: object) -> None:
        r"""

        Register a callback called at each progress step.

        Parameters
        ----------
        pyfunc : callable
            a function called with (step: int, duration: float) at each progress notification

        """
        return _pyagrum.PythonDatabaseGeneratorListener_setWhenProgress(self, pyfunc)

    def setWhenStop(self, pyfunc: object) -> None:
        r"""

        Register a callback called when the generation stops.

        Parameters
        ----------
        pyfunc : callable
            a function called with (message: str) when the generation is done

        """
        return _pyagrum.PythonDatabaseGeneratorListener_setWhenStop(self, pyfunc)

# Register PythonDatabaseGeneratorListener in _pyagrum:
_pyagrum.PythonDatabaseGeneratorListener_swigregister(PythonDatabaseGeneratorListener)
class BNGenerator(object):
    r"""

    BNGenerator is used to easily generate Bayesian networks.

    BNGenerator() -> BNGenerator
        default constructor

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def generate(self, n_nodes: int=10, n_arcs: int=15, n_modmax: int=4) -> "pyagrum.BayesNet":
        r"""

        Generate a new Bayesian network

        Parameters
        ----------
        n_nodes : int
            the number of nodes (default=10)
        n_arcs : int
            the number of arcs (default=15)
        n_nodmax : int
            the max number of modalities for a node (default=4)

        Returns
        -------
        pyagrum.BayesNet
            the generated Bayesian network

        Raises
        ------
          pyagrum.OperationNotAllowed
            If n_modmax < 2
          pyagrum.OperationNotAllowed
            If n_arcs is incompatible with n_nodes (not enough arcs)

        """
        return _pyagrum.BNGenerator_generate(self, n_nodes, n_arcs, n_modmax)

    def __init__(self):
        r"""

        BNGenerator is used to easily generate Bayesian networks.

        BNGenerator() -> BNGenerator
            default constructor

        """
        _pyagrum.BNGenerator_swiginit(self, _pyagrum.new_BNGenerator())
    __swig_destroy__ = _pyagrum.delete_BNGenerator

# Register BNGenerator in _pyagrum:
_pyagrum.BNGenerator_swigregister(BNGenerator)
class InformationTheory(object):
    r"""

    This class gathers information theory concepts for subsets named X,Y and Z computed with only one (optimized) inference.


    **it=pyagrum.InformationTheory(ie,X,Y,Z)**

    Parameters
    ----------
        ie : InferenceEngine
          the inference algorithme to use (for instance, `pyagrum.LazyPropagation`)
        X : int or str or iterable[int or str]
          a first nodeset
        Y  : int or str or iterable[int or str]
          a second nodeset
        Z :  : int or str or iterable[int or str] (optional)
          a third (an optional) nodeset

    Example-------
          .. code:: python

              import pyagrum as gum
              bn=pyagrum.fastBN('A->B<-C<-D->E<-F->G->A')
              ie=pyagrum.LazyPropagation(bn)
              it=pyagrum.InformationTheory(ie,'A',['B','G'],['C'])
              print(f'Entropy(A)={it.entropyX()}'')
              print(f'MutualInformation(A;B,G)={it.mutualInformationXY()}')
              print(f'MutualInformation(A;B,G| C)={it.mutualInformationXYgivenZ()}')
              print(f'VariationOfInformation(A;B,G)={it.variationOfInformationXY()}')

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        r"""

        This class gathers information theory concepts for subsets named X,Y and Z computed with only one (optimized) inference.


        **it=pyagrum.InformationTheory(ie,X,Y,Z)**

        Parameters
        ----------
            ie : InferenceEngine
              the inference algorithme to use (for instance, `pyagrum.LazyPropagation`)
            X : int or str or iterable[int or str]
              a first nodeset
            Y  : int or str or iterable[int or str]
              a second nodeset
            Z :  : int or str or iterable[int or str] (optional)
              a third (an optional) nodeset

        Example-------
              .. code:: python

                  import pyagrum as gum
                  bn=pyagrum.fastBN('A->B<-C<-D->E<-F->G->A')
                  ie=pyagrum.LazyPropagation(bn)
                  it=pyagrum.InformationTheory(ie,'A',['B','G'],['C'])
                  print(f'Entropy(A)={it.entropyX()}'')
                  print(f'MutualInformation(A;B,G)={it.mutualInformationXY()}')
                  print(f'MutualInformation(A;B,G| C)={it.mutualInformationXYgivenZ()}')
                  print(f'VariationOfInformation(A;B,G)={it.variationOfInformationXY()}')

        """
        _pyagrum.InformationTheory_swiginit(self, _pyagrum.new_InformationTheory(*args))

    def entropyXY(self) -> float:
        r"""

        Returns
        -------
          float
            The entropy of nodeset, union of X and Y.

        """
        return _pyagrum.InformationTheory_entropyXY(self)

    def entropyX(self) -> float:
        r"""

        Returns
        -------
        float
            the entropy of nodeset X

        """
        return _pyagrum.InformationTheory_entropyX(self)

    def entropyY(self) -> float:
        r"""

        Returns
        -------
          float
            The entropy of nodeset X.

        """
        return _pyagrum.InformationTheory_entropyY(self)

    def entropyXgivenY(self) -> float:
        r"""

        Returns
        -------
          float
            The conditional entropy of nodeset X conditionned by nodeset Y

        """
        return _pyagrum.InformationTheory_entropyXgivenY(self)

    def entropyYgivenX(self) -> float:
        r"""

        Returns
        -------
          float
            The conditional entropy of nodeset Y conditionned by nodeset X

        """
        return _pyagrum.InformationTheory_entropyYgivenX(self)

    def mutualInformationXY(self) -> float:
        r"""

        Returns
        -------
          float
            The mutual information between nodeset X and nodeset Y

        """
        return _pyagrum.InformationTheory_mutualInformationXY(self)

    def variationOfInformationXY(self) -> float:
        r"""

        Returns
        -------
          float
            The variation of information between nodeset X and nodeset Y

        """
        return _pyagrum.InformationTheory_variationOfInformationXY(self)

    def entropyXYgivenZ(self) -> float:
        r"""

        Returns
        -------
        float
            the conditional entropy of nodeset (X ∪ Y) conditioned by nodeset Z

        """
        return _pyagrum.InformationTheory_entropyXYgivenZ(self)

    def mutualInformationXYgivenZ(self) -> float:
        r"""

        Returns
        -------
          float
            The conditional mutual information between nodeset X and nodeset Y conditionned by nodeset Z

        """
        return _pyagrum.InformationTheory_mutualInformationXYgivenZ(self)
    __swig_destroy__ = _pyagrum.delete_InformationTheory

# Register InformationTheory in _pyagrum:
_pyagrum.InformationTheory_swigregister(InformationTheory)
class PRMexplorer(object):
    r"""

    PRMexplorer helps navigate through probabilistic relational models.

    PRMexplorer() -> PRMexplorer
        default constructor

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self):
        r"""

        PRMexplorer helps navigate through probabilistic relational models.

        PRMexplorer() -> PRMexplorer
            default constructor

        """
        _pyagrum.PRMexplorer_swiginit(self, _pyagrum.new_PRMexplorer())
    __swig_destroy__ = _pyagrum.delete_PRMexplorer

    def load(self, *args) -> None:
        r"""

        Load a PRM into the explorer.

        Parameters
        ----------
        filename : str
            the name of the o3prm file
        classpath : str
            the classpath of the PRM

        Raises
        ------
        pyagrum.FatalError
            If file not found

        """
        return _pyagrum.PRMexplorer_load(self, *args)

    def isType(self, name: str) -> bool:
        r"""

        Parameters
        ----------
        name : str
            an element name

        Returns
        -------
        bool
            True if the parameter correspond to a type in the PRM

        """
        return _pyagrum.PRMexplorer_isType(self, name)

    def isClass(self, name: str) -> bool:
        r"""

        Parameters
        ----------
        name : str
            an element name

        Returns
        -------
        bool
            True if the parameter correspond to a class in the PRM

        """
        return _pyagrum.PRMexplorer_isClass(self, name)

    def isInterface(self, name: str) -> bool:
        r"""

        Parameters
        ----------
        name : str
            an element name

        Returns
        -------
        bool
            True if the parameter correspond to an interface in the PRM

        """
        return _pyagrum.PRMexplorer_isInterface(self, name)

    def classes(self) -> list[str]:
        r"""

        Returns
        -------
        list
            the list of classes

        """
        return _pyagrum.PRMexplorer_classes(self)

    def classAttributes(self, class_name: str) -> list[object]:
        r"""

        Parameters
        ----------
        class_name : str
            a class name

        Returns
        -------
        list
            the list of attributes

        Raises
        ------
          pyagrum.IndexError
            If the class is not in the PRM

        """
        return _pyagrum.PRMexplorer_classAttributes(self, class_name)

    def isAttribute(self, class_name: str, att_name: str) -> bool:
        r"""

        Parameters
        ----------
        class_name : str
            a class name
        att_name : str
            the name of the attribute to be tested

        Returns
        -------
        bool
            True if att_name is an attribute of class_name

        Raises
        ------
        pyagrum.IndexError
            If the class is not in the PRM
        pyagrum.IndexError
            If att_name is not an element of class_name

        """
        return _pyagrum.PRMexplorer_isAttribute(self, class_name, att_name)

    def classReferences(self, class_name: str) -> list[object]:
        r"""

        Parameters
        ----------
        class_name : str
            a class name

        Returns
        -------
        list
            the list of references

        Raises
        ------
        pyagrum.IndexError
            If the class is not in the PRM

        """
        return _pyagrum.PRMexplorer_classReferences(self, class_name)

    def classParameters(self, class_name: str) -> list[str]:
        r"""

        Parameters
        ----------
        class_name : str
            a class name

        Returns
        -------
        list
            the list of parameters

        Raises
        ------
          pyagrum.IndexError
            If the class is not in the PRM

        """
        return _pyagrum.PRMexplorer_classParameters(self, class_name)

    def classImplements(self, class_name: str) -> list[str]:
        r"""

        Parameters
        ----------
        class_name : str
            a class name

        Returns
        -------
        list
            the list of interfaces implemented by the class

        """
        return _pyagrum.PRMexplorer_classImplements(self, class_name)
    aggType = property(_pyagrum.PRMexplorer_aggType_get, _pyagrum.PRMexplorer_aggType_set, doc=r"""

    min/max/count/exists/forall/or/and/amplitude/median

    """)

    def classAggregates(self, class_name: str) -> list[object]:
        r"""

        Parameters
        ----------
        class_name : str
            a class name

        Returns
        -------
        list
            the list of aggregates in the class

        Raises
        ------
          pyagrum.IndexError
            If the class is not in the PRM

        """
        return _pyagrum.PRMexplorer_classAggregates(self, class_name)

    def classSlotChains(self, class_name: str) -> list[object]:
        r"""

        Parameters
        ----------
        class_name : str
            a class name

        Returns
        -------
        list
            the list of class slot chains

        Raises
        ------
        pyagrum.IndexError
            if the class is not in the PRM

        """
        return _pyagrum.PRMexplorer_classSlotChains(self, class_name)

    def classDag(self, class_name: str) -> object:
        r"""

        Parameters
        ----------
        class_name : str
            a class name

        Returns
        -------
        tuple
            a description of the DAG

        Raises
        ------
          pyagrum.IndexError
            If the class is not in the PRM

        """
        return _pyagrum.PRMexplorer_classDag(self, class_name)

    def getalltheSystems(self) -> list[object]:
        r"""

        Returns
        -------
        list
            the list of all the systems and their components

        """
        return _pyagrum.PRMexplorer_getalltheSystems(self)

    def getSuperClass(self, class_name: str) -> str | None:
        r"""

        Parameters
        ----------
        class_name : str
            a class name

        Returns
        -------
        str
            the class extended by class_name

        Raises
        ------
        pyagrum.IndexError
            If the class is not in the PRM

        """
        return _pyagrum.PRMexplorer_getSuperClass(self, class_name)

    def getDirectSubClass(self, class_name: str) -> list[str]:
        r"""

        Parameters
        ----------
        class_name : str
            a class name

        Returns
        -------
        list
            the list of direct subclasses

        Raises
        ------
        pyagrum.IndexError
            If the class is not in the PRM

        """
        return _pyagrum.PRMexplorer_getDirectSubClass(self, class_name)

    def cpf(self, class_name: str, attribute: str) -> "pyagrum.Tensor":
        r"""

        Parameters
        ----------
        class_name : str
            a class name

        attribute : str
            an attribute

        Returns
        -------
        pyagrum.Tensor
            the tensor of the attribute

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the class element doesn't have any pyagrum.Tensor (like a pyagrum.PRMReferenceSlot).
        pyagrum.IndexError
            If the class is not in the PRM
        pyagrum.IndexError
            If the attribute in parameters does not exist

        """
        return _pyagrum.PRMexplorer_cpf(self, class_name, attribute)

    def types(self) -> list[str]:
        r"""

        Returns
        -------
        list
            the list of the custom types in the PRM

        """
        return _pyagrum.PRMexplorer_types(self)

    def getSuperType(self, type_name: str) -> str | None:
        r"""

        Parameters
        ----------
        type_name : str
            a type name

        Returns
        -------
        str
            the type extended by type_name

        Raises
        ------
        pyagrum.IndexError
            If the type is not in the PRM

        """
        return _pyagrum.PRMexplorer_getSuperType(self, type_name)

    def getDirectSubTypes(self, type_name: str) -> list[str]:
        r"""

        Parameters
        ----------
        type_name : str
            a type name

        Returns
        -------
        list
            the list of direct subtypes

        Raises
        ------
        pyagrum.IndexError
            If the type is not in the PRM

        """
        return _pyagrum.PRMexplorer_getDirectSubTypes(self, type_name)

    def getLabels(self, type_name: str) -> list[str]:
        r"""

        Parameters
        ----------
        type_name : str
            a type name

        Returns
        -------
        list
            the list of type labels

        Raises
        ------
        pyagrum.IndexError
            If the type is not in the PRM

        """
        return _pyagrum.PRMexplorer_getLabels(self, type_name)

    def getLabelMap(self, type_name: str) -> object:
        r"""

        Parameters
        ----------
        type_name : str
            a type name

        Returns
        -------
        dict
            a dict containing pairs of label and their values

        Raises
        ------
        pyagrum.IndexError
            If the type is not in the PRM

        """
        return _pyagrum.PRMexplorer_getLabelMap(self, type_name)

    def interfaces(self) -> list[str]:
        r"""

        Returns
        -------
        list
            the list of interfaces in the PRM

        """
        return _pyagrum.PRMexplorer_interfaces(self)

    def interAttributes(self, interface_name: str, allAttributes: bool=False) -> list[object]:
        r"""

        Parameters
        ----------
        interface_name : str
            an interface

        allAttributes : bool
            True if supertypes of a custom type should be indicated

        Returns
        -------
        list
            the list of (<type>,<attribute_name>) for the given interface

        Raises
        ------
        pyagrum.IndexError
            If the type is not in the PRM

        """
        return _pyagrum.PRMexplorer_interAttributes(self, interface_name, allAttributes)

    def interReferences(self, interface_name: str) -> list[object]:
        r"""

        Parameters
        ----------
        interface_name : str
            an interface

        Returns
        -------
        list
            the list of (<reference_type>,<reference_name>,<True if the reference is an array>) for the given interface

        Raises
        ------
        pyagrum.IndexError
            If the type is not in the PRM

        """
        return _pyagrum.PRMexplorer_interReferences(self, interface_name)

    def getSuperInterface(self, interface_name: str) -> str | None:
        r"""

        Parameters
        ----------
        interface_name : str
            an interface name

        Returns
        -------
        str
            the interace extended by interface_name

        Raises
        ------
        pyagrum.IndexError
            If the interface is not in the PRM

        """
        return _pyagrum.PRMexplorer_getSuperInterface(self, interface_name)

    def getDirectSubInterfaces(self, interface_name: str) -> list[str]:
        r"""

        Parameters
        ----------
        interface_name : str
            an interface name

        Returns
        -------
        list
            the list of direct subinterfaces

        Raises
        ------
        pyagrum.IndexError
            If the interface is not in the PRM

        """
        return _pyagrum.PRMexplorer_getDirectSubInterfaces(self, interface_name)

    def getImplementations(self, interface_name: str) -> list[str]:
        r"""

        Parameters
        ----------
        interface_name : str
            an interface name

        Returns
        -------
        str
            the list of classes implementing the interface

        Raises
        ------
        pyagrum.IndexError
            If the interface is not in the PRM

        """
        return _pyagrum.PRMexplorer_getImplementations(self, interface_name)

# Register PRMexplorer in _pyagrum:
_pyagrum.PRMexplorer_swigregister(PRMexplorer)
class EssentialGraph(object):
    r"""

    Class building the essential graph from a BN.

    Essential graph is a mixed graph (Chain Graph) that represents the class of markov equivalent Bayesian networks (with the same independency model).

    EssentialGraph(m) -> EssentialGraph
        Parameters:
          - **m** (*pyagrum.DAGmodel*) -- a DAGmodel

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.EssentialGraph_swiginit(self, _pyagrum.new_EssentialGraph(*args))
    __swig_destroy__ = _pyagrum.delete_EssentialGraph

    def pdag(self) -> "pyagrum.PDAG":
        r"""

        Returns
        -------
        pyagrum.PDAG
            the PDAG (Partially Directed Graph)

        """
        return _pyagrum.EssentialGraph_pdag(self)

    def toDot(self) -> str:
        r"""

        Returns
        -------
        str
            a friendly display of the graph in DOT format

        """
        return _pyagrum.EssentialGraph_toDot(self)

    def sizeArcs(self) -> int:
        r"""

        Returns
        -------
        int
            the number of arcs in the graph

        """
        return _pyagrum.EssentialGraph_sizeArcs(self)

    def sizeEdges(self) -> int:
        r"""

        Returns
        -------
        int
            the number of edges in the graph

        """
        return _pyagrum.EssentialGraph_sizeEdges(self)

    def sizeNodes(self) -> int:
        r"""

        Returns
        -------
        int
            the number of nodes in the graph

        """
        return _pyagrum.EssentialGraph_sizeNodes(self)

    def size(self) -> int:
        r"""

        Returns
        -------
        int
            the number of nodes in the graph

        """
        return _pyagrum.EssentialGraph_size(self)

    def skeleton(self) -> "pyagrum.UndiGraph":
        r"""

        Return the skeleton of the essential graph (the underlying undirected graph without arc orientations).

        Returns
        -------
        pyagrum.UndiGraph
            the skeleton of the essential graph

        """
        return _pyagrum.EssentialGraph_skeleton(self)

    def connectedComponents(self) -> dict[int,int]:
        r"""

        Return the connected components of the essential graph.

        Each node is mapped to the id of its component root (an arbitrarily chosen node from the same component).

        Returns
        -------
        dict[int, int]
            mapping node id → component root id

        """
        return _pyagrum.EssentialGraph_connectedComponents(self)

    def idFromName(self, name: str) -> int:
        r"""

        Parameters
        ----------
        name : str
          the name of the variable in the model

        Returns
        -------
        int
          the nodeId from the name of the variable in the model

        """
        return _pyagrum.EssentialGraph_idFromName(self, name)

    def nameFromId(self, node: int) -> str:
        r"""

        Parameters
        ----------
        node : int
          the nodeId of the variable in the model

        Returns
        -------
        str
          the name of the variable in the model from the nodeId

        """
        return _pyagrum.EssentialGraph_nameFromId(self, node)

    def nodes(self) -> set[int]:
        r"""

        Return the set of node ids in the essential graph.

        Returns
        -------
        set[int]
            the set of all node ids

        """
        return _pyagrum.EssentialGraph_nodes(self)

    def connectedComponentsList(self):
      """ connected components as a dict of sets

      Returns
      -------
      dict(int, set[int])
        dict of connected components (as sets of nodeIds) keyed by an arbitrary root nodeId per component.

      """
      cc = self.connectedComponents()
      result = {}
      for node, root in cc.items():
        if root not in result:
          result[root] = set()
        result[root].add(node)
      return result

    def connectedComponentsCount(self):
      """ number of connected components

      Returns
      -------
      int
        the number of connected components in the graph.

      """
      return len(set(self.connectedComponents().values()))

    def adjacencyMatrix(self):
      """ adjacency matrix from a graph/graphical models

      Compute the adjacency matrix of a pyAgrum's graph or graphical models
      (more generally an object that has `nodes`, `children`/`parents` or `neighbours` methods)

      Returns
      -------
      numpy.ndarray
        adjacency matrix (as numpy.ndarray) with nodeId as key.

      """
      nodes=self.nodes()
      n=self.size()
      am=numpy.zeros((n,n)).astype(int)

      for node in nodes:
          if hasattr(self,'children'):
              for children in self.children(node):
                  am[node,children]=1
          if hasattr(self,'neighbours'):
              for neighbour in self.neighbours(node):
                  am[node,neighbour]=1
      return am


    def arcs(self) -> set[tuple[int,int]]:
        r"""

        Returns
        -------
        list
            The lisf of arcs in the EssentialGraph

        """
        return _pyagrum.EssentialGraph_arcs(self)

    def parents(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id :
            The id of the child node

        Returns
        -------
        Set
            the set of the parents ids.

        """
        return _pyagrum.EssentialGraph_parents(self, id)

    def children(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id : int
          the id of the parent

        Returns
        -------
        Set
            the set of all the children

        """
        return _pyagrum.EssentialGraph_children(self, id)

    def descendants(self, *args) -> list[int]:
        r"""

        give the set of nodeid of descendants of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the descendants of node `norid`.

        """
        return _pyagrum.EssentialGraph_descendants(self, *args)

    def ancestors(self, *args) -> list[int]:
        r"""

        give the set of nodeid of ancestors of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the ancestors of node `norid`.

        """
        return _pyagrum.EssentialGraph_ancestors(self, *args)

    def edges(self) -> set[tuple[int,int]]:
        r"""

        Returns
        -------
        List
          the list of the edges

        """
        return _pyagrum.EssentialGraph_edges(self)

    def neighbours(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id : int
            the id of the checked node

        Returns
        -------
        Set
            The set of edges adjacent to the given node

        """
        return _pyagrum.EssentialGraph_neighbours(self, id)

    def connectedComponentsList(self):
      """ connected components as a dict of sets

      Returns
      -------
      dict(int, set[int])
        dict of connected components (as sets of nodeIds) keyed by an arbitrary root nodeId per component.

      """
      cc = self.connectedComponents()
      result = {}
      for node, root in cc.items():
        if root not in result:
          result[root] = set()
        result[root].add(node)
      return result

    def connectedComponentsCount(self):
      """ number of connected components

      Returns
      -------
      int
        the number of connected components in the graph.

      """
      return len(set(self.connectedComponents().values()))

    def adjacencyMatrix(self):
      """ adjacency matrix from a graph/graphical models

      Compute the adjacency matrix of a pyAgrum's graph or graphical models
      (more generally an object that has `nodes`, `children`/`parents` or `neighbours` methods)

      Returns
      -------
      numpy.ndarray
        adjacency matrix (as numpy.ndarray) with nodeId as key.

      """
      nodes=self.nodes()
      n=self.size()
      am=numpy.zeros((n,n)).astype(int)

      for node in nodes:
          if hasattr(self,'children'):
              for children in self.children(node):
                  am[node,children]=1
          if hasattr(self,'neighbours'):
              for neighbour in self.neighbours(node):
                  am[node,neighbour]=1
      return am


# Register EssentialGraph in _pyagrum:
_pyagrum.EssentialGraph_swigregister(EssentialGraph)
class MarkovBlanket(object):
    r"""

    Class building the Markov blanket of a node in a graph.

    MarkovBlanket(m,n) -> MarkovBlanket
        Parameters:
            - **m** (*pyagrum.DAGmodel*) -- a DAGmodel
            - **n** (int) -- a node id

    MarkovBlanket(m,name) -> MarkovBlanket
        Parameters:
            - **m** (*pyagrum.DAGmodel*) -- a DAGmodel
            - **name** (*str*) -- a node name

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.MarkovBlanket_swiginit(self, _pyagrum.new_MarkovBlanket(*args))
    __swig_destroy__ = _pyagrum.delete_MarkovBlanket

    def dag(self) -> "pyagrum.DAG":
        r"""

        Returns
        -------
        pyagrum.DAG
          a copy of the DAG

        """
        return _pyagrum.MarkovBlanket_dag(self)

    def toDot(self) -> str:
        r"""

        Returns
        -------
        str
            a friendly display of the graph in DOT format

        """
        return _pyagrum.MarkovBlanket_toDot(self)

    def sizeArcs(self) -> int:
        r"""

        Returns
        -------
        int
            the number of arcs in the graph

        """
        return _pyagrum.MarkovBlanket_sizeArcs(self)

    def sizeNodes(self) -> int:
        r"""

        Returns
        -------
        int
            the number of nodes in the graph

        """
        return _pyagrum.MarkovBlanket_sizeNodes(self)

    def size(self) -> int:
        r"""

        Returns
        -------
        int
            the number of nodes in the graph

        """
        return _pyagrum.MarkovBlanket_size(self)

    def hasSameStructure(self, other: "pyagrum.DAGmodel") -> bool:
        r"""

        Parameters
        ----------
        pyagrum.DAGmodel
            a direct acyclic model

        Returns
        -------
        bool
            True if all the named node are the same and all the named arcs are the same

        """
        return _pyagrum.MarkovBlanket_hasSameStructure(self, other)

    def nodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
            the set of ids

        """
        return _pyagrum.MarkovBlanket_nodes(self)

    def connectedComponentsList(self):
      """ connected components as a dict of sets

      Returns
      -------
      dict(int, set[int])
        dict of connected components (as sets of nodeIds) keyed by an arbitrary root nodeId per component.

      """
      cc = self.connectedComponents()
      result = {}
      for node, root in cc.items():
        if root not in result:
          result[root] = set()
        result[root].add(node)
      return result

    def connectedComponentsCount(self):
      """ number of connected components

      Returns
      -------
      int
        the number of connected components in the graph.

      """
      return len(set(self.connectedComponents().values()))

    def adjacencyMatrix(self):
      """ adjacency matrix from a graph/graphical models

      Compute the adjacency matrix of a pyAgrum's graph or graphical models
      (more generally an object that has `nodes`, `children`/`parents` or `neighbours` methods)

      Returns
      -------
      numpy.ndarray
        adjacency matrix (as numpy.ndarray) with nodeId as key.

      """
      nodes=self.nodes()
      n=self.size()
      am=numpy.zeros((n,n)).astype(int)

      for node in nodes:
          if hasattr(self,'children'):
              for children in self.children(node):
                  am[node,children]=1
          if hasattr(self,'neighbours'):
              for neighbour in self.neighbours(node):
                  am[node,neighbour]=1
      return am


    def arcs(self) -> set[tuple[int,int]]:
        r"""

        Returns
        -------
        List
            the list of the arcs

        """
        return _pyagrum.MarkovBlanket_arcs(self)

    def parents(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id :
            The id of the child node

        Returns
        -------
        Set
            the set of the parents ids.

        """
        return _pyagrum.MarkovBlanket_parents(self, id)

    def children(self, id: int) -> list[int]:
        r"""

        Parameters
        ----------
        id : int
          the id of the parent

        Returns
        -------
        Set
            the set of all the children

        """
        return _pyagrum.MarkovBlanket_children(self, id)

    def descendants(self, *args) -> list[int]:
        r"""

        give the set of nodeid of descendants of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the descendants of node `norid`.

        """
        return _pyagrum.MarkovBlanket_descendants(self, *args)

    def ancestors(self, *args) -> list[int]:
        r"""

        give the set of nodeid of ancestors of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the ancestors of node `norid`.

        """
        return _pyagrum.MarkovBlanket_ancestors(self, *args)

    def connectedComponentsList(self):
      """ connected components as a dict of sets

      Returns
      -------
      dict(int, set[int])
        dict of connected components (as sets of nodeIds) keyed by an arbitrary root nodeId per component.

      """
      cc = self.connectedComponents()
      result = {}
      for node, root in cc.items():
        if root not in result:
          result[root] = set()
        result[root].add(node)
      return result

    def connectedComponentsCount(self):
      """ number of connected components

      Returns
      -------
      int
        the number of connected components in the graph.

      """
      return len(set(self.connectedComponents().values()))

    def adjacencyMatrix(self):
      """ adjacency matrix from a graph/graphical models

      Compute the adjacency matrix of a pyAgrum's graph or graphical models
      (more generally an object that has `nodes`, `children`/`parents` or `neighbours` methods)

      Returns
      -------
      numpy.ndarray
        adjacency matrix (as numpy.ndarray) with nodeId as key.

      """
      nodes=self.nodes()
      n=self.size()
      am=numpy.zeros((n,n)).astype(int)

      for node in nodes:
          if hasattr(self,'children'):
              for children in self.children(node):
                  am[node,children]=1
          if hasattr(self,'neighbours'):
              for neighbour in self.neighbours(node):
                  am[node,neighbour]=1
      return am


# Register MarkovBlanket in _pyagrum:
_pyagrum.MarkovBlanket_swigregister(MarkovBlanket)
class StructuralMetrics(object):
    r"""

    Compares two graphs of the same type and size, computing precision, recall,
    F-score, SHD and SID metrics at both the skeleton level (ignoring orientation)
    and the arc/edge level (including orientation).

    After calling :meth:`compare`, the confusion-matrix accessors
    (:meth:`tp`, :meth:`fp`, :meth:`fn`, :meth:`tn` and their ``_skeleton``
    variants) become available, as do all derived metrics.

    Examples
    --------
    >>> import pyagrum as gum
    >>> ref = pyagrum.fastBN('A->B->C;A->C')
    >>> test = pyagrum.fastBN('A->B;C->B;A->C')
    >>> comp = pyagrum.StructuralMetrics()
    >>> comp.compare(ref, test)
    >>> comp.precision()
    >>> comp.sid(ref, test)

    StructuralMetrics() -> StructuralMetrics
        default constructor

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self):
        _pyagrum.StructuralMetrics_swiginit(self, _pyagrum.new_StructuralMetrics())
    __swig_destroy__ = _pyagrum.delete_StructuralMetrics

    def tp_skeleton(self) -> float:
        r"""

        Number of true positives at the skeleton level (undirected edges correctly
        predicted, ignoring orientation).

        Returns
        -------
        float
            True-positive count for the skeleton.

        """
        return _pyagrum.StructuralMetrics_tp_skeleton(self)

    def fp_skeleton(self) -> float:
        r"""

        Number of false positives at the skeleton level (extra undirected edges in
        the test skeleton).

        Returns
        -------
        float
            False-positive count for the skeleton.

        """
        return _pyagrum.StructuralMetrics_fp_skeleton(self)

    def fn_skeleton(self) -> float:
        r"""

        Number of false negatives at the skeleton level (undirected edges present in
        the ref skeleton but missing from the test skeleton).

        Returns
        -------
        float
            False-negative count for the skeleton.

        """
        return _pyagrum.StructuralMetrics_fn_skeleton(self)

    def tn_skeleton(self) -> float:
        r"""

        Number of true negatives at the skeleton level (pairs correctly absent from
        both ref and test skeletons).

        Returns
        -------
        float
            True-negative count for the skeleton.

        """
        return _pyagrum.StructuralMetrics_tn_skeleton(self)

    def precision_skeleton(self) -> float:
        r"""

        Precision at the skeleton level: TP / (TP + FP).

        Returns
        -------
        float
            Skeleton precision in [0, 1].

        """
        return _pyagrum.StructuralMetrics_precision_skeleton(self)

    def recall_skeleton(self) -> float:
        r"""

        Recall at the skeleton level: TP / (TP + FN).

        Returns
        -------
        float
            Skeleton recall in [0, 1].

        """
        return _pyagrum.StructuralMetrics_recall_skeleton(self)

    def f_score_skeleton(self) -> float:
        r"""

        F1-score at the skeleton level: harmonic mean of skeleton precision and recall.

        Returns
        -------
        float
            Skeleton F1-score in [0, 1].

        """
        return _pyagrum.StructuralMetrics_f_score_skeleton(self)

    def shd_skeleton(self) -> float:
        r"""

        Structural Hamming Distance at the skeleton level: number of edge insertions
        and deletions needed to transform the test skeleton into the ref skeleton.

        Returns
        -------
        float
            Skeleton SHD (non-negative integer returned as float).

        """
        return _pyagrum.StructuralMetrics_shd_skeleton(self)

    def tp(self) -> float:
        r"""

        Number of true positives at the arc/edge level (correctly predicted arcs or
        edges, orientation included for directed graphs).

        Returns
        -------
        float
            True-positive count.

        """
        return _pyagrum.StructuralMetrics_tp(self)

    def fp(self) -> float:
        r"""

        Number of false positives at the arc/edge level (extra or misoriented arcs/edges
        in the test graph).

        Returns
        -------
        float
            False-positive count.

        """
        return _pyagrum.StructuralMetrics_fp(self)

    def fn(self) -> float:
        r"""

        Number of false negatives at the arc/edge level (arcs/edges present in ref but
        missing from test).

        Returns
        -------
        float
            False-negative count.

        """
        return _pyagrum.StructuralMetrics_fn(self)

    def tn(self) -> float:
        r"""

        Number of true negatives at the arc/edge level (pairs correctly absent from
        both ref and test).

        Returns
        -------
        float
            True-negative count.

        """
        return _pyagrum.StructuralMetrics_tn(self)

    def precision(self) -> float:
        r"""

        Precision at the arc/edge level (orientation included): TP / (TP + FP).

        Returns
        -------
        float
            Precision in [0, 1].

        """
        return _pyagrum.StructuralMetrics_precision(self)

    def recall(self) -> float:
        r"""

        Recall at the arc/edge level (orientation included): TP / (TP + FN).

        Returns
        -------
        float
            Recall in [0, 1].

        """
        return _pyagrum.StructuralMetrics_recall(self)

    def f_score(self) -> float:
        r"""

        F1-score at the arc/edge level: harmonic mean of precision and recall.

        Returns
        -------
        float
            F1-score in [0, 1].

        """
        return _pyagrum.StructuralMetrics_f_score(self)

    def shd(self) -> float:
        r"""

        Structural Hamming Distance: number of arc/edge insertions, deletions and
        reversals needed to transform the test graph into the ref graph.

        Returns
        -------
        float
            SHD (non-negative integer returned as float).

        """
        return _pyagrum.StructuralMetrics_shd(self)

    def compare(self, *args) -> None:
        r"""

        Compare two graphs of the same type and equal size, populating the internal
        confusion matrix used by all metric accessors.

        The following overloads are available:

        * ``compare(DiGraph, DiGraph)`` — nodes matched by NodeId (no alignment).
        * ``compare(UndiGraph, UndiGraph)`` — nodes matched by NodeId (no alignment).
        * ``compare(PDAG, PDAG)`` — nodes matched by NodeId (no alignment).
        * ``compare(BayesNet, BayesNet)`` — nodes matched by variable name; comparison
          runs on the corresponding essential graphs (CPDAGs).
        * ``compare(BayesNet, PDAG)`` — ref BN is converted to its essential graph first.
        * ``compare(PDAG, BayesNet)`` — test BN is converted to its essential graph first.

        For a DAG-level comparison of two BayesNets, align NodeIds manually (e.g. by
        building an aligned BN by variable name) and call ``compare(DiGraph, DiGraph)``
        with ``bn.dag()``. SID (see :meth:`sid`) is the only metric that natively
        operates on the DAGs of BayesNets.

        Parameters
        ----------
        ref :
            Reference graph (DiGraph, UndiGraph, PDAG, or BayesNet).
        test :
            Graph to evaluate against the reference (same type as ref, or PDAG when
            ref is a BayesNet and vice-versa).

        Raises
        ------
        pyagrum.OperationNotAllowed
            If both graphs are empty, or if their sizes differ.
        pyagrum.InvalidNode
            If a node present in ref is absent from test (for BayesNet overloads,
            matching is done by variable name).

        """
        return _pyagrum.StructuralMetrics_compare(self, *args)

    def sid(self, *args) -> float:
        r"""

        Compute the Structural Intervention Distance (SID) between two DAGs
        (Peters & Bühlmann, 2015).

        SID counts the number of ordered pairs (i, j) for which the
        parent-adjustment set for the causal effect of i on j in the reference graph
        is incorrectly reproduced by the test graph.

        Properties:

        * ``sid(G, G) == 0``
        * If G ⊆ H (all parents of G are parents of H), then ``sid(G, H) == 0``.
        * SID is asymmetric: ``sid(G, H) != sid(H, G)`` in general.

        Two overloads are available:

        * ``sid(DAG, DAG)`` — operates directly on DAG objects.
        * ``sid(BayesNet, BayesNet)`` — nodes matched by variable name; the comparison
          runs on the underlying DAGs after name-based alignment.

        Parameters
        ----------
        ref :
            Reference DAG or BayesNet.
        test :
            DAG or BayesNet to evaluate against the reference.

        Returns
        -------
        float
            The SID value (non-negative integer returned as float).

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the graphs have different sizes.
        pyagrum.InvalidNode
            If a variable present in ref is absent from test (BayesNet overload only).

        """
        return _pyagrum.StructuralMetrics_sid(self, *args)

# Register StructuralMetrics in _pyagrum:
_pyagrum.StructuralMetrics_swigregister(StructuralMetrics)
FindBarrenNodesType_FIND_NO_BARREN_NODES = _pyagrum.FindBarrenNodesType_FIND_NO_BARREN_NODES
FindBarrenNodesType_FIND_BARREN_NODES = _pyagrum.FindBarrenNodesType_FIND_BARREN_NODES
class IBayesNet(DAGmodel):
    r"""

    Abstract class used by BayesNet.

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args, **kwargs):
        raise AttributeError("No constructor defined - class is abstract")
    __swig_destroy__ = _pyagrum.delete_IBayesNet

    def cpt(self, varId: int) -> "pyagrum.Tensor":
        r"""

        Returns the CPT of a variable.

        Parameters
        ----------
        VarId : int
            A variable's id in the pyagrum.IBayesNet.
        name : str
            A variable's name in the pyagrum.IBayesNet.

        Returns
        -------
        pyagrum.Tensor
            The variable's CPT.

        Raises
        ------
        pyagrum.NotFound
            If no variable's id matches varId.

        """
        return _pyagrum.IBayesNet_cpt(self, varId)

    def jointProbability(self, i: "pyagrum.Instantiation") -> float:
        r"""

        Parameters
        ----------
        i : pyagrum.instantiation
            an instantiation of the variables

        Returns
        -------
        float
            a parameter of the joint probability for the BayesNet

        Warnings
        --------
        a variable not present in the instantiation is assumed to be instantiated to 0

        """
        return _pyagrum.IBayesNet_jointProbability(self, i)

    def log2JointProbability(self, i: "pyagrum.Instantiation") -> float:
        r"""

        Parameters
        ----------
        i : pyagrum.instantiation
            an instantiation of the variables

        Returns
        -------
        float
            a parameter of the log joint probability for the BayesNet

        Warnings
        --------
        a variable not present in the instantiation is assumed to be instantiated to 0

        """
        return _pyagrum.IBayesNet_log2JointProbability(self, i)

    def check(self) -> tuple[str, ...]:
        r"""

        Check if the BayesNet is consistent (variables, CPT, ...)

        Returns
        -------
        list of str
          list of found issues

        """
        return _pyagrum.IBayesNet_check(self)

    def __eq__(self, _from: "IBayesNet") -> bool:
        return _pyagrum.IBayesNet___eq__(self, _from)

    def dim(self) -> int:
        r"""

        Returns the dimension (the number of free parameters) in this BayesNet.

        Returns
        -------
        int
            the dimension of the BayesNet

        """
        return _pyagrum.IBayesNet_dim(self)

    def maxVarDomainSize(self) -> int:
        r"""

        Returns
        -------
        int
            the biggest domain size among the variables of the BayesNet

        """
        return _pyagrum.IBayesNet_maxVarDomainSize(self)

    def minParam(self) -> float:
        r"""

        Returns
        -------
        float
            the smallest value in the CPTs of the IBayesNet

        """
        return _pyagrum.IBayesNet_minParam(self)

    def maxParam(self) -> float:
        r"""

        Returns
        -------
        float
            the biggest value in the CPTs of the BayesNet

        """
        return _pyagrum.IBayesNet_maxParam(self)

    def minNonZeroParam(self) -> float:
        r"""

        Returns
        -------
        float
            the smallest value (not equal to 0) in the CPTs of the IBayesNet

        """
        return _pyagrum.IBayesNet_minNonZeroParam(self)

    def maxNonOneParam(self) -> float:
        r"""

        Returns
        -------
        float
            The biggest value (not equal to 1) in the CPTs of the BayesNet

        """
        return _pyagrum.IBayesNet_maxNonOneParam(self)

    def toDot(self) -> str:
        r"""

        Returns
        -------
        str
            a friendly display of the graph in DOT format

        """
        return _pyagrum.IBayesNet_toDot(self)

    def evEq(self, name: str, value: float) -> "pyagrum.Tensor":
        r"""

        This method is used to set an observation on a quasi-continuous variables (pyagrum.DiscretizedVariable with a large number of ticks) that the variable is equal to a given value.

        Notes
        -----
          - see also Tensor.evEq
          - see also BayesNet.evGt, BayesNet.evLt, BayesNet.evIn

        Examples
        --------
        >>> import pyagrum as gum
        >>> bn = pyagrum.fastBN('A[0:10:20]'') # DiscretizedVariable from 0 to 10 in 20 steps
        >>> print(bn.evEqu('A',5)
          A                                                                                                                                                                                                    |
        [0;0.5[  |[0.5;1[  |[1;1.5[  |[1.5;2[  |[2;2.5[  |[2.5;3[  |[3;3.5[  |[3.5;4[  |[4;4.5[  |[4.5;5[  |[5;5.5[  |[5.5;6[  |[6;6.5[  |[6.5;7[  |[7;7.5[  |[7.5;8[  |[8;8.5[  |[8.5;9[  |[9;9.5[  |[9.5;10] |
        ---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
         0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 1.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  |

        Parameters
        ----------
        var : int | str
          the current name or the id of the variable
        value: float

        Returns
        -------
        pyagrum.Tensor
          The representation of the evidence as a pyagrum.Tensor.

        """
        return _pyagrum.IBayesNet_evEq(self, name, value)

    def evIn(self, name: str, val1: float, val2: float) -> "pyagrum.Tensor":
        r"""

        This method is used to set an observation on a quasi-continuous variables (pyagrum.DiscretizedVariable with a large number of ticks) that the variable is in a given range.

        Notes
        -----
          - see also Tensor.evIn
          - see also BayesNet.evEq, BayesNet.evGt, BayesNet.evLt

        Parameters
        ----------
        var : int | str
          the current name or the id of the variable
        valueMin: float
          the minimum value
        valueMax: float
          the maximum value

        Returns
        -------
        pyagrum.Tensor
          The representation of the evidence as a pyagrum.Tensor.

        """
        return _pyagrum.IBayesNet_evIn(self, name, val1, val2)

    def evLt(self, name: str, value: float) -> "pyagrum.Tensor":
        r"""

        This method is used to set an observation on a quasi-continuous variables (pyagrum.DiscretizedVariable with a large number of ticks) that the variable is less than a given value.

        Notes
        -----
          - see also Tensor.evLt
          - see also BayesNet.evEq, BayesNet.evGt, BayesNet.evIn

        Parameters
        ----------
        var : int | str
          the current name or the id of the variable
        value: float

        Returns
        -------
        pyagrum.Tensor
          The representation of the evidence as a pyagrum.Tensor.

        """
        return _pyagrum.IBayesNet_evLt(self, name, value)

    def evGt(self, name: str, value: float) -> "pyagrum.Tensor":
        r"""

        This method is used to set an observation on a quasi-continuous variables (pyagrum.DiscretizedVariable with a large number of ticks) that the variable is greater than a given value.

        Notes
        -----
          - see also Tensor.evGt
          - see also BayesNet.evEq, BayesNet.evLt, BayesNet.evIn

        Examples
        --------
        >>> import pyagrum as gum
        >>> bn = pyagrum.fastBN('A[0:10:20]'') # DiscretizedVariable from 0 to 10 in 20 steps
        >>> print(bn.evGt('A',5)
          A                                                                                                                                                                                                    |
        [0;0.5[  |[0.5;1[  |[1;1.5[  |[1.5;2[  |[2;2.5[  |[2.5;3[  |[3;3.5[  |[3.5;4[  |[4;4.5[  |[4.5;5[  |[5;5.5[  |[5.5;6[  |[6;6.5[  |[6.5;7[  |[7;7.5[  |[7.5;8[  |[8;8.5[  |[8.5;9[  |[9;9.5[  |[9.5;10] |
        ---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
         0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 0.0000  | 1.0000  | 1.0000  | 1.0000  | 1.0000  | 1.0000  | 1.0000  | 1.0000  | 1.0000  | 1.0000  | 1.0000  |

        Parameters
        ----------
        var : int | str
          the current name or the id of the variable
        value: float

        Returns
        -------
        pyagrum.Tensor
          The representation of the evidence as a pyagrum.Tensor.

        """
        return _pyagrum.IBayesNet_evGt(self, name, value)

    def memoryFootprint(self) -> int:
        r"""

        Returns the memory footprint in bytes of the Bayesian network.

        Returns
        -------
        int
          the memory footprint in bytes of the Bayesian network

        """
        return _pyagrum.IBayesNet_memoryFootprint(self)

    def ids(self, names: tuple[str, ...]) -> list[int]:
        r"""

        List of ids for a list of names of variables in the model

        Parameters
        ----------
        lov : list of str
          List of variable names

        Returns
        -------
        list of int
            The ids for the list of names of the graph variables

        """
        return _pyagrum.IBayesNet_ids(self, names)

    def nodeset(self, names: tuple[str, ...]) -> set[int]:
        r"""

        Set of ids for a list of names of variables in the model

        Parameters
        ----------
        lov : list of str
          List of variable names

        Returns
        -------
        set
            The set of ids for the list of names of the graph variables

        """
        return _pyagrum.IBayesNet_nodeset(self, names)

    def minimalCondSet(self, *args) -> set[int]:
        r"""

        Returns, given one or many targets and a list of variables, the minimal set of those needed to calculate the target/targets.

        Parameters
        ----------
        target : int
            The id of the target
        targets : list of int
            The ids of the targets
        list : list of int
            The list of available variables

        Returns
        -------
        set
            The minimal set of variables

        """
        return _pyagrum.IBayesNet_minimalCondSet(self, *args)

    def isIndependent(self, *args) -> bool:
        r"""

        check if nodes X and nodes Y are independent given nodes Z

        Parameters
        ----------
        X : str|int|list of str|int
              a list of of nodeIds or names
        Y : str|int|list of str|int
              a list of of nodeIds or names
        Z : str|int|list of str|int
              a list of of nodeIds or names

        Raises
        ------
        InvalidArgument
          if X and Y share variables

        Returns
        -------
        bool
          True if X and Y are independent given Z in the model

        """
        return _pyagrum.IBayesNet_isIndependent(self, *args)

    def names(self) -> set[str]:
        r"""

        Set of names of variables in the model

        Returns
        -------
        set
            The names of the graph variables

        """
        return _pyagrum.IBayesNet_names(self)

    def __iter__(self):
      """
      Iterate over the variables of the model

      Yield
      -----
      tuple[int,str]
        The index of the variable and its name
      """
      for i in self.nodes():
        yield i,self.variable(i).name()

    def __getitem__(self, key):
      if isinstance(key, int):
        return self.variable(key)
      elif isinstance(key, str):
        return self.variableFromName(key)
      else:
        raise TypeError("key must be an int or a string")


    def nodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
            the set of ids

        """
        return _pyagrum.IBayesNet_nodes(self)

    def connectedComponentsList(self):
      """ connected components as a dict of sets

      Returns
      -------
      dict(int, set[int])
        dict of connected components (as sets of nodeIds) keyed by an arbitrary root nodeId per component.

      """
      cc = self.connectedComponents()
      result = {}
      for node, root in cc.items():
        if root not in result:
          result[root] = set()
        result[root].add(node)
      return result

    def connectedComponentsCount(self):
      """ number of connected components

      Returns
      -------
      int
        the number of connected components in the graph.

      """
      return len(set(self.connectedComponents().values()))

    def adjacencyMatrix(self):
      """ adjacency matrix from a graph/graphical models

      Compute the adjacency matrix of a pyAgrum's graph or graphical models
      (more generally an object that has `nodes`, `children`/`parents` or `neighbours` methods)

      Returns
      -------
      numpy.ndarray
        adjacency matrix (as numpy.ndarray) with nodeId as key.

      """
      nodes=self.nodes()
      n=self.size()
      am=numpy.zeros((n,n)).astype(int)

      for node in nodes:
          if hasattr(self,'children'):
              for children in self.children(node):
                  am[node,children]=1
          if hasattr(self,'neighbours'):
              for neighbour in self.neighbours(node):
                  am[node,neighbour]=1
      return am


    def arcs(self) -> set[tuple[int,int]]:
        r"""

        Returns
        -------
        list
            The lisf of arcs in the IBayesNet

        """
        return _pyagrum.IBayesNet_arcs(self)

    def parents(self, norid: object) -> set[int]:
        r"""

        Parameters
        ----------
        id :
            The id of the child node

        Returns
        -------
        Set
            the set of the parents ids.

        """
        return _pyagrum.IBayesNet_parents(self, norid)

    def children(self, norid: object) -> set[int]:
        r"""

        Parameters
        ----------
        id : int
          the id of the parent

        Returns
        -------
        Set
            the set of all the children

        """
        return _pyagrum.IBayesNet_children(self, norid)

    def family(self, norid: object) -> set[int]:
        r"""

        give the set of parents of a node and the node

        Parameters
        ----------
        norid : str|int
          the node

        Returns
        -------
        set
          the set of nodeId of the family of the node `norid`

        """
        return _pyagrum.IBayesNet_family(self, norid)

    def descendants(self, norid: object) -> set[int]:
        r"""

        give the set of nodeid of descendants of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the descendants of node `norid`.

        """
        return _pyagrum.IBayesNet_descendants(self, norid)

    def ancestors(self, norid: object) -> set[int]:
        r"""

        give the set of nodeid of ancestors of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the ancestors of node `norid`.

        """
        return _pyagrum.IBayesNet_ancestors(self, norid)

    def moralizedAncestralGraph(self, nodes: object) -> "pyagrum.UndiGraph":
        r"""

        build a UndiGraph by moralizing the Ancestral Graph of a list of nodes

        Parameters
        ----------
        nodes : str|int|list of str|int
          the list of of nodeIds or names

        Warnings
        --------
          pyagrum.UndiGraph only knows NodeId. Hence the moralized ancestral graph does not include the names of the variables.graph

        Returns
        -------
        pyagrum.UndiGraph
          the moralized ancestral graph of the nodes

        """
        return _pyagrum.IBayesNet_moralizedAncestralGraph(self, nodes)

    def __repr__(self) -> str:
        return _pyagrum.IBayesNet___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.IBayesNet___str__(self)

# Register IBayesNet in _pyagrum:
_pyagrum.IBayesNet_swigregister(IBayesNet)
class BayesNet(IBayesNet):
    r"""

    BayesNet represents a Bayesian network.

    BayesNet(name='') -> BayesNet
        Parameters:
          - **name** (*str*) -- the name of the Bayes Net

    BayesNet(source) -> BayesNet
        Parameters:
          - **source** (*pyagrum.BayesNet*) -- the Bayesian network to copy

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    @staticmethod
    def fastPrototype(*args) -> "pyagrum.BayesNet":
        r"""

        Create a Bayesian network with a dot-like syntax which specifies:
            - the structure 'a->b->c;b->d<-e;'.
            - the type of the variables with different syntax:

              - by default, a variable is a binary `pyagrum.RangeVariable` using the default domain size ([2])
              - with 'a[10]', the variable is a `pyagrum.RangeVariable` using 10 as domain size (from 0 to 9)
              - with 'a[3,7]', the variable is a `pyagrum.RangeVariable` using a domainSize from 3 to 7
              - with 'a[1,3.14,5,6.2]', the variable is a `pyagrum.DiscretizedVariable` using the given ticks (at least 3 values)
              - with 'a[0.0:3.14:10]', the variable is a `pyagrum.DiscretizedVariable` of 10 intervals of same width from 0 to 3.14 (including both)
              - with 'a{top|middle|bottom}', the variable is a `pyagrum.LabelizedVariable` using the given labels.
              - with 'a{-1|5|0|3}', the variable is a `pyagrum.IntegerVariable` using the sorted given values.
              - with 'a{-0.5|5.01|0|3.1415}', the variable is a `pyagrum.NumericalDiscreteVariable` using the sorted given values.

        Notes
        -----
          - If the dot-like string contains such a specification more than once for a variable, the first specification will be used.
          - the CPTs are randomly generated.
          - see also the function pyagrum.fastBN.

        Examples
        --------
        >>> import pyagrum as gum
        >>> bn=pyagrum.BayesNet.fastPrototype('A->B[1,3]<-C{yes|No}->D[2,4]<-E[1,2.5,3.9]',6)

        Parameters
        ----------
        dotlike : str
                the string containing the specification
        domainSize : int or str
                the default domain size or the default domain for variables

        Returns
        -------
        pyagrum.BayesNet
                the resulting Bayesian network

        """
        return _pyagrum.BayesNet_fastPrototype(*args)
    __swig_destroy__ = _pyagrum.delete_BayesNet

    def __init__(self, *args):
        _pyagrum.BayesNet_swiginit(self, _pyagrum.new_BayesNet(*args))

    def cpt(self, *args) -> "pyagrum.Tensor":
        r"""

        Returns the CPT of a variable.

        Parameters
        ----------
        VarId :  int | str
            a variable's id (int) or name

        Returns
        -------
        pyagrum.Tensor
            The variable's CPT.

        Raises
        ------
        pyagrum.NotFound
            If no variable's id matches varId.

        """
        return _pyagrum.BayesNet_cpt(self, *args)

    def add(self, *args) -> int:
        r"""

        Add a variable to the pyagrum.BayesNet.

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            the variable added
        descr : str
            the description of the variable (following fast syntax)
        nbrmod : int
            the number of modalities for the new variable
        id : int
            the variable forced id in the pyagrum.BayesNet

        Returns
        -------
        int
            the id of the new node

        Raises
        ------
        pyagrum.DuplicateLabel
            If variable.name() or id is already used in this pyagrum.BayesNet.
        pyagrum.NotAllowed
            If nbrmod is less than 2

        """
        return _pyagrum.BayesNet_add(self, *args)

    def clear(self) -> None:
        r"""

        Clear the whole BayesNet

        """
        return _pyagrum.BayesNet_clear(self)

    def erase(self, *args) -> None:
        r"""

        Remove a variable from the pyagrum.BayesNet.

        Removes the corresponding variable from the pyagrum.BayesNet and from all of it's children pyagrum.Tensor.

        If no variable matches the given id, then nothing is done.

        Parameters
        ----------
        var : int | str | pyagrum.DiscreteVariable
            the current name, the id of the variable or a reference to the variable

        """
        return _pyagrum.BayesNet_erase(self, *args)

    def changeVariableName(self, *args) -> None:
        r"""

        Changes a variable's name in the pyagrum.BayesNet.

        This will change the pyagrum.DiscreteVariable names in the pyagrum.BayesNet.

        Parameters
        ----------
        var : int | str
            the current name or the id of the variable
        new_name : str
            the new name of the variable

        Raises
        ------
        pyagrum.DuplicateLabel
            If new_name is already used in this BayesNet.
        pyagrum.NotFound
            If no variable matches id.

        """
        return _pyagrum.BayesNet_changeVariableName(self, *args)

    def changeVariableLabel(self, *args) -> None:
        r"""

        change the label of the variable associated to nodeId to the new value.

        Parameters
        ----------
        var : int | str
            the current name or the id of the variable
        old_label : str
            the new label
        new_label : str
            the new label

        Raises
        ------
        pyagrum.NotFound
            if id/name is not a variable or if old_label does not exist.

        """
        return _pyagrum.BayesNet_changeVariableLabel(self, *args)

    def addArc(self, *args) -> None:
        r"""

        Add an arc in the BN, and update arc.head's CPT.

        Parameters
        ----------
        head : int | str
            a variable's id (int) or name
        tail : int | str
            a variable's id (int) or name

        Raises
        ------
        pyagrum.InvalidEdge
            If arc.tail and/or arc.head are not in the BN.
        pyagrum.DuplicateElement
            If the arc already exists.

        """
        return _pyagrum.BayesNet_addArc(self, *args)

    def eraseArc(self, *args) -> None:
        r"""

        Removes an arc in the BN, and update head's CTP.

        If (tail, head) doesn't exist, the nothing happens.

        Parameters
        ----------
        arc : pyagrum.Arc when calling eraseArc(arc)
            The arc to be removed.
        head : int | str
            a variable's id (int) or name for the head when calling eraseArc(head,tail)
        tail : int | str
            a variable's id (int) or name for the tail when calling eraseArc(head,tail)

        """
        return _pyagrum.BayesNet_eraseArc(self, *args)

    def beginTopologyTransformation(self) -> None:
        r"""

        When inserting/removing arcs, node CPTs change their dimension with a cost in time.
        begin Multiple Change for all CPTs
        These functions delay the CPTs change to be done just once at the end of a sequence of topology modification, begins a sequence of insertions/deletions of arcs without changing the dimensions of the CPTs.

        """
        return _pyagrum.BayesNet_beginTopologyTransformation(self)

    def endTopologyTransformation(self) -> None:
        r"""

        Terminates a sequence of insertions/deletions of arcs by adjusting all CPTs dimensions.
        End Multiple Change for all CPTs.

        Returns
        -------
        pyagrum.BayesNet

        """
        return _pyagrum.BayesNet_endTopologyTransformation(self)

    def reverseArc(self, *args) -> None:
        r"""

        Reverses an arc while preserving the same joint distribution.

        Parameters
        ----------
        tail
            (int) the id of the tail variable
        head
            (int) the id of the head variable
        tail
            (str) the name of the tail variable
        head
            (str) the name of the head variable
        arc : pyagrum.Arc
            an arc

        Raises
        ------
        pyagrum.InvalidArc
            If the arc does not exist or if its reversal would induce a directed cycle.

        """
        return _pyagrum.BayesNet_reverseArc(self, *args)

    def addNoisyOR(self, *args) -> int:
        r"""

        Add a variable, it's associate node and a noisyOR implementation.

        Since it seems that the 'classical' noisyOR is the Compound noisyOR, we keep the addNoisyOR as an alias for addNoisyORCompound.

        (The id of the new variable can be automatically generated.)

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            The variable added by copy
        externalWeight : float
            the added external weight
        id : int
            The proposed id for the variable.

        Returns
        -------
        int
            the id of the added variable.

        Raises
        ------
        pyagrum.DuplicateElement
            If id is already used

        """
        return _pyagrum.BayesNet_addNoisyOR(self, *args)

    def addNoisyORNet(self, *args) -> int:
        r"""

        Add a variable, its associate node and a noisyOR implementation.

        Since it seems that the 'classical' noisyOR is the Compound noisyOR, we keep the addNoisyOR as an alias for addNoisyORCompound.

        (The id of the new variable can be automatically generated.)

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            The variable added by copy
        externalWeight : float
            the added external weight
        id : int
            The proposed id for the variable.

        Returns
        -------
        int
            the id of the added variable.

        """
        return _pyagrum.BayesNet_addNoisyORNet(self, *args)

    def addNoisyORCompound(self, *args) -> int:
        r"""

        Add a variable, it's associate node and a noisyOR implementation.

        Since it seems that the 'classical' noisyOR is the Compound noisyOR, we keep the addNoisyOR as an alias for addNoisyORCompound.

        (The id of the new variable can be automatically generated.)

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            The variable added by copy
        externalWeight : float
            the added external weight
        id : int
            The proposed id for the variable.

        Returns
        -------
        int
            the id of the added variable.

        Raises
        ------
        pyagrum.DuplicateElement
            If id is already used

        """
        return _pyagrum.BayesNet_addNoisyORCompound(self, *args)

    def addNoisyAND(self, *args) -> int:
        r"""

        Add a variable, its associate node and a noisyAND implementation.

        (The id of the new variable can be automatically generated.)

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            The variable added by copy
        externalWeight : float
            the added external weight
        id : int
            The proposed id for the variable.

        Returns
        -------
        int
            the id of the added variable.

        Raises
        ------
        pyagrum.DuplicateElement
            If id is already used

        """
        return _pyagrum.BayesNet_addNoisyAND(self, *args)

    def addLogit(self, *args) -> int:
        r"""

        Add a variable, its associate node and a Logit implementation.

        (The id of the new variable can be automatically generated.)

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            The variable added by copy
        externalWeight : float
            the added external weight
        id : int
            The proposed id for the variable.
        Returns
        -------
        int
            the id of the added variable.

        Raises
        ------
        pyagrum.DuplicateElement
            If id is already used

        """
        return _pyagrum.BayesNet_addLogit(self, *args)

    def addOR(self, var: "pyagrum.DiscreteVariable") -> int:
        r"""

        Add a variable, it's associate node and an OR implementation.

        The id of the new variable is automatically generated.

        Warnings
        --------
            If parents are not boolean, all value>1 is True

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            The variable added by copy

        Returns
        -------
        int
            the id of the added variable.

        Raises
        ------
        pyagrum.SizeError
            If variable.domainSize()>2

        """
        return _pyagrum.BayesNet_addOR(self, var)

    def addAND(self, var: "pyagrum.DiscreteVariable") -> int:
        r"""

        Add a variable, it's associate node and an AND implementation.

        The id of the new variable is automatically generated.

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            The variable added by copy.

        Returns
        -------
        int
            the id of the added variable.

        Raises
        ------
        pyagrum.SizeError
            If variable.domainSize()>2

        """
        return _pyagrum.BayesNet_addAND(self, var)

    def addAMPLITUDE(self, var: "pyagrum.DiscreteVariable") -> int:
        r"""

        Others aggregators

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            the variable to be added

        Returns
        -------
        int
            the id of the added value

        """
        return _pyagrum.BayesNet_addAMPLITUDE(self, var)

    def addCOUNT(self, var: "pyagrum.DiscreteVariable", value: int=1) -> int:
        r"""

        Others aggregators

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            the variable to be added

        Returns
        -------
        int
            the id of the added value

        """
        return _pyagrum.BayesNet_addCOUNT(self, var, value)

    def addEXISTS(self, var: "pyagrum.DiscreteVariable", value: int=1) -> int:
        r"""

        Others aggregators

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            the variable to be added

        Returns
        -------
        int
            the id of the added value

        """
        return _pyagrum.BayesNet_addEXISTS(self, var, value)

    def addFORALL(self, var: "pyagrum.DiscreteVariable", value: int=1) -> int:
        r"""

        Others aggregators

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            the variable to be added

        Returns
        -------
        int
            the id of the added variable.

        """
        return _pyagrum.BayesNet_addFORALL(self, var, value)

    def addMAX(self, var: "pyagrum.DiscreteVariable") -> int:
        r"""

        Others aggregators

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            the variable to be added

        Returns
        -------
        int
            the id of the added value

        """
        return _pyagrum.BayesNet_addMAX(self, var)

    def addMEDIAN(self, var: "pyagrum.DiscreteVariable") -> int:
        r"""

        Others aggregators

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            the variable to be added

        Returns
        -------
        int
            the id of the added value

        """
        return _pyagrum.BayesNet_addMEDIAN(self, var)

    def addMIN(self, var: "pyagrum.DiscreteVariable") -> int:
        r"""

        Others aggregators

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            the variable to be added

        Returns
        -------
        int
            the id of the added value

        """
        return _pyagrum.BayesNet_addMIN(self, var)

    def addSUM(self, var: "pyagrum.DiscreteVariable") -> int:
        r"""

        Others aggregators

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
                the variable to be added

        Returns
        -------
        int
                the id of the added value

        """
        return _pyagrum.BayesNet_addSUM(self, var)

    def addWeightedArc(self, *args) -> None:
        r"""

        Add an arc in the BN, and update arc.head's CPT.

        Parameters
        ----------
        head : int | str
            a variable's id (int) or name
        tail : int | str
            a variable's id (int) or name
        causalWeight : float
            the added causal weight

        Raises
        ------
        pyagrum.InvalidArc
            If arc.tail and/or arc.head are not in the BN.
        pyagrum.InvalidArc
            If variable in arc.head is not a NoisyOR variable.

        """
        return _pyagrum.BayesNet_addWeightedArc(self, *args)

    def generateCPTs(self) -> None:
        r"""

        Randomly generates CPTs for a given structure.

        """
        return _pyagrum.BayesNet_generateCPTs(self)

    def generateCPT(self, *args) -> None:
        r"""

        Randomly generate CPT for a given node in a given structure.

        Parameters
        ----------
        node : int | str
            a variable's id (int) or name

        """
        return _pyagrum.BayesNet_generateCPT(self, *args)

    def changeTensor(self, *args) -> None:
        r"""

        change the CPT associated to nodeId to newPot delete the old CPT associated to nodeId.

        Parameters
        ----------
        var : int | str
            the current name or the id of the variable
        newPot : pyagrum.Tensor
            the new tensor

        Raises
        ------
        pyagrum.NotAllowed
            If newPot has not the same signature as __probaMap[NodeId]

        """
        return _pyagrum.BayesNet_changeTensor(self, *args)

    def dag(self) -> "pyagrum.DAG":
        r"""

        Returns
        -------
        pyagrum.DAG
            a constant reference to the dag of this BayesNet.

        """
        val = _pyagrum.BayesNet_dag(self)

        from pyagrum import DAG
        val = DAG(val) # copying the DAG


        return val


    def size(self) -> int:
        r"""

        Returns
        -------
        int
            the number of nodes in the graph

        """
        return _pyagrum.BayesNet_size(self)

    def log10DomainSize(self) -> float:
        r"""

        returns the log10 of the domain size of the model defined as the product of the domain sizes of the variables in the model.

        Returns
        -------
        float
            the log10 domain size.

        """
        return _pyagrum.BayesNet_log10DomainSize(self)

    def idFromName(self, name: str) -> int:
        r"""

        Return the node id of a variable given its name.

        Parameters
        ----------
        name : str
            the name of the variable

        Returns
        -------
        int
            the node id of the variable

        Raises
        ------
        pyagrum.NotFound
            if no variable with this name exists in the model

        """
        return _pyagrum.BayesNet_idFromName(self, name)

    def variableNodeMap(self) -> "pyagrum.VariableNodeMap":
        r"""

        Return the variable-to-node mapping of the model.

        Returns
        -------
        pyagrum.VariableNodeMap
            the internal variable-to-node bijection

        """
        return _pyagrum.BayesNet_variableNodeMap(self)

    def variable(self, *args) -> "pyagrum.DiscreteVariable":
        r"""

        Parameters
        ----------
        id : int
            a variable's id
        name : str
            a variable's name

        Returns
        -------
        pyagrum.DiscreteVariable
            the variable

        Raises
        ------
        pyagrum.IndexError
            If the graph does not contain the variable

        """
        return _pyagrum.BayesNet_variable(self, *args)

    def nodeId(self, var: "pyagrum.DiscreteVariable") -> int:
        r"""

        Return the node id of a variable.

        Parameters
        ----------
        var : pyagrum.DiscreteVariable
            the variable

        Returns
        -------
        int
            the node id of the variable

        Raises
        ------
        pyagrum.NotFound
            if the variable does not exist in the model

        """
        return _pyagrum.BayesNet_nodeId(self, var)

    def variableFromName(self, name: str) -> "pyagrum.DiscreteVariable":
        r"""

        Return the variable with the given name.

        Parameters
        ----------
        name : str
            the name of the variable

        Returns
        -------
        pyagrum.DiscreteVariable
            the variable

        Raises
        ------
        pyagrum.NotFound
            if no variable with this name exists in the model

        """
        return _pyagrum.BayesNet_variableFromName(self, name)

    def ids(self, names: tuple[str, ...]) -> list[int]:
        r"""

        List of ids for a list of names of variables in the model

        Parameters
        ----------
        lov : list of str
          List of variable names

        Returns
        -------
        list of int
            The ids for the list of names of the graph variables

        """
        return _pyagrum.BayesNet_ids(self, names)

    def nodeset(self, names: tuple[str, ...]) -> set[int]:
        r"""

        Set of ids for a list of names of variables in the model

        Parameters
        ----------
        lov : list of str
          List of variable names

        Returns
        -------
        set
            The set of ids for the list of names of the graph variables

        """
        return _pyagrum.BayesNet_nodeset(self, names)

    def minimalCondSet(self, *args) -> set[int]:
        r"""

        Returns, given one or many targets and a list of variables, the minimal set of those needed to calculate the target/targets.

        Parameters
        ----------
        target : int
            The id of the target
        targets : list of int
            The ids of the targets
        list : list of int
            The list of available variables

        Returns
        -------
        set
            The minimal set of variables

        """
        return _pyagrum.BayesNet_minimalCondSet(self, *args)

    def isIndependent(self, *args) -> bool:
        r"""

        check if nodes X and nodes Y are independent given nodes Z

        Parameters
        ----------
        X : str|int|list of str|int
              a list of of nodeIds or names
        Y : str|int|list of str|int
              a list of of nodeIds or names
        Z : str|int|list of str|int
              a list of of nodeIds or names

        Raises
        ------
        InvalidArgument
          if X and Y share variables

        Returns
        -------
        bool
          True if X and Y are independent given Z in the model

        """
        return _pyagrum.BayesNet_isIndependent(self, *args)

    def names(self) -> set[str]:
        r"""

        Set of names of variables in the model

        Returns
        -------
        set
            The names of the graph variables

        """
        return _pyagrum.BayesNet_names(self)

    def __iter__(self):
      """
      Iterate over the variables of the model

      Yield
      -----
      tuple[int,str]
        The index of the variable and its name
      """
      for i in self.nodes():
        yield i,self.variable(i).name()

    def __getitem__(self, key):
      if isinstance(key, int):
        return self.variable(key)
      elif isinstance(key, str):
        return self.variableFromName(key)
      else:
        raise TypeError("key must be an int or a string")


    def nodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
            the set of ids

        """
        return _pyagrum.BayesNet_nodes(self)

    def connectedComponentsList(self):
      """ connected components as a dict of sets

      Returns
      -------
      dict(int, set[int])
        dict of connected components (as sets of nodeIds) keyed by an arbitrary root nodeId per component.

      """
      cc = self.connectedComponents()
      result = {}
      for node, root in cc.items():
        if root not in result:
          result[root] = set()
        result[root].add(node)
      return result

    def connectedComponentsCount(self):
      """ number of connected components

      Returns
      -------
      int
        the number of connected components in the graph.

      """
      return len(set(self.connectedComponents().values()))

    def adjacencyMatrix(self):
      """ adjacency matrix from a graph/graphical models

      Compute the adjacency matrix of a pyAgrum's graph or graphical models
      (more generally an object that has `nodes`, `children`/`parents` or `neighbours` methods)

      Returns
      -------
      numpy.ndarray
        adjacency matrix (as numpy.ndarray) with nodeId as key.

      """
      nodes=self.nodes()
      n=self.size()
      am=numpy.zeros((n,n)).astype(int)

      for node in nodes:
          if hasattr(self,'children'):
              for children in self.children(node):
                  am[node,children]=1
          if hasattr(self,'neighbours'):
              for neighbour in self.neighbours(node):
                  am[node,neighbour]=1
      return am


    def arcs(self) -> set[tuple[int,int]]:
        r"""

        Returns
        -------
        list
            The lisf of arcs in the IBayesNet

        """
        return _pyagrum.BayesNet_arcs(self)

    def parents(self, norid: object) -> set[int]:
        r"""

        Parameters
        ----------
        id :
            The id of the child node

        Returns
        -------
        Set
            the set of the parents ids.

        """
        return _pyagrum.BayesNet_parents(self, norid)

    def children(self, norid: object) -> set[int]:
        r"""

        Parameters
        ----------
        id : int
          the id of the parent

        Returns
        -------
        Set
            the set of all the children

        """
        return _pyagrum.BayesNet_children(self, norid)

    def family(self, norid: object) -> set[int]:
        r"""

        give the set of parents of a node and the node

        Parameters
        ----------
        norid : str|int
          the node

        Returns
        -------
        set
          the set of nodeId of the family of the node `norid`

        """
        return _pyagrum.BayesNet_family(self, norid)

    def descendants(self, norid: object) -> set[int]:
        r"""

        give the set of nodeid of descendants of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the descendants of node `norid`.

        """
        return _pyagrum.BayesNet_descendants(self, norid)

    def ancestors(self, norid: object) -> set[int]:
        r"""

        give the set of nodeid of ancestors of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the ancestors of node `norid`.

        """
        return _pyagrum.BayesNet_ancestors(self, norid)

    def moralizedAncestralGraph(self, nodes: object) -> "pyagrum.UndiGraph":
        r"""

        build a UndiGraph by moralizing the Ancestral Graph of a list of nodes

        Parameters
        ----------
        nodes : str|int|list of str|int
          the list of of nodeIds or names

        Warnings
        --------
          pyagrum.UndiGraph only knows NodeId. Hence the moralized ancestral graph does not include the names of the variables.graph

        Returns
        -------
        pyagrum.UndiGraph
          the moralized ancestral graph of the nodes

        """
        return _pyagrum.BayesNet_moralizedAncestralGraph(self, nodes)

    def addVariables(self,listFastVariables,default_nbr_mod=2):
       """
       Add a list of variable in the form of 'fast' syntax.

       Parameters
       ----------
       listFastVariables: list[str]
         the list of variables in 'fast' syntax.
       default_nbr_mod: int
         the number of modalities for the variable if not specified following :ref:`fast syntax<Quick specification of (randomly parameterized) graphical models>`. Note that default_nbr_mod=1 is
         mandatory to create variables with only one modality (for utility for instance).

       Returns
       -------
       list[int]
         the list of created ids.
       """
       return [self.add(descr,default_nbr_mod) for descr in listFastVariables]

    def addArcs(self,listArcs):
      """
      add a list of arcs in te model.

      Parameters
      ----------
      listArcs : list[tuple[int|str,int|str]]
        the list of arcs
      """
      self.beginTopologyTransformation()
      for arc in listArcs:
        self.addArc(*arc)
      self.endTopologyTransformation()

    def addStructureListener(self,whenNodeAdded=None,whenNodeDeleted=None,whenArcAdded=None,whenArcDeleted=None):
        """
        Add the listeners in parameters to the list of existing ones.

        Parameters
        ----------
        whenNodeAdded : lambda expression
          a function for when a node is added
        whenNodeDeleted : lambda expression
          a function for when a node is removed
        whenArcAdded : lambda expression
          a function for when an arc is added
        whenArcDeleted : lambda expression
          a function for when an arc is removed
        """
        if [whenNodeAdded,whenNodeDeleted,whenArcAdded,whenArcDeleted]==[None,None,None,None]:
          return

        if not hasattr(self,"_listeners"):
          self._listeners=[]

        nl = PythonBNListener(self, self.variableNodeMap())
        if whenNodeAdded is not None:
          nl.setWhenNodeAdded(whenNodeAdded)
        if whenNodeDeleted is not None:
          nl.setWhenNodeDeleted(whenNodeDeleted)
        if whenArcAdded is not None:
          nl.setWhenArcAdded(whenArcAdded)
        if whenArcDeleted is not None:
          nl.setWhenArcDeleted(whenArcDeleted)

        self._listeners.append(nl)


    def loadGUM(self, name: str, l: object=None, binary: bool=False) -> None:
        r"""

        Load a jgum (JSON) or bgum (binary/msgpack) file.

        Parameters
        ----------
        name : str
            the file's path (extension: ``.jgum`` for JSON, ``.bgum`` for binary)
        binary : bool
            if True, read as bgum (msgpack) regardless of extension (default: False)

        Raises
        ------
        pyagrum.IOError
            If file not found
        pyagrum.FatalError
            If file content is not valid

        See Also
        --------
        :ref:`jgum-bgum-format` : complete format reference

        """
        return _pyagrum.BayesNet_loadGUM(self, name, l, binary)

    def _saveGUM(self, name: str, binary: bool=False, indent: int=2) -> None:
        r"""

        Save the BayesNet in a jgum (JSON) or bgum (binary/msgpack) file.

        Metadata properties (``software``, ``creation``, ``lastModification``) are updated automatically.

        Parameters
        ----------
        name : str
            the file's path
        binary : bool
            if True, write as bgum (msgpack); otherwise write as jgum (JSON) (default: False)
        indent : int
            indentation level for JSON output; -1 for compact, 2 for pretty-printed (default: 2)

        See Also
        --------
        :ref:`jgum-bgum-format` : complete format reference

        """
        return _pyagrum.BayesNet__saveGUM(self, name, binary, indent)

    def _saveGUMstring(self, indent: int=2) -> str:
        r"""

        Serialize the BayesNet to a jgum JSON string.

        Metadata properties (``software``, ``creation``, ``lastModification``) are updated automatically.

        Parameters
        ----------
        indent : int
            indentation level; -1 for compact, 2 for pretty-printed (default: 2)

        Returns
        -------
        str
            a JSON string representing the BayesNet in jgum format

        See Also
        --------
        :ref:`jgum-bgum-format` : complete format reference

        """
        return _pyagrum.BayesNet__saveGUMstring(self, indent)

    def loadGUMstring(self, content: str) -> None:
        r"""

        Deserialize a BayesNet from a jgum JSON string.

        Parameters
        ----------
        content : str
            a JSON string in jgum format

        Raises
        ------
        pyagrum.FatalError
            If the string is not valid jgum JSON or the type field does not match ``"BN"``

        See Also
        --------
        :ref:`jgum-bgum-format` : complete format reference

        """
        return _pyagrum.BayesNet_loadGUMstring(self, content)

    def loadBIF(self, name: str, l: object=None) -> str:
        r"""

        Load a BIF file.

        Parameters
        ----------
        name : str
            the file's name
        l : list
            list of functions to execute

        Raises
        ------
        pyagrum.IOError
            If file not found
        pyagrum.FatalError
            If file is not valid

        """
        return _pyagrum.BayesNet_loadBIF(self, name, l)

    def saveBIF(self, name: str, allowModificationWhenSaving: bool=False) -> None:
        r"""

        Save the BayesNet in a BIF file.

        Parameters
        ----------
        name : str
            the file's name
        allowModificationWhenSaving: bool
                False by default.
                if true, syntax errors are corrected when saving the file. If false, they throw a FatalError.

        """
        return _pyagrum.BayesNet_saveBIF(self, name, allowModificationWhenSaving)

    def loadDSL(self, name: str, l: object=None) -> str:
        r"""

        Load a DSL file.

        Parameters
        ----------
        name : str
            the file's name
        l : list
            list of functions to execute

        Raises
        ------
        pyagrum.IOError
            If file not found
        pyagrum.FatalError
            If file is not valid

        """
        return _pyagrum.BayesNet_loadDSL(self, name, l)

    def loadXDSL(self, name: str, l: object=None) -> str:
        r"""

        Load a XDSL file.

        Parameters
        ----------
        name : str
            the file's name
        l : list
            list of functions to execute

        Raises
        ------
        pyagrum.IOError
            If file not found
        pyagrum.FatalError
            If file is not valid

        """
        return _pyagrum.BayesNet_loadXDSL(self, name, l)

    def saveDSL(self, name: str, allowModificationWhenSaving: bool=False) -> None:
        r"""

        Save the BayesNet in a DSL file.

        Parameters
        ----------
        name : str
            the file's name
        allowModificationWhenSaving: bool
                False by default.
                if true, syntax errors are corrected when saving the file. If false, they throw a FatalError.

        """
        return _pyagrum.BayesNet_saveDSL(self, name, allowModificationWhenSaving)

    def saveXDSL(self, name: str, allowModificationWhenSaving: bool=False) -> None:
        r"""

        Save the BayesNet in a XDSL file.

        Parameters
        ----------
        name : str
            the file's name
        allowModificationWhenSaving: bool
                (not used).
                if true, syntax errors are corrected when saving the file. If false, they throw a FatalError.

        """
        return _pyagrum.BayesNet_saveXDSL(self, name, allowModificationWhenSaving)

    def loadNET(self, name: str, l: object=None) -> str:
        r"""

        Load a NET file.

        Parameters
        ----------
        name : str
            the name's file
        l : list
            list of functions to execute

        Raises
        ------
        pyagrum.IOError
            If file not found
        pyagrum.FatalError
            If file is not valid

        """
        return _pyagrum.BayesNet_loadNET(self, name, l)

    def saveNET(self, name: str, allowModificationWhenSaving: bool=False) -> None:
        r"""

        Save the BayesNet in a NET file.

        Parameters
        ----------
        name : str
            the file's name
        allowModificationWhenSaving: bool
                False by default.
                if true, syntax errors are corrected when saving the file. If false, they throw a FatalError.

        """
        return _pyagrum.BayesNet_saveNET(self, name, allowModificationWhenSaving)

    def loadO3PRM(self, *args) -> str:
        r"""

        Load an O3PRM file.

        Warnings
        --------
        The O3PRM language is the only language allowing to manipulate not only DiscretizedVariable but also RangeVariable and LabelizedVariable.

        Parameters
        ----------
        name : str
            the file's name
        system : str
            the system's name
        classpath : str
            the classpath
        l : list
            list of functions to execute

        Raises
        ------
        pyagrum.IOError
            If file not found
        pyagrum.FatalError
            If file is not valid

        """
        return _pyagrum.BayesNet_loadO3PRM(self, *args)

    def saveO3PRM(self, name: str, allowModificationWhenSaving: bool=False) -> None:
        r"""

        Save the BayesNet in an O3PRM file.

        Warnings
        --------
        The O3PRM language is the only language allowing to manipulate not only DiscretizedVariable but also RangeVariable and LabelizedVariable.

        Parameters
        ----------
        name : str
            the file's name
        allowModificationWhenSaving: bool
                False by default.
                if true, syntax errors are corrected when saving the file. If false, they throw a FatalError.

        """
        return _pyagrum.BayesNet_saveO3PRM(self, name, allowModificationWhenSaving)

    def loadBIFXML(self, name: str, l: object=None) -> str:
        r"""

        Load a BIFXML file.

        Parameters
        ----------
        name : str
            the name's file
        l : list
            list of functions to execute

        Raises
        ------
        pyagrum.IOError
            If file not found
        pyagrum.FatalError
            If file is not valid

        """
        return _pyagrum.BayesNet_loadBIFXML(self, name, l)

    def saveBIFXML(self, name: str, allowModificationWhenSaving: bool=False) -> None:
        r"""

        Save the BayesNet in a BIFXML file.

        Parameters
        ----------
        name : str
            the file's name
        allowModificationWhenSaving: bool
                False by default.
                if true, syntax errors are corrected when saving the file. If false, they throw a FatalError.

        """
        return _pyagrum.BayesNet_saveBIFXML(self, name, allowModificationWhenSaving)

    def loadUAI(self, name: str, l: object=None) -> str:
        r"""

        Load an UAI file.

        Parameters
        ----------
        name : str
            the name's file
        l : list
            list of functions to execute

        Raises
        ------
        pyagrum.IOError
            If file not found
        pyagrum.FatalError
            If file is not valid

        """
        return _pyagrum.BayesNet_loadUAI(self, name, l)

    def saveUAI(self, name: str, allowModificationWhenSaving: bool=False) -> None:
        r"""

        Save the BayesNet in an UAI file.

        Parameters
        ----------
        name : str
            the file's name
        allowModificationWhenSaving: bool
                False by default.
                if true, syntax errors are corrected when saving the file. If false, they throw a FatalError.

        """
        return _pyagrum.BayesNet_saveUAI(self, name, allowModificationWhenSaving)

    def contextualize(self, *args) -> "pyagrum.BayesNet":
        r"""

        Return a new Bayesian network conditioned on observations and interventions.

        Parameters
        ----------
        observations : dict
            a dict mapping variable names (str) or ids (int) to observed values (str or int)
        interventions : dict
            a dict mapping variable names (str) or ids (int) to intervened values (str or int)

        Returns
        -------
        pyagrum.BayesNet
            a new BayesNet representing the interventional/observational distribution

        Raises
        ------
        pyagrum.InvalidArgument
            if arguments are not dicts

        """
        return _pyagrum.BayesNet_contextualize(self, *args)

    def __getstate__(self):
        return self.saveGUMstring()

    def __setstate__(self,state):
        self.__init__()
        self.loadGUMstring(state)
        return self

    def saveGUM(self, name: str, binary: bool = False, indent: int = None) -> None:
      """
      Save the Bayesian network as a .jgum (text) or .bgum (binary) file.

      Parameters
      ----------
      name : str
        the name of the file
      binary : bool
        if True, save in the msgpack-based .bgum binary format instead of the JSON-based .jgum text format
      indent : int, optional
        JSON indentation for the .jgum text format (ignored if binary=True): -1 for the most
        compact output, N>=0 to pretty-print with N spaces. Defaults to
        ``pyagrum.config["core","default_jgumIndent"]``.
      """
      if indent is None:
        indent = pyagrum.config.typed["core", "default_jgumIndent"]
      self._saveGUM(name, binary, indent)

    def saveGUMstring(self, indent: int = None) -> str:
      """
      Save the Bayesian network as a jgum string.

      Parameters
      ----------
      indent : int, optional
        JSON indentation: -1 for the most compact output, N>=0 to pretty-print with N spaces.
        Defaults to ``pyagrum.config["core","default_jgumIndent"]``.

      Returns
      -------
      str
        the jgum string
      """
      if indent is None:
        indent = pyagrum.config.typed["core", "default_jgumIndent"]
      return self._saveGUMstring(indent)

    def toFast(self, filename: str = None) -> str:
      """
      Export the Bayesian network as *fast* syntax (in a string or in a python file)

      Parameters
      ----------
      filename : Optional[str]
        the name of the file (including the prefix), if None , use sys.stdout
      """

      def _toFastBN(bn,pythoncode=False):
        res = []
        sovars = set()
        for x, y in bn.arcs():
          if x in sovars:
            src = bn.variable(x).name()
          else:
            src = bn.variable(x).toFast()
            sovars.add(x)
          if y in sovars:
            dst = bn.variable(y).name()
          else:
            dst = bn.variable(y).toFast()
            sovars.add(y)
          res.append(f"{src}->{dst}")

        for x in bn.nodes():
          if x not in sovars:
            res .append(bn.variable(x).toFast())

        if pythoncode:
          return 'model=pyagrum.fastBN("""'+';\n     '.join(res)+'""")'
        else:
          return ';'.join(res)

      if filename is None:
        return _toFastBN(self)
      else:
        with open(filename, "w") as pyfile:
          print(_toFastBN(self,pythoncode=True), file=pyfile)


    def __repr__(self) -> str:
        return _pyagrum.BayesNet___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.BayesNet___str__(self)

# Register BayesNet in _pyagrum:
_pyagrum.BayesNet_swigregister(BayesNet)
class BayesNetFragment(IBayesNet, ):
    r"""

    BayesNetFragment represents a part of a Bayesian network (subset of nodes). By default, the arcs and the CPTs are the same as the BN but local CPTs can be build to express different local dependencies. All the non local CPTs are not copied. Therefore a BayesNetFragment is a light object.

    BayesNetFragment(BayesNet bn) -> BayesNetFragment
        Parameters:
          - **bn** (*pyagrum.BayesNet*) -- the bn refered by the fragment

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, bn: "IBayesNet"):
        _pyagrum.BayesNetFragment_swiginit(self, _pyagrum.new_BayesNetFragment(bn))
    __swig_destroy__ = _pyagrum.delete_BayesNetFragment

    def whenNodeAdded(self, src: object, id: int) -> None:
        r"""

        Internal callback invoked when a node is added to the referenced BayesNet.

        Parameters
        ----------
        src : object
            the event source
        id : int
            the id of the new node

        """
        return _pyagrum.BayesNetFragment_whenNodeAdded(self, src, id)

    def whenNodeDeleted(self, src: object, id: int) -> None:
        r"""

        Internal callback invoked when a node is removed from the referenced BayesNet.

        Parameters
        ----------
        src : object
            the event source
        id : int
            the id of the deleted node

        """
        return _pyagrum.BayesNetFragment_whenNodeDeleted(self, src, id)

    def whenArcAdded(self, src: object, _from: int, to: int) -> None:
        r"""

        Internal callback invoked when an arc is added to the referenced BayesNet.

        Parameters
        ----------
        src : object
            the event source
        from : int
            the tail node id
        to : int
            the head node id

        """
        return _pyagrum.BayesNetFragment_whenArcAdded(self, src, _from, to)

    def whenArcDeleted(self, src: object, _from: int, to: int) -> None:
        r"""

        Internal callback invoked when an arc is removed from the referenced BayesNet.

        Parameters
        ----------
        src : object
            the event source
        from : int
            the tail node id
        to : int
            the head node id

        """
        return _pyagrum.BayesNetFragment_whenArcDeleted(self, src, _from, to)

    def cpt(self, *args) -> "pyagrum.Tensor":
        r"""

        Returns the CPT of a variable.

        Parameters
        ----------
        VarId : int
            A variable's id in the pyagrum.IBayesNet.
        name : str
            A variable's name in the pyagrum.IBayesNet.

        Returns
        -------
        pyagrum.Tensor
            The variable's CPT.

        Raises
        ------
        pyagrum.NotFound
            If no variable's id matches varId.

        """
        return _pyagrum.BayesNetFragment_cpt(self, *args)

    def variableNodeMap(self) -> "pyagrum.VariableNodeMap":
        r"""

        Returns
        -------
        pyagrum.variableNodeMap
            the variable node map

        """
        return _pyagrum.BayesNetFragment_variableNodeMap(self)

    def variable(self, *args) -> "pyagrum.DiscreteVariable":
        r"""

        Parameters
        ----------
        id : int
            a variable's id
        name : str
            a variable's name

        Returns
        -------
        pyagrum.DiscreteVariable
            the variable

        Raises
        ------
        pyagrum.IndexError
            If the graph does not contain the variable

        """
        return _pyagrum.BayesNetFragment_variable(self, *args)

    def nodeId(self, var: "pyagrum.DiscreteVariable") -> int:
        r"""

        Parameters
        ----------
        var : pyagrum.DiscreteVariable
            a variable

        Returns
        -------
        int
            the id of the variable

        Raises
        ------
        pyagrum.IndexError
            If the graph does not contain the variable

        """
        return _pyagrum.BayesNetFragment_nodeId(self, var)

    def idFromName(self, name: str) -> int:
        r"""

        Returns a variable's id given its name in the graph.

        Parameters
        ----------
        name : str
            The variable's name from which the id is returned.

        Notes
        -----
          A convenient shortcut for ``g.variableFromName(name)`` is ``g[name]``.

        Returns
        -------
        int :
            The variable's node id.

        Raises
        ------
        pyagrum.NotFound
            If name does not match a variable in the graph

        """
        return _pyagrum.BayesNetFragment_idFromName(self, name)

    def variableFromName(self, name: str) -> "pyagrum.DiscreteVariable":
        r"""

        Parameters
        ----------
        name : str
            a variable's name

        Returns
        -------
        pyagrum.DiscreteVariable
            the variable

        Raises
        ------
        pyagrum.IndexError
            If the graph does not contain the variable

        """
        return _pyagrum.BayesNetFragment_variableFromName(self, name)

    def toDot(self) -> str:
        r"""

        Returns
        -------
        str
            a friendly display of the graph in DOT format

        """
        return _pyagrum.BayesNetFragment_toDot(self)

    def isInstalledNode(self, *args) -> bool:
        r"""

        Check if a node is in the fragment

        Parameters
        ----------
        n : int, str
            the id or the name of the variable.

        """
        return _pyagrum.BayesNetFragment_isInstalledNode(self, *args)

    def installNode(self, *args) -> None:
        r"""

        Add a node to the fragment. The arcs that can be added between installed nodes are created.
        No specific CPT are created. Then either the parents of the node are already in the fragment
        and the node is consistant, or the parents are not in the fragment and the node is not consistant.

        Parameters
        ----------
        n : int, str
            the id or the name of the variable.

        Raises
        ------
        pyagrum.NotFound
          if the node is not found.

        """
        return _pyagrum.BayesNetFragment_installNode(self, *args)

    def installAscendants(self, *args) -> None:
        r"""

        Add the variable and all its ascendants in the fragment. No inconsistant node are created.

        Parameters
        ----------
        n : int, str
            the id or the name of the variable.

        Raises
        ------
          pyagrum.NotFound
          if the node is not found.

        """
        return _pyagrum.BayesNetFragment_installAscendants(self, *args)

    def uninstallNode(self, *args) -> None:
        r"""

        Remove a node from the fragment. The fragment can become inconsistant.

        Parameters
        ----------
        n : int, str
            the id or the name of the variable.

        Raises
        ------
        pyagrum.NotFound
          if the node is not found.

        """
        return _pyagrum.BayesNetFragment_uninstallNode(self, *args)

    def installMarginal(self, *args) -> None:
        r"""

        Install a local marginal for a node. Doing so, it removes the parents of the node in the fragment.

        Parameters
        ----------
        n : int, str
            the id or the name of the variable.
        pot : Tensor
          the Tensor (marginal) to install

        Raises
        ------
        pyagrum.NotFound
          if the node is not found.

        """
        return _pyagrum.BayesNetFragment_installMarginal(self, *args)

    def installCPT(self, *args) -> None:
        r"""

        Install a local CPT for a node. Doing so, it changes the parents of the node in the fragment.

        Parameters
        ----------
        n : int, str
            the id or the name of the variable.
        pot : Tensor
          the Tensor to install

        Raises
        ------
        pyagrum.NotFound
          if the node is not found.

        """
        return _pyagrum.BayesNetFragment_installCPT(self, *args)

    def uninstallCPT(self, *args) -> None:
        r"""

        Remove a local CPT. The fragment can become inconsistant.

        Parameters
        ----------
        n : int, str
            the id or the name of the variable.

        Raises
        ------
        pyagrum.NotFound
          if the node is not found.

        """
        return _pyagrum.BayesNetFragment_uninstallCPT(self, *args)

    def checkConsistency(self, *args) -> bool:
        r"""

        If a variable is added to the fragment but not its parents, there is no CPT consistant for this variable. This function checks the consistency for a variable of for all.

        Parameters
        ----------
        n : int, str (optional)
            the id or the name of the variable. If no argument, the function checks all the variables.

        Returns
        -------
        boolean
            True if the variable(s) is consistant.

        Raises
        ------
          pyagrum.NotFound
          if the node is not found.

        """
        return _pyagrum.BayesNetFragment_checkConsistency(self, *args)

    def toBN(self) -> "pyagrum.BayesNet":
        r"""

        Create a BayesNet from a fragment.

        Raises
        ------
        pyagrum.OperationNotAllowed
          if the fragment is not consistent.

        """
        return _pyagrum.BayesNetFragment_toBN(self)

    def dag(self) -> "pyagrum.DAG":
        r"""

        Returns
        -------
        pyagrum.DAG
            a constant reference to the dag of this BayesNet.

        """
        val = _pyagrum.BayesNetFragment_dag(self)

        from pyagrum import DAG
        val = DAG(val) # copying the DAG


        return val


    def ids(self, names: tuple[str, ...]) -> list[int]:
        r"""

        List of ids for a list of names of variables in the model

        Parameters
        ----------
        lov : list of str
          List of variable names

        Returns
        -------
        list of int
            The ids for the list of names of the graph variables

        """
        return _pyagrum.BayesNetFragment_ids(self, names)

    def nodeset(self, names: tuple[str, ...]) -> set[int]:
        r"""

        Set of ids for a list of names of variables in the model

        Parameters
        ----------
        lov : list of str
          List of variable names

        Returns
        -------
        set
            The set of ids for the list of names of the graph variables

        """
        return _pyagrum.BayesNetFragment_nodeset(self, names)

    def minimalCondSet(self, *args) -> set[int]:
        r"""

        Returns, given one or many targets and a list of variables, the minimal set of those needed to calculate the target/targets.

        Parameters
        ----------
        target : int
            The id of the target
        targets : list of int
            The ids of the targets
        list : list of int
            The list of available variables

        Returns
        -------
        set
            The minimal set of variables

        """
        return _pyagrum.BayesNetFragment_minimalCondSet(self, *args)

    def isIndependent(self, *args) -> bool:
        r"""

        check if nodes X and nodes Y are independent given nodes Z

        Parameters
        ----------
        X : str|int|list of str|int
              a list of of nodeIds or names
        Y : str|int|list of str|int
              a list of of nodeIds or names
        Z : str|int|list of str|int
              a list of of nodeIds or names

        Raises
        ------
        InvalidArgument
          if X and Y share variables

        Returns
        -------
        bool
          True if X and Y are independent given Z in the model

        """
        return _pyagrum.BayesNetFragment_isIndependent(self, *args)

    def names(self) -> set[str]:
        r"""

        Set of names of variables in the model

        Returns
        -------
        set
            The names of the graph variables

        """
        return _pyagrum.BayesNetFragment_names(self)

    def __iter__(self):
      """
      Iterate over the variables of the model

      Yield
      -----
      tuple[int,str]
        The index of the variable and its name
      """
      for i in self.nodes():
        yield i,self.variable(i).name()

    def __getitem__(self, key):
      if isinstance(key, int):
        return self.variable(key)
      elif isinstance(key, str):
        return self.variableFromName(key)
      else:
        raise TypeError("key must be an int or a string")


    def nodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
            the set of ids

        """
        return _pyagrum.BayesNetFragment_nodes(self)

    def connectedComponentsList(self):
      """ connected components as a dict of sets

      Returns
      -------
      dict(int, set[int])
        dict of connected components (as sets of nodeIds) keyed by an arbitrary root nodeId per component.

      """
      cc = self.connectedComponents()
      result = {}
      for node, root in cc.items():
        if root not in result:
          result[root] = set()
        result[root].add(node)
      return result

    def connectedComponentsCount(self):
      """ number of connected components

      Returns
      -------
      int
        the number of connected components in the graph.

      """
      return len(set(self.connectedComponents().values()))

    def adjacencyMatrix(self):
      """ adjacency matrix from a graph/graphical models

      Compute the adjacency matrix of a pyAgrum's graph or graphical models
      (more generally an object that has `nodes`, `children`/`parents` or `neighbours` methods)

      Returns
      -------
      numpy.ndarray
        adjacency matrix (as numpy.ndarray) with nodeId as key.

      """
      nodes=self.nodes()
      n=self.size()
      am=numpy.zeros((n,n)).astype(int)

      for node in nodes:
          if hasattr(self,'children'):
              for children in self.children(node):
                  am[node,children]=1
          if hasattr(self,'neighbours'):
              for neighbour in self.neighbours(node):
                  am[node,neighbour]=1
      return am


    def arcs(self) -> set[tuple[int,int]]:
        r"""

        Returns
        -------
        list
            The lisf of arcs in the IBayesNet

        """
        return _pyagrum.BayesNetFragment_arcs(self)

    def parents(self, norid: object) -> set[int]:
        r"""

        Parameters
        ----------
        id :
            The id of the child node

        Returns
        -------
        Set
            the set of the parents ids.

        """
        return _pyagrum.BayesNetFragment_parents(self, norid)

    def children(self, norid: object) -> set[int]:
        r"""

        Parameters
        ----------
        id : int
          the id of the parent

        Returns
        -------
        Set
            the set of all the children

        """
        return _pyagrum.BayesNetFragment_children(self, norid)

    def family(self, norid: object) -> set[int]:
        r"""

        give the set of parents of a node and the node

        Parameters
        ----------
        norid : str|int
          the node

        Returns
        -------
        set
          the set of nodeId of the family of the node `norid`

        """
        return _pyagrum.BayesNetFragment_family(self, norid)

    def descendants(self, norid: object) -> set[int]:
        r"""

        give the set of nodeid of descendants of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the descendants of node `norid`.

        """
        return _pyagrum.BayesNetFragment_descendants(self, norid)

    def ancestors(self, norid: object) -> set[int]:
        r"""

        give the set of nodeid of ancestors of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the ancestors of node `norid`.

        """
        return _pyagrum.BayesNetFragment_ancestors(self, norid)

    def moralizedAncestralGraph(self, nodes: object) -> "pyagrum.UndiGraph":
        r"""

        build a UndiGraph by moralizing the Ancestral Graph of a list of nodes

        Parameters
        ----------
        nodes : str|int|list of str|int
          the list of of nodeIds or names

        Warnings
        --------
          pyagrum.UndiGraph only knows NodeId. Hence the moralized ancestral graph does not include the names of the variables.graph

        Returns
        -------
        pyagrum.UndiGraph
          the moralized ancestral graph of the nodes

        """
        return _pyagrum.BayesNetFragment_moralizedAncestralGraph(self, nodes)

    def addVariables(self,listFastVariables,default_nbr_mod=2):
       """
       Add a list of variable in the form of 'fast' syntax.

       Parameters
       ----------
       listFastVariables: list[str]
         the list of variables in 'fast' syntax.
       default_nbr_mod: int
         the number of modalities for the variable if not specified following :ref:`fast syntax<Quick specification of (randomly parameterized) graphical models>`. Note that default_nbr_mod=1 is
         mandatory to create variables with only one modality (for utility for instance).

       Returns
       -------
       list[int]
         the list of created ids.
       """
       return [self.add(descr,default_nbr_mod) for descr in listFastVariables]

    def addArcs(self,listArcs):
      """
      add a list of arcs in te model.

      Parameters
      ----------
      listArcs : list[tuple[int|str,int|str]]
        the list of arcs
      """
      self.beginTopologyTransformation()
      for arc in listArcs:
        self.addArc(*arc)
      self.endTopologyTransformation()

    def addStructureListener(self,whenNodeAdded=None,whenNodeDeleted=None,whenArcAdded=None,whenArcDeleted=None):
        """
        Add the listeners in parameters to the list of existing ones.

        Parameters
        ----------
        whenNodeAdded : lambda expression
          a function for when a node is added
        whenNodeDeleted : lambda expression
          a function for when a node is removed
        whenArcAdded : lambda expression
          a function for when an arc is added
        whenArcDeleted : lambda expression
          a function for when an arc is removed
        """
        if [whenNodeAdded,whenNodeDeleted,whenArcAdded,whenArcDeleted]==[None,None,None,None]:
          return

        if not hasattr(self,"_listeners"):
          self._listeners=[]

        nl = PythonBNListener(self, self.variableNodeMap())
        if whenNodeAdded is not None:
          nl.setWhenNodeAdded(whenNodeAdded)
        if whenNodeDeleted is not None:
          nl.setWhenNodeDeleted(whenNodeDeleted)
        if whenArcAdded is not None:
          nl.setWhenArcAdded(whenArcAdded)
        if whenArcDeleted is not None:
          nl.setWhenArcDeleted(whenArcDeleted)

        self._listeners.append(nl)


# Register BayesNetFragment in _pyagrum:
_pyagrum.BayesNetFragment_swigregister(BayesNetFragment)
class LazyPropagation(object):
    r"""

    Class used for Lazy Propagation

    LazyPropagation(bn) -> LazyPropagation
        Parameters:
            - **bn** (*pyagrum.BayesNet*) -- a Bayesian network

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.LazyPropagation_swiginit(self, _pyagrum.new_LazyPropagation(*args))

        self._model=args[0]



    __swig_destroy__ = _pyagrum.delete_LazyPropagation

    def setTriangulation(self, new_triangulation: "pyagrum.Triangulation") -> None:
        r"""

        Replace the triangulation algorithm used to build the junction tree.

        Parameters
        ----------
        new_triangulation : pyagrum.Triangulation
            the new triangulation algorithm

        """
        return _pyagrum.LazyPropagation_setTriangulation(self, new_triangulation)

    def setRelevantTensorsFinderType(self, type: int) -> None:
        r"""

        sets how we determine the relevant tensors to combine

        When a clique sends a message to a separator, it first constitute the set of the tensors it contains and of the tensors contained in the messages it received. If RelevantTensorsFinderType = FIND_ALL, all these tensors are combined and projected to produce the message sent to the separator. If RelevantTensorsFinderType = DSEP_BAYESBALL_NODES, then only the set of tensors d-connected to the variables of the separator are kept for combination and projection.

        0 = FIND_ALL
        1 = DSEP_BAYESBALL_NODES
        2 = DSEP_BAYESBALL_TENSORS
        3 = DSEP_KOLLER_FRIEDMAN_2009

        Parameters
        ----------
        type : int
          the finder type

        Raises
        ------
        pyagrum.InvalidArgument
          If type is not implemented

        """
        return _pyagrum.LazyPropagation_setRelevantTensorsFinderType(self, type)

    def setFindBarrenNodesType(self, type: int) -> None:
        r"""

        sets how we determine barren nodes

        Barren nodes are unnecessary for probability inference, so they can be safely discarded in this case (type = FIND_BARREN_NODES). This speeds-up inference. However, there are some cases in which we do not want to remove barren nodes, typically when we want to answer queries such as Most Probable Explanations (MPE).

        0 = FIND_NO_BARREN_NODES
        1 = FIND_BARREN_NODES

        Parameters
        ----------
        type : int
          the finder type

        Raises
        ------
        pyagrum.InvalidArgument
          If type is not implemented

        """
        return _pyagrum.LazyPropagation_setFindBarrenNodesType(self, type)

    def joinTree(self) -> "pyagrum.CliqueGraph":
        r"""

        Returns
        -------
        pyagrum.CliqueGraph
          the current join tree used

        """
        return _pyagrum.LazyPropagation_joinTree(self)

    def junctionTree(self) -> "pyagrum.JunctionTree":
        r"""

        Returns
        -------
        pyagrum.CliqueGraph
          the current junction tree

        """
        val = _pyagrum.LazyPropagation_junctionTree(self)

        val._engine=self


        return val


    def evidenceProbability(self) -> float:
        r"""

        Returns
        -------
        float
          the probability of evidence

        """
        return _pyagrum.LazyPropagation_evidenceProbability(self)

    def mpe(self) -> "pyagrum.Instantiation":
        r"""

        Find the Most Probable Explanation (MPE) given the evidence (if any) added into LazyPropagation

        Returns
        -------
        pyagrum.Instantiation
          An instantiation of all the variables of the Bayes net representing the Most Probable Explanation.

        """
        return _pyagrum.LazyPropagation_mpe(self)

    def mpeLog2Posterior(self) -> tuple["pyagrum.Instantiation",float]:
        r"""

        Find the Most Probable Explanation (MPE) given the evidence (if any) added into LazyPropagation as well as the log2 of its posterior probability

        Returns
        -------
        tuple
            A tuple with the instantiation of all the variables of the Bayes net representing the Most Probable Explanation and the log2 of its posterior probability

        """
        return _pyagrum.LazyPropagation_mpeLog2Posterior(self)

    def makeInference(self) -> None:
        r"""

        Perform the heavy computations needed to compute the targets' posteriors

        In a Junction tree propagation scheme, for instance, the heavy computations are those of the messages sent in the JT.
        This is precisely what makeInference should compute. Later, the computations of the posteriors can be done 'lightly' by multiplying and projecting those messages.

        """
        return _pyagrum.LazyPropagation_makeInference(self)

    def addEvidence(self, *args) -> None:
        r"""

        Adds a new evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val :
          (int) a node value
        val :
          (str) the label of the node value
        vals : list
          a list of values

        Raises
        ------
          pyagrum.InvalidArgument
            If the node already has an evidence
          pyagrum.InvalidArgument
            If val is not a value for the node
          pyagrum.InvalidArgument
            If the size of vals is different from the domain side of the node
          pyagrum.FatalError
            If vals is a vector of 0s
          pyagrum.UndefinedElement
            If the node does not belong to the Bayesian network

        """
        return _pyagrum.LazyPropagation_addEvidence(self, *args)

    def chgEvidence(self, *args) -> None:
        r"""

        Change the value of an already existing evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val : int|str
          a node value or the label of the node value
        vals : list of float
          a list of values

        Raises
        ------
        pyagrum.InvalidArgument
          If the node does not already have an evidence
        pyagrum.InvalidArgument
          If val is not a value for the node
        pyagrum.InvalidArgument
          If the size of vals is different from the domain side of the node
        pyagrum.FatalError
          If vals is a vector of 0s
        pyagrum.UndefinedElement
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LazyPropagation_chgEvidence(self, *args)

    def hasEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if some node(s) (or the one in parameters) have received evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LazyPropagation_hasEvidence(self, *args)

    def eraseAllEvidence(self) -> None:
        r"""

        Removes all the evidence entered into the network.

        """
        return _pyagrum.LazyPropagation_eraseAllEvidence(self)

    def eraseEvidence(self, *args) -> None:
        r"""

        Remove the evidence, if any, corresponding to the node Id or name.

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LazyPropagation_eraseEvidence(self, *args)

    def hasHardEvidence(self, nodeName: str) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a hard evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LazyPropagation_hasHardEvidence(self, nodeName)

    def hasSoftEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a soft evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LazyPropagation_hasSoftEvidence(self, *args)

    def nbrEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of evidence entered into the Bayesian network

        """
        return _pyagrum.LazyPropagation_nbrEvidence(self)

    def nbrHardEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of hard evidence entered into the Bayesian network

        """
        return _pyagrum.LazyPropagation_nbrHardEvidence(self)

    def nbrSoftEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of soft evidence entered into the Bayesian network

        """
        return _pyagrum.LazyPropagation_nbrSoftEvidence(self)

    def eraseAllTargets(self) -> None:
        r"""

        Clear all previously defined targets (marginal and joint targets).

        As a result, no posterior can be computed (since we can only compute the posteriors of the marginal or joint targets that have been added by the user).

        """
        return _pyagrum.LazyPropagation_eraseAllTargets(self)

    def addAllTargets(self) -> None:
        r"""

        Add all the nodes as targets.

        """
        return _pyagrum.LazyPropagation_addAllTargets(self)

    def addTarget(self, *args) -> None:
        r"""

        Add a marginal target to the list of targets.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : str
          a node name

        Raises
        ------
        pyagrum.UndefinedElement
          If target is not a NodeId in the Bayes net

        """
        return _pyagrum.LazyPropagation_addTarget(self, *args)

    def eraseTarget(self, *args) -> None:
        r"""

        Remove, if existing, the marginal target.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.LazyPropagation_eraseTarget(self, *args)

    def isTarget(self, *args) -> bool:
        r"""

        Parameters
        ----------
        variable : int
         a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if variable is a (marginal) target

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.LazyPropagation_isTarget(self, *args)

    def nbrTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of marginal targets

        """
        return _pyagrum.LazyPropagation_nbrTargets(self)

    def H(self, *args) -> float:
        r"""

        Parameters
        ----------
        X : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        float
          the computed Shanon's entropy of a node given the observation

        """
        return _pyagrum.LazyPropagation_H(self, *args)

    def BN(self) -> "pyagrum.IBayesNet":
        r"""

        Returns
        -------
        pyagrum.IBayesNet
          A constant reference over the IBayesNet referenced by this class.

        Raises
        ------
          pyagrum.UndefinedElement
            If no Bayes net has been assigned to the inference.

        """
        return _pyagrum.LazyPropagation_BN(self)

    def posterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior probability of the node

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.LazyPropagation_posterior(self, *args)

    def eraseAllJointTargets(self) -> None:
        r"""

        Clear all previously defined joint targets.

        """
        return _pyagrum.LazyPropagation_eraseAllJointTargets(self)

    def eraseAllMarginalTargets(self) -> None:
        r"""

        Clear all the previously defined marginal targets.

        """
        return _pyagrum.LazyPropagation_eraseAllMarginalTargets(self)

    def nbrJointTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of joint targets

        """
        return _pyagrum.LazyPropagation_nbrJointTargets(self)

    def setNumberOfThreads(self, nb: int) -> None:
        r"""

        If the argument nb is different from 0, this number of threads will be used during inferences, hence overriding aGrUM's default number of threads.
        If, on the contrary, nb is equal to 0, the parallelized inference engine will comply with aGrUM's default number of threads.

        Parameters
        ----------
        nb : int
            the number of threads to be used by ShaferShenoyMRFInference

        """
        return _pyagrum.LazyPropagation_setNumberOfThreads(self, nb)

    def getNumberOfThreads(self) -> int:
        r"""

        returns the number of threads used by LazyPropagation during inferences.

        Returns
        -------
        int
            the number of threads used by LazyPropagation during inferences

        """
        return _pyagrum.LazyPropagation_getNumberOfThreads(self)

    def isGumNumberOfThreadsOverriden(self) -> bool:
        r"""

        Indicates whether LazyPropagation currently overrides aGrUM's default number of threads (see method setNumberOfThreads).

        Returns
        -------
        bool
            A Boolean indicating whether LazyPropagation currently overrides aGrUM's default number of threads

        """
        return _pyagrum.LazyPropagation_isGumNumberOfThreadsOverriden(self)

    def setMaxMemory(self, gigabytes: int) -> None:
        r"""

        sets an upper bound on the memory consumption admissible

        Parameters
        ----------
        gigabytes: float
          this upper bound in gigabytes.

        """
        return _pyagrum.LazyPropagation_setMaxMemory(self, gigabytes)

    def setEvidence(self, evidces):
        """
        Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
        pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          self.eraseAllEvidence()
          for k,v in evidces.items():
            self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          self.eraseAllEvidence()
          for p in evidces:
            self.addEvidence(p)
          return
        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def updateEvidence(self, evidces):
        """
        Apply chgEvidence(key,value) for every pairs in evidces (or addEvidence).

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
          pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          for k,v in evidces.items():
              if self.hasEvidence(k):
                  self.chgEvidence(k,v)
              else:
                  self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          for p in evidces:
              k=p.variable(0)
              if self.hasEvidence(k):
                  self.chgEvidence(p)
              else:
                  self.addEvidence(p)
          return

        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def setTargets(self, targets):
        """
        Remove all the targets and add the ones in parameter.

        Parameters
        ----------
        targets : set
          a set of targets

        Raises
        ------
          pyagrum.UndefinedElement
            If one target is not in the Bayes net
        """
        if not isinstance(targets, set):
            raise TypeError("setTargets parameter must be a set, not %s"%(type(targets)))

        self.eraseAllTargets()
        for k in targets:
            self.addTarget(k)



    def hardEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with hard evidence

        """
        return _pyagrum.LazyPropagation_hardEvidenceNodes(self)

    def softEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with soft evidence

        """
        return _pyagrum.LazyPropagation_softEvidenceNodes(self)

    def targets(self) -> set[int]:
        r"""

        Returns
        -------
        list
          the list of marginal targets

        """
        return _pyagrum.LazyPropagation_targets(self)

    def evidenceImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(target|evs) (for all instanciation of target and evs)

        Parameters
        ----------
        target : set
          a set of targets ids or names.
        evs : set
          a set of nodes ids or names.

        Warnings
        --------
        if some evs are d-separated, they are not included in the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(targets|evs)

        """
        return _pyagrum.LazyPropagation_evidenceImpact(self, *args)

    def jointMutualInformation(self, targets: object) -> float:
        r"""

        Compute the joint mutual information (interaction information) among a set of target variables.

        See https://en.wikipedia.org/wiki/Interaction_information

        Parameters
        ----------
        targets : list[str] or set[int]
            the target variables (names or node ids)

        Returns
        -------
        float
            the joint mutual information

        """
        return _pyagrum.LazyPropagation_jointMutualInformation(self, targets)

    def evidenceJointImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(joint targets|evs) (for all instanciation of targets and evs)

        Parameters
        ----------
        targets : list of int|str
          a list of node Ids or node names
        evs : set
          a set of nodes ids or names.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(target|evs)

        Raises
        ------
        pyagrum.Exception
          If some evidene entered into the Bayes net are incompatible (their joint proba = 0)

        """
        return _pyagrum.LazyPropagation_evidenceJointImpact(self, *args)

    def jointPosterior(self, targets: object) -> "pyagrum.Tensor":
        r"""

        Compute the joint posterior of a set of nodes.

        Parameters
        ----------
        list :
          the list of nodes whose posterior joint probability is wanted


        Warnings
        --------
        The order of the variables given by the list here or when the jointTarget is declared can not be assumed to be used by the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior joint probability of the set of nodes.

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.LazyPropagation_jointPosterior(self, targets)

    def addJointTarget(self, targets: object) -> None:
        r"""

        Add a list of nodes as a new joint target. As a collateral effect, every node is added as a marginal target.

        Parameters
        ----------
        list
          a list of names of nodes

        Raises
        ------
        pyagrum.UndefinedElement
          If some node(s) do not belong to the Bayesian network

        """
        return _pyagrum.LazyPropagation_addJointTarget(self, targets)

    def eraseJointTarget(self, targets: object) -> None:
        r"""

        Remove, if existing, the joint target.

        Parameters
        ----------
        list
          a list of names or Ids of nodes

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.LazyPropagation_eraseJointTarget(self, targets)

    def isJointTarget(self, targets: object) -> bool:
        r"""

        Parameters
        ----------
        list
          a list of nodes ids or names.

        Returns
        -------
        bool
          True if target is a joint target.

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.LazyPropagation_isJointTarget(self, targets)

    def jointTargets(self) -> list[set[int]]:
        r"""

        Returns
        -------
        list
          the list of target sets

        """
        return _pyagrum.LazyPropagation_jointTargets(self)

# Register LazyPropagation in _pyagrum:
_pyagrum.LazyPropagation_swigregister(LazyPropagation)
class ShaferShenoyInference(object):
    r"""

    Class used for Shafer-Shenoy inferences.

    ShaferShenoyInference(bn) -> ShaferShenoyInference
        Parameters:
            - **bn** (*pyagrum.BayesNet*) -- a Bayesian network

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.ShaferShenoyInference_swiginit(self, _pyagrum.new_ShaferShenoyInference(*args))

        self._model=args[0]



    __swig_destroy__ = _pyagrum.delete_ShaferShenoyInference

    def setTriangulation(self, new_triangulation: "pyagrum.Triangulation") -> None:
        r"""

        Replace the triangulation algorithm used to build the junction tree.

        Parameters
        ----------
        new_triangulation : pyagrum.Triangulation
            the new triangulation algorithm

        """
        return _pyagrum.ShaferShenoyInference_setTriangulation(self, new_triangulation)

    def setRelevantTensorsFinderType(self, type: int) -> None:
        r"""

        sets how we determine the relevant tensors to combine

        When a clique sends a message to a separator, it first constitute the set of the tensors it contains and of the tensors contained in the messages it received. If RelevantTensorsFinderType = FIND_ALL, all these tensors are combined and projected to produce the message sent to the separator. If RelevantTensorsFinderType = DSEP_BAYESBALL_NODES, then only the set of tensors d-connected to the variables of the separator are kept for combination and projection.

        0 = FIND_ALL
        1 = DSEP_BAYESBALL_NODES
        2 = DSEP_BAYESBALL_TENSORS
        3 = DSEP_KOLLER_FRIEDMAN_2009

        Parameters
        ----------
        type : int
          the finder type

        Raises
        ------
        pyagrum.InvalidArgument
          If type is not implemented

        """
        return _pyagrum.ShaferShenoyInference_setRelevantTensorsFinderType(self, type)

    def setFindBarrenNodesType(self, type: int) -> None:
        r"""

        sets how we determine barren nodes

        Barren nodes are unnecessary for probability inference, so they can be safely discarded in this case (type = FIND_BARREN_NODES). This speeds-up inference. However, there are some cases in which we do not want to remove barren nodes, typically when we want to answer queries such as Most Probable Explanations (MPE).

        0 = FIND_NO_BARREN_NODES
        1 = FIND_BARREN_NODES

        Parameters
        ----------
        type : int
          the finder type

        Raises
        ------
        pyagrum.InvalidArgument
          If type is not implemented

        """
        return _pyagrum.ShaferShenoyInference_setFindBarrenNodesType(self, type)

    def joinTree(self) -> "pyagrum.CliqueGraph":
        r"""

        Returns
        -------
        pyagrum.CliqueGraph
          the current join tree used

        """
        return _pyagrum.ShaferShenoyInference_joinTree(self)

    def junctionTree(self) -> "pyagrum.JunctionTree":
        r"""

        Returns
        -------
        pyagrum.CliqueGraph
          the current junction tree

        """
        val = _pyagrum.ShaferShenoyInference_junctionTree(self)

        val._engine=self


        return val


    def evidenceProbability(self) -> float:
        r"""

        Returns
        -------
        float
          the probability of evidence

        """
        return _pyagrum.ShaferShenoyInference_evidenceProbability(self)

    def makeInference(self) -> None:
        r"""

        Perform the heavy computations needed to compute the targets' posteriors

        In a Junction tree propagation scheme, for instance, the heavy computations are those of the messages sent in the JT.
        This is precisely what makeInference should compute. Later, the computations of the posteriors can be done 'lightly' by multiplying and projecting those messages.

        """
        return _pyagrum.ShaferShenoyInference_makeInference(self)

    def addEvidence(self, *args) -> None:
        r"""

        Adds a new evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val :
          (int) a node value
        val :
          (str) the label of the node value
        vals : list
          a list of values

        Raises
        ------
          pyagrum.InvalidArgument
            If the node already has an evidence
          pyagrum.InvalidArgument
            If val is not a value for the node
          pyagrum.InvalidArgument
            If the size of vals is different from the domain side of the node
          pyagrum.FatalError
            If vals is a vector of 0s
          pyagrum.UndefinedElement
            If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyInference_addEvidence(self, *args)

    def chgEvidence(self, *args) -> None:
        r"""

        Change the value of an already existing evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val : int|str
          a node value or the label of the node value
        vals : list of float
          a list of values

        Raises
        ------
        pyagrum.InvalidArgument
          If the node does not already have an evidence
        pyagrum.InvalidArgument
          If val is not a value for the node
        pyagrum.InvalidArgument
          If the size of vals is different from the domain side of the node
        pyagrum.FatalError
          If vals is a vector of 0s
        pyagrum.UndefinedElement
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyInference_chgEvidence(self, *args)

    def hasEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if some node(s) (or the one in parameters) have received evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyInference_hasEvidence(self, *args)

    def eraseAllEvidence(self) -> None:
        r"""

        Removes all the evidence entered into the network.

        """
        return _pyagrum.ShaferShenoyInference_eraseAllEvidence(self)

    def eraseEvidence(self, *args) -> None:
        r"""

        Remove the evidence, if any, corresponding to the node Id or name.

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyInference_eraseEvidence(self, *args)

    def hasHardEvidence(self, nodeName: str) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a hard evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyInference_hasHardEvidence(self, nodeName)

    def hasSoftEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a soft evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyInference_hasSoftEvidence(self, *args)

    def nbrEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of evidence entered into the Bayesian network

        """
        return _pyagrum.ShaferShenoyInference_nbrEvidence(self)

    def nbrHardEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of hard evidence entered into the Bayesian network

        """
        return _pyagrum.ShaferShenoyInference_nbrHardEvidence(self)

    def nbrSoftEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of soft evidence entered into the Bayesian network

        """
        return _pyagrum.ShaferShenoyInference_nbrSoftEvidence(self)

    def eraseAllTargets(self) -> None:
        r"""

        Clear all previously defined targets (marginal and joint targets).

        As a result, no posterior can be computed (since we can only compute the posteriors of the marginal or joint targets that have been added by the user).

        """
        return _pyagrum.ShaferShenoyInference_eraseAllTargets(self)

    def addAllTargets(self) -> None:
        r"""

        Add all the nodes as targets.

        """
        return _pyagrum.ShaferShenoyInference_addAllTargets(self)

    def addTarget(self, *args) -> None:
        r"""

        Add a marginal target to the list of targets.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : str
          a node name

        Raises
        ------
        pyagrum.UndefinedElement
          If target is not a NodeId in the Bayes net

        """
        return _pyagrum.ShaferShenoyInference_addTarget(self, *args)

    def eraseTarget(self, *args) -> None:
        r"""

        Remove, if existing, the marginal target.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.ShaferShenoyInference_eraseTarget(self, *args)

    def isTarget(self, *args) -> bool:
        r"""

        Parameters
        ----------
        variable : int
         a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if variable is a (marginal) target

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.ShaferShenoyInference_isTarget(self, *args)

    def nbrTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of marginal targets

        """
        return _pyagrum.ShaferShenoyInference_nbrTargets(self)

    def H(self, *args) -> float:
        r"""

        Parameters
        ----------
        X : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        float
          the computed Shanon's entropy of a node given the observation

        """
        return _pyagrum.ShaferShenoyInference_H(self, *args)

    def BN(self) -> "pyagrum.IBayesNet":
        r"""

        Returns
        -------
        pyagrum.IBayesNet
          A constant reference over the IBayesNet referenced by this class.

        Raises
        ------
          pyagrum.UndefinedElement
            If no Bayes net has been assigned to the inference.

        """
        return _pyagrum.ShaferShenoyInference_BN(self)

    def posterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior probability of the node

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.ShaferShenoyInference_posterior(self, *args)

    def eraseAllJointTargets(self) -> None:
        r"""

        Clear all previously defined joint targets.

        """
        return _pyagrum.ShaferShenoyInference_eraseAllJointTargets(self)

    def eraseAllMarginalTargets(self) -> None:
        r"""

        Clear all the previously defined marginal targets.

        """
        return _pyagrum.ShaferShenoyInference_eraseAllMarginalTargets(self)

    def nbrJointTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of joint targets

        """
        return _pyagrum.ShaferShenoyInference_nbrJointTargets(self)

    def setNumberOfThreads(self, nb: int) -> None:
        r"""

        If the argument nb is different from 0, this number of threads will be used during inferences, hence overriding aGrUM's default number of threads.
        If, on the contrary, nb is equal to 0, the parallelized inference engine will comply with aGrUM's default number of threads.

        Parameters
        ----------
        nb : int
            the number of threads to be used by ShaferShenoyMRFInference

        """
        return _pyagrum.ShaferShenoyInference_setNumberOfThreads(self, nb)

    def getNumberOfThreads(self) -> int:
        r"""

        returns the number of threads used by LazyPropagation during inferences.

        Returns
        -------
        int
            the number of threads used by LazyPropagation during inferences

        """
        return _pyagrum.ShaferShenoyInference_getNumberOfThreads(self)

    def isGumNumberOfThreadsOverriden(self) -> bool:
        r"""

        Indicates whether LazyPropagation currently overrides aGrUM's default number of threads (see method setNumberOfThreads).

        Returns
        -------
        bool
            A Boolean indicating whether LazyPropagation currently overrides aGrUM's default number of threads

        """
        return _pyagrum.ShaferShenoyInference_isGumNumberOfThreadsOverriden(self)

    def setMaxMemory(self, gigabytes: int) -> None:
        r"""

        sets an upper bound on the memory consumption admissible

        Parameters
        ----------
        gigabytes: float
          this upper bound in gigabytes.

        """
        return _pyagrum.ShaferShenoyInference_setMaxMemory(self, gigabytes)

    def setEvidence(self, evidces):
        """
        Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
        pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          self.eraseAllEvidence()
          for k,v in evidces.items():
            self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          self.eraseAllEvidence()
          for p in evidces:
            self.addEvidence(p)
          return
        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def updateEvidence(self, evidces):
        """
        Apply chgEvidence(key,value) for every pairs in evidces (or addEvidence).

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
          pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          for k,v in evidces.items():
              if self.hasEvidence(k):
                  self.chgEvidence(k,v)
              else:
                  self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          for p in evidces:
              k=p.variable(0)
              if self.hasEvidence(k):
                  self.chgEvidence(p)
              else:
                  self.addEvidence(p)
          return

        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def setTargets(self, targets):
        """
        Remove all the targets and add the ones in parameter.

        Parameters
        ----------
        targets : set
          a set of targets

        Raises
        ------
          pyagrum.UndefinedElement
            If one target is not in the Bayes net
        """
        if not isinstance(targets, set):
            raise TypeError("setTargets parameter must be a set, not %s"%(type(targets)))

        self.eraseAllTargets()
        for k in targets:
            self.addTarget(k)



    def hardEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with hard evidence

        """
        return _pyagrum.ShaferShenoyInference_hardEvidenceNodes(self)

    def softEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with soft evidence

        """
        return _pyagrum.ShaferShenoyInference_softEvidenceNodes(self)

    def targets(self) -> set[int]:
        r"""

        Returns
        -------
        list
          the list of marginal targets

        """
        return _pyagrum.ShaferShenoyInference_targets(self)

    def evidenceImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(target|evs) (for all instanciation of target and evs)

        Parameters
        ----------
        target : set
          a set of targets ids or names.
        evs : set
          a set of nodes ids or names.

        Warnings
        --------
        if some evs are d-separated, they are not included in the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(targets|evs)

        """
        return _pyagrum.ShaferShenoyInference_evidenceImpact(self, *args)

    def jointMutualInformation(self, targets: object) -> float:
        r"""

        Compute the joint mutual information (interaction information) among a set of target variables.

        See https://en.wikipedia.org/wiki/Interaction_information

        Parameters
        ----------
        targets : list[str] or set[int]
            the target variables (names or node ids)

        Returns
        -------
        float
            the joint mutual information

        """
        return _pyagrum.ShaferShenoyInference_jointMutualInformation(self, targets)

    def evidenceJointImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(joint targets|evs) (for all instanciation of targets and evs)

        Parameters
        ----------
        targets : list of int|str
          a list of node Ids or node names
        evs : set
          a set of nodes ids or names.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(target|evs)

        Raises
        ------
        pyagrum.Exception
          If some evidene entered into the Bayes net are incompatible (their joint proba = 0)

        """
        return _pyagrum.ShaferShenoyInference_evidenceJointImpact(self, *args)

    def jointPosterior(self, targets: object) -> "pyagrum.Tensor":
        r"""

        Compute the joint posterior of a set of nodes.

        Parameters
        ----------
        list :
          the list of nodes whose posterior joint probability is wanted


        Warnings
        --------
        The order of the variables given by the list here or when the jointTarget is declared can not be assumed to be used by the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior joint probability of the set of nodes.

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.ShaferShenoyInference_jointPosterior(self, targets)

    def addJointTarget(self, targets: object) -> None:
        r"""

        Add a list of nodes as a new joint target. As a collateral effect, every node is added as a marginal target.

        Parameters
        ----------
        list
          a list of names of nodes

        Raises
        ------
        pyagrum.UndefinedElement
          If some node(s) do not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyInference_addJointTarget(self, targets)

    def eraseJointTarget(self, targets: object) -> None:
        r"""

        Remove, if existing, the joint target.

        Parameters
        ----------
        list
          a list of names or Ids of nodes

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.ShaferShenoyInference_eraseJointTarget(self, targets)

    def isJointTarget(self, targets: object) -> bool:
        r"""

        Parameters
        ----------
        list
          a list of nodes ids or names.

        Returns
        -------
        bool
          True if target is a joint target.

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.ShaferShenoyInference_isJointTarget(self, targets)

    def jointTargets(self) -> list[set[int]]:
        r"""

        Returns
        -------
        list
          the list of target sets

        """
        return _pyagrum.ShaferShenoyInference_jointTargets(self)

# Register ShaferShenoyInference in _pyagrum:
_pyagrum.ShaferShenoyInference_swigregister(ShaferShenoyInference)
class VariableElimination(object):
    r"""

    Class used for Variable Elimination inference algorithm.

    Warnings
    --------
      Even if this inference has the same API than the other (exact) inferences, its mode of operation is different and is specifically dedicated to the calculation of a single posterior. Any other use (for instance for multiple targets) is possibly inefficient.

    VariableElimination(bn) -> VariableElimination
        Parameters:
            - **bn** (*pyagrum.BayesNet*) -- a Bayesian network

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.VariableElimination_swiginit(self, _pyagrum.new_VariableElimination(*args))

        self._model=args[0]



    __swig_destroy__ = _pyagrum.delete_VariableElimination

    def setTriangulation(self, new_triangulation: "pyagrum.Triangulation") -> None:
        r"""

        Replace the triangulation algorithm used to build the junction tree.

        Parameters
        ----------
        new_triangulation : pyagrum.Triangulation
            the new triangulation algorithm

        """
        return _pyagrum.VariableElimination_setTriangulation(self, new_triangulation)

    def setRelevantTensorsFinderType(self, type: int) -> None:
        r"""

        sets how we determine the relevant tensors to combine

        When a clique sends a message to a separator, it first constitute the set of the tensors it contains and of the tensors contained in the messages it received. If RelevantTensorsFinderType = FIND_ALL, all these tensors are combined and projected to produce the message sent to the separator. If RelevantTensorsFinderType = DSEP_BAYESBALL_NODES, then only the set of tensors d-connected to the variables of the separator are kept for combination and projection.

        0 = FIND_ALL
        1 = DSEP_BAYESBALL_NODES
        2 = DSEP_BAYESBALL_TENSORS
        3 = DSEP_KOLLER_FRIEDMAN_2009

        Parameters
        ----------
        type : int
          the finder type

        Raises
        ------
        pyagrum.InvalidArgument
          If type is not implemented

        """
        return _pyagrum.VariableElimination_setRelevantTensorsFinderType(self, type)

    def setFindBarrenNodesType(self, type: int) -> None:
        r"""

        sets how we determine barren nodes

        Barren nodes are unnecessary for probability inference, so they can be safely discarded in this case (type = FIND_BARREN_NODES). This speeds-up inference. However, there are some cases in which we do not want to remove barren nodes, typically when we want to answer queries such as Most Probable Explanations (MPE).

        0 = FIND_NO_BARREN_NODES
        1 = FIND_BARREN_NODES

        Parameters
        ----------
        type : int
          the finder type

        Raises
        ------
        pyagrum.InvalidArgument
          If type is not implemented

        """
        return _pyagrum.VariableElimination_setFindBarrenNodesType(self, type)

    def junctionTree(self, id: int) -> "pyagrum.JunctionTree":
        r"""

        Returns
        -------
        pyagrum.CliqueGraph
          the current junction tree

        """
        val = _pyagrum.VariableElimination_junctionTree(self, id)

        val._engine=self


        return val


    def makeInference(self) -> None:
        r"""

        Perform the heavy computations needed to compute the targets' posteriors

        In a Junction tree propagation scheme, for instance, the heavy computations are those of the messages sent in the JT.
        This is precisely what makeInference should compute. Later, the computations of the posteriors can be done 'lightly' by multiplying and projecting those messages.

        """
        return _pyagrum.VariableElimination_makeInference(self)

    def posterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior probability of the node

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.VariableElimination_posterior(self, *args)

    def addEvidence(self, *args) -> None:
        r"""

        Adds a new evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val :
          (int) a node value
        val :
          (str) the label of the node value
        vals : list
          a list of values

        Raises
        ------
          pyagrum.InvalidArgument
            If the node already has an evidence
          pyagrum.InvalidArgument
            If val is not a value for the node
          pyagrum.InvalidArgument
            If the size of vals is different from the domain side of the node
          pyagrum.FatalError
            If vals is a vector of 0s
          pyagrum.UndefinedElement
            If the node does not belong to the Bayesian network

        """
        return _pyagrum.VariableElimination_addEvidence(self, *args)

    def chgEvidence(self, *args) -> None:
        r"""

        Change the value of an already existing evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val : int|str
          a node value or the label of the node value
        vals : list of float
          a list of values

        Raises
        ------
        pyagrum.InvalidArgument
          If the node does not already have an evidence
        pyagrum.InvalidArgument
          If val is not a value for the node
        pyagrum.InvalidArgument
          If the size of vals is different from the domain side of the node
        pyagrum.FatalError
          If vals is a vector of 0s
        pyagrum.UndefinedElement
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.VariableElimination_chgEvidence(self, *args)

    def hasEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if some node(s) (or the one in parameters) have received evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.VariableElimination_hasEvidence(self, *args)

    def eraseAllEvidence(self) -> None:
        r"""

        Removes all the evidence entered into the network.

        """
        return _pyagrum.VariableElimination_eraseAllEvidence(self)

    def eraseEvidence(self, *args) -> None:
        r"""

        Remove the evidence, if any, corresponding to the node Id or name.

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.VariableElimination_eraseEvidence(self, *args)

    def hasHardEvidence(self, nodeName: str) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a hard evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.VariableElimination_hasHardEvidence(self, nodeName)

    def hasSoftEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a soft evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.VariableElimination_hasSoftEvidence(self, *args)

    def nbrEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of evidence entered into the Bayesian network

        """
        return _pyagrum.VariableElimination_nbrEvidence(self)

    def nbrHardEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of hard evidence entered into the Bayesian network

        """
        return _pyagrum.VariableElimination_nbrHardEvidence(self)

    def nbrSoftEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of soft evidence entered into the Bayesian network

        """
        return _pyagrum.VariableElimination_nbrSoftEvidence(self)

    def eraseAllTargets(self) -> None:
        r"""

        Clear all previously defined targets (marginal and joint targets).

        As a result, no posterior can be computed (since we can only compute the posteriors of the marginal or joint targets that have been added by the user).

        """
        return _pyagrum.VariableElimination_eraseAllTargets(self)

    def addAllTargets(self) -> None:
        r"""

        Add all the nodes as targets.

        """
        return _pyagrum.VariableElimination_addAllTargets(self)

    def addTarget(self, *args) -> None:
        r"""

        Add a marginal target to the list of targets.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : str
          a node name

        Raises
        ------
        pyagrum.UndefinedElement
          If target is not a NodeId in the Bayes net

        """
        return _pyagrum.VariableElimination_addTarget(self, *args)

    def eraseTarget(self, *args) -> None:
        r"""

        Remove, if existing, the marginal target.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.VariableElimination_eraseTarget(self, *args)

    def isTarget(self, *args) -> bool:
        r"""

        Parameters
        ----------
        variable : int
         a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if variable is a (marginal) target

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.VariableElimination_isTarget(self, *args)

    def nbrTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of marginal targets

        """
        return _pyagrum.VariableElimination_nbrTargets(self)

    def H(self, *args) -> float:
        r"""

        Parameters
        ----------
        X : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        float
          the computed Shanon's entropy of a node given the observation

        """
        return _pyagrum.VariableElimination_H(self, *args)

    def BN(self) -> "pyagrum.IBayesNet":
        r"""

        Returns
        -------
        pyagrum.IBayesNet
          A constant reference over the IBayesNet referenced by this class.

        Raises
        ------
          pyagrum.UndefinedElement
            If no Bayes net has been assigned to the inference.

        """
        return _pyagrum.VariableElimination_BN(self)

    def setNumberOfThreads(self, nb: int) -> None:
        r"""

        If the argument nb is different from 0, this number of threads will be used during inferences, hence overriding aGrUM's default number of threads.
        If, on the contrary, nb is equal to 0, the parallelized inference engine will comply with aGrUM's default number of threads.

        Parameters
        ----------
        nb : int
            the number of threads to be used by ShaferShenoyMRFInference

        """
        return _pyagrum.VariableElimination_setNumberOfThreads(self, nb)

    def getNumberOfThreads(self) -> int:
        r"""

        returns the number of threads used by LazyPropagation during inferences.

        Returns
        -------
        int
            the number of threads used by LazyPropagation during inferences

        """
        return _pyagrum.VariableElimination_getNumberOfThreads(self)

    def isGumNumberOfThreadsOverriden(self) -> bool:
        r"""

        Indicates whether LazyPropagation currently overrides aGrUM's default number of threads (see method setNumberOfThreads).

        Returns
        -------
        bool
            A Boolean indicating whether LazyPropagation currently overrides aGrUM's default number of threads

        """
        return _pyagrum.VariableElimination_isGumNumberOfThreadsOverriden(self)

    def setMaxMemory(self, gigabytes: int) -> None:
        r"""

        sets an upper bound on the memory consumption admissible

        Parameters
        ----------
        gigabytes: float
          this upper bound in gigabytes.

        """
        return _pyagrum.VariableElimination_setMaxMemory(self, gigabytes)

    def setEvidence(self, evidces):
        """
        Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
        pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          self.eraseAllEvidence()
          for k,v in evidces.items():
            self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          self.eraseAllEvidence()
          for p in evidces:
            self.addEvidence(p)
          return
        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def updateEvidence(self, evidces):
        """
        Apply chgEvidence(key,value) for every pairs in evidces (or addEvidence).

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
          pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          for k,v in evidces.items():
              if self.hasEvidence(k):
                  self.chgEvidence(k,v)
              else:
                  self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          for p in evidces:
              k=p.variable(0)
              if self.hasEvidence(k):
                  self.chgEvidence(p)
              else:
                  self.addEvidence(p)
          return

        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def setTargets(self, targets):
        """
        Remove all the targets and add the ones in parameter.

        Parameters
        ----------
        targets : set
          a set of targets

        Raises
        ------
          pyagrum.UndefinedElement
            If one target is not in the Bayes net
        """
        if not isinstance(targets, set):
            raise TypeError("setTargets parameter must be a set, not %s"%(type(targets)))

        self.eraseAllTargets()
        for k in targets:
            self.addTarget(k)



    def hardEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with hard evidence

        """
        return _pyagrum.VariableElimination_hardEvidenceNodes(self)

    def softEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with soft evidence

        """
        return _pyagrum.VariableElimination_softEvidenceNodes(self)

    def targets(self) -> set[int]:
        r"""

        Returns
        -------
        list
          the list of marginal targets

        """
        return _pyagrum.VariableElimination_targets(self)

    def evidenceImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(target|evs) (for all instanciation of target and evs)

        Parameters
        ----------
        target : set
          a set of targets ids or names.
        evs : set
          a set of nodes ids or names.

        Warnings
        --------
        if some evs are d-separated, they are not included in the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(targets|evs)

        """
        return _pyagrum.VariableElimination_evidenceImpact(self, *args)

    def jointMutualInformation(self, targets: object) -> float:
        r"""

        Compute the joint mutual information (interaction information) among a set of target variables.

        See https://en.wikipedia.org/wiki/Interaction_information

        Parameters
        ----------
        targets : list[str] or set[int]
            the target variables (names or node ids)

        Returns
        -------
        float
            the joint mutual information

        """
        return _pyagrum.VariableElimination_jointMutualInformation(self, targets)

    def evidenceJointImpact(self, targets: object, evs: object) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(joint targets|evs) (for all instanciation of targets and evs)

        Parameters
        ----------
        targets : list of int|str
          a list of node Ids or node names
        evs : set
          a set of nodes ids or names.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(target|evs)

        Raises
        ------
        pyagrum.Exception
          If some evidene entered into the Bayes net are incompatible (their joint proba = 0)

        """
        return _pyagrum.VariableElimination_evidenceJointImpact(self, targets, evs)

    def jointPosterior(self, targets: object) -> "pyagrum.Tensor":
        r"""

        Compute the joint posterior of a set of nodes.

        Parameters
        ----------
        list :
          the list of nodes whose posterior joint probability is wanted


        Warnings
        --------
        The order of the variables given by the list here or when the jointTarget is declared can not be assumed to be used by the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior joint probability of the set of nodes.

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.VariableElimination_jointPosterior(self, targets)

    def addJointTarget(self, targets: object) -> None:
        r"""

        Add a list of nodes as a new joint target. As a collateral effect, every node is added as a marginal target.

        Parameters
        ----------
        list
          a list of names of nodes

        Raises
        ------
        pyagrum.UndefinedElement
          If some node(s) do not belong to the Bayesian network

        """
        return _pyagrum.VariableElimination_addJointTarget(self, targets)

    def eraseJointTarget(self, targets: object) -> None:
        r"""

        Remove, if existing, the joint target.

        Parameters
        ----------
        list
          a list of names or Ids of nodes

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.VariableElimination_eraseJointTarget(self, targets)

    def isJointTarget(self, targets: object) -> bool:
        r"""

        Parameters
        ----------
        list
          a list of nodes ids or names.

        Returns
        -------
        bool
          True if target is a joint target.

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.VariableElimination_isJointTarget(self, targets)

    def jointTargets(self) -> list[set[int]]:
        r"""

        Returns
        -------
        list
          the list of target sets

        """
        return _pyagrum.VariableElimination_jointTargets(self)

# Register VariableElimination in _pyagrum:
_pyagrum.VariableElimination_swigregister(VariableElimination)
class GibbsSampling(object):
    r"""

    Class for making Gibbs sampling inference in Bayesian networks.

    GibbsSampling(bn) -> GibbsSampling
        Parameters:
          - **bn** (*pyagrum.BayesNet*) -- a Bayesian network

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, bn: "IBayesNet"):
        _pyagrum.GibbsSampling_swiginit(self, _pyagrum.new_GibbsSampling(bn))

        self._model=bn#BN



    __swig_destroy__ = _pyagrum.delete_GibbsSampling

    def setBurnIn(self, b: int) -> None:
        r"""

        Parameters
        ----------
        b : int
          size of burn in on number of iteration

        """
        return _pyagrum.GibbsSampling_setBurnIn(self, b)

    def burnIn(self) -> int:
        r"""

        Returns
        -------
        int
          size of burn in on number of iteration

        """
        return _pyagrum.GibbsSampling_burnIn(self)

    def setVerbosity(self, v: bool) -> None:
        r"""

        Parameters
        ----------
        v : bool
                verbosity

        """
        return _pyagrum.GibbsSampling_setVerbosity(self, v)

    def setEpsilon(self, eps: float) -> None:
        r"""

        Parameters
        ----------
        eps : float
            the epsilon we want to use

        Raises
        ------
        pyagrum.OutOfBounds
            If eps<0

        """
        return _pyagrum.GibbsSampling_setEpsilon(self, eps)

    def setMinEpsilonRate(self, rate: float) -> None:
        r"""

        Parameters
        ----------
        rate : float
            the minimal epsilon rate

        """
        return _pyagrum.GibbsSampling_setMinEpsilonRate(self, rate)

    def setMaxIter(self, max: int) -> None:
        r"""

        Parameters
        ----------
        max : int
            the maximum number of iteration

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1

        """
        return _pyagrum.GibbsSampling_setMaxIter(self, max)

    def setMaxTime(self, timeout: float) -> None:
        r"""

        Parameters
        ----------
        tiemout : float
            stopping criterion on timeout (in seconds)

        Raises
        ------
        pyagrum.OutOfBounds
            If timeout<=0.0

        """
        return _pyagrum.GibbsSampling_setMaxTime(self, timeout)

    def setPeriodSize(self, p: int) -> None:
        r"""

        Parameters
        ----------
        p : int
            number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.GibbsSampling_setPeriodSize(self, p)

    def verbosity(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the verbosity is enabled

        """
        return _pyagrum.GibbsSampling_verbosity(self)

    def epsilon(self) -> float:
        r"""

        Returns
        -------
        float
            the value of epsilon

        """
        return _pyagrum.GibbsSampling_epsilon(self)

    def minEpsilonRate(self) -> float:
        r"""

        Returns
        -------
        float
            the value of the minimal epsilon rate

        """
        return _pyagrum.GibbsSampling_minEpsilonRate(self)

    def maxIter(self) -> int:
        r"""

        Returns
        -------
        int
            the criterion on number of iterations

        """
        return _pyagrum.GibbsSampling_maxIter(self)

    def maxTime(self) -> float:
        r"""

        Returns
        -------
        float
            the timeout(in seconds)

        """
        return _pyagrum.GibbsSampling_maxTime(self)

    def periodSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.GibbsSampling_periodSize(self)

    def nbrIterations(self) -> int:
        r"""

        Returns
        -------
        int
            the number of iterations

        """
        return _pyagrum.GibbsSampling_nbrIterations(self)

    def currentTime(self) -> float:
        r"""

        Returns
        -------
        float
            get the current running time in second (float)

        """
        return _pyagrum.GibbsSampling_currentTime(self)

    def messageApproximationScheme(self) -> str:
        r"""

        Returns
        -------
        str
            the approximation scheme message

        """
        return _pyagrum.GibbsSampling_messageApproximationScheme(self)

    def history(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple
            the scheme history

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the scheme did not performed or if verbosity is set to false

        """
        return _pyagrum.GibbsSampling_history(self)

    def _asIApproximationSchemeConfiguration(self) -> "pyagrum.YetUnWrapped":
        return _pyagrum.GibbsSampling__asIApproximationSchemeConfiguration(self)

    def makeInference(self) -> None:
        r"""

        Perform the heavy computations needed to compute the targets' posteriors

        In a Junction tree propagation scheme, for instance, the heavy computations are those of the messages sent in the JT.
        This is precisely what makeInference should compute. Later, the computations of the posteriors can be done 'lightly' by multiplying and projecting those messages.

        """
        return _pyagrum.GibbsSampling_makeInference(self)

    def posterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior probability of the node

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.GibbsSampling_posterior(self, *args)

    def addEvidence(self, *args) -> None:
        r"""

        Adds a new evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val :
          (int) a node value
        val :
          (str) the label of the node value
        vals : list
          a list of values

        Raises
        ------
          pyagrum.InvalidArgument
            If the node already has an evidence
          pyagrum.InvalidArgument
            If val is not a value for the node
          pyagrum.InvalidArgument
            If the size of vals is different from the domain side of the node
          pyagrum.FatalError
            If vals is a vector of 0s
          pyagrum.UndefinedElement
            If the node does not belong to the Bayesian network

        """
        return _pyagrum.GibbsSampling_addEvidence(self, *args)

    def chgEvidence(self, *args) -> None:
        r"""

        Change the value of an already existing evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val : int|str
          a node value or the label of the node value
        vals : list of float
          a list of values

        Raises
        ------
        pyagrum.InvalidArgument
          If the node does not already have an evidence
        pyagrum.InvalidArgument
          If val is not a value for the node
        pyagrum.InvalidArgument
          If the size of vals is different from the domain side of the node
        pyagrum.FatalError
          If vals is a vector of 0s
        pyagrum.UndefinedElement
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.GibbsSampling_chgEvidence(self, *args)

    def hasEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if some node(s) (or the one in parameters) have received evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.GibbsSampling_hasEvidence(self, *args)

    def eraseAllEvidence(self) -> None:
        r"""

        Removes all the evidence entered into the network.

        """
        return _pyagrum.GibbsSampling_eraseAllEvidence(self)

    def eraseEvidence(self, *args) -> None:
        r"""

        Remove the evidence, if any, corresponding to the node Id or name.

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.GibbsSampling_eraseEvidence(self, *args)

    def hasHardEvidence(self, nodeName: str) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a hard evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.GibbsSampling_hasHardEvidence(self, nodeName)

    def hasSoftEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a soft evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.GibbsSampling_hasSoftEvidence(self, *args)

    def nbrEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of evidence entered into the Bayesian network

        """
        return _pyagrum.GibbsSampling_nbrEvidence(self)

    def nbrHardEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of hard evidence entered into the Bayesian network

        """
        return _pyagrum.GibbsSampling_nbrHardEvidence(self)

    def nbrSoftEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of soft evidence entered into the Bayesian network

        """
        return _pyagrum.GibbsSampling_nbrSoftEvidence(self)

    def eraseAllTargets(self) -> None:
        r"""

        Clear all previously defined targets (marginal and joint targets).

        As a result, no posterior can be computed (since we can only compute the posteriors of the marginal or joint targets that have been added by the user).

        """
        return _pyagrum.GibbsSampling_eraseAllTargets(self)

    def addAllTargets(self) -> None:
        r"""

        Add all the nodes as targets.

        """
        return _pyagrum.GibbsSampling_addAllTargets(self)

    def addTarget(self, *args) -> None:
        r"""

        Add a marginal target to the list of targets.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : str
          a node name

        Raises
        ------
        pyagrum.UndefinedElement
          If target is not a NodeId in the Bayes net

        """
        return _pyagrum.GibbsSampling_addTarget(self, *args)

    def eraseTarget(self, *args) -> None:
        r"""

        Remove, if existing, the marginal target.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.GibbsSampling_eraseTarget(self, *args)

    def isTarget(self, *args) -> bool:
        r"""

        Parameters
        ----------
        variable : int
         a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if variable is a (marginal) target

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.GibbsSampling_isTarget(self, *args)

    def nbrTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of marginal targets

        """
        return _pyagrum.GibbsSampling_nbrTargets(self)

    def H(self, *args) -> float:
        r"""

        Parameters
        ----------
        X : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        float
          the computed Shanon's entropy of a node given the observation

        """
        return _pyagrum.GibbsSampling_H(self, *args)

    def BN(self) -> "pyagrum.IBayesNet":
        r"""

        Returns
        -------
        pyagrum.IBayesNet
          A constant reference over the IBayesNet referenced by this class.

        Raises
        ------
          pyagrum.UndefinedElement
            If no Bayes net has been assigned to the inference.

        """
        return _pyagrum.GibbsSampling_BN(self)

    def currentPosterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the current posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the current posterior probability of the node

        Raises
        ------
        UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.GibbsSampling_currentPosterior(self, *args)

    def nbrDrawnVar(self) -> int:
        r"""

        Returns
        -------
        int
          the number of variable drawn at each iteration

        """
        return _pyagrum.GibbsSampling_nbrDrawnVar(self)

    def setNbrDrawnVar(self, _nbr: int) -> None:
        r"""

        Parameters
        ----------
        _nbr : int
          the number of variables to be drawn at each iteration

        """
        return _pyagrum.GibbsSampling_setNbrDrawnVar(self, _nbr)

    def isDrawnAtRandom(self) -> bool:
        r"""

        Returns
        -------
        bool
          True if variables are drawn at random

        """
        return _pyagrum.GibbsSampling_isDrawnAtRandom(self)

    def setDrawnAtRandom(self, _atRandom: bool) -> None:
        r"""

        Parameters
        ----------
        _atRandom : bool
          indicates if variables should be drawn at random

        """
        return _pyagrum.GibbsSampling_setDrawnAtRandom(self, _atRandom)

    def setEvidence(self, evidces):
        """
        Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
        pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          self.eraseAllEvidence()
          for k,v in evidces.items():
            self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          self.eraseAllEvidence()
          for p in evidces:
            self.addEvidence(p)
          return
        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def updateEvidence(self, evidces):
        """
        Apply chgEvidence(key,value) for every pairs in evidces (or addEvidence).

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
          pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          for k,v in evidces.items():
              if self.hasEvidence(k):
                  self.chgEvidence(k,v)
              else:
                  self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          for p in evidces:
              k=p.variable(0)
              if self.hasEvidence(k):
                  self.chgEvidence(p)
              else:
                  self.addEvidence(p)
          return

        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def setTargets(self, targets):
        """
        Remove all the targets and add the ones in parameter.

        Parameters
        ----------
        targets : set
          a set of targets

        Raises
        ------
          pyagrum.UndefinedElement
            If one target is not in the Bayes net
        """
        if not isinstance(targets, set):
            raise TypeError("setTargets parameter must be a set, not %s"%(type(targets)))

        self.eraseAllTargets()
        for k in targets:
            self.addTarget(k)



    def hardEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with hard evidence

        """
        return _pyagrum.GibbsSampling_hardEvidenceNodes(self)

    def softEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with soft evidence

        """
        return _pyagrum.GibbsSampling_softEvidenceNodes(self)

    def targets(self) -> set[int]:
        r"""

        Returns
        -------
        list
          the list of marginal targets

        """
        return _pyagrum.GibbsSampling_targets(self)

    def evidenceImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(target|evs) (for all instanciation of target and evs)

        Parameters
        ----------
        target : set
          a set of targets ids or names.
        evs : set
          a set of nodes ids or names.

        Warnings
        --------
        if some evs are d-separated, they are not included in the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(targets|evs)

        """
        return _pyagrum.GibbsSampling_evidenceImpact(self, *args)

# Register GibbsSampling in _pyagrum:
_pyagrum.GibbsSampling_swigregister(GibbsSampling)
class ImportanceSampling(object):
    r"""

    Class used for inferences using the Importance Sampling algorithm.

    ImportanceSampling(bn) -> ImportanceSampling
        Parameters:
            - **bn** (*pyagrum.BayesNet*) -- a Bayesian network

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, bn: "IBayesNet"):
        _pyagrum.ImportanceSampling_swiginit(self, _pyagrum.new_ImportanceSampling(bn))

        self._model=bn#BN



    __swig_destroy__ = _pyagrum.delete_ImportanceSampling

    def setVerbosity(self, v: bool) -> None:
        r"""

        Parameters
        ----------
        v : bool
                verbosity

        """
        return _pyagrum.ImportanceSampling_setVerbosity(self, v)

    def setEpsilon(self, eps: float) -> None:
        r"""

        Parameters
        ----------
        eps : float
            the epsilon we want to use

        Raises
        ------
        pyagrum.OutOfBounds
            If eps<0

        """
        return _pyagrum.ImportanceSampling_setEpsilon(self, eps)

    def setMinEpsilonRate(self, rate: float) -> None:
        r"""

        Parameters
        ----------
        rate : float
            the minimal epsilon rate

        """
        return _pyagrum.ImportanceSampling_setMinEpsilonRate(self, rate)

    def setMaxIter(self, max: int) -> None:
        r"""

        Parameters
        ----------
        max : int
            the maximum number of iteration

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1

        """
        return _pyagrum.ImportanceSampling_setMaxIter(self, max)

    def setMaxTime(self, timeout: float) -> None:
        r"""

        Parameters
        ----------
        tiemout : float
            stopping criterion on timeout (in seconds)

        Raises
        ------
        pyagrum.OutOfBounds
            If timeout<=0.0

        """
        return _pyagrum.ImportanceSampling_setMaxTime(self, timeout)

    def setPeriodSize(self, p: int) -> None:
        r"""

        Parameters
        ----------
        p : int
            number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.ImportanceSampling_setPeriodSize(self, p)

    def verbosity(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the verbosity is enabled

        """
        return _pyagrum.ImportanceSampling_verbosity(self)

    def epsilon(self) -> float:
        r"""

        Returns
        -------
        float
            the value of epsilon

        """
        return _pyagrum.ImportanceSampling_epsilon(self)

    def minEpsilonRate(self) -> float:
        r"""

        Returns
        -------
        float
            the value of the minimal epsilon rate

        """
        return _pyagrum.ImportanceSampling_minEpsilonRate(self)

    def maxIter(self) -> int:
        r"""

        Returns
        -------
        int
            the criterion on number of iterations

        """
        return _pyagrum.ImportanceSampling_maxIter(self)

    def maxTime(self) -> float:
        r"""

        Returns
        -------
        float
            the timeout(in seconds)

        """
        return _pyagrum.ImportanceSampling_maxTime(self)

    def periodSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.ImportanceSampling_periodSize(self)

    def nbrIterations(self) -> int:
        r"""

        Returns
        -------
        int
            the number of iterations

        """
        return _pyagrum.ImportanceSampling_nbrIterations(self)

    def currentTime(self) -> float:
        r"""

        Returns
        -------
        float
            get the current running time in second (float)

        """
        return _pyagrum.ImportanceSampling_currentTime(self)

    def messageApproximationScheme(self) -> str:
        r"""

        Returns
        -------
        str
            the approximation scheme message

        """
        return _pyagrum.ImportanceSampling_messageApproximationScheme(self)

    def history(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple
            the scheme history

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the scheme did not performed or if verbosity is set to false

        """
        return _pyagrum.ImportanceSampling_history(self)

    def _asIApproximationSchemeConfiguration(self) -> "pyagrum.YetUnWrapped":
        return _pyagrum.ImportanceSampling__asIApproximationSchemeConfiguration(self)

    def makeInference(self) -> None:
        r"""

        Perform the heavy computations needed to compute the targets' posteriors

        In a Junction tree propagation scheme, for instance, the heavy computations are those of the messages sent in the JT.
        This is precisely what makeInference should compute. Later, the computations of the posteriors can be done 'lightly' by multiplying and projecting those messages.

        """
        return _pyagrum.ImportanceSampling_makeInference(self)

    def posterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior probability of the node

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.ImportanceSampling_posterior(self, *args)

    def addEvidence(self, *args) -> None:
        r"""

        Adds a new evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val :
          (int) a node value
        val :
          (str) the label of the node value
        vals : list
          a list of values

        Raises
        ------
          pyagrum.InvalidArgument
            If the node already has an evidence
          pyagrum.InvalidArgument
            If val is not a value for the node
          pyagrum.InvalidArgument
            If the size of vals is different from the domain side of the node
          pyagrum.FatalError
            If vals is a vector of 0s
          pyagrum.UndefinedElement
            If the node does not belong to the Bayesian network

        """
        return _pyagrum.ImportanceSampling_addEvidence(self, *args)

    def chgEvidence(self, *args) -> None:
        r"""

        Change the value of an already existing evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val : int|str
          a node value or the label of the node value
        vals : list of float
          a list of values

        Raises
        ------
        pyagrum.InvalidArgument
          If the node does not already have an evidence
        pyagrum.InvalidArgument
          If val is not a value for the node
        pyagrum.InvalidArgument
          If the size of vals is different from the domain side of the node
        pyagrum.FatalError
          If vals is a vector of 0s
        pyagrum.UndefinedElement
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ImportanceSampling_chgEvidence(self, *args)

    def hasEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if some node(s) (or the one in parameters) have received evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ImportanceSampling_hasEvidence(self, *args)

    def eraseAllEvidence(self) -> None:
        r"""

        Removes all the evidence entered into the network.

        """
        return _pyagrum.ImportanceSampling_eraseAllEvidence(self)

    def eraseEvidence(self, *args) -> None:
        r"""

        Remove the evidence, if any, corresponding to the node Id or name.

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ImportanceSampling_eraseEvidence(self, *args)

    def hasHardEvidence(self, nodeName: str) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a hard evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ImportanceSampling_hasHardEvidence(self, nodeName)

    def hasSoftEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a soft evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ImportanceSampling_hasSoftEvidence(self, *args)

    def nbrEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of evidence entered into the Bayesian network

        """
        return _pyagrum.ImportanceSampling_nbrEvidence(self)

    def nbrHardEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of hard evidence entered into the Bayesian network

        """
        return _pyagrum.ImportanceSampling_nbrHardEvidence(self)

    def nbrSoftEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of soft evidence entered into the Bayesian network

        """
        return _pyagrum.ImportanceSampling_nbrSoftEvidence(self)

    def eraseAllTargets(self) -> None:
        r"""

        Clear all previously defined targets (marginal and joint targets).

        As a result, no posterior can be computed (since we can only compute the posteriors of the marginal or joint targets that have been added by the user).

        """
        return _pyagrum.ImportanceSampling_eraseAllTargets(self)

    def addAllTargets(self) -> None:
        r"""

        Add all the nodes as targets.

        """
        return _pyagrum.ImportanceSampling_addAllTargets(self)

    def addTarget(self, *args) -> None:
        r"""

        Add a marginal target to the list of targets.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : str
          a node name

        Raises
        ------
        pyagrum.UndefinedElement
          If target is not a NodeId in the Bayes net

        """
        return _pyagrum.ImportanceSampling_addTarget(self, *args)

    def eraseTarget(self, *args) -> None:
        r"""

        Remove, if existing, the marginal target.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.ImportanceSampling_eraseTarget(self, *args)

    def isTarget(self, *args) -> bool:
        r"""

        Parameters
        ----------
        variable : int
         a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if variable is a (marginal) target

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.ImportanceSampling_isTarget(self, *args)

    def nbrTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of marginal targets

        """
        return _pyagrum.ImportanceSampling_nbrTargets(self)

    def H(self, *args) -> float:
        r"""

        Parameters
        ----------
        X : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        float
          the computed Shanon's entropy of a node given the observation

        """
        return _pyagrum.ImportanceSampling_H(self, *args)

    def BN(self) -> "pyagrum.IBayesNet":
        r"""

        Returns
        -------
        pyagrum.IBayesNet
          A constant reference over the IBayesNet referenced by this class.

        Raises
        ------
          pyagrum.UndefinedElement
            If no Bayes net has been assigned to the inference.

        """
        return _pyagrum.ImportanceSampling_BN(self)

    def currentPosterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the current posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the current posterior probability of the node

        Raises
        ------
        UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.ImportanceSampling_currentPosterior(self, *args)

    def setEvidence(self, evidces):
        """
        Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
        pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          self.eraseAllEvidence()
          for k,v in evidces.items():
            self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          self.eraseAllEvidence()
          for p in evidces:
            self.addEvidence(p)
          return
        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def updateEvidence(self, evidces):
        """
        Apply chgEvidence(key,value) for every pairs in evidces (or addEvidence).

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
          pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          for k,v in evidces.items():
              if self.hasEvidence(k):
                  self.chgEvidence(k,v)
              else:
                  self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          for p in evidces:
              k=p.variable(0)
              if self.hasEvidence(k):
                  self.chgEvidence(p)
              else:
                  self.addEvidence(p)
          return

        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def setTargets(self, targets):
        """
        Remove all the targets and add the ones in parameter.

        Parameters
        ----------
        targets : set
          a set of targets

        Raises
        ------
          pyagrum.UndefinedElement
            If one target is not in the Bayes net
        """
        if not isinstance(targets, set):
            raise TypeError("setTargets parameter must be a set, not %s"%(type(targets)))

        self.eraseAllTargets()
        for k in targets:
            self.addTarget(k)



    def hardEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with hard evidence

        """
        return _pyagrum.ImportanceSampling_hardEvidenceNodes(self)

    def softEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with soft evidence

        """
        return _pyagrum.ImportanceSampling_softEvidenceNodes(self)

    def targets(self) -> set[int]:
        r"""

        Returns
        -------
        list
          the list of marginal targets

        """
        return _pyagrum.ImportanceSampling_targets(self)

    def evidenceImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(target|evs) (for all instanciation of target and evs)

        Parameters
        ----------
        target : set
          a set of targets ids or names.
        evs : set
          a set of nodes ids or names.

        Warnings
        --------
        if some evs are d-separated, they are not included in the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(targets|evs)

        """
        return _pyagrum.ImportanceSampling_evidenceImpact(self, *args)

# Register ImportanceSampling in _pyagrum:
_pyagrum.ImportanceSampling_swigregister(ImportanceSampling)
class WeightedSampling(object):
    r"""

    Class used for Weighted sampling inference algorithm.

    WeightedSampling(bn) -> WeightedSampling
        Parameters:
            - **bn** (*pyagrum.BayesNet*) -- a Bayesian network

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, bn: "IBayesNet"):
        _pyagrum.WeightedSampling_swiginit(self, _pyagrum.new_WeightedSampling(bn))

        self._model=bn#BN



    __swig_destroy__ = _pyagrum.delete_WeightedSampling

    def setVerbosity(self, v: bool) -> None:
        r"""

        Parameters
        ----------
        v : bool
                verbosity

        """
        return _pyagrum.WeightedSampling_setVerbosity(self, v)

    def setEpsilon(self, eps: float) -> None:
        r"""

        Parameters
        ----------
        eps : float
            the epsilon we want to use

        Raises
        ------
        pyagrum.OutOfBounds
            If eps<0

        """
        return _pyagrum.WeightedSampling_setEpsilon(self, eps)

    def setMinEpsilonRate(self, rate: float) -> None:
        r"""

        Parameters
        ----------
        rate : float
            the minimal epsilon rate

        """
        return _pyagrum.WeightedSampling_setMinEpsilonRate(self, rate)

    def setMaxIter(self, max: int) -> None:
        r"""

        Parameters
        ----------
        max : int
            the maximum number of iteration

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1

        """
        return _pyagrum.WeightedSampling_setMaxIter(self, max)

    def setMaxTime(self, timeout: float) -> None:
        r"""

        Parameters
        ----------
        tiemout : float
            stopping criterion on timeout (in seconds)

        Raises
        ------
        pyagrum.OutOfBounds
            If timeout<=0.0

        """
        return _pyagrum.WeightedSampling_setMaxTime(self, timeout)

    def setPeriodSize(self, p: int) -> None:
        r"""

        Parameters
        ----------
        p : int
            number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.WeightedSampling_setPeriodSize(self, p)

    def verbosity(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the verbosity is enabled

        """
        return _pyagrum.WeightedSampling_verbosity(self)

    def epsilon(self) -> float:
        r"""

        Returns
        -------
        float
            the value of epsilon

        """
        return _pyagrum.WeightedSampling_epsilon(self)

    def minEpsilonRate(self) -> float:
        r"""

        Returns
        -------
        float
            the value of the minimal epsilon rate

        """
        return _pyagrum.WeightedSampling_minEpsilonRate(self)

    def maxIter(self) -> int:
        r"""

        Returns
        -------
        int
            the criterion on number of iterations

        """
        return _pyagrum.WeightedSampling_maxIter(self)

    def maxTime(self) -> float:
        r"""

        Returns
        -------
        float
            the timeout(in seconds)

        """
        return _pyagrum.WeightedSampling_maxTime(self)

    def periodSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.WeightedSampling_periodSize(self)

    def nbrIterations(self) -> int:
        r"""

        Returns
        -------
        int
            the number of iterations

        """
        return _pyagrum.WeightedSampling_nbrIterations(self)

    def currentTime(self) -> float:
        r"""

        Returns
        -------
        float
            get the current running time in second (float)

        """
        return _pyagrum.WeightedSampling_currentTime(self)

    def messageApproximationScheme(self) -> str:
        r"""

        Returns
        -------
        str
            the approximation scheme message

        """
        return _pyagrum.WeightedSampling_messageApproximationScheme(self)

    def history(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple
            the scheme history

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the scheme did not performed or if verbosity is set to false

        """
        return _pyagrum.WeightedSampling_history(self)

    def _asIApproximationSchemeConfiguration(self) -> "pyagrum.YetUnWrapped":
        return _pyagrum.WeightedSampling__asIApproximationSchemeConfiguration(self)

    def makeInference(self) -> None:
        r"""

        Perform the heavy computations needed to compute the targets' posteriors

        In a Junction tree propagation scheme, for instance, the heavy computations are those of the messages sent in the JT.
        This is precisely what makeInference should compute. Later, the computations of the posteriors can be done 'lightly' by multiplying and projecting those messages.

        """
        return _pyagrum.WeightedSampling_makeInference(self)

    def posterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior probability of the node

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.WeightedSampling_posterior(self, *args)

    def addEvidence(self, *args) -> None:
        r"""

        Adds a new evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val :
          (int) a node value
        val :
          (str) the label of the node value
        vals : list
          a list of values

        Raises
        ------
          pyagrum.InvalidArgument
            If the node already has an evidence
          pyagrum.InvalidArgument
            If val is not a value for the node
          pyagrum.InvalidArgument
            If the size of vals is different from the domain side of the node
          pyagrum.FatalError
            If vals is a vector of 0s
          pyagrum.UndefinedElement
            If the node does not belong to the Bayesian network

        """
        return _pyagrum.WeightedSampling_addEvidence(self, *args)

    def chgEvidence(self, *args) -> None:
        r"""

        Change the value of an already existing evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val : int|str
          a node value or the label of the node value
        vals : list of float
          a list of values

        Raises
        ------
        pyagrum.InvalidArgument
          If the node does not already have an evidence
        pyagrum.InvalidArgument
          If val is not a value for the node
        pyagrum.InvalidArgument
          If the size of vals is different from the domain side of the node
        pyagrum.FatalError
          If vals is a vector of 0s
        pyagrum.UndefinedElement
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.WeightedSampling_chgEvidence(self, *args)

    def hasEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if some node(s) (or the one in parameters) have received evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.WeightedSampling_hasEvidence(self, *args)

    def eraseAllEvidence(self) -> None:
        r"""

        Removes all the evidence entered into the network.

        """
        return _pyagrum.WeightedSampling_eraseAllEvidence(self)

    def eraseEvidence(self, *args) -> None:
        r"""

        Remove the evidence, if any, corresponding to the node Id or name.

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.WeightedSampling_eraseEvidence(self, *args)

    def hasHardEvidence(self, nodeName: str) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a hard evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.WeightedSampling_hasHardEvidence(self, nodeName)

    def hasSoftEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a soft evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.WeightedSampling_hasSoftEvidence(self, *args)

    def nbrEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of evidence entered into the Bayesian network

        """
        return _pyagrum.WeightedSampling_nbrEvidence(self)

    def nbrHardEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of hard evidence entered into the Bayesian network

        """
        return _pyagrum.WeightedSampling_nbrHardEvidence(self)

    def nbrSoftEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of soft evidence entered into the Bayesian network

        """
        return _pyagrum.WeightedSampling_nbrSoftEvidence(self)

    def eraseAllTargets(self) -> None:
        r"""

        Clear all previously defined targets (marginal and joint targets).

        As a result, no posterior can be computed (since we can only compute the posteriors of the marginal or joint targets that have been added by the user).

        """
        return _pyagrum.WeightedSampling_eraseAllTargets(self)

    def addAllTargets(self) -> None:
        r"""

        Add all the nodes as targets.

        """
        return _pyagrum.WeightedSampling_addAllTargets(self)

    def addTarget(self, *args) -> None:
        r"""

        Add a marginal target to the list of targets.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : str
          a node name

        Raises
        ------
        pyagrum.UndefinedElement
          If target is not a NodeId in the Bayes net

        """
        return _pyagrum.WeightedSampling_addTarget(self, *args)

    def eraseTarget(self, *args) -> None:
        r"""

        Remove, if existing, the marginal target.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.WeightedSampling_eraseTarget(self, *args)

    def isTarget(self, *args) -> bool:
        r"""

        Parameters
        ----------
        variable : int
         a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if variable is a (marginal) target

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.WeightedSampling_isTarget(self, *args)

    def nbrTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of marginal targets

        """
        return _pyagrum.WeightedSampling_nbrTargets(self)

    def H(self, *args) -> float:
        r"""

        Parameters
        ----------
        X : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        float
          the computed Shanon's entropy of a node given the observation

        """
        return _pyagrum.WeightedSampling_H(self, *args)

    def BN(self) -> "pyagrum.IBayesNet":
        r"""

        Returns
        -------
        pyagrum.IBayesNet
          A constant reference over the IBayesNet referenced by this class.

        Raises
        ------
          pyagrum.UndefinedElement
            If no Bayes net has been assigned to the inference.

        """
        return _pyagrum.WeightedSampling_BN(self)

    def currentPosterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the current posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the current posterior probability of the node

        Raises
        ------
        UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.WeightedSampling_currentPosterior(self, *args)

    def setEvidence(self, evidces):
        """
        Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
        pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          self.eraseAllEvidence()
          for k,v in evidces.items():
            self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          self.eraseAllEvidence()
          for p in evidces:
            self.addEvidence(p)
          return
        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def updateEvidence(self, evidces):
        """
        Apply chgEvidence(key,value) for every pairs in evidces (or addEvidence).

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
          pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          for k,v in evidces.items():
              if self.hasEvidence(k):
                  self.chgEvidence(k,v)
              else:
                  self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          for p in evidces:
              k=p.variable(0)
              if self.hasEvidence(k):
                  self.chgEvidence(p)
              else:
                  self.addEvidence(p)
          return

        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def setTargets(self, targets):
        """
        Remove all the targets and add the ones in parameter.

        Parameters
        ----------
        targets : set
          a set of targets

        Raises
        ------
          pyagrum.UndefinedElement
            If one target is not in the Bayes net
        """
        if not isinstance(targets, set):
            raise TypeError("setTargets parameter must be a set, not %s"%(type(targets)))

        self.eraseAllTargets()
        for k in targets:
            self.addTarget(k)



    def hardEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with hard evidence

        """
        return _pyagrum.WeightedSampling_hardEvidenceNodes(self)

    def softEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with soft evidence

        """
        return _pyagrum.WeightedSampling_softEvidenceNodes(self)

    def targets(self) -> set[int]:
        r"""

        Returns
        -------
        list
          the list of marginal targets

        """
        return _pyagrum.WeightedSampling_targets(self)

    def evidenceImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(target|evs) (for all instanciation of target and evs)

        Parameters
        ----------
        target : set
          a set of targets ids or names.
        evs : set
          a set of nodes ids or names.

        Warnings
        --------
        if some evs are d-separated, they are not included in the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(targets|evs)

        """
        return _pyagrum.WeightedSampling_evidenceImpact(self, *args)

# Register WeightedSampling in _pyagrum:
_pyagrum.WeightedSampling_swigregister(WeightedSampling)
class MonteCarloSampling(object):
    r"""

    Class used for Monte Carlo sampling inference algorithm.

    MonteCarloSampling(bn) -> MonteCarloSampling
        Parameters:
            - **bn** (*pyagrum.BayesNet*) -- a Bayesian network

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, bn: "IBayesNet"):
        _pyagrum.MonteCarloSampling_swiginit(self, _pyagrum.new_MonteCarloSampling(bn))

        self._model=bn#BN



    __swig_destroy__ = _pyagrum.delete_MonteCarloSampling

    def setVerbosity(self, v: bool) -> None:
        r"""

        Parameters
        ----------
        v : bool
                verbosity

        """
        return _pyagrum.MonteCarloSampling_setVerbosity(self, v)

    def setEpsilon(self, eps: float) -> None:
        r"""

        Parameters
        ----------
        eps : float
            the epsilon we want to use

        Raises
        ------
        pyagrum.OutOfBounds
            If eps<0

        """
        return _pyagrum.MonteCarloSampling_setEpsilon(self, eps)

    def setMinEpsilonRate(self, rate: float) -> None:
        r"""

        Parameters
        ----------
        rate : float
            the minimal epsilon rate

        """
        return _pyagrum.MonteCarloSampling_setMinEpsilonRate(self, rate)

    def setMaxIter(self, max: int) -> None:
        r"""

        Parameters
        ----------
        max : int
            the maximum number of iteration

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1

        """
        return _pyagrum.MonteCarloSampling_setMaxIter(self, max)

    def setMaxTime(self, timeout: float) -> None:
        r"""

        Parameters
        ----------
        tiemout : float
            stopping criterion on timeout (in seconds)

        Raises
        ------
        pyagrum.OutOfBounds
            If timeout<=0.0

        """
        return _pyagrum.MonteCarloSampling_setMaxTime(self, timeout)

    def setPeriodSize(self, p: int) -> None:
        r"""

        Parameters
        ----------
        p : int
            number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.MonteCarloSampling_setPeriodSize(self, p)

    def verbosity(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the verbosity is enabled

        """
        return _pyagrum.MonteCarloSampling_verbosity(self)

    def epsilon(self) -> float:
        r"""

        Returns
        -------
        float
            the value of epsilon

        """
        return _pyagrum.MonteCarloSampling_epsilon(self)

    def minEpsilonRate(self) -> float:
        r"""

        Returns
        -------
        float
            the value of the minimal epsilon rate

        """
        return _pyagrum.MonteCarloSampling_minEpsilonRate(self)

    def maxIter(self) -> int:
        r"""

        Returns
        -------
        int
            the criterion on number of iterations

        """
        return _pyagrum.MonteCarloSampling_maxIter(self)

    def maxTime(self) -> float:
        r"""

        Returns
        -------
        float
            the timeout(in seconds)

        """
        return _pyagrum.MonteCarloSampling_maxTime(self)

    def periodSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.MonteCarloSampling_periodSize(self)

    def nbrIterations(self) -> int:
        r"""

        Returns
        -------
        int
            the number of iterations

        """
        return _pyagrum.MonteCarloSampling_nbrIterations(self)

    def currentTime(self) -> float:
        r"""

        Returns
        -------
        float
            get the current running time in second (float)

        """
        return _pyagrum.MonteCarloSampling_currentTime(self)

    def messageApproximationScheme(self) -> str:
        r"""

        Returns
        -------
        str
            the approximation scheme message

        """
        return _pyagrum.MonteCarloSampling_messageApproximationScheme(self)

    def history(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple
            the scheme history

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the scheme did not performed or if verbosity is set to false

        """
        return _pyagrum.MonteCarloSampling_history(self)

    def _asIApproximationSchemeConfiguration(self) -> "pyagrum.YetUnWrapped":
        return _pyagrum.MonteCarloSampling__asIApproximationSchemeConfiguration(self)

    def makeInference(self) -> None:
        r"""

        Perform the heavy computations needed to compute the targets' posteriors

        In a Junction tree propagation scheme, for instance, the heavy computations are those of the messages sent in the JT.
        This is precisely what makeInference should compute. Later, the computations of the posteriors can be done 'lightly' by multiplying and projecting those messages.

        """
        return _pyagrum.MonteCarloSampling_makeInference(self)

    def posterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior probability of the node

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.MonteCarloSampling_posterior(self, *args)

    def addEvidence(self, *args) -> None:
        r"""

        Adds a new evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val :
          (int) a node value
        val :
          (str) the label of the node value
        vals : list
          a list of values

        Raises
        ------
          pyagrum.InvalidArgument
            If the node already has an evidence
          pyagrum.InvalidArgument
            If val is not a value for the node
          pyagrum.InvalidArgument
            If the size of vals is different from the domain side of the node
          pyagrum.FatalError
            If vals is a vector of 0s
          pyagrum.UndefinedElement
            If the node does not belong to the Bayesian network

        """
        return _pyagrum.MonteCarloSampling_addEvidence(self, *args)

    def chgEvidence(self, *args) -> None:
        r"""

        Change the value of an already existing evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val : int|str
          a node value or the label of the node value
        vals : list of float
          a list of values

        Raises
        ------
        pyagrum.InvalidArgument
          If the node does not already have an evidence
        pyagrum.InvalidArgument
          If val is not a value for the node
        pyagrum.InvalidArgument
          If the size of vals is different from the domain side of the node
        pyagrum.FatalError
          If vals is a vector of 0s
        pyagrum.UndefinedElement
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.MonteCarloSampling_chgEvidence(self, *args)

    def hasEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if some node(s) (or the one in parameters) have received evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.MonteCarloSampling_hasEvidence(self, *args)

    def eraseAllEvidence(self) -> None:
        r"""

        Removes all the evidence entered into the network.

        """
        return _pyagrum.MonteCarloSampling_eraseAllEvidence(self)

    def eraseEvidence(self, *args) -> None:
        r"""

        Remove the evidence, if any, corresponding to the node Id or name.

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.MonteCarloSampling_eraseEvidence(self, *args)

    def hasHardEvidence(self, nodeName: str) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a hard evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.MonteCarloSampling_hasHardEvidence(self, nodeName)

    def hasSoftEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a soft evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.MonteCarloSampling_hasSoftEvidence(self, *args)

    def nbrEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of evidence entered into the Bayesian network

        """
        return _pyagrum.MonteCarloSampling_nbrEvidence(self)

    def nbrHardEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of hard evidence entered into the Bayesian network

        """
        return _pyagrum.MonteCarloSampling_nbrHardEvidence(self)

    def nbrSoftEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of soft evidence entered into the Bayesian network

        """
        return _pyagrum.MonteCarloSampling_nbrSoftEvidence(self)

    def eraseAllTargets(self) -> None:
        r"""

        Clear all previously defined targets (marginal and joint targets).

        As a result, no posterior can be computed (since we can only compute the posteriors of the marginal or joint targets that have been added by the user).

        """
        return _pyagrum.MonteCarloSampling_eraseAllTargets(self)

    def addAllTargets(self) -> None:
        r"""

        Add all the nodes as targets.

        """
        return _pyagrum.MonteCarloSampling_addAllTargets(self)

    def addTarget(self, *args) -> None:
        r"""

        Add a marginal target to the list of targets.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : str
          a node name

        Raises
        ------
        pyagrum.UndefinedElement
          If target is not a NodeId in the Bayes net

        """
        return _pyagrum.MonteCarloSampling_addTarget(self, *args)

    def eraseTarget(self, *args) -> None:
        r"""

        Remove, if existing, the marginal target.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.MonteCarloSampling_eraseTarget(self, *args)

    def isTarget(self, *args) -> bool:
        r"""

        Parameters
        ----------
        variable : int
         a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if variable is a (marginal) target

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.MonteCarloSampling_isTarget(self, *args)

    def nbrTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of marginal targets

        """
        return _pyagrum.MonteCarloSampling_nbrTargets(self)

    def H(self, *args) -> float:
        r"""

        Parameters
        ----------
        X : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        float
          the computed Shanon's entropy of a node given the observation

        """
        return _pyagrum.MonteCarloSampling_H(self, *args)

    def BN(self) -> "pyagrum.IBayesNet":
        r"""

        Returns
        -------
        pyagrum.IBayesNet
          A constant reference over the IBayesNet referenced by this class.

        Raises
        ------
          pyagrum.UndefinedElement
            If no Bayes net has been assigned to the inference.

        """
        return _pyagrum.MonteCarloSampling_BN(self)

    def currentPosterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the current posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the current posterior probability of the node

        Raises
        ------
        UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.MonteCarloSampling_currentPosterior(self, *args)

    def setEvidence(self, evidces):
        """
        Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
        pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          self.eraseAllEvidence()
          for k,v in evidces.items():
            self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          self.eraseAllEvidence()
          for p in evidces:
            self.addEvidence(p)
          return
        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def updateEvidence(self, evidces):
        """
        Apply chgEvidence(key,value) for every pairs in evidces (or addEvidence).

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
          pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          for k,v in evidces.items():
              if self.hasEvidence(k):
                  self.chgEvidence(k,v)
              else:
                  self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          for p in evidces:
              k=p.variable(0)
              if self.hasEvidence(k):
                  self.chgEvidence(p)
              else:
                  self.addEvidence(p)
          return

        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def setTargets(self, targets):
        """
        Remove all the targets and add the ones in parameter.

        Parameters
        ----------
        targets : set
          a set of targets

        Raises
        ------
          pyagrum.UndefinedElement
            If one target is not in the Bayes net
        """
        if not isinstance(targets, set):
            raise TypeError("setTargets parameter must be a set, not %s"%(type(targets)))

        self.eraseAllTargets()
        for k in targets:
            self.addTarget(k)



    def hardEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with hard evidence

        """
        return _pyagrum.MonteCarloSampling_hardEvidenceNodes(self)

    def softEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with soft evidence

        """
        return _pyagrum.MonteCarloSampling_softEvidenceNodes(self)

    def targets(self) -> set[int]:
        r"""

        Returns
        -------
        list
          the list of marginal targets

        """
        return _pyagrum.MonteCarloSampling_targets(self)

    def evidenceImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(target|evs) (for all instanciation of target and evs)

        Parameters
        ----------
        target : set
          a set of targets ids or names.
        evs : set
          a set of nodes ids or names.

        Warnings
        --------
        if some evs are d-separated, they are not included in the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(targets|evs)

        """
        return _pyagrum.MonteCarloSampling_evidenceImpact(self, *args)

# Register MonteCarloSampling in _pyagrum:
_pyagrum.MonteCarloSampling_swigregister(MonteCarloSampling)
class LoopyImportanceSampling(ImportanceSampling):
    r"""

    Class used for inferences using a loopy version of importance sampling.

    LoopyImportanceSampling(bn) -> LoopyImportanceSampling
        Parameters:
            - **bn** (*pyagrum.BayesNet*) -- a Bayesian network

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, bn: "IBayesNet"):
        _pyagrum.LoopyImportanceSampling_swiginit(self, _pyagrum.new_LoopyImportanceSampling(bn))

        self._model=bn#BN



    __swig_destroy__ = _pyagrum.delete_LoopyImportanceSampling

    def makeInference_(self) -> None:
        r"""

        Internal implementation of the loopy sampling inference.

        This protected virtual method performs the actual sampling-based inference loop.

        """
        return _pyagrum.LoopyImportanceSampling_makeInference_(self)

    def setVirtualLBPSize(self, vlbpsize: float) -> None:
        r"""

        Parameters
        ----------
        vlbpsize : float
          the size of the virtual LBP

        """
        return _pyagrum.LoopyImportanceSampling_setVirtualLBPSize(self, vlbpsize)

    def setVerbosity(self, v: bool) -> None:
        r"""

        Parameters
        ----------
        v : bool
                verbosity

        """
        return _pyagrum.LoopyImportanceSampling_setVerbosity(self, v)

    def setEpsilon(self, eps: float) -> None:
        r"""

        Parameters
        ----------
        eps : float
            the epsilon we want to use

        Raises
        ------
        pyagrum.OutOfBounds
            If eps<0

        """
        return _pyagrum.LoopyImportanceSampling_setEpsilon(self, eps)

    def setMinEpsilonRate(self, rate: float) -> None:
        r"""

        Parameters
        ----------
        rate : float
            the minimal epsilon rate

        """
        return _pyagrum.LoopyImportanceSampling_setMinEpsilonRate(self, rate)

    def setMaxIter(self, max: int) -> None:
        r"""

        Parameters
        ----------
        max : int
            the maximum number of iteration

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1

        """
        return _pyagrum.LoopyImportanceSampling_setMaxIter(self, max)

    def setMaxTime(self, timeout: float) -> None:
        r"""

        Parameters
        ----------
        tiemout : float
            stopping criterion on timeout (in seconds)

        Raises
        ------
        pyagrum.OutOfBounds
            If timeout<=0.0

        """
        return _pyagrum.LoopyImportanceSampling_setMaxTime(self, timeout)

    def setPeriodSize(self, p: int) -> None:
        r"""

        Parameters
        ----------
        p : int
            number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.LoopyImportanceSampling_setPeriodSize(self, p)

    def verbosity(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the verbosity is enabled

        """
        return _pyagrum.LoopyImportanceSampling_verbosity(self)

    def epsilon(self) -> float:
        r"""

        Returns
        -------
        float
            the value of epsilon

        """
        return _pyagrum.LoopyImportanceSampling_epsilon(self)

    def minEpsilonRate(self) -> float:
        r"""

        Returns
        -------
        float
            the value of the minimal epsilon rate

        """
        return _pyagrum.LoopyImportanceSampling_minEpsilonRate(self)

    def maxIter(self) -> int:
        r"""

        Returns
        -------
        int
            the criterion on number of iterations

        """
        return _pyagrum.LoopyImportanceSampling_maxIter(self)

    def maxTime(self) -> float:
        r"""

        Returns
        -------
        float
            the timeout(in seconds)

        """
        return _pyagrum.LoopyImportanceSampling_maxTime(self)

    def periodSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.LoopyImportanceSampling_periodSize(self)

    def nbrIterations(self) -> int:
        r"""

        Returns
        -------
        int
            the number of iterations

        """
        return _pyagrum.LoopyImportanceSampling_nbrIterations(self)

    def currentTime(self) -> float:
        r"""

        Returns
        -------
        float
            get the current running time in second (float)

        """
        return _pyagrum.LoopyImportanceSampling_currentTime(self)

    def messageApproximationScheme(self) -> str:
        r"""

        Returns
        -------
        str
            the approximation scheme message

        """
        return _pyagrum.LoopyImportanceSampling_messageApproximationScheme(self)

    def history(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple
            the scheme history

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the scheme did not performed or if verbosity is set to false

        """
        return _pyagrum.LoopyImportanceSampling_history(self)

    def _asIApproximationSchemeConfiguration(self) -> "pyagrum.YetUnWrapped":
        return _pyagrum.LoopyImportanceSampling__asIApproximationSchemeConfiguration(self)

    def makeInference(self) -> None:
        r"""

        Perform the heavy computations needed to compute the targets' posteriors

        In a Junction tree propagation scheme, for instance, the heavy computations are those of the messages sent in the JT.
        This is precisely what makeInference should compute. Later, the computations of the posteriors can be done 'lightly' by multiplying and projecting those messages.

        """
        return _pyagrum.LoopyImportanceSampling_makeInference(self)

    def posterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior probability of the node

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.LoopyImportanceSampling_posterior(self, *args)

    def addEvidence(self, *args) -> None:
        r"""

        Adds a new evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val :
          (int) a node value
        val :
          (str) the label of the node value
        vals : list
          a list of values

        Raises
        ------
          pyagrum.InvalidArgument
            If the node already has an evidence
          pyagrum.InvalidArgument
            If val is not a value for the node
          pyagrum.InvalidArgument
            If the size of vals is different from the domain side of the node
          pyagrum.FatalError
            If vals is a vector of 0s
          pyagrum.UndefinedElement
            If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyImportanceSampling_addEvidence(self, *args)

    def chgEvidence(self, *args) -> None:
        r"""

        Change the value of an already existing evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val : int|str
          a node value or the label of the node value
        vals : list of float
          a list of values

        Raises
        ------
        pyagrum.InvalidArgument
          If the node does not already have an evidence
        pyagrum.InvalidArgument
          If val is not a value for the node
        pyagrum.InvalidArgument
          If the size of vals is different from the domain side of the node
        pyagrum.FatalError
          If vals is a vector of 0s
        pyagrum.UndefinedElement
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyImportanceSampling_chgEvidence(self, *args)

    def hasEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if some node(s) (or the one in parameters) have received evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyImportanceSampling_hasEvidence(self, *args)

    def eraseAllEvidence(self) -> None:
        r"""

        Removes all the evidence entered into the network.

        """
        return _pyagrum.LoopyImportanceSampling_eraseAllEvidence(self)

    def eraseEvidence(self, *args) -> None:
        r"""

        Remove the evidence, if any, corresponding to the node Id or name.

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyImportanceSampling_eraseEvidence(self, *args)

    def hasHardEvidence(self, nodeName: str) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a hard evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyImportanceSampling_hasHardEvidence(self, nodeName)

    def hasSoftEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a soft evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyImportanceSampling_hasSoftEvidence(self, *args)

    def nbrEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of evidence entered into the Bayesian network

        """
        return _pyagrum.LoopyImportanceSampling_nbrEvidence(self)

    def nbrHardEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of hard evidence entered into the Bayesian network

        """
        return _pyagrum.LoopyImportanceSampling_nbrHardEvidence(self)

    def nbrSoftEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of soft evidence entered into the Bayesian network

        """
        return _pyagrum.LoopyImportanceSampling_nbrSoftEvidence(self)

    def eraseAllTargets(self) -> None:
        r"""

        Clear all previously defined targets (marginal and joint targets).

        As a result, no posterior can be computed (since we can only compute the posteriors of the marginal or joint targets that have been added by the user).

        """
        return _pyagrum.LoopyImportanceSampling_eraseAllTargets(self)

    def addAllTargets(self) -> None:
        r"""

        Add all the nodes as targets.

        """
        return _pyagrum.LoopyImportanceSampling_addAllTargets(self)

    def addTarget(self, *args) -> None:
        r"""

        Add a marginal target to the list of targets.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : str
          a node name

        Raises
        ------
        pyagrum.UndefinedElement
          If target is not a NodeId in the Bayes net

        """
        return _pyagrum.LoopyImportanceSampling_addTarget(self, *args)

    def eraseTarget(self, *args) -> None:
        r"""

        Remove, if existing, the marginal target.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.LoopyImportanceSampling_eraseTarget(self, *args)

    def isTarget(self, *args) -> bool:
        r"""

        Parameters
        ----------
        variable : int
         a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if variable is a (marginal) target

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.LoopyImportanceSampling_isTarget(self, *args)

    def nbrTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of marginal targets

        """
        return _pyagrum.LoopyImportanceSampling_nbrTargets(self)

    def H(self, *args) -> float:
        r"""

        Parameters
        ----------
        X : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        float
          the computed Shanon's entropy of a node given the observation

        """
        return _pyagrum.LoopyImportanceSampling_H(self, *args)

    def BN(self) -> "pyagrum.IBayesNet":
        r"""

        Returns
        -------
        pyagrum.IBayesNet
          A constant reference over the IBayesNet referenced by this class.

        Raises
        ------
          pyagrum.UndefinedElement
            If no Bayes net has been assigned to the inference.

        """
        return _pyagrum.LoopyImportanceSampling_BN(self)

    def currentPosterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the current posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the current posterior probability of the node

        Raises
        ------
        UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.LoopyImportanceSampling_currentPosterior(self, *args)

    def setEvidence(self, evidces):
        """
        Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
        pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          self.eraseAllEvidence()
          for k,v in evidces.items():
            self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          self.eraseAllEvidence()
          for p in evidces:
            self.addEvidence(p)
          return
        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def updateEvidence(self, evidces):
        """
        Apply chgEvidence(key,value) for every pairs in evidces (or addEvidence).

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
          pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          for k,v in evidces.items():
              if self.hasEvidence(k):
                  self.chgEvidence(k,v)
              else:
                  self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          for p in evidces:
              k=p.variable(0)
              if self.hasEvidence(k):
                  self.chgEvidence(p)
              else:
                  self.addEvidence(p)
          return

        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def setTargets(self, targets):
        """
        Remove all the targets and add the ones in parameter.

        Parameters
        ----------
        targets : set
          a set of targets

        Raises
        ------
          pyagrum.UndefinedElement
            If one target is not in the Bayes net
        """
        if not isinstance(targets, set):
            raise TypeError("setTargets parameter must be a set, not %s"%(type(targets)))

        self.eraseAllTargets()
        for k in targets:
            self.addTarget(k)



    def hardEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with hard evidence

        """
        return _pyagrum.LoopyImportanceSampling_hardEvidenceNodes(self)

    def softEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with soft evidence

        """
        return _pyagrum.LoopyImportanceSampling_softEvidenceNodes(self)

    def targets(self) -> set[int]:
        r"""

        Returns
        -------
        list
          the list of marginal targets

        """
        return _pyagrum.LoopyImportanceSampling_targets(self)

    def evidenceImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(target|evs) (for all instanciation of target and evs)

        Parameters
        ----------
        target : set
          a set of targets ids or names.
        evs : set
          a set of nodes ids or names.

        Warnings
        --------
        if some evs are d-separated, they are not included in the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(targets|evs)

        """
        return _pyagrum.LoopyImportanceSampling_evidenceImpact(self, *args)

# Register LoopyImportanceSampling in _pyagrum:
_pyagrum.LoopyImportanceSampling_swigregister(LoopyImportanceSampling)
class LoopyWeightedSampling(WeightedSampling):
    r"""

    Class used for inferences using a loopy version of importance sampling.

    LoopyImportanceSampling(bn) -> LoopyImportanceSampling
        Parameters:
            - **bn** (*pyagrum.BayesNet*) -- a Bayesian network

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, bn: "IBayesNet"):
        _pyagrum.LoopyWeightedSampling_swiginit(self, _pyagrum.new_LoopyWeightedSampling(bn))

        self._model=bn#BN



    __swig_destroy__ = _pyagrum.delete_LoopyWeightedSampling

    def makeInference_(self) -> None:
        r"""

        Internal implementation of the loopy sampling inference.

        This protected virtual method performs the actual sampling-based inference loop.

        """
        return _pyagrum.LoopyWeightedSampling_makeInference_(self)

    def setVirtualLBPSize(self, vlbpsize: float) -> None:
        r"""

        Parameters
        ----------
        vlbpsize : float
          the size of the virtual LBP

        """
        return _pyagrum.LoopyWeightedSampling_setVirtualLBPSize(self, vlbpsize)

    def setVerbosity(self, v: bool) -> None:
        r"""

        Parameters
        ----------
        v : bool
                verbosity

        """
        return _pyagrum.LoopyWeightedSampling_setVerbosity(self, v)

    def setEpsilon(self, eps: float) -> None:
        r"""

        Parameters
        ----------
        eps : float
            the epsilon we want to use

        Raises
        ------
        pyagrum.OutOfBounds
            If eps<0

        """
        return _pyagrum.LoopyWeightedSampling_setEpsilon(self, eps)

    def setMinEpsilonRate(self, rate: float) -> None:
        r"""

        Parameters
        ----------
        rate : float
            the minimal epsilon rate

        """
        return _pyagrum.LoopyWeightedSampling_setMinEpsilonRate(self, rate)

    def setMaxIter(self, max: int) -> None:
        r"""

        Parameters
        ----------
        max : int
            the maximum number of iteration

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1

        """
        return _pyagrum.LoopyWeightedSampling_setMaxIter(self, max)

    def setMaxTime(self, timeout: float) -> None:
        r"""

        Parameters
        ----------
        tiemout : float
            stopping criterion on timeout (in seconds)

        Raises
        ------
        pyagrum.OutOfBounds
            If timeout<=0.0

        """
        return _pyagrum.LoopyWeightedSampling_setMaxTime(self, timeout)

    def setPeriodSize(self, p: int) -> None:
        r"""

        Parameters
        ----------
        p : int
            number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.LoopyWeightedSampling_setPeriodSize(self, p)

    def verbosity(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the verbosity is enabled

        """
        return _pyagrum.LoopyWeightedSampling_verbosity(self)

    def epsilon(self) -> float:
        r"""

        Returns
        -------
        float
            the value of epsilon

        """
        return _pyagrum.LoopyWeightedSampling_epsilon(self)

    def minEpsilonRate(self) -> float:
        r"""

        Returns
        -------
        float
            the value of the minimal epsilon rate

        """
        return _pyagrum.LoopyWeightedSampling_minEpsilonRate(self)

    def maxIter(self) -> int:
        r"""

        Returns
        -------
        int
            the criterion on number of iterations

        """
        return _pyagrum.LoopyWeightedSampling_maxIter(self)

    def maxTime(self) -> float:
        r"""

        Returns
        -------
        float
            the timeout(in seconds)

        """
        return _pyagrum.LoopyWeightedSampling_maxTime(self)

    def periodSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.LoopyWeightedSampling_periodSize(self)

    def nbrIterations(self) -> int:
        r"""

        Returns
        -------
        int
            the number of iterations

        """
        return _pyagrum.LoopyWeightedSampling_nbrIterations(self)

    def currentTime(self) -> float:
        r"""

        Returns
        -------
        float
            get the current running time in second (float)

        """
        return _pyagrum.LoopyWeightedSampling_currentTime(self)

    def messageApproximationScheme(self) -> str:
        r"""

        Returns
        -------
        str
            the approximation scheme message

        """
        return _pyagrum.LoopyWeightedSampling_messageApproximationScheme(self)

    def history(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple
            the scheme history

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the scheme did not performed or if verbosity is set to false

        """
        return _pyagrum.LoopyWeightedSampling_history(self)

    def _asIApproximationSchemeConfiguration(self) -> "pyagrum.YetUnWrapped":
        return _pyagrum.LoopyWeightedSampling__asIApproximationSchemeConfiguration(self)

    def makeInference(self) -> None:
        r"""

        Perform the heavy computations needed to compute the targets' posteriors

        In a Junction tree propagation scheme, for instance, the heavy computations are those of the messages sent in the JT.
        This is precisely what makeInference should compute. Later, the computations of the posteriors can be done 'lightly' by multiplying and projecting those messages.

        """
        return _pyagrum.LoopyWeightedSampling_makeInference(self)

    def posterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior probability of the node

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.LoopyWeightedSampling_posterior(self, *args)

    def addEvidence(self, *args) -> None:
        r"""

        Adds a new evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val :
          (int) a node value
        val :
          (str) the label of the node value
        vals : list
          a list of values

        Raises
        ------
          pyagrum.InvalidArgument
            If the node already has an evidence
          pyagrum.InvalidArgument
            If val is not a value for the node
          pyagrum.InvalidArgument
            If the size of vals is different from the domain side of the node
          pyagrum.FatalError
            If vals is a vector of 0s
          pyagrum.UndefinedElement
            If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyWeightedSampling_addEvidence(self, *args)

    def chgEvidence(self, *args) -> None:
        r"""

        Change the value of an already existing evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val : int|str
          a node value or the label of the node value
        vals : list of float
          a list of values

        Raises
        ------
        pyagrum.InvalidArgument
          If the node does not already have an evidence
        pyagrum.InvalidArgument
          If val is not a value for the node
        pyagrum.InvalidArgument
          If the size of vals is different from the domain side of the node
        pyagrum.FatalError
          If vals is a vector of 0s
        pyagrum.UndefinedElement
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyWeightedSampling_chgEvidence(self, *args)

    def hasEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if some node(s) (or the one in parameters) have received evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyWeightedSampling_hasEvidence(self, *args)

    def eraseAllEvidence(self) -> None:
        r"""

        Removes all the evidence entered into the network.

        """
        return _pyagrum.LoopyWeightedSampling_eraseAllEvidence(self)

    def eraseEvidence(self, *args) -> None:
        r"""

        Remove the evidence, if any, corresponding to the node Id or name.

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyWeightedSampling_eraseEvidence(self, *args)

    def hasHardEvidence(self, nodeName: str) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a hard evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyWeightedSampling_hasHardEvidence(self, nodeName)

    def hasSoftEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a soft evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyWeightedSampling_hasSoftEvidence(self, *args)

    def nbrEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of evidence entered into the Bayesian network

        """
        return _pyagrum.LoopyWeightedSampling_nbrEvidence(self)

    def nbrHardEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of hard evidence entered into the Bayesian network

        """
        return _pyagrum.LoopyWeightedSampling_nbrHardEvidence(self)

    def nbrSoftEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of soft evidence entered into the Bayesian network

        """
        return _pyagrum.LoopyWeightedSampling_nbrSoftEvidence(self)

    def eraseAllTargets(self) -> None:
        r"""

        Clear all previously defined targets (marginal and joint targets).

        As a result, no posterior can be computed (since we can only compute the posteriors of the marginal or joint targets that have been added by the user).

        """
        return _pyagrum.LoopyWeightedSampling_eraseAllTargets(self)

    def addAllTargets(self) -> None:
        r"""

        Add all the nodes as targets.

        """
        return _pyagrum.LoopyWeightedSampling_addAllTargets(self)

    def addTarget(self, *args) -> None:
        r"""

        Add a marginal target to the list of targets.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : str
          a node name

        Raises
        ------
        pyagrum.UndefinedElement
          If target is not a NodeId in the Bayes net

        """
        return _pyagrum.LoopyWeightedSampling_addTarget(self, *args)

    def eraseTarget(self, *args) -> None:
        r"""

        Remove, if existing, the marginal target.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.LoopyWeightedSampling_eraseTarget(self, *args)

    def isTarget(self, *args) -> bool:
        r"""

        Parameters
        ----------
        variable : int
         a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if variable is a (marginal) target

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.LoopyWeightedSampling_isTarget(self, *args)

    def nbrTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of marginal targets

        """
        return _pyagrum.LoopyWeightedSampling_nbrTargets(self)

    def H(self, *args) -> float:
        r"""

        Parameters
        ----------
        X : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        float
          the computed Shanon's entropy of a node given the observation

        """
        return _pyagrum.LoopyWeightedSampling_H(self, *args)

    def BN(self) -> "pyagrum.IBayesNet":
        r"""

        Returns
        -------
        pyagrum.IBayesNet
          A constant reference over the IBayesNet referenced by this class.

        Raises
        ------
          pyagrum.UndefinedElement
            If no Bayes net has been assigned to the inference.

        """
        return _pyagrum.LoopyWeightedSampling_BN(self)

    def currentPosterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the current posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the current posterior probability of the node

        Raises
        ------
        UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.LoopyWeightedSampling_currentPosterior(self, *args)

    def setEvidence(self, evidces):
        """
        Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
        pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          self.eraseAllEvidence()
          for k,v in evidces.items():
            self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          self.eraseAllEvidence()
          for p in evidces:
            self.addEvidence(p)
          return
        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def updateEvidence(self, evidces):
        """
        Apply chgEvidence(key,value) for every pairs in evidces (or addEvidence).

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
          pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          for k,v in evidces.items():
              if self.hasEvidence(k):
                  self.chgEvidence(k,v)
              else:
                  self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          for p in evidces:
              k=p.variable(0)
              if self.hasEvidence(k):
                  self.chgEvidence(p)
              else:
                  self.addEvidence(p)
          return

        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def setTargets(self, targets):
        """
        Remove all the targets and add the ones in parameter.

        Parameters
        ----------
        targets : set
          a set of targets

        Raises
        ------
          pyagrum.UndefinedElement
            If one target is not in the Bayes net
        """
        if not isinstance(targets, set):
            raise TypeError("setTargets parameter must be a set, not %s"%(type(targets)))

        self.eraseAllTargets()
        for k in targets:
            self.addTarget(k)



    def hardEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with hard evidence

        """
        return _pyagrum.LoopyWeightedSampling_hardEvidenceNodes(self)

    def softEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with soft evidence

        """
        return _pyagrum.LoopyWeightedSampling_softEvidenceNodes(self)

    def targets(self) -> set[int]:
        r"""

        Returns
        -------
        list
          the list of marginal targets

        """
        return _pyagrum.LoopyWeightedSampling_targets(self)

    def evidenceImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(target|evs) (for all instanciation of target and evs)

        Parameters
        ----------
        target : set
          a set of targets ids or names.
        evs : set
          a set of nodes ids or names.

        Warnings
        --------
        if some evs are d-separated, they are not included in the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(targets|evs)

        """
        return _pyagrum.LoopyWeightedSampling_evidenceImpact(self, *args)

# Register LoopyWeightedSampling in _pyagrum:
_pyagrum.LoopyWeightedSampling_swigregister(LoopyWeightedSampling)
class LoopyGibbsSampling(GibbsSampling):
    r"""

    Class used for inferences using a loopy version of importance sampling.

    LoopyImportanceSampling(bn) -> LoopyImportanceSampling
        Parameters:
            - **bn** (*pyagrum.BayesNet*) -- a Bayesian network

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, bn: "IBayesNet"):
        _pyagrum.LoopyGibbsSampling_swiginit(self, _pyagrum.new_LoopyGibbsSampling(bn))

        self._model=bn#BN



    __swig_destroy__ = _pyagrum.delete_LoopyGibbsSampling

    def makeInference_(self) -> None:
        r"""

        Internal implementation of the loopy sampling inference.

        This protected virtual method performs the actual sampling-based inference loop.

        """
        return _pyagrum.LoopyGibbsSampling_makeInference_(self)

    def setVirtualLBPSize(self, vlbpsize: float) -> None:
        r"""

        Parameters
        ----------
        vlbpsize : float
          the size of the virtual LBP

        """
        return _pyagrum.LoopyGibbsSampling_setVirtualLBPSize(self, vlbpsize)

    def setVerbosity(self, v: bool) -> None:
        r"""

        Parameters
        ----------
        v : bool
                verbosity

        """
        return _pyagrum.LoopyGibbsSampling_setVerbosity(self, v)

    def setEpsilon(self, eps: float) -> None:
        r"""

        Parameters
        ----------
        eps : float
            the epsilon we want to use

        Raises
        ------
        pyagrum.OutOfBounds
            If eps<0

        """
        return _pyagrum.LoopyGibbsSampling_setEpsilon(self, eps)

    def setMinEpsilonRate(self, rate: float) -> None:
        r"""

        Parameters
        ----------
        rate : float
            the minimal epsilon rate

        """
        return _pyagrum.LoopyGibbsSampling_setMinEpsilonRate(self, rate)

    def setMaxIter(self, max: int) -> None:
        r"""

        Parameters
        ----------
        max : int
            the maximum number of iteration

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1

        """
        return _pyagrum.LoopyGibbsSampling_setMaxIter(self, max)

    def setMaxTime(self, timeout: float) -> None:
        r"""

        Parameters
        ----------
        tiemout : float
            stopping criterion on timeout (in seconds)

        Raises
        ------
        pyagrum.OutOfBounds
            If timeout<=0.0

        """
        return _pyagrum.LoopyGibbsSampling_setMaxTime(self, timeout)

    def setPeriodSize(self, p: int) -> None:
        r"""

        Parameters
        ----------
        p : int
            number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.LoopyGibbsSampling_setPeriodSize(self, p)

    def verbosity(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the verbosity is enabled

        """
        return _pyagrum.LoopyGibbsSampling_verbosity(self)

    def epsilon(self) -> float:
        r"""

        Returns
        -------
        float
            the value of epsilon

        """
        return _pyagrum.LoopyGibbsSampling_epsilon(self)

    def minEpsilonRate(self) -> float:
        r"""

        Returns
        -------
        float
            the value of the minimal epsilon rate

        """
        return _pyagrum.LoopyGibbsSampling_minEpsilonRate(self)

    def maxIter(self) -> int:
        r"""

        Returns
        -------
        int
            the criterion on number of iterations

        """
        return _pyagrum.LoopyGibbsSampling_maxIter(self)

    def maxTime(self) -> float:
        r"""

        Returns
        -------
        float
            the timeout(in seconds)

        """
        return _pyagrum.LoopyGibbsSampling_maxTime(self)

    def periodSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.LoopyGibbsSampling_periodSize(self)

    def nbrIterations(self) -> int:
        r"""

        Returns
        -------
        int
            the number of iterations

        """
        return _pyagrum.LoopyGibbsSampling_nbrIterations(self)

    def currentTime(self) -> float:
        r"""

        Returns
        -------
        float
            get the current running time in second (float)

        """
        return _pyagrum.LoopyGibbsSampling_currentTime(self)

    def messageApproximationScheme(self) -> str:
        r"""

        Returns
        -------
        str
            the approximation scheme message

        """
        return _pyagrum.LoopyGibbsSampling_messageApproximationScheme(self)

    def history(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple
            the scheme history

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the scheme did not performed or if verbosity is set to false

        """
        return _pyagrum.LoopyGibbsSampling_history(self)

    def _asIApproximationSchemeConfiguration(self) -> "pyagrum.YetUnWrapped":
        return _pyagrum.LoopyGibbsSampling__asIApproximationSchemeConfiguration(self)

    def makeInference(self) -> None:
        r"""

        Perform the heavy computations needed to compute the targets' posteriors

        In a Junction tree propagation scheme, for instance, the heavy computations are those of the messages sent in the JT.
        This is precisely what makeInference should compute. Later, the computations of the posteriors can be done 'lightly' by multiplying and projecting those messages.

        """
        return _pyagrum.LoopyGibbsSampling_makeInference(self)

    def posterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior probability of the node

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.LoopyGibbsSampling_posterior(self, *args)

    def addEvidence(self, *args) -> None:
        r"""

        Adds a new evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val :
          (int) a node value
        val :
          (str) the label of the node value
        vals : list
          a list of values

        Raises
        ------
          pyagrum.InvalidArgument
            If the node already has an evidence
          pyagrum.InvalidArgument
            If val is not a value for the node
          pyagrum.InvalidArgument
            If the size of vals is different from the domain side of the node
          pyagrum.FatalError
            If vals is a vector of 0s
          pyagrum.UndefinedElement
            If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyGibbsSampling_addEvidence(self, *args)

    def chgEvidence(self, *args) -> None:
        r"""

        Change the value of an already existing evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val : int|str
          a node value or the label of the node value
        vals : list of float
          a list of values

        Raises
        ------
        pyagrum.InvalidArgument
          If the node does not already have an evidence
        pyagrum.InvalidArgument
          If val is not a value for the node
        pyagrum.InvalidArgument
          If the size of vals is different from the domain side of the node
        pyagrum.FatalError
          If vals is a vector of 0s
        pyagrum.UndefinedElement
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyGibbsSampling_chgEvidence(self, *args)

    def hasEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if some node(s) (or the one in parameters) have received evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyGibbsSampling_hasEvidence(self, *args)

    def eraseAllEvidence(self) -> None:
        r"""

        Removes all the evidence entered into the network.

        """
        return _pyagrum.LoopyGibbsSampling_eraseAllEvidence(self)

    def eraseEvidence(self, *args) -> None:
        r"""

        Remove the evidence, if any, corresponding to the node Id or name.

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyGibbsSampling_eraseEvidence(self, *args)

    def hasHardEvidence(self, nodeName: str) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a hard evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyGibbsSampling_hasHardEvidence(self, nodeName)

    def hasSoftEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a soft evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyGibbsSampling_hasSoftEvidence(self, *args)

    def nbrEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of evidence entered into the Bayesian network

        """
        return _pyagrum.LoopyGibbsSampling_nbrEvidence(self)

    def nbrHardEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of hard evidence entered into the Bayesian network

        """
        return _pyagrum.LoopyGibbsSampling_nbrHardEvidence(self)

    def nbrSoftEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of soft evidence entered into the Bayesian network

        """
        return _pyagrum.LoopyGibbsSampling_nbrSoftEvidence(self)

    def eraseAllTargets(self) -> None:
        r"""

        Clear all previously defined targets (marginal and joint targets).

        As a result, no posterior can be computed (since we can only compute the posteriors of the marginal or joint targets that have been added by the user).

        """
        return _pyagrum.LoopyGibbsSampling_eraseAllTargets(self)

    def addAllTargets(self) -> None:
        r"""

        Add all the nodes as targets.

        """
        return _pyagrum.LoopyGibbsSampling_addAllTargets(self)

    def addTarget(self, *args) -> None:
        r"""

        Add a marginal target to the list of targets.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : str
          a node name

        Raises
        ------
        pyagrum.UndefinedElement
          If target is not a NodeId in the Bayes net

        """
        return _pyagrum.LoopyGibbsSampling_addTarget(self, *args)

    def eraseTarget(self, *args) -> None:
        r"""

        Remove, if existing, the marginal target.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.LoopyGibbsSampling_eraseTarget(self, *args)

    def isTarget(self, *args) -> bool:
        r"""

        Parameters
        ----------
        variable : int
         a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if variable is a (marginal) target

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.LoopyGibbsSampling_isTarget(self, *args)

    def nbrTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of marginal targets

        """
        return _pyagrum.LoopyGibbsSampling_nbrTargets(self)

    def H(self, *args) -> float:
        r"""

        Parameters
        ----------
        X : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        float
          the computed Shanon's entropy of a node given the observation

        """
        return _pyagrum.LoopyGibbsSampling_H(self, *args)

    def BN(self) -> "pyagrum.IBayesNet":
        r"""

        Returns
        -------
        pyagrum.IBayesNet
          A constant reference over the IBayesNet referenced by this class.

        Raises
        ------
          pyagrum.UndefinedElement
            If no Bayes net has been assigned to the inference.

        """
        return _pyagrum.LoopyGibbsSampling_BN(self)

    def currentPosterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the current posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the current posterior probability of the node

        Raises
        ------
        UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.LoopyGibbsSampling_currentPosterior(self, *args)

    def nbrDrawnVar(self) -> int:
        r"""

        Returns
        -------
        int
          the number of variable drawn at each iteration

        """
        return _pyagrum.LoopyGibbsSampling_nbrDrawnVar(self)

    def setNbrDrawnVar(self, _nbr: int) -> None:
        r"""

        Parameters
        ----------
        _nbr : int
          the number of variables to be drawn at each iteration

        """
        return _pyagrum.LoopyGibbsSampling_setNbrDrawnVar(self, _nbr)

    def isDrawnAtRandom(self) -> bool:
        r"""

        Returns
        -------
        bool
          True if variables are drawn at random

        """
        return _pyagrum.LoopyGibbsSampling_isDrawnAtRandom(self)

    def setDrawnAtRandom(self, _atRandom: bool) -> None:
        r"""

        Parameters
        ----------
        _atRandom : bool
          indicates if variables should be drawn at random

        """
        return _pyagrum.LoopyGibbsSampling_setDrawnAtRandom(self, _atRandom)

    def burnIn(self) -> int:
        r"""

        Returns
        -------
        int
          size of burn in on number of iteration

        """
        return _pyagrum.LoopyGibbsSampling_burnIn(self)

    def setBurnIn(self, b: int) -> None:
        r"""

        Parameters
        ----------
        b : int
          size of burn in on number of iteration

        """
        return _pyagrum.LoopyGibbsSampling_setBurnIn(self, b)

    def setEvidence(self, evidces):
        """
        Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
        pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          self.eraseAllEvidence()
          for k,v in evidces.items():
            self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          self.eraseAllEvidence()
          for p in evidces:
            self.addEvidence(p)
          return
        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def updateEvidence(self, evidces):
        """
        Apply chgEvidence(key,value) for every pairs in evidces (or addEvidence).

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
          pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          for k,v in evidces.items():
              if self.hasEvidence(k):
                  self.chgEvidence(k,v)
              else:
                  self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          for p in evidces:
              k=p.variable(0)
              if self.hasEvidence(k):
                  self.chgEvidence(p)
              else:
                  self.addEvidence(p)
          return

        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def setTargets(self, targets):
        """
        Remove all the targets and add the ones in parameter.

        Parameters
        ----------
        targets : set
          a set of targets

        Raises
        ------
          pyagrum.UndefinedElement
            If one target is not in the Bayes net
        """
        if not isinstance(targets, set):
            raise TypeError("setTargets parameter must be a set, not %s"%(type(targets)))

        self.eraseAllTargets()
        for k in targets:
            self.addTarget(k)



    def hardEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with hard evidence

        """
        return _pyagrum.LoopyGibbsSampling_hardEvidenceNodes(self)

    def softEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with soft evidence

        """
        return _pyagrum.LoopyGibbsSampling_softEvidenceNodes(self)

    def targets(self) -> set[int]:
        r"""

        Returns
        -------
        list
          the list of marginal targets

        """
        return _pyagrum.LoopyGibbsSampling_targets(self)

    def evidenceImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(target|evs) (for all instanciation of target and evs)

        Parameters
        ----------
        target : set
          a set of targets ids or names.
        evs : set
          a set of nodes ids or names.

        Warnings
        --------
        if some evs are d-separated, they are not included in the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(targets|evs)

        """
        return _pyagrum.LoopyGibbsSampling_evidenceImpact(self, *args)

# Register LoopyGibbsSampling in _pyagrum:
_pyagrum.LoopyGibbsSampling_swigregister(LoopyGibbsSampling)
class LoopyMonteCarloSampling(MonteCarloSampling):
    r"""

    Class used for inferences using a loopy version of importance sampling.

    LoopyImportanceSampling(bn) -> LoopyImportanceSampling
        Parameters:
            - **bn** (*pyagrum.BayesNet*) -- a Bayesian network

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, bn: "IBayesNet"):
        _pyagrum.LoopyMonteCarloSampling_swiginit(self, _pyagrum.new_LoopyMonteCarloSampling(bn))

        self._model=bn#BN



    __swig_destroy__ = _pyagrum.delete_LoopyMonteCarloSampling

    def makeInference_(self) -> None:
        r"""

        Internal implementation of the loopy sampling inference.

        This protected virtual method performs the actual sampling-based inference loop.

        """
        return _pyagrum.LoopyMonteCarloSampling_makeInference_(self)

    def setVirtualLBPSize(self, vlbpsize: float) -> None:
        r"""

        Parameters
        ----------
        vlbpsize : float
          the size of the virtual LBP

        """
        return _pyagrum.LoopyMonteCarloSampling_setVirtualLBPSize(self, vlbpsize)

    def setVerbosity(self, v: bool) -> None:
        r"""

        Parameters
        ----------
        v : bool
                verbosity

        """
        return _pyagrum.LoopyMonteCarloSampling_setVerbosity(self, v)

    def setEpsilon(self, eps: float) -> None:
        r"""

        Parameters
        ----------
        eps : float
            the epsilon we want to use

        Raises
        ------
        pyagrum.OutOfBounds
            If eps<0

        """
        return _pyagrum.LoopyMonteCarloSampling_setEpsilon(self, eps)

    def setMinEpsilonRate(self, rate: float) -> None:
        r"""

        Parameters
        ----------
        rate : float
            the minimal epsilon rate

        """
        return _pyagrum.LoopyMonteCarloSampling_setMinEpsilonRate(self, rate)

    def setMaxIter(self, max: int) -> None:
        r"""

        Parameters
        ----------
        max : int
            the maximum number of iteration

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1

        """
        return _pyagrum.LoopyMonteCarloSampling_setMaxIter(self, max)

    def setMaxTime(self, timeout: float) -> None:
        r"""

        Parameters
        ----------
        tiemout : float
            stopping criterion on timeout (in seconds)

        Raises
        ------
        pyagrum.OutOfBounds
            If timeout<=0.0

        """
        return _pyagrum.LoopyMonteCarloSampling_setMaxTime(self, timeout)

    def setPeriodSize(self, p: int) -> None:
        r"""

        Parameters
        ----------
        p : int
            number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.LoopyMonteCarloSampling_setPeriodSize(self, p)

    def verbosity(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the verbosity is enabled

        """
        return _pyagrum.LoopyMonteCarloSampling_verbosity(self)

    def epsilon(self) -> float:
        r"""

        Returns
        -------
        float
            the value of epsilon

        """
        return _pyagrum.LoopyMonteCarloSampling_epsilon(self)

    def minEpsilonRate(self) -> float:
        r"""

        Returns
        -------
        float
            the value of the minimal epsilon rate

        """
        return _pyagrum.LoopyMonteCarloSampling_minEpsilonRate(self)

    def maxIter(self) -> int:
        r"""

        Returns
        -------
        int
            the criterion on number of iterations

        """
        return _pyagrum.LoopyMonteCarloSampling_maxIter(self)

    def maxTime(self) -> float:
        r"""

        Returns
        -------
        float
            the timeout(in seconds)

        """
        return _pyagrum.LoopyMonteCarloSampling_maxTime(self)

    def periodSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.LoopyMonteCarloSampling_periodSize(self)

    def nbrIterations(self) -> int:
        r"""

        Returns
        -------
        int
            the number of iterations

        """
        return _pyagrum.LoopyMonteCarloSampling_nbrIterations(self)

    def currentTime(self) -> float:
        r"""

        Returns
        -------
        float
            get the current running time in second (float)

        """
        return _pyagrum.LoopyMonteCarloSampling_currentTime(self)

    def messageApproximationScheme(self) -> str:
        r"""

        Returns
        -------
        str
            the approximation scheme message

        """
        return _pyagrum.LoopyMonteCarloSampling_messageApproximationScheme(self)

    def history(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple
            the scheme history

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the scheme did not performed or if verbosity is set to false

        """
        return _pyagrum.LoopyMonteCarloSampling_history(self)

    def _asIApproximationSchemeConfiguration(self) -> "pyagrum.YetUnWrapped":
        return _pyagrum.LoopyMonteCarloSampling__asIApproximationSchemeConfiguration(self)

    def makeInference(self) -> None:
        r"""

        Perform the heavy computations needed to compute the targets' posteriors

        In a Junction tree propagation scheme, for instance, the heavy computations are those of the messages sent in the JT.
        This is precisely what makeInference should compute. Later, the computations of the posteriors can be done 'lightly' by multiplying and projecting those messages.

        """
        return _pyagrum.LoopyMonteCarloSampling_makeInference(self)

    def posterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior probability of the node

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.LoopyMonteCarloSampling_posterior(self, *args)

    def addEvidence(self, *args) -> None:
        r"""

        Adds a new evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val :
          (int) a node value
        val :
          (str) the label of the node value
        vals : list
          a list of values

        Raises
        ------
          pyagrum.InvalidArgument
            If the node already has an evidence
          pyagrum.InvalidArgument
            If val is not a value for the node
          pyagrum.InvalidArgument
            If the size of vals is different from the domain side of the node
          pyagrum.FatalError
            If vals is a vector of 0s
          pyagrum.UndefinedElement
            If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyMonteCarloSampling_addEvidence(self, *args)

    def chgEvidence(self, *args) -> None:
        r"""

        Change the value of an already existing evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val : int|str
          a node value or the label of the node value
        vals : list of float
          a list of values

        Raises
        ------
        pyagrum.InvalidArgument
          If the node does not already have an evidence
        pyagrum.InvalidArgument
          If val is not a value for the node
        pyagrum.InvalidArgument
          If the size of vals is different from the domain side of the node
        pyagrum.FatalError
          If vals is a vector of 0s
        pyagrum.UndefinedElement
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyMonteCarloSampling_chgEvidence(self, *args)

    def hasEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if some node(s) (or the one in parameters) have received evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyMonteCarloSampling_hasEvidence(self, *args)

    def eraseAllEvidence(self) -> None:
        r"""

        Removes all the evidence entered into the network.

        """
        return _pyagrum.LoopyMonteCarloSampling_eraseAllEvidence(self)

    def eraseEvidence(self, *args) -> None:
        r"""

        Remove the evidence, if any, corresponding to the node Id or name.

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyMonteCarloSampling_eraseEvidence(self, *args)

    def hasHardEvidence(self, nodeName: str) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a hard evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyMonteCarloSampling_hasHardEvidence(self, nodeName)

    def hasSoftEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a soft evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyMonteCarloSampling_hasSoftEvidence(self, *args)

    def nbrEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of evidence entered into the Bayesian network

        """
        return _pyagrum.LoopyMonteCarloSampling_nbrEvidence(self)

    def nbrHardEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of hard evidence entered into the Bayesian network

        """
        return _pyagrum.LoopyMonteCarloSampling_nbrHardEvidence(self)

    def nbrSoftEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of soft evidence entered into the Bayesian network

        """
        return _pyagrum.LoopyMonteCarloSampling_nbrSoftEvidence(self)

    def eraseAllTargets(self) -> None:
        r"""

        Clear all previously defined targets (marginal and joint targets).

        As a result, no posterior can be computed (since we can only compute the posteriors of the marginal or joint targets that have been added by the user).

        """
        return _pyagrum.LoopyMonteCarloSampling_eraseAllTargets(self)

    def addAllTargets(self) -> None:
        r"""

        Add all the nodes as targets.

        """
        return _pyagrum.LoopyMonteCarloSampling_addAllTargets(self)

    def addTarget(self, *args) -> None:
        r"""

        Add a marginal target to the list of targets.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : str
          a node name

        Raises
        ------
        pyagrum.UndefinedElement
          If target is not a NodeId in the Bayes net

        """
        return _pyagrum.LoopyMonteCarloSampling_addTarget(self, *args)

    def eraseTarget(self, *args) -> None:
        r"""

        Remove, if existing, the marginal target.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.LoopyMonteCarloSampling_eraseTarget(self, *args)

    def isTarget(self, *args) -> bool:
        r"""

        Parameters
        ----------
        variable : int
         a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if variable is a (marginal) target

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.LoopyMonteCarloSampling_isTarget(self, *args)

    def nbrTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of marginal targets

        """
        return _pyagrum.LoopyMonteCarloSampling_nbrTargets(self)

    def H(self, *args) -> float:
        r"""

        Parameters
        ----------
        X : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        float
          the computed Shanon's entropy of a node given the observation

        """
        return _pyagrum.LoopyMonteCarloSampling_H(self, *args)

    def BN(self) -> "pyagrum.IBayesNet":
        r"""

        Returns
        -------
        pyagrum.IBayesNet
          A constant reference over the IBayesNet referenced by this class.

        Raises
        ------
          pyagrum.UndefinedElement
            If no Bayes net has been assigned to the inference.

        """
        return _pyagrum.LoopyMonteCarloSampling_BN(self)

    def currentPosterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the current posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the current posterior probability of the node

        Raises
        ------
        UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.LoopyMonteCarloSampling_currentPosterior(self, *args)

    def setEvidence(self, evidces):
        """
        Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
        pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          self.eraseAllEvidence()
          for k,v in evidces.items():
            self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          self.eraseAllEvidence()
          for p in evidces:
            self.addEvidence(p)
          return
        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def updateEvidence(self, evidces):
        """
        Apply chgEvidence(key,value) for every pairs in evidces (or addEvidence).

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
          pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          for k,v in evidces.items():
              if self.hasEvidence(k):
                  self.chgEvidence(k,v)
              else:
                  self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          for p in evidces:
              k=p.variable(0)
              if self.hasEvidence(k):
                  self.chgEvidence(p)
              else:
                  self.addEvidence(p)
          return

        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def setTargets(self, targets):
        """
        Remove all the targets and add the ones in parameter.

        Parameters
        ----------
        targets : set
          a set of targets

        Raises
        ------
          pyagrum.UndefinedElement
            If one target is not in the Bayes net
        """
        if not isinstance(targets, set):
            raise TypeError("setTargets parameter must be a set, not %s"%(type(targets)))

        self.eraseAllTargets()
        for k in targets:
            self.addTarget(k)



    def hardEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with hard evidence

        """
        return _pyagrum.LoopyMonteCarloSampling_hardEvidenceNodes(self)

    def softEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with soft evidence

        """
        return _pyagrum.LoopyMonteCarloSampling_softEvidenceNodes(self)

    def targets(self) -> set[int]:
        r"""

        Returns
        -------
        list
          the list of marginal targets

        """
        return _pyagrum.LoopyMonteCarloSampling_targets(self)

    def evidenceImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(target|evs) (for all instanciation of target and evs)

        Parameters
        ----------
        target : set
          a set of targets ids or names.
        evs : set
          a set of nodes ids or names.

        Warnings
        --------
        if some evs are d-separated, they are not included in the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(targets|evs)

        """
        return _pyagrum.LoopyMonteCarloSampling_evidenceImpact(self, *args)

# Register LoopyMonteCarloSampling in _pyagrum:
_pyagrum.LoopyMonteCarloSampling_swigregister(LoopyMonteCarloSampling)
class LoopyBeliefPropagation(object):
    r"""

    Class used for inferences using loopy belief propagation algorithm.

    LoopyBeliefPropagation(bn) -> LoopyBeliefPropagation
        Parameters:
            - **bn** (*pyagrum.BayesNet*) -- a Bayesian network

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, bn: "IBayesNet"):
        _pyagrum.LoopyBeliefPropagation_swiginit(self, _pyagrum.new_LoopyBeliefPropagation(bn))

        self._model=bn#BN



    __swig_destroy__ = _pyagrum.delete_LoopyBeliefPropagation

    def setVerbosity(self, v: bool) -> None:
        r"""

        Parameters
        ----------
        v : bool
                verbosity

        """
        return _pyagrum.LoopyBeliefPropagation_setVerbosity(self, v)

    def setEpsilon(self, eps: float) -> None:
        r"""

        Parameters
        ----------
        eps : float
            the epsilon we want to use

        Raises
        ------
        pyagrum.OutOfBounds
            If eps<0

        """
        return _pyagrum.LoopyBeliefPropagation_setEpsilon(self, eps)

    def setMinEpsilonRate(self, rate: float) -> None:
        r"""

        Parameters
        ----------
        rate : float
            the minimal epsilon rate

        """
        return _pyagrum.LoopyBeliefPropagation_setMinEpsilonRate(self, rate)

    def setMaxIter(self, max: int) -> None:
        r"""

        Parameters
        ----------
        max : int
            the maximum number of iteration

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1

        """
        return _pyagrum.LoopyBeliefPropagation_setMaxIter(self, max)

    def setMaxTime(self, timeout: float) -> None:
        r"""

        Parameters
        ----------
        tiemout : float
            stopping criterion on timeout (in seconds)

        Raises
        ------
        pyagrum.OutOfBounds
            If timeout<=0.0

        """
        return _pyagrum.LoopyBeliefPropagation_setMaxTime(self, timeout)

    def setPeriodSize(self, p: int) -> None:
        r"""

        Parameters
        ----------
        p : int
            number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.LoopyBeliefPropagation_setPeriodSize(self, p)

    def verbosity(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the verbosity is enabled

        """
        return _pyagrum.LoopyBeliefPropagation_verbosity(self)

    def epsilon(self) -> float:
        r"""

        Returns
        -------
        float
            the value of epsilon

        """
        return _pyagrum.LoopyBeliefPropagation_epsilon(self)

    def minEpsilonRate(self) -> float:
        r"""

        Returns
        -------
        float
            the value of the minimal epsilon rate

        """
        return _pyagrum.LoopyBeliefPropagation_minEpsilonRate(self)

    def maxIter(self) -> int:
        r"""

        Returns
        -------
        int
            the criterion on number of iterations

        """
        return _pyagrum.LoopyBeliefPropagation_maxIter(self)

    def maxTime(self) -> float:
        r"""

        Returns
        -------
        float
            the timeout(in seconds)

        """
        return _pyagrum.LoopyBeliefPropagation_maxTime(self)

    def periodSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.LoopyBeliefPropagation_periodSize(self)

    def nbrIterations(self) -> int:
        r"""

        Returns
        -------
        int
            the number of iterations

        """
        return _pyagrum.LoopyBeliefPropagation_nbrIterations(self)

    def currentTime(self) -> float:
        r"""

        Returns
        -------
        float
            get the current running time in second (float)

        """
        return _pyagrum.LoopyBeliefPropagation_currentTime(self)

    def messageApproximationScheme(self) -> str:
        r"""

        Returns
        -------
        str
            the approximation scheme message

        """
        return _pyagrum.LoopyBeliefPropagation_messageApproximationScheme(self)

    def history(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple
            the scheme history

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the scheme did not performed or if verbosity is set to false

        """
        return _pyagrum.LoopyBeliefPropagation_history(self)

    def _asIApproximationSchemeConfiguration(self) -> "pyagrum.YetUnWrapped":
        return _pyagrum.LoopyBeliefPropagation__asIApproximationSchemeConfiguration(self)

    def makeInference(self) -> None:
        r"""

        Perform the heavy computations needed to compute the targets' posteriors

        In a Junction tree propagation scheme, for instance, the heavy computations are those of the messages sent in the JT.
        This is precisely what makeInference should compute. Later, the computations of the posteriors can be done 'lightly' by multiplying and projecting those messages.

        """
        return _pyagrum.LoopyBeliefPropagation_makeInference(self)

    def posterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior probability of the node

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.LoopyBeliefPropagation_posterior(self, *args)

    def addEvidence(self, *args) -> None:
        r"""

        Adds a new evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val :
          (int) a node value
        val :
          (str) the label of the node value
        vals : list
          a list of values

        Raises
        ------
          pyagrum.InvalidArgument
            If the node already has an evidence
          pyagrum.InvalidArgument
            If val is not a value for the node
          pyagrum.InvalidArgument
            If the size of vals is different from the domain side of the node
          pyagrum.FatalError
            If vals is a vector of 0s
          pyagrum.UndefinedElement
            If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyBeliefPropagation_addEvidence(self, *args)

    def chgEvidence(self, *args) -> None:
        r"""

        Change the value of an already existing evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val : int|str
          a node value or the label of the node value
        vals : list of float
          a list of values

        Raises
        ------
        pyagrum.InvalidArgument
          If the node does not already have an evidence
        pyagrum.InvalidArgument
          If val is not a value for the node
        pyagrum.InvalidArgument
          If the size of vals is different from the domain side of the node
        pyagrum.FatalError
          If vals is a vector of 0s
        pyagrum.UndefinedElement
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyBeliefPropagation_chgEvidence(self, *args)

    def hasEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if some node(s) (or the one in parameters) have received evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyBeliefPropagation_hasEvidence(self, *args)

    def eraseAllEvidence(self) -> None:
        r"""

        Removes all the evidence entered into the network.

        """
        return _pyagrum.LoopyBeliefPropagation_eraseAllEvidence(self)

    def eraseEvidence(self, *args) -> None:
        r"""

        Remove the evidence, if any, corresponding to the node Id or name.

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyBeliefPropagation_eraseEvidence(self, *args)

    def hasHardEvidence(self, nodeName: str) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a hard evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyBeliefPropagation_hasHardEvidence(self, nodeName)

    def hasSoftEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a soft evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.LoopyBeliefPropagation_hasSoftEvidence(self, *args)

    def nbrEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of evidence entered into the Bayesian network

        """
        return _pyagrum.LoopyBeliefPropagation_nbrEvidence(self)

    def nbrHardEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of hard evidence entered into the Bayesian network

        """
        return _pyagrum.LoopyBeliefPropagation_nbrHardEvidence(self)

    def nbrSoftEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of soft evidence entered into the Bayesian network

        """
        return _pyagrum.LoopyBeliefPropagation_nbrSoftEvidence(self)

    def eraseAllTargets(self) -> None:
        r"""

        Clear all previously defined targets (marginal and joint targets).

        As a result, no posterior can be computed (since we can only compute the posteriors of the marginal or joint targets that have been added by the user).

        """
        return _pyagrum.LoopyBeliefPropagation_eraseAllTargets(self)

    def addAllTargets(self) -> None:
        r"""

        Add all the nodes as targets.

        """
        return _pyagrum.LoopyBeliefPropagation_addAllTargets(self)

    def addTarget(self, *args) -> None:
        r"""

        Add a marginal target to the list of targets.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : str
          a node name

        Raises
        ------
        pyagrum.UndefinedElement
          If target is not a NodeId in the Bayes net

        """
        return _pyagrum.LoopyBeliefPropagation_addTarget(self, *args)

    def eraseTarget(self, *args) -> None:
        r"""

        Remove, if existing, the marginal target.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.LoopyBeliefPropagation_eraseTarget(self, *args)

    def isTarget(self, *args) -> bool:
        r"""

        Parameters
        ----------
        variable : int
         a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if variable is a (marginal) target

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.LoopyBeliefPropagation_isTarget(self, *args)

    def nbrTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of marginal targets

        """
        return _pyagrum.LoopyBeliefPropagation_nbrTargets(self)

    def H(self, *args) -> float:
        r"""

        Parameters
        ----------
        X : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        float
          the computed Shanon's entropy of a node given the observation

        """
        return _pyagrum.LoopyBeliefPropagation_H(self, *args)

    def BN(self) -> "pyagrum.IBayesNet":
        r"""

        Returns
        -------
        pyagrum.IBayesNet
          A constant reference over the IBayesNet referenced by this class.

        Raises
        ------
          pyagrum.UndefinedElement
            If no Bayes net has been assigned to the inference.

        """
        return _pyagrum.LoopyBeliefPropagation_BN(self)

    def setEvidence(self, evidces):
        """
        Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
        pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          self.eraseAllEvidence()
          for k,v in evidces.items():
            self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          self.eraseAllEvidence()
          for p in evidces:
            self.addEvidence(p)
          return
        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def updateEvidence(self, evidces):
        """
        Apply chgEvidence(key,value) for every pairs in evidces (or addEvidence).

        Parameters
        ----------
        evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
          a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

        Raises
        ------
          pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if isinstance(evidces, dict):
          for k,v in evidces.items():
              if self.hasEvidence(k):
                  self.chgEvidence(k,v)
              else:
                  self.addEvidence(k,v)
          return
        elif isinstance(evidces, list):#should be a list of Tensor
          for p in evidces:
              k=p.variable(0)
              if self.hasEvidence(k):
                  self.chgEvidence(p)
              else:
                  self.addEvidence(p)
          return

        raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))



    def setTargets(self, targets):
        """
        Remove all the targets and add the ones in parameter.

        Parameters
        ----------
        targets : set
          a set of targets

        Raises
        ------
          pyagrum.UndefinedElement
            If one target is not in the Bayes net
        """
        if not isinstance(targets, set):
            raise TypeError("setTargets parameter must be a set, not %s"%(type(targets)))

        self.eraseAllTargets()
        for k in targets:
            self.addTarget(k)



    def hardEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with hard evidence

        """
        return _pyagrum.LoopyBeliefPropagation_hardEvidenceNodes(self)

    def softEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with soft evidence

        """
        return _pyagrum.LoopyBeliefPropagation_softEvidenceNodes(self)

    def targets(self) -> set[int]:
        r"""

        Returns
        -------
        list
          the list of marginal targets

        """
        return _pyagrum.LoopyBeliefPropagation_targets(self)

    def evidenceImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(target|evs) (for all instanciation of target and evs)

        Parameters
        ----------
        target : set
          a set of targets ids or names.
        evs : set
          a set of nodes ids or names.

        Warnings
        --------
        if some evs are d-separated, they are not included in the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(targets|evs)

        """
        return _pyagrum.LoopyBeliefPropagation_evidenceImpact(self, *args)

# Register LoopyBeliefPropagation in _pyagrum:
_pyagrum.LoopyBeliefPropagation_swigregister(LoopyBeliefPropagation)
class ExactBNdistance(object):
    r"""

    Class representing exacte computation of divergence and distance between BNs

    ExactBNdistance(P,Q) -> ExactBNdistance
        Parameters:
            - **P** (*pyagrum.BayesNet*)
              a Bayesian network
            - **Q** (*pyagrum.BayesNet*)
              another Bayesian network to compare with the first one

    ExactBNdistance(ebnd) -> ExactBNdistance
        Parameters:
            - **ebnd** (*pyagrum.ExactBNdistance*)
              the exact BNdistance to copy

    Raises
    ------
      pyagrum.OperationNotAllowed
        If the 2BNs have not the same domain size of compatible node sets

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.ExactBNdistance_swiginit(self, _pyagrum.new_ExactBNdistance(*args))
    __swig_destroy__ = _pyagrum.delete_ExactBNdistance

    def compute(self) -> dict[str, object]:
        r"""

        Returns
        -------
        dict
            a dictionnary containing the different values after the computation.

        """
        return _pyagrum.ExactBNdistance_compute(self)

# Register ExactBNdistance in _pyagrum:
_pyagrum.ExactBNdistance_swigregister(ExactBNdistance)
class GibbsBNdistance(ApproximationScheme):
    r"""

    Class representing a Gibbs-Approximated computation of divergence and distance between BNs


    GibbsBNdistance(P,Q) -> GibbsBNdistance
        Parameters:
            - **P** (*pyagrum.BayesNet*) -- a Bayesian network
            - **Q** (*pyagrum.BayesNet*) -- another Bayesian network to compare with the first one

    GibbsBNdistance(gbnd) -> GibbsBNdistance
        Parameters:
            - **gbnd** (*pyagrum.GibbsBNdistance*) -- the Gibbs BNdistance to copy

    Raises
    ------
      pyagrum.OperationNotAllowed
        If the 2BNs have not the same domain size of compatible node sets

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.GibbsBNdistance_swiginit(self, _pyagrum.new_GibbsBNdistance(*args))
    __swig_destroy__ = _pyagrum.delete_GibbsBNdistance

    def setBurnIn(self, b: int) -> None:
        r"""

        Parameters
        ----------
        b : int
            size of burn in on number of iteration

        """
        return _pyagrum.GibbsBNdistance_setBurnIn(self, b)

    def burnIn(self) -> int:
        r"""

        Returns
        -------
        int
            size of burn in on number of iteration

        """
        return _pyagrum.GibbsBNdistance_burnIn(self)

    def compute(self) -> dict[str, object]:
        r"""

        Returns
        -------
        dict
            a dictionnary containing the different values after the computation.

        """
        return _pyagrum.GibbsBNdistance_compute(self)

    def setVerbosity(self, v: bool) -> None:
        r"""

        Parameters
        ----------
        v : bool
                verbosity

        """
        return _pyagrum.GibbsBNdistance_setVerbosity(self, v)

    def setEpsilon(self, eps: float) -> None:
        r"""

        Parameters
        ----------
        eps : float
            the epsilon we want to use

        Raises
        ------
        pyagrum.OutOfBounds
            If eps<0

        """
        return _pyagrum.GibbsBNdistance_setEpsilon(self, eps)

    def setMinEpsilonRate(self, rate: float) -> None:
        r"""

        Parameters
        ----------
        rate : float
            the minimal epsilon rate

        """
        return _pyagrum.GibbsBNdistance_setMinEpsilonRate(self, rate)

    def setMaxIter(self, max: int) -> None:
        r"""

        Parameters
        ----------
        max : int
            the maximum number of iteration

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1

        """
        return _pyagrum.GibbsBNdistance_setMaxIter(self, max)

    def setMaxTime(self, timeout: float) -> None:
        r"""

        Parameters
        ----------
        tiemout : float
            stopping criterion on timeout (in seconds)

        Raises
        ------
        pyagrum.OutOfBounds
            If timeout<=0.0

        """
        return _pyagrum.GibbsBNdistance_setMaxTime(self, timeout)

    def setPeriodSize(self, p: int) -> None:
        r"""

        Parameters
        ----------
        p : int
            number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.GibbsBNdistance_setPeriodSize(self, p)

    def verbosity(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the verbosity is enabled

        """
        return _pyagrum.GibbsBNdistance_verbosity(self)

    def epsilon(self) -> float:
        r"""

        Returns
        -------
        float
            the value of epsilon

        """
        return _pyagrum.GibbsBNdistance_epsilon(self)

    def minEpsilonRate(self) -> float:
        r"""

        Returns
        -------
        float
            the value of the minimal epsilon rate

        """
        return _pyagrum.GibbsBNdistance_minEpsilonRate(self)

    def maxIter(self) -> int:
        r"""

        Returns
        -------
        int
            the criterion on number of iterations

        """
        return _pyagrum.GibbsBNdistance_maxIter(self)

    def maxTime(self) -> float:
        r"""

        Returns
        -------
        float
            the timeout(in seconds)

        """
        return _pyagrum.GibbsBNdistance_maxTime(self)

    def periodSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.GibbsBNdistance_periodSize(self)

    def nbrIterations(self) -> int:
        r"""

        Returns
        -------
        int
            the number of iterations

        """
        return _pyagrum.GibbsBNdistance_nbrIterations(self)

    def currentTime(self) -> float:
        r"""

        Returns
        -------
        float
            get the current running time in second (float)

        """
        return _pyagrum.GibbsBNdistance_currentTime(self)

    def messageApproximationScheme(self) -> str:
        r"""

        Returns
        -------
        str
            the approximation scheme message

        """
        return _pyagrum.GibbsBNdistance_messageApproximationScheme(self)

    def history(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple
            the scheme history

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the scheme did not performed or if verbosity is set to false

        """
        return _pyagrum.GibbsBNdistance_history(self)

    def _asIApproximationSchemeConfiguration(self) -> "pyagrum.YetUnWrapped":
        return _pyagrum.GibbsBNdistance__asIApproximationSchemeConfiguration(self)

    def nbrDrawnVar(self) -> int:
        r"""

        Returns
        -------
        int
          the number of variable drawn at each iteration

        """
        return _pyagrum.GibbsBNdistance_nbrDrawnVar(self)

    def setNbrDrawnVar(self, _nbr: int) -> None:
        r"""

        Parameters
        ----------
        _nbr : int
          the number of variables to be drawn at each iteration

        """
        return _pyagrum.GibbsBNdistance_setNbrDrawnVar(self, _nbr)

    def isDrawnAtRandom(self) -> bool:
        r"""

        Returns
        -------
        bool
          True if variables are drawn at random

        """
        return _pyagrum.GibbsBNdistance_isDrawnAtRandom(self)

    def setDrawnAtRandom(self, _atRandom: bool) -> None:
        r"""

        Parameters
        ----------
        _atRandom : bool
          indicates if variables should be drawn at random

        """
        return _pyagrum.GibbsBNdistance_setDrawnAtRandom(self, _atRandom)

# Register GibbsBNdistance in _pyagrum:
_pyagrum.GibbsBNdistance_swigregister(GibbsBNdistance)
class MCBNDistance(ApproximationScheme):
    r"""

    Class representing a Monte-Carlo-approximated computation of divergence and distance between BNs

    Unlike GibbsBNdistance, each sample is drawn independently (forward sampling in topological order), so there is no Markov chain and no burn-in to configure.

    MCBNDistance(P,Q) -> MCBNDistance
        Parameters:
            - **P** (*pyagrum.BayesNet*) -- a Bayesian network
            - **Q** (*pyagrum.BayesNet*) -- another Bayesian network to compare with the first one

    MCBNDistance(mcbnd) -> MCBNDistance
        Parameters:
            - **mcbnd** (*pyagrum.MCBNDistance*) -- the Monte Carlo BNdistance to copy

    Raises
    ------
      pyagrum.OperationNotAllowed
        If the 2BNs have not the same domain size of compatible node sets

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.MCBNDistance_swiginit(self, _pyagrum.new_MCBNDistance(*args))
    __swig_destroy__ = _pyagrum.delete_MCBNDistance

    def compute(self) -> dict[str, object]:
        r"""

        Returns
        -------
        dict
            a dictionnary containing the different values after the computation.

        """
        return _pyagrum.MCBNDistance_compute(self)

    def setVerbosity(self, v: bool) -> None:
        r"""

        Parameters
        ----------
        v : bool
                verbosity

        """
        return _pyagrum.MCBNDistance_setVerbosity(self, v)

    def setEpsilon(self, eps: float) -> None:
        r"""

        Parameters
        ----------
        eps : float
            the epsilon we want to use

        Raises
        ------
        pyagrum.OutOfBounds
            If eps<0

        """
        return _pyagrum.MCBNDistance_setEpsilon(self, eps)

    def setMinEpsilonRate(self, rate: float) -> None:
        r"""

        Parameters
        ----------
        rate : float
            the minimal epsilon rate

        """
        return _pyagrum.MCBNDistance_setMinEpsilonRate(self, rate)

    def setMaxIter(self, max: int) -> None:
        r"""

        Parameters
        ----------
        max : int
            the maximum number of iteration

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1

        """
        return _pyagrum.MCBNDistance_setMaxIter(self, max)

    def setMaxTime(self, timeout: float) -> None:
        r"""

        Parameters
        ----------
        tiemout : float
            stopping criterion on timeout (in seconds)

        Raises
        ------
        pyagrum.OutOfBounds
            If timeout<=0.0

        """
        return _pyagrum.MCBNDistance_setMaxTime(self, timeout)

    def setPeriodSize(self, p: int) -> None:
        r"""

        Parameters
        ----------
        p : int
            number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.MCBNDistance_setPeriodSize(self, p)

    def verbosity(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the verbosity is enabled

        """
        return _pyagrum.MCBNDistance_verbosity(self)

    def epsilon(self) -> float:
        r"""

        Returns
        -------
        float
            the value of epsilon

        """
        return _pyagrum.MCBNDistance_epsilon(self)

    def minEpsilonRate(self) -> float:
        r"""

        Returns
        -------
        float
            the value of the minimal epsilon rate

        """
        return _pyagrum.MCBNDistance_minEpsilonRate(self)

    def maxIter(self) -> int:
        r"""

        Returns
        -------
        int
            the criterion on number of iterations

        """
        return _pyagrum.MCBNDistance_maxIter(self)

    def maxTime(self) -> float:
        r"""

        Returns
        -------
        float
            the timeout(in seconds)

        """
        return _pyagrum.MCBNDistance_maxTime(self)

    def periodSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.MCBNDistance_periodSize(self)

    def nbrIterations(self) -> int:
        r"""

        Returns
        -------
        int
            the number of iterations

        """
        return _pyagrum.MCBNDistance_nbrIterations(self)

    def currentTime(self) -> float:
        r"""

        Returns
        -------
        float
            get the current running time in second (float)

        """
        return _pyagrum.MCBNDistance_currentTime(self)

    def messageApproximationScheme(self) -> str:
        r"""

        Returns
        -------
        str
            the approximation scheme message

        """
        return _pyagrum.MCBNDistance_messageApproximationScheme(self)

    def history(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple
            the scheme history

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the scheme did not performed or if verbosity is set to false

        """
        return _pyagrum.MCBNDistance_history(self)

    def _asIApproximationSchemeConfiguration(self) -> "pyagrum.YetUnWrapped":
        return _pyagrum.MCBNDistance__asIApproximationSchemeConfiguration(self)

# Register MCBNDistance in _pyagrum:
_pyagrum.MCBNDistance_swigregister(MCBNDistance)
class BNDatabaseGenerator(object):
    r"""

    BNDatabaseGenerator is used to easily generate databases from a pyagrum.BayesNet.

    Parameters
    ----------
    bn: pyagrum.BayesNet
      the Bayesian network used to generate data.

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, bn: "pyagrum.BayesNet"):
        _pyagrum.BNDatabaseGenerator_swiginit(self, _pyagrum.new_BNDatabaseGenerator(bn))
    __swig_destroy__ = _pyagrum.delete_BNDatabaseGenerator

    def setDiscretizedLabelModeRandom(self) -> None:
        r"""

        Set the discretized label mode to RANDOM (default mode) : sampling a `pyagrum.discretizedVariable` will give a random value from the uniform distribution on that interval.

        Examples
        --------
        >>> import pyagrum as gum
        >>> bn=pyagrum.fastBN('A->B{yes|maybe|no}<-C->D->E<-F[1,1.5,3,10.2]<-B')
        >>> g=pyagrum.BNDatabaseGenerator(bn)
        >>> g.setRandomVarOrder()
        >>> g.drawSamples(5,
        ...               {'B':'yes','E':'1'})
        -122.98754206579288
        >>> g.setDiscretizedLabelModeRandom() # By default
        >>> g.to_pandas()
             B         F  A  C  E  D
        0  yes  2.802302  0  0  1  0
        1  yes  1.761605  0  0  1  0
        2  yes  2.507535  0  0  1  1
        3  yes  2.815282  0  1  1  0
        4  yes  5.548571  1  0  1  1

        """
        return _pyagrum.BNDatabaseGenerator_setDiscretizedLabelModeRandom(self)

    def setDiscretizedLabelModeMedian(self) -> None:
        r"""

        Set the discretized label mode to MEDIAN : sampling a `pyagrum.discretizedVariable` will give a deterministic value : the median of the uniform distribution on that interval.

        Examples
        --------
        >>> import pyagrum as gum
        >>> bn=pyagrum.fastBN('A->B{yes|maybe|no}<-C->D->E<-F[1,1.5,3,10.2]<-B')
        >>> g=pyagrum.BNDatabaseGenerator(bn)
        >>> g.setRandomVarOrder()
        >>> g.drawSamples(5,
        ...               {'B':'yes','E':'1'})
        -122.98754206579288
        >>> g.setDiscretizedLabelModeMedian()
        >>> g.to_pandas()
             B         F  A  C  E  D
        0  yes  2.250000  0  0  1  0
        1  yes  2.250000  0  0  1  0
        2  yes  2.250000  0  0  1  1
        3  yes  2.250000  0  1  1  0
        4  yes  6.600000  1  0  1  1

        """
        return _pyagrum.BNDatabaseGenerator_setDiscretizedLabelModeMedian(self)

    def setDiscretizedLabelModeInterval(self) -> None:
        r"""

        Set the discretized label mode to INTERVAL : sampling a `pyagrum.discretizedVariable` will give a deterministic value : the string representation of the interval.

        Examples
        --------
        >>> import pyagrum as gum
        >>> bn=pyagrum.fastBN('A->B{yes|maybe|no}<-C->D->E<-F[1,1.5,3,10.2]<-B')
        >>> g=pyagrum.BNDatabaseGenerator(bn)
        >>> g.setRandomVarOrder()
        >>> g.drawSamples(5,
        ...               {'B':'yes','E':'1'})
        -122.98754206579288
        >>> g.setDiscretizedLabelModeInterval()
        >>> g.to_pandas()
             B         F  A  C  E  D
        0  yes   [1.5;3[  0  0  1  0
        1  yes   [1.5;3[  0  0  1  0
        2  yes   [1.5;3[  0  0  1  1
        3  yes   [1.5;3[  0  1  1  0
        4  yes  [3;10.2]  1  0  1  1

        """
        return _pyagrum.BNDatabaseGenerator_setDiscretizedLabelModeInterval(self)

    def toCSV(self, *args) -> None:
        r"""

        generates csv representing the generated database.

        Parameters
        ----------
        csvFilename: str
          the name of the csv file
        useLabels: bool
          whether label or id in the csv file (default true)
        append: bool
          append in the file or rewrite the file (default false)
        csvSeparator: str
          separator in the csv file (default ',')

        """
        return _pyagrum.BNDatabaseGenerator_toCSV(self, *args)

    def samplesNbRows(self) -> int:
        r"""

        return the number of rows in the samples

        """
        return _pyagrum.BNDatabaseGenerator_samplesNbRows(self)

    def samplesNbCols(self) -> int:
        r"""

        return the number of columns in the samples

        """
        return _pyagrum.BNDatabaseGenerator_samplesNbCols(self)

    def samplesAt(self, row: int, col: int) -> int:
        r"""

        Get the value of the database in (row,col)

        Parameters
        ----------
        row : int
          the row
        col : int
          the column (using the ordered list of variables)

        Returns
        -------
        int
          the index of the modality of the variable in this position

        """
        return _pyagrum.BNDatabaseGenerator_samplesAt(self, row, col)

    def samplesLabelAt(self, row: int, col: int) -> str:
        r"""

        Get the label of the database in (row,col)

        Parameters
        ----------
        row : int
          the row
        col : int
          the column (using the ordered list of variables)

        Returns
        -------
        str
          the label of the modality of the variable in this position

        """
        return _pyagrum.BNDatabaseGenerator_samplesLabelAt(self, row, col)

    def setVarOrder(self, *args) -> None:
        r"""

        Set a specific order with a list of names

        Parameters
        ----------
        vars : list of str
          order specified by the list of variable names.

        """
        return _pyagrum.BNDatabaseGenerator_setVarOrder(self, *args)

    def setVarOrderFromCSV(self, *args) -> None:
        r"""

        Set the same order than in a csv file

        Parameters
        ----------
        filename:str
          the name of the CSV file

        """
        return _pyagrum.BNDatabaseGenerator_setVarOrderFromCSV(self, *args)

    def setTopologicalVarOrder(self) -> None:
        r"""

        Select a topological order for the variables in the database.

        """
        return _pyagrum.BNDatabaseGenerator_setTopologicalVarOrder(self)

    def setAntiTopologicalVarOrder(self) -> None:
        r"""

        Select an anti-topological order for the variables in the database.

        """
        return _pyagrum.BNDatabaseGenerator_setAntiTopologicalVarOrder(self)

    def setRandomVarOrder(self) -> None:
        r"""

        Select an random order for the variables in the database.

        """
        return _pyagrum.BNDatabaseGenerator_setRandomVarOrder(self)

    def varOrderNames(self) -> tuple[str, ...]:
        r"""

        The actual order for the variable (as a tuple of NodeId)

        Returns
        -------
        tuple
          the tuple of names

        """
        return _pyagrum.BNDatabaseGenerator_varOrderNames(self)

    def log2likelihood(self) -> float:
        r"""

        Get the  log2likelihood of the generated database

        Raises
        ------
        pyagrum.OperationNotAllowed
          if nothing has been sampled yet (using `pyagrum.BNDatabaseGenerator.drawSamples()` for instance)

        Returns
        -------
        float
          the log2likelihood

        """
        return _pyagrum.BNDatabaseGenerator_log2likelihood(self)

    def bn(self) -> "pyagrum.BayesNet":
        r"""

        Get the Bayesian network used to generate the samples

        Returns
        -------
        pyagrum.BayesNet
          The Bayesian network


        """
        return _pyagrum.BNDatabaseGenerator_bn(self)

    def varOrder(self) -> tuple[int, ...]:
        r"""

        The actual order for the variable (as a tuple of NodeId)

        Returns
        -------
        tuple
          the tuple of NodeId

        """
        return _pyagrum.BNDatabaseGenerator_varOrder(self)

    def drawSamples(self, *args) -> float:
        r"""

        Generate and stock a database generated by sampling the Bayesian network.

        If `evs` is specified, the samples are stored only if there are compatible with these observations.

        Returns the log2likelihood of this database.

        Parameters
        ----------
        nbSamples : int
            the number of samples that will be generated
        evs : pyagrum.Instantiation or dict
          (optional) The evidence that will be observed by the resulting samples.
        timeout : int
          (optional) The maximum time in seconds to generate the samples (default 600)

        Warnings
        --------
        `nbSamples` is not the number of generated samples but the size of the database.It may happen that the evidence is very rare (or even impossible). In this case, the generation process may be *very* slow (it may even not stop). For this case a timeout is provided (default 600 seconds) and then the size of the database can be smaller than `nbSamples` (even equal to 0).

        Warnings
        --------
        For discretized variable, aGrum/pyAgrum defines 3 behaviors when generating sample with labels :
        - RANDOM (default) : the value is chosen randomly in the interval
        - MEDIAN : the value is the median of the interval
        - INTERVAL : the value is the interval itself (for instance `« [0,1[ »`)

        The behavior can be set using `setDiscretizedLabelMode{Random|Median|Interval}`.

        Examples
        --------
        >>> import pyagrum as gum
        >>> bn=pyagrum.fastBN('A->B{yes|maybe|no}<-C->D->E<-F[1,1.5,3,10.2]<-B')
        >>> g=pyagrum.BNDatabaseGenerator(bn)
        >>> g.setRandomVarOrder()
        >>> g.drawSamples(5,
        ...               {'B':'yes','E':'1'})
        -122.98754206579288
        >>> g.setDiscretizedLabelModeRandom() # By default
        >>> g.to_pandas()
             B         F  A  C  E  D
        0  yes  2.802302  0  0  1  0
        1  yes  1.761605  0  0  1  0
        2  yes  2.507535  0  0  1  1
        3  yes  2.815282  0  1  1  0
        4  yes  5.548571  1  0  1  1
        >>> g.setDiscretizedLabelModeMedian()
        >>> g.to_pandas()
             B         F  A  C  E  D
        0  yes  2.250000  0  0  1  0
        1  yes  2.250000  0  0  1  0
        2  yes  2.250000  0  0  1  1
        3  yes  2.250000  0  1  1  0
        4  yes  6.600000  1  0  1  1
        >>> g.setDiscretizedLabelModeInterval()
        >>> g.to_pandas()
             B         F  A  C  E  D
        0  yes   [1.5;3[  0  0  1  0
        1  yes   [1.5;3[  0  0  1  0
        2  yes   [1.5;3[  0  0  1  1
        3  yes   [1.5;3[  0  1  1  0
        4  yes  [3;10.2]  1  0  1  1

        """
        return _pyagrum.BNDatabaseGenerator_drawSamples(self, *args)

    def to_pandas(self,with_labels=True):
      r"""
      export the samples as a pandas.DataFrame.

      Parameters
      ----------
      with_labels: bool
        is the DataFrame full of labels of variables or full of index of labels of variables
      """
      import pandas

      nrow=self.samplesNbRows()
      ncol=self.samplesNbCols()

      if with_labels:
        ldatas=[[self.samplesLabelAt(row,col) for col in range(ncol)] for row in range(nrow)]
      else:
        ldatas=[[self.samplesAt(row,col) for col in range(ncol)] for row in range(nrow)]

      return pandas.DataFrame(columns=self.varOrderNames(),data=ldatas)


# Register BNDatabaseGenerator in _pyagrum:
_pyagrum.BNDatabaseGenerator_swigregister(BNDatabaseGenerator)
class BNLearner(object):
    r"""

    This class provides functionality for learning Bayesian Networks from data.

    BNLearner(filename,inducedTypes=True) -> BNLearner
        Parameters:
            - **source** (*str* or *pandas.DataFrame*) -- the data to learn from
            - **missingSymbols** (*list of str*) -- list of strings that will be interpreted as missing values (by default : `?`)
            - **inducedTypes** (*Bool*) -- whether BNLearner should try to automatically find the type of each variable

    BNLearner(filename,src) -> BNLearner
        Parameters:
            - **source** (*str* or *pandas.DataFrame*) -- the data to learn from
            - **src** (*pyagrum.BayesNet*) -- the Bayesian network used to find those modalities
            - **missingSymbols** (*list of str*) -- list of strings that will be interpreted as missing values (by default : `?`)

    BNLearner(learner) -> BNLearner
        Parameters:
            - **learner** (*pyagrum.BNLearner*) -- the BNLearner to copy

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args):

        if type(args[0]) is not str:
          if hasattr(args[0],"to_csv") or hasattr(args[0],"write_csv"):
              import tempfile
              csvfile = tempfile.NamedTemporaryFile(delete=False)
              tmpfilename = csvfile.name
              csvfilename = tmpfilename + ".csv"
              csvfile.close()

              if hasattr(args[0],"to_csv"):
                args[0].to_csv(csvfilename,na_rep="?",index=False)
              else:
                args[0].write_csv(csvfilename,null_value="?")

              self.__init__(csvfilename,*args[1:])
              return
          else:
            raise TypeError("first argument must be a string or a DataFrame")


        _pyagrum.BNLearner_swiginit(self, _pyagrum.new_BNLearner(*args))
    __swig_destroy__ = _pyagrum.delete_BNLearner

    def learnBN(self) -> "pyagrum.BayesNet":
        r"""

        Learns a BayesNet (both parameters and structure) from the BNLearner's database

        Returns
        -------
        pyagrum.BayesNet
            the learnt BayesNet

        """
        return _pyagrum.BNLearner_learnBN(self)

    def learnParameters(self, *args) -> "pyagrum.BayesNet":
        r"""

        Creates a Bayes net whose structure corresponds to that passed in argument or to
        the last structure learnt by Method `learnDAG()`, and whose parameters are learnt
        from the BNLearner's database.

        Usage:
          1. `learnParameters(dag, take_into_account_score=True)`
          2. `learnParameters(bn, take_into_account_score=True)`
          3. `learnParameters(take_into_account_score=True)`

        When the first argument of Method `learnParameters()` is a DAG or a Bayes net
        (usages 1. and 2.), this one specifies the graphical structure of the returned
        Bayes net. Otherwise (usage 3.), Method `learnParameters()` is called implicitly
        with the last DAG learnt by the BNLearner.

        The difference between calling this method with a DAG (usages 1. and 3.) or a
        Bayes net (usage 2.) arises when the database contains missing values and EM is
        used to learn the parameters. EM needs to initialize the conditional probability
        distributions (CPT) before iterating the expectation/maximimzation steps. When a
        DAG is passed in argument, these initializations are performed using a specific
        estimator that does not take into account the missing values in the database. The
        resulting CPTs are then perturbed randomly (see the noise in method `useEM()`).
        When a Bayes net is passed in argument, its CPT for a node A can be either filled
        exclusively with zeroes or not. In the first case, the initialization is performed
        as described above. In the second case, the value of A's CPT is used as is, and
        a subsequent perturbation controlled by the noise level is applied.

        Parameters
        ----------
        dag : pyagrum.DAG
            specifies the graphical structure of the returned Bayes net.
        bn : pyagrum.BayesNet
            specifies the graphical structure of the returned Bayes net and, when
            the database contains missing values and EM is used for learning, force
            EM to initialize the CPTs of the resulting Bayes net to the values of
            those passed in argument (when they are not fully filled with zeroes)
            before iterating over the expectation/maximization steps.
        take_into_account_score : bool, default=True
            The graphical structure passed in argument may have been learnt from a
            structure learning. In this case, if the score used to learn the structure
            has an implicit prior (like K2 which has a 1-smoothing prior), it is important
            to also take into account this implicit prior for parameter learning. By
            default (`take_into_account_score=True`), we will learn parameters by taking
            into account the prior specified by methods usePriorXXX() + the implicit prior
            of the score (if any). If `take_into_account_score=False`, we just take into
            account the prior specified by `usePriorXXX()`.

        Returns
        -------
        pyagrum.BayesNet
            the learnt BayesNet

        Raises
        ------
        pyagrum.MissingVariableInDatabase
            If a variable of the Bayes net is not found in the database
        pyagrum.MissingValueInDatabase
            If the database contains some missing values and EM is not used for the learning
        pyagrum.OperationNotAllowed
            If EM is used but no stopping criterion has been selected
        pyagrum.UnknownLabelInDatabase
            If a label is found in the database that do not correspond to the variable

        Warnings
        --------
        When using a `pyagrum.DAG` as input parameter, the NodeIds in the dag and index of
        rows in the database must fit in order to coherently fix the structure of the BN.
        Generally, it is safer to use a `pyagrum.BayesNet` as input or even to use
        `pyagrum.BNLearner.fitParameters`.

        """
        val = _pyagrum.BNLearner_learnParameters(self, *args)

        if self._EM_warning():
            import warnings
            warnings.warn("\nthe learnParameters's EM algorithm has completed prematurely due to a likelihood divergence\n", UserWarning)


        return val


    def copyState(self, learner: "BNLearner") -> None:
        r"""

        Copy the state of the given pyagrum.BNLearner (as argument).

        Parameters
        ----------
        learner : pyagrum.BNLearner
            the BNLearner to copy state from.

        """
        val = _pyagrum.BNLearner_copyState(self, learner)

        return self


        return val


    def setInitialDAG(self, dag: "pyagrum.DAG") -> "pyagrum.BNLearner":
        r"""

        Sets the initial structure (DAG) used by the structure learning algorithm.

        Parameters
        ----------
        dag : pyagrum.DAG
            an initial pyagrum.DAG structure

        """
        return _pyagrum.BNLearner_setInitialDAG(self, dag)

    def useEM(self, *args) -> "pyagrum.BNLearner":
        r"""

        Sets whether we use EM for parameter learning or not, depending on the value of epsilon.

        usage:
          * `useEM(epsilon, noise=0.1)`

        When epsilon is equal to 0.0, EM is forbidden, else EM is used for parameter learning
        whenever the database contains missing values. In this case, its stopping criterion
        is a threshold on the log-likelihood evolution rate, i.e., if llc and llo refer to
        the log-likelihoods at the current and previous EM steps respectively, EM will stop
        when (llc - llo) / llc drops below epsilon. If you wish to be more specific on which
        stopping criterion to use, you may prefer exploiting methods `useEMWithRateCriterion()`
        or `useEMWithDiffCriterion()`.

        Parameters
        ----------
        epsilon : float
            if epsilon>0 then EM is used and stops whenever the relative difference between two
            consecutive log-likelihoods (log-likelihood evolution rate) drops below epsilon.

            if epsilon=0.0 then EM is not used. But if you wish to forbid the use of EM, prefer
            executing Method `forbidEM()` rather than useEM(0.0) as it is more unequivocal.

        noise: float, default=0.1
                During EM's initialization, the CPTs are randomly perturbed using the following formula:
                new_CPT = (1-noise) * CPT + noise * random_CPT. Parameter noise must belong
                to interval [0,1]. By default, noise is equal to 0.1.

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        Raises
        ------
        pyagrum.OutOfBounds
                if `epsilon` is strictly negative or if `noise` does not belong to interval [0,1].

        """
        return _pyagrum.BNLearner_useEM(self, *args)

    def useEMWithRateCriterion(self, *args) -> "pyagrum.BNLearner":
        r"""

        Enforces that EM with the log-likelihood min evolution rate stopping criterion will be
        used for parameter learning when the dataset contains missing values.

        Parameters
        ----------
        epsilon : float
             epsilon sets the approximation stopping criterion: EM stops whenever the absolute
             value of the relative difference between two consecutive log-likelihoods drops below
             epsilon. Note that epsilon should be strictly positive.

        noise: float, default=0.1
                During EM's initialization, the CPTs are randomly perturbed using the following formula:
                new_CPT = (1-noise) * CPT + noise * random_CPT. Parameter noise must belong
                to interval [0,1]. By default, noise is equal to 0.1.

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        Raises
        ------
        pyagrum.OutOfBounds
                if `epsilon` is not strictly positive or if `noise` does not belong to interval [0,1].

        """
        return _pyagrum.BNLearner_useEMWithRateCriterion(self, *args)

    def useEMWithDiffCriterion(self, *args) -> "pyagrum.BNLearner":
        r"""

        Enforces that EM with the log-likelihood min difference criterion will be used for
        parameter learning whenever the dataset contains missing values.

        Parameters
        ----------
        epsilon : float
             epsilon sets the approximation stopping criterion: EM stops whenever the
             difference between two consecutive log-likelihoods drops below
             epsilon. Note that epsilon should be strictly positive.

        noise: float (optional, default = 0.1)
                During EM's initialization, the CPTs are randomly perturbed using the following formula:
                new_CPT = (1-noise) * CPT + noise * random_CPT. Parameter noise must belong
                to interval [0,1]. By default, noise is equal to 0.1.

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        Raises
        ------
        pyagrum.OutOfBounds
                if epsilon is not strictly positive or if noise does not belong to interval [0,1].

        """
        return _pyagrum.BNLearner_useEMWithDiffCriterion(self, *args)

    def forbidEM(self) -> "pyagrum.BNLearner":
        r"""

        Forbids the use of EM for parameter learning.

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        """
        return _pyagrum.BNLearner_forbidEM(self)

    def EMsetEpsilon(self, eps: float) -> "pyagrum.BNLearner":
        r"""

        Enforces that the minimal difference between two consecutive log-likelihoods
        is chosen as a stopping criterion of the EM parameter learning algorithm and
        specifies the threshold on this criterion.

        Parameters
        ----------
        eps: float
                the log-likelihood difference below which EM stops its iterations

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        Raises
        ------
        pyagrum.OutOfBounds
                If eps <= 0.

        """
        return _pyagrum.BNLearner_EMsetEpsilon(self, eps)

    def EMdisableEpsilon(self) -> "pyagrum.BNLearner":
        r"""

        Disables the minimal difference between two consecutive log-likelihoods as a
        stopping criterion for the EM parameter learning algorithm.

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        """
        return _pyagrum.BNLearner_EMdisableEpsilon(self)

    def EMenableEpsilon(self) -> "pyagrum.BNLearner":
        r"""

        Enforces that the minimal difference between two consecutive log-likelihoods is
        a stopping criterion for the EM parameter learning algorithm.

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        Warnings
        --------
        Setting the min difference between two consecutive log-likelihoods as a stopping
        criterion disables the min log-likelihood evolution rate as a stopping criterion.

        """
        return _pyagrum.BNLearner_EMenableEpsilon(self)

    def EMsetMinEpsilonRate(self, rate: float) -> "pyagrum.BNLearner":
        r"""

        Enforces that the minimal log-likelihood's evolution rate is considered by the EM
        parameter learning algorithm as a stopping criterion.

        Parameters
        ----------
        rate: float
            the log-likelihood evolution rate below which EM stops its iterations

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        Raises
        ------
        pyagrum.OutOfBounds
            If rate <= 0.

        Warnings
        --------
        Setting this stopping criterion disables the min log-likelihod difference criterion
        (if this one was enabled).

        """
        return _pyagrum.BNLearner_EMsetMinEpsilonRate(self, rate)

    def EMdisableMinEpsilonRate(self) -> "pyagrum.BNLearner":
        r"""

        Disables the minimal log-likelihood's evolution rate as an EM parameter learning
        stopping criterion.

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        """
        return _pyagrum.BNLearner_EMdisableMinEpsilonRate(self)

    def EMenableMinEpsilonRate(self) -> "pyagrum.BNLearner":
        r"""

        Enables the minimal log-likelihood's evolution rate as an EM parameter learning
        stopping criterion.

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        Warnings
        --------
        Setting this stopping criterion disables the min log-likelihod difference criterion.

        """
        return _pyagrum.BNLearner_EMenableMinEpsilonRate(self)

    def EMsetMaxIter(self, max: int) -> "pyagrum.BNLearner":
        r"""

        Enforces a limit on the number of expectation/maximization steps performed by EM.

        Parameters
        ----------
        max : int
            the maximal number of iterations that EM is allowed to perform

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1.

        """
        return _pyagrum.BNLearner_EMsetMaxIter(self, max)

    def EMdisableMaxIter(self) -> "pyagrum.BNLearner":
        r"""

        Do not limit EM to perform a maximal number of iterations.

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        """
        return _pyagrum.BNLearner_EMdisableMaxIter(self)

    def EMenableMaxIter(self) -> "pyagrum.BNLearner":
        r"""

        Enables a limit on the number of iterations performed by EM. This number is
        equal to the last number specified with Method `EMsetMaxIter()`.
        See Method `EMMaxIter()` to get its current value.

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        """
        return _pyagrum.BNLearner_EMenableMaxIter(self)

    def EMsetMaxTime(self, timeout: float) -> "pyagrum.BNLearner":
        r"""

        Adds a constraint on the time that EM is allowed to run for learning
        parameters.

        Parameters
        ----------
        timeout : float
            the timeout in milliseconds

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        Raises
        ------
        pyagrum.OutOfBounds
                If timeout<=0.0

        """
        return _pyagrum.BNLearner_EMsetMaxTime(self, timeout)

    def EMdisableMaxTime(self) -> "pyagrum.BNLearner":
        r"""

        Allow EM to learn parameters for an infinite amount of time.

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        """
        return _pyagrum.BNLearner_EMdisableMaxTime(self)

    def EMenableMaxTime(self) -> "pyagrum.BNLearner":
        r"""

        Forbid EM to run more than a given amount of time.

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        """
        return _pyagrum.BNLearner_EMenableMaxTime(self)

    def EMsetPeriodSize(self, p: int) -> "pyagrum.BNLearner":
        r"""

        Set the period size for the EM stopping criterion check.

        Parameters
        ----------
        p : int
            number of iterations between two stopping criterion evaluations (must be ≥ 1)

        Raises
        ------
        pyagrum.OutOfBounds
            if p < 1

        """
        return _pyagrum.BNLearner_EMsetPeriodSize(self, p)

    def EMPeriodSize(self) -> int:
        r"""

        Return the period size used by the EM stopping criterion check.

        Returns
        -------
        int
            number of EM iterations between two stopping criterion evaluations

        """
        return _pyagrum.BNLearner_EMPeriodSize(self)

    def EMsetVerbosity(self, v: bool) -> "pyagrum.BNLearner":
        r"""

        Sets or unsets the verbosity of the EM parameter learning algorithm.

        Verbosity is necessary for keeping track of the history of the learning.
        See Method `EMHistory()`.

        Parameters
        ----------
        v : bool
            sets EM's verbose mode if and only if v = True.

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() methods.

        """
        return _pyagrum.BNLearner_EMsetVerbosity(self, v)

    def useScoreAIC(self) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use an AIC score.

        """
        return _pyagrum.BNLearner_useScoreAIC(self)

    def useScoreBD(self) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use a BD score.

        """
        return _pyagrum.BNLearner_useScoreBD(self)

    def useScoreBDeu(self) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use a BDeu score.

        """
        return _pyagrum.BNLearner_useScoreBDeu(self)

    def useScoreBIC(self) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use a BIC score.

        """
        return _pyagrum.BNLearner_useScoreBIC(self)

    def useScoreK2(self) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use a K2 score.

        """
        return _pyagrum.BNLearner_useScoreK2(self)

    def useScoreLog2Likelihood(self) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use a Log2Likelihood score.

        """
        return _pyagrum.BNLearner_useScoreLog2Likelihood(self)

    def useNoPrior(self) -> "pyagrum.BNLearner":
        r"""

        Use no prior.

        """
        return _pyagrum.BNLearner_useNoPrior(self)

    def useBDeuPrior(self, weight: float=1.0) -> "pyagrum.BNLearner":
        r"""

        The BDeu prior adds weight to all the cells of the counting tables.
        In other words, it adds weight rows in the database with equally probable
        values.

        Parameters
        ----------
        weight : float
            the prior weight

        """
        return _pyagrum.BNLearner_useBDeuPrior(self, weight)

    def useSmoothingPrior(self, weight: float=1) -> "pyagrum.BNLearner":
        r"""

        Use the prior smoothing.

        Parameters
        ----------
        weight : float
                pass in argument a weight if you wish to assign a weight to the smoothing, otherwise the current weight of the learner will be used.

        """
        return _pyagrum.BNLearner_useSmoothingPrior(self, weight)

    def useDirichletPrior(self, *args) -> "pyagrum.BNLearner":
        r"""

        Use the Dirichlet prior.

        Parameters
        ----------
        source : str|pyagrum.BayesNet
                the Dirichlet related source (filename of a database or a Bayesian network)
        weight : float (optional)
                the weight of the prior (the 'size' of the corresponding 'virtual database')

        """
        return _pyagrum.BNLearner_useDirichletPrior(self, *args)

    def useGreedyHillClimbing(self) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use a greedy hill climbing algorithm. This algorithm
        exploits all the three operators arc addition, arc deletion and arc reversal
        and only these operators.

        """
        return _pyagrum.BNLearner_useGreedyHillClimbing(self)

    def useExtendedGreedyHillClimbing(self) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use a greedy hill climbing algorithm, controlling the
        set of arc operations that this learning algorithm is allowed to exploit.
        Those are the traditional arc additions, arc deletions and arc reversals, plus
        two additional operators arc triangle deletion1 and arc triangle deletion2
        that are capable of transforming triangles into v-structures. To control which
        operations are allowed, see Methods allowArcAdditions, allowArcDeletions,
        allowArcReversals, allowArcTriangleDeletions.

        """
        return _pyagrum.BNLearner_useExtendedGreedyHillClimbing(self)

    def useGreedyThickThinning(self) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use the greedy thick-thinning algorithm.

        This two-phase algorithm first greedily adds arcs (thick phase) until no
        addition improves the score, then greedily removes arcs (thin phase) until no
        deletion improves the score. By default the thin phase uses only arc deletions;
        arc reversals can optionally be allowed via setGreedyThickThinningReversals.

        """
        return _pyagrum.BNLearner_useGreedyThickThinning(self)

    def setGreedyThickThinningReversals(self, allow: bool) -> "pyagrum.BNLearner":
        r"""

        Enable or disable arc reversals during the thin phase of greedy thick-thinning.

        Parameters
        ----------
        allow : bool
            If True, arc reversals are considered in addition to arc deletions during
            the thin phase (default: False).

        """
        return _pyagrum.BNLearner_setGreedyThickThinningReversals(self, allow)

    def greedyThickThinningReversals(self) -> bool:
        r"""

        Returns whether arc reversals are allowed in the thin phase of greedy thick-thinning.

        Returns
        -------
        bool
            True if arc reversals are enabled in the thin phase, False otherwise.

        """
        return _pyagrum.BNLearner_greedyThickThinningReversals(self)

    def useLocalSearchWithTabuList(self, tabu_size: int=100, nb_decrease: int=2) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use a local search with tabu list

        Parameters
        ----------
        tabu_size : int
                The size of the tabu list

        nb_decrease : int
                The max number of changes decreasing the score consecutively that we allow to apply

        """
        return _pyagrum.BNLearner_useLocalSearchWithTabuList(self, tabu_size, nb_decrease)

    def useMIIC(self) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use MIIC.

        """
        return _pyagrum.BNLearner_useMIIC(self)

    def usePC(self) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use the PC (Peter-Clark) algorithm.

        PC is a constraint-based structure learning algorithm. It learns a CPDAG
        (completed partially directed acyclic graph) from data via conditional
        independence tests. Three phases: skeleton discovery, v-structure orientation,
        and Meek rules propagation.

        After calling `usePC()`, configure the independence test with `useChi2Test()`
        (default) or `useG2Test()`, and optionally tune the significance threshold with
        `setPCAlpha()`.

        Returns
        -------
        pyagrum.BNLearner
            the BNLearner itself, to allow method chaining.

        """
        return _pyagrum.BNLearner_usePC(self)

    def useFCI(self) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use the FCI (Fast Causal Inference) algorithm.

        FCI is a constraint-based structure learning algorithm that, unlike PC, handles
        latent confounders and selection bias. It learns a PAG (Partial Ancestral Graph)
        from data via conditional independence tests. Use `learnPAG()` to retrieve the result.

        After calling `useFCI()`, configure the independence test with `useFCIChi2Test()`
        (default) or `useFCIG2Test()`, and tune the significance threshold with `setFCIAlpha()`.

        Returns
        -------
        pyagrum.BNLearner
            the BNLearner itself, to allow method chaining.

        """
        val = _pyagrum.BNLearner_useFCI(self)

        return self


        return val


    def useFCIChi2Test(self) -> "pyagrum.BNLearner":
        r"""

        Select the chi-squared (χ²) independence test for the FCI algorithm.

        This is the default test when `useFCI()` is called.

        Returns
        -------
        pyagrum.BNLearner
            the BNLearner itself, to allow method chaining.

        Raises
        ------
        pyagrum.OperationNotAllowed
            If FCI has not been selected (call `useFCI()` first).

        """
        val = _pyagrum.BNLearner_useFCIChi2Test(self)

        return self


        return val


    def useFCIG2Test(self) -> "pyagrum.BNLearner":
        r"""

        Select the G² (log-likelihood ratio) independence test for the FCI algorithm.

        Returns
        -------
        pyagrum.BNLearner
            the BNLearner itself, to allow method chaining.

        Raises
        ------
        pyagrum.OperationNotAllowed
            If FCI has not been selected (call `useFCI()` first).

        """
        val = _pyagrum.BNLearner_useFCIG2Test(self)

        return self


        return val


    def setFCIAlpha(self, alpha: float) -> "pyagrum.BNLearner":
        r"""

        Set the significance threshold (alpha) for independence tests used by FCI.

        Parameters
        ----------
        alpha : float
            significance level in (0, 1). Lower values produce sparser graphs.

        Returns
        -------
        pyagrum.BNLearner
            the BNLearner itself, to allow method chaining.

        Raises
        ------
        pyagrum.OperationNotAllowed
            If FCI has not been selected (call `useFCI()` first).

        """
        val = _pyagrum.BNLearner_setFCIAlpha(self, alpha)

        return self


        return val


    def setFCIMaxPathLength(self, max_len: int) -> "pyagrum.BNLearner":
        r"""

        Set the maximum conditioning set size for independence tests used by FCI.

        Parameters
        ----------
        max_len : int
            maximum path length (conditioning set size). Use -1 for unlimited.

        Returns
        -------
        pyagrum.BNLearner
            the BNLearner itself, to allow method chaining.

        Raises
        ------
        pyagrum.OperationNotAllowed
            If FCI has not been selected (call `useFCI()` first).

        """
        val = _pyagrum.BNLearner_setFCIMaxPathLength(self, max_len)

        return self


        return val


    def setFCIExhaustiveSepSet(self, exhaustive: bool) -> "pyagrum.BNLearner":
        r"""

        Enable or disable exhaustive separating-set mode for the FCI skeleton phase.

        In standard mode (default, ``False``) FCI stops at the first separating set found
        for each pair of nodes at each conditioning depth. In exhaustive mode (``True``) it
        tests all subsets of that depth and stores the *union* of all separating sets found.
        A wider separating set suppresses more collider orientations (conservative), at the
        cost of more independence tests.

        Parameters
        ----------
        exhaustive : bool
            ``True`` to enable exhaustive mode, ``False`` to restore standard mode.

        Returns
        -------
        pyagrum.BNLearner
            the BNLearner itself, to allow method chaining.

        Raises
        ------
        pyagrum.OperationNotAllowed
            If FCI has not been selected (call `useFCI()` first).

        """
        val = _pyagrum.BNLearner_setFCIExhaustiveSepSet(self, exhaustive)

        return self


        return val


    def fciExhaustiveSepSet(self) -> bool:
        r"""

        Return whether FCI exhaustive separating-set mode is enabled.

        Returns
        -------
        bool
            ``True`` if exhaustive sepset mode is on, ``False`` otherwise (default).

        Raises
        ------
        pyagrum.OperationNotAllowed
            If FCI has not been selected (call `useFCI()` first).

        """
        return _pyagrum.BNLearner_fciExhaustiveSepSet(self)

    def useChi2Test(self) -> "pyagrum.BNLearner":
        r"""

        Select the chi-squared (χ²) independence test for the PC algorithm.

        This is the default test when `usePC()` is called. The test measures
        whether two variables are conditionally independent given a set of
        conditioning variables by comparing observed and expected counts under
        the independence hypothesis.

        Returns
        -------
        pyagrum.BNLearner
            the BNLearner itself, to allow method chaining.

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the PC algorithm has not been selected (call `usePC()` first).

        """
        return _pyagrum.BNLearner_useChi2Test(self)

    def useG2Test(self) -> "pyagrum.BNLearner":
        r"""

        Select the G² (log-likelihood ratio) independence test for the PC algorithm.

        G² is an alternative to χ² based on the likelihood-ratio statistic.
        It is asymptotically equivalent to χ² but may perform better on sparse
        contingency tables.

        Returns
        -------
        pyagrum.BNLearner
            the BNLearner itself, to allow method chaining.

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the PC algorithm has not been selected (call `usePC()` first).

        """
        return _pyagrum.BNLearner_useG2Test(self)

    def setPCAlpha(self, alpha: float) -> "pyagrum.BNLearner":
        r"""

        Set the significance threshold (α) used by the PC algorithm to decide
        whether two variables are conditionally independent.

        An edge (X, Y) is removed from the skeleton when the p-value of the
        conditional independence test exceeds α. Smaller α makes PC more
        conservative (fewer edges removed); larger α makes it more aggressive.

        Parameters
        ----------
        alpha : float
            significance threshold in (0, 1). Default is 0.05.

        Returns
        -------
        pyagrum.BNLearner
            the BNLearner itself, to allow method chaining.

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the PC algorithm has not been selected (call `usePC()` first).

        """
        return _pyagrum.BNLearner_setPCAlpha(self, alpha)

    def setPCStable(self, stable: bool) -> "pyagrum.BNLearner":
        r"""

        Set whether to use the stable variant of the PC algorithm.

        In stable PC (default), the adjacency sets used to build conditioning
        subsets are frozen at the start of each depth level and edge removals
        are deferred until the end of that level. This makes the result
        order-independent within each depth level.

        In unstable PC (`stable=False`), edges are removed immediately and
        adjacency sets are updated on the fly, which may cause the result to
        depend on the order in which edges are processed.

        Parameters
        ----------
        stable : bool
            True (default) for stable PC, False for the original (unstable) PC.

        Returns
        -------
        pyagrum.BNLearner
            the BNLearner itself, to allow method chaining.

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the PC algorithm has not been selected (call `usePC()` first).

        """
        return _pyagrum.BNLearner_setPCStable(self, stable)

    def setPCMaxCondSetSize(self, max_k: int) -> "pyagrum.BNLearner":
        r"""

        Set the maximum size of conditioning sets considered by the PC algorithm.

        PC tests conditional independence of X and Y given subsets S of the
        neighbours of X (or Y). By default all subset sizes are considered
        (unlimited). Setting a limit k restricts tests to :math:`|S| \leq k`, which
        reduces runtime at the cost of possibly missing some independencies.

        Parameters
        ----------
        max_k : int
            maximum conditioning set size. Pass a very large integer (or leave
            unset) for no limit.

        Returns
        -------
        pyagrum.BNLearner
            the BNLearner itself, to allow method chaining.

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the PC algorithm has not been selected (call `usePC()` first).

        """
        return _pyagrum.BNLearner_setPCMaxCondSetSize(self, max_k)

    def setPCUnshieldedColliderSorted(self, sorted: bool) -> "pyagrum.BNLearner":
        r"""

        Set the ordering strategy used when orienting unshielded colliders in the PC algorithm.

        When ``sorted=True``, triples are processed in descending order of the p-value
        of the separating set test (strongest evidence of collider first). This can
        reduce orientation conflicts and improve reproducibility.

        When ``sorted=False`` (default), triples are processed in natural traversal order.

        Parameters
        ----------
        sorted : bool
            True to process unshielded colliders by descending p-value; False for natural order.

        Returns
        -------
        pyagrum.BNLearner
            the BNLearner itself, to allow method chaining.

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the PC algorithm has not been selected (call ``usePC()`` first).

        """
        return _pyagrum.BNLearner_setPCUnshieldedColliderSorted(self, sorted)

    def useNMLCorrection(self) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use the NML correction for MIIC

        """
        return _pyagrum.BNLearner_useNMLCorrection(self)

    def useMDLCorrection(self) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use the MDL correction for MIIC

        """
        return _pyagrum.BNLearner_useMDLCorrection(self)

    def useNoCorrection(self) -> "pyagrum.BNLearner":
        r"""

        Indicate that we wish to use the NoCorr correction for MIIC

        """
        return _pyagrum.BNLearner_useNoCorrection(self)

    def setMaxIndegree(self, max_indegree: int) -> "pyagrum.BNLearner":
        r"""

        Parameters
        ----------
        max_indegree : int
            the limit number of parents

        """
        return _pyagrum.BNLearner_setMaxIndegree(self, max_indegree)

    def setTotalOrder(self, *args) -> "pyagrum.BNLearner":
        r"""

        Sets a total order on a set of nodes (not necessarily all the nodes). In the
        case where only a proper subset of all the nodes is passed in argument, then
        the constraint applies only on the arcs for which both extremal nodes belong
        to the set.

        Parameters
        ----------
        l : list
                a list of row ids or strings (variable names)

        """
        return _pyagrum.BNLearner_setTotalOrder(self, *args)

    def addForbiddenArc(self, *args) -> "pyagrum.BNLearner":
        r"""

        Forbid the arc passed in argument to be added during structure learning
        (methods `learnDAG()` or `learnBN()`).

        Usage:
          1. addForbiddenArc(tail, head)
          2. addForbiddenArc(arc)

        Parameters
        ----------
        arc : pyagrum.Arc
            an arc
        head : int | str
            a variable's id or name
        tail : int | str
            a variable's id or name

        """
        return _pyagrum.BNLearner_addForbiddenArc(self, *args)

    def eraseForbiddenArc(self, *args) -> "pyagrum.BNLearner":
        r"""

        Allow the arc to be added if necessary.

        Parameters
        ----------
        arc: pyagrum.Arc
            an arc
        head : int | str
            a variable's id or name
        tail : int | str
            a variable's id or name

        """
        return _pyagrum.BNLearner_eraseForbiddenArc(self, *args)

    def addMandatoryArc(self, *args) -> "pyagrum.BNLearner":
        r"""

        Allow an arc to be added if necessary during structure learning
        (methods `learnDAG()` or `learnBN()`).

        Usage:
          1. addMandatoryArc(tail, head)
          2. addMandatoryArc(arc)

        Parameters
        ----------
        arc : pyagrum.Arc
            an arc
        head : int | str
            a variable's id or name
        tail : int | str
            a variable's id or name

        Raises
        ------
        pyagrum.InvalidDirectedCycle
            If the added arc creates a directed cycle in the DAG

        """
        return _pyagrum.BNLearner_addMandatoryArc(self, *args)

    def eraseMandatoryArc(self, *args) -> "pyagrum.BNLearner":
        r"""

        Parameters
        ----------
        arc: pyagrum.Arc
            an arc
        head : int | str
            a variable's id or name
        tail : int | str
            a variable's id or name

        """
        return _pyagrum.BNLearner_eraseMandatoryArc(self, *args)

    def addPossibleEdge(self, *args) -> "pyagrum.BNLearner":
        r"""

        assign a new possible edge

        Warnings
        --------
        By default, any edge is possible. However, once at least one possible edge is defined,
        all other edges not declared possible are considered as impossible.

        Parameters
        ----------
        arc : pyagrum.Arc
            an arc
        head : int | str
            a variable's id or name
        tail : int | str
            a variable's id or name

        """
        return _pyagrum.BNLearner_addPossibleEdge(self, *args)

    def erasePossibleEdge(self, *args) -> "pyagrum.BNLearner":
        r"""

        Allow the 2 arcs to be added if necessary.

        Parameters
        ----------
        arc : pyagrum.Arc
            an arc
        head : int | str
            a variable's id or name
        tail : int | str
            a variable's id or name

        """
        return _pyagrum.BNLearner_erasePossibleEdge(self, *args)

    def setPossibleSkeleton(self, skeleton: "pyagrum.UndiGraph") -> "pyagrum.BNLearner":
        r"""

        Add a constraint by fixing the set of possible edges as a pyagrum.UndiGraph.

        Parameters
        ----------
        g : pyagrum.UndiGraph
            the fixed skeleton

        """
        return _pyagrum.BNLearner_setPossibleSkeleton(self, skeleton)

    def addNoParentNode(self, *args) -> "pyagrum.BNLearner":
        r"""

        Add the constraint that this node cannot have any parent.

        Parameters
        ----------
        node : int | str
            a variable's id or name

        """
        return _pyagrum.BNLearner_addNoParentNode(self, *args)

    def eraseNoParentNode(self, *args) -> "pyagrum.BNLearner":
        r"""

        Remove the constraint that this node cannot have any parent.

        Parameters
        ----------
        node : int | str
            a variable's id or name

        """
        return _pyagrum.BNLearner_eraseNoParentNode(self, *args)

    def addNoChildrenNode(self, *args) -> "pyagrum.BNLearner":
        r"""

        Add to structure learning algorithms the constraint that this node cannot have any children.

        Parameters
        ----------
        node : int | str
            a variable's id or name

        """
        return _pyagrum.BNLearner_addNoChildrenNode(self, *args)

    def eraseNoChildrenNode(self, *args) -> "pyagrum.BNLearner":
        r"""

        Remove in structure learning algorithms the constraint that this node cannot have any children.

        Parameters
        ----------
        node : int | str
            a variable's id or name

        """
        return _pyagrum.BNLearner_eraseNoChildrenNode(self, *args)

    def allowArcAdditions(self, allow: bool) -> "pyagrum.BNLearner":
        r"""

        Sets whether ExtendedGreedyHillClimbing and LocalSearchWithTabuList are
        allowed to perform arc addition operations or not.

        Parameters
        ----------
        allow : bool
            arc additions allowed if and only if allow is equal to true

        Returns
        -------
        pyagrum.BNLearner
            the BNLearner itself, so that we can chain useXXX() and allowXXX() methods.

        """
        return _pyagrum.BNLearner_allowArcAdditions(self, allow)

    def allowArcDeletions(self, allow: bool) -> "pyagrum.BNLearner":
        r"""

        Sets whether ExtendedGreedyHillClimbing and LocalSearchWithTabuList are
        allowed to perform arc deletion operations or not.

        Parameters
        ----------
        allow : bool
            arc deletions allowed if and only if allow is equal to true

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() and allowXXX() methods.

        """
        return _pyagrum.BNLearner_allowArcDeletions(self, allow)

    def allowArcReversals(self, allow: bool) -> "pyagrum.BNLearner":
        r"""

        Sets whether ExtendedGreedyHillClimbing and LocalSearchWithTabuList are
        allowed to perform arc reveral operations (changing the direction of the arc) or not.

        Parameters
        ----------
        allow : bool
            arc reversals allowed if and only if allow is equal to true

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() and allowXXX() methods.

        """
        return _pyagrum.BNLearner_allowArcReversals(self, allow)

    def allowArcTriangleDeletions(self, allow: bool) -> "pyagrum.BNLearner":
        r"""

        Sets whether ExtendedGreedyHillClimbing and LocalSearchWithTabuList are
        allowed to perform arc triangle deletion operations or not. Arc triangle deletions
        consist of substituting triangles (node1 -> node2 -> node3 + node1 -> node3) into
        v-structures node2 -> node1 <- node3 or node1 -> node2 <- node3.

        Parameters
        ----------
        allow : bool
            arc triangle deletions allowed if and only if allow is equal to true

        Returns
        -------
        pyagrum.BNLearner
                the BNLearner itself, so that we can chain useXXX() and allowXXX() methods.

        """
        return _pyagrum.BNLearner_allowArcTriangleDeletions(self, allow)

    def isConstraintBased(self) -> bool:
        r"""

        Return wether the current learning method is constraint-based or not.

        Returns
        -------
        bool
            True if the current learning method is constraint-based.

        """
        return _pyagrum.BNLearner_isConstraintBased(self)

    def isScoreBased(self) -> bool:
        r"""

        Return wether the current learning method is score-based or not.

        Returns
        -------
        bool
            True if the current learning method is score-based.

        """
        return _pyagrum.BNLearner_isScoreBased(self)

    def __repr__(self) -> str:
        return _pyagrum.BNLearner___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.BNLearner___str__(self)

    def setVerbosity(self, v: bool) -> None:
        r"""

        Parameters
        ----------
        v : bool
                verbosity

        """
        return _pyagrum.BNLearner_setVerbosity(self, v)

    def setEpsilon(self, eps: float) -> None:
        r"""

        Parameters
        ----------
        eps : float
            the epsilon we want to use

        Raises
        ------
        pyagrum.OutOfBounds
            If eps<0

        """
        return _pyagrum.BNLearner_setEpsilon(self, eps)

    def setMinEpsilonRate(self, rate: float) -> None:
        r"""

        Parameters
        ----------
        rate : float
            the minimal epsilon rate

        """
        return _pyagrum.BNLearner_setMinEpsilonRate(self, rate)

    def setMaxIter(self, max: int) -> None:
        r"""

        Parameters
        ----------
        max : int
            the maximum number of iteration

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1

        """
        return _pyagrum.BNLearner_setMaxIter(self, max)

    def setMaxTime(self, timeout: float) -> None:
        r"""

        Parameters
        ----------
        tiemout : float
            stopping criterion on timeout (in seconds)

        Raises
        ------
        pyagrum.OutOfBounds
            If timeout<=0.0

        """
        return _pyagrum.BNLearner_setMaxTime(self, timeout)

    def setPeriodSize(self, p: int) -> None:
        r"""

        Parameters
        ----------
        p : int
            number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.BNLearner_setPeriodSize(self, p)

    def verbosity(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the verbosity is enabled

        """
        return _pyagrum.BNLearner_verbosity(self)

    def epsilon(self) -> float:
        r"""

        Returns
        -------
        float
            the value of epsilon

        """
        return _pyagrum.BNLearner_epsilon(self)

    def minEpsilonRate(self) -> float:
        r"""

        Returns
        -------
        float
            the value of the minimal epsilon rate

        """
        return _pyagrum.BNLearner_minEpsilonRate(self)

    def maxIter(self) -> int:
        r"""

        Returns
        -------
        int
            the criterion on number of iterations

        """
        return _pyagrum.BNLearner_maxIter(self)

    def maxTime(self) -> float:
        r"""

        Returns
        -------
        float
            the timeout(in seconds)

        """
        return _pyagrum.BNLearner_maxTime(self)

    def periodSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.BNLearner_periodSize(self)

    def nbrIterations(self) -> int:
        r"""

        Returns
        -------
        int
            the number of iterations

        """
        return _pyagrum.BNLearner_nbrIterations(self)

    def currentTime(self) -> float:
        r"""

        Returns
        -------
        float
            get the current running time in second (float)

        """
        return _pyagrum.BNLearner_currentTime(self)

    def messageApproximationScheme(self) -> str:
        r"""

        Returns
        -------
        str
            the approximation scheme message

        """
        return _pyagrum.BNLearner_messageApproximationScheme(self)

    def history(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple
            the scheme history

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the scheme did not performed or if verbosity is set to false

        """
        return _pyagrum.BNLearner_history(self)

    def _asIApproximationSchemeConfiguration(self) -> "pyagrum.YetUnWrapped":
        return _pyagrum.BNLearner__asIApproximationSchemeConfiguration(self)

    def learnDAG(self) -> "pyagrum.DAG":
        r"""

        learn a structure from a file

        Returns
        -------
        pyagrum.DAG
            the learned DAG

        """
        return _pyagrum.BNLearner_learnDAG(self)

    def learnPDAG(self) -> "pyagrum.PDAG":
        r"""

        learn a partially directed acyclic graph (PDAG) from the BNLearner's database

        Returns
        -------
        pyagrum.PDAG
            the learned PDAG

        Warnings
        --------
          The learning method must be constraint-based (MIIC, etc.) and not score-based
          (K2, GreedyHillClimbing, etc.)

        """
        return _pyagrum.BNLearner_learnPDAG(self)

    def learnPAG(self) -> "pyagrum.PAG":
        r"""

        Learn a Partial Ancestral Graph (PAG) from the BNLearner's database using FCI.

        Returns
        -------
        pyagrum.PAG
            the learned PAG

        Raises
        ------
        pyagrum.OperationNotAllowed
            If `useFCI()` has not been called first.

        See Also
        --------
        useFCI, setFCIAlpha, setFCIMaxPathLength

        """
        return _pyagrum.BNLearner_learnPAG(self)

    def names(self) -> tuple[str, ...]:
        r"""

        Returns
        -------
        tuple
                the names of the variables in the database

        """
        return _pyagrum.BNLearner_names(self)

    def idFromName(self, var_name: str) -> int:
        r"""

        Parameters
        ----------
        var_names : str
            a variable's name

        Returns
        -------
        int
            the column id corresponding to a variable name

        Raises
        ------
        pyagrum.MissingVariableInDatabase
            If a variable of the BN is not found in the database.

        """
        return _pyagrum.BNLearner_idFromName(self, var_name)

    def nameFromId(self, id: int) -> str:
        r"""

        Parameters
        ----------
        id : int
                a node id

        Returns
        -------
        str
                the variable's name

        """
        return _pyagrum.BNLearner_nameFromId(self, id)

    def setDatabaseWeight(self, new_weight: float) -> None:
        r"""

        Set the database weight which is given as an equivalent sample size.

        Warnings
        --------
        The same weight is assigned to all the rows of the learning database so that the sum of their
        weights is equal to the value of the parameter `weight`.

        Parameters
        ----------
        weight : float
                the database weight

        """
        return _pyagrum.BNLearner_setDatabaseWeight(self, new_weight)

    def setRecordWeight(self, i: int, weight: float) -> None:
        r"""

        Set the weight of the ith record

        Parameters
        ----------
        i : int
          the position  of the record in the database
        weight : float
          the weight assigned to this record

        Raises
        ------
        pyagrum.OutOfBounds
          if i is outside the set of indices of the records

        """
        return _pyagrum.BNLearner_setRecordWeight(self, i, weight)

    def databaseWeight(self) -> float:
        r"""

        Get the database weight which is given as an equivalent sample size.

        Returns
        -------
        float
          The weight of the database

        """
        return _pyagrum.BNLearner_databaseWeight(self)

    def recordWeight(self, i: int) -> float:
        r"""

        Get the weight of the ith record

        Parameters
        ----------
        i : int
          the position  of the record in the database

        Raises
        ------
        pyagrum.OutOfBounds
          if i is outside the set of indices of the records

        Returns
        -------
        float
          The weight of the ith record of the database

        """
        return _pyagrum.BNLearner_recordWeight(self, i)

    def hasMissingValues(self) -> bool:
        r"""

        Indicates whether there are missing values in the database.

        Returns
        -------
        bool
            True if there are some missing values in the database.

        """
        return _pyagrum.BNLearner_hasMissingValues(self)

    def logLikelihood(self, *args) -> float:
        r"""

        logLikelihood computes the log-likelihood for the columns in vars, given the columns in the list knowing (optional)


        Parameters
        ----------
        vars: list of str
                the name of the columns of interest

        knowing : list of str
                the (optional) list of names of conditioning columns

        Returns
        -------
        float
                the log-likelihood (base 2)

        """
        return _pyagrum.BNLearner_logLikelihood(self, *args)

    def score(self, *args) -> float:
        r"""

        Returns the value of the score currently in use by the BNLearner of a variable given a set of other variables

        Parameters
        ----------
        name1: str
            the name of the variable at the LHS of the conditioning bar

        knowing : list of str
            the list of names of the conditioning variables

        Returns
        -------
        float
            the value of the score

        """
        return _pyagrum.BNLearner_score(self, *args)

    def mutualInformation(self, *args) -> float:
        r"""

        computes the (log2) mutual information between two columns, given a list of other columns.

        Warnings
        --------
        This function gives the 'raw' mutual information. If you want a version taking into account correction and prior, use
        pyagrum.BNLearner.correctedMutualInformation

        Parameters
        ----------
        name1: str
                the name of the first column

        name2 : str
                the name of the second column

        knowing : list of str
                the list of names of conditioning columns

        Returns
        -------
        float
          the log2 mutual information

        """
        return _pyagrum.BNLearner_mutualInformation(self, *args)

    def correctedMutualInformation(self, *args) -> float:
        r"""

        computes the mutual information between two columns, given a list of other columns (log2).

        Warnings
        --------
        This function takes into account correction and prior. If you want the 'raw' mutual information, use
        pyagrum.BNLearner.mutualInformation


        Parameters
        ----------
        name1: str
                the name of the first column

        name2 : str
                the name of the second column

        knowing : list of str
                the list of names of conditioning columns

        Returns
        -------
        tuple
                the G2 statistic and the associated p-value as a Tuple

        """
        return _pyagrum.BNLearner_correctedMutualInformation(self, *args)

    def rawPseudoCount(self, *args) -> tuple[float, ...]:
        r"""

        computes the pseudoCount (taking priors into account) of the list of variables as a list of floats.


        Parameters
        ----------
        vars: list of int|str
                the list of variables

        Returns
        -------
        list of float
                the pseudo-count as a list of float

        """
        return _pyagrum.BNLearner_rawPseudoCount(self, *args)

    def nbRows(self) -> int:
        r"""

        Return the number of row in the database


        Returns
        -------
        int
                the number of rows in the database

        """
        return _pyagrum.BNLearner_nbRows(self)

    def nbCols(self) -> int:
        r"""

        Return the number of columns in the database


        Returns
        -------
        int
                the number of columns in the database

        """
        return _pyagrum.BNLearner_nbCols(self)

    def domainSize(self, *args) -> int:
        r"""

        Return the domain size of the variable with the given name.

        Parameters
        ----------
        n : str | int
          the name of the id of the variable

        """
        return _pyagrum.BNLearner_domainSize(self, *args)

    def isUsingEM(self) -> bool:
        r"""

        returns a Boolean indicating whether EM is used for parameter learning when
        the database contains missing values.

        """
        return _pyagrum.BNLearner_isUsingEM(self)

    def EMEpsilon(self) -> float:
        r"""

        Returns a float corresponding to the minimal difference between two consecutive
        log-likelihoods under which the EM parameter learning algorithm stops.

        Returns
        -------
        float
               the minimal difference between two consecutive log-likelihoods under which EM stops.

        """
        return _pyagrum.BNLearner_EMEpsilon(self)

    def EMisEnabledEpsilon(self) -> bool:
        r"""

        Returns a Boolean indicating whether the minimal difference between two consecutive
        log-likelihoods is a stopping criterion for the EM parameter learning algorithm.

        """
        return _pyagrum.BNLearner_EMisEnabledEpsilon(self)

    def EMMinEpsilonRate(self) -> float:
        r"""

        Returns a float corresponding to the minimal log-likelihood's evolution rate under
        which the EM parameter learning algorithm stops its iterations.

        Returns
        -------
        float
            the limit under which EM stops its expectation/maximization iterations

        """
        return _pyagrum.BNLearner_EMMinEpsilonRate(self)

    def EMisEnabledMinEpsilonRate(self) -> bool:
        r"""

        Returns a Boolean indicating whether the minimal log-likelihood's evolution rate is
        considered as a stopping criterion by the EM parameter learning algorithm.

        """
        return _pyagrum.BNLearner_EMisEnabledMinEpsilonRate(self)

    def EMMaxIter(self) -> int:
        r"""

        Returns an int containing the max number of iterations the EM parameter learning
        algorithm is allowed to perform when the max iterations stopping criterion is enabled.

        Returns
        -------
        int
            the max number of expectation/maximization iterations EM is allowed to perform

        """
        return _pyagrum.BNLearner_EMMaxIter(self)

    def EMisEnabledMaxIter(self) -> bool:
        r"""

        Returns a Boolean indicating whether the max number of iterations is used
        by EM as a stopping criterion.

        """
        return _pyagrum.BNLearner_EMisEnabledMaxIter(self)

    def EMMaxTime(self) -> float:
        r"""

        Returns a float indicating EM's time limit when the max time stopping
        criterion is used by the EM parameter learning algorithm.

        Returns
        -------
        float
                the max time EM is allowed to execute its expectation/maximization iterations

        """
        return _pyagrum.BNLearner_EMMaxTime(self)

    def EMisEnabledMaxTime(self) -> bool:
        r"""

        Returns a Boolean indicating whether the max time criterion is used as
        an EM stopping criterion.

        """
        return _pyagrum.BNLearner_EMisEnabledMaxTime(self)

    def EMVerbosity(self) -> bool:
        r"""

        Returns a Boolean indicating whether the EM parameter learning algorithm
        is in a verbose mode.

        Note that EM verbosity is necessary for recording the history of the
        log-likelihoods computed at each expectation/maximization step.

        Returns
        -------
        bool
                indicates whether EM's verbose mode is active or not

        """
        return _pyagrum.BNLearner_EMVerbosity(self)

    def EMnbrIterations(self) -> int:
        r"""

        Returns the number of iterations performed by the EM parameter learning algorithm.

        """
        return _pyagrum.BNLearner_EMnbrIterations(self)

    def EMHistory(self) -> tuple[float, ...]:
        r"""

        Returns a list containing the log-likelihoods recorded after each
        expectation/maximization iteration of the EM parameter learning algorithm.

        Returns
        -------
        list of float
                A list of all the log-likelihoods recorded during EM's execution

        Warnings
        --------
        Recording log-likelihoods is enabled only when EM is executed in verbose
        mode. See method `EMsetVerbosity()`.

        """
        return _pyagrum.BNLearner_EMHistory(self)

    def EMStateApproximationScheme(self) -> int:
        r"""

        Returns an int representing the state of the EM algorithm.

        The possible states are:
          0. Undefined
          1. Continue: EM has not been stopped yet
          2. Epsilon: EM has been stopped due to the min log-likelihood difference stopping criterion
          3. Rate: EM has been stopped due to the min log-likelihood evolution rate stopping criterion
          4. Limit: EM has been stopped due to a limit on the number of iterations
          5. TimeLimit: EM has been stopped due to a timeout
          6. Stopped: EM has been stopped because there was a divergence on the log-likelihood

        """
        return _pyagrum.BNLearner_EMStateApproximationScheme(self)

    def EMStateMessage(self) -> str:
        r"""

        Return a human-readable message describing the current state of the EM algorithm.

        Returns
        -------
        str
            state message (same information as EMStateApproximationScheme but as text)

        """
        return _pyagrum.BNLearner_EMStateMessage(self)

    def setNumberOfThreads(self, nb: int) -> None:
        r"""

        If the parameter n passed in argument is different from 0, the BNLearner will use n threads during learning, hence overriding pyagrum default number of threads.
        If, on the contrary, n is equal to 0, the BNLearner will comply with pyagrum default number of threads.

        Parameters
        ----------
        n : int
            the number of threads to be used by the BNLearner

        """
        return _pyagrum.BNLearner_setNumberOfThreads(self, nb)

    def getNumberOfThreads(self) -> int:
        r"""

        Return the number of threads used by the BNLearner during structure and parameter learning.

        Returns
        -------
        int
            the number of threads used by the BNLearner during structure and parameter learning

        """
        return _pyagrum.BNLearner_getNumberOfThreads(self)

    def isGumNumberOfThreadsOverriden(self) -> bool:
        r"""

        Check if the number of threads use by the learner is the default one or not.

        Returns
        -------
        bool
            True if the number of threads used by the BNLearner has been set.

        """
        return _pyagrum.BNLearner_isGumNumberOfThreadsOverriden(self)

    def chi2(self, *args) -> tuple[float, float]:
        r"""

        chi2 computes the chi2 statistic and p-value of two variables conditionally to a
        list of other variables.

        The variables correspond to columns in the database and are specified as the
        names of these columns in the database. The list of variables in the conditioning
        set can be empty. In this case, no need to specify it.

        Usage:
          * `chi2(name1, name2, knowing=[])`

        Parameters
        ----------
        name1: str
                the name of a variable/column in the database

        name2 : str
                the name/column of another variable

        knowing : list of str
                the list of the column names of the conditioning variables

        Returns
        -------
        tuple
                the chi2 statistics and the associated p-value as a Tuple

        """
        return _pyagrum.BNLearner_chi2(self, *args)

    def G2(self, *args) -> tuple[float, float]:
        r"""

        G2 computes the G2 statistic and p-value of two variables conditionally to a
        list of other variables.

        The variables correspond to columns in the database and are specified as the
        names of these columns in the database. The list of variables in the conditioning
        set can be empty. In this case, no need to specify it.

        Usage:
          * `G2(name1, name2, knowing=[])`

        Parameters
        ----------
        name1: str
                the name of a variable/column in the database

        name2 : str
                the name/column of another variable

        knowing : list of str
                the list of the column names of the conditioning variables

        Returns
        -------
        tuple
                the G2 statistics and the corresponding p-value as a Tuple

        """
        return _pyagrum.BNLearner_G2(self, *args)

    def _EM_warning(self) -> bool:
        return _pyagrum.BNLearner__EM_warning(self)

    def setSliceOrder(self, *args) -> "pyagrum.BNLearner":
        r"""

        Set a partial order on the nodes.

        Parameters
        ----------
        l : list
                a list of sequences (composed of ids of rows or string)

        """
        return _pyagrum.BNLearner_setSliceOrder(self, *args)

    def useK2(self, *args) -> "pyagrum.BNLearner":
        r"""

        Indicate to use the K2 algorithm (which needs a total ordering of the variables).

        Parameters
        ----------
        order : list[int or str]
              sequences of (ids or name)

        """
        return _pyagrum.BNLearner_useK2(self, *args)

    def latentVariables(self) -> list[tuple[int,int]]:
        r"""

        Returns the list of latent variables discovered by MIIC.

        Warnings
        --------
        learner must be using MIIC algorithm

        Returns
        -------
        list
            the list of latent variables

        """
        return _pyagrum.BNLearner_latentVariables(self)

    def state(self) -> dict[str, tuple[str, str]]:
        r"""

        Returns a dictionary containing the current state of the BNLearner.

        Returns
        -------
        dict
            a dictionary containing the current state of the BNLearner.

        """
        return _pyagrum.BNLearner_state(self)

    def setPossibleEdges(self, *args) -> None:
        r"""

        Adds a constraint to the structure learning algorithm by fixing the set of possible edges.

        Parameters
        ----------
        edges : set
            a set of edges as couples of nodeIds.

        """
        return _pyagrum.BNLearner_setPossibleEdges(self, *args)

    def pseudoCount(self,vars):
        """ access to pseudo-count (priors taken into account)

        Parameters
        ----------
        vars : list[str]
          a list of name of vars to add in the pseudo_count

        Returns
        -------
        a Tensor containing this pseudo-counts
        """
        p=pyagrum.Tensor()
        lv=list()
        for i in vars:
            if type(i) is str:
                name=i
            else:
                name=self.nameFromId(i)
            p.add(pyagrum.RangeVariable(name,name,0,self.domainSize(i)-1))
            lv.append(name)
        p.fillWith(self.rawPseudoCount(lv))
        return p

    def fitParameters(self, bn, take_into_account_score=True):
      """
      Fit the parameters of a BayesNet in place using this learner's database.

      Parameters
      ----------
      bn : pyagrum.BayesNet
          the network whose CPTs will be filled (structure must match the database variables)
      take_into_account_score : bool
          if True, include the implicit prior of the structure-learning score (e.g. K2) in parameter learning

      Returns
      -------
      pyagrum.BNLearner
          self
      """
      if not set(self.names()).issuperset(bn.names()):
        raise Exception(f"Some variables are in the BN but not in the data : {bn.names()-set(self.names())}")

      tmp=self.learnParameters(bn,take_into_account_score)
      for n in tmp.names():
        bn.cpt(bn.idFromName(n)).fillWith(tmp.cpt(n))
      return self

    def learnEssentialGraph(self):
      """
      learn an essential graph from a file

      Returns
      -------
      pyagrum.EssentialGraph
        the learned essential graph
      """
      bn = BayesNet()
      for i in range(len(self.names())):
        bn.add(self.nameFromId(i),2)
      try:
        ge = EssentialGraph(bn,self.learnPDAG()) # for constraint-based methods
      except:
        bn = self.learnBN()
        ge = EssentialGraph(bn)  # for score-based methods

      ge._bn=bn

      return ge


# Register BNLearner in _pyagrum:
_pyagrum.BNLearner_swigregister(BNLearner)

import warnings

def availableBNExts():
  """ Give the list of all formats known by pyAgrum to save a Bayesian network.

  :return: a string which lists all suffixes for supported BN file formats.
  """
  return "bif|dsl|net|bifxml|o3prm|uai|xdsl|pkl|jgum|bgum"


def loadBN(filename, listeners=None, verbose=False, **opts):
  """load a BN from a file with optional listeners and arguments

  Parameters
  ----------
  filename: str
      the name of the input file
  listeners: list[object]
      list of functions to execute when listening
  verbose: bool
      whether to print or not warning messages
  system: str
      (for O3PRM) name of the system to flatten in a BN
  classpath: list[str]
      (for O3PRM) list of folders containing classes

  Returns
  -------
  pyagrum.BayesNet
      a BN from a file using one of the availableBNExts() suffixes.

  Notes
  ----
      Listeners could be added in order to monitor its loading.

      pkl suffix is used to load a pickled BN. In this case, listeners and options are ignored.

      jgum/bgum suffixes use the native aGrUM JSON/binary format and also ignore listeners.

  Examples
  --------
  >>> import pyagrum as gum
  >>>
  >>> # creating listeners
  >>> def foo_listener(progress):
  >>>    if progress==200:
  >>>        print(' BN loaded ')
  >>>        return
  >>>    elif progress==100:
  >>>        car='%'
  >>>    elif progress%10==0:
  >>>        car='#'
  >>>    else:
  >>>        car='.'
  >>>    print(car,end='',flush=True)
  >>>
  >>> def bar_listener(progress):
  >>>    if progress==50:
  >>>        print('50%')
  >>>
  >>> # loadBN with list of listeners
  >>> pyagrum.loadBN('./bn.bif',listeners=[foo_listener,bar_listener])
  >>> # .........#.........#.........#.........#..50%
  >>> # .......#.........#.........#.........#.........#.........% | bn loaded
  """
  bn = BayesNet()

  warns = ""
  extension = filename.split('.')[-1].upper()
  if extension == "BIF":
    warns = bn.loadBIF(filename, listeners)
  elif extension == "BIFXML":
    warns = bn.loadBIFXML(filename, listeners)
  elif extension == "DSL":
    warns = bn.loadDSL(filename, listeners)
  elif extension == "XDSL":
    warns = bn.loadXDSL(filename, listeners)
  elif extension == "NET":
    warns = bn.loadNET(filename, listeners)
  elif extension == "O3PRM":
    warns = bn.loadO3PRM(filename, opts.get('system', ''),
                         opts.get('classpath', ''), listeners)
  elif extension == "UAI":
    warns = bn.loadUAI(filename, listeners)
  elif extension == "JGUM":
    bn.loadGUM(filename)
  elif extension == "BGUM":
    bn.loadGUM(filename, binary=True)
  elif extension == "PKL":
    bn = _gum_pickle_load(filename)
  else:
    raise InvalidArgument("extension " + filename.split('.')
    [-1] + " unknown. Please use among " + availableBNExts())

  if verbose:
    if len(warns) > 0:
      warnings.warn(warns)

  _gum_set_name_property(bn, filename)
  return bn



def saveBN(bn, filename, allowModificationWhenSaving=None):
  """
  save a BN into a file using the format corresponding to one of the availableWriteBNExts() suffixes.

  Parameters
  ----------
  bn : pyagrum.BayesNet
    the BN to save
  filename : str
    the name of the output file
  allowModificationWhenSaving: bool
      whether syntax errors in the BN should throw a FatalError or can be corrected. Also controlled by `pyagrum.config["BN","allow_modification_when_saving"]`.

  Notes
  ----
      pkl suffix is used to save a BN using pickle. In this case, options are ignored.

      jgum/bgum suffixes use the native aGrUM JSON/binary format and also ignore ``allowModificationWhenSaving``.
  """
  if allowModificationWhenSaving is None:
    allowModificationWhenSaving = pyagrum.config.typed["core", "allow_modification_when_saving"]

  extension = filename.split('.')[-1].upper()

  if extension == "BIF":
    bn.saveBIF(filename, allowModificationWhenSaving)
  elif extension == "BIFXML":
    bn.saveBIFXML(filename, allowModificationWhenSaving)
  elif extension == "DSL":
    bn.saveDSL(filename, allowModificationWhenSaving)
  elif extension == "XDSL":
    bn.saveXDSL(filename, allowModificationWhenSaving)
  elif extension == "NET":
    bn.saveNET(filename, allowModificationWhenSaving)
  elif extension == "UAI":
    bn.saveUAI(filename, allowModificationWhenSaving)
  elif extension == "O3PRM":
    bn.saveO3PRM(filename, allowModificationWhenSaving)
  elif extension == "JGUM":
    bn.saveGUM(filename)
  elif extension == "BGUM":
    bn.saveGUM(filename, binary=True)
  elif extension == "PKL":
    _gum_pickle_save(bn, filename)
  else:
    raise InvalidArgument("[pyAgrum] extension " + filename.split('.')
    [-1] + " unknown. Please use among " + availableBNExts())

def fastBN(structure:str, domain="[2]"):
  """
  Create a Bayesian network with a dot-like syntax which specifies:
      - the structure 'a->b->c;b->d<-e;',
      - the type of the variables with different syntax (cf documentation).

  Examples
  --------
  >>> import pyagrum as gum
  >>> bn=pyagrum.fastBN('A->B[1,3]<-C{yes|No}->D[2,4]<-E[1,2.5,3.9]',6)

  Parameters
  ----------
  structure : str
          the string containing the specification
  domain : int or str
          the default domain size (int) or domain specification (str) for variables (default is "[2]"

  Returns
  -------
  pyagrum.BayesNet
          the resulting bayesian network
  """
  return BayesNet.fastPrototype(structure, domain)


def generateSample(bn, n=1, name_out=None, show_progress=False, with_labels=True, random_order=True):
  """
  generate a CSV file of samples from a bn.

  Parameters
  ----------
  bn: pyagrum.BayesNet
    the Bayes Net from which the sample is generated
  n: int
    the number of samples
  name_out: str
    the name for the output csv filename. If name_out is None, a pandas.DataFrame is generated
  show_progress: bool
    if True, show a progress bar. Default is False
  with_labels: bool
    if True, use the labels of the modalities of variables in the csv. If False, use their ids. Default is True
  random_order: bool
    if True, the columns in the csv are randomized sorted. Default is True

  Returns
  -------
  float|tuple[pandas.DataFrame,float]
    the log2-likelihood of the generated base or if name_out is None, the couple (generated pandas.DataFrame,log2-likelihood)
  """
  genere = BNDatabaseGenerator(bn)
  if show_progress:
    from tqdm import tqdm
    pbar = tqdm(total=100, desc=name_out, bar_format='{desc}: {percentage:3.0f}%|{bar}|', ncols=60)
    listen = PythonDatabaseGeneratorListener(genere)

    def whenStep(x, y):
      pbar.update(1)

    def whenStop(msg):
      pbar.close()

    listen.setWhenProgress(whenStep)
    listen.setWhenStop(whenStop)

  if random_order:
    genere.setRandomVarOrder()
  ll = genere.drawSamples(n)

  if name_out is not None:
    genere.toCSV(name_out, with_labels)

  if show_progress:
    print(f"Log2-Likelihood : {ll}")

  if name_out is not None:
    return ll
  else:
    return genere.to_pandas(with_labels), ll


def randomBN(*, n: int = 5, names: list[str] = None, ratio_arc: float = 1.2, domain_size: int = 2) -> BayesNet:
  """
  Creates a random BN using the (forced) keyword parameters. This function use :class:`pyagrum.BNGenerator` but the random
  variables will be named w.r.t. a topological order.

  Warning
  -------
  Number of nodes given with arg `n`or `names` must be bigger than 4, in order to be consistant

  Examples
  --------
  >>> bn=pyagrum.randomBN()
  >>> bn=pyagrum.randomBN(n=10)
  >>> bn=pyagrum.randomBN(names="ABCDEF")
  >>> bn=pyagrum.randomBN(names=["Asia","Tuberculosis","Smoking"],ratio_arc=1.5,domain_size=3)

  Warnings
  --------
  This function has only keyword parameters (no positional).

  Parameters
  ----------
  n : int
      number of nodes
  names: list[str]
      list of names
  ratio_arc: float
      number of arcs = n * ratio_arc
  domain_size: int
      the domain size for the variables.

  Returns
  -------
    pyagrum.BayesNet
  """
  nbr = n if names is None else len(names)
  if nbr <= 3:
    raise ArgumentError("A BN can not be randomly generated from less than 4 nodes.")

  gen = BNGenerator()
  bn = gen.generate(nbr, int(nbr * ratio_arc), domain_size)

  if names is not None:
# try to find very rare name
    for i in bn.nodes():
      bn.changeVariableName(i, f"__{i}{i}__{i}{i}__")
    for i, nod in enumerate(bn.topologicalOrder()):
      bn.changeVariableName(nod, names[i])

  return bn


def mutilateBN(bn, intervention=None, observation=None):
  """
  Modify the bayesian network bn to reflect the effect of interventions and/or observations on a set of variables.
  Due to the causal nature of interventions, we suppose the given bn to have a causal interpretation.
  Warning: experimental use of evidence definition

  Interventions or observations can be HARD or SOFT.

    Hard interventions or observations:
        1) [0,... 1, 0] -> sum(x) = 1
        3) X : [n] -> with n a value

    Soft interventions or observations:
        1) X : [empty list] -> equiprobability is assumed
        2) X : [x1, ... xn] -> sum(x) = 1
        3) X : [1, ... 1, 0] -> sum(x) >= 1
        4) X : [n1, n2, n3] -> with n_i values that could happen

    X is the name of a variable

  Parameters
  ----------
  bn : pyagrum.pyagrum.BayesNet
    A bayesian network
  intervention : dict[str,list[str|float|int]]
    set of variables on which we intervene to force the value
  observation : dict[str,list[str|float|int]]
    set of variables whose value is observed

  Returns
  -------
  inter_bn : new bayesian network reflecting the interventions and observations (pyagrum.pyagrum.BayesNet)
  evidence : dictionary of all evidences for future inferences (dict)
  """
  if intervention is None:
    intervention = dict()

  if observation is None:
    observation = dict()

  inter_bn = BayesNet(bn)

# Check that a variable is not an intervention and an observation
  if len(set(intervention).intersection(set(observation))) > 0:
    raise ValueError('A variable can\'t be an intervention and an observation')

  evidence = dict()  # Track the new distribution to update
  list_hard = dict()  # Track the hard values
  toModify = {"intervention": intervention, "observation": observation}

## Delete relations
  for typeSet in toModify:

# For each variable we wish to modify
    for var in toModify[typeSet]:

# Get the ID and the name
      if var in bn.names():
        var_id = bn.idFromName(var)

      else:
        var_id = var
        var = bn.variable(var_id).name()

# Delete relations from parents for interventions
      if typeSet == "intervention":
        for par in bn.parents(var):
          inter_bn.eraseArc(par, var_id)

# Determine the new distributions
      n = bn.variable(var).domainSize()
      new_dis = toModify[typeSet][var]
      hard = False

      if len(new_dis) == 0:  # soft 1)
        new_dis = [1 / n for _ in range(n)]

      elif str in [type(i) for i in new_dis]:  # hard - soft 3) 4)
        new_dis = [1 if bn.variable(var).labels()[i] == new_dis[0] else 0 for i in range(n)]

        if len(toModify[typeSet][var]) == 1:
          new_val = toModify[typeSet][var][0]
          hard = True

      elif sum(new_dis) == 1 and 1 in new_dis:  # hard 1)
        new_val = bn.variable(var).labels()[new_dis.index(1)]
        hard = True

      evidence[var] = new_dis

# If hard values
      if hard:
# Track the new values
        list_hard[var] = new_val

# Delete relation toward children
        for chi in bn.children(var):
          inter_bn.eraseArc(var_id, chi)

## Update the distributions
  for var in list(evidence):

# Update variable if intervention
    if var in intervention:
      inter_bn.cpt(var).fillWith(evidence[var])

# Update children if hard evidence
    if var in list_hard:
      for chi in bn.children(var):
        new_cpt = bn.cpt(chi)[list_hard]

        inter_bn.cpt(chi)[:] = new_cpt

# If intervention, remove var
      if var in intervention:
        inter_bn.erase(var)
        del evidence[var]

  return (inter_bn, evidence)



class CredalNet(object):
    r"""

    Constructor used to create a CredalNet (step by step or with two BayesNet)

    CredalNet() -> CredalNet
        default constructor

    CredalNet(src_min_num,src_max_den) -> CredalNet

    Parameters
    ----------
    src_min_num : str or pyagrum.BayesNet
                The path to a BayesNet or the BN itself which contains lower probabilities.
    src_max_den : str or pyagrum.BayesNet
                The (optional) path to a BayesNet or the BN itself which contains upper probabilities.


    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    NodeType_Precise = _pyagrum.CredalNet_NodeType_Precise
    NodeType_Credal = _pyagrum.CredalNet_NodeType_Credal
    NodeType_Vacuous = _pyagrum.CredalNet_NodeType_Vacuous
    NodeType_Indic = _pyagrum.CredalNet_NodeType_Indic

    def __init__(self, *args):
        _pyagrum.CredalNet_swiginit(self, _pyagrum.new_CredalNet(*args))
    __swig_destroy__ = _pyagrum.delete_CredalNet

    def addVariable(self, name: str, card: int) -> int:
        r"""

        Parameters
        ----------
        name : str
            the name of the new variable
        card: int
            the domainSize of the new variable

        Returns
        -------
        int
            the id of the new node

        """
        return _pyagrum.CredalNet_addVariable(self, name, card)

    def addArc(self, tail: int, head: int) -> None:
        r"""

        Adds an arc between two nodes

        Parameters
        ----------
        tail :
            the id of the tail node
        head : int
            the id of the head node

        Raises
        ------
        pyagrum.InvalidDirectedCircle
            If any (directed) cycle is created by this arc
        pyagrum.InvalidNode
            If head or tail does not belong to the graph nodes
        pyagrum.DuplicateElement
            If one of the arc already exists

        """
        return _pyagrum.CredalNet_addArc(self, tail, head)

    def setCPTs(self, id: int, cpt: "pyagrum.YetUnWrapped") -> None:
        r"""

        Warnings
        --------
        (experimental function) - Parameters to be wrapped


        Set the vertices of the credal sets (all of the conditionals) of a given node

        Parameters
        ----------
        id : int
            the NodeId of the node
        cpt    : tbw
            the vertices of every credal set (for each instantiation of the parents)

        Warnings
        --------
        DOES not change the BayesNet (s) associated to this credal net !

        """
        return _pyagrum.CredalNet_setCPTs(self, id, cpt)

    def setCPT(self, *args) -> None:
        r"""

        Warnings
        --------
        (experimental function) - Parameters to be wrapped


        Set the vertices of one credal set of a given node (any instantiation index)

        Parameters
        ----------
        id : int
            the Id of the node
        entry : int
            the index of the instantiation (from 0 to K - 1) excluding the given node (only the parents are used to compute the index of the credal set)
        ins : pyagrum.Instantiation
            the Instantiation (only the parents matter to find the credal set index)
        cpt    : tbw
            the vertices of every credal set (for each instantiation of the parents)

        Warnings
        --------
        DOES not change the BayesNet(s) associated to this credal net !

        """
        return _pyagrum.CredalNet_setCPT(self, *args)

    def fillConstraints(self, id: int, lower: tuple[float, ...], upper: tuple[float, ...]) -> None:
        r"""

        Set the interval constraints of the credal sets of a given node (all instantiations)

        Parameters
        ----------
        id : int
            The id of the node
        lower : list
            The lower value for each probability in correct order
        upper : list
            The upper value for each probability in correct order

        Warnings
        --------
        You need to call intervalToCredal when done filling all constraints.

        Warnings
        --------
        DOES change the BayesNet (s) associated to this credal net !

        """
        return _pyagrum.CredalNet_fillConstraints(self, id, lower, upper)

    def fillConstraint(self, *args) -> None:
        r"""

        Set the interval constraints of a credal set of a given node (from an instantiation index)

        Parameters
        ----------
        id : int
            The id of the node
        entry : int
            The index of the instantiation excluding the given node (only the parents are used to compute the index of the credal set)
        ins : pyagrum.Instantiation
            The Instantiation
        lower : list
            The lower value for each probability in correct order
        upper : list
            The upper value for each probability in correct order

        Warnings
        --------
        You need to call intervalToCredal when done filling all constraints.

        Warnings
        --------
        DOES change the BayesNet (s) associated to this credal net !

        """
        return _pyagrum.CredalNet_fillConstraint(self, *args)

    def instantiation(self, id: int) -> "pyagrum.Instantiation":
        r"""

        Get an Instantiation from a node id, usefull to fill the constraints of the network.

        bnet accessors / shortcuts.

        Parameters
        ----------
        id : int
            the id of the node we want an instantiation from

        Returns
        -------
        pyagrum.Instantiation
            the instantiation

        """
        return _pyagrum.CredalNet_instantiation(self, id)

    def domainSize(self, id: int) -> int:
        r"""

        Parameters
        ----------
        id : int
            The id of the node

        Returns
        -------
        int
            The cardinality of the node

        """
        return _pyagrum.CredalNet_domainSize(self, id)

    def bnToCredal(self, *args) -> None:
        r"""

        Perturbates the BayesNet provided as input for this CredalNet by generating intervals instead of point probabilities and then computes each vertex of each credal set.

        Parameters
        ----------
        beta : float
            The beta used to perturbate the network
        oneNet : bool
            used as a flag. Set to True if one BayesNet if provided with counts, to False if two BayesNet are provided; one with probabilities (the lower net) and one with denominators over the first modalities (the upper net)
        keepZeroes : bool
            used as a flag as whether or not - respectively True or False - we keep zeroes as zeroes. Default is False, i.e. zeroes are not kept

        """
        return _pyagrum.CredalNet_bnToCredal(self, *args)

    def intervalToCredalWithFiles(self) -> None:
        r"""

        Warnings
        --------
        Deprecated : use intervalToCredal (lrsWrapper with no input / output files needed).


        Computes the vertices of each credal set according to their interval definition (uses lrs).

        Use this method when using a single BayesNet storing counts of events.

        """
        return _pyagrum.CredalNet_intervalToCredalWithFiles(self)

    def intervalToCredal(self) -> None:
        r"""

        Computes the vertices of each credal set according to their interval definition (uses lrs).

        Use this method when using two BayesNet, one with lower probabilities and one with upper probabilities.

        """
        return _pyagrum.CredalNet_intervalToCredal(self)

    def lagrangeNormalization(self) -> None:
        r"""

        Normalize counts of a BayesNet storing counts of each events such that no probability is 0.

        Use this method when using a single BayesNet storing counts of events. Lagrange normalization. This call is irreversible and modify counts stored by __src_bn.

        Doest not performs computations of the parameters but keeps normalized counts of events only. Call idmLearning to compute the probabilities (with any parameter value).

        """
        return _pyagrum.CredalNet_lagrangeNormalization(self)

    def idmLearning(self, *args) -> None:
        r"""

        Learns parameters from a BayesNet storing counts of events.

        Use this method when using a single BayesNet storing counts of events. IDM model if s > 0, standard point probability if s = 0 (default value if none precised).

        Parameters
        ----------
        s : int
            the IDM parameter.
        keepZeroes : bool
            used as a flag as whether or not - respectively True or False - we keep zeroes as zeroes. Default is False, i.e. zeroes are not kept.

        """
        return _pyagrum.CredalNet_idmLearning(self, *args)

    def approximatedBinarization(self) -> None:
        r"""

        Approximate binarization.

        Each bit has a lower and upper probability which is the lowest - resp. highest - over all vertices of the credal set. Enlarge the orignal credal sets and may induce huge imprecision.

        Warnings
        --------
        Enlarge the orignal credal sets and therefor induce huge imprecision by propagation. Not recommended, use MCSampling or something else instead

        """
        return _pyagrum.CredalNet_approximatedBinarization(self)

    def saveBNsMinMax(self, min_path: str, max_path: str) -> None:
        r"""

        If this CredalNet was built over a perturbed BayesNet, one can save the intervals as two BayesNet.

        to call after bnToCredal(GUM_SCALAR beta) save a BN with lower probabilities and a BN with upper ones

        Parameters
        ----------
        min_path : str
            the path to save the BayesNet which contains the lower probabilities of each node X.
        max_path : str
            the path to save the BayesNet which contains the upper probabilities of each node X.

        """
        return _pyagrum.CredalNet_saveBNsMinMax(self, min_path, max_path)

    def computeBinaryCPTMinMax(self) -> None:
        r"""

        Used with binary networks to speed-up L2U inference.

        Store the lower and upper probabilities of each node X over the 'True' modality.

        """
        return _pyagrum.CredalNet_computeBinaryCPTMinMax(self)

    def src_bn(self) -> "pyagrum.BayesNet":
        r"""

        Returns
        -------
        pyagrum.BayesNet
            Returns a constant reference to the original BayesNet (used as a DAG, it's CPTs does not matter).

        """
        return _pyagrum.CredalNet_src_bn(self)

    def current_bn(self) -> "pyagrum.BayesNet":
        r"""

        Returns
        -------
        pyagrum.BayesNet
            Returs a constant reference to the actual BayesNet (used as a DAG, it's CPTs does not matter).

        """
        return _pyagrum.CredalNet_current_bn(self)

    def credalNet_currentCpt(self) -> "pyagrum.YetUnWrapped":
        r"""

        Warnings
        --------
        Experimental function - Return type to be wrapped

        Returns
        -------
        tbw
            a constant reference to the (up-to-date) CredalNet CPTs.

        """
        return _pyagrum.CredalNet_credalNet_currentCpt(self)

    def credalNet_srcCpt(self) -> "pyagrum.YetUnWrapped":
        r"""

        Warnings
        --------
        Experimental function - Return type to be wrapped

        Returns
        -------
        tbw
            a constant reference to the (up-to-date) CredalNet CPTs.

        """
        return _pyagrum.CredalNet_credalNet_srcCpt(self)

    def currentNodeType(self, id: int) -> int:
        r"""

        Parameters
        ----------
        id : int
            The constant reference to the choosen NodeId

        Returns
        -------
        pyagrum.CredalNet
            the type of the choosen node in the (up-to-date) CredalNet __current_bn if any, __src_bn otherwise.

        """
        return _pyagrum.CredalNet_currentNodeType(self, id)

    def nodeType(self, id: int) -> int:
        r"""

        Parameters
        ----------
        id : int
            the constant reference to the choosen NodeId

        Returns
        -------
        pyagrum.CredalNet
            the type of the choosen node in the (up-to-date) CredalNet in __src_bn.

        """
        return _pyagrum.CredalNet_nodeType(self, id)

    def epsilonMin(self) -> float:
        r"""

        Returns
        -------
        float
            a constant reference to the lowest perturbation of the BayesNet provided as input for this CredalNet.

        """
        return _pyagrum.CredalNet_epsilonMin(self)

    def epsilonMax(self) -> float:
        r"""

        Returns
        -------
        float
            a constant reference to the highest perturbation of the BayesNet provided as input for this CredalNet.

        """
        return _pyagrum.CredalNet_epsilonMax(self)

    def epsilonMean(self) -> float:
        r"""

        Returns
        -------
        float
            a constant reference to the average perturbation of the BayesNet provided as input for this CredalNet.

        """
        return _pyagrum.CredalNet_epsilonMean(self)

    def isSeparatelySpecified(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if this CredalNet is separately and interval specified, False otherwise.

        """
        return _pyagrum.CredalNet_isSeparatelySpecified(self)

    def hasComputedBinaryCPTMinMax(self) -> bool:
        r"""

        Check whether the binary CPT min/max values have been computed.

        Returns
        -------
        bool
            True if binary CPT min/max have been computed (via the dedicated computation method)

        """
        return _pyagrum.CredalNet_hasComputedBinaryCPTMinMax(self)

    def get_binaryCPT_min(self) -> "pyagrum.YetUnWrapped":
        r"""

        Warnings
        --------
        Experimental function - Return type to be wrapped

        Returns
        -------
        tbw
            a constant reference to the lower probabilities of each node X over the 'True' modality

        """
        return _pyagrum.CredalNet_get_binaryCPT_min(self)

    def get_binaryCPT_max(self) -> "pyagrum.YetUnWrapped":
        r"""

        Warnings
        --------
        Experimental function - Return type to be wrapped

        Returns
        -------
        tbw
            a constant reference to the upper probabilities of each node X over the 'True' modality

        """
        return _pyagrum.CredalNet_get_binaryCPT_max(self)

    def __repr__(self) -> str:
        return _pyagrum.CredalNet___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.CredalNet___str__(self)

# Register CredalNet in _pyagrum:
_pyagrum.CredalNet_swigregister(CredalNet)
class CNMonteCarloSampling(object):
    r"""

    Class used for inferences in credal networks with Monte Carlo sampling algorithm.

    CNMonteCarloSampling(cn) -> CNMonteCarloSampling
        Parameters:
            - **cn** (*pyagrum.CredalNet*) -- a credal network

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, credalNet: "CredalNet"):
        _pyagrum.CNMonteCarloSampling_swiginit(self, _pyagrum.new_CNMonteCarloSampling(credalNet))

        self._model=credalNet



    __swig_destroy__ = _pyagrum.delete_CNMonteCarloSampling

    def makeInference(self) -> None:
        r"""

        Starts the inference.

        """
        return _pyagrum.CNMonteCarloSampling_makeInference(self)

    def insertEvidenceFile(self, path: str) -> None:
        r"""

        Insert evidence from file.

        Parameters
        ----------
        path : str
            the path to the evidence file.

        """
        return _pyagrum.CNMonteCarloSampling_insertEvidenceFile(self, path)

    def setVerbosity(self, v: bool) -> None:
        r"""

        Parameters
        ----------
        v : bool
                verbosity

        """
        return _pyagrum.CNMonteCarloSampling_setVerbosity(self, v)

    def setEpsilon(self, eps: float) -> None:
        r"""

        Parameters
        ----------
        eps : float
            the epsilon we want to use

        Raises
        ------
        pyagrum.OutOfBounds
            If eps<0

        """
        return _pyagrum.CNMonteCarloSampling_setEpsilon(self, eps)

    def setMinEpsilonRate(self, rate: float) -> None:
        r"""

        Parameters
        ----------
        rate : float
            the minimal epsilon rate

        """
        return _pyagrum.CNMonteCarloSampling_setMinEpsilonRate(self, rate)

    def setMaxIter(self, max: int) -> None:
        r"""

        Parameters
        ----------
        max : int
            the maximum number of iteration

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1

        """
        return _pyagrum.CNMonteCarloSampling_setMaxIter(self, max)

    def setMaxTime(self, timeout: float) -> None:
        r"""

        Parameters
        ----------
        tiemout : float
            stopping criterion on timeout (in seconds)

        Raises
        ------
        pyagrum.OutOfBounds
            If timeout<=0.0

        """
        return _pyagrum.CNMonteCarloSampling_setMaxTime(self, timeout)

    def setPeriodSize(self, p: int) -> None:
        r"""

        Parameters
        ----------
        p : int
            number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.CNMonteCarloSampling_setPeriodSize(self, p)

    def verbosity(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the verbosity is enabled

        """
        return _pyagrum.CNMonteCarloSampling_verbosity(self)

    def epsilon(self) -> float:
        r"""

        Returns
        -------
        float
            the value of epsilon

        """
        return _pyagrum.CNMonteCarloSampling_epsilon(self)

    def minEpsilonRate(self) -> float:
        r"""

        Returns
        -------
        float
            the value of the minimal epsilon rate

        """
        return _pyagrum.CNMonteCarloSampling_minEpsilonRate(self)

    def maxIter(self) -> int:
        r"""

        Returns
        -------
        int
            the criterion on number of iterations

        """
        return _pyagrum.CNMonteCarloSampling_maxIter(self)

    def maxTime(self) -> float:
        r"""

        Returns
        -------
        float
            the timeout(in seconds)

        """
        return _pyagrum.CNMonteCarloSampling_maxTime(self)

    def periodSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.CNMonteCarloSampling_periodSize(self)

    def nbrIterations(self) -> int:
        r"""

        Returns
        -------
        int
            the number of iterations

        """
        return _pyagrum.CNMonteCarloSampling_nbrIterations(self)

    def currentTime(self) -> float:
        r"""

        Returns
        -------
        float
            get the current running time in second (float)

        """
        return _pyagrum.CNMonteCarloSampling_currentTime(self)

    def messageApproximationScheme(self) -> str:
        r"""

        Returns
        -------
        str
            the approximation scheme message

        """
        return _pyagrum.CNMonteCarloSampling_messageApproximationScheme(self)

    def history(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple
            the scheme history

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the scheme did not performed or if verbosity is set to false

        """
        return _pyagrum.CNMonteCarloSampling_history(self)

    def _asIApproximationSchemeConfiguration(self) -> "pyagrum.YetUnWrapped":
        return _pyagrum.CNMonteCarloSampling__asIApproximationSchemeConfiguration(self)

    def setRepetitiveInd(self, flag: bool) -> None:
        r"""

        Parameters
        ----------
        flag : bool
            True if repetitive independence is to be used, false otherwise. Only usefull with dynamic networks.

        """
        return _pyagrum.CNMonteCarloSampling_setRepetitiveInd(self, flag)

    def marginalMax(self, *args) -> "pyagrum.Tensor":
        r"""

        Get the upper marginals of a given node id.

        Parameters
        ----------
        id : int
            the node id which upper marginals we want.
        varName : str
            the variable name which upper marginals we want.

        Returns
        -------
        list
            a constant reference to this node upper marginals.

        Raises
        ------
          pyagrum.IndexError
            If the node does not belong to the Credal network

        """
        return _pyagrum.CNMonteCarloSampling_marginalMax(self, *args)

    def marginalMin(self, *args) -> "pyagrum.Tensor":
        r"""

        Get the lower marginals of a given node id.

        Parameters
        ----------
        id : int
            the node id which lower marginals we want.
        varName : str
            the variable name which lower marginals we want.

        Returns
        -------
        list
            a constant reference to this node lower marginals.

        Raises
        ------
          pyagrum.IndexError
            If the node does not belong to the Credal network

        """
        return _pyagrum.CNMonteCarloSampling_marginalMin(self, *args)

    def insertModalsFile(self, path: str) -> None:
        r"""

        Insert variables modalities from file to compute expectations.

        Parameters
        ----------
        path : str
            The path to the modalities file.

        """
        return _pyagrum.CNMonteCarloSampling_insertModalsFile(self, path)

    def dynamicExpMax(self, varName: str) -> tuple[float, ...]:
        r"""

        Get the upper dynamic expectation of a given variable prefix.

        Parameters
        ----------
        varName : str
            the variable name prefix which upper expectation we want.

        Returns
        -------
        float
            a constant reference to the variable upper expectation over all time steps.

        """
        return _pyagrum.CNMonteCarloSampling_dynamicExpMax(self, varName)

    def dynamicExpMin(self, varName: str) -> tuple[float, ...]:
        r"""

        Get the lower dynamic expectation of a given variable prefix.

        Parameters
        ----------
        varName : str
            the variable name prefix which lower expectation we want.

        Returns
        -------
        float
            a constant reference to the variable lower expectation over all time steps.

        """
        return _pyagrum.CNMonteCarloSampling_dynamicExpMin(self, varName)

    def eraseAllEvidence(self) -> None:
        r"""

        Erase all inference related data to perform another one.

        You need to insert evidence again if needed but modalities are kept. You can insert new ones by using the appropriate method which will delete the old ones.

        """
        return _pyagrum.CNMonteCarloSampling_eraseAllEvidence(self)

    def addEvidence(self, *args) -> None:
        r"""

        Add evidence for a variable in the credal network.

        Parameters
        ----------
        id_or_name : int | str
            the node id or name
        val_or_label : int | str
            the value index or label for the evidence

        """
        return _pyagrum.CNMonteCarloSampling_addEvidence(self, *args)

    def CN(self) -> "pyagrum.CredalNet":
        r"""

        Return the credal network used by this inference engine.

        Returns
        -------
        pyagrum.CredalNet
            the credal network

        """
        return _pyagrum.CNMonteCarloSampling_CN(self)

    def setEvidence(self, evidces):
      """
      Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

      Parameters
      ----------
      evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
       a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

      Raises
      ------
      pyagrum.InvalidArgument
         If one value is not a value for the node
       pyagrum.InvalidArgument
         If the size of a value is different from the domain side of the node
       pyagrum.FatalError
         If one value is a vector of 0s
       pyagrum.UndefinedElement
         If one node does not belong to the Bayesian network
      """
      if isinstance(evidces, dict):
        self.eraseAllEvidence()
        for k,v in evidces.items():
          self.addEvidence(k,v)
        return
      elif isinstance(evidces, list):#should be a list of Tensor
        self.eraseAllEvidence()
        for p in evidces:
          self.addEvidence(p)
        return
      raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))


# Register CNMonteCarloSampling in _pyagrum:
_pyagrum.CNMonteCarloSampling_swigregister(CNMonteCarloSampling)
class CNLoopyPropagation(object):
    r"""

    Class used for inferences in credal networks with Loopy Propagation algorithm.

    CNLoopyPropagation(cn) -> CNLoopyPropagation
        Parameters:
          - **cn** (*pyagrum.CredalNet*) -- a Credal network

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr
    InferenceType_nodeToNeighbours = _pyagrum.CNLoopyPropagation_InferenceType_nodeToNeighbours
    InferenceType_ordered = _pyagrum.CNLoopyPropagation_InferenceType_ordered
    InferenceType_randomOrder = _pyagrum.CNLoopyPropagation_InferenceType_randomOrder

    def makeInference(self) -> None:
        r"""

        Starts the inference.

        """
        return _pyagrum.CNLoopyPropagation_makeInference(self)

    def insertEvidenceFile(self, path: str) -> None:
        r"""

        Insert evidence from file.

        Parameters
        ----------
        path : str
            the path to the evidence file.

        """
        return _pyagrum.CNLoopyPropagation_insertEvidenceFile(self, path)

    def inferenceType(self, *args) -> int:
        r"""

        Returns
        -------
        int
            the inference type

        """
        return _pyagrum.CNLoopyPropagation_inferenceType(self, *args)

    def saveInference(self, path: str) -> None:
        r"""

        Saves marginals.

        Parameters
        ----------
        path : str
            The path to the file to save marginals.

        """
        return _pyagrum.CNLoopyPropagation_saveInference(self, path)

    def __init__(self, credalNet: "CredalNet"):
        _pyagrum.CNLoopyPropagation_swiginit(self, _pyagrum.new_CNLoopyPropagation(credalNet))

        self._model=credalNet



    __swig_destroy__ = _pyagrum.delete_CNLoopyPropagation

    def setVerbosity(self, v: bool) -> None:
        r"""

        Parameters
        ----------
        v : bool
                verbosity

        """
        return _pyagrum.CNLoopyPropagation_setVerbosity(self, v)

    def setEpsilon(self, eps: float) -> None:
        r"""

        Parameters
        ----------
        eps : float
            the epsilon we want to use

        Raises
        ------
        pyagrum.OutOfBounds
            If eps<0

        """
        return _pyagrum.CNLoopyPropagation_setEpsilon(self, eps)

    def setMinEpsilonRate(self, rate: float) -> None:
        r"""

        Parameters
        ----------
        rate : float
            the minimal epsilon rate

        """
        return _pyagrum.CNLoopyPropagation_setMinEpsilonRate(self, rate)

    def setMaxIter(self, max: int) -> None:
        r"""

        Parameters
        ----------
        max : int
            the maximum number of iteration

        Raises
        ------
        pyagrum.OutOfBounds
            If max <= 1

        """
        return _pyagrum.CNLoopyPropagation_setMaxIter(self, max)

    def setMaxTime(self, timeout: float) -> None:
        r"""

        Parameters
        ----------
        tiemout : float
            stopping criterion on timeout (in seconds)

        Raises
        ------
        pyagrum.OutOfBounds
            If timeout<=0.0

        """
        return _pyagrum.CNLoopyPropagation_setMaxTime(self, timeout)

    def setPeriodSize(self, p: int) -> None:
        r"""

        Parameters
        ----------
        p : int
            number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.CNLoopyPropagation_setPeriodSize(self, p)

    def verbosity(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if the verbosity is enabled

        """
        return _pyagrum.CNLoopyPropagation_verbosity(self)

    def epsilon(self) -> float:
        r"""

        Returns
        -------
        float
            the value of epsilon

        """
        return _pyagrum.CNLoopyPropagation_epsilon(self)

    def minEpsilonRate(self) -> float:
        r"""

        Returns
        -------
        float
            the value of the minimal epsilon rate

        """
        return _pyagrum.CNLoopyPropagation_minEpsilonRate(self)

    def maxIter(self) -> int:
        r"""

        Returns
        -------
        int
            the criterion on number of iterations

        """
        return _pyagrum.CNLoopyPropagation_maxIter(self)

    def maxTime(self) -> float:
        r"""

        Returns
        -------
        float
            the timeout(in seconds)

        """
        return _pyagrum.CNLoopyPropagation_maxTime(self)

    def periodSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of samples between 2 stopping

        Raises
        ------
        pyagrum.OutOfBounds
            If p<1

        """
        return _pyagrum.CNLoopyPropagation_periodSize(self)

    def nbrIterations(self) -> int:
        r"""

        Returns
        -------
        int
            the number of iterations

        """
        return _pyagrum.CNLoopyPropagation_nbrIterations(self)

    def currentTime(self) -> float:
        r"""

        Returns
        -------
        float
            get the current running time in second (float)

        """
        return _pyagrum.CNLoopyPropagation_currentTime(self)

    def messageApproximationScheme(self) -> str:
        r"""

        Returns
        -------
        str
            the approximation scheme message

        """
        return _pyagrum.CNLoopyPropagation_messageApproximationScheme(self)

    def history(self) -> tuple[float, ...]:
        r"""

        Returns
        -------
        tuple
            the scheme history

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the scheme did not performed or if verbosity is set to false

        """
        return _pyagrum.CNLoopyPropagation_history(self)

    def _asIApproximationSchemeConfiguration(self) -> "pyagrum.YetUnWrapped":
        return _pyagrum.CNLoopyPropagation__asIApproximationSchemeConfiguration(self)

    def setRepetitiveInd(self, flag: bool) -> None:
        r"""

        Parameters
        ----------
        flag : bool
            True if repetitive independence is to be used, false otherwise. Only usefull with dynamic networks.

        """
        return _pyagrum.CNLoopyPropagation_setRepetitiveInd(self, flag)

    def marginalMax(self, *args) -> "pyagrum.Tensor":
        r"""

        Get the upper marginals of a given node id.

        Parameters
        ----------
        id : int
            the node id which upper marginals we want.
        varName : str
            the variable name which upper marginals we want.

        Returns
        -------
        list
            a constant reference to this node upper marginals.

        Raises
        ------
          pyagrum.IndexError
            If the node does not belong to the Credal network

        """
        return _pyagrum.CNLoopyPropagation_marginalMax(self, *args)

    def marginalMin(self, *args) -> "pyagrum.Tensor":
        r"""

        Get the lower marginals of a given node id.

        Parameters
        ----------
        id : int
            the node id which lower marginals we want.
        varName : str
            the variable name which lower marginals we want.

        Returns
        -------
        list
            a constant reference to this node lower marginals.

        Raises
        ------
          pyagrum.IndexError
            If the node does not belong to the Credal network

        """
        return _pyagrum.CNLoopyPropagation_marginalMin(self, *args)

    def insertModalsFile(self, path: str) -> None:
        r"""

        Insert variables modalities from file to compute expectations.

        Parameters
        ----------
        path : str
            The path to the modalities file.

        """
        return _pyagrum.CNLoopyPropagation_insertModalsFile(self, path)

    def dynamicExpMax(self, varName: str) -> tuple[float, ...]:
        r"""

        Get the upper dynamic expectation of a given variable prefix.

        Parameters
        ----------
        varName : str
            the variable name prefix which upper expectation we want.

        Returns
        -------
        float
            a constant reference to the variable upper expectation over all time steps.

        """
        return _pyagrum.CNLoopyPropagation_dynamicExpMax(self, varName)

    def dynamicExpMin(self, varName: str) -> tuple[float, ...]:
        r"""

        Get the lower dynamic expectation of a given variable prefix.

        Parameters
        ----------
        varName : str
            the variable name prefix which lower expectation we want.

        Returns
        -------
        float
            a constant reference to the variable lower expectation over all time steps.

        """
        return _pyagrum.CNLoopyPropagation_dynamicExpMin(self, varName)

    def eraseAllEvidence(self) -> None:
        r"""

        Erase all inference related data to perform another one.

        You need to insert evidence again if needed but modalities are kept. You can insert new ones by using the appropriate method which will delete the old ones.

        """
        return _pyagrum.CNLoopyPropagation_eraseAllEvidence(self)

    def addEvidence(self, *args) -> None:
        r"""

        Add evidence for a variable in the credal network.

        Parameters
        ----------
        id_or_name : int | str
            the node id or name
        val_or_label : int | str
            the value index or label for the evidence

        """
        return _pyagrum.CNLoopyPropagation_addEvidence(self, *args)

    def CN(self) -> "pyagrum.CredalNet":
        r"""

        Return the credal network used by this inference engine.

        Returns
        -------
        pyagrum.CredalNet
            the credal network

        """
        return _pyagrum.CNLoopyPropagation_CN(self)

    def setEvidence(self, evidces):
      """
      Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

      Parameters
      ----------
      evidces : dict[str,Union[int,str,list[float]]] or list[pyagrum.Tensor]
       a dict of "name:evidence" where name is a string (the name of the variable) and evidence is an integer (an index) or a string (a label) or a list of float (a likelihood).

      Raises
      ------
      pyagrum.InvalidArgument
         If one value is not a value for the node
       pyagrum.InvalidArgument
         If the size of a value is different from the domain side of the node
       pyagrum.FatalError
         If one value is a vector of 0s
       pyagrum.UndefinedElement
         If one node does not belong to the Bayesian network
      """
      if isinstance(evidces, dict):
        self.eraseAllEvidence()
        for k,v in evidces.items():
          self.addEvidence(k,v)
        return
      elif isinstance(evidces, list):#should be a list of Tensor
        self.eraseAllEvidence()
        for p in evidces:
          self.addEvidence(p)
        return
      raise TypeError("Parameter must be a dict or a list, not %s"%(type(evidces)))


# Register CNLoopyPropagation in _pyagrum:
_pyagrum.CNLoopyPropagation_swigregister(CNLoopyPropagation)
class IDGenerator(object):
    r"""

    IDGenerator is used to easily generate influence diagrams.

    IDGenerator() -> IDGenerator
        default constructor

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def generate(self, nbrNodes: int=10, arcDensity: float=0.2, chanceNodeDensity: float=0.8, utilityNodeDensity: float=0.1, max_modality: int=2) -> "pyagrum.InfluenceDiagram":
        r"""

        Generate a new influence diagram given the parameters.

        Parameters
        ----------
        nbrNodes : int
            the number of node
        arcDensity : float
            the density of arc (1 for a complete graph)
        chanceNodeDensity : float
            the density of chance node
        utilityNodeDensity : float
            the density of utility node
        max_modality : int
            the maximum value for modalities

        Returns
        -------
        pyagrum.InfluenceDiagram
            the generated influence diagram

        """
        return _pyagrum.IDGenerator_generate(self, nbrNodes, arcDensity, chanceNodeDensity, utilityNodeDensity, max_modality)

    def __init__(self):
        r"""

        IDGenerator is used to easily generate influence diagrams.

        IDGenerator() -> IDGenerator
            default constructor

        """
        _pyagrum.IDGenerator_swiginit(self, _pyagrum.new_IDGenerator())
    __swig_destroy__ = _pyagrum.delete_IDGenerator

# Register IDGenerator in _pyagrum:
_pyagrum.IDGenerator_swigregister(IDGenerator)
class InfluenceDiagram(DAGmodel):
    r"""

    InfluenceDiagram represents an Influence Diagram.

    InfluenceDiagram() -> InfluenceDiagram
        default constructor

    InfluenceDiagram(source) -> InfluenceDiagram
        Parameters:
            - **source** (*pyagrum.InfluenceDiagram*) -- the InfluenceDiagram to copy

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    @staticmethod
    def fastPrototype(*args) -> "pyagrum.InfluenceDiagram":
        r"""

        Create an Influence Diagram with a dot-like syntax which specifies:
            - the structure 'a->b<-c;b->d;c<-e;'.
            - a prefix for the type of node (chance/decision/utiliy nodes):

              - `a` : a chance node named 'a' (by default)
              - `$a` : a utility node named 'a'
              - `*a` : a decision node named 'a'

            - the type of the variables with different syntax as postfix:

              - by default, a variable is a pyagrum.RangeVariable using the default domain size (second argument)
              - with `'a[10]'`, the variable is a pyagrum.RangeVariable using 10 as domain size (from 0 to 9)
              - with `'a[3,7]'`, the variable is a pyagrum.RangeVariable using a domainSize from 3 to 7
              - with `'a[1,3.14,5,6.2]'`, the variable is a pyagrum.DiscretizedVariable using the given ticks (at least 3 values)
              - with `'a{top|middle|bottom}'`, the variable is a pyagrum.LabelizedVariable using the given labels.
              - with 'a{-1|5|0|3}', the variable is a pyagrum.IntegerVariable using the sorted given values.
              - with 'a{-0.5|5.01|0|3.1415}', the variable is a pyagrum.NumericalDiscreteVariable using the sorted given values.

        Notes
        -----
          - If the dot-like string contains such a specification more than once for a variable, the first specification will be used.
          - the tensors (probabilities, utilities) are randomly generated.
          - see also pyagrum.fastID.

        Examples
        --------
        >>> import pyagrum as gum
        >>> bn=pyagrum.fastID('A->B[1,3]<-*C{yes|No}->$D<-E[1,2.5,3.9]',6)

        Parameters
        ----------
        dotlike : str
                the string containing the specification
        domainSize :int or str
                the default domain size or the default domain for variables

        Returns
        -------
        pyagrum.InfluenceDiagram
                the resulting Influence Diagram

        """
        return _pyagrum.InfluenceDiagram_fastPrototype(*args)
    __swig_destroy__ = _pyagrum.delete_InfluenceDiagram

    def __init__(self, *args):
        _pyagrum.InfluenceDiagram_swiginit(self, _pyagrum.new_InfluenceDiagram(*args))

    def __eq__(self, other: "InfluenceDiagram") -> bool:
        return _pyagrum.InfluenceDiagram___eq__(self, other)

    def toDot(self) -> str:
        r"""

        Returns
        -------
        str
            a friendly display of the graph in DOT format

        """
        return _pyagrum.InfluenceDiagram_toDot(self)

    def clear(self) -> None:
        r"""

        Clear the influence diagram, removing all nodes and arcs.

        """
        return _pyagrum.InfluenceDiagram_clear(self)

    def cpt(self, *args) -> "pyagrum.Tensor":
        r"""

        Returns the CPT of a variable.

        Parameters
        ----------
        var : int | str
            a variable's id (int) or name

        Returns
        -------
        pyagrum.Tensor
            The variable's CPT.

        Raises
        ------
        pyagrum.NotFound
            If no variable's id matches varId.

        """
        return _pyagrum.InfluenceDiagram_cpt(self, *args)

    def utility(self, *args) -> "pyagrum.Tensor":
        r"""

        Parameters
        ----------
        var : int | str
            a variable's id (int) or name

        Returns
        -------
        pyagrum.Tensor
            the utility table of the node

        Raises
        ------
        pyagrum.IndexError
            If the InfluenceDiagram does not contain the variable

        """
        return _pyagrum.InfluenceDiagram_utility(self, *args)

    def isUtilityNode(self, *args) -> bool:
        r"""

        Parameters
        ----------
        varId : int
            the tested node id.

        Returns
        -------
        bool
            true if node is an utility node

        """
        return _pyagrum.InfluenceDiagram_isUtilityNode(self, *args)

    def isDecisionNode(self, *args) -> bool:
        r"""

        Parameters
        ----------
        varId : int
            the tested node id.

        Returns
        -------
        bool
            true if node is a decision node

        """
        return _pyagrum.InfluenceDiagram_isDecisionNode(self, *args)

    def isChanceNode(self, *args) -> bool:
        r"""

        Parameters
        ----------
        varId : int
            the tested node id.

        Returns
        -------
        bool
            true if node is a chance node

        """
        return _pyagrum.InfluenceDiagram_isChanceNode(self, *args)

    def utilityNodeSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of utility nodes

        """
        return _pyagrum.InfluenceDiagram_utilityNodeSize(self)

    def chanceNodeSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of chance nodes.

        """
        return _pyagrum.InfluenceDiagram_chanceNodeSize(self)

    def decisionNodeSize(self) -> int:
        r"""

        Returns
        -------
        int
            the number of decision nodes

        """
        return _pyagrum.InfluenceDiagram_decisionNodeSize(self)

    def add(self, *args) -> int:
        r"""

        Add a variable, it's associate node and it's CPT.

        The id of the new variable is automatically generated.

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            The variable added by copy that will be a chance node.
        descr: str
          the descr of the variable following :ref:`fast syntax<Quick specification of (randomly parameterized) graphical models>` extended for :func:`pyagrum.fastID`.
        nbr_mod_or_id : int
            if the first argument is `variable`, this set an optional fixed id for the node. If the first argument is `descr`, this gives the default number of modalities
            for the variable. Note that if a utility node is described in `descr`, this value is overriden by 1.

        Returns
        -------
        int
            the id of the added variable.

        Raises
        ------
          pyagrum.DuplicateElement
              If already used id or name.

        """
        return _pyagrum.InfluenceDiagram_add(self, *args)

    def addChanceNode(self, *args) -> int:
        r"""

        Add a chance variable, it's associate node and it's CPT.

        The id of the new variable is automatically generated.

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            the variable added by copy.
        id : int
            the chosen id. If 0, the NodeGraphPart will choose.

        Warnings
        --------
        give an id (not 0) should be reserved for rare and specific situations !!!

        Returns
        -------
        int
            the id of the added variable.

        Raises
        ------
        pyagrum.DuplicateElement
            If id(<>0) is already used

        """
        return _pyagrum.InfluenceDiagram_addChanceNode(self, *args)

    def addUtilityNode(self, *args) -> int:
        r"""

        Add a utility variable, it's associate node and it's UT.

        The id of the new variable is automatically generated.

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            the variable added by copy
        id : int
            the chosen id. If 0, the NodeGraphPart will choose

        Warnings
        --------
        give an id (not 0) should be reserved for rare and specific situations !!!

        Returns
        -------
        int
            the id of the added variable.

        Raises
        ------
        pyagrum.InvalidArgument
            If variable has more than one label
        pyagrum.DuplicateElement
            If id(<>0) is already used

        """
        return _pyagrum.InfluenceDiagram_addUtilityNode(self, *args)

    def addDecisionNode(self, *args) -> int:
        r"""

        Add a decision variable.

        The id of the new variable is automatically generated.

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            the variable added by copy.
        id : int
            the chosen id. If 0, the NodeGraphPart will choose.

        Warnings
        --------
        give an id (not 0) should be reserved for rare and specific situations !!!

        Returns
        -------
        int
            the id of the added variable.

        Raises
        ------
        pyagrum.DuplicateElement
            If id(<>0) is already used

        """
        return _pyagrum.InfluenceDiagram_addDecisionNode(self, *args)

    def erase(self, *args) -> None:
        r"""

        Erase a Variable from the network and remove the variable from all his childs.

        If no variable matches the id, then nothing is done.

        Parameters
        ----------
        id : int
            The id of the variable to erase.
        var :  int | str | pyagrum.DiscreteVariable
            a variable's id (int) or name or th reference on the variable to remove.

        """
        return _pyagrum.InfluenceDiagram_erase(self, *args)

    def changeVariableName(self, *args) -> None:
        r"""

        Parameters
        ----------
        var : int | str
            a variable's id (int) or name
        new_name : str
            the name of the variable

        Raises
        ------
        pyagrum.DuplicateLabel
            If this name already exists
        pyagrum.NotFound
            If no nodes matches id.

        """
        return _pyagrum.InfluenceDiagram_changeVariableName(self, *args)

    def addArc(self, *args) -> None:
        r"""

        Add an arc in the ID, and update diagram's tensor nodes cpt if necessary.

        Parameters
        ----------
        tail : int | str
            a variable's id (int) or name
        head : int | str
            a variable's id (int) or name

        Raises
        ------
          pyagrum.InvalidEdge
            If arc.tail and/or arc.head are not in the ID.
          pyagrum.InvalidEdge
            If tail is a utility node

        """
        return _pyagrum.InfluenceDiagram_addArc(self, *args)

    def eraseArc(self, *args) -> None:
        r"""

        Removes an arc in the ID, and update diagram's tensor nodes cpt if necessary.

        If (tail, head) doesn't exist, the nothing happens.

        Parameters
        ----------
        arc : pyagrum.Arc
            The arc to be removed whn calling eraseArc(arc)
        tail : int | str
            a variable's id (int) or name when calling eraseArc(tail,head)
        head : int | str
            a variable's id (int) or name when calling eraseArc(tail,head)

        """
        return _pyagrum.InfluenceDiagram_eraseArc(self, *args)

    def decisionOrderExists(self) -> bool:
        r"""

        Returns
        -------
        bool
            True if a directed path exist with all decision node

        """
        return _pyagrum.InfluenceDiagram_decisionOrderExists(self)

    def getDecisionGraph(self) -> "pyagrum.DAG":
        r"""

        Returns
        -------
        pyagrum.DAG
            the temporal Graph.

        """
        return _pyagrum.InfluenceDiagram_getDecisionGraph(self)

    def decisionOrder(self) -> list[int]:
        r"""

        Return the sequence of decision nodes in a valid decision order.

        Returns
        -------
        list[int]
            the ordered list of decision node ids

        Raises
        ------
        pyagrum.NotFound
            if no valid decision order exists

        """
        return _pyagrum.InfluenceDiagram_decisionOrder(self)

    def existsPathBetween(self, *args) -> bool:
        r"""

        Returns
        -------
        bool
            true if a path exists between two nodes.

        """
        return _pyagrum.InfluenceDiagram_existsPathBetween(self, *args)

    def beginTopologyTransformation(self) -> None:
        r"""

        Begin a sequence of structural modifications to the influence diagram.

        Structural changes are batched until endTopologyTransformation is called.

        """
        return _pyagrum.InfluenceDiagram_beginTopologyTransformation(self)

    def endTopologyTransformation(self) -> None:
        r"""

        End a sequence of structural modifications and recompute internal structures.

        Should be called after beginTopologyTransformation when all modifications are done.

        """
        return _pyagrum.InfluenceDiagram_endTopologyTransformation(self)

    def idFromName(self, name: str) -> int:
        r"""

        Return the node id of a variable given its name.

        Parameters
        ----------
        name : str
            the name of the variable

        Returns
        -------
        int
            the node id of the variable

        Raises
        ------
        pyagrum.NotFound
            if no variable with this name exists in the model

        """
        return _pyagrum.InfluenceDiagram_idFromName(self, name)

    def variable(self, *args) -> "pyagrum.DiscreteVariable":
        r"""

        Parameters
        ----------
        id : int
             the node id

        Returns
        -------
        pyagrum.DiscreteVariable
            a constant reference over a variabe given it's node id

        Raises
        ------
        pyagrum.NotFound
            If no variable's id matches the parameter

        """
        return _pyagrum.InfluenceDiagram_variable(self, *args)

    def nodeId(self, var: "pyagrum.DiscreteVariable") -> int:
        r"""

        Return the node id of a variable.

        Parameters
        ----------
        var : pyagrum.DiscreteVariable
            the variable

        Returns
        -------
        int
            the node id of the variable

        Raises
        ------
        pyagrum.NotFound
            if the variable does not exist in the model

        """
        return _pyagrum.InfluenceDiagram_nodeId(self, var)

    def variableFromName(self, name: str) -> "pyagrum.DiscreteVariable":
        r"""

        Return the variable with the given name.

        Parameters
        ----------
        name : str
            the name of the variable

        Returns
        -------
        pyagrum.DiscreteVariable
            the variable

        Raises
        ------
        pyagrum.NotFound
            if no variable with this name exists in the model

        """
        return _pyagrum.InfluenceDiagram_variableFromName(self, name)

    def names(self) -> set[str]:
        r"""

        Returns
        -------
        list of str
            The names of the InfluenceDiagram variables

        """
        return _pyagrum.InfluenceDiagram_names(self)

    def __iter__(self):
      """
      Iterate over the variables of the model

      Yield
      -----
      tuple[int,str]
        The index of the variable and its name
      """
      for i in self.nodes():
        yield i,self.variable(i).name()

    def __getitem__(self, key):
      if isinstance(key, int):
        return self.variable(key)
      elif isinstance(key, str):
        return self.variableFromName(key)
      else:
        raise TypeError("key must be an int or a string")


    def nodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
            the set of ids

        """
        return _pyagrum.InfluenceDiagram_nodes(self)

    def connectedComponentsList(self):
      """ connected components as a dict of sets

      Returns
      -------
      dict(int, set[int])
        dict of connected components (as sets of nodeIds) keyed by an arbitrary root nodeId per component.

      """
      cc = self.connectedComponents()
      result = {}
      for node, root in cc.items():
        if root not in result:
          result[root] = set()
        result[root].add(node)
      return result

    def connectedComponentsCount(self):
      """ number of connected components

      Returns
      -------
      int
        the number of connected components in the graph.

      """
      return len(set(self.connectedComponents().values()))

    def adjacencyMatrix(self):
      """ adjacency matrix from a graph/graphical models

      Compute the adjacency matrix of a pyAgrum's graph or graphical models
      (more generally an object that has `nodes`, `children`/`parents` or `neighbours` methods)

      Returns
      -------
      numpy.ndarray
        adjacency matrix (as numpy.ndarray) with nodeId as key.

      """
      nodes=self.nodes()
      n=self.size()
      am=numpy.zeros((n,n)).astype(int)

      for node in nodes:
          if hasattr(self,'children'):
              for children in self.children(node):
                  am[node,children]=1
          if hasattr(self,'neighbours'):
              for neighbour in self.neighbours(node):
                  am[node,neighbour]=1
      return am


    def arcs(self) -> set[tuple[int,int]]:
        r"""

        Returns
        -------
        list:
            the list of all the arcs in the Influence Diagram.

        """
        return _pyagrum.InfluenceDiagram_arcs(self)

    def parents(self, norid: object) -> set[int]:
        r"""

        Parameters
        ----------
        var : int | str
            a variable's id (int) or name

        Returns
        -------
        set
            the set of the parents ids.

        """
        return _pyagrum.InfluenceDiagram_parents(self, norid)

    def children(self, norid: object) -> set[int]:
        r"""

        Parameters
        ----------
        var : int | str
            a variable's id (int) or name

        Returns
        -------
        Set
            the set of all the children

        """
        return _pyagrum.InfluenceDiagram_children(self, norid)

    def family(self, norid: object) -> set[int]:
        r"""

        give the set of parents of a node and the node

        Parameters
        ----------
        norid : str|int
          the node

        Returns
        -------
        set
          the set of nodeId of the family of the node `norid`

        """
        return _pyagrum.InfluenceDiagram_family(self, norid)

    def descendants(self, norid: object) -> set[int]:
        r"""

        give the set of nodeid of descendants of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the descendants of node `norid`.

        """
        return _pyagrum.InfluenceDiagram_descendants(self, norid)

    def ancestors(self, norid: object) -> set[int]:
        r"""

        give the set of nodeid of ancestors of a node

        Parameters
        ----------
        norid : str|int
          the name or the id of the node

        Returns
        -------
        set
          the set of ids of the ancestors of node `norid`.

        """
        return _pyagrum.InfluenceDiagram_ancestors(self, norid)

    def moralizedAncestralGraph(self, nodes: object) -> "pyagrum.UndiGraph":
        r"""

        build a UndiGraph by moralizing the Ancestral Graph of a list of nodes

        Parameters
        ----------
        nodes : str|int|list of str|int
          the list of of nodeIds or names

        Warnings
        --------
          pyagrum.UndiGraph only knows NodeId. Hence the moralized ancestral graph does not include the names of the variables.graph

        Returns
        -------
        pyagrum.UndiGraph
          the moralized ancestral graph of the nodes

        """
        return _pyagrum.InfluenceDiagram_moralizedAncestralGraph(self, nodes)

    def loadGUM(self, name: str, binary: bool=False) -> None:
        r"""

        Load a jgum (JSON) or bgum (binary/msgpack) file.

        Parameters
        ----------
        name : str
            the file's path (extension: ``.jgum`` for JSON, ``.bgum`` for binary)
        binary : bool
            if True, read as bgum (msgpack) regardless of extension (default: False)

        Raises
        ------
        pyagrum.IOError
            If file not found
        pyagrum.FatalError
            If file content is not valid

        See Also
        --------
        :ref:`jgum-bgum-format` : complete format reference

        """
        return _pyagrum.InfluenceDiagram_loadGUM(self, name, binary)

    def _saveGUM(self, name: str, binary: bool=False, indent: int=2) -> None:
        r"""

        Save the InfluenceDiagram in a jgum (JSON) or bgum (binary/msgpack) file.

        Metadata properties (``software``, ``creation``, ``lastModification``) are updated automatically.

        Parameters
        ----------
        name : str
            the file's path
        binary : bool
            if True, write as bgum (msgpack); otherwise write as jgum (JSON) (default: False)
        indent : int
            indentation level for JSON output; -1 for compact, 2 for pretty-printed (default: 2)

        See Also
        --------
        :ref:`jgum-bgum-format` : complete format reference

        """
        return _pyagrum.InfluenceDiagram__saveGUM(self, name, binary, indent)

    def _saveGUMstring(self, indent: int=2) -> str:
        r"""

        Serialize the InfluenceDiagram to a jgum JSON string.

        Metadata properties (``software``, ``creation``, ``lastModification``) are updated automatically.

        Parameters
        ----------
        indent : int
            indentation level; -1 for compact, 2 for pretty-printed (default: 2)

        Returns
        -------
        str
            a JSON string representing the InfluenceDiagram in jgum format

        See Also
        --------
        :ref:`jgum-bgum-format` : complete format reference

        """
        return _pyagrum.InfluenceDiagram__saveGUMstring(self, indent)

    def loadGUMstring(self, content: str) -> None:
        r"""

        Deserialize an InfluenceDiagram from a jgum JSON string.

        Parameters
        ----------
        content : str
            a JSON string in jgum format

        Raises
        ------
        pyagrum.FatalError
            If the string is not valid jgum JSON or the type field does not match ``"ID"``

        See Also
        --------
        :ref:`jgum-bgum-format` : complete format reference

        """
        return _pyagrum.InfluenceDiagram_loadGUMstring(self, content)

    def loadBIFXML(self, *args) -> bool:
        r"""

        Load a BIFXML file.

        Parameters
        ----------
        name : str
            the name's file

        Raises
        ------
        pyagrum.IOError
            If file not found
        pyagrum.FatalError
            If file is not valid

        """
        return _pyagrum.InfluenceDiagram_loadBIFXML(self, *args)

    def saveBIFXML(self, name: str) -> None:
        r"""

        Save the BayesNet in a BIFXML file.

        Parameters
        ----------
        name : str
            the file's name

        """
        return _pyagrum.InfluenceDiagram_saveBIFXML(self, name)

    def addVariables(self,listFastVariables,default_nbr_mod=2):
       """
       Add a list of variable in the form of 'fast' syntax.

       Parameters
       ----------
       listFastVariables: list[str]
         the list of variables following :ref:`fast syntax<Quick specification of (randomly parameterized) graphical models>` extended for :func:`pyagrum.fastID`.
       default_nbr_mod: int
         the number of modalities for the variable if not specified in the fast description. Note that default_nbr_mod=1 is
         mandatory to create variables with only one modality (for utility for instance).

       Returns
       -------
       list[int]
         the list of created ids.
       """
       return [self.add(descr,default_nbr_mod) for descr in listFastVariables]

    def addArcs(self,listArcs):
      """
      add a list of arcs in te model.

      Parameters
      ----------
      listArcs : list[tuple[int,int]]
        the list of arcs
      """
      for arc in listArcs:
        self.addArc(*arc)


    def addStructureListener(self,whenNodeAdded=None,whenNodeDeleted=None,whenArcAdded=None,whenArcDeleted=None):
      """
      Add the listeners in parameters to the list of existing ones.

      Parameters
      ----------
      whenNodeAdded : lambda expression
        a function for when a node is added
      whenNodeDeleted : lambda expression
        a function for when a node is removed
      whenArcAdded : lambda expression
        a function for when an arc is added
      whenArcDeleted : lambda expression
        a function for when an arc is removed
      """
      if [whenNodeAdded,whenNodeDeleted,whenArcAdded,whenArcDeleted]==[None,None,None,None]:
        return

      if not hasattr(self,"_listeners"):
        self._listeners=[]

      nl = PythonBNListener(self, self.variableNodeMap())
      if whenNodeAdded is not None:
        nl.setWhenNodeAdded(whenNodeAdded)
      if whenNodeDeleted is not None:
        nl.setWhenNodeDeleted(whenNodeDeleted)
      if whenArcAdded is not None:
        nl.setWhenArcAdded(whenArcAdded)
      if whenArcDeleted is not None:
        nl.setWhenArcDeleted(whenArcDeleted)

      self._listeners.append(nl)


    def __getstate__(self):
        return self.saveGUMstring()

    def __setstate__(self,state):
        self.__init__()
        self.loadGUMstring(state)
        return self


    def saveGUM(self, name: str, binary: bool = False, indent: int = None) -> None:
      """
      Save the influence diagram as a .jgum (text) or .bgum (binary) file.

      Parameters
      ----------
      name : str
        the name of the file
      binary : bool
        if True, save in the msgpack-based .bgum binary format instead of the JSON-based .jgum text format
      indent : int, optional
        JSON indentation for the .jgum text format (ignored if binary=True): -1 for the most
        compact output, N>=0 to pretty-print with N spaces. Defaults to
        ``pyagrum.config["core","default_jgumIndent"]``.
      """
      if indent is None:
        indent = pyagrum.config.typed["core", "default_jgumIndent"]
      self._saveGUM(name, binary, indent)


    def saveGUMstring(self, indent: int = None) -> str:
      """
      Save the influence diagram as a jgum string.

      Parameters
      ----------
      indent : int, optional
        JSON indentation: -1 for the most compact output, N>=0 to pretty-print with N spaces.
        Defaults to ``pyagrum.config["core","default_jgumIndent"]``.

      Returns
      -------
      str
        the jgum string
      """
      if indent is None:
        indent = pyagrum.config.typed["core", "default_jgumIndent"]
      return self._saveGUMstring(indent)


    def toFast(self, filename: str = None) -> str:
      """
      Export the influence Diagram as *fast* syntax (in a string or in a python file)

      Parameters
      ----------
      filename : Optional[str]
        the name of the file (including the prefix), if None , use sys.stdout
      """
      def _toFastVar(model,i):
        res=""
        if model.isUtilityNode(i):
          res="$"
        elif model.isDecisionNode(i):
          res="*"
        return res+model.variable(i).toFast()

      def _toFastBN(model,pythoncode=False):
        res = []
        sovars = set()
        for x, y in model.arcs():
          if x in sovars:
            src = model.variable(x).name()
          else:
            src = _toFastVar(model,x)
            sovars.add(x)
          if y in sovars:
            dst = model.variable(y).name()
          else:
             dst = _toFastVar(model,y)
             sovars.add(y)
          res.append(f"{src}->{dst}")

        for x in model.nodes():
          if x not in sovars:
             res .append(_toFastVar(model,x))

        if pythoncode:
          return 'model=pyagrum.fastID("""'+';\n     '.join(res)+'""")'
        else:
          return ';'.join(res)

      if filename is None:
        return _toFastBN(self)
      else:
        with open(filename, "w") as pyfile:
          print(_toFastBN(self,pythoncode=True), file=pyfile)


    def __repr__(self) -> str:
        return _pyagrum.InfluenceDiagram___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.InfluenceDiagram___str__(self)

# Register InfluenceDiagram in _pyagrum:
_pyagrum.InfluenceDiagram_swigregister(InfluenceDiagram)
class ShaferShenoyLIMIDInference(object):
    r"""

    This inference considers the provided model as a LIMID rather than an influence diagram. It is an optimized
    implementation of the LIMID resolution algorithm. However an inference on a classical influence diagram can be performed
    by adding a assumption of the existence of the sequence of decision nodes to be solved, which also implies that the
    decision choices can have an impact on the rest of the sequence (Non Forgetting Assumption,
    cf. pyagrum.ShaferShenoyLIMIDInference.addNoForgettingAssumption).

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, infDiag: "InfluenceDiagram"):
        _pyagrum.ShaferShenoyLIMIDInference_swiginit(self, _pyagrum.new_ShaferShenoyLIMIDInference(infDiag))

        self._model=infDiag



    __swig_destroy__ = _pyagrum.delete_ShaferShenoyLIMIDInference

    def junctionTree(self) -> "pyagrum.JunctionTree":
        r"""

        Returns
        -------
        pyagrum.CliqueGraph
          the current junction tree

        """
        val = _pyagrum.ShaferShenoyLIMIDInference_junctionTree(self)

        val._engine=self


        return val


    def clear(self) -> None:
        r"""

        Clear all evidence and reset the inference engine to its initial state.

        """
        return _pyagrum.ShaferShenoyLIMIDInference_clear(self)

    def addNoForgettingAssumption(self, *args) -> None:
        r"""

        Add a no-forgetting assumption for a list of decision variables.

        The no-forgetting assumption means that each decision node has access to all variables observed before it in the decision order.

        Parameters
        ----------
        ids : list[int]
            the list of decision node ids

        Raises
        ------
        pyagrum.InvalidArgument
            if a given id is not a decision node

        """
        return _pyagrum.ShaferShenoyLIMIDInference_addNoForgettingAssumption(self, *args)

    def hasNoForgettingAssumption(self) -> bool:
        r"""

        Check whether the no-forgetting assumption has been added.

        Returns
        -------
        bool
            True if the no-forgetting assumption is currently active

        """
        return _pyagrum.ShaferShenoyLIMIDInference_hasNoForgettingAssumption(self)

    def reducedGraph(self) -> "pyagrum.DAG":
        r"""

        Returns the DAG build to solve the influence diagram.

        Returns
        -------
        pyagrum.DAG
          a copy of the reduced graph


        """
        return _pyagrum.ShaferShenoyLIMIDInference_reducedGraph(self)

    def reversePartialOrder(self) -> "pyagrum.YetUnWrapped":
        r"""

        Return the reversed partial order of decision nodes used internally.

        Returns
        -------
        list[set[int]]
            the reversed partial order as a list of node id sets

        """
        return _pyagrum.ShaferShenoyLIMIDInference_reversePartialOrder(self)

    def reducedLIMID(self) -> "pyagrum.InfluenceDiagram":
        r"""

        Returns the (reduced) LIMID build to solve the influence diagram.

        Returns
        -------
        pyagrum.InfluenceDiagram
          a copy of the reduced influence Diagram (LIMID)


        """
        return _pyagrum.ShaferShenoyLIMIDInference_reducedLIMID(self)

    def isSolvable(self) -> bool:
        r"""

        check wether the influence diagram is solvable or not

        Returns
        -------
        bool
          True if the influence diagram is solvable

        """
        return _pyagrum.ShaferShenoyLIMIDInference_isSolvable(self)

    def optimalDecision(self, *args) -> "pyagrum.Tensor":
        r"""

        Returns best choice for decision variable given in parameter ( based upon MEU criteria )

        Parameters
        ----------
        decisionId : int,str
            the id or name of the decision variable

        Raises
        ------
          pyagrum.OperationNotAllowed
            If no inference have yet been made
        pyagrum.InvalidNode
            If node given in parmaeter is not a decision node

        """
        return _pyagrum.ShaferShenoyLIMIDInference_optimalDecision(self, *args)

    def posteriorUtility(self, *args) -> "pyagrum.Tensor":
        r"""

        Returns the posterior utiliyt of a utility node (after optimisation) depending on decision nodes, if any.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior utility of the utility node


        """
        return _pyagrum.ShaferShenoyLIMIDInference_posteriorUtility(self, *args)

    def makeInference(self) -> None:
        r"""

        Perform the heavy computations needed to compute the optimal decisions.

        """
        return _pyagrum.ShaferShenoyLIMIDInference_makeInference(self)

    def posterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Returns the posterior of a chance or a decision node (after optimisation).

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior probability of the node

        """
        return _pyagrum.ShaferShenoyLIMIDInference_posterior(self, *args)

    def addEvidence(self, *args) -> None:
        r"""

        Adds a new evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val :
          (int) a node value
        val :
          (str) the label of the node value
        vals : list
          a list of values

        Raises
        ------
          pyagrum.InvalidArgument
            If the node already has an evidence
          pyagrum.InvalidArgument
            If val is not a value for the node
          pyagrum.InvalidArgument
            If the size of vals is different from the domain side of the node
          pyagrum.FatalError
            If vals is a vector of 0s
          pyagrum.UndefinedElement
            If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyLIMIDInference_addEvidence(self, *args)

    def chgEvidence(self, *args) -> None:
        r"""

        Change the value of an already existing evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val : int|str
          a node value or the label of the node value
        vals : list of float
          a list of values

        Raises
        ------
        pyagrum.InvalidArgument
          If the node does not already have an evidence
        pyagrum.InvalidArgument
          If val is not a value for the node
        pyagrum.InvalidArgument
          If the size of vals is different from the domain side of the node
        pyagrum.FatalError
          If vals is a vector of 0s
        pyagrum.UndefinedElement
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyLIMIDInference_chgEvidence(self, *args)

    def hasEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if some node(s) (or the one in parameters) have received evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyLIMIDInference_hasEvidence(self, *args)

    def eraseAllEvidence(self) -> None:
        r"""

        Remove all evidence.

        """
        return _pyagrum.ShaferShenoyLIMIDInference_eraseAllEvidence(self)

    def eraseEvidence(self, *args) -> None:
        r"""

        Remove the evidence, if any, corresponding to the node Id or name.

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyLIMIDInference_eraseEvidence(self, *args)

    def hasHardEvidence(self, nodeName: str) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a hard evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyLIMIDInference_hasHardEvidence(self, nodeName)

    def hasSoftEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a soft evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyLIMIDInference_hasSoftEvidence(self, *args)

    def nbrEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of evidence entered into the Bayesian network

        """
        return _pyagrum.ShaferShenoyLIMIDInference_nbrEvidence(self)

    def nbrHardEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of hard evidence entered into the Bayesian network

        """
        return _pyagrum.ShaferShenoyLIMIDInference_nbrHardEvidence(self)

    def nbrSoftEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of soft evidence entered into the Bayesian network

        """
        return _pyagrum.ShaferShenoyLIMIDInference_nbrSoftEvidence(self)

    def influenceDiagram(self) -> "pyagrum.InfluenceDiagram":
        r"""

        Returns a constant reference over the InfluenceDiagram on which this class work.

        Returns
        -------
        pyagrum.InfluenceDiagram
            the InfluenceDiagram on which this class work

        """
        return _pyagrum.ShaferShenoyLIMIDInference_influenceDiagram(self)

    def setEvidence(self, evidces):
        """
        Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

        Parameters
        ----------
        evidces : dict
          a dict of evidences

        Raises
        ------
          pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the influence diagram
        """
        if not isinstance(evidces, dict):
            raise TypeError("setEvidence parameter must be a dict, not %s"%(type(evidces)))
        self.eraseAllEvidence()
        for k,v in evidces.items():
            self.addEvidence(k,v)



    def updateEvidence(self, evidces):
        """
        Apply chgEvidence(key,value) for every pairs in evidces (or addEvidence).

        Parameters
        ----------
        evidces : dict
          a dict of evidences

        Raises
        ------
          pyagrum.InvalidArgument
            If one value is not a value for the node
          pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
          pyagrum.FatalError
            If one value is a vector of 0s
          pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if not isinstance(evidces, dict):
            raise TypeError("setEvidence parameter must be a dict, not %s"%(type(evidces)))

        for k,v in evidces.items():
            if self.hasEvidence(k):
                self.chgEvidence(k,v)
            else:
                self.addEvidence(k,v)



    def hardEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with hard evidence

        """
        return _pyagrum.ShaferShenoyLIMIDInference_hardEvidenceNodes(self)

    def softEvidenceNodes(self) -> set[int]:
        r"""

        Return the set of nodes with soft (likelihood) evidence.

        Returns
        -------
        set[int]
            the set of node ids with soft evidence

        """
        return _pyagrum.ShaferShenoyLIMIDInference_softEvidenceNodes(self)

    def MEU(self, *args) -> dict[str, float]:
        r"""

        Returns maximum expected utility obtained from inference.

        Raises
        ------
        pyagrum.OperationNotAllowed
            If no inference have yet been made

        """
        return _pyagrum.ShaferShenoyLIMIDInference_MEU(self, *args)

    def meanVar(self, *args) -> dict[str, float]:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        dict[str, float]
          a dictionary with the mean and variance of the node (after the inference)

        """
        return _pyagrum.ShaferShenoyLIMIDInference_meanVar(self, *args)

# Register ShaferShenoyLIMIDInference in _pyagrum:
_pyagrum.ShaferShenoyLIMIDInference_swigregister(ShaferShenoyLIMIDInference)
############################################################################
#   This file is part of the aGrUM/pyAgrum library.                        #
#                                                                          #
#   Copyright (c) 2005-2026 by                                             #
#       - Pierre-Henri WUILLEMIN(_at_LIP6)                                 #
#       - Christophe GONZALES(_at_AMU)                                     #
#                                                                          #
#   The aGrUM/pyAgrum library is free software; you can redistribute it    #
#   and/or modify it under the terms of either :                           #
#                                                                          #
#    - 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,                              #
#    - the MIT license (MIT),                                              #
#    - or both in dual license, as here.                                   #
#                                                                          #
#   (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html)    #
#                                                                          #
#   This aGrUM/pyAgrum library is distributed in the hope that it will be  #
#   useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,          #
#   INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS #
#   FOR A PARTICULAR PURPOSE  AND NONINFRINGEMENT. IN NO EVENT SHALL THE   #
#   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
#   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,        #
#   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR  #
#   OTHER DEALINGS IN THE SOFTWARE.                                        #
#                                                                          #
#   See LICENCES for more details.                                         #
#                                                                          #
#   SPDX-FileCopyrightText: Copyright 2005-2026                            #
#       - Pierre-Henri WUILLEMIN(_at_LIP6)                                 #
#       - Christophe GONZALES(_at_AMU)                                     #
#   SPDX-License-Identifier: LGPL-3.0-or-later OR MIT                      #
#                                                                          #
#   Contact  : info_at_agrum_dot_org                                       #
#   homepage : http://agrum.gitlab.io                                      #
#   gitlab   : https://gitlab.com/agrumery/agrum                           #
#                                                                          #
############################################################################

__version__ = '3.1.1'
__license__ = __doc__
__project_url__ = 'https://agrum.org'
__project_name__ = 'pyAgrum'
__project_description__ = __doc__
__project__ = __doc__


def about():
  """
  about() for pyAgrum

  """
  print(f"pyAgrum {__version__}")
  print("(c) 2015-2025 Pierre-Henri Wuillemin, Christophe Gonzales")
  print("""
    This is free software; see the source code for copying conditions.
    There is ABSOLUTELY NO WARRANTY; not even for MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.
    """)


def availableIDExts():
  """ Give the list of all formats known by pyAgrum to save a influence diagram.

  Returns
  ------
  str
    a string which lists all suffixes for supported ID file formats.
  """
  return "xmlbif|bifxml|xml|jgum|bgum|pkl"


def loadID(filename):
  """
  read a pyagrum.InfluenceDiagram from a ID file

  Parameters
  ----------
  filename: str
    the name of the input file

  Returns
  -------
  pyagrum.InfluenceDiagram
    the InfluenceDiagram

  Notes
  -----
  pkl suffix is used to load a pickled InfluenceDiagram. In this case, options are ignored.

  jgum/bgum suffixes use the native aGrUM JSON/binary format.
  """
  extension = filename.split('.')[-1].upper()

  if extension in {"BIFXML", "XMLBIF", "XML"}:
    diag = pyagrum.InfluenceDiagram()
# for now, just one format
    res = diag.loadBIFXML(filename)

    if not res:
      raise IOError(f"Error(s) in {filename}")
  elif extension == "JGUM":
    diag = pyagrum.InfluenceDiagram()
    diag.loadGUM(filename)
  elif extension == "BGUM":
    diag = pyagrum.InfluenceDiagram()
    diag.loadGUM(filename, binary=True)
  elif extension == "PKL":
    diag = _gum_pickle_load(filename)
  else:
    raise InvalidArgument("extension " + filename.split('.')[-1] + " unknown. Please use among " + availableIDExts())

  _gum_set_name_property(diag, filename)
  return diag


def saveID(infdiag, filename):
  """
  save an ID into a file using the format corresponding to one of the availableWriteIDExts() suffixes.

  Parameters
  ----------
  infdiag : pyagrum.InfluenceDiagram
    the Influence Diagram to save
  filename : str
    the name of the output file

  Notes
  -----
  pkl suffix is used to save an InfluenceDiagram using pickle.

  jgum/bgum suffixes use the native aGrUM JSON/binary format.
  """
  extension = filename.split('.')[-1].upper()

  if extension in {"BIFXML", "XMLBIF", "XML"}:
    infdiag.saveBIFXML(filename)
  elif extension == "JGUM":
    infdiag.saveGUM(filename)
  elif extension == "BGUM":
    infdiag.saveGUM(filename, binary=True)
  elif extension == "PKL":
    _gum_pickle_save(infdiag, filename)
  else:
    raise InvalidArgument("extension " + filename.split('.')[-1] + " unknown. Please use among " + availableIDExts())


def fastID(structure, domain="[2]"):
  """
  Create an Influence Diagram with a modified dot-like syntax which specifies:
      - the structure and the type of the variables following :ref:`fast syntax<Quick specification of (randomly parameterized) graphical models>`,
      - a prefix for the type of node (chance/decision/utiliy nodes):

        - ``a`` : a chance node named 'a' (by default)
        - ``$a`` : a utility node named 'a'
        - ``*a`` : a decision node named 'a'

  Examples
  --------
  >>> import pyagrum as gum
  >>> bn=pyagrum.fastID('A->B[1,3]<-*C{yes|No}->$D<-E[1,2.5,3.9]',6)

  Parameters
  ----------
  structure : str
          the string containing the specification
  domain: int or str
          the default domain size (int) or domain specification (str) for variables (default is "[2]"

  Returns
  -------
  pyagrum.InfluenceDiagram
          the resulting Influence Diagram
  """
  return InfluenceDiagram.fastPrototype(structure, domain)

class IMarkovRandomField(UGmodel):
    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    def __init__(self, *args, **kwargs):
        raise AttributeError("No constructor defined - class is abstract")
    __swig_destroy__ = _pyagrum.delete_IMarkovRandomField

    def smallestFactorFromNode(self, *args) -> list[int]:
        return _pyagrum.IMarkovRandomField_smallestFactorFromNode(self, *args)

    def factors(self) -> list[set[int]]:
        return _pyagrum.IMarkovRandomField_factors(self)

    def __eq__(self, _from: "IMarkovRandomField") -> bool:
        return _pyagrum.IMarkovRandomField___eq__(self, _from)

    def dim(self) -> int:
        r"""

        Return the dimension (total number of free parameters) of the Markov random field.

        Returns
        -------
        int
            the number of free parameters

        """
        return _pyagrum.IMarkovRandomField_dim(self)

    def maxVarDomainSize(self) -> int:
        r"""

        Return the maximum domain size among all variables in the model.

        Returns
        -------
        int
            the maximum domain size

        """
        return _pyagrum.IMarkovRandomField_maxVarDomainSize(self)

    def minParam(self) -> float:
        r"""

        Return the minimum parameter value across all factors.

        Returns
        -------
        float
            the minimum factor parameter

        """
        return _pyagrum.IMarkovRandomField_minParam(self)

    def maxParam(self) -> float:
        r"""

        Return the maximum parameter value across all factors.

        Returns
        -------
        float
            the maximum factor parameter

        """
        return _pyagrum.IMarkovRandomField_maxParam(self)

    def minNonZeroParam(self) -> float:
        r"""

        Return the minimum non-zero parameter value across all factors.

        Returns
        -------
        float
            the minimum non-zero factor parameter

        """
        return _pyagrum.IMarkovRandomField_minNonZeroParam(self)

    def maxNonOneParam(self) -> float:
        r"""

        Return the maximum parameter value strictly less than 1 across all factors.

        Returns
        -------
        float
            the maximum non-one factor parameter

        """
        return _pyagrum.IMarkovRandomField_maxNonOneParam(self)

    def toDot(self) -> str:
        r"""

        Return a Graphviz dot representation of the Markov random field.

        Returns
        -------
        str
            a dot-format string

        """
        return _pyagrum.IMarkovRandomField_toDot(self)

    def toDotAsFactorGraph(self) -> str:
        r"""

        Return a Graphviz dot representation of the Markov random field as a factor graph.

        Returns
        -------
        str
            a dot-format string with variable nodes and factor nodes

        """
        return _pyagrum.IMarkovRandomField_toDotAsFactorGraph(self)

    def names(self) -> set[str]:
        r"""

        Set of names of variables in the model

        Returns
        -------
        set
            The names of the graph variables

        """
        return _pyagrum.IMarkovRandomField_names(self)

    def __iter__(self):
      """
      Iterate over the variables of the model

      Yield
      -----
      tuple[int,str]
        The index of the variable and its name
      """
      for i in self.nodes():
        yield i,self.variable(i).name()

    def __getitem__(self, key):
      if isinstance(key, int):
        return self.variable(key)
      elif isinstance(key, str):
        return self.variableFromName(key)
      else:
        raise TypeError("key must be an int or a string")


    def nodes(self) -> set[int]:
        return _pyagrum.IMarkovRandomField_nodes(self)

    def connectedComponentsList(self):
      """ connected components as a dict of sets

      Returns
      -------
      dict(int, set[int])
        dict of connected components (as sets of nodeIds) keyed by an arbitrary root nodeId per component.

      """
      cc = self.connectedComponents()
      result = {}
      for node, root in cc.items():
        if root not in result:
          result[root] = set()
        result[root].add(node)
      return result

    def connectedComponentsCount(self):
      """ number of connected components

      Returns
      -------
      int
        the number of connected components in the graph.

      """
      return len(set(self.connectedComponents().values()))

    def adjacencyMatrix(self):
      """ adjacency matrix from a graph/graphical models

      Compute the adjacency matrix of a pyAgrum's graph or graphical models
      (more generally an object that has `nodes`, `children`/`parents` or `neighbours` methods)

      Returns
      -------
      numpy.ndarray
        adjacency matrix (as numpy.ndarray) with nodeId as key.

      """
      nodes=self.nodes()
      n=self.size()
      am=numpy.zeros((n,n)).astype(int)

      for node in nodes:
          if hasattr(self,'children'):
              for children in self.children(node):
                  am[node,children]=1
          if hasattr(self,'neighbours'):
              for neighbour in self.neighbours(node):
                  am[node,neighbour]=1
      return am


    def neighbours(self, norid: object) -> set[int]:
        return _pyagrum.IMarkovRandomField_neighbours(self, norid)

    def edges(self) -> set[tuple[int,int]]:
        return _pyagrum.IMarkovRandomField_edges(self)

    def minimalCondSet(self, *args) -> set[int]:
        return _pyagrum.IMarkovRandomField_minimalCondSet(self, *args)

    def factor(self, *args) -> "pyagrum.Tensor":
        return _pyagrum.IMarkovRandomField_factor(self, *args)

    def isIndependent(self, *args) -> bool:
        r"""

        check if nodes X and nodes Y are independent given nodes Z

        Parameters
        ----------
        X : str|int|list of str|int
              a list of of nodeIds or names
        Y : str|int|list of str|int
              a list of of nodeIds or names
        Z : str|int|list of str|int
              a list of of nodeIds or names

        Raises
        ------
        InvalidArgument
          if X and Y share variables

        Returns
        -------
        bool
          True if X and Y are independent given Z in the model

        """
        return _pyagrum.IMarkovRandomField_isIndependent(self, *args)

    def __repr__(self) -> str:
        return _pyagrum.IMarkovRandomField___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.IMarkovRandomField___str__(self)

# Register IMarkovRandomField in _pyagrum:
_pyagrum.IMarkovRandomField_swigregister(IMarkovRandomField)
class MarkovRandomField(IMarkovRandomField):
    r"""

    MarkovRandomField represents a Markov random field.

    MarkovRandomField(name='') -> MarkovRandomField
        Parameters:
          - **name** (*str*) -- the name of the Bayes Net

    MarkovRandomField(source) -> MarkovRandomField
        Parameters:
          - **source** (*pyagrum.MarkovRandomField*) -- the Markov random field to copy

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")

    @staticmethod
    def fastPrototype(*args) -> "pyagrum.MarkovRandomField":
        r"""

        Create a Markov random field with a modified dot-like syntax which specifies:
            - the structure ``a--b--c;b--d--e;``. The substring ``a--b--c`` indicates a factor with the scope (a,b,c).
            - the type of the variables with different syntax (cf documentation).

        Examples
        --------
        >>> import pyagrum as gum
        >>> bn=pyagrum.MarkovRandomField.fastPrototype('A--B[1,3]--C{yes|No}--D[2,4]--E[1,2.5,3.9]',6)

        Parameters
        ----------
        dotlike : str
                the string containing the specification
        domainSize : int or str
                the default domain size or the default domain for variables

        Returns
        -------
        pyagrum.MarkovRandomField
                the resulting Markov random field

        """
        return _pyagrum.MarkovRandomField_fastPrototype(*args)

    @staticmethod
    def fromBN(bn: "pyagrum.BayesNet") -> "pyagrum.MarkovRandomField":
        r"""

        Create a Markov random field from a Bayesian network.

        Parameters
        ----------
        bn : pyagrum.BayesNet
            the Bayesian network to convert

        Returns
        -------
        pyagrum.MarkovRandomField
            a new MRF with the same variables and moralised structure

        """
        return _pyagrum.MarkovRandomField_fromBN(bn)
    __swig_destroy__ = _pyagrum.delete_MarkovRandomField

    def __init__(self, *args):
        _pyagrum.MarkovRandomField_swiginit(self, _pyagrum.new_MarkovRandomField(*args))

    def smallestFactorFromNode(self, node: int) -> list[int]:
        r"""

        Return the id set of the smallest factor that contains the given node.

        Parameters
        ----------
        node : int
            the node id

        Returns
        -------
        set[int]
            the id set of the smallest factor containing this node

        Raises
        ------
        pyagrum.NotFound
            if no factor contains this node

        """
        return _pyagrum.MarkovRandomField_smallestFactorFromNode(self, node)

    def factors(self) -> list[set[int]]:
        r"""

        Return the table of all factors in the Markov random field.

        Returns
        -------
        dict
            a dict mapping frozenset[int] (node id sets) to pyagrum.Tensor

        """
        return _pyagrum.MarkovRandomField_factors(self)

    def add(self, *args) -> int:
        r"""

        Add a variable to the pyagrum.MarkovRandomField.

        Parameters
        ----------
        variable : pyagrum.DiscreteVariable
            the variable added
        name : str
            the variable name
        nbrmod : int
            the number of modalities for the new variable
        id : int
            the variable forced id in the pyagrum.MarkovRandomField

        Returns
        -------
        int
            the id of the new node

        Raises
        ------
        pyagrum.DuplicateLabel
            If variable.name() is already used in this pyagrum.MarkovRandomField.
        pyagrum.OperationNotAllowed
            If nbrmod is less than 2
        pyagrum.DuplicateElement
            If id is already used.

        """
        return _pyagrum.MarkovRandomField_add(self, *args)

    def clear(self) -> None:
        r"""

        Clear the whole MarkovRandomField

        """
        return _pyagrum.MarkovRandomField_clear(self)

    def erase(self, *args) -> None:
        r"""

        Remove a variable from the pyagrum.MarkovRandomField.

        Removes the corresponding variable from the pyagrum.MarkovRandomField and from all of it's children pyagrum.Tensor.

        If no variable matches the given id, then nothing is done.

        Parameters
        ----------
        var :int | str | pyagrum.DiscreteVariable
            a variable's id (int) or name of variable or a reference of this variable to remove.

        """
        return _pyagrum.MarkovRandomField_erase(self, *args)

    def changeVariableName(self, *args) -> None:
        r"""

        Changes a variable's name in the pyagrum.MarkovRandomField.

        This will change the pyagrum.DiscreteVariable names in the pyagrum.MarkovRandomField.

        Parameters
        ----------
        car :int | str
            a variable's id (int) or name
        new_name : str
            the new name of the variable

        Raises
        ------
        pyagrum.DuplicateLabel
            If new_name is already used in this MarkovRandomField.
        pyagrum.NotFound
            If no variable matches id.

        """
        return _pyagrum.MarkovRandomField_changeVariableName(self, *args)

    def changeVariableLabel(self, *args) -> None:
        r"""

        change the label of the variable associated to nodeId to the new value.

        Parameters
        ----------
        var :int | str
            a variable's id (int) or name
        old_label : str
            the old label
        new_label : str
            the new label

        Raises
        ------
        pyagrum.NotFound
            if id/name is not a variable or if old_label does not exist.

        """
        return _pyagrum.MarkovRandomField_changeVariableLabel(self, *args)

    def generateFactors(self) -> None:
        r"""

        Randomly generates factors parameters for a given structure.

        """
        return _pyagrum.MarkovRandomField_generateFactors(self)

    def generateFactor(self, vars: list[int]) -> None:
        r"""

        Randomly generate factor parameters for a given factor in a given structure.

        Parameters
        ----------
        node : int | str
            a variable's id (int) or name

        """
        return _pyagrum.MarkovRandomField_generateFactor(self, vars)

    def beginTopologyTransformation(self) -> None:
        r"""

        Begin a sequence of structural modifications (factor additions/deletions).

        Structural changes are batched until endTopologyTransformation is called, which then adjusts all factor dimensions.

        """
        return _pyagrum.MarkovRandomField_beginTopologyTransformation(self)

    def endTopologyTransformation(self) -> None:
        r"""

        Terminates a sequence of insertions/deletions of arcs by adjusting all CPTs dimensions.
        End Multiple Change for all CPTs.

        Returns
        -------
        pyagrum.MarkovRandomField

        """
        return _pyagrum.MarkovRandomField_endTopologyTransformation(self)

    def graph(self) -> "pyagrum.UndiGraph":
        r"""

        Return the underlying undirected graph.

        Returns
        -------
        pyagrum.UndiGraph
            the underlying graph

        """
        return _pyagrum.MarkovRandomField_graph(self)

    def size(self) -> int:
        r"""

        Return the number of nodes (variables) in the graphical model.

        Returns
        -------
        int
            the number of nodes

        """
        return _pyagrum.MarkovRandomField_size(self)

    def log10DomainSize(self) -> float:
        r"""

        returns the log10 of the domain size of the model defined as the product of the domain sizes of the variables in the model.

        Returns
        -------
        float
            the log10 domain size.

        """
        return _pyagrum.MarkovRandomField_log10DomainSize(self)

    def idFromName(self, name: str) -> int:
        r"""

        Return the node id of a variable given its name.

        Parameters
        ----------
        name : str
            the name of the variable

        Returns
        -------
        int
            the node id of the variable

        Raises
        ------
        pyagrum.NotFound
            if no variable with this name exists in the model

        """
        return _pyagrum.MarkovRandomField_idFromName(self, name)

    def variableNodeMap(self) -> "pyagrum.VariableNodeMap":
        r"""

        Return the variable-to-node mapping of the model.

        Returns
        -------
        pyagrum.VariableNodeMap
            the internal variable-to-node bijection

        """
        return _pyagrum.MarkovRandomField_variableNodeMap(self)

    def variable(self, *args) -> "pyagrum.DiscreteVariable":
        r"""

        Return the variable associated with a given node id.

        Parameters
        ----------
        id : int
            the node id

        Returns
        -------
        pyagrum.DiscreteVariable
            the variable

        Raises
        ------
        pyagrum.NotFound
            if the node id does not exist

        """
        return _pyagrum.MarkovRandomField_variable(self, *args)

    def nodeId(self, var: "pyagrum.DiscreteVariable") -> int:
        r"""

        Return the node id of a variable.

        Parameters
        ----------
        var : pyagrum.DiscreteVariable
            the variable

        Returns
        -------
        int
            the node id of the variable

        Raises
        ------
        pyagrum.NotFound
            if the variable does not exist in the model

        """
        return _pyagrum.MarkovRandomField_nodeId(self, var)

    def variableFromName(self, name: str) -> "pyagrum.DiscreteVariable":
        r"""

        Return the variable with the given name.

        Parameters
        ----------
        name : str
            the name of the variable

        Returns
        -------
        pyagrum.DiscreteVariable
            the variable

        Raises
        ------
        pyagrum.NotFound
            if no variable with this name exists in the model

        """
        return _pyagrum.MarkovRandomField_variableFromName(self, name)

    def names(self) -> set[str]:
        r"""

        Set of names of variables in the model

        Returns
        -------
        set
            The names of the graph variables

        """
        return _pyagrum.MarkovRandomField_names(self)

    def __iter__(self):
      """
      Iterate over the variables of the model

      Yield
      -----
      tuple[int,str]
        The index of the variable and its name
      """
      for i in self.nodes():
        yield i,self.variable(i).name()

    def __getitem__(self, key):
      if isinstance(key, int):
        return self.variable(key)
      elif isinstance(key, str):
        return self.variableFromName(key)
      else:
        raise TypeError("key must be an int or a string")


    def nodes(self) -> set[int]:
        r"""

        Return the set of node ids in the model.

        Returns
        -------
        set[int]
            the set of node ids

        """
        return _pyagrum.MarkovRandomField_nodes(self)

    def connectedComponentsList(self):
      """ connected components as a dict of sets

      Returns
      -------
      dict(int, set[int])
        dict of connected components (as sets of nodeIds) keyed by an arbitrary root nodeId per component.

      """
      cc = self.connectedComponents()
      result = {}
      for node, root in cc.items():
        if root not in result:
          result[root] = set()
        result[root].add(node)
      return result

    def connectedComponentsCount(self):
      """ number of connected components

      Returns
      -------
      int
        the number of connected components in the graph.

      """
      return len(set(self.connectedComponents().values()))

    def adjacencyMatrix(self):
      """ adjacency matrix from a graph/graphical models

      Compute the adjacency matrix of a pyAgrum's graph or graphical models
      (more generally an object that has `nodes`, `children`/`parents` or `neighbours` methods)

      Returns
      -------
      numpy.ndarray
        adjacency matrix (as numpy.ndarray) with nodeId as key.

      """
      nodes=self.nodes()
      n=self.size()
      am=numpy.zeros((n,n)).astype(int)

      for node in nodes:
          if hasattr(self,'children'):
              for children in self.children(node):
                  am[node,children]=1
          if hasattr(self,'neighbours'):
              for neighbour in self.neighbours(node):
                  am[node,neighbour]=1
      return am


    def neighbours(self, norid: object) -> set[int]:
        r"""

        Return the set of neighbours of a node.

        Parameters
        ----------
        id : int
            the node id

        Returns
        -------
        set[int]
            the set of neighbour node ids

        """
        return _pyagrum.MarkovRandomField_neighbours(self, norid)

    def edges(self) -> set[tuple[int,int]]:
        r"""

        Returns
        -------
        set
            the set of edges in the Markov random field

        """
        return _pyagrum.MarkovRandomField_edges(self)

    def minimalCondSet(self, *args) -> set[int]:
        r"""

        Return a minimal conditioning set of a target given source nodes in the MRF.

        Parameters
        ----------
        target : int | str | list[int|str]
            the target node id(s) or name(s)
        soids : list[int|str]
            the list of source node ids or names

        Returns
        -------
        set[int]
            the minimal conditioning set (as node ids)

        """
        return _pyagrum.MarkovRandomField_minimalCondSet(self, *args)

    def factor(self, *args) -> "pyagrum.Tensor":
        r"""

        Returns the factor of a set of variables (if existing).

        Parameters
        ----------
        vars : set
            A set of ids or names of variable the pyagrum.MarkovRandomField.

        Returns
        -------
        pyagrum.Tensor
            The factor of the set of nodes.

        Raises
        ------
        pyagrum.NotFound
            If no variable's id matches varId.

        """
        return _pyagrum.MarkovRandomField_factor(self, *args)

    def isIndependent(self, *args) -> bool:
        r"""

        check if nodes X and nodes Y are independent given nodes Z

        Parameters
        ----------
        X : str|int|list of str|int
              a list of of nodeIds or names
        Y : str|int|list of str|int
              a list of of nodeIds or names
        Z : str|int|list of str|int
              a list of of nodeIds or names

        Raises
        ------
        InvalidArgument
          if X and Y share variables

        Returns
        -------
        bool
          True if X and Y are independent given Z in the model

        """
        return _pyagrum.MarkovRandomField_isIndependent(self, *args)

    def loadGUM(self, name: str, binary: bool=False) -> None:
        r"""

        Load a jgum (JSON) or bgum (binary/msgpack) file.

        Parameters
        ----------
        name : str
            the file's path (extension: ``.jgum`` for JSON, ``.bgum`` for binary)
        binary : bool
            if True, read as bgum (msgpack) regardless of extension (default: False)

        Raises
        ------
        pyagrum.IOError
            If file not found
        pyagrum.FatalError
            If file content is not valid

        See Also
        --------
        :ref:`jgum-bgum-format` : complete format reference

        """
        return _pyagrum.MarkovRandomField_loadGUM(self, name, binary)

    def _saveGUM(self, name: str, binary: bool=False, indent: int=2) -> None:
        r"""

        Save the MarkovRandomField in a jgum (JSON) or bgum (binary/msgpack) file.

        Metadata properties (``software``, ``creation``, ``lastModification``) are updated automatically.

        Parameters
        ----------
        name : str
            the file's path
        binary : bool
            if True, write as bgum (msgpack); otherwise write as jgum (JSON) (default: False)
        indent : int
            indentation level for JSON output; -1 for compact, 2 for pretty-printed (default: 2)

        See Also
        --------
        :ref:`jgum-bgum-format` : complete format reference

        """
        return _pyagrum.MarkovRandomField__saveGUM(self, name, binary, indent)

    def _saveGUMstring(self, indent: int=2) -> str:
        r"""

        Serialize the MarkovRandomField to a jgum JSON string.

        Metadata properties (``software``, ``creation``, ``lastModification``) are updated automatically.

        Parameters
        ----------
        indent : int
            indentation level; -1 for compact, 2 for pretty-printed (default: 2)

        Returns
        -------
        str
            a JSON string representing the MarkovRandomField in jgum format

        See Also
        --------
        :ref:`jgum-bgum-format` : complete format reference

        """
        return _pyagrum.MarkovRandomField__saveGUMstring(self, indent)

    def loadGUMstring(self, content: str) -> None:
        r"""

        Deserialize a MarkovRandomField from a jgum JSON string.

        Parameters
        ----------
        content : str
            a JSON string in jgum format

        Raises
        ------
        pyagrum.FatalError
            If the string is not valid jgum JSON or the type field does not match ``"MRF"``

        See Also
        --------
        :ref:`jgum-bgum-format` : complete format reference

        """
        return _pyagrum.MarkovRandomField_loadGUMstring(self, content)

    def loadUAI(self, *args) -> str:
        r"""

        Load an UAI file.

        Parameters
        ----------
        name : str
            the name's file
        l : list
            list of functions to execute

        Raises
        ------
        pyagrum.IOError
            If file not found
        pyagrum.FatalError
            If file is not valid

        """
        return _pyagrum.MarkovRandomField_loadUAI(self, *args)

    def saveUAI(self, name: str) -> None:
        r"""

        Save the MarkovRandomField in an UAI file.

        Parameters
        ----------
        name : str
            the file's name

        """
        return _pyagrum.MarkovRandomField_saveUAI(self, name)

    def __getstate__(self):
        return self.saveGUMstring()

    def __setstate__(self,state):
        self.__init__()
        self.loadGUMstring(state)
        return self

    def saveGUM(self, name: str, binary: bool = False, indent: int = None) -> None:
      """
      Save the Markov random field as a .jgum (text) or .bgum (binary) file.

      Parameters
      ----------
      name : str
        the name of the file
      binary : bool
        if True, save in the msgpack-based .bgum binary format instead of the JSON-based .jgum text format
      indent : int, optional
        JSON indentation for the .jgum text format (ignored if binary=True): -1 for the most
        compact output, N>=0 to pretty-print with N spaces. Defaults to
        ``pyagrum.config["core","default_jgumIndent"]``.
      """
      if indent is None:
        indent = pyagrum.config.typed["core", "default_jgumIndent"]
      self._saveGUM(name, binary, indent)

    def saveGUMstring(self, indent: int = None) -> str:
      """
      Save the Markov random field as a jgum string.

      Parameters
      ----------
      indent : int, optional
        JSON indentation: -1 for the most compact output, N>=0 to pretty-print with N spaces.
        Defaults to ``pyagrum.config["core","default_jgumIndent"]``.

      Returns
      -------
      str
        the jgum string
      """
      if indent is None:
        indent = pyagrum.config.typed["core", "default_jgumIndent"]
      return self._saveGUMstring(indent)

    def toFast(self, filename: str = None) -> str:
      """
      Export the MRF as *fast* syntax (in a string or in a python file)

      Parameters
      ----------
      filename : Optional[str]
        the name of the file (including the prefix), if None , use sys.stdout
      """

      def _toFastMRF(model,pythoncode=False):
        res = []
        sovars = set()
        first = True
        for f in model.factors():
          l = []
          for x in f:
              if x in sovars:
                src = model.variable(x).name()
              else:
                src = model.variable(x).toFast()
                sovars.add(x)
              l.append(src)
          res.append("--".join(l))

        for x in model.nodes():
          if x not in sovars:
            res.append(model.variable(x).toFast())

        if pythoncode:
          return 'model=pyagrum.fastMRF("""'+';\n     '.join(res)+'""")'
        else:
          return ';'.join(res)

      if filename is None:
        return _toFastMRF(self)
      else:
        with open(filename, "w") as pyfile:
          print(_toFastBN(self,pythoncode=True), file=pyfile)


    def __repr__(self) -> str:
        return _pyagrum.MarkovRandomField___repr__(self)

    def __str__(self) -> str:
        return _pyagrum.MarkovRandomField___str__(self)

    def addFactor(self, *args) -> "pyagrum.Tensor":
        r"""

        Add a factor from a list or a set of id or str. If the argument is a set, the order is the order of the IDs of the variables

        Parameters
        ----------
        seq : sequence (list or set) of int or string
            The sequence (ordered or not) of node id or names

        """
        return _pyagrum.MarkovRandomField_addFactor(self, *args)

    def eraseFactor(self, *args) -> None:
        r"""

        Remove the factor that covers a given set of variables.

        Parameters
        ----------
        vars : set[int] | list[str]
            the set of variable ids or names whose factor should be removed

        """
        return _pyagrum.MarkovRandomField_eraseFactor(self, *args)

    def addVariables(self,listFastVariables,default_nbr_mod=2):
       """
       Add a list of variable in the form of 'fast' syntax.

       Parameters
       ----------
       listFastVariables: list[str]
         the list of variables in 'fast' syntax.
       default_nbr_mod: int
         the number of modalities for the variable if not specified following :ref:`fast syntax<Quick specification of (randomly parameterized) graphical models>`. Note that default_nbr_mod=1 is
         mandatory to create variables with only one modality (for utility for instance).

       Returns
       -------
       list[int]
         the list of created ids.
       """
       return [self.add(descr,default_nbr_mod) for descr in listFastVariables]

    def addStructureListener(self,whenNodeAdded=None,whenNodeDeleted=None,whenEdgeAdded=None,whenedgeDeleted=None):
        """
        Add the listeners in parameters to the list of existing ones.

        Parameters
        ----------
        whenNodeAdded : lambda expression
          a function for when a node is added
        whenNodeDeleted : lambda expression
          a function for when a node is removed
        whenEdgeAdded : lambda expression
          a function for when an edge is added
        whenEdgeDeleted : lambda expression
          a function for when an edge is removed
        """
        if [whenNodeAdded,whenNodeDeleted,whenEdgeAdded,whenedgeDeleted]==[None,None,None,None]:
          return

        if not hasattr(self,"_listeners"):
          self._listeners=[]

        nl = PythonBNListener(self, self.variableNodeMap())
        if whenNodeAdded is not None:
          nl.setWhenNodeAdded(whenNodeAdded)
        if whenNodeDeleted is not None:
          nl.setWhenNodeDeleted(whenNodeDeleted)
        if whenEdgeAdded is not None:
          nl.setWhenEdgeAdded(whenEdgeAdded)
        if whenedgeDeleted is not None:
          nl.setWhenArcDeleted(whenedgeDeleted)

        self._listeners.append(nl)


# Register MarkovRandomField in _pyagrum:
_pyagrum.MarkovRandomField_swigregister(MarkovRandomField)
class ShaferShenoyMRFInference(object):
    r"""

    Class used for Shafer-Shenoy inferences for Markov random field.

    ShaferShenoyMRFInference(bn) -> ShaferShenoyMRFInference
        Parameters:
            - **mrf** (*pyagrum.MarkovRandomField*) -- a Markov random field

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, MN: "IMarkovRandomField", use_binary_join_tree: bool=True):
        _pyagrum.ShaferShenoyMRFInference_swiginit(self, _pyagrum.new_ShaferShenoyMRFInference(MN, use_binary_join_tree))

        self._model=MN#first arg of the constructor



    __swig_destroy__ = _pyagrum.delete_ShaferShenoyMRFInference

    def setTriangulation(self, new_triangulation: "pyagrum.Triangulation") -> None:
        r"""

        Replace the triangulation algorithm used to build the junction tree.

        Parameters
        ----------
        new_triangulation : pyagrum.Triangulation
            the new triangulation algorithm

        """
        return _pyagrum.ShaferShenoyMRFInference_setTriangulation(self, new_triangulation)

    def joinTree(self) -> "pyagrum.CliqueGraph":
        r"""

        Returns
        -------
        pyagrum.CliqueGraph
          the current join tree used

        """
        return _pyagrum.ShaferShenoyMRFInference_joinTree(self)

    def junctionTree(self) -> "pyagrum.JunctionTree":
        r"""

        Returns
        -------
        pyagrum.CliqueGraph
          the current junction tree

        """
        val = _pyagrum.ShaferShenoyMRFInference_junctionTree(self)

        val._engine=self


        return val


    def evidenceProbability(self) -> float:
        r"""

        Returns
        -------
        float
          the probability of evidence

        """
        return _pyagrum.ShaferShenoyMRFInference_evidenceProbability(self)

    def setNumberOfThreads(self, nb: int) -> None:
        r"""

        If the argument nb is different from 0, this number of threads will be used during inferences, hence overriding aGrUM's default number of threads.
        If, on the contrary, nb is equal to 0, the parallelized inference engine will comply with aGrUM's default number of threads.

        Parameters
        ----------
        nb : int
            the number of threads to be used by ShaferShenoyMRFInference

        """
        return _pyagrum.ShaferShenoyMRFInference_setNumberOfThreads(self, nb)

    def getNumberOfThreads(self) -> int:
        r"""

        returns the number of threads used by LazyPropagation during inferences.

        Returns
        -------
        int
            the number of threads used by LazyPropagation during inferences

        """
        return _pyagrum.ShaferShenoyMRFInference_getNumberOfThreads(self)

    def isGumNumberOfThreadsOverriden(self) -> bool:
        r"""

        Indicates whether LazyPropagation currently overrides aGrUM's default number of threads (see method setNumberOfThreads).

        Returns
        -------
        bool
            A Boolean indicating whether LazyPropagation currently overrides aGrUM's default number of threads

        """
        return _pyagrum.ShaferShenoyMRFInference_isGumNumberOfThreadsOverriden(self)

    def setMaxMemory(self, gigabytes: int) -> None:
        r"""

        sets an upper bound on the memory consumption admissible

        Parameters
        ----------
        gigabytes: float
          this upper bound in gigabytes.

        """
        return _pyagrum.ShaferShenoyMRFInference_setMaxMemory(self, gigabytes)

    def makeInference(self) -> None:
        r"""

        Perform the heavy computations needed to compute the targets' posteriors

        In a Junction tree propagation scheme, for instance, the heavy computations are those of the messages sent in the JT.
        This is precisely what makeInference should compute. Later, the computations of the posteriors can be done 'lightly' by multiplying and projecting those messages.

        """
        return _pyagrum.ShaferShenoyMRFInference_makeInference(self)

    def addEvidence(self, *args) -> None:
        r"""

        Adds a new evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val :
          (int) a node value
        val :
          (str) the label of the node value
        vals : list
          a list of values

        Raises
        ------
          pyagrum.InvalidArgument
            If the node already has an evidence
          pyagrum.InvalidArgument
            If val is not a value for the node
          pyagrum.InvalidArgument
            If the size of vals is different from the domain side of the node
          pyagrum.FatalError
            If vals is a vector of 0s
          pyagrum.UndefinedElement
            If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyMRFInference_addEvidence(self, *args)

    def chgEvidence(self, *args) -> None:
        r"""

        Change the value of an already existing evidence on a node (might be soft or hard).

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name
        val : int|str
          a node value or the label of the node value
        vals : list of float
          a list of values

        Raises
        ------
        pyagrum.InvalidArgument
          If the node does not already have an evidence
        pyagrum.InvalidArgument
          If val is not a value for the node
        pyagrum.InvalidArgument
          If the size of vals is different from the domain side of the node
        pyagrum.FatalError
          If vals is a vector of 0s
        pyagrum.UndefinedElement
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyMRFInference_chgEvidence(self, *args)

    def hasEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if some node(s) (or the one in parameters) have received evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyMRFInference_hasEvidence(self, *args)

    def eraseAllEvidence(self) -> None:
        r"""

        Removes all the evidence entered into the network.

        """
        return _pyagrum.ShaferShenoyMRFInference_eraseAllEvidence(self)

    def eraseEvidence(self, *args) -> None:
        r"""

        Remove the evidence, if any, corresponding to the node Id or name.

        Parameters
        ----------
        id : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyMRFInference_eraseEvidence(self, *args)

    def hasHardEvidence(self, nodeName: str) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a hard evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyMRFInference_hasHardEvidence(self, nodeName)

    def hasSoftEvidence(self, *args) -> bool:
        r"""

        Parameters
        ----------
        id : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if node has received a soft evidence

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyMRFInference_hasSoftEvidence(self, *args)

    def nbrEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of evidence entered into the Bayesian network

        """
        return _pyagrum.ShaferShenoyMRFInference_nbrEvidence(self)

    def nbrHardEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of hard evidence entered into the Bayesian network

        """
        return _pyagrum.ShaferShenoyMRFInference_nbrHardEvidence(self)

    def nbrSoftEvidence(self) -> int:
        r"""

        Returns
        -------
        int
          the number of soft evidence entered into the Bayesian network

        """
        return _pyagrum.ShaferShenoyMRFInference_nbrSoftEvidence(self)

    def eraseAllTargets(self) -> None:
        r"""

        Clear all previously defined targets (marginal and joint targets).

        As a result, no posterior can be computed (since we can only compute the posteriors of the marginal or joint targets that have been added by the user).

        """
        return _pyagrum.ShaferShenoyMRFInference_eraseAllTargets(self)

    def addAllTargets(self) -> None:
        r"""

        Add all the nodes as targets.

        """
        return _pyagrum.ShaferShenoyMRFInference_addAllTargets(self)

    def addTarget(self, *args) -> None:
        r"""

        Add a marginal target to the list of targets.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : str
          a node name

        Raises
        ------
        pyagrum.UndefinedElement
          If target is not a NodeId in the Bayes net

        """
        return _pyagrum.ShaferShenoyMRFInference_addTarget(self, *args)

    def eraseTarget(self, *args) -> None:
        r"""

        Remove, if existing, the marginal target.

        Parameters
        ----------
        target : int
          a node Id
        nodeName : int
          a node name

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.ShaferShenoyMRFInference_eraseTarget(self, *args)

    def isTarget(self, *args) -> bool:
        r"""

        Parameters
        ----------
        variable : int
         a node Id
        nodeName : str
          a node name

        Returns
        -------
        bool
          True if variable is a (marginal) target

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.ShaferShenoyMRFInference_isTarget(self, *args)

    def nbrTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of marginal targets

        """
        return _pyagrum.ShaferShenoyMRFInference_nbrTargets(self)

    def H(self, *args) -> float:
        r"""

        Parameters
        ----------
        X : int
          a node Id
        nodeName : str
          a node name

        Returns
        -------
        float
          the computed Shanon's entropy of a node given the observation

        """
        return _pyagrum.ShaferShenoyMRFInference_H(self, *args)

    def MRF(self) -> "pyagrum.IMarkovRandomField":
        r"""

        Return the Markov random field associated with this inference engine.

        Returns
        -------
        pyagrum.IMarkovRandomField
            the Markov random field

        """
        return _pyagrum.ShaferShenoyMRFInference_MRF(self)

    def posterior(self, *args) -> "pyagrum.Tensor":
        r"""

        Computes and returns the posterior of a node.

        Parameters
        ----------
        var : int
          the node Id of the node for which we need a posterior probability
        nodeName : str
          the node name of the node for which we need a posterior probability

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior probability of the node

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.ShaferShenoyMRFInference_posterior(self, *args)

    def eraseAllJointTargets(self) -> None:
        r"""

        Clear all previously defined joint targets.

        """
        return _pyagrum.ShaferShenoyMRFInference_eraseAllJointTargets(self)

    def eraseAllMarginalTargets(self) -> None:
        r"""

        Clear all the previously defined marginal targets.

        """
        return _pyagrum.ShaferShenoyMRFInference_eraseAllMarginalTargets(self)

    def nbrJointTargets(self) -> int:
        r"""

        Returns
        -------
        int
          the number of joint targets

        """
        return _pyagrum.ShaferShenoyMRFInference_nbrJointTargets(self)

    def setEvidence(self, evidces):
        """
        Erase all the evidences and apply addEvidence(key,value) for every pairs in evidces.

        Parameters
        ----------
        evidces : dict
          a dict of evidences

        Raises
        ------
        pyagrum.InvalidArgument
            If one value is not a value for the node
        pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
        pyagrum.FatalError
            If one value is a vector of 0s
        pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if not isinstance(evidces, dict):
            raise TypeError("setEvidence parameter must be a dict, not %s"%(type(evidces)))
        self.eraseAllEvidence()
        for k,v in evidces.items():
            self.addEvidence(k,v)



    def updateEvidence(self, evidces):
        """
        Apply chgEvidence(key,value) for every pairs in evidces (or addEvidence).

        Parameters
        ----------
        evidces : dict
          a dict of evidences

        Raises
        ------
        pyagrum.InvalidArgument
            If one value is not a value for the node
        pyagrum.InvalidArgument
            If the size of a value is different from the domain side of the node
        pyagrum.FatalError
            If one value is a vector of 0s
        pyagrum.UndefinedElement
            If one node does not belong to the Bayesian network
        """
        if not isinstance(evidces, dict):
            raise TypeError("setEvidence parameter must be a dict, not %s"%(type(evidces)))

        for k,v in evidces.items():
            if self.hasEvidence(k):
                self.chgEvidence(k,v)
            else:
                self.addEvidence(k,v)



    def setTargets(self, targets):
        """
        Remove all the targets and add the ones in parameter.

        Parameters
        ----------
        targets : set
          a set of targets

        Raises
        ------
        pyagrum.UndefinedElement
            If one target is not in the Bayes net
        """
        if not isinstance(targets, set):
            raise TypeError("setTargets parameter must be a set, not %s"%(type(targets)))

        self.eraseAllTargets()
        for k in targets:
            self.addTarget(k)



    def hardEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with hard evidence

        """
        return _pyagrum.ShaferShenoyMRFInference_hardEvidenceNodes(self)

    def softEvidenceNodes(self) -> set[int]:
        r"""

        Returns
        -------
        set
          the set of nodes with soft evidence

        """
        return _pyagrum.ShaferShenoyMRFInference_softEvidenceNodes(self)

    def targets(self) -> set[int]:
        r"""

        Returns
        -------
        list
          the list of marginal targets

        """
        return _pyagrum.ShaferShenoyMRFInference_targets(self)

    def evidenceImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(target|evs) (for all instanciation of target and evs)

        Parameters
        ----------
        target : set
          a set of targets ids or names.
        evs : set
          a set of nodes ids or names.

        Warnings
        --------
        if some evs are d-separated, they are not included in the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(targets|evs)

        """
        return _pyagrum.ShaferShenoyMRFInference_evidenceImpact(self, *args)

    def jointMutualInformation(self, targets: object) -> float:
        r"""

        Compute the joint mutual information (interaction information) among a set of target variables.

        Parameters
        ----------
        targets : list[str] or set[int]
            the target variables (names or node ids)

        Returns
        -------
        float
            the joint mutual information

        """
        return _pyagrum.ShaferShenoyMRFInference_jointMutualInformation(self, targets)

    def evidenceJointImpact(self, *args) -> "pyagrum.Tensor":
        r"""

        Create a pyagrum.Tensor for P(joint targets|evs) (for all instanciation of targets and evs)

        Parameters
        ----------
        targets : list of int|str
          a list of node Ids or node names
        evs : set
          a set of nodes ids or names.

        Returns
        -------
        pyagrum.Tensor
          a Tensor for P(target|evs)

        Raises
        ------
        pyagrum.Exception
          If some evidene entered into the Bayes net are incompatible (their joint proba = 0)

        """
        return _pyagrum.ShaferShenoyMRFInference_evidenceJointImpact(self, *args)

    def jointPosterior(self, targets: object) -> "pyagrum.Tensor":
        r"""

        Compute the joint posterior of a set of nodes.

        Parameters
        ----------
        list :
          the list of nodes whose posterior joint probability is wanted


        Warnings
        --------
        The order of the variables given by the list here or when the jointTarget is declared can not be assumed to be used by the Tensor.

        Returns
        -------
        pyagrum.Tensor
          a const ref to the posterior joint probability of the set of nodes.

        Raises
        ------
        pyagrum.UndefinedElement
          If an element of nodes is not in targets

        """
        return _pyagrum.ShaferShenoyMRFInference_jointPosterior(self, targets)

    def addJointTarget(self, targets: object) -> None:
        r"""

        Add a list of nodes as a new joint target. As a collateral effect, every node is added as a marginal target.

        Parameters
        ----------
        list
          a list of names of nodes

        Raises
        ------
        pyagrum.UndefinedElement
          If some node(s) do not belong to the Bayesian network

        """
        return _pyagrum.ShaferShenoyMRFInference_addJointTarget(self, targets)

    def eraseJointTarget(self, targets: object) -> None:
        r"""

        Remove, if existing, the joint target.

        Parameters
        ----------
        list
          a list of names or Ids of nodes

        Raises
        ------
        pyagrum.IndexError
          If one of the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.ShaferShenoyMRFInference_eraseJointTarget(self, targets)

    def isJointTarget(self, targets: object) -> bool:
        r"""

        Parameters
        ----------
        list
          a list of nodes ids or names.

        Returns
        -------
        bool
          True if target is a joint target.

        Raises
        ------
        pyagrum.IndexError
          If the node does not belong to the Bayesian network
        pyagrum.UndefinedElement
          If node Id is not in the Bayesian network

        """
        return _pyagrum.ShaferShenoyMRFInference_isJointTarget(self, targets)

    def jointTargets(self) -> list[set[int]]:
        r"""

        Returns
        -------
        list
          the list of target sets

        """
        return _pyagrum.ShaferShenoyMRFInference_jointTargets(self)

# Register ShaferShenoyMRFInference in _pyagrum:
_pyagrum.ShaferShenoyMRFInference_swigregister(ShaferShenoyMRFInference)

def availableMRFExts():
  """ Give the list of all formats known by pyAgrum to save a Markov random field.

  Returns
  ------
  str
    a string which lists all suffixes for supported MRF file formats.
  """
  return "uai|jgum|bgum|pkl"


def loadMRF(filename, listeners=None, verbose=False):
  """load a MRF from a file with optional listeners and arguments

  Parameters
  ----------
  filename: str
    the name of the input file
  listeners: list[Object]
    list of functions to execute
  verbose: bool
    whether to print or not warning messages

  Returns
  -------
  pyagrum.MarkovRandomField
    a MRF from a file using one of the availableMNExts() suffixes.

  Listeners could be added in order to monitor its loading.

  pkl suffix is used to load a pickled MRF. In this case, options are ignored.

  jgum/bgum suffixes use the native aGrUM JSON/binary format and also ignore listeners.

  Examples
  --------
  >>> import pyagrum as gum
  >>>
  >>> # creating listeners
  >>> def foo_listener(progress):
  >>>    if progress==200:
  >>>        print(' BN loaded ')
  >>>        return
  >>>    elif progress==100:
  >>>        car='%'
  >>>    elif progress%10==0:
  >>>        car='#'
  >>>    else:
  >>>        car='.'
  >>>    print(car,end='',flush=True)
  >>>
  >>> def bar_listener(progress):
  >>>    if progress==50:
  >>>        print('50%')
  >>>
  >>> # loadBN with list of listeners
  >>> pyagrum.loadMRF('./bn.uai',listeners=[foo_listener,bar_listener])
  >>> # .........#.........#.........#.........#..50%
  >>> # .......#.........#.........#.........#.........#.........% | bn loaded
  """
  mn = MarkovRandomField()

  warns = ""
  extension = filename.split('.')[-1].upper()
  if extension == "UAI":
    warns = mn.loadUAI(filename, listeners)
  elif extension == "JGUM":
    mn.loadGUM(filename)
  elif extension == "BGUM":
    mn.loadGUM(filename, binary=True)
  elif extension == "PKL":
    mn = _gum_pickle_load(filename)
  else:
    raise InvalidArgument("extension " + filename.split('.')
    [-1] + " unknown. Please use among " + availableMRFExts())

  if verbose:
    warnings.warn(warns)

  _gum_set_name_property(mn, filename)
  return mn


def saveMRF(mn, filename):
  """
  save a MRF into a file using the format corresponding to one of the availableWriteMNExts() suffixes.

  Parameters
  ----------
  mn : pyagrum.MarkovRandomField
    the MRF to save
  filename : str
    the name of the output file

  Notes
  -----
  pkl suffix is used to save a MRF using pickle.

  jgum/bgum suffixes use the native aGrUM JSON/binary format.
  """
  extension = filename.split('.')[-1].upper()

  if extension == "UAI":
    mn.saveUAI(filename)
  elif extension == "JGUM":
    mn.saveGUM(filename)
  elif extension == "BGUM":
    mn.saveGUM(filename, binary=True)
  elif extension == "PKL":
    _gum_pickle_save(mn, filename)
  else:
    raise InvalidArgument("extension " + filename.split('.')[-1] + " unknown. Please use among " + availableMRFExts())

def fastMRF(structure, domain="[2]"):
  """
  Create a Markov random field with a modified dot-like syntax which specifies:
      - the structure 'a-b-c;b-d;c-e;' where each chain 'a-b-c' specifies a factor,
      - the type of the variables with different syntax (cf documentation).

  Examples
  --------
  >>> import pyagrum as gum
  >>> bn=pyagrum.fastMRF('A--B[1,3]--C{yes|No};C--D[2,4]--E[1,2.5,3.9]',6)

  Parameters
  ----------
  structure : str
          the string containing the specification
  domain: int or str
          the default domain size (int) or domain specification (str) for variables (default is "[2]"

  Returns
  -------
  pyagrum.MarkovRandomField
          the resulting Markov random field
  """
  return MarkovRandomField.fastPrototype(structure, domain)


def getPosterior(model, *, target, evs=None):
  """
  Compute the posterior of a single target (variable) in a BN given evidence


  getPosterior uses a VariableElimination inference.
  If more than one target is needed with the same set of evidence or if the same
  target is needed with more than one set of evidence, this function is not
  relevant since it creates a new inference engine every time it is called.

  Parameters
  ----------
  bn : pyagrum.BayesNet or pyagrum.MarkovRandomField
    The probabilistic Graphical Model
  target: string or int
    variable name or id (forced keyword argument)
  evs:  dict[name|id:val, name|id : list[ val1, val2 ], ...]. (optional forced keyword argument)
    the (hard and soft) evidence

  Returns
  -------
    posterior (pyagrum.Tensor or other)
  """
  if isinstance(model, pyagrum.BayesNet):
    inf = pyagrum.VariableElimination(model)
  elif isinstance(model, MarkovRandomField):
    inf = ShaferShenoyMRFInference(model)
  else:
    raise InvalidArgument("Argument model should be a PGM (BayesNet or MarkovRandomField")

  if evs is not None:
    inf.setEvidence(evs)
  inf.addTarget(target)
  inf.makeInference()
# creating a new Tensor from posterior(will disappear with ie)
  return pyagrum.Tensor(inf.posterior(target))


def causalImpact(cm, *, on, doing, knowing=None, values=None):
    """
    Identify and evaluate the causal effect of do(doing) on on, optionally
    conditioning on knowing.

    The identification procedure tries in order: d-separation (no effect),
    backdoor adjustment, frontdoor adjustment, and general do-calculus
    (ID algorithm).

    Parameters
    ----------
    cm : pyagrum.CausalModel
        The causal model.
    on : str or set of str
        Target variable(s) of the causal query. A single string is
        automatically converted to a one-element set.
    doing : str or set of str
        Intervened variable(s) (the do-operator applies to these). A single
        string is automatically converted to a one-element set.
    knowing : str or set of str, optional
        Observed variable(s) to condition on. Default is empty.
    values : dict of str → str, optional
        Specific values for the on/knowing variables as
        ``{variable_name: value_name}``. When provided, the returned tensor
        is sliced to those values. Default is no slicing.

    Returns
    -------
    tuple (pyagrum.CausalImpact, pyagrum.Tensor or None, str)
        - The CausalImpact object encoding the identified formula.
        - The evaluated tensor P(on | do(doing), knowing), or None if the
          effect is not identifiable.
        - A string explaining the identification method used or why
          identification failed.

    Examples
    --------
    >>> import pyagrum as gum
    >>> bn = pyagrum.BayesNet.fastPrototype('X->Y->Z')
    >>> cm = pyagrum.CausalModel(bn)
    >>> formula, tensor, expl = pyagrum.causalImpact(cm, on='Z', doing='X')
    >>> print(expl)
    """
    if isinstance(on, str):
        on = {on}
    if isinstance(doing, str):
        doing = {doing}
    if isinstance(knowing, str):
        knowing = {knowing}
    lat, pot, expl = _causalImpact(cm, on, doing,
                                   knowing if knowing is not None else set(),
                                   values  if values  is not None else {})
    pot._model = cm
    return lat, (pot if lat.isIdentified() else None), expl

def counterfactual(cm, *, on, whatif, profile=None, values=None):
    """
    Compute a counterfactual distribution using Pearl's twin network method.

    Answers the question: 'Given that we observed *profile*, what would
    *on* have been if *whatif* had been set as specified in *values*?'

    The computation follows the three-step algorithm from Pearl (2018),
    *The Book of Why*, chapter 8: abduction (update parentless node priors
    from the profile), action (apply do(whatif) on the twin model), and
    prediction (evaluate the causal effect on the twin).

    Parameters
    ----------
    cm : pyagrum.CausalModel
        The causal model.
    on : str or set of str
        Target variable(s) of the counterfactual query. A single string is
        automatically converted to a one-element set.
    whatif : str or set of str
        Variable(s) whose values are changed in the counterfactual scenario.
        A single string is automatically converted to a one-element set.
    profile : dict of str → str, optional
        The factual observation as ``{variable_name: value_name}``. This
        grounds the counterfactual (step 1: abduction). Default is empty
        (no factual observation).
    values : dict of str → str, optional
        Counterfactual values for the *whatif* variables as
        ``{variable_name: value_name}``. If omitted, the full distribution
        over all *whatif* values is returned.

    Returns
    -------
    pyagrum.Tensor
        The counterfactual distribution P(on | do(whatif)) evaluated on the
        twin model, optionally sliced by *values*.

    Examples
    --------
    >>> import pyagrum as gum
    >>> bn = pyagrum.BayesNet.fastPrototype('X->Y->Z')
    >>> cm = pyagrum.CausalModel(bn)
    >>> t = pyagrum.counterfactual(cm, on='Z', whatif='X',
    ...                        profile={'Y': 'True'}, values={'X': 'False'})
    """
    p=_counterfactual(cm, on, whatif,
                           profile if profile is not None else {},
                           values  if values  is not None else {})
    p._model=cm
    return p

def counterfactualModel(cm, profile=None, whatif=None):
    """
    Build the twin causal model for a counterfactual query.

    Implements steps 1-2 of Pearl's three-step counterfactual algorithm:
    compute the posterior of parentless (idiosyncratic) nodes in the
    observational BN given *profile* as evidence, then replace their priors
    in a copy of the model with those posteriors.

    Parameters
    ----------
    cm : pyagrum.CausalModel
        The original causal model.
    profile : dict of str → str, optional
        The factual observation as ``{variable_name: value_name}``.
        Default is empty (no observation; the twin model equals the original).
    whatif : str or set of str, optional
        Intervened variable(s) in the counterfactual scenario. These are
        excluded from the set of idiosyncratic nodes that get updated.
        A single string is automatically converted to a one-element set.
        Default is empty.

    Returns
    -------
    pyagrum.CausalModel
        The twin causal model ready for the prediction step.

    Examples
    --------
    >>> import pyagrum as gum
    >>> bn = pyagrum.BayesNet.fastPrototype('X->Y->Z')
    >>> cm = pyagrum.CausalModel(bn)
    >>> twin = pyagrum.counterfactualModel(cm, profile={'Y': 'True'}, whatif='X')
    """
    p=_counterfactualModel(cm,
                           profile if profile is not None else {},
                           whatif  if whatif  is not None else set())
    p._model=cm
    return p


class DoorCriteria(object):
    r"""

    Utility class implementing the backdoor and frontdoor criteria on a causal DAG.

    All methods are static and take the DAG as their first argument. This class is
    stateless: it does not store any model.

    A **backdoor adjustment set** Z between X and Y blocks all spurious (non-causal)
    paths from X to Y while leaving all directed causal paths open.

    A **frontdoor adjustment set** Z between X and Y intercepts every directed path
    from X to Y, has no open backdoor path from X to Z, and all backdoor paths from
    Z to Y are blocked by X.

    Notes
    -----
    High-level search for a single valid set is available via
    :meth:`pyagrum.CausalModel.backDoor` and
    :meth:`pyagrum.CausalModel.frontDoor`.

    Examples
    --------
    >>> import pyagrum as gum
    >>> bn = pyagrum.BayesNet.fastPrototype('X->Z->Y')
    >>> dag = bn.dag()
    >>> x, y, z = bn.idFromName('X'), bn.idFromName('Y'), bn.idFromName('Z')
    >>> pyagrum.DoorCriteria.satisfiesBackdoorCriterion(dag, x, y, set())
    True
    >>> pyagrum.DoorCriteria.enumerateFrontdoorSets(dag, x, y)
    [{z}]

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    @staticmethod
    def satisfiesBackdoorCriterion(dag: "pyagrum.DAG", X: int, Y: int, Z: list[int]) -> bool:
        r"""

        Test whether Z satisfies the backdoor criterion for the effect of X on Y.

        A set Z satisfies the backdoor criterion with respect to (X, Y) if:

          1. No node in Z is a descendant of X in the causal DAG.
          2. Z blocks every backdoor path between X and Y (paths that begin with an
             arc pointing into X).

        Parameters
        ----------
        dag : pyagrum.DAG
            The causal DAG.
        X : int
            NodeId of the treatment variable.
        Y : int
            NodeId of the outcome variable.
        Z : set of int
            Candidate adjustment set (NodeIds).

        Returns
        -------
        bool
            True if Z satisfies the backdoor criterion for (X, Y).

        """
        return _pyagrum.DoorCriteria_satisfiesBackdoorCriterion(dag, X, Y, Z)

    @staticmethod
    def enumerateBackdoorSets(*args) -> "pyagrum.DoorCriteria::NodeSetVec":
        return _pyagrum.DoorCriteria_enumerateBackdoorSets(*args)

    @staticmethod
    def firstBackdoor(*args) -> list[int] | None:
        r"""

        Return the first valid backdoor adjustment set for the causal effect of X on Y.

        Parameters
        ----------
        dag : pyagrum.DAG
            The causal DAG.
        X : int
            NodeId of the treatment variable.
        Y : int
            NodeId of the outcome variable.
        excluded_nodes : set of int, optional
            Nodes that cannot appear in any adjustment set. Default is empty.
        max_cardinality : int, optional
            Maximum size of the adjustment set. 0 means no limit. Default is 0.
        only_minimal : bool, optional
            If True, return only a minimal adjustment set. Default is True.

        Returns
        -------
        list of int or None
            The first valid backdoor adjustment set as a list of NodeIds, or None if none exists.

        """
        return _pyagrum.DoorCriteria_firstBackdoor(*args)

    @staticmethod
    def satisfiesFrontdoorCriterion(dag: "pyagrum.DAG", X: int, Y: int, Z: list[int]) -> bool:
        r"""

        Test whether Z satisfies the frontdoor criterion for the effect of X on Y.

        A set Z satisfies the frontdoor criterion with respect to (X, Y) if:

          1. Z intercepts all directed paths from X to Y.
          2. There are no unblocked backdoor paths from X to Z.
          3. All backdoor paths from Z to Y are blocked by X.

        Parameters
        ----------
        dag : pyagrum.DAG
            The causal DAG.
        X : int
            NodeId of the treatment variable.
        Y : int
            NodeId of the outcome variable.
        Z : set of int
            Candidate mediator set (NodeIds).

        Returns
        -------
        bool
            True if Z satisfies the frontdoor criterion for (X, Y).

        """
        return _pyagrum.DoorCriteria_satisfiesFrontdoorCriterion(dag, X, Y, Z)

    @staticmethod
    def enumerateFrontdoorSets(*args) -> "pyagrum.DoorCriteria::NodeSetVec":
        return _pyagrum.DoorCriteria_enumerateFrontdoorSets(*args)

    @staticmethod
    def firstFrontdoor(*args) -> list[int] | None:
        r"""

        Return the first valid frontdoor adjustment set for the causal effect of X on Y.

        Parameters
        ----------
        dag : pyagrum.DAG
            The causal DAG.
        X : int
            NodeId of the treatment variable.
        Y : int
            NodeId of the outcome variable.
        excluded_nodes : set of int, optional
            Nodes that cannot appear in any adjustment set. Default is empty.
        max_cardinality : int, optional
            Maximum size of the adjustment set. 0 means no limit. Default is 0.
        only_minimal : bool, optional
            If True, return only a minimal adjustment set. Default is True.

        Returns
        -------
        list of int or None
            The first valid frontdoor adjustment set as a list of NodeIds, or None if none exists.

        """
        return _pyagrum.DoorCriteria_firstFrontdoor(*args)

    @staticmethod
    def existsUnblockedDirectedPath(dag: "pyagrum.DAG", X: int, Y: int, Z: list[int]) -> bool:
        r"""

        Test whether a directed path from X to Y exists that is not blocked by Z.

        Parameters
        ----------
        dag : pyagrum.DAG
            The causal DAG.
        X : int
            NodeId of the source variable.
        Y : int
            NodeId of the target variable.
        Z : set of int
            Blocking set (NodeIds). A directed path is blocked if it passes through
            a node in Z.

        Returns
        -------
        bool
            True if at least one unblocked directed path from X to Y exists.

        """
        return _pyagrum.DoorCriteria_existsUnblockedDirectedPath(dag, X, Y, Z)

    @staticmethod
    def nodesOnDirectedPaths(dag: "pyagrum.DAG", X: int, Y: int) -> list[int] | None:
        r"""

        Return the set of nodes lying on any directed path from X to Y.

        Parameters
        ----------
        dag : pyagrum.DAG
            The causal DAG.
        X : int
            NodeId of the source variable.
        Y : int
            NodeId of the target variable.

        Returns
        -------
        set of int
            NodeIds of all nodes (including X and Y) that lie on at least one
            directed path from X to Y. Empty if no directed path exists.

        """
        return _pyagrum.DoorCriteria_nodesOnDirectedPaths(dag, X, Y)

    @staticmethod
    def backdoorReach(dag: "pyagrum.DAG", X: int) -> list[int]:
        r"""

        Return all nodes reachable from X via a backdoor path.

        A backdoor path starts with an arc pointing *into* X (i.e. it begins by
        going to a parent of X) and then follows any sequence of edges.

        Parameters
        ----------
        dag : pyagrum.DAG
            The causal DAG.
        X : int
            NodeId of the source variable.

        Returns
        -------
        set of int
            NodeIds of all nodes reachable from X via a backdoor path.

        """
        return _pyagrum.DoorCriteria_backdoorReach(dag, X)

    @staticmethod
    def hasBackdoorPath(dag: "pyagrum.DAG", X: int, Y: int, Z: list[int]) -> bool:
        r"""

        Test whether an open backdoor path from X to Y exists given evidence on Z.

        Parameters
        ----------
        dag : pyagrum.DAG
            The causal DAG.
        X : int
            NodeId of the treatment variable.
        Y : int
            NodeId of the outcome variable.
        Z : set of int
            Conditioning set (NodeIds).

        Returns
        -------
        bool
            True if an open backdoor path from X to Y exists after conditioning on Z.

        """
        return _pyagrum.DoorCriteria_hasBackdoorPath(dag, X, Y, Z)

    @staticmethod
    def enumerateBackdoorSets(dag, X, Y, *, excluded_nodes=None, max_cardinality=0,
                                            only_minimal=True, stopAtFirst=False):
        """
        Enumerate valid backdoor adjustment sets for the causal effect of X on Y.

        Parameters
        ----------
        dag : pyagrum.DAG
            The causal DAG.
        X : int
            NodeId of the treatment variable.
        Y : int
            NodeId of the outcome variable.
        excluded_nodes : set of int, optional
            Nodes that cannot appear in any adjustment set. Default is empty.
        max_cardinality : int, optional
            Maximum size of returned sets. 0 means no limit. Default is 0.
        only_minimal : bool, optional
            If True, return only minimal adjustment sets (no redundant variables).
            Default is True.
        stopAtFirst : bool, optional
            If True, stop after finding the first valid set. Default is False.

        Returns
        -------
        list of set of int
            All valid backdoor adjustment sets (as NodeId sets).
        """
        return _pyagrum.DoorCriteria_enumerateBackdoorSets(
            dag, X, Y,
            excluded_nodes if excluded_nodes is not None else set(),
            max_cardinality, only_minimal, stopAtFirst)

    @staticmethod
    def enumerateFrontdoorSets(dag, X, Y, *, excluded_nodes=None, max_cardinality=0,
                                             only_minimal=True, stopAtFirst=False):
        """
        Enumerate valid frontdoor adjustment sets for the causal effect of X on Y.

        Parameters
        ----------
        dag : pyagrum.DAG
            The causal DAG.
        X : int
            NodeId of the treatment variable.
        Y : int
            NodeId of the outcome variable.
        excluded_nodes : set of int, optional
            Nodes that cannot appear in any adjustment set. Default is empty.
        max_cardinality : int, optional
            Maximum size of returned sets. 0 means no limit. Default is 0.
        only_minimal : bool, optional
            If True, return only minimal adjustment sets. Default is True.
        stopAtFirst : bool, optional
            If True, stop after finding the first valid set. Default is False.

        Returns
        -------
        list of set of int
            All valid frontdoor adjustment sets (as NodeId sets).
        """
        return _pyagrum.DoorCriteria_enumerateFrontdoorSets(
            dag, X, Y,
            excluded_nodes if excluded_nodes is not None else set(),
            max_cardinality, only_minimal, stopAtFirst)


    def __init__(self):
        _pyagrum.DoorCriteria_swiginit(self, _pyagrum.new_DoorCriteria())
    __swig_destroy__ = _pyagrum.delete_DoorCriteria

# Register DoorCriteria in _pyagrum:
_pyagrum.DoorCriteria_swigregister(DoorCriteria)

def _causalImpact(*args) -> tuple["pyagrum.CausalImpact","pyagrum.Tensor",str]:
    return _pyagrum._causalImpact(*args)
class CausalModel(object):
    r"""

    A causal model pairing an observational Bayesian network with a causal DAG.

    A CausalModel extends an observational BayesNet by adding latent (hidden)
    variables that represent unobserved common causes between observed variables.
    The causal DAG includes both observed and latent nodes, while the observational
    BN contains only the observed ones.

    CausalModel(bn) -> CausalModel
        Parameters:
            - **bn** (*pyagrum.BayesNet*) -- the observational Bayesian network.

    CausalModel(bn, latents, assumeNonSpurious=False) -> CausalModel
        Parameters:
            - **bn** (*pyagrum.BayesNet*) -- the observational Bayesian network.
            - **latents** (*list of (str, list of str)*) -- description of latent
              variables. Each entry is a pair ``(name, children)`` where ``name``
              is the latent variable name and ``children`` is the list of observed
              variable names it affects.
            - **assumeNonSpurious** (*bool*) -- if True, existing arcs between the children
              of each latent variable are preserved. Default is False (arcs between
              affected children are removed as they are assumed to be explained by
              the latent confounder).

    Examples
    --------
    >>> import pyagrum as gum
    >>> bn = pyagrum.BayesNet.fastPrototype('X->Y;X->Z;Y->Z')
    >>> cm = pyagrum.CausalModel(bn)

    Create a model with a latent confounder U between X and Y:

    >>> cm = pyagrum.CausalModel(bn, [('U', ['X', 'Y'])], assumeNonSpurious=False)

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr
    __swig_destroy__ = _pyagrum.delete_CausalModel

    def addLatentVariable(self, *args) -> None:
        r"""

        Add a latent (hidden) variable to the causal model.

        The latent variable is added as a common cause of the specified children.
        By default, any existing arc between two affected children is removed, as
        it is assumed to be explained by the new latent confounder.

        Parameters
        ----------
        name : str
            Name of the new latent variable.
        children : list of str
            Names of the observed variables that are children of this latent variable.
        assumeNonSpurious : bool, optional
            If True, preserve existing arcs between the specified children.
            Default is False.

        """
        return _pyagrum.CausalModel_addLatentVariable(self, *args)

    def existsArc(self, *args) -> bool:
        r"""

        Check whether an arc exists in the causal DAG.

        Parameters
        ----------
        x : int or str
            Tail of the arc (NodeId or variable name).
        y : int or str
            Head of the arc (NodeId or variable name).

        Returns
        -------
        bool
            True if the arc x→y exists in the causal DAG.

        """
        return _pyagrum.CausalModel_existsArc(self, *args)

    def assumeSpurious(self, *args) -> None:
        r"""

        Mark an arc in the causal DAG as spurious.

        A spurious arc x→y means the observed correlation between x and y is believed
        to be explained by a latent common cause rather than a direct causal effect.
        This changes the structural interpretation but does not remove the arc.

        Parameters
        ----------
        x : int or str
            Tail of the arc (NodeId or variable name).
        y : int or str
            Head of the arc (NodeId or variable name).

        """
        return _pyagrum.CausalModel_assumeSpurious(self, *args)

    def assumeNonSpurious(self, *args) -> None:
        r"""

        Mark an arc in the causal DAG as non-spurious (a genuine causal effect).

        Parameters
        ----------
        x : int or str
            Tail of the arc (NodeId or variable name).
        y : int or str
            Head of the arc (NodeId or variable name).

        """
        return _pyagrum.CausalModel_assumeNonSpurious(self, *args)

    def isAssumedSpurious(self, *args) -> bool:
        r"""

        Check whether the arc x→y is assumed spurious.

        Parameters
        ----------
        x : int or str
            Tail of the arc (NodeId or variable name).
        y : int or str
            Head of the arc (NodeId or variable name).

        Returns
        -------
        bool
            True if the arc x→y is marked as spurious.

        """
        return _pyagrum.CausalModel_isAssumedSpurious(self, *args)

    def backDoor(self, *args) -> list[int] | None:
        r"""

        Find a backdoor adjustment set between cause and effect.

        Returns the first valid backdoor adjustment set found. A backdoor set Z
        blocks all spurious (non-causal) paths between cause and effect while
        leaving all directed causal paths open, enabling estimation of the causal
        effect P(effect | do(cause)) via standard conditioning on Z.

        Parameters
        ----------
        cause : int or str
            The treatment variable (NodeId or variable name).
        effect : int or str
            The outcome variable (NodeId or variable name).

        Returns
        -------
        set of int or None
            A valid backdoor adjustment set as NodeIds, or ``None`` if no backdoor set
            exists. Note: an empty set is a *valid* backdoor (returned when X has no
            back-door paths); ``None`` means the causal effect cannot be identified via
            the backdoor criterion.

        See Also
        --------
        pyagrum.DoorCriteria.enumerateBackdoorSets : enumerate all valid sets.

        """
        return _pyagrum.CausalModel_backDoor(self, *args)

    def frontDoor(self, *args) -> list[int] | None:
        r"""

        Find a frontdoor adjustment set between cause and effect.

        Returns the first valid frontdoor adjustment set found. A frontdoor set Z
        intercepts all directed paths from cause to effect and enables estimation of
        P(effect | do(cause)) even in the presence of unobserved confounders.

        Parameters
        ----------
        cause : int or str
            The treatment variable (NodeId or variable name).
        effect : int or str
            The outcome variable (NodeId or variable name).

        Returns
        -------
        set of int or None
            A valid frontdoor adjustment set as NodeIds, or ``None`` if no frontdoor
            set exists. Note: an empty set is a *valid* frontdoor in degenerate cases;
            ``None`` means the causal effect cannot be identified via the frontdoor
            criterion.

        See Also
        --------
        pyagrum.DoorCriteria.enumerateFrontdoorSets : enumerate all valid sets.

        """
        return _pyagrum.CausalModel_frontDoor(self, *args)

    def inducedCausalSubModel(self, cm: "CausalModel", subset: list[int]) -> "pyagrum.CausalModel":
        r"""

        Return the causal sub-model induced by a subset of observed nodes.

        The sub-model is restricted to the specified nodes, preserving the relevant
        portion of the causal DAG and latent structure.

        Parameters
        ----------
        cm : pyagrum.CausalModel
            The original causal model.
        subset : set of int
            NodeIds of the observed variables to keep.

        Returns
        -------
        pyagrum.CausalModel
            The induced causal sub-model.

        """
        return _pyagrum.CausalModel_inducedCausalSubModel(self, cm, subset)

    def toDot(self, *args) -> str:
        r"""

        Return a Graphviz dot string representing the causal model.

        Observed nodes are shown with default styling. Latent nodes are displayed
        with a distinct background colour. Their names are hidden by default.

        Parameters
        ----------
        SHOW_LATENT_NAMES : bool, optional
            If True, display the names of latent nodes in the graph. Default is False.
        NODE_BG : str, optional
            Background colour for latent nodes (hex or CSS colour name).
            Default is '#404040'.
        NODE_FG : str, optional
            Text colour for latent nodes. Default is 'white'.
        EDGE_COL : str, optional
            Edge colour. Default is '#4A4A4A'.

        Returns
        -------
        str
            A dot-format string representation of the causal model.

        Examples
        --------
        >>> import pyagrum as gum
        >>> bn = pyagrum.BayesNet.fastPrototype('X->Y->Z')
        >>> cm = pyagrum.CausalModel(bn)
        >>> print(cm.toDot())

        """
        return _pyagrum.CausalModel_toDot(self, *args)

    def observationalBN(self) -> "pyagrum.BayesNet":
        r"""

        Return the observational Bayesian network underlying the causal model.

        Warnings
        --------
        Do not use this BN for causal inference. It represents the observational
        distribution only. Use :func:`pyagrum.causalImpact` for
        interventional queries.

        Returns
        -------
        pyagrum.BayesNet
            The observational BN (observed variables only).

        """
        return _pyagrum.CausalModel_observationalBN(self)

    def causalDAG(self) -> "pyagrum.DAG":
        r"""

        Return the full causal DAG, including latent variables.

        Returns
        -------
        pyagrum.DAG
            The causal DAG (observed + latent nodes).

        """
        return _pyagrum.CausalModel_causalDAG(self)

    def names(self) -> set[str]:
        r"""

        Return the names of all variables in the causal model (observed and latent).

        Returns
        -------
        set of str
            The set of all variable names.

        """
        return _pyagrum.CausalModel_names(self)

    def idFromName(self, name: str) -> int:
        r"""

        Return the NodeId of a variable by name.

        Parameters
        ----------
        name : str
            The variable name.

        Returns
        -------
        int
            The NodeId of the variable.

        Raises
        ------
        pyagrum.NotFound
            If no variable with that name exists in the causal model.

        """
        return _pyagrum.CausalModel_idFromName(self, name)

    def nameFromId(self, id: int) -> str:
        r"""

        Return the name of a variable by NodeId.

        Parameters
        ----------
        id : int
            The NodeId of the variable.

        Returns
        -------
        str
            The variable name.

        Raises
        ------
        pyagrum.NotFound
            If no variable with that NodeId exists in the causal model.

        """
        return _pyagrum.CausalModel_nameFromId(self, id)

    def latentVariablesIds(self) -> list[int]:
        r"""

        Return the NodeIds of all latent (hidden) variables in the causal model.

        Returns
        -------
        set of int
            NodeIds of latent variables.

        """
        return _pyagrum.CausalModel_latentVariablesIds(self)

    def latentVariablesNames(self) -> set[str]:
        r"""

        Return the names of all latent (hidden) variables in the causal model.

        Returns
        -------
        set of str
            Names of latent variables.

        """
        return _pyagrum.CausalModel_latentVariablesNames(self)

    def parents(self, *args) -> list[int]:
        r"""

        Return the parents of a variable in the causal DAG.

        Parameters
        ----------
        x : int or str
            The variable (NodeId or name).

        Returns
        -------
        set of int
            NodeIds of the variable's parents in the causal DAG.

        """
        return _pyagrum.CausalModel_parents(self, *args)

    def children(self, *args) -> list[int]:
        r"""

        Return the children of a variable in the causal DAG.

        Parameters
        ----------
        x : int or str
            The variable (NodeId or name).

        Returns
        -------
        set of int
            NodeIds of the variable's children in the causal DAG.

        """
        return _pyagrum.CausalModel_children(self, *args)

    def connectedComponents(self) -> dict[int, list[int]]:
        r"""

        Return the connected components of the causal DAG (treating arcs as undirected).

        Returns
        -------
        dict of int → set of int
            A mapping from component index to the set of NodeIds in that component.

        """
        return _pyagrum.CausalModel_connectedComponents(self)

    def variable(self, *args) -> "pyagrum.DiscreteVariable":
        r"""

        Return the variable with the given id or name (observed variables only).

        Parameters
        ----------
        id_or_name : int | str
            the node id or name of the variable

        Returns
        -------
        pyagrum.DiscreteVariable
            the discrete variable

        Raises
        ------
        pyagrum.NotFound
            if the id or name does not correspond to an observed variable in the model

        """
        return _pyagrum.CausalModel_variable(self, *args)

    def __init__(self, *args):
        _pyagrum.CausalModel_swiginit(self, _pyagrum.new_CausalModel(*args))

# Register CausalModel in _pyagrum:
_pyagrum.CausalModel_swigregister(CausalModel)
class CausalImpact(object):
    r"""

    Represents the result of a causal identification query P(on | do(doing), knowing).

    CausalImpact encodes the identified causal formula (when the effect is
    identifiable) as an expression tree that can be evaluated numerically.
    It also records the identification method used and an explanation string.

    The identification procedure tries the following strategies in order:
    d-separation (no effect), backdoor adjustment, frontdoor adjustment, and
    general do-calculus (ID algorithm).

    Notes
    -----
    You may prefer to use the high-level function :func:`pyagrum.causalImpact`
    instead of constructing a CausalImpact object directly.

    CausalImpact(cm, \*, on, doing, knowing=None) -> CausalImpact
        Parameters:
            - **cm** (*pyagrum.CausalModel*) -- the causal model.
            - **on** (*str or set of str*) -- target variable(s) of the query.
              A single string is automatically converted to a one-element set.
              Keyword-only.
            - **doing** (*str or set of str*) -- intervened variable(s)
              (the do-operator applies to these). A single string is automatically
              converted to a one-element set. Keyword-only.
            - **knowing** (*str or set of str, optional*) -- observed variable(s)
              to condition on. A single string is automatically converted to a
              one-element set. Default is empty. Keyword-only.

    Examples
    --------
    >>> import pyagrum as gum
    >>> bn = pyagrum.BayesNet.fastPrototype('X->Y->Z')
    >>> cm = pyagrum.CausalModel(bn)
    >>> formula, tensor, expl = pyagrum.causalImpact(cm, on='Z', doing='X')
    >>> print(expl)

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def eval(self) -> "pyagrum.Tensor":
        r"""

        Evaluate the identified causal formula and return the result as a tensor.

        Returns
        -------
        pyagrum.Tensor
            The distribution P(on | do(doing), knowing).

        Raises
        ------
        pyagrum.OperationNotAllowed
            If the causal effect is not identified (:meth:`isIdentified` is False).

        """
        return _pyagrum.CausalImpact_eval(self)

    def toString(self) -> str:
        r"""

        Return a string representation of the identified causal formula.

        Returns
        -------
        str
            Human-readable form of the formula (e.g. a sum-product expression over
            conditional probabilities).

        """
        return _pyagrum.CausalImpact_toString(self)

    def toLatex(self, *, doOperatorPrefix=None, doOperatorSuffix=None):
        """
        Return a LaTeX string representation of the causal impact formula.

        Parameters
        ----------
        doOperatorPrefix : str, optional
            LaTeX prefix for the do-operator. Defaults to the value in pyAgrum config.
        doOperatorSuffix : str, optional
            LaTeX suffix for the do-operator. Defaults to the value in pyAgrum config.

        Returns
        -------
        str
            LaTeX representation of the causal impact expression.
        """
        if doOperatorPrefix is None:
            doOperatorPrefix = config["causal", "latex_do_prefix"]
        if doOperatorSuffix is None:
            doOperatorSuffix = config["causal", "latex_do_suffix"]
        return _pyagrum.CausalImpact_toLatex(self, doOperatorPrefix, doOperatorSuffix)



    def latexQuery(self, *args) -> str:
        r"""

        Return a LaTeX string for the original causal query before identification.

        The query has the form P(on | do(doing), knowing).

        Parameters
        ----------
        doOperatorPrefix : str, optional
            Prefix for the do-operator notation. Default is 'do('.
        doOperatorSuffix : str, optional
            Suffix for the do-operator notation. Default is ')'.

        Returns
        -------
        str
            LaTeX string of the causal query.

        """
        return _pyagrum.CausalImpact_latexQuery(self, *args)

    def isIdentified(self) -> bool:
        r"""

        Return True if the causal effect was successfully identified.

        If False, the effect cannot be computed from the available data given
        the causal structure, and :meth:`eval` will raise an exception.

        Returns
        -------
        bool
            True if the query is identifiable.

        """
        return _pyagrum.CausalImpact_isIdentified(self)

    def cm(self) -> "pyagrum.CausalModel":
        r"""

        Return the causal model associated with this query.

        Returns
        -------
        pyagrum.CausalModel
            The causal model.

        """
        return _pyagrum.CausalImpact_cm(self)

    def on(self) -> list[int]:
        r"""

        Return the NodeIds of the target variables.

        Returns
        -------
        set of int
            NodeIds of the variables in the on-set.

        """
        return _pyagrum.CausalImpact_on(self)

    def doing(self) -> list[int]:
        r"""

        Return the NodeIds of the intervened variables.

        Returns
        -------
        set of int
            NodeIds of the variables in the doing-set (do-operator).

        """
        return _pyagrum.CausalImpact_doing(self)

    def knowing(self) -> list[int]:
        r"""

        Return the NodeIds of the observed variables.

        Returns
        -------
        set of int
            NodeIds of the variables in the knowing-set.

        """
        return _pyagrum.CausalImpact_knowing(self)

    def explanation(self) -> str:
        r"""

        Return a human-readable explanation of the identification result.

        Describes the method used (e.g. 'd-separation', 'backdoor adjustment',
        'frontdoor adjustment', 'do-calculus (ID)') or explains why identification
        failed.

        Returns
        -------
        str
            Explanation of the identification outcome.

        """
        return _pyagrum.CausalImpact_explanation(self)

    def onNames(self) -> tuple[str, ...]:
        r"""

        Return the names of the target variables.

        Returns
        -------
        tuple of str
            Variable names in the on-set.

        """
        return _pyagrum.CausalImpact_onNames(self)

    def doingNames(self) -> tuple[str, ...]:
        r"""

        Return the names of the intervened variables.

        Returns
        -------
        tuple of str
            Variable names in the doing-set.

        """
        return _pyagrum.CausalImpact_doingNames(self)

    def knowingNames(self) -> tuple[str, ...]:
        r"""

        Return the names of the observed variables.

        Returns
        -------
        tuple of str
            Variable names in the knowing-set.

        """
        return _pyagrum.CausalImpact_knowingNames(self)

    def __init__(self, cm, *, on, doing, knowing=None):
        if isinstance(on, str):
            on = {on}
        if isinstance(doing, str):
            doing = {doing}
        if isinstance(knowing, str):
            knowing = {knowing}
        _pyagrum.CausalImpact_swiginit(self, _pyagrum.new_CausalImpact(cm, on, doing,
                                       knowing if knowing is not None else set(),
                                       False))



    def print_ast(self):
      """
      Print the AST of a CausalImpact function in a human readable way.

      Parameters
      ----------
        impact : pyagrum.CausalImpact
          the function whose AST we want to print
      """
      def print_ast_rec(node, name="", indent="  ", last=True):
        marker = "└─ " if last else "├─ "

        if isinstance(node, dict):
            label = node.get("op", name)
            print(f"{indent}{marker}{label}")

            indent += "   " if last else "│  "

            children = [(k, v) for k, v in node.items() if k != "op"]

            for i, (key, value) in enumerate(children):
                is_last = i == len(children) - 1
                print_ast_rec(value, name=key, indent=indent, last=is_last)
        else:
            print(f"{indent}{marker}{name}: {node}")

      def print_title(label:"AST"):
        width = len(label) + 2
        print(f"┌{'─' * width}┐")
        print(f"│ {label} │")
        print(f"└{'─' * width}┘")
        return int(width/2)

      def get_title(f:pyagrum.CausalImpact):
        label=f"P({','.join(f.onNames())}|do({','.join(f.doingNames())})"
        if len(f.knowingNames())>0:
              label+=f",{','.join(f.knowingNames())}"
        label+=")"
        return label

      print_ast_rec(self.toDict(),indent=" "*print_title(get_title(self)))


    def toDict(self) -> dict[str, object]:
        r"""

        Return the identified causal formula as a JSON-serialisable dictionary.

        The dictionary mirrors the AST node hierarchy. Each node is a dict with
        an ``"op"`` key identifying its type, plus type-specific keys:

        - Binary operators (``+``, ``-``, ``*``, ``/``):
          ``{"op": "+", "op1": {...}, "op2": {...}}``
        - Conditional probability ``P(vars | knowing)``:
          ``{"op": "P", "vars": [...], "knowing": [...]}``
        - Joint probability ``P(vars)``:
          ``{"op": "P", "vars": [...]}``
        - Summation / marginalisation :math:`\sum_{\text{var}}`:
          ``{"op": "sum", "var": "...", "term": {...}}``

        Returns ``None`` if the effect is not identified
        (i.e. :meth:`isIdentified` is False).

        Returns
        -------
        dict or None
            The AST as a nested dict, or None if not identifiable.

        Examples
        --------
        >>> import pyagrum as gum
        >>> bn = pyagrum.BayesNet.fastPrototype('X->Y->Z')
        >>> cm = pyagrum.CausalModel(bn)
        >>> ci = pyagrum.CausalImpact(cm, on='Z', doing='X')
        >>> import json
        >>> print(json.dumps(ci.toDict(), indent=2))

        """
        return _pyagrum.CausalImpact_toDict(self)
    __swig_destroy__ = _pyagrum.delete_CausalImpact

# Register CausalImpact in _pyagrum:
_pyagrum.CausalImpact_swigregister(CausalImpact)
class Counterfactual(object):
    r"""

    Computes a counterfactual distribution using Pearl's twin network method.

    A counterfactual query asks: 'Given that we observed *profile*, what would
    *on* have been if *whatif* had been set to the values in *values*?'

    The computation follows the three-step algorithm from Pearl (2018),
    *The Book of Why*, chapter 8:

      1. **Abduction** -- compute the posterior distribution of parentless
         (idiosyncratic) nodes in the original BN given the observed *profile*.
      2. **Action** -- build a *twin* causal model in which the priors of those
         nodes are replaced by their posteriors from step 1, then apply
         ``do(whatif)`` on the twin.
      3. **Prediction** -- evaluate the causal impact of the intervention on
         the twin model to obtain the counterfactual distribution of *on*.

    Notes
    -----
    Prefer using the high-level function :func:`pyagrum.counterfactual`
    instead of constructing a Counterfactual object directly.

    Counterfactual(cm, on, whatif, profile={}, values={}) -> Counterfactual
        Parameters:
            - **cm** (*pyagrum.CausalModel*) -- the causal model.
            - **on** (*set of str or set of int*) -- target variables of the
              counterfactual query.
            - **whatif** (*set of str or set of int*) -- variables whose values
              are changed in the counterfactual scenario.
            - **profile** (*dict of str → str*) -- the factual observation,
              given as ``{variable_name: value_name}``. Default is empty.
            - **values** (*dict of str → str*) -- counterfactual values for the
              *whatif* variables, given as ``{variable_name: value_name}``.
              If omitted, the full joint distribution over all *whatif* values
              is returned.

    Examples
    --------
    >>> import pyagrum as gum
    >>> bn = pyagrum.BayesNet.fastPrototype('X->Y->Z')
    >>> cm = pyagrum.CausalModel(bn)
    >>> t = pyagrum.counterfactual(cm, on='Z', whatif='X',
    ...                        profile={'Y': 'True'}, values={'X': 'False'})

    """

    thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
    __repr__ = _swig_repr

    def __init__(self, *args):
        _pyagrum.Counterfactual_swiginit(self, _pyagrum.new_Counterfactual(*args))

    @staticmethod
    def counterFactualModel(*args) -> "pyagrum.CausalModel":
        r"""

        Build the twin causal model from the original model and a factual profile.

        This static method implements steps 1-2 of the three-step counterfactual
        algorithm: it computes the posterior of parentless (idiosyncratic) nodes
        in the observational BN given the profile evidence, then replaces their
        prior distributions in a copy of the model with those posteriors.

        Parameters
        ----------
        cm : pyagrum.CausalModel
            The original causal model.
        profile : dict of str → str
            The factual observation as ``{variable_name: value_name}``.
        whatif : set of str
            Intervened variables in the counterfactual scenario. These are excluded
            from the set of idiosyncratic nodes that get updated.

        Returns
        -------
        pyagrum.CausalModel
            The twin causal model ready for the prediction step.

        See Also
        --------
        pyagrum.counterfactualModel : high-level function wrapper.

        """
        return _pyagrum.Counterfactual_counterFactualModel(*args)

    def run(self) -> None:
        r"""

        Execute the counterfactual computation (steps 2 and 3).

        This method is called automatically by the constructor. Call it explicitly
        only if you need to re-run after modifying the object's state.

        """
        return _pyagrum.Counterfactual_run(self)

    def originalModel(self) -> "pyagrum.CausalModel":
        r"""

        Return the original causal model.

        Returns
        -------
        pyagrum.CausalModel
            The original causal model passed to the constructor.

        """
        return _pyagrum.Counterfactual_originalModel(self)

    def twinModel(self) -> "pyagrum.CausalModel":
        r"""

        Return the twin causal model built during the abduction step.

        The twin model has the same structure as the original but with updated
        priors for parentless nodes (based on the observed profile).

        Returns
        -------
        pyagrum.CausalModel
            The twin model.

        """
        return _pyagrum.Counterfactual_twinModel(self)

    def impact(self) -> "pyagrum.CausalImpact":
        r"""
        Return the CausalImpact used to compute the counterfactual distribution.

        The formula is evaluated on the twin model.

        Returns
        -------
        pyagrum.CausalImpact
            The identified causal impact on the twin model.
        """
        return _pyagrum.Counterfactual_impact(self)



    def value(self) -> "pyagrum.Tensor":
        r"""

        Return the counterfactual distribution as a tensor.

        Returns
        -------
        pyagrum.Tensor
            The distribution P(on | do(whatif)) evaluated on the twin model,
            optionally sliced by *values* if provided at construction.

        """
        return _pyagrum.Counterfactual_value(self)

    def on(self) -> set[str]:
        r"""

        Return the names of the target variables.

        Returns
        -------
        set of str
            Variable names in the on-set.

        """
        return _pyagrum.Counterfactual_on(self)

    def whatif(self) -> set[str]:
        r"""

        Return the names of the intervened variables.

        Returns
        -------
        set of str
            Variable names in the whatif-set.

        """
        return _pyagrum.Counterfactual_whatif(self)

    def profile(self) -> dict[str,str]:
        r"""

        Return the factual evidence used for abduction.

        Returns
        -------
        dict of str → str
            The observed profile as ``{variable_name: value_name}``.

        """
        return _pyagrum.Counterfactual_profile(self)

    def values(self) -> dict[str,str]:
        r"""

        Return the counterfactual assignments for the whatif variables.

        Returns
        -------
        dict of str → str
            The counterfactual values as ``{variable_name: value_name}``.
            Empty dict if no specific values were requested.

        """
        return _pyagrum.Counterfactual_values(self)

    def toString(self) -> str:
        r"""

        Return a string description of the counterfactual query and its result.

        Returns
        -------
        str
            Human-readable summary of the counterfactual computation.

        """
        return _pyagrum.Counterfactual_toString(self)
    __swig_destroy__ = _pyagrum.delete_Counterfactual

# Register Counterfactual in _pyagrum:
_pyagrum.Counterfactual_swigregister(Counterfactual)

def _counterfactual(*args) -> "pyagrum.Tensor":
    return _pyagrum._counterfactual(*args)

def _counterfactualModel(cm: "CausalModel", profile: dict[str, str], whatif: set[str]) -> "pyagrum.CausalModel":
    return _pyagrum._counterfactualModel(cm, profile, whatif)

