packages/engine/scram/src/reporter.cc
Implements Reporter class.
Namespaces
| Name |
|---|
| scram |
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 "reporter.h"
#include <ctime>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <boost/algorithm/string/join.hpp>
#include <boost/exception/errinfo_errno.hpp>
#include <boost/exception/errinfo_file_name.hpp>
#include <boost/exception/errinfo_file_open_mode.hpp>
#include <boost/range/adaptor/filtered.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include "ccf_group.h"
#include "element.h"
#include "error.h"
#include "logger.h"
#include "parameter.h"
namespace scram {
namespace {
std::string Base64Encode(const unsigned char* data, std::size_t len) {
static constexpr char kTable[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
if (len == 0)
return {};
std::string out;
out.reserve(((len + 2) / 3) * 4);
std::size_t i = 0;
while (i + 3 <= len) {
const unsigned int b0 = data[i++];
const unsigned int b1 = data[i++];
const unsigned int b2 = data[i++];
out.push_back(kTable[(b0 >> 2) & 0x3F]);
out.push_back(kTable[((b0 & 0x03) << 4) | ((b1 >> 4) & 0x0F)]);
out.push_back(kTable[((b1 & 0x0F) << 2) | ((b2 >> 6) & 0x03)]);
out.push_back(kTable[b2 & 0x3F]);
}
const std::size_t rem = len - i;
if (rem == 1) {
const unsigned int b0 = data[i++];
out.push_back(kTable[(b0 >> 2) & 0x3F]);
out.push_back(kTable[((b0 & 0x03) << 4)]);
out.push_back('=');
out.push_back('=');
} else if (rem == 2) {
const unsigned int b0 = data[i++];
const unsigned int b1 = data[i++];
out.push_back(kTable[(b0 >> 2) & 0x3F]);
out.push_back(kTable[((b0 & 0x03) << 4) | ((b1 >> 4) & 0x0F)]);
out.push_back(kTable[((b1 & 0x0F) << 2)]);
out.push_back('=');
}
return out;
}
void PutId(const core::RiskAnalysis::Result::Id& id,
xml::StreamElement* report) {
struct {
void operator()(const mef::Gate* gate) {
report_->SetAttribute("name", gate->id());
}
void operator()(const std::pair<const mef::InitiatingEvent&,
const mef::Sequence&>& sequence) {
report_->SetAttribute("initiating-event", sequence.first.name());
report_->SetAttribute("name", sequence.second.name());
}
xml::StreamElement* report_;
} extractor{report};
std::visit(extractor, id.target);
if (id.context) {
report->SetAttribute("alignment", id.context->alignment.name());
report->SetAttribute("phase", id.context->phase.name());
}
}
} // namespace
void Reporter::Report(const core::RiskAnalysis& risk_an, std::FILE* out,
bool indent,
std::optional<std::uint64_t> total_runtime_clock_start) {
xml::Stream xml_stream(out, indent);
xml::StreamElement report = xml_stream.root("report");
const bool has_results =
!(risk_an.results().empty() && risk_an.event_tree_results().empty());
// Map to store per-result reporting times
std::unordered_map<const void*, double> report_times;
if (has_results) {
TIMER(DEBUG1, "Reporting analysis results");
xml::StreamElement results = report.AddChild("results");
if (risk_an.settings().probability_analysis()) {
for (const core::RiskAnalysis::EtaResult& result :
risk_an.event_tree_results()) {
ReportResults(result, &results);
}
}
for (const core::RiskAnalysis::Result& result : risk_an.results()) {
CLOCK(result_report_time);
if (result.fault_tree_analysis) {
if (!result.fault_tree_analysis->settings().skip_products()) {
ReportResults(result.id, *result.fault_tree_analysis,
result.probability_analysis.get(), &results);
}
}
if (result.probability_analysis)
ReportResults(result.id, *result.probability_analysis, &results);
if (result.importance_analysis)
ReportResults(result.id, *result.importance_analysis, &results);
if (result.uncertainty_analysis)
ReportResults(result.id, *result.uncertainty_analysis, &results);
// Store the reporting time for this result
report_times[&result] = DUR(result_report_time);
}
}
ReportInformation(risk_an, &report, total_runtime_clock_start, &report_times);
}
void Reporter::Report(const core::RiskAnalysis& risk_an,
const std::string& file, bool indent,
std::optional<std::uint64_t> total_runtime_clock_start) {
std::unique_ptr<std::FILE, decltype(&std::fclose)> fp(
std::fopen(file.c_str(), "w"), &std::fclose);
try {
if (!fp) {
SCRAM_THROW(IOError("Cannot open the output file for report."))
<< boost::errinfo_errno(errno) << boost::errinfo_file_open_mode("w");
}
Report(risk_an, fp.get(), indent, total_runtime_clock_start);
} catch (IOError& err) {
err << boost::errinfo_file_name(file);
throw;
}
}
template <>
void Reporter::ReportCalculatedQuantity<core::FaultTreeAnalysis>(
const core::Settings& settings, xml::StreamElement* information) {
{
xml::StreamElement quant = information->AddChild("calculated-quantity");
if (settings.prime_implicants()) {
quant.SetAttribute("name", "Prime Implicants");
} else {
quant.SetAttribute("name", "Minimal Cut Sets");
}
xml::StreamElement methods = quant.AddChild("calculation-method");
switch (settings.algorithm()) {
case core::Algorithm::kBdd:
methods.SetAttribute("name", "Binary Decision Diagram");
break;
case core::Algorithm::kZbdd:
methods.SetAttribute("name", "Zero-Suppressed Binary Decision Diagram");
break;
case core::Algorithm::kMocus:
methods.SetAttribute("name", "MOCUS");
break;
}
methods.AddChild("limits")
.AddChild("product-order")
.AddText(settings.limit_order());
}
if (settings.ccf_analysis()) {
information->AddChild("calculated-quantity")
.SetAttribute("name", "Common Cause Failure Analysis")
.SetAttribute("definition",
"Incorporation of common cause failure models");
}
}
template <>
void Reporter::ReportCalculatedQuantity<core::ProbabilityAnalysis>(
const core::Settings& settings, xml::StreamElement* information) {
xml::StreamElement quant = information->AddChild("calculated-quantity");
quant.SetAttribute("name", "Probability Analysis")
.SetAttribute("definition",
"Quantitative analysis of"
" failure probability or unavailability")
.SetAttribute("approximation",
core::kApproximationToString[static_cast<int>(
settings.approximation())]);
xml::StreamElement methods = quant.AddChild("calculation-method");
switch (settings.approximation()) {
case core::Approximation::kNone:
methods.SetAttribute("name", "Binary Decision Diagram");
break;
case core::Approximation::kRareEvent:
methods.SetAttribute("name", "Rare-Event Approximation");
break;
case core::Approximation::kMcub:
methods.SetAttribute("name", "MCUB Approximation");
break;
}
xml::StreamElement limits = methods.AddChild("limits");
limits.AddChild("mission-time").AddText(settings.mission_time());
if (settings.time_step())
limits.AddChild("time-step").AddText(settings.time_step());
}
template <>
void Reporter::ReportCalculatedQuantity<core::ImportanceAnalysis>(
const core::Settings& /*settings*/, xml::StreamElement* information) {
information->AddChild("calculated-quantity")
.SetAttribute("name", "Importance Analysis")
.SetAttribute("definition",
"Quantitative analysis of contributions and "
"importance factors of events.");
}
template <>
void Reporter::ReportCalculatedQuantity<core::UncertaintyAnalysis>(
const core::Settings& settings, xml::StreamElement* information) {
xml::StreamElement quant = information->AddChild("calculated-quantity");
quant.SetAttribute("name", "Uncertainty Analysis")
.SetAttribute("definition",
"Calculation of uncertainties with the Monte Carlo method");
xml::StreamElement methods = quant.AddChild("calculation-method");
methods.SetAttribute("name", "Monte Carlo");
xml::StreamElement limits = methods.AddChild("limits");
limits.AddChild("number-of-trials").AddText(settings.num_trials());
if (settings.seed() >= 0) {
limits.AddChild("seed").AddText(settings.seed());
}
}
template <>
void Reporter::ReportCalculatedQuantity<core::RiskAnalysis>(
const core::Settings& settings, xml::StreamElement* information) {
// Report the fault tree analysis by default.
ReportCalculatedQuantity<core::FaultTreeAnalysis>(settings, information);
// Report optional analyses.
if (settings.probability_analysis()) {
ReportCalculatedQuantity<core::ProbabilityAnalysis>(settings, information);
}
if (settings.safety_integrity_levels()) {
information->AddChild("calculated-quantity")
.SetAttribute("name", "Safety Integrity Levels");
}
if (settings.importance_analysis()) {
ReportCalculatedQuantity<core::ImportanceAnalysis>(settings, information);
}
if (settings.uncertainty_analysis()) {
ReportCalculatedQuantity<core::UncertaintyAnalysis>(settings, information);
}
}
void Reporter::ReportInformation(
const core::RiskAnalysis& risk_an, xml::StreamElement* report,
std::optional<std::uint64_t> total_runtime_clock_start,
const std::unordered_map<const void*, double>* report_times) {
xml::StreamElement information = report->AddChild("information");
ReportSoftwareInformation(&information);
ReportCalculatedQuantity(risk_an.settings(), &information);
ReportModelFeatures(risk_an.model(), &information);
ReportUnusedElements(risk_an.model().basic_events(),
"Unused basic events: ", &information);
ReportUnusedElements(risk_an.model().house_events(),
"Unused house events: ", &information);
ReportUnusedElements(risk_an.model().parameters(),
"Unused parameters: ", &information);
ReportUnusedElements(risk_an.model().libraries(),
"Unused libraries: ", &information);
ReportUnusedElements(risk_an.model().extern_functions(),
"Unused extern functions: ", &information);
ReportUnusedElements(risk_an.model().initiating_events(),
"Unused initiating events: ", &information);
ReportUnusedElements(risk_an.model().event_trees(),
"Unused event trees: ", &information);
ReportUnusedElements(risk_an.model().sequences(),
"Unused sequences: ", &information);
ReportUnusedElements(risk_an.model().rules(), "Unused rules: ", &information);
for (const mef::EventTree& event_tree : risk_an.model().event_trees()) {
std::string header = "In event tree " + event_tree.name() + ", ";
ReportUnusedElements(event_tree.branches(),
header + "unused branches: ", &information);
ReportUnusedElements(event_tree.functional_events(),
header + "unused functional events: ", &information);
}
ReportPerformance(risk_an, &information, total_runtime_clock_start, report_times);
}
void Reporter::ReportSoftwareInformation(xml::StreamElement* information) {
information->AddChild("software")
.SetAttribute("name", "SCRAM")
.SetAttribute("version", "UNSET")
.SetAttribute("contacts", "");
std::time_t current_time = std::time(nullptr);
char iso_extended[20] = {};
auto ret = std::strftime(iso_extended, sizeof(iso_extended),
"%Y-%m-%dT%H:%M:%S", std::gmtime(¤t_time));
assert(ret && "Time formatting failure. Who is running this in year 10000?");
if (ret) // The user can set the wall-clock year beyond 10k.
information->AddChild("time").AddText(iso_extended);
}
void Reporter::ReportModelFeatures(const mef::Model& model,
xml::StreamElement* information) {
xml::StreamElement model_features = information->AddChild("model-features");
if (!model.HasDefaultName())
model_features.SetAttribute("name", model.name());
auto feature = [&model_features](const char* name, const auto& container) {
if (!container.empty())
model_features.AddChild(name).AddText(container.size());
};
feature("gates", model.gates());
feature("basic-events", model.basic_events());
feature("house-events", model.house_events());
feature("ccf-groups", model.ccf_groups());
feature("fault-trees", model.fault_trees());
feature("event-trees", model.event_trees());
int num_functional_events = 0;
for (const mef::EventTree& event_tree : model.event_trees())
num_functional_events += event_tree.functional_events().size();
if (num_functional_events)
model_features.AddChild("functional-events").AddText(num_functional_events);
feature("sequences", model.sequences());
feature("rules", model.rules());
feature("initiating-events", model.initiating_events());
feature("substitutions", model.substitutions());
}
void Reporter::ReportPerformance(
const core::RiskAnalysis& risk_an, xml::StreamElement* information,
std::optional<std::uint64_t> total_runtime_clock_start,
const std::unordered_map<const void*, double>* report_times) {
if (risk_an.results().empty())
return;
// Setup for performance information.
xml::StreamElement performance = information->AddChild("performance");
for (const core::RiskAnalysis::Result& result : risk_an.results()) {
xml::StreamElement calc_time = performance.AddChild("calculation-time");
scram::PutId(result.id, &calc_time);
if (result.preprocessing_seconds)
calc_time.AddChild("preprocessing")
.AddText(*result.preprocessing_seconds);
if (result.fault_tree_analysis)
calc_time.AddChild("products")
.AddText(result.fault_tree_analysis->analysis_time());
if (result.probability_analysis)
calc_time.AddChild("probability")
.AddText(result.probability_analysis->analysis_time());
if (result.importance_analysis)
calc_time.AddChild("importance")
.AddText(result.importance_analysis->analysis_time());
if (result.uncertainty_analysis)
calc_time.AddChild("uncertainty")
.AddText(result.uncertainty_analysis->analysis_time());
// Add the measured report generation time if available
if (report_times && report_times->count(&result)) {
calc_time.AddChild("report-generation")
.AddText(report_times->at(&result));
} else if (result.report_generation_seconds) {
// Fallback to stored value if available
calc_time.AddChild("report-generation")
.AddText(*result.report_generation_seconds);
}
}
if (const auto& metrics = risk_an.runtime_metrics()) {
xml::StreamElement summary = performance.AddChild("runtime-summary");
summary.AddChild("analysis-seconds").AddText(metrics->analysis_seconds);
std::optional<double> total_runtime_seconds;
if (total_runtime_clock_start) {
const double total_seconds =
(static_cast<double>(TIME_STAMP() - *total_runtime_clock_start)) *
1e-9;
total_runtime_seconds = total_seconds;
} else if (metrics->total_runtime_seconds) {
total_runtime_seconds = metrics->total_runtime_seconds;
}
if (total_runtime_seconds)
summary.AddChild("total-seconds").AddText(*total_runtime_seconds);
if (metrics->post_analysis_rss_mib || metrics->post_analysis_peak_rss_mib) {
xml::StreamElement post_analysis = summary.AddChild("post-analysis-memory");
if (metrics->post_analysis_rss_mib)
post_analysis.AddChild("rss-mib").AddText(*metrics->post_analysis_rss_mib);
if (metrics->post_analysis_peak_rss_mib)
post_analysis.AddChild("peak-rss-mib").AddText(*metrics->post_analysis_peak_rss_mib);
}
if (metrics->post_run_rss_mib || metrics->post_run_peak_rss_mib) {
xml::StreamElement post_run = summary.AddChild("post-run-memory");
if (metrics->post_run_rss_mib)
post_run.AddChild("rss-mib").AddText(*metrics->post_run_rss_mib);
if (metrics->post_run_peak_rss_mib)
post_run.AddChild("peak-rss-mib").AddText(*metrics->post_run_peak_rss_mib);
}
}
}
template <class T>
void Reporter::ReportUnusedElements(const T& container,
const std::string& header,
xml::StreamElement* information) {
std::string out = boost::join(
container | boost::adaptors::filtered([](const auto& arg) {
return !arg.usage();
}) | boost::adaptors::transformed([](const auto& arg) -> decltype(auto) {
return mef::Id::unique_name(arg);
}),
" ");
if (!out.empty())
information->AddChild("warning").AddText(header + out);
}
void Reporter::ReportResults(const core::RiskAnalysis::EtaResult& eta_result,
xml::StreamElement* results) {
const core::EventTreeAnalysis& eta = *eta_result.event_tree_analysis;
xml::StreamElement initiating_event = results->AddChild("initiating-event");
initiating_event.SetAttribute("name", eta.initiating_event().name());
if (eta_result.context) {
initiating_event
.SetAttribute("alignment", eta_result.context->alignment.name())
.SetAttribute("phase", eta_result.context->phase.name());
}
initiating_event.SetAttribute("sequences", eta.sequences().size());
for (const core::EventTreeAnalysis::Result& result_sequence :
eta.sequences()) {
initiating_event.AddChild("sequence")
.SetAttribute("name", result_sequence.sequence.name())
.SetAttribute("value", result_sequence.p_sequence);
}
}
void Reporter::ReportResults(const core::RiskAnalysis::Result::Id& id,
const core::FaultTreeAnalysis& fta,
const core::ProbabilityAnalysis* prob_analysis,
xml::StreamElement* results) {
TIMER(DEBUG2, "Reporting products");
xml::StreamElement sum_of_products = results->AddChild("sum-of-products");
scram::PutId(id, &sum_of_products);
std::string warning = fta.warnings();
if (prob_analysis && prob_analysis->warnings().empty() == false)
warning += (warning.empty() ? "" : "; ") + prob_analysis->warnings();
if (!warning.empty())
sum_of_products.SetAttribute("warning", warning);
const core::ProductSummary* summary = fta.last_product_summary();
if (summary) {
sum_of_products.SetAttribute("original-products", summary->original_product_count);
}
if (fta.adaptive_mode_used()) {
if (fta.adaptive_target_probability() > 0.0)
sum_of_products.SetAttribute("exact-probability", fta.adaptive_target_probability());
}
// If products are unavailable (BDD probability-only mode), skip product metrics.
bool has_products = fta.has_products();
if (has_products) {
sum_of_products
.SetAttribute("basic-events", fta.products().product_events().size())
.SetAttribute("products", fta.products().size());
}
if (prob_analysis)
sum_of_products.SetAttribute("probability", prob_analysis->p_total());
if (has_products && fta.products().empty() == false) {
sum_of_products.SetAttribute(
"distribution",
boost::join(fta.products().distribution() |
boost::adaptors::transformed(
[](int number) { return std::to_string(number); }),
" "));
}
double sum = 0; // Sum of probabilities for contribution calculations.
if (has_products) {
if (fta.settings().bit_pack_cut_sets()) {
const core::ProductContainer& products = fta.products();
const core::Pdag& graph = products.graph();
const std::size_t num_events = graph.basic_events().size();
const std::size_t bytes_per_vector = (num_events + 7) / 8;
constexpr std::size_t kBatchRecords = 10'000'000;
constexpr std::size_t kFlushBytes = 1u << 20; // 1 MiB base64 chunks.
// Map BasicEvent* to 0-based index for fast packing.
std::unordered_map<const mef::BasicEvent*, std::size_t> event_index;
event_index.reserve(num_events);
for (std::size_t i = 0; i < num_events; ++i) {
const int pdag_index = core::Pdag::kVariableStartIndex + static_cast<int>(i);
const mef::BasicEvent* be = graph.basic_events()[pdag_index];
if (be)
event_index.emplace(be, i);
}
xml::StreamElement packed = sum_of_products.AddChild("bit-packed-cut-sets");
packed.SetAttribute("encoding", "base64")
.SetAttribute("batch-records", kBatchRecords)
.SetAttribute("basic-events", num_events)
.SetAttribute("bytes-per-vector", bytes_per_vector)
.SetAttribute("order-bytes", 2)
.SetAttribute("endianness", "little")
.SetAttribute("bit-order", "lsb0");
{
xml::StreamElement table = packed.AddChild("basic-event-table");
for (std::size_t i = 0; i < num_events; ++i) {
const int pdag_index = core::Pdag::kVariableStartIndex + static_cast<int>(i);
const mef::BasicEvent* be = graph.basic_events()[pdag_index];
if (!be)
continue;
table.AddChild("basic-event")
.SetAttribute("index", i)
.SetAttribute("name", mef::Id::unique_name(*be));
}
}
xml::StreamElement buffers = packed.AddChild("buffers");
auto it = products.begin();
const auto it_end = products.end();
const std::size_t record_bytes = 2 + bytes_per_vector;
std::vector<unsigned char> record(record_bytes);
std::vector<unsigned char> bin;
bin.reserve(std::min<std::size_t>(kFlushBytes + record_bytes, 4 * kFlushBytes));
for (std::size_t buffer_index = 0; it != it_end; ++buffer_index) {
xml::StreamElement buffer = buffers.AddChild("buffer");
buffer.SetAttribute("index", buffer_index)
.SetAttribute("max-records", kBatchRecords)
.SetAttribute("record-bytes", record_bytes);
xml::StreamElement data = buffer.AddChild("data");
data.SetAttribute("encoding", "base64");
std::size_t in_batch = 0;
bin.clear();
while (it != it_end && in_batch < kBatchRecords) {
const core::Product& product_set = *it;
++it;
// Record layout: [uint16 order][bitvector bytes]
std::fill(record.begin(), record.end(), 0);
std::uint16_t order = 0;
for (const core::Literal& literal : product_set) {
if (literal.complement)
continue;
const auto found = event_index.find(&literal.event);
if (found == event_index.end())
continue;
const std::size_t bit = found->second;
const std::size_t byte = bit / 8;
const std::size_t bit_in_byte = bit % 8;
record[2 + byte] |= static_cast<unsigned char>(1u << bit_in_byte);
++order;
}
record[0] = static_cast<unsigned char>(order & 0xFF);
record[1] = static_cast<unsigned char>((order >> 8) & 0xFF);
const std::size_t old_size = bin.size();
bin.resize(old_size + record_bytes);
std::copy(record.begin(), record.end(), bin.begin() + static_cast<std::ptrdiff_t>(old_size));
++in_batch;
if (bin.size() >= kFlushBytes) {
data.AddText(Base64Encode(bin.data(), bin.size()));
bin.clear();
}
}
if (!bin.empty()) {
data.AddText(Base64Encode(bin.data(), bin.size()));
}
}
return;
}
if (prob_analysis) {
for (const core::Product& product_set : fta.products())
sum += product_set.p();
}
for (const core::Product& product_set : fta.products()) {
xml::StreamElement product = sum_of_products.AddChild("product");
product.SetAttribute("order", product_set.order());
if (prob_analysis) {
double prob = product_set.p();
product.SetAttribute("probability", prob);
if (sum != 0)
product.SetAttribute("contribution", prob / sum);
}
for (const core::Literal& literal : product_set) {
ReportLiteral(literal, &product);
}
}
}
}
void Reporter::ReportResults(const core::RiskAnalysis::Result::Id& id,
const core::ProbabilityAnalysis& prob_analysis,
xml::StreamElement* results) {
if (!prob_analysis.p_time().empty()) {
xml::StreamElement curve = results->AddChild("curve");
scram::PutId(id, &curve);
curve.SetAttribute("description", "Probability values over time")
.SetAttribute("X-title", "Mission time")
.SetAttribute("Y-title", "Probability")
.SetAttribute("X-unit", "hours");
for (const std::pair<double, double>& p_vs_time : prob_analysis.p_time()) {
curve.AddChild("point")
.SetAttribute("X", p_vs_time.second)
.SetAttribute("Y", p_vs_time.first);
}
}
if (prob_analysis.settings().safety_integrity_levels()) {
xml::StreamElement sil = results->AddChild("safety-integrity-levels");
scram::PutId(id, &sil);
sil.SetAttribute("PFD-avg", prob_analysis.sil().pfd_avg)
.SetAttribute("PFH-avg", prob_analysis.sil().pfh_avg);
auto report_sil_fractions = [&sil](const auto& sil_fractions) {
xml::StreamElement hist = sil.AddChild("histogram");
hist.SetAttribute("number", sil_fractions.size());
double b_0 = 0;
int bin_number = 1;
for (const std::pair<const double, double>& sil_bucket : sil_fractions) {
double b_1 = sil_bucket.first;
hist.AddChild("bin")
.SetAttribute("number", bin_number++)
.SetAttribute("value", sil_bucket.second)
.SetAttribute("lower-bound", b_0)
.SetAttribute("upper-bound", b_1);
b_0 = b_1;
}
};
report_sil_fractions(prob_analysis.sil().pfd_fractions);
report_sil_fractions(prob_analysis.sil().pfh_fractions);
}
}
void Reporter::ReportResults(
const core::RiskAnalysis::Result::Id& id,
const core::ImportanceAnalysis& importance_analysis,
xml::StreamElement* results) {
xml::StreamElement importance = results->AddChild("importance");
scram::PutId(id, &importance);
if (!importance_analysis.warnings().empty()) {
importance.SetAttribute("warning", importance_analysis.warnings());
}
importance.SetAttribute("basic-events",
importance_analysis.importance().size());
for (const core::ImportanceRecord& entry : importance_analysis.importance()) {
const core::ImportanceFactors& factors = entry.factors;
const mef::BasicEvent& event = entry.event;
auto add_data = [&event, &factors](xml::StreamElement* element) {
element->SetAttribute("occurrence", factors.occurrence)
.SetAttribute("probability", event.p())
.SetAttribute("MIF", factors.mif)
.SetAttribute("CIF", factors.cif)
.SetAttribute("DIF", factors.dif)
.SetAttribute("RAW", factors.raw)
.SetAttribute("RRW", factors.rrw);
};
ReportBasicEvent(event, &importance, add_data);
}
}
void Reporter::ReportResults(const core::RiskAnalysis::Result::Id& id,
const core::UncertaintyAnalysis& uncert_analysis,
xml::StreamElement* results) {
xml::StreamElement measure = results->AddChild("measure");
scram::PutId(id, &measure);
if (!uncert_analysis.warnings().empty()) {
measure.SetAttribute("warning", uncert_analysis.warnings());
}
measure.AddChild("mean").SetAttribute("value", uncert_analysis.mean());
measure.AddChild("standard-deviation")
.SetAttribute("value", uncert_analysis.sigma());
measure.AddChild("confidence-range")
.SetAttribute("percentage", "95")
.SetAttribute("lower-bound", uncert_analysis.confidence_interval().first)
.SetAttribute("upper-bound",
uncert_analysis.confidence_interval().second);
measure.AddChild("error-factor")
.SetAttribute("percentage", "95")
.SetAttribute("value", uncert_analysis.error_factor());
{
xml::StreamElement quantiles = measure.AddChild("quantiles");
int num_quantiles = uncert_analysis.quantiles().size();
quantiles.SetAttribute("number", num_quantiles);
double prev_bound = 0;
double delta = 1.0 / num_quantiles;
for (int i = 0; i < num_quantiles; ++i) {
double upper = uncert_analysis.quantiles()[i];
double value = delta * (i + 1);
quantiles.AddChild("quantile")
.SetAttribute("number", i + 1)
.SetAttribute("value", value)
.SetAttribute("lower-bound", prev_bound)
.SetAttribute("upper-bound", upper);
prev_bound = upper;
}
}
{
xml::StreamElement hist = measure.AddChild("histogram");
int num_bins = uncert_analysis.distribution().size() - 1;
hist.SetAttribute("number", num_bins);
for (int i = 0; i < num_bins; ++i) {
double lower = uncert_analysis.distribution()[i].first;
double upper = uncert_analysis.distribution()[i + 1].first;
double value = uncert_analysis.distribution()[i].second;
hist.AddChild("bin")
.SetAttribute("number", i + 1)
.SetAttribute("value", value)
.SetAttribute("lower-bound", lower)
.SetAttribute("upper-bound", upper);
}
}
}
void Reporter::ReportLiteral(const core::Literal& literal,
xml::StreamElement* parent) {
auto add_data = [](xml::StreamElement* /*element*/) {};
if (literal.complement) {
xml::StreamElement not_parent = parent->AddChild("not");
ReportBasicEvent(literal.event, ¬_parent, add_data);
} else {
ReportBasicEvent(literal.event, parent, add_data);
}
}
template <class T>
void Reporter::ReportBasicEvent(const mef::BasicEvent& basic_event,
xml::StreamElement* parent, const T& add_data) {
const auto* ccf_event = dynamic_cast<const mef::CcfEvent*>(&basic_event);
if (!ccf_event) {
xml::StreamElement element = parent->AddChild("basic-event");
element.SetAttribute("name", basic_event.id());
add_data(&element);
} else {
const mef::CcfGroup& ccf_group = ccf_event->ccf_group();
xml::StreamElement element = parent->AddChild("ccf-event");
element.SetAttribute("ccf-group", ccf_group.id())
.SetAttribute("order", ccf_event->members().size())
.SetAttribute("group-size", ccf_group.members().size());
add_data(&element);
for (const mef::Gate* member : ccf_event->members()) {
element.AddChild("basic-event").SetAttribute("name", member->name());
}
}
}
} // namespace scramUpdated on 2026-01-09 at 21:59:13 +0000
