Skip to content

packages/engine/scram/src/event_tree_analysis.cc

Implementation of event tree analysis facilities.

Namespaces

Name
scram
scram::core

Source code

cpp
/*
 * Copyright (C) 2014-2018 Olzhas Rakhimov
 * Copyright (C) 2023 OpenPRA ORG Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "event_tree_analysis.h"

#include <sstream>
#include <type_traits>
#include <unordered_set>

#include "expression/numerical.h"
#include "ext/find_iterator.h"
#include "instruction.h"

namespace scram::core {

EventTreeAnalysis::EventTreeAnalysis(
    const mef::InitiatingEvent& initiating_event, const Settings& settings,
    mef::Context* context)
    : Analysis(settings),
      initiating_event_(initiating_event),
      context_(context) {}

namespace {  // The model cloning functions.

std::unique_ptr<mef::Formula>
Clone(const mef::Formula& formula,
      const std::unordered_map<std::string, bool>& set_instructions,
      std::vector<std::unique_ptr<mef::Event>>* clones)  {
  struct {
    mef::Formula::ArgEvent operator()(mef::BasicEvent* arg) { return arg; }
    mef::Formula::ArgEvent operator()(mef::HouseEvent* arg) {
      if (auto it = ext::find(set_house, arg->id())) {
        if (it->second == arg->state())
          return arg;
        auto clone = std::make_unique<mef::HouseEvent>(
            arg->name(), "__clone__." + arg->id(),
            mef::RoleSpecifier::kPrivate);
        clone->state(it->second);
        auto* ptr = clone.get();
        event_register->emplace_back(std::move(clone));
        return ptr;
      }
      return arg;
    }
    mef::Formula::ArgEvent operator()(mef::Gate* arg) {
      if (set_house.empty())
        return arg;
      auto clone = std::make_unique<mef::Gate>(
          arg->name(), "__clone__." + arg->id(), mef::RoleSpecifier::kPrivate);
      clone->formula(Clone(arg->formula(), set_house, event_register));
      auto* ptr = clone.get();
      event_register->emplace_back(std::move(clone));
      return ptr;
    }

    const std::unordered_map<std::string, bool>& set_house;
    std::vector<std::unique_ptr<mef::Event>>* event_register;
  } cloner{set_instructions, clones};

  mef::Formula::ArgSet arg_set;
  for (const mef::Formula::Arg& arg : formula.args())
    arg_set.Add(std::visit(cloner, arg.event), arg.complement);

  return std::make_unique<mef::Formula>(
      formula.connective(), std::move(arg_set), formula.min_number(),
      formula.max_number());
}

}  // namespace

namespace {  // Diagnostics helpers.

template <class>
struct AlwaysFalse : std::false_type {};

using GateVisitSet = std::unordered_set<const mef::Gate*>;

std::string DescribeFormulaInternal(const mef::Formula& formula,
                                    GateVisitSet* visited);

std::string DescribeEvent(const mef::Formula::ArgEvent& event,
                          GateVisitSet* visited) {
  return std::visit(
      [&](auto* ptr) -> std::string {
        using PtrType = std::decay_t<decltype(ptr)>;
        if (!ptr)
          return "<null>";
        if constexpr (std::is_same_v<PtrType, mef::Gate*>) {
          const std::string& label = ptr->name().empty() ? ptr->id() : ptr->name();
          if (!ptr->HasFormula())
            return label;
          if (visited->count(ptr) != 0)
            return label + "{...}";
          visited->insert(ptr);
          std::string nested = DescribeFormulaInternal(ptr->formula(), visited);
          visited->erase(ptr);
          return label + "->" + nested;
        } else if constexpr (std::is_same_v<PtrType, mef::BasicEvent*>) {
          return ptr->name().empty() ? ptr->id() : ptr->name();
        } else if constexpr (std::is_same_v<PtrType, mef::HouseEvent*>) {
          if (ptr == &mef::HouseEvent::kTrue)
            return "TRUE";
          if (ptr == &mef::HouseEvent::kFalse)
            return "FALSE";
          const std::string& label = ptr->name().empty() ? ptr->id() : ptr->name();
          return label + "=" + (ptr->state() ? "TRUE" : "FALSE");
        } else {
          static_assert(AlwaysFalse<PtrType>::value, "Unhandled ArgEvent type");
          return "<unsupported>";
        }
      },
      event);
}

std::string DescribeArg(const mef::Formula::Arg& arg, GateVisitSet* visited) {
  std::string value = DescribeEvent(arg.event, visited);
  if (arg.complement)
    return "NOT(" + value + ")";
  return value;
}

std::string DescribeFormulaInternal(const mef::Formula& formula,
                                    GateVisitSet* visited) {
  std::ostringstream oss;
  oss << mef::kConnectiveToString[static_cast<int>(formula.connective())];
  if (formula.connective() == mef::kAtleast ||
      formula.connective() == mef::kCardinality) {
    auto min = formula.min_number();
    auto max = formula.max_number();
    if (min || max) {
      oss << "[";
      bool wrote = false;
      if (min) {
        oss << "min=" << *min;
        wrote = true;
      }
      if (max) {
        if (wrote)
          oss << ",";
        oss << "max=" << *max;
      }
      oss << "]";
    }
  }
  oss << "(";
  bool first = true;
  for (const mef::Formula::Arg& arg : formula.args()) {
    if (!first)
      oss << ", ";
    first = false;
    oss << DescribeArg(arg, visited);
  }
  if (first)
    oss << "<empty>";
  oss << ")";
  return oss.str();
}

std::string DescribeFormula(const mef::Formula& formula) {
  GateVisitSet visited;
  return DescribeFormulaInternal(formula, &visited);
}

}  // namespace

EventTreeAnalysis::PathCollector::PathCollector(const PathCollector& other)
    : expressions(other.expressions), set_instructions(other.set_instructions) {
  for (const mef::FormulaPtr& formula : other.formulas)
    formulas.push_back(std::make_unique<mef::Formula>(*formula));
}

void EventTreeAnalysis::Analyze()  {
  // assert(initiating_event_.event_tree());
  int formula_id = 0;  // Enumeration of collected formulas turned into gates.
  // Creates an internal gate representing the formula.
  auto make_gate = [&formula_id, this](mef::FormulaPtr formula) {
    std::string gate_name = "___" + initiating_event_.name() + "__formula_" + std::to_string(formula_id++) + "__";
    auto gate = std::make_unique<mef::Gate>(gate_name);
    gate->formula(std::move(formula));
    auto* address = gate.get();
    events_.emplace_back(std::move(gate));
    return address;
  };

  SequenceCollector collector{initiating_event_, *context_};
  CollectSequences(initiating_event_.event_tree()->initial_state(), &collector);
  for (auto& sequence : collector.sequences) {
    auto gate = std::make_unique<mef::Gate>("__" + sequence.first->name());
    std::vector<mef::FormulaPtr> gate_formulas;
    std::vector<mef::Expression*> arg_expressions;
    for (PathCollector& path_collector : sequence.second) {
      if (path_collector.formulas.size() == 1) {
        gate_formulas.push_back(std::move(path_collector.formulas.front()));
      } else if (path_collector.formulas.size() > 1) {
        mef::Formula::ArgSet arg_set;
        for (mef::FormulaPtr& arg_formula : path_collector.formulas) {
            arg_set.Add(make_gate(std::move(arg_formula)));
        }
        gate_formulas.push_back(std::make_unique<mef::Formula>(mef::kAnd, std::move(arg_set)));
      }
      if (path_collector.expressions.size() == 1) {
        arg_expressions.push_back(path_collector.expressions.front());
      } else if (path_collector.expressions.size() > 1) {
        expressions_.push_back(std::make_unique<mef::Mul>(std::move(path_collector.expressions)));
        arg_expressions.push_back(expressions_.back().get());
      }
    }
    assert(gate_formulas.empty() || arg_expressions.empty());
    bool is_expression_only = !arg_expressions.empty();
    if (gate_formulas.size() == 1) {
      gate->formula(std::move(gate_formulas.front()));
    } else if (gate_formulas.size() > 1) {
      mef::Formula::ArgSet arg_set;
      for (mef::FormulaPtr& arg_formula : gate_formulas) {
          arg_set.Add(make_gate(std::move(arg_formula)));
      }
      gate->formula(std::make_unique<mef::Formula>(mef::kOr, std::move(arg_set)));
    } else if (!arg_expressions.empty()) {
      auto event = std::make_unique<mef::BasicEvent>("__" + sequence.first->name());
      if (arg_expressions.size() == 1) {
        event->expression(arg_expressions.front());
      } else if (arg_expressions.size() > 1) {
        expressions_.push_back(std::make_unique<mef::Add>(std::move(arg_expressions)));
        event->expression(expressions_.back().get());
      }
      gate->formula(std::make_unique<mef::Formula>(mef::kNull, mef::Formula::ArgSet{event.get()}));
      events_.push_back(std::move(event));
    } else {
      gate->formula(std::make_unique<mef::Formula>(mef::kNull, mef::Formula::ArgSet{&mef::HouseEvent::kTrue}));
    }
    sequences_.push_back({*sequence.first, std::move(gate), is_expression_only});
  }
}

void EventTreeAnalysis::CollectSequences(const mef::Branch& initial_state,
                                         SequenceCollector* result)  {
  struct Collector {
    class Visitor : public mef::InstructionVisitor {
     public:
      explicit Visitor(Collector* collector) : collector_(*collector) {}

      void Visit(const mef::SetHouseEvent* house_event) override {
        collector_.path_collector_.set_instructions[house_event->name()] =
            house_event->state();
      }

      void Visit(const mef::Link* link) override {
        is_linked_ = true;
        Collector continue_connector(collector_);
        auto save = std::move(collector_.result_->context.functional_events);
        continue_connector(&link->event_tree().initial_state());
        collector_.result_->context.functional_events = std::move(save);
      }

      void Visit(const mef::CollectFormula* collect_formula) override {
        // clang-format off
        collector_.path_collector_.formulas.push_back(
            core::Clone(collect_formula->formula(),
                        collector_.path_collector_.set_instructions,
                        collector_.clones_));
        // clang-format on
      }

      void Visit(const mef::CollectExpression* collect_expression) override {
        collector_.path_collector_.expressions.push_back(
            &collect_expression->expression());
      }

      bool is_linked() const { return is_linked_; }

     private:
      Collector& collector_;
      bool is_linked_ = false;  
    };

    void operator()(const mef::Sequence* sequence) {
      Visitor visitor(this);
      for (const mef::Instruction* instruction : sequence->instructions())
        instruction->Accept(&visitor);
      if (!visitor.is_linked())
        result_->sequences[sequence].push_back(std::move(path_collector_));
    }

    void operator()(const mef::Fork* fork) const {
      const std::string& name = fork->functional_event().name();
      assert(result_->context.functional_events.count(name) == false);
      std::string& state = result_->context.functional_events[name];
      assert(state.empty());
      for (const mef::Path& fork_path : fork->paths()) {
        state = fork_path.state();
        // clang-format off
        Collector(*this)(&fork_path);  // NOLINT(runtime/explicit)
        // clang-format on
      }
      result_->context.functional_events.erase(name);
    }

    void operator()(const mef::Branch* branch) {
      Visitor visitor(this);
      for (const mef::Instruction* instruction : branch->instructions())
        instruction->Accept(&visitor);

      std::visit(*this, branch->target());
    }

    SequenceCollector* result_;
    std::vector<std::unique_ptr<mef::Event>>* clones_;
    PathCollector path_collector_;
  };
  context_->functional_events.clear();
  context_->initiating_event = initiating_event_.name();
  Collector{result, &events_}(&initial_state);  // NOLINT(whitespace/braces)
}

}  // namespace scram::core

Updated on 2026-01-09 at 21:59:13 +0000