Skip to content

packages/engine/scram-node/src/initializer.cc

Implementation of input file processing into analysis constructs.

Namespaces

Name
scram
scram::mef

Classes

Name
structscram::mef::Initializer::Extractor
structscram::mef::Initializer::Extractor< T, -1 > <br>Specialization of Extractor to extract all expressions into arg vector.

Defines

Name
GET_EVENT(gates, basic_events, house_events, path_reference) <br>Helper macro for Initializer::GetEvent event discovery.

Macros Documentation

define GET_EVENT

cpp
#define GET_EVENT(
    gates,
    basic_events,
    house_events,
    path_reference
)
  do {                                                               \
    if (auto it = ext::find(gates, path_reference))                  \
      return &*it;                                                   \
    if (auto it = ext::find(basic_events, path_reference))           \
      return &*it;                                                   \
    if (auto it = ext::find(house_events, path_reference))           \
      return &*it;                                                   \
  } while (false)

Helper macro for Initializer::GetEvent event discovery.

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 "initializer.h"

#include <functional>  // std::mem_fn
#include <sstream>
#include <type_traits>

#include <boost/exception/errinfo_at_line.hpp>
#include <boost/exception/errinfo_file_name.hpp>
#include <boost/filesystem.hpp>
#include <boost/range/adaptor/filtered.hpp>
#include <boost/range/adaptor/indirected.hpp>
#include <boost/range/algorithm.hpp>

#include "cycle.h"
#include "env.h"
#include "error.h"
#include "expression/boolean.h"
#include "expression/conditional.h"
#include "expression/exponential.h"
#include "expression/extern.h"
#include "expression/numerical.h"
#include "expression/random_deviate.h"
#include "expression/test_event.h"
#include "ext/algorithm.h"
#include "ext/find_iterator.h"
#include "logger.h"

namespace scram::mef {

namespace {  // Helper function and wrappers for MEF initializations.

RoleSpecifier GetRole(const std::string_view& s) {
  assert(!s.empty());
  assert(s == "public" || s == "private");
  return s == "public" ? RoleSpecifier::kPublic : RoleSpecifier::kPrivate;
}

RoleSpecifier GetRole(const std::string_view& s, RoleSpecifier parent_role) {
  return s.empty() ? parent_role : GetRole(s);
}

void AttachLabelAndAttributes(const xml::Element& xml_element,
                              Element* element) {
  if (std::optional<xml::Element> label = xml_element.child("label")) {
    assert(element->label().empty() && "Resetting element label.");
    element->label(std::string(label->text()));
  }

  std::optional<xml::Element> attributes = xml_element.child("attributes");
  if (!attributes)
    return;
  for (const xml::Element& attribute : attributes->children()) {
    assert(attribute.name() == "attribute");
    try {
      element->AddAttribute({std::string(attribute.attribute("name")),
                             std::string(attribute.attribute("value")),
                             std::string(attribute.attribute("type"))});
    } catch (ValidityError& err) {
      err << boost::errinfo_at_line(attribute.line());
      throw;
    }
  }
}

template <class T>
std::enable_if_t<std::is_base_of_v<Element, T>, std::unique_ptr<T>>
ConstructElement(const xml::Element& xml_element) {
  auto element =
      std::make_unique<T>(std::string(xml_element.attribute("name")));
  AttachLabelAndAttributes(xml_element, element.get());
  return element;
}

template <class T>
std::enable_if_t<std::is_base_of_v<Role, T>, std::unique_ptr<T>>
ConstructElement(const xml::Element& xml_element, const std::string& base_path,
                 RoleSpecifier base_role) {
  auto element =
      std::make_unique<T>(std::string(xml_element.attribute("name")), base_path,
                          GetRole(xml_element.attribute("role"), base_role));
  AttachLabelAndAttributes(xml_element, element.get());
  return element;
}

auto GetNonAttributeElements(const xml::Element& xml_element) {
  return xml_element.children() |
         boost::adaptors::filtered([](const xml::Element& child) {
           std::string_view name = child.name();
           return name != "label" && name != "attributes";
         });
}

template <>
std::unique_ptr<Phase>
ConstructElement<Phase>(const xml::Element& xml_element) {
  std::unique_ptr<Phase> element;
  try {
    element = std::make_unique<Phase>(
        std::string(xml_element.attribute("name")),
        *xml_element.attribute<double>("time-fraction"));
  } catch (ValidityError& err) {
    err << boost::errinfo_at_line(xml_element.line());
    throw;
  }
  AttachLabelAndAttributes(xml_element, element.get());
  return element;
}

}  // namespace

Initializer::Initializer(const std::vector<std::string>& xml_files,
                         core::Settings settings, bool allow_extern,
                         xml::Validator* extra_validator)
    : settings_(std::move(settings)),
      allow_extern_(allow_extern),
      extra_validator_(extra_validator) {
  BLOG(WARNING, allow_extern_) << "Enabling external dynamic libraries";
  ProcessInputFiles(xml_files);
}

void Initializer::CheckFileExistence(
    const std::vector<std::string>& xml_files) {
  for (auto& xml_file : xml_files) {
    if (boost::filesystem::exists(xml_file) == false) {
      SCRAM_THROW(IOError("Input file doesn't exist."))
          << boost::errinfo_file_name(xml_file);
    }
  }
}

void Initializer::CheckDuplicateFiles(
    const std::vector<std::string>& xml_files) {
  namespace fs = boost::filesystem;
  // Collection of input file locations in canonical path.
  std::unordered_set<std::string> full_paths;
  for (auto& xml_file : xml_files) {
    auto [it, is_unique] = full_paths.emplace(fs::canonical(xml_file).string());
    if (!is_unique)
      SCRAM_THROW(IOError("Duplicate input file"))
          << boost::errinfo_file_name(*it);
  }
}

void Initializer::ProcessInputFiles(const std::vector<std::string>& xml_files) {
  static xml::Validator validator(env::input_schema());

  CLOCK(input_time);
  LOG(DEBUG1) << "Processing input files";
  CheckFileExistence(xml_files);
  CheckDuplicateFiles(xml_files);
  for (const auto& xml_file : xml_files) {
    CLOCK(parse_time);
    LOG(DEBUG3) << "Parsing " << xml_file << " ...";
    xml::Document document(xml_file, &validator);
    if (extra_validator_)
      extra_validator_->validate(document);
    documents_.emplace_back(std::move(document));
    LOG(DEBUG3) << "Parsed " << xml_file << " in " << DUR(parse_time);
  }
  CLOCK(def_time);
  for (const xml::Document& document : documents_) {
    try {
      ProcessInputFile(document);
    } catch (ValidityError& err) {
      err << boost::errinfo_file_name(document.root().filename());
      throw;
    }
  }
  ProcessTbdElements();
  LOG(DEBUG2) << "Element definition time " << DUR(def_time);
  LOG(DEBUG1) << "Input files are processed in " << DUR(input_time);

  CLOCK(valid_time);
  LOG(DEBUG1) << "Validating the initialization";
  // Check if the initialization is successful.
  ValidateInitialization();
  LOG(DEBUG1) << "Validation is finished in " << DUR(valid_time);

  CLOCK(setup_time);
  LOG(DEBUG1) << "Setting up for the analysis";
  // Perform setup for analysis using configurations from the input files.
  SetupForAnalysis();
  EnsureNoCcfSubstitutions();
  EnsureSubstitutionsWithApproximations();
  LOG(DEBUG1) << "Setup time " << DUR(setup_time);
}

template <class T>
void Initializer::Register(std::unique_ptr<T> element,
                           const xml::Element& xml_element) {
  try {
    model_->Add(std::move(element));
  } catch (ValidityError& err) {
    err << boost::errinfo_at_line(xml_element.line());
    throw;
  }
}

template <>
Gate* Initializer::Register(const xml::Element& gate_node,
                            const std::string& base_path,
                            RoleSpecifier container_role) {
  std::unique_ptr<Gate> ptr =
      ConstructElement<Gate>(gate_node, base_path, container_role);
  auto* gate = ptr.get();
  Register(std::move(ptr), gate_node);
  path_gates_.insert(gate);
  tbd_.emplace_back(gate, gate_node);
  return gate;
}

template <>
BasicEvent* Initializer::Register(const xml::Element& event_node,
                                  const std::string& base_path,
                                  RoleSpecifier container_role) {
  std::unique_ptr<BasicEvent> ptr =
      ConstructElement<BasicEvent>(event_node, base_path, container_role);
  auto* basic_event = ptr.get();
  Register(std::move(ptr), event_node);
  path_basic_events_.insert(basic_event);
  tbd_.emplace_back(basic_event, event_node);
  return basic_event;
}

template <>
HouseEvent* Initializer::Register(const xml::Element& event_node,
                                  const std::string& base_path,
                                  RoleSpecifier container_role) {
  std::unique_ptr<HouseEvent> ptr =
      ConstructElement<HouseEvent>(event_node, base_path, container_role);
  auto* house_event = ptr.get();
  Register(std::move(ptr), event_node);
  path_house_events_.insert(house_event);

  // Only Boolean xml.
  if (std::optional<xml::Element> constant = event_node.child("constant")) {
    house_event->state(*constant->attribute<bool>("value"));
  }
  return house_event;
}

template <>
Parameter* Initializer::Register(const xml::Element& param_node,
                                 const std::string& base_path,
                                 RoleSpecifier container_role) {
  std::unique_ptr<Parameter> ptr =
      ConstructElement<Parameter>(param_node, base_path, container_role);
  auto* parameter = ptr.get();
  Register(std::move(ptr), param_node);
  path_parameters_.insert(parameter);
  tbd_.emplace_back(parameter, param_node);

  // Attach units.
  std::string_view unit = param_node.attribute("unit");
  if (!unit.empty()) {
    int pos = boost::find(kUnitsToString, unit) - std::begin(kUnitsToString);
    assert(pos < kNumUnits && "Unexpected unit kind.");
    parameter->unit(static_cast<Units>(pos));
  }
  return parameter;
}

template <>
CcfGroup* Initializer::Register(const xml::Element& ccf_node,
                                const std::string& base_path,
                                RoleSpecifier container_role) {
  auto ptr = [&]() -> std::unique_ptr<CcfGroup> {
    std::string_view model = ccf_node.attribute("model");
    if (model == "beta-factor")
      return ConstructElement<BetaFactorModel>(ccf_node, base_path,
                                               container_role);
    if (model == "MGL")
      return ConstructElement<MglModel>(ccf_node, base_path, container_role);
    if (model == "alpha-factor")
      return ConstructElement<AlphaFactorModel>(ccf_node, base_path,
                                                container_role);
    assert(model == "phi-factor" && "Unrecognized CCF model.");
    return ConstructElement<PhiFactorModel>(ccf_node, base_path,
                                            container_role);
  }();
  auto* ccf_group = ptr.get();
  Register(std::move(ptr), ccf_node);

  ProcessCcfMembers(*ccf_node.child("members"), ccf_group);

  tbd_.emplace_back(ccf_group, ccf_node);
  return ccf_group;
}

template <>
Sequence* Initializer::Register(const xml::Element& xml_node,
                                const std::string& /*base_path*/,
                                RoleSpecifier /*container_role*/) {
  std::unique_ptr<Sequence> ptr = ConstructElement<Sequence>(xml_node);
  auto* sequence = ptr.get();
  Register(std::move(ptr), xml_node);
  tbd_.emplace_back(sequence, xml_node);
  return sequence;
}

void Initializer::ProcessInputFile(const xml::Document& document) {
  xml::Element root = document.root();
  assert(root.name() == "opsa-mef");

  if (!model_) {  // Create only one model for multiple files.
    model_ = ConstructElement<Model>(root);
    model_->mission_time().value(settings_.mission_time());
  }

  for (const xml::Element& node : root.children()) {
    if (node.name() == "define-initiating-event") {
      std::unique_ptr<InitiatingEvent> initiating_event =
          ConstructElement<InitiatingEvent>(node);
      auto* ref_ptr = initiating_event.get();
      Register(std::move(initiating_event), node);
      tbd_.emplace_back(ref_ptr, node);

    } else if (node.name() == "define-rule") {
      std::unique_ptr<Rule> rule = ConstructElement<Rule>(node);
      auto* ref_ptr = rule.get();
      Register(std::move(rule), node);
      tbd_.emplace_back(ref_ptr, node);

    } else if (node.name() == "define-event-tree") {
      DefineEventTree(node);

    } else if (node.name() == "define-fault-tree") {
      DefineFaultTree(node);

    } else if (node.name() == "define-CCF-group") {
      Register<CcfGroup>(node, "", RoleSpecifier::kPublic);

    } else if (node.name() == "define-alignment") {
      std::unique_ptr<Alignment> alignment = ConstructElement<Alignment>(node);
      auto* address = alignment.get();
      Register(std::move(alignment), node);
      tbd_.emplace_back(address, node);

    } else if (node.name() == "define-substitution") {
      std::unique_ptr<Substitution> substitution =
          ConstructElement<Substitution>(node);
      auto* address = substitution.get();
      Register(std::move(substitution), node);
      tbd_.emplace_back(address, node);

    } else if (node.name() == "model-data") {
      ProcessModelData(node);

    } else if (node.name() == "define-extern-library") {
      if (!allow_extern_) {
        SCRAM_THROW(
            IllegalOperation("Loading external libraries is disallowed!"))
            << boost::errinfo_file_name(node.filename())
            << boost::errinfo_at_line(node.line());
      }
      DefineExternLibraries(node);
    }
  }
}

template <>
void Initializer::Define(const xml::Element& gate_node, Gate* gate) {
  auto formulas = GetNonAttributeElements(gate_node);
  // Assumes that there are no attributes and labels.
  assert(!formulas.empty() && ++formulas.begin() == formulas.end());
  assert(!gate->HasFormula() && "Resetting gate formula");
  gate->formula(GetFormula(*formulas.begin(), gate->base_path()));
}

template <>
void Initializer::Define(const xml::Element& event_node,
                         BasicEvent* basic_event) {
  auto expressions = GetNonAttributeElements(event_node);
  if (!expressions.empty()) {
    assert(basic_event->HasExpression() == false && "Resetting expressions.");
    basic_event->expression(
        GetExpression(*expressions.begin(), basic_event->base_path()));
  } else if (settings_.probability_analysis()) {
    SCRAM_THROW(ValidityError("The basic event does not have an expression."))
        << errinfo_element(basic_event->id(), "basic event")
        << boost::errinfo_at_line(event_node.line());
  }
}

template <>
void Initializer::Define(const xml::Element& param_node, Parameter* parameter) {
  auto expressions = GetNonAttributeElements(param_node);
  assert(!expressions.empty() && ++expressions.begin() == expressions.end());
  parameter->expression(
      GetExpression(*expressions.begin(), parameter->base_path()));
}

template <>
void Initializer::Define(const xml::Element& ccf_node, CcfGroup* ccf_group) {
  for (const xml::Element& element : ccf_node.children()) {
    std::string_view name = element.name();
    if (name == "distribution") {
      ccf_group->AddDistribution(
          GetExpression(*element.child(), ccf_group->base_path()));

    } else if (name == "factor") {
      DefineCcfFactor(element, ccf_group);

    } else if (name == "factors") {
      for (const xml::Element& factor_node : element.children())
        DefineCcfFactor(factor_node, ccf_group);
    }
  }
}

template <>
void Initializer::Define(const xml::Element& xml_node, Sequence* sequence) {
  std::vector<Instruction*> instructions;
  for (const xml::Element& node : GetNonAttributeElements(xml_node)) {
    instructions.emplace_back(GetInstruction(node));
  }
  sequence->instructions(std::move(instructions));
}

template <>
void Initializer::Define(const xml::Element& et_node, EventTree* event_tree) {
  for (const xml::Element& node : et_node.children("define-branch")) {
    auto it = ext::find(event_tree->table<NamedBranch>(),
                        std::string(node.attribute("name")));
    assert(it);
    DefineBranch(GetNonAttributeElements(node), event_tree, &*it);
  }
  Branch initial_state;
  DefineBranch(et_node.child("initial-state")->children(), event_tree,
               &initial_state);
  event_tree->initial_state(std::move(initial_state));
}

template <>
void Initializer::Define(const xml::Element& xml_node,
                         InitiatingEvent* initiating_event) {
  std::string_view event_tree_name = xml_node.attribute("event-tree");
  if (!event_tree_name.empty()) {
    try {
      auto& event_tree = model_->Get<EventTree>(event_tree_name);
      initiating_event->event_tree(&event_tree);
      initiating_event->usage(true);
      event_tree.usage(true);

    } catch (Error& err) {
      err << boost::errinfo_at_line(xml_node.line());
      throw;
    }
  }
}

template <>
void Initializer::Define(const xml::Element& rule_node, Rule* rule) {
  std::vector<Instruction*> instructions;
  for (const xml::Element& xml_node : GetNonAttributeElements(rule_node))
    instructions.push_back(GetInstruction(xml_node));
  rule->instructions(std::move(instructions));
}

template <>
void Initializer::Define(const xml::Element& xml_node, Alignment* alignment) {
  for (const xml::Element& node : xml_node.children("define-phase")) {
    try {
      std::unique_ptr<Phase> phase = ConstructElement<Phase>(node);
      std::vector<SetHouseEvent*> instructions;
      for (const xml::Element& arg : node.children("set-house-event")) {
        instructions.push_back(
            static_cast<SetHouseEvent*>(GetInstruction(arg)));
      }
      phase->instructions(std::move(instructions));
      alignment->Add(std::move(phase));
    } catch (ValidityError& err) {
      err << boost::errinfo_at_line(node.line());
      throw;
    }
  }
  try {
    alignment->Validate();
  } catch (ValidityError& err) {
    err << boost::errinfo_at_line(xml_node.line());
    throw;
  }
}

template <>
void Initializer::Define(const xml::Element& xml_node,
                         Substitution* substitution) {
  substitution->hypothesis(
      GetFormula(xml_node.child("hypothesis")->child().value(), ""));

  if (std::optional<xml::Element> source = xml_node.child("source")) {
    for (const xml::Element& basic_event : source->children()) {
      assert(basic_event.name() == "basic-event");
      try {
        BasicEvent* event = GetBasicEvent(basic_event.attribute("name"), "");
        substitution->Add(event);
        event->usage(true);
      } catch (ValidityError& err) {
        err << boost::errinfo_at_line(basic_event.line());
        throw;
      }
    }
    assert(substitution->source().empty() == false);
  }

  xml::Element target = xml_node.child("target")->child().value();
  if (target.name() == "basic-event") {
    try {
      BasicEvent* event = GetBasicEvent(target.attribute("name"), "");
      substitution->target(event);
      event->usage(true);
    } catch (ValidityError& err) {
      err << boost::errinfo_at_line(target.line());
      throw;
    }
  } else {
    assert(target.name() == "constant");
    substitution->target(target.attribute<bool>("value").value());
  }

  try {
    substitution->Validate();
    std::string_view type = xml_node.attribute("type");
    if (!type.empty()) {
      std::optional<Substitution::Type> deduced_type = substitution->type();
      int pos = std::distance(kSubstitutionTypeToString,
                              boost::find(kSubstitutionTypeToString, type));
      assert(pos < 3 && "Unexpected substitution type string.");
      if (!deduced_type ||
          static_cast<Substitution::Type>(pos) != deduced_type.value())
        SCRAM_THROW(ValidityError(
            "The declared substitution type does not match the deduced one."));
    }
  } catch (ValidityError& err) {
    err << boost::errinfo_at_line(xml_node.line());
    throw;
  }
}

void Initializer::ProcessTbdElements() {
  for (const xml::Document& document : documents_) {
    xml::Element root = document.root();
    for (const xml::Element& node : root.children("define-extern-function")) {
      try {
        DefineExternFunction(node);
      } catch (ValidityError& err) {
        err << boost::errinfo_file_name(root.filename());
        throw;
      }
    }
  }

  for (const auto& tbd_pair : tbd_) {
    const auto& tbd_element = tbd_pair.first;
    const auto& xml_element = tbd_pair.second;
    try {
      std::visit(
          [this, &xml_element](auto* tbd_construct) {
            this->Define(xml_element, tbd_construct);
          },
          tbd_element);
    } catch (ValidityError& err) {
      err << boost::errinfo_file_name(xml_element.filename());
      throw;
    }
  }
}

void Initializer::DefineEventTree(const xml::Element& et_node) {
  std::unique_ptr<EventTree> event_tree = ConstructElement<EventTree>(et_node);
  for (const xml::Element& node : et_node.children()) {
    if (node.name() == "define-sequence") {
      event_tree->Add(
          Register<Sequence>(node, event_tree->name(), RoleSpecifier::kPublic));
    } else {
      try {
        if (node.name() == "define-branch") {
          event_tree->Add(ConstructElement<NamedBranch>(node));
        } else if (node.name() == "define-functional-event") {
          std::unique_ptr<FunctionalEvent> event =
              ConstructElement<FunctionalEvent>(node);
          event->order(event_tree->functional_events().size() + 1);
          event_tree->Add(std::move(event));
        }
      } catch (ValidityError& err) {
        err << boost::errinfo_at_line(node.line());
        throw;
      }
    }
  }
  EventTree* tbd_element = event_tree.get();
  Register(std::move(event_tree), et_node);
  // Save only after registration.
  tbd_.emplace_back(tbd_element, et_node);
}

void Initializer::DefineFaultTree(const xml::Element& ft_node) {
  std::unique_ptr<FaultTree> fault_tree = ConstructElement<FaultTree>(ft_node);
  RegisterFaultTreeData(ft_node, fault_tree->name(), fault_tree.get());
  Register(std::move(fault_tree), ft_node);
}

std::unique_ptr<Component> Initializer::DefineComponent(
    const xml::Element& component_node, const std::string& base_path,
    RoleSpecifier container_role) {
  std::unique_ptr<Component> component =
      ConstructElement<Component>(component_node, base_path, container_role);
  RegisterFaultTreeData(component_node, base_path + "." + component->name(),
                        component.get());
  return component;
}

void Initializer::RegisterFaultTreeData(const xml::Element& ft_node,
                                        const std::string& base_path,
                                        Component* component) {
  for (const xml::Element& node : ft_node.children()) {
    if (node.name() == "define-basic-event") {
      component->Add(Register<BasicEvent>(node, base_path, component->role()));

    } else if (node.name() == "define-parameter") {
      component->Add(Register<Parameter>(node, base_path, component->role()));

    } else if (node.name() == "define-gate") {
      component->Add(Register<Gate>(node, base_path, component->role()));

    } else if (node.name() == "define-house-event") {
      component->Add(Register<HouseEvent>(node, base_path, component->role()));

    } else if (node.name() == "define-CCF-group") {
      component->Add(Register<CcfGroup>(node, base_path, component->role()));

    } else if (node.name() == "define-component") {
      std::unique_ptr<Component> sub =
          DefineComponent(node, base_path, component->role());
      try {
        component->Add(std::move(sub));
      } catch (ValidityError& err) {
        err << boost::errinfo_at_line(node.line());
        throw;
      }
    }
  }
}

void Initializer::ProcessModelData(const xml::Element& model_data) {
  for (const xml::Element& node : model_data.children()) {
    if (node.name() == "define-basic-event") {
      Register<BasicEvent>(node, "", RoleSpecifier::kPublic);
    } else if (node.name() == "define-parameter") {
      Register<Parameter>(node, "", RoleSpecifier::kPublic);
    } else if (node.name() == "define-house-event") {
      Register<HouseEvent>(node, "", RoleSpecifier::kPublic);
    }
  }
}

std::unique_ptr<Formula> Initializer::GetFormula(
    const xml::Element& formula_node, const std::string& base_path) {
  Connective formula_type = [&formula_node]() {
    if (formula_node.has_attribute("name") || formula_node.name() == "constant")
      return kNull;
    int pos = boost::find(kConnectiveToString, formula_node.name()) -
              std::begin(kConnectiveToString);
    assert(pos < kNumConnectives && "Unexpected connective.");
    return static_cast<Connective>(pos);
  }();

  Formula::ArgSet arg_set;

  auto add_event = [this, &arg_set, &base_path](const xml::Element& element,
                                                bool complement) {
    std::string_view element_type = [&element] {
      // This is for the case "<event name="id" type="type"/>".
      std::string_view type = element.attribute("type");
      return type.empty() ? element.name() : type;
    }();

    std::string_view name = element.attribute("name");
    assert(!name.empty() && "Not an appropriate XML element for arg Event.");

    try {
      auto arg_event = [this, &element_type, &name,
                        &base_path]() -> Formula::ArgEvent {
        if (element_type == "event") {  // Undefined type yet.
          return GetEvent(name, base_path);

        } else if (element_type == "gate") {
          return GetGate(name, base_path);

        } else if (element_type == "basic-event") {
          return GetBasicEvent(name, base_path);

        } else {
          assert(element_type == "house-event");
          return GetHouseEvent(name, base_path);
        }
      }();
      arg_set.Add(arg_event, complement);
    } catch (ValidityError& err) {
      err << boost::errinfo_at_line(element.line());
      throw;
    }
  };

  auto add_arg = [this, &arg_set, &add_event](const xml::Element& element) {
    if (element.name() == "constant") {
      arg_set.Add(*element.attribute<bool>("value") ? &HouseEvent::kTrue
                                                    : &HouseEvent::kFalse);
      return;
    }

    if (element.name() == "not") {
      assert(element.children().size() == 1);
      add_event(element.child().value(), /*complement=*/true);
    } else {
      add_event(element, /*complement=*/false);
    }
  };

  // Process arguments of this formula.
  if (formula_type == kNull) {  // Special case of pass-through.
    add_arg(formula_node);
  } else {
    for (const xml::Element& node : formula_node.children())
      add_arg(node);
  }

  try {
    return std::make_unique<Formula>(formula_type, std::move(arg_set),
                                     formula_node.attribute<int>("min"),
                                     formula_node.attribute<int>("max"));
  } catch (ValidityError& err) {
    err << boost::errinfo_at_line(formula_node.line());
    throw;
  }
}

void Initializer::DefineBranchTarget(const xml::Element& target_node,
                                     EventTree* event_tree, Branch* branch) {
  try {
    if (target_node.name() == "fork") {
      auto& functional_event = event_tree->Get<FunctionalEvent>(
          target_node.attribute("functional-event"));
      std::vector<Path> paths;
      for (const xml::Element& path_element : target_node.children("path")) {
        paths.emplace_back(std::string(path_element.attribute("state")));
        DefineBranch(path_element.children(), event_tree, &paths.back());
      }
      assert(!paths.empty());
      try {
        auto fork = std::make_unique<Fork>(functional_event, std::move(paths));
        branch->target(fork.get());
        event_tree->Add(std::move(fork));
        functional_event.usage(true);
      } catch (ValidityError& err) {
        err << errinfo_container(event_tree->name(), "event tree");
        throw;
      }
    } else if (target_node.name() == "sequence") {
      auto& sequence = model_->Get<Sequence>(target_node.attribute("name"));
      branch->target(&sequence);
      sequence.usage(true);
    } else {
      assert(target_node.name() == "branch");
      auto& named_branch =
          event_tree->Get<NamedBranch>(target_node.attribute("name"));
      branch->target(&named_branch);
      named_branch.usage(true);
    }
  } catch (ValidityError& err) {
    err << boost::errinfo_at_line(target_node.line());
    throw;
  }
}

template <class SinglePassRange>
void Initializer::DefineBranch(const SinglePassRange& xml_nodes,
                               EventTree* event_tree, Branch* branch) {
  assert(!xml_nodes.empty() && "At least the branch target must be defined.");

  std::vector<Instruction*> instructions;
  for (auto it = xml_nodes.begin(), it_end = xml_nodes.end(); it != it_end;) {
    auto it_cur = it++;
    if (it == it_end) {
      DefineBranchTarget(*it_cur, event_tree, branch);
    } else {
      instructions.emplace_back(GetInstruction(*it_cur));
    }
  }
  branch->instructions(std::move(instructions));
}

Instruction* Initializer::GetInstruction(const xml::Element& xml_element) {
  std::string_view node_name = xml_element.name();
  auto invoke = [&xml_element](auto&& action) {
    try {
      return action();
    } catch (UndefinedElement& err) {
      err << boost::errinfo_at_line(xml_element.line());
      throw;
    }
  };

  if (node_name == "rule") {
    return invoke([&xml_element, this] {
      auto& rule = model_->Get<Rule>(xml_element.attribute("name"));
      rule.usage(true);
      return &rule;
    });
  }

  auto register_instruction = [this](std::unique_ptr<Instruction> instruction) {
    auto* ret_ptr = instruction.get();
    model_->Add(std::move(instruction));
    return ret_ptr;
  };

  if (node_name == "event-tree") {
    return invoke([&] {
      auto& event_tree = model_->Get<EventTree>(xml_element.attribute("name"));
      event_tree.usage(true);
      links_.push_back(static_cast<Link*>(
          register_instruction(std::make_unique<Link>(event_tree))));
      return links_.back();
    });
  }

  if (node_name == "collect-expression") {
    return register_instruction(std::make_unique<CollectExpression>(
        GetExpression(*xml_element.child(), "")));
  }

  if (node_name == "collect-formula") {
    return register_instruction(
        std::make_unique<CollectFormula>(GetFormula(*xml_element.child(), "")));
  }

  if (node_name == "if") {
    xml::Element::Range args = xml_element.children();
    auto it = args.begin();
    Expression* if_expression = GetExpression(*it++, "");
    Instruction* then_instruction = GetInstruction(*it++);
    Instruction* else_instruction =
        it == args.end() ? nullptr : GetInstruction(*it);

    return register_instruction(std::make_unique<IfThenElse>(
        if_expression, then_instruction, else_instruction));
  }

  if (node_name == "block") {
    std::vector<Instruction*> instructions;
    for (const xml::Element& xml_node : xml_element.children())
      instructions.push_back(GetInstruction(xml_node));
    return register_instruction(
        std::make_unique<Block>(std::move(instructions)));
  }

  if (node_name == "set-house-event") {
    std::string_view name = xml_element.attribute("name");
    if (!model_->house_events().count(name)) {
      SCRAM_THROW(UndefinedElement())
          << errinfo_element(std::string(name), "house event")
          << boost::errinfo_at_line(xml_element.line());
    }
    return register_instruction(std::make_unique<SetHouseEvent>(
        std::string(name), *xml_element.child()->attribute<bool>("value")));
  }

  LOG(ERROR) << "Unknown instruction type.";
  exit(1);
}

template <class T, int N>
struct Initializer::Extractor {
  std::unique_ptr<T> operator()(const xml::Element::Range& args,
                                const std::string& base_path,
                                Initializer* init) {
    static_assert(N > 0, "The number of arguments can't be fewer than 1.");
    return (*this)(args.begin(), args.end(), base_path, init);
  }

  template <class... Ts>
  std::unique_ptr<T> operator()(xml::Element::Range::iterator it,
                                xml::Element::Range::iterator it_end,
                                const std::string& base_path, Initializer* init,
                                Ts&&... expressions) {
    static_assert(N >= 0);

    if constexpr (N == 0) {
      static_assert(sizeof...(Ts), "Unintended use case.");
      assert(it == it_end && "Too many arguments in the args container.");
      return std::make_unique<T>(std::forward<Ts>(expressions)...);

    } else {
      assert(it != it_end && "Not enough arguments in the args container.");
      return Extractor<T, N - 1>()(std::next(it), it_end, base_path, init,
                                   std::forward<Ts>(expressions)...,
                                   init->GetExpression(*it, base_path));
    }
  }
};

template <class T>
struct Initializer::Extractor<T, -1> {
  std::unique_ptr<T> operator()(const xml::Element::Range& args,
                                const std::string& base_path,
                                Initializer* init) {
    std::vector<Expression*> expr_args;
    for (const xml::Element& node : args) {
      expr_args.push_back(init->GetExpression(node, base_path));
    }
    return std::make_unique<T>(std::move(expr_args));
  }
};

namespace {  // Expression extraction helper functions.

template <class T, class A, class... As>
constexpr int count_args() {
  if constexpr (std::is_constructible_v<T, A, As...>) {
    return 1 + sizeof...(As);
  } else {
    return count_args<T, A, A, As...>();
  }
}

template <class T>
constexpr std::enable_if_t<std::is_base_of_v<Expression, T>, int> num_args() {
  static_assert(!std::is_default_constructible_v<T>, "No zero args.");
  if constexpr (std::is_constructible_v<T, std::vector<Expression*>>) {
    return -1;
  } else {
    return count_args<T, Expression*>();
  }
}

}  // namespace

template <class T>
std::unique_ptr<Expression>
Initializer::Extract(const xml::Element::Range& args,
                     const std::string& base_path, Initializer* init) {
  return Extractor<T, num_args<T>()>()(args, base_path, init);
}

template <>
std::unique_ptr<Expression>
Initializer::Extract<Histogram>(const xml::Element::Range& args,
                                const std::string& base_path,
                                Initializer* init) {
  auto it = args.begin();
  std::vector<Expression*> boundaries = {init->GetExpression(*it, base_path)};
  std::vector<Expression*> weights;
  for (++it; it != args.end(); ++it) {
    xml::Element::Range bin = it->children();
    assert(bin.size() == 2);
    auto it_bin = bin.begin();
    boundaries.push_back(init->GetExpression(*it_bin++, base_path));
    weights.push_back(init->GetExpression(*it_bin, base_path));
  }
  assert(!weights.empty() && "At least one bin must be present.");
  return std::make_unique<Histogram>(std::move(boundaries), std::move(weights));
}

template <>
std::unique_ptr<Expression>
Initializer::Extract<LognormalDeviate>(const xml::Element::Range& args,
                                       const std::string& base_path,
                                       Initializer* init) {
  if (args.size() == 3)
    return Extractor<LognormalDeviate, 3>()(args, base_path, init);
  return Extractor<LognormalDeviate, 2>()(args, base_path, init);
}

template <>
std::unique_ptr<Expression>
Initializer::Extract<PeriodicTest>(const xml::Element::Range& args,
                                   const std::string& base_path,
                                   Initializer* init) {
  switch (args.size()) {
    case 4:
      return Extractor<PeriodicTest, 4>()(args, base_path, init);
    case 5:
      return Extractor<PeriodicTest, 5>()(args, base_path, init);
    case 11:
      return Extractor<PeriodicTest, 11>()(args, base_path, init);
    default:
      SCRAM_THROW(
          ValidityError("Invalid number of arguments for Periodic Test."));
  }
}

template <>
std::unique_ptr<Expression>
Initializer::Extract<Switch>(const xml::Element::Range& args,
                             const std::string& base_path, Initializer* init) {
  assert(!args.empty());
  Expression* default_value = nullptr;
  std::vector<Switch::Case> cases;
  for (auto it = args.begin(), it_end = args.end(); it != it_end;) {
    auto it_cur = it++;
    if (it == it_end) {
      default_value = init->GetExpression(*it_cur, base_path);
      break;
    }
    xml::Element::Range nodes = it_cur->children();
    assert(nodes.size() == 2);
    auto it_node = nodes.begin();
    cases.push_back({*init->GetExpression(*it_node++, base_path),
                     *init->GetExpression(*it_node, base_path)});
  }
  assert(default_value);
  return std::make_unique<Switch>(std::move(cases), default_value);
}

const Initializer::ExtractorMap Initializer::kExpressionExtractors_ = {
    {"exponential", &Extract<Exponential>},
    {"GLM", &Extract<Glm>},
    {"Weibull", &Extract<Weibull>},
    {"periodic-test", &Extract<PeriodicTest>},
    {"uniform-deviate", &Extract<UniformDeviate>},
    {"normal-deviate", &Extract<NormalDeviate>},
    {"lognormal-deviate", &Extract<LognormalDeviate>},
    {"gamma-deviate", &Extract<GammaDeviate>},
    {"beta-deviate", &Extract<BetaDeviate>},
    {"histogram", &Extract<Histogram>},
    {"neg", &Extract<Neg>},
    {"add", &Extract<Add>},
    {"sub", &Extract<Sub>},
    {"mul", &Extract<Mul>},
    {"div", &Extract<Div>},
    {"abs", &Extract<Abs>},
    {"acos", &Extract<Acos>},
    {"asin", &Extract<Asin>},
    {"atan", &Extract<Atan>},
    {"cos", &Extract<Cos>},
    {"sin", &Extract<Sin>},
    {"tan", &Extract<Tan>},
    {"cosh", &Extract<Cosh>},
    {"sinh", &Extract<Sinh>},
    {"tanh", &Extract<Tanh>},
    {"exp", &Extract<Exp>},
    {"log", &Extract<Log>},
    {"log10", &Extract<Log10>},
    {"mod", &Extract<Mod>},
    {"pow", &Extract<Pow>},
    {"sqrt", &Extract<Sqrt>},
    {"ceil", &Extract<Ceil>},
    {"floor", &Extract<Floor>},
    {"min", &Extract<Min>},
    {"max", &Extract<Max>},
    {"mean", &Extract<Mean>},
    {"not", &Extract<Not>},
    {"and", &Extract<And>},
    {"or", &Extract<Or>},
    {"eq", &Extract<Eq>},
    {"df", &Extract<Df>},
    {"lt", &Extract<Lt>},
    {"gt", &Extract<Gt>},
    {"leq", &Extract<Leq>},
    {"geq", &Extract<Geq>},
    {"ite", &Extract<Ite>},
    {"switch", &Extract<Switch>}};

Expression* Initializer::GetExpression(const xml::Element& expr_element,
                                       const std::string& base_path) {
  std::string_view expr_type = expr_element.name();
  auto register_expression = [this](std::unique_ptr<Expression> expression) {
    auto* ret_ptr = expression.get();
    model_->Add(std::move(expression));
    return ret_ptr;
  };
  if (expr_type == "int") {
    int val = *expr_element.attribute<int>("value");
    return register_expression(std::make_unique<ConstantExpression>(val));
  }
  if (expr_type == "float") {
    double val = *expr_element.attribute<double>("value");
    return register_expression(std::make_unique<ConstantExpression>(val));
  }
  if (expr_type == "bool") {
    bool val = *expr_element.attribute<bool>("value");
    return val ? &ConstantExpression::kOne : &ConstantExpression::kZero;
  }
  if (expr_type == "pi")
    return &ConstantExpression::kPi;

  if (expr_type == "test-initiating-event") {
    return register_expression(std::make_unique<TestInitiatingEvent>(
        std::string(expr_element.attribute("name")), model_->context()));
  }
  if (expr_type == "test-functional-event") {
    return register_expression(std::make_unique<TestFunctionalEvent>(
        std::string(expr_element.attribute("name")),
        std::string(expr_element.attribute("state")), model_->context()));
  }

  if (expr_type == "extern-function") {
    const ExternFunction<void>* extern_function = [this, &expr_element] {
      try {
        auto& ret =
            model_->Get<ExternFunction<void>>(expr_element.attribute("name"));
        ret.usage(true);
        return &ret;

      } catch (UndefinedElement& err) {
        err << boost::errinfo_at_line(expr_element.line());
        throw;
      }
    }();

    std::vector<Expression*> expr_args;
    for (const xml::Element& node : expr_element.children())
      expr_args.push_back(GetExpression(node, base_path));

    try {
      return register_expression(extern_function->apply(std::move(expr_args)));
    } catch (ValidityError& err) {
      err << boost::errinfo_at_line(expr_element.line());
      throw;
    }
  }

  if (auto* expression = GetParameter(expr_type, expr_element, base_path))
    return expression;

  try {
    Expression* expression = register_expression(kExpressionExtractors_.at(
        expr_type)(expr_element.children(), base_path, this));
    // Register for late validation after ensuring no cycles.
    expressions_.emplace_back(expression, expr_element);
    return expression;
  } catch (ValidityError& err) {
    err << boost::errinfo_at_line(expr_element.line());
    throw;
  }
}

Expression* Initializer::GetParameter(const std::string_view& expr_type,
                                      const xml::Element& expr_element,
                                      const std::string& base_path) {
  auto check_units = [&expr_element](const auto& parameter) {
    std::string_view unit = expr_element.attribute("unit");
    const char* param_unit = scram::mef::kUnitsToString[parameter.unit()];
    if (!unit.empty() && unit != param_unit) {
      std::stringstream msg;
      msg << "Parameter unit mismatch.\nExpected: " << param_unit
          << "\nGiven: " << unit;
      SCRAM_THROW(ValidityError(msg.str()))
          << boost::errinfo_at_line(expr_element.line());
    }
  };

  if (expr_type == "parameter") {
    try {
      Parameter* param =
          GetParameter(expr_element.attribute("name"), base_path);
      param->usage(true);
      check_units(*param);
      return param;
    } catch (ValidityError& err) {
      err << boost::errinfo_at_line(expr_element.line());
      throw;
    }
  } else if (expr_type == "system-mission-time") {
    check_units(model_->mission_time());
    return &model_->mission_time();
  }
  return nullptr;  // The expression is not a parameter.
}

void Initializer::ProcessCcfMembers(const xml::Element& members_node,
                                    CcfGroup* ccf_group) {
  for (const xml::Element& event_node : members_node.children()) {
    assert("basic-event" == event_node.name());
    auto basic_event =
        std::make_unique<BasicEvent>(std::string(event_node.attribute("name")),
                                     ccf_group->base_path(), ccf_group->role());
    try {
      ccf_group->AddMember(basic_event.get());
    } catch (DuplicateElementError& err) {
      err << boost::errinfo_at_line(event_node.line());
      throw;
    }
    Register(std::move(basic_event), event_node);
  }
}

void Initializer::DefineCcfFactor(const xml::Element& factor_node,
                                  CcfGroup* ccf_group) {
  Expression* expression =
      GetExpression(*factor_node.child(), ccf_group->base_path());

  try {
    ccf_group->AddFactor(expression, factor_node.attribute<int>("level"));
  } catch (ValidityError& err) {
    err << boost::errinfo_at_line(factor_node.line());
    throw;
  }
}

Parameter* Initializer::GetParameter(std::string_view entity_reference,
                                     const std::string& base_path) {
  return GetEntity(entity_reference, base_path, model_->table<Parameter>(),
                   TableRange(path_parameters_));
}

HouseEvent* Initializer::GetHouseEvent(std::string_view entity_reference,
                                       const std::string& base_path) {
  return GetEntity(entity_reference, base_path, model_->table<HouseEvent>(),
                   TableRange(path_house_events_));
}

BasicEvent* Initializer::GetBasicEvent(std::string_view entity_reference,
                                       const std::string& base_path) {
  return GetEntity(entity_reference, base_path, model_->table<BasicEvent>(),
                   TableRange(path_basic_events_));
}

Gate* Initializer::GetGate(std::string_view entity_reference,
                           const std::string& base_path) {
  return GetEntity(entity_reference, base_path, model_->table<Gate>(),
                   TableRange(path_gates_));
}

template <class P, class T>
T* Initializer::GetEntity(std::string_view entity_reference,
                          const std::string& base_path,
                          const TableRange<IdTable<P>>& container,
                          const TableRange<PathTable<T>>& path_container) {
  assert(!entity_reference.empty());
  if (!base_path.empty()) {  // Check the local scope.
    std::string full_path = base_path + ".";
    full_path.append(entity_reference.data(), entity_reference.size());
    if (auto it = ext::find(path_container, full_path))
      return &*it;
  }

  auto at = [&entity_reference, &base_path](const auto& reference_container) {
    if (auto it = ext::find(reference_container, entity_reference))
      return &*it;
    SCRAM_THROW(UndefinedElement())
        << errinfo_reference(std::string(entity_reference))
        << errinfo_base_path(base_path) << errinfo_element_type(T::kTypeString);
  };

  if (entity_reference.find('.') == std::string_view::npos)  // Public entity.
    return at(container);

  return at(path_container);  // Direct access.
}

#define GET_EVENT(gates, basic_events, house_events, path_reference) \
  do {                                                               \
    if (auto it = ext::find(gates, path_reference))                  \
      return &*it;                                                   \
    if (auto it = ext::find(basic_events, path_reference))           \
      return &*it;                                                   \
    if (auto it = ext::find(house_events, path_reference))           \
      return &*it;                                                   \
  } while (false)

Formula::ArgEvent Initializer::GetEvent(std::string_view entity_reference,
                                        const std::string& base_path) {
  // Do not implement this in terms of
  // GetGate, GetBasicEvent, or GetHouseEvent.
  // The semantics for local lookup with the base type is different.
  assert(!entity_reference.empty());
  if (!base_path.empty()) {  // Check the local scope.
    std::string full_path = base_path + ".";
    full_path.append(entity_reference.data(), entity_reference.size());
    GET_EVENT(TableRange(path_gates_), TableRange(path_basic_events_),
              TableRange(path_house_events_), full_path);
  }

  if (entity_reference.find('.') == std::string_view::npos) {  // Public entity.
    GET_EVENT(model_->table<Gate>(), model_->table<BasicEvent>(),
              model_->table<HouseEvent>(), entity_reference);
  } else {  // Direct access.
    GET_EVENT(TableRange(path_gates_), TableRange(path_basic_events_),
              TableRange(path_house_events_), entity_reference);
  }
  SCRAM_THROW(UndefinedElement())
      << errinfo_reference(std::string(entity_reference))
      << errinfo_base_path(base_path) << errinfo_element_type("event");
}

#undef GET_EVENT

void Initializer::DefineExternLibraries(const xml::Element& xml_node) {
  auto optional_bool = [&xml_node](const char* tag) {
    std::optional<bool> attribute = xml_node.attribute<bool>(tag);
    return attribute ? *attribute : false;
  };
  auto library = [&xml_node, &optional_bool] {
    try {
      return std::make_unique<ExternLibrary>(
          std::string(xml_node.attribute("name")),
          std::string(xml_node.attribute("path")),
          boost::filesystem::path(xml_node.filename()).parent_path(),
          optional_bool("system"), optional_bool("decorate"));
    } catch (DLError& err) {
      err << boost::errinfo_file_name(xml_node.filename())
          << boost::errinfo_at_line(xml_node.line());
      throw;
    } catch (ValidityError& err) {
      err << boost::errinfo_at_line(xml_node.line());
      throw;
    }
  }();
  AttachLabelAndAttributes(xml_node, library.get());
  Register(std::move(library), xml_node);
}

namespace {  // Extern function initialization helpers.

enum class ExternParamType { kInt = 1, kDouble };
const int kExternTypeBase = 3;  
const int kMaxNumParam = 5;  
const int kNumInterfaces = 126;  

template <class SinglePassRange>
int Encode(const SinglePassRange& args) noexcept {
  assert(!args.empty());
  auto to_digit = [](const xml::Element& node) -> int {
    std::string_view name = node.name();
    return static_cast<int>([&name] {
      if (name == "int")
        return ExternParamType::kInt;
      assert(name == "double");
      return ExternParamType::kDouble;
    }());
  };

  int ret = 0;
  int base_power = 1;  // Base ^ (pos - 1).
  for (const xml::Element& node : args) {
    ret += base_power * to_digit(node);
    base_power *= kExternTypeBase;
  }

  return ret;
}

template <typename T, typename... Ts>
constexpr int Encode(int base_power = 1) noexcept {
  if constexpr (sizeof...(Ts)) {
    return Encode<T>(base_power) + Encode<Ts...>(base_power * kExternTypeBase);

  } else if constexpr (std::is_same_v<T, int>) {
    return base_power * static_cast<int>(ExternParamType::kInt);

  } else {
    static_assert(std::is_same_v<T, double>);
    return base_power * static_cast<int>(ExternParamType::kDouble);
  }
}

using ExternFunctionExtractor = ExternFunctionPtr (*)(std::string,
                                                      const std::string&,
                                                      const ExternLibrary&);
using ExternFunctionExtractorMap =
    std::unordered_map<int, ExternFunctionExtractor>;

template <int N, typename... Ts>
void GenerateExternFunctionExtractor(ExternFunctionExtractorMap* function_map) {
  static_assert(N >= 0);
  static_assert(sizeof...(Ts));

  if constexpr (N == 0) {
    function_map->emplace(
        Encode<Ts...>(),
        [](std::string name, const std::string& symbol,
           const ExternLibrary& library) -> ExternFunctionPtr {
          return std::make_unique<ExternFunction<Ts...>>(std::move(name),
                                                         symbol, library);
        });
  } else {
    GenerateExternFunctionExtractor<0, Ts...>(function_map);
    GenerateExternFunctionExtractor<N - 1, Ts..., int>(function_map);
    GenerateExternFunctionExtractor<N - 1, Ts..., double>(function_map);
  }
}

}  // namespace

void Initializer::DefineExternFunction(const xml::Element& xml_element) {
  static const ExternFunctionExtractorMap function_extractors = [] {
    ExternFunctionExtractorMap function_map;
    function_map.reserve(kNumInterfaces);
    GenerateExternFunctionExtractor<kMaxNumParam, int>(&function_map);
    GenerateExternFunctionExtractor<kMaxNumParam, double>(&function_map);
    assert(function_map.size() == kNumInterfaces);
    return function_map;
  }();

  const ExternLibrary& library = [this, &xml_element]() -> decltype(auto) {
    try {
      auto& lib = model_->Get<ExternLibrary>(xml_element.attribute("library"));
      lib.usage(true);
      return lib;
    } catch (UndefinedElement& err) {
      err << boost::errinfo_at_line(xml_element.line());
      throw;
    }
  }();

  ExternFunctionPtr extern_function = [&xml_element, &library] {
    auto args = GetNonAttributeElements(xml_element);
    assert(!args.empty());
    int num_args = std::distance(args.begin(), args.end()) - /*return*/ 1;
    if (num_args > kMaxNumParam) {
      SCRAM_THROW(ValidityError("The number of function parameters '" +
                                std::to_string(num_args) +
                                "' exceeds the number of allowed parameters '" +
                                std::to_string(kMaxNumParam) + "'"))
          << boost::errinfo_at_line(xml_element.line());
    }
    int encoding = Encode(args);
    try {
      return function_extractors.at(encoding)(
          std::string(xml_element.attribute("name")),
          std::string(xml_element.attribute("symbol")), library);
    } catch (Error& err) {
      err << boost::errinfo_at_line(xml_element.line());
      throw;
    }
  }();

  Register(std::move(extern_function), xml_element);
}

void Initializer::ValidateInitialization() {
  // Check if *all* gates have no cycles.
  cycle::CheckCycle<Gate>(model_->table<Gate>(), "gate");

  // Check for cycles in event tree instruction rules.
  cycle::CheckCycle<Rule>(model_->table<Rule>(), "rule");

  // Check for cycles in event tree branches.
  for (EventTree& event_tree : model_->table<EventTree>()) {
    try {
      cycle::CheckCycle<NamedBranch>(event_tree.table<NamedBranch>(), "branch");
    } catch (CycleError& err) {
      err << errinfo_container(event_tree.name(), "event tree");
      throw;
    }
  }

  // All other event-tree checks available after ensuring no-cycles in branches.
  for (const EventTree& event_tree : model_->event_trees()) {
    try {
      for (const NamedBranch& branch : event_tree.branches()) {
        CheckFunctionalEventOrder(branch);  // The order of events in forks.
        EnsureLinksOnlyInSequences(branch);  // Link instructions in sequences.
      }
      CheckFunctionalEventOrder(event_tree.initial_state());
      EnsureLinksOnlyInSequences(event_tree.initial_state());
    } catch (ValidityError& err) {
      err << errinfo_container(event_tree.name(), "event tree");
      throw;
    }
  }

  // The cycles in links are checked only after ensuring their valid locations.
  cycle::CheckCycle<Link>(boost::adaptors::indirect(links_), "event-tree link");

  // Event-tree instruction homogeneity checks only after cycle checks.
  for (const EventTree& event_tree : model_->event_trees()) {
    try {
      for (const NamedBranch& branch : event_tree.branches()) {
        EnsureHomogeneousEventTree(branch);  // No mixed instructions.
      }
      EnsureHomogeneousEventTree(event_tree.initial_state());
    } catch (ValidityError& err) {
      err << errinfo_container(event_tree.name(), "event tree");
      throw;
    }
  }

  EnsureNoSubstitutionConflicts();

  ValidateExpressions();
}

void Initializer::CheckFunctionalEventOrder(const Branch& branch) {
  struct CheckOrder {
    void operator()(Sequence*) const {}

    void operator()(NamedBranch* named_branch) const {
      std::visit(*this, named_branch->target());
    }

    void operator()(Fork* fork) const {
      if (functional_event.order() == fork->functional_event().order()) {
        assert(&functional_event == &fork->functional_event());
        SCRAM_THROW(ValidityError("Functional event " +
                                  functional_event.name() +
                                  " is duplicated in event tree fork paths."));
      }

      if (functional_event.order() > fork->functional_event().order())
        SCRAM_THROW(ValidityError(
            "Functional event " + functional_event.name() +
            " must appear after functional event " +
            fork->functional_event().name() + " in event tree fork paths."));
    }

    const FunctionalEvent& functional_event;
  };

  struct OrderValidator {
    void operator()(Sequence*) const {}
    void operator()(NamedBranch*) const {}
    void operator()(Fork* fork) const {
      for (const Path& fork_path : fork->paths()) {
        initializer->CheckFunctionalEventOrder(fork_path);
        std::visit(CheckOrder{fork->functional_event()}, fork_path.target());
      }
    }
    Initializer* initializer;
  };

  std::visit(OrderValidator{this}, branch.target());
}

void Initializer::EnsureLinksOnlyInSequences(const Branch& branch) {
  struct Validator : public NullVisitor {
    void Visit(const Link* link) override {
      SCRAM_THROW(ValidityError("Link " + link->event_tree().name() +
                                " can only be used in end-state sequences."));
    }
  };

  struct {
    void operator()(Sequence*) {}
    void operator()(const NamedBranch*) {}

    void operator()(const Branch* arg_branch) {
      for (const Instruction* instruction : arg_branch->instructions())
        instruction->Accept(&validator);
      std::visit(*this, arg_branch->target());
    }

    void operator()(Fork* fork) {
      for (const Path& fork_path : fork->paths())
        (*this)(&fork_path);
    }
    Validator validator;
  } link_checker;

  link_checker(&branch);
}

void Initializer::EnsureHomogeneousEventTree(const Branch& branch) {
  enum Type { kUnknown, kExpression, kFormula };

  struct Visitor : public NullVisitor {
    void Visit(const CollectExpression*) override {
      switch (type) {
        case kFormula:
          SCRAM_THROW(
              ValidityError("Mixed collect-expression and collect-formula"));
        case kUnknown:
          type = kExpression;
        case kExpression:
          break;
      }
    }

    void Visit(const CollectFormula*) override {
      switch (type) {
        case kExpression:
          SCRAM_THROW(
              ValidityError("Mixed collect-expression and collect-formula"));
        case kUnknown:
          type = kFormula;
        case kFormula:
          break;
      }
    }

    void Visit(const Link* link) override {
      (*this)(&link->event_tree().initial_state());
    }

    void CheckInstructions(const std::vector<Instruction*>& instructions) {
      for (const Instruction* instruction : instructions)
        instruction->Accept(this);
    }

    void operator()(const Sequence* sequence) {
      CheckInstructions(sequence->instructions());
    }
    void operator()(const Branch* arg_branch) {
      CheckInstructions(arg_branch->instructions());
      std::visit(*this, arg_branch->target());
    }
    void operator()(const Fork* fork) {
      for (const Path& fork_path : fork->paths())
        (*this)(&fork_path);
    }

    Type type = kUnknown;
  } homogeneous_checker;

  homogeneous_checker(&branch);
}

void Initializer::EnsureNoSubstitutionConflicts() {
  auto substitutions = model_->substitutions() |
                       boost::adaptors::filtered([](const auto& substitution) {
                         return !substitution.declarative();
                       });
  for (const Substitution& origin : substitutions) {
    const auto* target_ptr = std::get_if<BasicEvent*>(&origin.target());
    for (const Substitution& substitution : substitutions) {
      if (target_ptr && boost::count(substitution.source(), *target_ptr))
        SCRAM_THROW(
            ValidityError("Non-declarative substitution target event should "
                          "not appear in any substitution source."))
            << errinfo_element(origin.name(), "substitution");
      if (&origin == &substitution)
        continue;
      auto in_hypothesis = [&substitution](const BasicEvent* source) {
        return ext::any_of(substitution.hypothesis().args(),
                           [source](const Formula::Arg& arg) {
                             return std::get<BasicEvent*>(arg.event) == source;
                           });
      };
      if (target_ptr && in_hypothesis(*target_ptr))
        SCRAM_THROW(
            ValidityError("Non-declarative substitution target event should "
                          "not appear in another substitution hypothesis."))
            << errinfo_element(origin.name(), "substitution");
      if (ext::any_of(origin.source(), in_hypothesis))
        SCRAM_THROW(
            ValidityError("Non-declarative substitution source event should "
                          "not appear in another substitution hypothesis."))
            << errinfo_element(origin.name(), "substitution");
    }
  }
}

void Initializer::EnsureNoCcfSubstitutions() {
  auto substitutions = model_->substitutions() |
                       boost::adaptors::filtered([](const auto& substitution) {
                         return !substitution.declarative();
                       });
  auto is_ccf = [](const Substitution& substitution) {
    if (ext::any_of(substitution.hypothesis().args(),
                    [](const Formula::Arg& arg) {
                      return std::get<BasicEvent*>(arg.event)->HasCcf();
                    }))
      return true;

    const auto* target_ptr = std::get_if<BasicEvent*>(&substitution.target());
    if (target_ptr && (*target_ptr)->HasCcf())
      return true;

    if (ext::any_of(substitution.source(), std::mem_fn(&BasicEvent::HasCcf)))
      return true;

    return false;
  };

  for (const Substitution& substitution : substitutions) {
    if (is_ccf(substitution))
      SCRAM_THROW(ValidityError("Non-declarative substitution '" +
                                substitution.name() +
                                "' events cannot be in a CCF group."));
  }
}

void Initializer::EnsureSubstitutionsWithApproximations() {
  if (settings_.approximation() != core::Approximation::kNone)
    return;

  if (ext::any_of(model_->substitutions(),
                  [](const Substitution& substitution) {
                    return !substitution.declarative();
                  }))
    SCRAM_THROW(ValidityError(
        "Non-declarative substitutions do not apply to exact analyses."));
}

void Initializer::ValidateExpressions() {
  // Check for cycles in parameters.
  // This must be done before expressions.
  cycle::CheckCycle<Parameter>(model_->table<Parameter>(), "parameter");

  // Validate expressions.
  for (const std::pair<Expression*, xml::Element>& expression : expressions_) {
    try {
      expression.first->Validate();
    } catch (ValidityError& err) {
      err << boost::errinfo_file_name(expression.second.filename())
          << boost::errinfo_at_line(expression.second.line());
      throw;
    }
  }

  // Validate CCF groups.
  for (const CcfGroup& group : model_->ccf_groups())
    group.Validate();

  // Check probability values for primary events.
  for (const BasicEvent& event : model_->basic_events()) {
    if (event.HasExpression())
      event.Validate();
  }
}

void Initializer::SetupForAnalysis() {
  {
    TIMER(DEBUG2, "Collecting top events of fault trees");
    for (Gate& gate : model_->table<Gate>())
      gate.mark(NodeMark::kClear);

    for (FaultTree& ft : model_->table<FaultTree>())
      ft.CollectTopEvents();
  }

  {
    TIMER(DEBUG2, "Applying CCF models");
    // CCF groups must apply models to basic event members.
    for (CcfGroup& group : model_->table<CcfGroup>())
      group.ApplyModel();
  }
}

}  // namespace scram::mef

Updated on 2025-11-11 at 16:51:08 +0000