Skip to content

packages/engine/scram/src/product_filter.cc

Namespaces

Name
scram
scram::core
scram::core::product_filter

Attributes

Name
std::vector< int >product
doubleprobability

Attributes Documentation

variable product

cpp
std::vector< int > product;

variable probability

cpp
double probability;

Source code

cpp
/*
 * Copyright (C) 2025 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 "product_filter.h"

#include <algorithm>
#include <cmath>
#include <limits>

#include <boost/range/algorithm.hpp>

namespace scram::core::product_filter {

namespace {

struct ScoredProduct {
    std::vector<int> product;
    double probability;
};

} // namespace

double CalculateProductProbability(const std::vector<int> &product,
                                    const Pdag &graph,
                                    double stop_threshold) {
    const int kFirstIndex = Pdag::kVariableStartIndex;
    const int kLastIndexExclusive =
        kFirstIndex + static_cast<int>(graph.basic_events().size());
    double probability = 1.0;
    for (int literal : product) {
        const int index = std::abs(literal);
        if (index < kFirstIndex || index >= kLastIndexExclusive)
            continue;
        const mef::BasicEvent *event = graph.basic_events()[index];
        const double event_probability = event ? event->p() : 0.0;
        probability *= literal < 0 ? 1.0 - event_probability : event_probability;
        if (stop_threshold >= 0.0 && probability < stop_threshold)
            break;
    }
    probability *= graph.initiating_event_frequency();
    return probability;
}

ProductSummary FilterProducts(const Zbdd &products,
                              const Pdag &graph,
                              const FilterOptions &options,
                              const ProductConsumer &consumer) {
    ProductSummary summary;
    const bool enforce_order = options.limit_order > 0;
    const bool enforce_cut_off = options.cut_off > 0.0;
    const bool adaptive_active = options.adaptive && options.adaptive_target > 0.0;
    const bool filtering_requested = enforce_order || enforce_cut_off || adaptive_active;
    const bool requires_probability = enforce_cut_off || adaptive_active;

    if (!filtering_requested) {
        Pdag::IndexMap<bool> seen_events(graph.basic_events().size());
        const int kFirstIndex = Pdag::kVariableStartIndex;
        const int kLastIndexExclusive = kFirstIndex + static_cast<int>(graph.basic_events().size());

        for (const std::vector<int> &product : products) {
            summary.original_product_count++;
            summary.product_count++;
            const int order_index = product.empty() ? 0 : static_cast<int>(product.size()) - 1;
            if (static_cast<int>(summary.distribution.size()) <= order_index)
                summary.distribution.resize(order_index + 1, 0);
            summary.distribution[order_index]++;

            for (int literal : product) {
                const int index = std::abs(literal);
                if (index < kFirstIndex || index >= kLastIndexExclusive)
                    continue;
                if (seen_events[index])
                    continue;
                seen_events[index] = true;
                summary.event_indices.push_back(index);
            }
        }

        boost::sort(summary.event_indices);
        return summary;
    }

    std::vector<ScoredProduct> retained;
    retained.reserve(64);

    for (const std::vector<int> &product : products) {
        summary.original_product_count++;

        if (enforce_order && static_cast<int>(product.size()) > options.limit_order)
            continue;

        double probability = 0.0;
        if (requires_probability || adaptive_active) {
            if (options.exact_quantification) {
                const double stop_threshold = enforce_cut_off ? options.cut_off : -1.0;
                probability = CalculateProductProbability(product, graph, stop_threshold);
            } else {
                probability = CalculateProductProbability(product, graph);
            }
        }

        // Filter based on the mean of probability and machine epsilon.
        double epsilon = std::numeric_limits<double>::epsilon();
        double threshold = 0.0;

        // Method 1: Log 10 based arithmetic mean
        if (probability > 0) {
            double log_prob = std::log10(probability);
            double log_eps = std::log10(epsilon);
            double log_mean = (log_prob + log_eps) / 2.0;
            threshold = std::pow(10, log_mean);
        }

        // Method 2: Harmonic Mean
        // threshold = (2.0 * probability * epsilon) / (probability + epsilon);

        // Method 3: Geometric Mean
        // threshold = std::sqrt(probability * epsilon);

        if (probability <= threshold)
            continue;

        if (enforce_cut_off && probability < options.cut_off)
            continue;

        retained.push_back({product, probability});
    }

    double applied_cut_off = enforce_cut_off ? options.cut_off : 0.0;

    if (adaptive_active && !retained.empty()) {
        const bool use_rare_event = options.approximation == Approximation::kRareEvent;
        boost::sort(retained, [](const ScoredProduct &lhs, const ScoredProduct &rhs) {
            return lhs.probability > rhs.probability;
        });

        std::vector<ScoredProduct> adaptive_subset;
        adaptive_subset.reserve(retained.size());
        double complement_acc = 1.0;
        double rare_sum = 0.0;
        for (const auto &item : retained) {
            adaptive_subset.push_back(item);
            double estimated_total = 0.0;
            if (use_rare_event) {
                rare_sum += item.probability;
                rare_sum = std::min(rare_sum, 1.0);
                estimated_total = rare_sum;
            } else {
                const double complement_factor = std::clamp(1.0 - item.probability, 0.0, 1.0);
                complement_acc *= complement_factor;
                estimated_total = 1.0 - complement_acc;
            }

            if (estimated_total + options.epsilon >= options.adaptive_target) {
                applied_cut_off = item.probability;
                break;
            }
        }
        if (!adaptive_subset.empty()) {
            retained.swap(adaptive_subset);
        }
    }

    summary.product_count = static_cast<int>(retained.size());
    summary.pruned_products = std::max(0, summary.original_product_count - summary.product_count);
    summary.cut_off_applied = enforce_cut_off || (adaptive_active && !retained.empty());
    summary.applied_cut_off = summary.cut_off_applied ? applied_cut_off : 0.0;

    Pdag::IndexMap<bool> seen_events(graph.basic_events().size());
    const int kFirstIndex = Pdag::kVariableStartIndex;
    const int kLastIndexExclusive = kFirstIndex + static_cast<int>(graph.basic_events().size());

    for (const auto &item : retained) {
        const std::vector<int> &product = item.product;
        const int order_index = product.empty() ? 0 : static_cast<int>(product.size()) - 1;
        if (static_cast<int>(summary.distribution.size()) <= order_index)
            summary.distribution.resize(order_index + 1, 0);
        summary.distribution[order_index]++;

        for (int literal : product) {
            const int index = std::abs(literal);
            if (index < kFirstIndex || index >= kLastIndexExclusive)
                continue;
            if (seen_events[index])
                continue;
            seen_events[index] = true;
            summary.event_indices.push_back(index);
        }
    }

    const bool emit_filtered = consumer && (!retained.empty()) &&
                               (summary.pruned_products > 0 || summary.cut_off_applied || adaptive_active);
    if (emit_filtered) {
        for (const auto &item : retained)
            consumer(item.product, item.probability);
    }

    boost::sort(summary.event_indices);
    return summary;
}

} // namespace scram::core::product_filter

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