Skip to documentation
SLOP

tiny.pluck.weight_dd

Reference tiny.pluck weight_dd

Defined in tiny.pluck.

API (32)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callstiny.pluckweight dd
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallstest sourcelib.pluck.src.weighttest: WeightDD mul/add match brute fo...private sourcelib.pluck.src.weight.WeightDDaddWithCacheweight_dd.WeightDDadd
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callersprivate sourcelib.pluck.src.weight.WeightDDaddWithCacheweight_dd.WeightDDnodeCountLimitedweight_dd.WeightDDaddLimited
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallstest sourcelib.pluck.src.weighttest: WeightDD canonicalizes leaves a...test sourcelib.pluck.src.weighttest: WeightDD ite matches brute forc...test sourcelib.pluck.src.weighttest: WeightDD mul/add match brute fo...test sourcelib.pluck.src.weighttest: WeightDD nodeCount reports reac...test sourcelib.pluck.src.weighttest: wmcWeightedWithCache matches pe...+2 moreprivate sourcelib.pluck.src.weight.WeightDDgetOrInsertBranchweight_dd.WeightDDbranch
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.pluck.src.profiling.internal.factortest: BENCHMARK: WeightDD refinement ...weight_dd.WeightDDrefineWeighttest sourcelib.pluck.src.weighttest: WeightDD buildFromGuardList and...test sourcelib.pluck.src.weighttest: WeightDD rejects NaN weightsprivate sourcelib.pluck.src.weight.WeightDDbuildFromGuardListInnerprivate sourcelib.pluck.src.weightnormalizeWeightweight_dd.WeightDDbuildFromGuardList
Static calls · unresolved targets: 3 · external targets: 1.
Called byCallsNo direct callstest sourcelib.pluck.src.profiling.internal.factortest: BENCHMARK: WeightDD refinement ...test sourcelib.pluck.src.weighttest: WeightDD buildFromGuardList and...test sourcelib.pluck.src.weighttest: WeightDD canonicalizes leaves a...test sourcelib.pluck.src.weighttest: WeightDD ite matches brute forc...test sourcelib.pluck.src.weighttest: WeightDD mul/add match brute fo...+8 moreweight_dd.WeightDDdeinit
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callsprivate sourcelib.pluck.src.weight.WeightDDapplyBinaryweight_dd.WeightDDisUnknownprivate sourcelib.pluck.src.weight.WeightDDiteWithCacheprivate sourcelib.pluck.src.weight.WeightDDnodeCountHelperweight_dd.WeightDDgetNode
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.pluck.src.profiling.internal.factortest: BENCHMARK: WeightDD refinement ...stateinitCheckedtest sourcelib.pluck.src.weighttest: WeightDD buildFromGuardList and...test sourcelib.pluck.src.weighttest: WeightDD canonicalizes leaves a...test sourcelib.pluck.src.weighttest: WeightDD ite matches brute forc...+9 moreweight_dd.WeightDDinit
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallstest sourcelib.pluck.src.profiling.internal.factortest: BENCHMARK: WeightDD refinement ...test sourcelib.pluck.src.weighttest: WeightDD refineWeight respects ...weight_dd.WeightDDgetNodeweight_dd.WeightDDisUnknown
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.pluck.src.weighttest: WeightDD ite matches brute forc...private sourcelib.pluck.src.weight.WeightDDiteWithCacheweight_dd.WeightDDite
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callersprivate sourcelib.pluck.src.weight.WeightDDiteWithCacheweight_dd.WeightDDnodeCountLimitedweight_dd.WeightDDiteLimited
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsstateinitCheckedprivate sourcelib.pluck.src.weight.WeightDDapplyBinaryprivate sourcelib.pluck.src.weight.WeightDDbuildFromGuardListInnertest sourcelib.pluck.src.weighttest: WeightDD canonicalizes leaves a...test sourcelib.pluck.src.weighttest: WeightDD ite matches brute forc...+7 moreprivate sourcelib.pluck.src.weightnormalizeWeightweight_dd.WeightDDleaf
Static calls · unresolved targets: 3 · external targets: 0.
Called byCallsNo direct callstest sourcelib.pluck.src.weighttest: WeightDD buildFromGuardList and...test sourcelib.pluck.src.weighttest: WeightDD ite matches brute forc...test sourcelib.pluck.src.weighttest: WeightDD mul/add match brute fo...weight_dd.WeightDDlookup
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.pluck.src.weighttest: WeightDD mul/add match brute fo...private sourcelib.pluck.src.weight.WeightDDmulWithCacheweight_dd.WeightDDmul
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callersprivate sourcelib.pluck.src.weight.WeightDDmulWithCacheweight_dd.WeightDDnodeCountLimitedweight_dd.WeightDDmulLimited
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallstest sourcelib.pluck.src.weighttest: WeightDD nodeCount reports reac...private sourcelib.pluck.src.weight.WeightDDnodeCountHelperweight_dd.WeightDDnodeCount
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsweight_dd.WeightDDaddLimitedweight_dd.WeightDDiteLimitedweight_dd.WeightDDmulLimitedtest sourcelib.pluck.src.weighttest: WeightDD nodeCount reports reac...private sourcelib.pluck.src.weight.WeightDDnodeCountHelperweight_dd.WeightDDnodeCountLimited
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallstest sourcelib.pluck.src.profiling.internal.factortest: BENCHMARK: WeightDD refinement ...test sourcelib.pluck.src.weighttest: WeightDD refineWeight respects ...weight_dd.WeightDDbuildFromGuardListprivate sourcelib.pluck.src.weight.WeightDDbuildFromGuardListInnerLimitedprivate sourcelib.pluck.src.weightnormalizeWeightweight_dd.WeightDDrefineWeight
Static calls · unresolved targets: 3 · external targets: 1.
Called byCallstest sourcelib.pluck.src.weighttest: wmcWeightedWithCache matches pe...test sourcelib.pluck.src.weighttest: wmcWeighted handles weight var ...test sourcelib.pluck.src.weighttest: wmcWeighted matches brute force...private sourcelib.pluck.src.weightwmcWeightedWithAllocatorweight_ddwmcWeighted
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.pluck.src.weighttest: wmcWeightedWithCache matches pe...private sourcelib.pluck.src.weightwmcWeightedWithAllocatorprivate sourcelib.pluck.src.weightwmcWeightedHelperweight_ddwmcWeightedWithCache
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/pluck/src/root.zig:27

zig
pub const weight_dd = @import("weight.zig");

Source: lib/pluck/src/weight.zig

zig
const std = @import("std");const bdd = @import("bdd.zig");const Allocator = std.mem.Allocator;pub const VarLabel = bdd.VarLabel;pub const Bdd = bdd.Bdd;pub const NodeIndex = u32;pub const Weight = struct {    index: NodeIndex,    pub fn eql(self: Weight, other: Weight) bool {        return self.index == other.index;    }};pub const Branch = struct {    var_label: VarLabel,    low: Weight,    high: Weight,};pub const GuardRange = struct {    start: u32,    len: u32,};pub const WeightNode = union(enum) {    leaf: f64,    branch: Branch,    unknown: GuardRange,};pub const GuardedWeight = struct {    guard: Bdd,    weight: f64,};const BranchKey = struct {    var_label: VarLabel,    low: NodeIndex,    high: NodeIndex,};const NodeKey = union(enum) {    leaf: u64,    branch: BranchKey,};const BuildBudget = struct {    remaining: usize,};const NodeLimitError = error{    NodeLimitExceeded,};const NodeKeyHashContext = struct {    const K: u64 = 0x517cc1b727220a95;    inline fn fxHashWord(h: u64, word: u64) u64 {        return (std.math.rotl(u64, h, 5) ^ word) *% K;    }    pub fn hash(self: @This(), key: NodeKey) u64 {        _ = self;        var h: u64 = 0;        switch (key) {            .leaf => |bits| {                h = fxHashWord(h, bits);            },            .branch => |branch_key| {                h = fxHashWord(h, @as(u64, branch_key.var_label));                h = fxHashWord(h, @as(u64, branch_key.low));                h = fxHashWord(h, @as(u64, branch_key.high));            },        }        return h;    }    pub fn eql(self: @This(), a: NodeKey, b: NodeKey) bool {        _ = self;        return switch (a) {            .leaf => |bits_a| switch (b) {                .leaf => |bits_b| bits_a == bits_b,                .branch => false,            },            .branch => |branch_a| switch (b) {                .leaf => false,                .branch => |branch_b| branch_a.var_label == branch_b.var_label and                    branch_a.low == branch_b.low and                    branch_a.high == branch_b.high,            },        };    }};pub const WeightError = error{    NaNWeight,    NonFiniteWeight,};pub const ApplyError = WeightError || NodeLimitError || Allocator.Error;pub const LookupError = error{    MissingAssignment,    UnknownWeight,};pub const WeightDD = struct {    allocator: Allocator,    bdd_manager: *bdd.Manager,    nodes: std.ArrayListUnmanaged(WeightNode),    unique_table: std.HashMapUnmanaged(NodeKey, NodeIndex, NodeKeyHashContext, 80),    guard_storage: std.ArrayListUnmanaged(GuardedWeight),    const DEFAULT_HASH_CAPACITY = 131072;    pub fn init(allocator: Allocator, bdd_manager: *bdd.Manager) !WeightDD {        var nodes = std.ArrayListUnmanaged(WeightNode).empty;        errdefer nodes.deinit(allocator);        var unique_table = std.HashMapUnmanaged(NodeKey, NodeIndex, NodeKeyHashContext, 80){};        try unique_table.ensureTotalCapacity(allocator, DEFAULT_HASH_CAPACITY);        var guard_storage = std.ArrayListUnmanaged(GuardedWeight).empty;        errdefer guard_storage.deinit(allocator);        var dd = WeightDD{            .allocator = allocator,            .bdd_manager = bdd_manager,            .nodes = nodes,            .unique_table = unique_table,            .guard_storage = guard_storage,        };        _ = try dd.leaf(0.0);        return dd;    }    pub fn deinit(self: *WeightDD) void {        self.nodes.deinit(self.allocator);        self.unique_table.deinit(self.allocator);        self.guard_storage.deinit(self.allocator);    }    pub fn nodeCount(self: *const WeightDD, root: Weight) usize {        var visited = std.AutoHashMap(NodeIndex, void).init(self.allocator);        defer visited.deinit();        return self.nodeCountHelper(root, &visited, null) catch unreachable;    }    pub fn nodeCountLimited(self: *const WeightDD, root: Weight, max_nodes: usize) ApplyError!usize {        var visited = std.AutoHashMap(NodeIndex, void).init(self.allocator);        defer visited.deinit();        return self.nodeCountHelper(root, &visited, max_nodes);    }    pub fn leaf(self: *WeightDD, value: f64) !Weight {        const normalized = try normalizeWeight(value);        const bits: u64 = @bitCast(normalized);        const key = NodeKey{ .leaf = bits };        if (self.unique_table.get(key)) |existing| {            return Weight{ .index = existing };        }        const idx: NodeIndex = @intCast(self.nodes.items.len);        try self.nodes.append(self.allocator, .{ .leaf = normalized });        try self.unique_table.put(self.allocator, key, idx);        return Weight{ .index = idx };    }    pub fn branch(self: *WeightDD, var_label: VarLabel, low: Weight, high: Weight) !Weight {        return self.getOrInsertBranch(var_label, low, high);    }    pub fn getNode(self: *const WeightDD, weight: Weight) WeightNode {        return self.nodes.items[weight.index];    }    pub fn isUnknown(self: *const WeightDD, weight: Weight) bool {        return switch (self.getNode(weight)) {            .unknown => true,            else => false,        };    }    pub fn lookup(self: *const WeightDD, root: Weight, assignment: []const bool) (LookupError)!f64 {        var current = root;        while (true) {            switch (self.nodes.items[current.index]) {                .leaf => |value| return value,                .branch => |branch_node| {                    const idx: usize = @intCast(branch_node.var_label);                    if (idx >= assignment.len) {                        return error.MissingAssignment;                    }                    current = if (assignment[idx]) branch_node.high else branch_node.low;                },                .unknown => return error.UnknownWeight,            }        }    }    pub fn buildFromGuardList(self: *WeightDD, guards: []const GuardedWeight) !Weight {        var normalized: std.ArrayList(GuardedWeight) = .empty;        defer normalized.deinit(self.allocator);        try normalized.ensureTotalCapacity(self.allocator, guards.len);        for (guards) |entry| {            if (entry.guard.isFalse()) continue;            const weight = try normalizeWeight(entry.weight);            if (weight == 0.0) continue;            try normalized.append(self.allocator, .{ .guard = entry.guard, .weight = weight });        }        return self.buildFromGuardListInner(normalized.items);    }    pub fn refineWeight(self: *WeightDD, guards: []const GuardedWeight, max_nodes: usize) ApplyError!Weight {        if (max_nodes == 0) {            return self.buildFromGuardList(guards);        }        var normalized: std.ArrayList(GuardedWeight) = .empty;        defer normalized.deinit(self.allocator);        try normalized.ensureTotalCapacity(self.allocator, guards.len);        for (guards) |entry| {            if (entry.guard.isFalse()) continue;            const weight = try normalizeWeight(entry.weight);            if (weight == 0.0) continue;            try normalized.append(self.allocator, .{ .guard = entry.guard, .weight = weight });        }        var budget = BuildBudget{ .remaining = max_nodes };        return self.buildFromGuardListInnerLimited(normalized.items, &budget);    }    fn buildFromGuardListInner(self: *WeightDD, guards: []const GuardedWeight) !Weight {        if (guards.len == 0) {            return self.leaf(0.0);        }        var all_const = true;        var total: f64 = 0.0;        for (guards) |entry| {            if (entry.guard.isTrue()) {                total += entry.weight;                continue;            }            if (entry.guard.isFalse()) continue;            all_const = false;        }        if (all_const) {            return self.leaf(total);        }        var top_var: VarLabel = undefined;        var has_top = false;        for (guards) |entry| {            if (entry.guard.isTrue() or entry.guard.isFalse()) continue;            const var_label = self.bdd_manager.topVar(entry.guard);            if (!has_top) {                top_var = var_label;                has_top = true;                continue;            }            if (self.bdd_manager.getVarPosition(var_label) <                self.bdd_manager.getVarPosition(top_var))            {                top_var = var_label;            }        }        if (!has_top) {            return self.leaf(total);        }        var low_list: std.ArrayList(GuardedWeight) = .empty;        defer low_list.deinit(self.allocator);        var high_list: std.ArrayList(GuardedWeight) = .empty;        defer high_list.deinit(self.allocator);        try low_list.ensureTotalCapacity(self.allocator, guards.len);        try high_list.ensureTotalCapacity(self.allocator, guards.len);        for (guards) |entry| {            const guard = entry.guard;            if (guard.isFalse()) continue;            if (guard.isTrue()) {                try low_list.append(self.allocator, entry);                try high_list.append(self.allocator, entry);                continue;            }            const low_guard = try self.bdd_manager.condition(guard, top_var, false);            const high_guard = try self.bdd_manager.condition(guard, top_var, true);            if (!low_guard.isFalse()) {                try low_list.append(self.allocator, .{ .guard = low_guard, .weight = entry.weight });            }            if (!high_guard.isFalse()) {                try high_list.append(self.allocator, .{ .guard = high_guard, .weight = entry.weight });            }        }        const low_node = try self.buildFromGuardListInner(low_list.items);        const high_node = try self.buildFromGuardListInner(high_list.items);        return self.getOrInsertBranch(top_var, low_node, high_node);    }    fn buildFromGuardListInnerLimited(        self: *WeightDD,        guards: []const GuardedWeight,        budget: *BuildBudget,    ) ApplyError!Weight {        if (guards.len == 0) {            return self.leafLimited(0.0, budget, guards);        }        var all_const = true;        var total: f64 = 0.0;        for (guards) |entry| {            if (entry.guard.isTrue()) {                total += entry.weight;                continue;            }            if (entry.guard.isFalse()) continue;            all_const = false;        }        if (all_const) {            return self.leafLimited(total, budget, guards);        }        if (budget.remaining == 0) {            return self.unknownFromGuards(guards);        }        var top_var: VarLabel = undefined;        var has_top = false;        for (guards) |entry| {            if (entry.guard.isTrue() or entry.guard.isFalse()) continue;            const var_label = self.bdd_manager.topVar(entry.guard);            if (!has_top) {                top_var = var_label;                has_top = true;                continue;            }            if (self.bdd_manager.getVarPosition(var_label) <                self.bdd_manager.getVarPosition(top_var))            {                top_var = var_label;            }        }        if (!has_top) {            return self.leafLimited(total, budget, guards);        }        var low_list: std.ArrayList(GuardedWeight) = .empty;        defer low_list.deinit(self.allocator);        var high_list: std.ArrayList(GuardedWeight) = .empty;        defer high_list.deinit(self.allocator);        try low_list.ensureTotalCapacity(self.allocator, guards.len);        try high_list.ensureTotalCapacity(self.allocator, guards.len);        for (guards) |entry| {            const guard = entry.guard;            if (guard.isFalse()) continue;            if (guard.isTrue()) {                try low_list.append(self.allocator, entry);                try high_list.append(self.allocator, entry);                continue;            }            const low_guard = try self.bdd_manager.condition(guard, top_var, false);            const high_guard = try self.bdd_manager.condition(guard, top_var, true);            if (!low_guard.isFalse()) {                try low_list.append(self.allocator, .{ .guard = low_guard, .weight = entry.weight });            }            if (!high_guard.isFalse()) {                try high_list.append(self.allocator, .{ .guard = high_guard, .weight = entry.weight });            }        }        const low_node = try self.buildFromGuardListInnerLimited(low_list.items, budget);        const high_node = try self.buildFromGuardListInnerLimited(high_list.items, budget);        return self.branchLimited(top_var, low_node, high_node, budget, guards);    }    fn storeGuards(self: *WeightDD, guards: []const GuardedWeight) Allocator.Error!GuardRange {        const start: u32 = @intCast(self.guard_storage.items.len);        try self.guard_storage.appendSlice(self.allocator, guards);        return GuardRange{ .start = start, .len = @intCast(guards.len) };    }    fn unknownFromGuards(self: *WeightDD, guards: []const GuardedWeight) ApplyError!Weight {        const range = try self.storeGuards(guards);        const idx: NodeIndex = @intCast(self.nodes.items.len);        try self.nodes.append(self.allocator, .{ .unknown = range });        return Weight{ .index = idx };    }    fn leafLimited(self: *WeightDD, value: f64, budget: *BuildBudget, guards: []const GuardedWeight) ApplyError!Weight {        const normalized = try normalizeWeight(value);        const bits: u64 = @bitCast(normalized);        const key = NodeKey{ .leaf = bits };        if (self.unique_table.get(key)) |existing| {            return Weight{ .index = existing };        }        if (budget.remaining == 0) {            return self.unknownFromGuards(guards);        }        budget.remaining -= 1;        const idx: NodeIndex = @intCast(self.nodes.items.len);        try self.nodes.append(self.allocator, .{ .leaf = normalized });        try self.unique_table.put(self.allocator, key, idx);        return Weight{ .index = idx };    }    fn branchLimited(        self: *WeightDD,        var_label: VarLabel,        low: Weight,        high: Weight,        budget: *BuildBudget,        guards: []const GuardedWeight,    ) ApplyError!Weight {        if (low.index == high.index) return low;        const key = NodeKey{ .branch = .{            .var_label = var_label,            .low = low.index,            .high = high.index,        } };        if (self.unique_table.get(key)) |existing| {            return Weight{ .index = existing };        }        if (budget.remaining == 0) {            return self.unknownFromGuards(guards);        }        budget.remaining -= 1;        const idx: NodeIndex = @intCast(self.nodes.items.len);        try self.nodes.append(self.allocator, .{ .branch = .{            .var_label = var_label,            .low = low,            .high = high,        } });        try self.unique_table.put(self.allocator, key, idx);        return Weight{ .index = idx };    }    pub fn mul(self: *WeightDD, a: Weight, b: Weight) ApplyError!Weight {        var cache = std.AutoHashMap(u64, Weight).init(self.allocator);        defer cache.deinit();        return self.mulWithCache(a, b, &cache);    }    pub fn mulLimited(self: *WeightDD, a: Weight, b: Weight, max_nodes: usize) ApplyError!Weight {        var cache = std.AutoHashMap(u64, Weight).init(self.allocator);        defer cache.deinit();        const result = try self.mulWithCache(a, b, &cache);        _ = try self.nodeCountLimited(result, max_nodes);        return result;    }    pub fn add(self: *WeightDD, a: Weight, b: Weight) ApplyError!Weight {        var cache = std.AutoHashMap(u64, Weight).init(self.allocator);        defer cache.deinit();        return self.addWithCache(a, b, &cache);    }    pub fn addLimited(self: *WeightDD, a: Weight, b: Weight, max_nodes: usize) ApplyError!Weight {        var cache = std.AutoHashMap(u64, Weight).init(self.allocator);        defer cache.deinit();        const result = try self.addWithCache(a, b, &cache);        _ = try self.nodeCountLimited(result, max_nodes);        return result;    }    pub fn ite(self: *WeightDD, guard: Bdd, then_dd: Weight, else_dd: Weight) ApplyError!Weight {        var cache = std.AutoHashMap(IteCacheKey, Weight).init(self.allocator);        defer cache.deinit();        return self.iteWithCache(guard, then_dd, else_dd, &cache);    }    pub fn iteLimited(self: *WeightDD, guard: Bdd, then_dd: Weight, else_dd: Weight, max_nodes: usize) ApplyError!Weight {        var cache = std.AutoHashMap(IteCacheKey, Weight).init(self.allocator);        defer cache.deinit();        const result = try self.iteWithCache(guard, then_dd, else_dd, &cache);        _ = try self.nodeCountLimited(result, max_nodes);        return result;    }    fn getOrInsertBranch(self: *WeightDD, var_label: VarLabel, low: Weight, high: Weight) !Weight {        if (low.index == high.index) return low;        const key = NodeKey{ .branch = .{            .var_label = var_label,            .low = low.index,            .high = high.index,        } };        if (self.unique_table.get(key)) |existing| {            return Weight{ .index = existing };        }        const idx: NodeIndex = @intCast(self.nodes.items.len);        try self.nodes.append(self.allocator, .{ .branch = .{            .var_label = var_label,            .low = low,            .high = high,        } });        try self.unique_table.put(self.allocator, key, idx);        return Weight{ .index = idx };    }    fn nodeCountHelper(        self: *const WeightDD,        root: Weight,        visited: *std.AutoHashMap(NodeIndex, void),        max_nodes: ?usize,    ) ApplyError!usize {        if (visited.contains(root.index)) return 0;        try visited.put(root.index, {});        if (max_nodes) |limit| {            if (visited.count() > limit) return error.NodeLimitExceeded;        }        var total: usize = 1;        switch (self.getNode(root)) {            .leaf => {},            .branch => |branch_node| {                total += try self.nodeCountHelper(branch_node.low, visited, max_nodes);                total += try self.nodeCountHelper(branch_node.high, visited, max_nodes);            },            .unknown => {                if (max_nodes != null) return error.NodeLimitExceeded;            },        }        return total;    }    fn mulWithCache(self: *WeightDD, a: Weight, b: Weight, cache: *std.AutoHashMap(u64, Weight)) ApplyError!Weight {        return self.applyBinary(.mul, a, b, cache);    }    fn addWithCache(self: *WeightDD, a: Weight, b: Weight, cache: *std.AutoHashMap(u64, Weight)) ApplyError!Weight {        return self.applyBinary(.add, a, b, cache);    }    fn applyBinary(        self: *WeightDD,        op: ApplyOp,        a_in: Weight,        b_in: Weight,        cache: *std.AutoHashMap(u64, Weight),    ) ApplyError!Weight {        var a = a_in;        var b = b_in;        if (a.index > b.index) {            a = b_in;            b = a_in;        }        const key: u64 = (@as(u64, a.index) << 32) | @as(u64, b.index);        if (cache.get(key)) |cached| return cached;        const a_node = self.getNode(a);        const b_node = self.getNode(b);        if (a_node == .unknown or b_node == .unknown) {            return error.NodeLimitExceeded;        }        if (op == .mul) {            if (a_node == .leaf) {                if (a_node.leaf == 0.0) return a;                if (a_node.leaf == 1.0) return b;            }            if (b_node == .leaf) {                if (b_node.leaf == 0.0) return b;                if (b_node.leaf == 1.0) return a;            }        } else {            if (a_node == .leaf and a_node.leaf == 0.0) return b;            if (b_node == .leaf and b_node.leaf == 0.0) return a;        }        if (a_node == .leaf and b_node == .leaf) {            const result_value = switch (op) {                .mul => a_node.leaf * b_node.leaf,                .add => a_node.leaf + b_node.leaf,            };            const result = try self.leaf(result_value);            cache.put(key, result) catch {};            return result;        }        const a_var_opt: ?VarLabel = switch (a_node) {            .leaf => null,            .branch => |branch_node| branch_node.var_label,            .unknown => null,        };        const b_var_opt: ?VarLabel = switch (b_node) {            .leaf => null,            .branch => |branch_node| branch_node.var_label,            .unknown => null,        };        const next_var = self.pickNextVar(a_var_opt, b_var_opt);        var a_low = a;        var a_high = a;        if (a_var_opt != null and a_var_opt.? == next_var) {            const branch_node = switch (a_node) {                .branch => |branch_node| branch_node,                .leaf => unreachable,                .unknown => unreachable,            };            a_low = branch_node.low;            a_high = branch_node.high;        }        var b_low = b;        var b_high = b;        if (b_var_opt != null and b_var_opt.? == next_var) {            const branch_node = switch (b_node) {                .branch => |branch_node| branch_node,                .leaf => unreachable,                .unknown => unreachable,            };            b_low = branch_node.low;            b_high = branch_node.high;        }        const low = try self.applyBinary(op, a_low, b_low, cache);        const high = try self.applyBinary(op, a_high, b_high, cache);        const result = try self.getOrInsertBranch(next_var, low, high);        cache.put(key, result) catch {};        return result;    }    fn iteWithCache(        self: *WeightDD,        guard: Bdd,        then_dd: Weight,        else_dd: Weight,        cache: *std.AutoHashMap(IteCacheKey, Weight),    ) ApplyError!Weight {        if (guard.isTrue()) return then_dd;        if (guard.isFalse()) return else_dd;        if (then_dd.index == else_dd.index) return then_dd;        const key = IteCacheKey{            .guard_raw = guard.toRaw(),            .then_index = then_dd.index,            .else_index = else_dd.index,        };        if (cache.get(key)) |cached| return cached;        const guard_var_opt: ?VarLabel = if (guard.isConst()) null else self.bdd_manager.getNode(guard).var_label;        const then_node = self.getNode(then_dd);        const else_node = self.getNode(else_dd);        if (then_node == .unknown or else_node == .unknown) {            return error.NodeLimitExceeded;        }        const then_var_opt: ?VarLabel = switch (then_node) {            .leaf => null,            .branch => |branch_node| branch_node.var_label,            .unknown => null,        };        const else_var_opt: ?VarLabel = switch (else_node) {            .leaf => null,            .branch => |branch_node| branch_node.var_label,            .unknown => null,        };        const next_var = self.pickNextVar3(guard_var_opt, then_var_opt, else_var_opt);        var guard_low = guard;        var guard_high = guard;        if (guard_var_opt != null and guard_var_opt.? == next_var) {            const node = self.bdd_manager.getNode(guard);            guard_low = if (guard.complement) node.low.neg() else node.low;            guard_high = if (guard.complement) node.high.neg() else node.high;        }        var then_low = then_dd;        var then_high = then_dd;        if (then_var_opt != null and then_var_opt.? == next_var) {            const branch_node = switch (then_node) {                .branch => |branch_node| branch_node,                .leaf => unreachable,                .unknown => unreachable,            };            then_low = branch_node.low;            then_high = branch_node.high;        }        var else_low = else_dd;        var else_high = else_dd;        if (else_var_opt != null and else_var_opt.? == next_var) {            const branch_node = switch (else_node) {                .branch => |branch_node| branch_node,                .leaf => unreachable,                .unknown => unreachable,            };            else_low = branch_node.low;            else_high = branch_node.high;        }        const low = try self.iteWithCache(guard_low, then_low, else_low, cache);        const high = try self.iteWithCache(guard_high, then_high, else_high, cache);        const result = try self.getOrInsertBranch(next_var, low, high);        cache.put(key, result) catch {};        return result;    }    fn pickNextVar(self: *const WeightDD, a: ?VarLabel, b: ?VarLabel) VarLabel {        if (a == null) return b.?;        if (b == null) return a.?;        if (self.bdd_manager.getVarPosition(a.?) <= self.bdd_manager.getVarPosition(b.?)) {            return a.?;        }        return b.?;    }    fn pickNextVar3(self: *const WeightDD, a: ?VarLabel, b: ?VarLabel, c: ?VarLabel) VarLabel {        var choice: ?VarLabel = null;        const vars = [_]?VarLabel{ a, b, c };        for (vars) |var_opt| {            if (var_opt == null) continue;            if (choice == null) {                choice = var_opt;                continue;            }            if (self.bdd_manager.getVarPosition(var_opt.?) <= self.bdd_manager.getVarPosition(choice.?)) {                choice = var_opt;            }        }        return choice.?;    }};const ApplyOp = enum {    mul,    add,};const IteCacheKey = struct {    guard_raw: u32,    then_index: NodeIndex,    else_index: NodeIndex,};fn normalizeWeight(value: f64) WeightError!f64 {    if (std.math.isNan(value)) return error.NaNWeight;    if (!std.math.isFinite(value)) return error.NonFiniteWeight;    var normalized = value;    if (normalized == 0.0) {        normalized = 0.0;    }    return normalized;}pub fn wmcWeighted(    dd: *const WeightDD,    root_bdd: Bdd,    root_weight: Weight,    params: *const bdd.WmcParams,) f64 {    return wmcWeightedWithAllocator(dd, root_bdd, root_weight, params, dd.allocator);}fn wmcWeightedWithAllocator(    dd: *const WeightDD,    root_bdd: Bdd,    root_weight: Weight,    params: *const bdd.WmcParams,    allocator: Allocator,) f64 {    var cache = std.AutoHashMap(u64, f64).init(allocator);    defer cache.deinit();    return wmcWeightedWithCache(dd, root_bdd, root_weight, params, &cache);}pub fn wmcWeightedWithCache(    dd: *const WeightDD,    root_bdd: Bdd,    root_weight: Weight,    params: *const bdd.WmcParams,    cache: *std.AutoHashMap(u64, f64),) f64 {    return wmcWeightedHelper(dd, root_bdd, root_weight, params, cache);}fn wmcWeightedHelper(    dd: *const WeightDD,    root_bdd: Bdd,    root_weight: Weight,    params: *const bdd.WmcParams,    cache: *std.AutoHashMap(u64, f64),) f64 {    if (root_bdd.isFalse()) return 0.0;    switch (dd.getNode(root_weight)) {        .leaf => |value| {            if (value == 0.0) return 0.0;            if (root_bdd.isTrue()) return value;        },        .branch => {},        .unknown => {            std.debug.assert(false);            return 0.0;        },    }    const cache_key: u64 = (@as(u64, root_bdd.toRaw()) << 32) | @as(u64, root_weight.index);    if (cache.get(cache_key)) |cached| {        return cached;    }    const bdd_var_opt: ?VarLabel = if (root_bdd.isConst()) null else dd.bdd_manager.getNode(root_bdd).var_label;    const weight_var_opt: ?VarLabel = switch (dd.getNode(root_weight)) {        .leaf => null,        .branch => |branch_node| branch_node.var_label,        .unknown => null,    };    const next_var: VarLabel = if (bdd_var_opt == null and weight_var_opt != null)        weight_var_opt.?    else if (weight_var_opt == null and bdd_var_opt != null)        bdd_var_opt.?    else if (bdd_var_opt != null and weight_var_opt != null)        if (dd.bdd_manager.getVarPosition(bdd_var_opt.?) <= dd.bdd_manager.getVarPosition(weight_var_opt.?))            bdd_var_opt.?        else            weight_var_opt.?    else        return 0.0;    var bdd_low = root_bdd;    var bdd_high = root_bdd;    if (bdd_var_opt != null and bdd_var_opt.? == next_var) {        const node = dd.bdd_manager.getNode(root_bdd);        bdd_low = if (root_bdd.complement) node.low.neg() else node.low;        bdd_high = if (root_bdd.complement) node.high.neg() else node.high;    }    var weight_low = root_weight;    var weight_high = root_weight;    if (weight_var_opt != null and weight_var_opt.? == next_var) {        const node = dd.getNode(root_weight);        const branch = node.branch;        weight_low = branch.low;        weight_high = branch.high;    }    const var_weight = params.getWeight(next_var);    const low_result = wmcWeightedHelper(dd, bdd_low, weight_low, params, cache);    const high_result = wmcWeightedHelper(dd, bdd_high, weight_high, params, cache);    const result = var_weight.low * low_result + var_weight.high * high_result;    cache.put(cache_key, result) catch {};    return result;}fn evalBdd(manager: *const bdd.Manager, root: Bdd, assignment: []const bool) bool {    var current = root;    while (true) {        if (current.isTrue()) return true;        if (current.isFalse()) return false;        const node = manager.getNode(current);        const take_high = assignment[@intCast(node.var_label)];        const next = if (take_high) node.high else node.low;        current = if (current.complement) next.neg() else next;    }}fn bruteForceWmcWeighted(    allocator: Allocator,    manager: *const bdd.Manager,    root_bdd: Bdd,    dd: *const WeightDD,    root_weight: Weight,    params: *const bdd.WmcParams,    vars: []const VarLabel,) !f64 {    var assignment = try allocator.alloc(bool, manager.numVars());    defer allocator.free(assignment);    @memset(assignment, false);    var total: usize = 1;    for (0..vars.len) |_| {        total *= 2;    }    var sum: f64 = 0.0;    for (0..total) |mask| {        for (vars, 0..) |var_label, bit| {            assignment[@intCast(var_label)] = ((mask >> @intCast(bit)) & 1) == 1;        }        if (!evalBdd(manager, root_bdd, assignment)) continue;        const weight_value = try dd.lookup(root_weight, assignment);        var prob: f64 = 1.0;        for (vars) |var_label| {            const var_weight = params.getWeight(var_label);            prob *= if (assignment[@intCast(var_label)]) var_weight.high else var_weight.low;        }        sum += prob * weight_value;    }    return sum;}test "WeightDD canonicalizes leaves and branches" {    var manager = try bdd.Manager.init(std.testing.allocator);    defer manager.deinit();    var dd = try WeightDD.init(std.testing.allocator, &manager);    defer dd.deinit();    const leaf_zero = try dd.leaf(0.0);    const leaf_neg_zero = try dd.leaf(-0.0);    try std.testing.expectEqual(leaf_zero.index, leaf_neg_zero.index);    const leaf_a = try dd.leaf(1.5);    const leaf_b = try dd.leaf(1.5);    try std.testing.expectEqual(leaf_a.index, leaf_b.index);    const branch_reduced = try dd.branch(0, leaf_a, leaf_a);    try std.testing.expectEqual(leaf_a.index, branch_reduced.index);    const branch1 = try dd.branch(0, leaf_zero, leaf_a);    const branch2 = try dd.branch(0, leaf_zero, leaf_a);    try std.testing.expectEqual(branch1.index, branch2.index);}test "WeightDD buildFromGuardList and lookup" {    var manager = try bdd.Manager.init(std.testing.allocator);    defer manager.deinit();    const x = try manager.newVar(true);    var dd = try WeightDD.init(std.testing.allocator, &manager);    defer dd.deinit();    const guards = [_]GuardedWeight{        .{ .guard = x, .weight = 2.0 },        .{ .guard = x.neg(), .weight = 3.5 },    };    const root = try dd.buildFromGuardList(&guards);    try std.testing.expectEqual(@as(f64, 2.0), try dd.lookup(root, &[_]bool{true}));    try std.testing.expectEqual(@as(f64, 3.5), try dd.lookup(root, &[_]bool{false}));}test "WeightDD refineWeight respects budget" {    var manager = try bdd.Manager.init(std.testing.allocator);    defer manager.deinit();    const x = try manager.newVar(true);    var dd = try WeightDD.init(std.testing.allocator, &manager);    defer dd.deinit();    const guards = [_]GuardedWeight{        .{ .guard = x, .weight = 0.2 },        .{ .guard = manager.bddNot(x), .weight = 0.8 },    };    const root = try dd.refineWeight(&guards, 1);    try std.testing.expect(dd.isUnknown(root));}test "WeightDD rejects NaN weights" {    var manager = try bdd.Manager.init(std.testing.allocator);    defer manager.deinit();    var dd = try WeightDD.init(std.testing.allocator, &manager);    defer dd.deinit();    const nan_weight = std.math.nan(f64);    const guards = [_]GuardedWeight{        .{ .guard = Bdd.TRUE, .weight = nan_weight },    };    try std.testing.expectError(error.NaNWeight, dd.buildFromGuardList(&guards));}test "wmcWeighted matches brute force on guarded weight" {    var manager = try bdd.Manager.init(std.testing.allocator);    defer manager.deinit();    const x = try manager.newVar(true);    const y = try manager.newVar(true);    const formula = try manager.bddAnd(x, y);    var dd = try WeightDD.init(std.testing.allocator, &manager);    defer dd.deinit();    const leaf_lo = try dd.leaf(3.0);    const leaf_hi = try dd.leaf(2.0);    const weight_root = try dd.branch(manager.topVar(x), leaf_lo, leaf_hi);    var params = bdd.WmcParams.init(std.testing.allocator);    defer params.deinit();    try params.setWeight(0, 0.4, 0.6);    try params.setWeight(1, 0.2, 0.8);    const vars = [_]VarLabel{ manager.topVar(x), manager.topVar(y) };    const expected = try bruteForceWmcWeighted(std.testing.allocator, &manager, formula, &dd, weight_root, &params, &vars);    const actual = wmcWeighted(&dd, formula, weight_root, &params);    try std.testing.expectApproxEqAbs(expected, actual, 1e-12);}test "wmcWeightedWithCache matches per-call WMC" {    var manager = try bdd.Manager.init(std.testing.allocator);    defer manager.deinit();    const x = try manager.newVar(true);    const y = try manager.newVar(true);    const formula = try manager.bddOr(x, y);    var dd = try WeightDD.init(std.testing.allocator, &manager);    defer dd.deinit();    const leaf_lo = try dd.leaf(0.25);    const leaf_hi = try dd.leaf(1.75);    const weight_root = try dd.branch(manager.topVar(x), leaf_lo, leaf_hi);    var params = bdd.WmcParams.init(std.testing.allocator);    defer params.deinit();    try params.setWeight(0, 0.3, 0.7);    try params.setWeight(1, 0.6, 0.4);    var cache = std.AutoHashMap(u64, f64).init(std.testing.allocator);    defer cache.deinit();    const expected_formula = wmcWeighted(&dd, formula, weight_root, &params);    const actual_formula = wmcWeightedWithCache(&dd, formula, weight_root, &params, &cache);    const expected_x = wmcWeighted(&dd, x, weight_root, &params);    const actual_x = wmcWeightedWithCache(&dd, x, weight_root, &params, &cache);    try std.testing.expect(cache.count() > 0);    try std.testing.expectApproxEqAbs(expected_formula, actual_formula, 1e-12);    try std.testing.expectApproxEqAbs(expected_x, actual_x, 1e-12);}test "wmcWeighted handles weight var outside BDD" {    var manager = try bdd.Manager.init(std.testing.allocator);    defer manager.deinit();    const x = try manager.newVar(true);    const y = try manager.newVar(true);    const formula = y;    var dd = try WeightDD.init(std.testing.allocator, &manager);    defer dd.deinit();    const leaf_lo = try dd.leaf(1.0);    const leaf_hi = try dd.leaf(5.0);    const weight_root = try dd.branch(manager.topVar(x), leaf_lo, leaf_hi);    var params = bdd.WmcParams.init(std.testing.allocator);    defer params.deinit();    try params.setWeight(0, 0.4, 0.6);    try params.setWeight(1, 0.3, 0.7);    const vars = [_]VarLabel{ manager.topVar(x), manager.topVar(y) };    const expected = try bruteForceWmcWeighted(std.testing.allocator, &manager, formula, &dd, weight_root, &params, &vars);    const actual = wmcWeighted(&dd, formula, weight_root, &params);    try std.testing.expectApproxEqAbs(expected, actual, 1e-12);}test "WeightDD mul/add match brute force enumeration" {    var manager = try bdd.Manager.init(std.testing.allocator);    defer manager.deinit();    const x = try manager.newVar(true);    const y = try manager.newVar(true);    var dd = try WeightDD.init(std.testing.allocator, &manager);    defer dd.deinit();    const leaf_x0 = try dd.leaf(2.0);    const leaf_x1 = try dd.leaf(3.5);    const leaf_y0 = try dd.leaf(-1.0);    const leaf_y1 = try dd.leaf(4.25);    const dd_x = try dd.branch(manager.topVar(x), leaf_x0, leaf_x1);    const dd_y = try dd.branch(manager.topVar(y), leaf_y0, leaf_y1);    const mul_root = try dd.mul(dd_x, dd_y);    const add_root = try dd.add(dd_x, dd_y);    var assignment = try std.testing.allocator.alloc(bool, manager.numVars());    defer std.testing.allocator.free(assignment);    @memset(assignment, false);    const vars = [_]VarLabel{ manager.topVar(x), manager.topVar(y) };    var total: usize = 1;    for (vars) |_| total *= 2;    for (0..total) |mask| {        for (vars, 0..) |var_label, bit| {            assignment[@intCast(var_label)] = ((mask >> @intCast(bit)) & 1) == 1;        }        const x_val = try dd.lookup(dd_x, assignment);        const y_val = try dd.lookup(dd_y, assignment);        const expected_mul = x_val * y_val;        const expected_add = x_val + y_val;        const actual_mul = try dd.lookup(mul_root, assignment);        const actual_add = try dd.lookup(add_root, assignment);        try std.testing.expectApproxEqAbs(expected_mul, actual_mul, 1e-12);        try std.testing.expectApproxEqAbs(expected_add, actual_add, 1e-12);    }}test "WeightDD ite matches brute force enumeration" {    var manager = try bdd.Manager.init(std.testing.allocator);    defer manager.deinit();    const x = try manager.newVar(true);    const y = try manager.newVar(true);    var dd = try WeightDD.init(std.testing.allocator, &manager);    defer dd.deinit();    const leaf_then0 = try dd.leaf(1.25);    const leaf_then1 = try dd.leaf(2.75);    const leaf_else0 = try dd.leaf(-3.0);    const leaf_else1 = try dd.leaf(0.5);    const then_dd = try dd.branch(manager.topVar(x), leaf_then0, leaf_then1);    const else_dd = try dd.branch(manager.topVar(x), leaf_else0, leaf_else1);    const guard = y.neg();    const ite_root = try dd.ite(guard, then_dd, else_dd);    var assignment = try std.testing.allocator.alloc(bool, manager.numVars());    defer std.testing.allocator.free(assignment);    @memset(assignment, false);    const vars = [_]VarLabel{ manager.topVar(x), manager.topVar(y) };    var total: usize = 1;    for (vars) |_| total *= 2;    for (0..total) |mask| {        for (vars, 0..) |var_label, bit| {            assignment[@intCast(var_label)] = ((mask >> @intCast(bit)) & 1) == 1;        }        const expected = if (evalBdd(&manager, guard, assignment))            try dd.lookup(then_dd, assignment)        else            try dd.lookup(else_dd, assignment);        const actual = try dd.lookup(ite_root, assignment);        try std.testing.expectApproxEqAbs(expected, actual, 1e-12);    }}test "WeightDD nodeCount reports reachable nodes" {    var manager = try bdd.Manager.init(std.testing.allocator);    defer manager.deinit();    const x = try manager.newVar(true);    var dd = try WeightDD.init(std.testing.allocator, &manager);    defer dd.deinit();    const leaf_lo = try dd.leaf(2.0);    const leaf_hi = try dd.leaf(5.0);    const root = try dd.branch(manager.topVar(x), leaf_lo, leaf_hi);    try std.testing.expectEqual(@as(usize, 3), dd.nodeCount(root));    try std.testing.expectError(error.NodeLimitExceeded, dd.nodeCountLimited(root, 2));}

Complete caller list for weight_dd.WeightDD.branch

7 direct callers.

Complete caller list for weight_dd.WeightDD.deinit

13 direct callers.

Complete caller list for weight_dd.WeightDD.init

14 direct callers.

Complete caller list for weight_dd.WeightDD.leaf

12 direct callers.

Audit

Definitions31
Public names31
Members20
Version26.7.0
Revisiondaab053ee433