tiny.pluck.weight_dd
Defined in tiny.pluck.
API (32)
Actions
Public operations.
Weight.eqlWeightDD.addWeightDD.addLimitedWeightDD.branchWeightDD.buildFromGuardListWeightDD.deinitWeightDD.getNodeWeightDD.initWeightDD.isUnknownWeightDD.iteWeightDD.iteLimitedWeightDD.leafWeightDD.lookupWeightDD.mulWeightDD.mulLimitedWeightDD.nodeCountWeightDD.nodeCountLimitedWeightDD.refineWeightwmcWeightedwmcWeightedWithCache
Types and contracts
Public types and contracts.
ApplyErrorBddBranchGuardRangeGuardedWeightLookupErrorNodeIndexVarLabelWeightWeightDDWeightErrorWeightNode
Source
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, ¶ms, &vars); const actual = wmcWeighted(&dd, formula, weight_root, ¶ms); 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, ¶ms); const actual_formula = wmcWeightedWithCache(&dd, formula, weight_root, ¶ms, &cache); const expected_x = wmcWeighted(&dd, x, weight_root, ¶ms); const actual_x = wmcWeightedWithCache(&dd, x, weight_root, ¶ms, &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, ¶ms, &vars); const actual = wmcWeighted(&dd, formula, weight_root, ¶ms); 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.
lib.pluck.src.weight.test_WeightDD_canonicalizes_leaves_and_branches[function] — test source atlib/pluck/src/weight.zig:935in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_ite_matches_brute_force_enumeration[function] — test source atlib/pluck/src/weight.zig:1144in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_mul/add_match_brute_force_enumeration[function] — test source atlib/pluck/src/weight.zig:1097in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_nodeCount_reports_reachable_nodes[function] — test source atlib/pluck/src/weight.zig:1188in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_wmcWeightedWithCache_matches_per-call_WMC[function] — test source atlib/pluck/src/weight.zig:1037in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_wmcWeighted_handles_weight_var_outside_BDD[function] — test source atlib/pluck/src/weight.zig:1070in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_wmcWeighted_matches_brute_force_on_guarded_weight[function] — test source atlib/pluck/src/weight.zig:1010in nearest public ownertiny.pluck.weight_dd
Complete caller list for weight_dd.WeightDD.deinit
13 direct callers.
lib.pluck.src.profiling.internal.factor.test_BENCHMARK:_WeightDD_refinement_budget[function] — test source atlib/pluck/src/profiling/internal/factor.zig:697in nearest public ownerlib.pluck.src.profiling.internal.factorlib.pluck.src.weight.test_WeightDD_buildFromGuardList_and_lookup[function] — test source atlib/pluck/src/weight.zig:958in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_canonicalizes_leaves_and_branches[function] — test source atlib/pluck/src/weight.zig:935in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_ite_matches_brute_force_enumeration[function] — test source atlib/pluck/src/weight.zig:1144in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_mul/add_match_brute_force_enumeration[function] — test source atlib/pluck/src/weight.zig:1097in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_nodeCount_reports_reachable_nodes[function] — test source atlib/pluck/src/weight.zig:1188in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_refineWeight_respects_budget[function] — test source atlib/pluck/src/weight.zig:977in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_rejects_NaN_weights[function] — test source atlib/pluck/src/weight.zig:995in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_wmcWeightedWithCache_matches_per-call_WMC[function] — test source atlib/pluck/src/weight.zig:1037in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_wmcWeighted_handles_weight_var_outside_BDD[function] — test source atlib/pluck/src/weight.zig:1070in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_wmcWeighted_matches_brute_force_on_guarded_weight[function] — test source atlib/pluck/src/weight.zig:1010in nearest public ownertiny.pluck.weight_ddlib.pluck.src.wmc.test_parallel_WMC_handles_exact_divisibility_by_thread_count[function] — test source atlib/pluck/src/wmc.zig:224in nearest public ownertiny.pluck.wmclib.pluck.src.wmc.test_parallel_WMC_produces_same_results_as_sequential[function] — test source atlib/pluck/src/wmc.zig:173in nearest public ownertiny.pluck.wmc
Complete caller list for weight_dd.WeightDD.init
14 direct callers.
lib.pluck.src.profiling.internal.factor.test_BENCHMARK:_WeightDD_refinement_budget[function] — test source atlib/pluck/src/profiling/internal/factor.zig:697in nearest public ownerlib.pluck.src.profiling.internal.factortiny.pluck.state.initChecked[function] atlib/pluck/src/state/machine.zig:102lib.pluck.src.weight.test_WeightDD_buildFromGuardList_and_lookup[function] — test source atlib/pluck/src/weight.zig:958in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_canonicalizes_leaves_and_branches[function] — test source atlib/pluck/src/weight.zig:935in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_ite_matches_brute_force_enumeration[function] — test source atlib/pluck/src/weight.zig:1144in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_mul/add_match_brute_force_enumeration[function] — test source atlib/pluck/src/weight.zig:1097in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_nodeCount_reports_reachable_nodes[function] — test source atlib/pluck/src/weight.zig:1188in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_refineWeight_respects_budget[function] — test source atlib/pluck/src/weight.zig:977in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_rejects_NaN_weights[function] — test source atlib/pluck/src/weight.zig:995in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_wmcWeightedWithCache_matches_per-call_WMC[function] — test source atlib/pluck/src/weight.zig:1037in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_wmcWeighted_handles_weight_var_outside_BDD[function] — test source atlib/pluck/src/weight.zig:1070in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_wmcWeighted_matches_brute_force_on_guarded_weight[function] — test source atlib/pluck/src/weight.zig:1010in nearest public ownertiny.pluck.weight_ddlib.pluck.src.wmc.test_parallel_WMC_handles_exact_divisibility_by_thread_count[function] — test source atlib/pluck/src/wmc.zig:224in nearest public ownertiny.pluck.wmclib.pluck.src.wmc.test_parallel_WMC_produces_same_results_as_sequential[function] — test source atlib/pluck/src/wmc.zig:173in nearest public ownertiny.pluck.wmc
Complete caller list for weight_dd.WeightDD.leaf
12 direct callers.
tiny.pluck.state.initChecked[function] atlib/pluck/src/state/machine.zig:102lib.pluck.src.weight.WeightDD.applyBinary[method] — private source atlib/pluck/src/weight.zig:564in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.WeightDD.buildFromGuardListInner[method] — private source atlib/pluck/src/weight.zig:242in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_canonicalizes_leaves_and_branches[function] — test source atlib/pluck/src/weight.zig:935in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_ite_matches_brute_force_enumeration[function] — test source atlib/pluck/src/weight.zig:1144in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_mul/add_match_brute_force_enumeration[function] — test source atlib/pluck/src/weight.zig:1097in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_WeightDD_nodeCount_reports_reachable_nodes[function] — test source atlib/pluck/src/weight.zig:1188in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_wmcWeightedWithCache_matches_per-call_WMC[function] — test source atlib/pluck/src/weight.zig:1037in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_wmcWeighted_handles_weight_var_outside_BDD[function] — test source atlib/pluck/src/weight.zig:1070in nearest public ownertiny.pluck.weight_ddlib.pluck.src.weight.test_wmcWeighted_matches_brute_force_on_guarded_weight[function] — test source atlib/pluck/src/weight.zig:1010in nearest public ownertiny.pluck.weight_ddlib.pluck.src.wmc.test_parallel_WMC_handles_exact_divisibility_by_thread_count[function] — test source atlib/pluck/src/wmc.zig:224in nearest public ownertiny.pluck.wmclib.pluck.src.wmc.test_parallel_WMC_produces_same_results_as_sequential[function] — test source atlib/pluck/src/wmc.zig:173in nearest public ownertiny.pluck.wmc
Audit
| Definitions | 31 |
|---|---|
| Public names | 31 |
| Members | 20 |
| Version | 26.7.0 |
| Revision | daab053ee433 |