/****************************************************************************
 *   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                           *
 *                                                                          *
 ****************************************************************************/

#pragma once


/**
 * @file
 * @brief Implementation of the non pure virtual methods of class
 * GraphicalModelInference.
 */

#include <agrum/base/graphicalModels/inference/graphicalModelInference.h>

namespace gum {


  // Default Constructor
  template < GUM_Numeric GUM_SCALAR >
  GraphicalModelInference< GUM_SCALAR >::GraphicalModelInference(const GraphicalModel* model) :
      _model_(model) {
    _computeDomainSizes_();

    GUM_CONSTRUCTOR(GraphicalModelInference);
  }

  // Default Constructor
  template < GUM_Numeric GUM_SCALAR >
  GraphicalModelInference< GUM_SCALAR >::GraphicalModelInference() {
    GUM_CONSTRUCTOR(GraphicalModelInference);
  }

  // Destructor
  template < GUM_Numeric GUM_SCALAR >
  GraphicalModelInference< GUM_SCALAR >::~GraphicalModelInference() {
    // clear all evidence.
    // Warning: Do not use method eraseAllEvidence () because it contains a call
    // to pure virtual method onAllEvidenceErased_ which belongs to an inherited
    // instance and, therefore, does not exist anymore when
    // ~GraphicalModelInference () is called
    for (const auto& pair: _evidence_) {
      if (pair.second != nullptr) { delete (pair.second); }
    }

    GUM_DESTRUCTOR(GraphicalModelInference);
  }

  // returns whether the inference object is in a ready state
  template < GUM_Numeric GUM_SCALAR >
  bool GraphicalModelInference< GUM_SCALAR >::isInferenceReady() const noexcept {
    return (_state_ == StateOfInference::ReadyForInference);
  }

  // returns whether the inference object is in a OutdatedStructure state
  template < GUM_Numeric GUM_SCALAR >
  bool GraphicalModelInference< GUM_SCALAR >::isInferenceOutdatedStructure() const noexcept {
    return (_state_ == StateOfInference::OutdatedStructure);
  }

  // returns whether the inference object is in a OutdatedTensor state
  template < GUM_Numeric GUM_SCALAR >
  bool GraphicalModelInference< GUM_SCALAR >::isInferenceOutdatedTensors() const noexcept {
    return (_state_ == StateOfInference::OutdatedTensors);
  }

  // returns whether the inference object is in a InferenceDone state
  template < GUM_Numeric GUM_SCALAR >
  bool GraphicalModelInference< GUM_SCALAR >::isInferenceDone() const noexcept {
    return (_state_ == StateOfInference::Done);
  }

  // returns the state of the inference engine
  template < GUM_Numeric GUM_SCALAR >
  typename GraphicalModelInference< GUM_SCALAR >::StateOfInference
      GraphicalModelInference< GUM_SCALAR >::state() const noexcept {
    return _state_;
  }

  // set the state of the inference
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::setState_(const StateOfInference state) {
    if (_state_ != state) {
      _state_ = state;
      onStateChanged_();
    }
  }

  // Returns a constant reference over the IBayesNet referenced by this class
  template < GUM_Numeric GUM_SCALAR >
  const GraphicalModel& GraphicalModelInference< GUM_SCALAR >::model() const {
    if (_model_ == nullptr)
      GUM_ERROR(UndefinedElement,
                "No Bayes net has been assigned to "
                "the inference algorithm.");
    return *_model_;
  }

  // assigns a new BN to the inference engine
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::setModel_(const GraphicalModel* model) {
    clear();
    _model_ = model;
    _computeDomainSizes_();
    onModelChanged_(model);
    setState_(StateOfInference::OutdatedStructure);
  }

  // assigns a BN to a newly constructed inference engine
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::setModelDuringConstruction_(
      const GraphicalModel* model) {
    _model_ = model;
    _computeDomainSizes_();
    setState_(StateOfInference::OutdatedStructure);
  }

  // clears all the data structures allocated for the last inference
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::clear() {
    eraseAllEvidence();
    setState_(StateOfInference::OutdatedStructure);
  }

  /// computes the domain sizes of the random variables
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::_computeDomainSizes_() {
    _domain_sizes_.clear();
    if (!hasNoModel_()) {
      for (auto node: _model_->nodes()) {
        _domain_sizes_.insert(node, _model_->variable(node).domainSize());
      }
    }
  }

  // get the domain sizes of the random variables of the BN
  template < GUM_Numeric GUM_SCALAR >
  const NodeProperty< Size >& GraphicalModelInference< GUM_SCALAR >::domainSizes() const {
    return _domain_sizes_;
  }

  // ##############################################################################
  // Evidence
  // ##############################################################################

  // create the internal structure for a hard evidence
  template < GUM_Numeric GUM_SCALAR >
  Tensor< GUM_SCALAR >
      GraphicalModelInference< GUM_SCALAR >::_createHardEvidence_(NodeId id, const Idx val) const {
    // check that it is possible to create the evidence
    if (_model_ == nullptr)
      GUM_ERROR(NullElement,
                "No Bayes net has been assigned to the "
                "inference algorithm");

    if (!_model_->exists(id)) { GUM_ERROR(UndefinedElement, id << " is not a NodeId in the model") }

    if (_model_->variable(id).domainSize() <= val) {
      GUM_ERROR(InvalidArgument,
                "node " << _model_->variable(id) << " has fewer possible values than " << val);
    }

    return Tensor< GUM_SCALAR >::deterministicTensor(_model_->variable(id), val);
  }

  // checks wether a tensor corresponds to a hard evidence
  template < GUM_Numeric GUM_SCALAR >
  bool GraphicalModelInference< GUM_SCALAR >::_isHardEvidence_(const Tensor< GUM_SCALAR >& pot,
                                                               Idx& val) const {
    // checking if pot is determininstic
    bool          notZero = false;
    Instantiation I(pot);

    for (I.setFirst(); !I.end(); I.inc()) {
      if (pot[I] != GUM_SCALAR(0.0)) {
        if (notZero) {   // we already met a non-zero value
          return false;
        } else {
          val     = I.val(0);
          notZero = true;   // this is the first met non-zero value
        }
      }
    }

    if (!notZero) {   // we met no non-zero value
      GUM_ERROR(FatalError, "Evidence of impossibility (vector of 0s)")
    }

    return true;   // pot is deterministic
  }

  // adds a new hard evidence on node id
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::addEvidence(NodeId id, const Idx val) {
    addEvidence(_createHardEvidence_(id, val));
  }

  // adds a new hard evidence on node id
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::addEvidence(std::string_view nodeName,
                                                          const Idx        val) {
    addEvidence(this->model().idFromName(nodeName), val);
  }

  // adds a new hard evidence on node id
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::addEvidence(NodeId id, std::string_view label) {
    addEvidence(id, this->model().variable(id)[label]);
  }

  // adds a new hard evidence on node id
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::addEvidence(std::string_view nodeName,
                                                          std::string_view label) {
    const NodeId id = this->model().idFromName(nodeName);
    addEvidence(id, this->model().variable(id)[label]);
  }

  // adds a new evidence on node id (might be soft or hard)
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::addEvidence(NodeId                           id,
                                                          const std::vector< GUM_SCALAR >& vals) {
    // checks that the evidence is meaningful
    if (_model_ == nullptr)
      GUM_ERROR(NullElement,
                "No Bayes net has been assigned to the "
                "inference algorithm");

    if (!_model_->exists(id)) { GUM_ERROR(UndefinedElement, id << " is not a NodeId in the model") }

    if (_model_->variable(id).domainSize() != vals.size()) {
      GUM_ERROR(InvalidArgument,
                "node " << _model_->variable(id)
                        << " and its evidence vector have different sizes.");
    }

    Tensor< GUM_SCALAR > pot;
    pot.add(_model_->variable(id));
    pot.fillWith(vals);
    addEvidence(std::move(pot));
  }

  // adds a new evidence on node id (might be soft or hard)
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::addEvidence(std::string_view                 nodeName,
                                                          const std::vector< GUM_SCALAR >& vals) {
    addEvidence(this->model().idFromName(nodeName), vals);
  }

  // adds a new evidence on node id (might be soft or hard)
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::addEvidence(Tensor< GUM_SCALAR >&& pot) {
    // check if the tensor corresponds to an evidence
    if (pot.nbrDim() != 1) { GUM_ERROR(InvalidArgument, pot << " is not mono-dimensional.") }
    if (_model_ == nullptr)
      GUM_ERROR(NullElement,
                "No Bayes net has been assigned to the "
                "inference algorithm");

    NodeId id = _model_->nodeId(pot.variable(0));

    if (hasEvidence(id)) {
      GUM_ERROR(InvalidArgument,
                " node " << id << " already has an evidence. Please use chgEvidence().");
    }

    // check whether we have a hard evidence (and also check whether the
    // tensor only contains 0 (in this case, this will automatically raise
    // an exception) )
    Idx  val              = 0;
    bool is_hard_evidence = _isHardEvidence_(pot, val);

    // insert the evidence
    _evidence_.insert(id, new Tensor< GUM_SCALAR >(std::forward< Tensor< GUM_SCALAR > >(pot)));
    if (is_hard_evidence) {   // pot is deterministic
      _hard_evidence_.insert(id, val);
      _hard_evidence_nodes_.insert(id);
    } else {
      _soft_evidence_nodes_.insert(id);
    }
    setState_(StateOfInference::OutdatedStructure);
    onEvidenceAdded_(id, is_hard_evidence);
  }

  // adds a new evidence on node id (might be soft or hard)
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::addEvidence(const Tensor< GUM_SCALAR >& pot) {
    Tensor< GUM_SCALAR > new_pot(pot);
    addEvidence(std::move(new_pot));
  }

  /// adds a new list of evidence
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::addListOfEvidence(
      const List< const Tensor< GUM_SCALAR >* >& potlist) {
    for (const auto pot: potlist)
      addEvidence(*pot);
  }

  /// adds a new set of evidence
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::addSetOfEvidence(
      const Set< const Tensor< GUM_SCALAR >* >& potset) {
    for (const auto pot: potset)
      addEvidence(*pot);
  }

  // indicates whether some node(s) have received evidence
  template < GUM_Numeric GUM_SCALAR >
  bool GraphicalModelInference< GUM_SCALAR >::hasEvidence() const {
    return !_evidence_.empty();
  }

  // indicates whether node id has received an evidence
  template < GUM_Numeric GUM_SCALAR >
  bool GraphicalModelInference< GUM_SCALAR >::hasEvidence(NodeId id) const {
    return _evidence_.exists(id);
  }

  // indicates whether node id has received a hard evidence
  template < GUM_Numeric GUM_SCALAR >
  bool GraphicalModelInference< GUM_SCALAR >::hasHardEvidence(NodeId id) const {
    return _hard_evidence_nodes_.exists(id);
  }

  // indicates whether node id has received a soft evidence
  template < GUM_Numeric GUM_SCALAR >
  bool GraphicalModelInference< GUM_SCALAR >::hasSoftEvidence(NodeId id) const {
    return _soft_evidence_nodes_.exists(id);
  }

  // indicates whether node id has received an evidence
  template < GUM_Numeric GUM_SCALAR >
  bool GraphicalModelInference< GUM_SCALAR >::hasEvidence(std::string_view nodeName) const {
    return hasEvidence(this->model().idFromName(nodeName));
  }

  // indicates whether node id has received a hard evidence
  template < GUM_Numeric GUM_SCALAR >
  bool GraphicalModelInference< GUM_SCALAR >::hasHardEvidence(std::string_view nodeName) const {
    return hasHardEvidence(this->model().idFromName(nodeName));
  }

  // indicates whether node id has received a soft evidence
  template < GUM_Numeric GUM_SCALAR >
  bool GraphicalModelInference< GUM_SCALAR >::hasSoftEvidence(std::string_view nodeName) const {
    return hasSoftEvidence(this->model().idFromName(nodeName));
  }

  // change the value of an already existing hard evidence
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::chgEvidence(NodeId id, const Idx val) {
    chgEvidence(_createHardEvidence_(id, val));
  }

  // change the value of an already existing hard evidence
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::chgEvidence(std::string_view nodeName,
                                                          const Idx        val) {
    chgEvidence(this->model().idFromName(nodeName), val);
  }

  // change the value of an already existing hard evidence
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::chgEvidence(NodeId id, std::string_view label) {
    chgEvidence(id, this->model().variable(id)[label]);
  }

  // change the value of an already existing hard evidence
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::chgEvidence(std::string_view nodeName,
                                                          std::string_view label) {
    NodeId id = this->model().idFromName(nodeName);
    chgEvidence(id, this->model().variable(id)[label]);
  }

  // change the value of an already existing evidence (might be soft or hard)
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::chgEvidence(NodeId                           id,
                                                          const std::vector< GUM_SCALAR >& vals) {
    // check whether this corresponds to an evidence
    if (_model_ == nullptr)
      GUM_ERROR(NullElement,
                "No Bayes net has been assigned to the "
                "inference algorithm");

    if (!_model_->exists(id)) { GUM_ERROR(UndefinedElement, id << " is not a NodeId in the model") }

    if (_model_->variable(id).domainSize() != vals.size()) {
      GUM_ERROR(InvalidArgument,
                "node " << _model_->variable(id) << " and its evidence have different sizes.");
    }

    // create the tensor corresponding to vals
    Tensor< GUM_SCALAR > pot;
    pot.add(_model_->variable(id));
    pot.fillWith(vals);
    chgEvidence(pot);
  }

  // change the value of an already existing evidence (might be soft or hard)
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::chgEvidence(std::string_view                 nodeName,
                                                          const std::vector< GUM_SCALAR >& vals) {
    chgEvidence(this->model().idFromName(nodeName), vals);
  }

  // change the value of an already existing evidence (might be soft or hard)
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::chgEvidence(const Tensor< GUM_SCALAR >& pot) {
    // check if the tensor corresponds to an evidence
    if (pot.nbrDim() != 1) {
      GUM_ERROR(InvalidArgument, pot << " is not a mono-dimensional tensor.")
    }
    if (_model_ == nullptr)
      GUM_ERROR(NullElement,
                "No Bayes net has been assigned to the "
                "inference algorithm");

    NodeId id = _model_->nodeId(pot.variable(0));

    if (!hasEvidence(id)) {
      GUM_ERROR(InvalidArgument, id << " has no evidence. Please use addEvidence().")
    }

    // check whether we have a hard evidence (and also check whether the
    // tensor only contains 0 (in this case, this will automatically raise
    // an exception) )
    Idx  val;
    bool is_hard_evidence = _isHardEvidence_(pot, val);

    // modify the evidence already stored
    const Tensor< GUM_SCALAR >* localPot = _evidence_[id];
    Instantiation               I(pot);
    for (I.setFirst(); !I.end(); I.inc()) {
      localPot->set(I, pot[I]);
    }

    // the inference state will be different
    // whether evidence change from Hard to Soft or not.
    bool hasChangedSoftHard = false;

    if (is_hard_evidence) {
      if (!hasHardEvidence(id)) {
        hasChangedSoftHard = true;
        _hard_evidence_.insert(id, val);
        _hard_evidence_nodes_.insert(id);
        _soft_evidence_nodes_.erase(id);
      } else {
        _hard_evidence_[id] = val;
      }
    } else {
      if (hasHardEvidence(id)) {   // evidence was hard
        _hard_evidence_.erase(id);
        _hard_evidence_nodes_.erase(id);
        _soft_evidence_nodes_.insert(id);
        hasChangedSoftHard = true;
      }
    }

    if (hasChangedSoftHard) {
      setState_(StateOfInference::OutdatedStructure);
    } else {
      if (!isInferenceOutdatedStructure()) { setState_(StateOfInference::OutdatedTensors); }
    }

    onEvidenceChanged_(id, hasChangedSoftHard);
  }

  // removed the evidence, if any, corresponding to node id
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::eraseEvidence(NodeId id) {
    if (hasEvidence(id)) {
      if (hasHardEvidence(id)) {
        onEvidenceErased_(id, true);
        _hard_evidence_.erase(id);
        _hard_evidence_nodes_.erase(id);
        setState_(StateOfInference::OutdatedStructure);
      } else {
        onEvidenceErased_(id, false);
        _soft_evidence_nodes_.erase(id);
        if (!isInferenceOutdatedStructure()) { setState_(StateOfInference::OutdatedTensors); }
      }

      delete (_evidence_[id]);
      _evidence_.erase(id);
    }
  }

  // removed the evidence, if any, corresponding to node of name nodeName
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::eraseEvidence(std::string_view nodeName) {
    eraseEvidence(this->model().idFromName(nodeName));
  }

  // removes all the evidence entered into the network
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::eraseAllEvidence() {
    bool has_hard_evidence = !_hard_evidence_.empty();
    this->onAllEvidenceErased_(has_hard_evidence);

    for (const auto& pair: _evidence_) {
      if (pair.second != nullptr) { delete (pair.second); }
    }

    _evidence_.clear();
    _hard_evidence_.clear();
    _hard_evidence_nodes_.clear();
    _soft_evidence_nodes_.clear();

    if (has_hard_evidence) {
      setState_(StateOfInference::OutdatedStructure);
    } else {
      if (!isInferenceOutdatedStructure()) { setState_(StateOfInference::OutdatedTensors); }
    }
  }

  // returns the number of evidence entered into the Bayesian network
  template < GUM_Numeric GUM_SCALAR >
  Size GraphicalModelInference< GUM_SCALAR >::nbrEvidence() const {
    return _evidence_.size();
  }

  // returns the number of hard evidence entered into the Bayesian network
  template < GUM_Numeric GUM_SCALAR >
  Size GraphicalModelInference< GUM_SCALAR >::nbrHardEvidence() const {
    return _hard_evidence_nodes_.size();
  }

  // returns the number of soft evidence entered into the Bayesian network
  template < GUM_Numeric GUM_SCALAR >
  Size GraphicalModelInference< GUM_SCALAR >::nbrSoftEvidence() const {
    return _soft_evidence_nodes_.size();
  }

  // indicate for each node with hard evidence which value it took
  template < GUM_Numeric GUM_SCALAR >
  const NodeProperty< Idx >& GraphicalModelInference< GUM_SCALAR >::hardEvidence() const {
    return _hard_evidence_;
  }

  // the set of evidence entered into the network
  template < GUM_Numeric GUM_SCALAR >
  const NodeProperty< const Tensor< GUM_SCALAR >* >&
      GraphicalModelInference< GUM_SCALAR >::evidence() const {
    return _evidence_;
  }

  /// the set of nodes that received soft evidence
  template < GUM_Numeric GUM_SCALAR >
  const NodeSet& GraphicalModelInference< GUM_SCALAR >::softEvidenceNodes() const {
    return _soft_evidence_nodes_;
  }

  /// the set of nodes that received hard evidence
  template < GUM_Numeric GUM_SCALAR >
  const NodeSet& GraphicalModelInference< GUM_SCALAR >::hardEvidenceNodes() const {
    return _hard_evidence_nodes_;
  }

  // ##############################################################################
  // Inference
  // ##############################################################################

  // put the inference into an unprepared state
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::setOutdatedStructureState_() {
    setState_(StateOfInference::OutdatedStructure);
  }

  /** puts the inference into an OutdatedTensors state if it is not
   *  already in an OutdatedStructure state */
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::setOutdatedTensorsState_() {
    setState_(StateOfInference::OutdatedTensors);
  }

  // prepare the internal inference structures for the next inference
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::prepareInference() {
    if (isInferenceReady() || isInferenceDone()) { return; }

    if (_model_ == nullptr)
      GUM_ERROR(NullElement,
                "No model been assigned to the "
                "inference algorithm");

    if (_state_ == StateOfInference::OutdatedStructure) updateOutdatedStructure_();
    else updateOutdatedTensors_();

    setState_(StateOfInference::ReadyForInference);
  }

  // perform the heavy computations needed to compute the targets' posteriors
  template < GUM_Numeric GUM_SCALAR >
  void GraphicalModelInference< GUM_SCALAR >::makeInference() {
    if (isInferenceDone()) { return; }

    if (!isInferenceReady()) { prepareInference(); }

    makeInference_();

    setState_(StateOfInference::Done);
  }

  template < GUM_Numeric GUM_SCALAR >
  bool GraphicalModelInference< GUM_SCALAR >::hasNoModel_() const {
    return _model_ == nullptr;
  }

} /* namespace gum */
