tiny.pluck.evaluator
Defined in tiny.pluck.
API (63)
Actions
Public operations.
LimitReason.messagebindMonadcombineIntDistscompareCallstackscompilecompileInnercomputeWmcParallelcomputeWmcSequentialconditionWorldscreateEvaluatorOpsenumerateIntDistevaluateThunkextractNatForcingThunksfalsePathConditionWorldsforceValueforceValueDeterministicfreeWorldsSliceifThenElseMonadinferFullDistributioninferenceErrorWorldsintDistAtIntjoinMonadmakeThunkprocessIntDistWorldsprocessMarginalQueryprocessPosteriorQueryprogramErrorWorldspureMonadsubproblemMonteCarloImpltracedCompileInner
Types and contracts
Public types and contracts.
AdaptiveKPolicyCallstackCombinedIntDistCompileErrorCompileResultEvaluatorOpsFallbackModeIncrementalLPSMCInferenceModeIntDistWithGuardLPSMCVarianceStatsLazyKCConfigLazyKCStateLazyKCState.CallstackHashContextLazyKCState.CallstackKeyLazyKCState.DeferredWeightLazyKCStatsLazyKCStats.MaxBddSamplesLimitReasonLpsmcRunStatsNestedWorldPathChoiceRuntimeValueContextSubproblemCacheThunkDependenciesThunkIdThunkIdContextThunkIdSetThunkRegistryVarLabelSetWeightedResultWorldWorldsResult
Source
Source: lib/pluck/src/dist.zig:18
Source: lib/pluck/src/dist.zig:13
Source: lib/pluck/src/monad.zig:23
pub const CompileError = error{ OutOfMemory, PluckError, InvalidExpression, NotImplemented, InvalidThunkUnion,};Source: lib/pluck/src/state/config.zig:55
pub const FallbackMode = enum { @"error", lpsmc,};Source: lib/pluck/src/state/config.zig:60
pub const InferenceMode = enum { exact, lpsmc,};Source: lib/pluck/src/state/config.zig:7
pub const LazyKCConfig = struct { max_depth: ?u32 = null, sample_after_max_depth: bool = false, use_strict_order: bool = true, use_reverse_order: bool = false, definition_order: ?*const definition_order.DefinitionOrder = null, use_thunk_unions: bool = true, disable_validity_tracking: bool = false, disable_path_conditions: bool = false, time_limit: ?f64 = null, ite_limit: ?u64 = null, factor_max_branches: usize = 64, weight_dd_max_nodes: usize = 0, fallback_mode: FallbackMode = .@"error", inference_mode: InferenceMode = .exact, sample_constraint: ?Bdd = null, full_dist: bool = false, dual: bool = false, vector_size: u32 = 0, state_vars: StateVars = StateVars.init(), stacktrace: bool = true, parallel_wmc: bool = false, parallel_wmc_threshold: usize = 16, parallel_wmc_threads: ?usize = null,};Source: lib/pluck/src/state/machine.zig:37
pub const LazyKCState = struct { allocator: Allocator, manager: *Manager, wmc_params: WmcParams, weight_dd: WeightDD, weight_dd_root: Weight, weight_dd_one: Weight, deferred_weights: std.ArrayListUnmanaged(DeferredWeight), cfg: LazyKCConfig, stats: LazyKCStats, callstack: std.ArrayList(i32), var_of_callstack: std.HashMapUnmanaged(StateCallstackKey, Bdd, StateCallstackHashContext, 80), sorted_callstacks: std.ArrayList(StateCallstackKey), depth: u32, definitions: *const Definitions, current_def_name: ?Symbol, stacktrace_buf: std.ArrayList(*PExpr), query: ?*PExpr, next_thunk_id: u32, start_time: i128, registry: ?*ThunkRegistry, sampled_flips: std.HashMapUnmanaged(StateCallstackKey, bool, StateCallstackHashContext, 80), prng: std.Random.DefaultPrng, def_thunks: std.StringHashMapUnmanaged(*RuntimeValue), pub const DeferredWeight = struct { guards: []GuardedWeight, }; pub const CallstackKey: type = callstack.CallstackKey; pub const CallstackHashContext: type = callstack.CallstackHashContext;};Source: lib/pluck/src/state/stats.zig:17
pub const LazyKCStats = struct { pub const MaxBddSamples: usize = 32; time_ns: u64 = 0, num_forward_calls: u64 = 0, limit_reason: ?LimitReason = null, program_error: bool = false, num_recursive_calls: u64 = 0, ite_cache_hits: u64 = 0, ite_cache_misses: u64 = 0, unique_table_grows: u64 = 0, ite_cache_grows: u64 = 0, thunk_reuse_hits: u64 = 0, thunk_reuse_misses: u64 = 0, thunk_evaluations: u64 = 0, thunk_cache_hits: u64 = 0, variable_count: u64 = 0, node_count: u64 = 0, wmc_time_ns: u64 = 0, refinement_time_ns: u64 = 0, refinement_count: u64 = 0, bdd_samples_forward_calls: [MaxBddSamples]u64 = @as([MaxBddSamples]u64, @splat(0)), bdd_samples_vars: [MaxBddSamples]u64 = @as([MaxBddSamples]u64, @splat(0)), bdd_samples_nodes: [MaxBddSamples]u64 = @as([MaxBddSamples]u64, @splat(0)), bdd_samples_len: u8 = 0, max_factor_guard_branches: usize = 0,};Source: lib/pluck/src/state/stats.zig:1
pub const LimitReason = enum { max_depth, time_limit, ite_limit, factor_weight_too_complex, pub fn message(self: LimitReason) []const u8 { return switch (self) { .max_depth => "max_depth limit exceeded (consider increasing --max-depth or using bounded distributions)", .time_limit => "time limit exceeded", .ite_limit => "BDD work quota exceeded (model may be too complex)", .factor_weight_too_complex => "factor weight too complex for exact KC (retry with --fallback-mode=lpsmc)", }; }};Source: lib/pluck/src/dist.zig:23
pub fn combineIntDists( allocator: Allocator, int_dist_results: []const IntDistWithGuard, manager: *Manager,) !CombinedIntDist { if (int_dist_results.len == 0) { return .{ .int_dist = IntDist.init(&.{}), .overall_guard = Bdd.FALSE }; } const width = int_dist_results[0].int_dist.bits.len; const result_bits = try allocator.alloc(Bdd, width); for (result_bits) |*bit| { bit.* = Bdd.FALSE; } var overall_guard = Bdd.FALSE; for (int_dist_results) |entry| { std.debug.assert(entry.int_dist.bits.len == width); overall_guard = try manager.bddOr(overall_guard, entry.guard); for (0..width) |i| { const new_bit = try manager.bddAnd(entry.int_dist.bits[i], entry.guard); result_bits[i] = try manager.bddOr(result_bits[i], new_bit); } } return .{ .int_dist = IntDist.init(result_bits), .overall_guard = overall_guard, };}Source: lib/pluck/src/dist.zig:79
pub fn enumerateIntDist( allocator: Allocator, int_dist: IntDist, overall_guard: Bdd, manager: *Manager,) ![]World { const num_bits = int_dist.bits.len; std.debug.assert(num_bits <= 20); const num_values: usize = @as(usize, 1) << @intCast(num_bits); var results: std.ArrayList(World) = .empty; defer results.deinit(allocator); for (0..num_values) |i| { const value: u64 = @intCast(i); const value_guard = try intDistAtInt(int_dist, value, manager); const combined_guard = try manager.bddAnd(value_guard, overall_guard); if (combined_guard.isFalse()) { continue; } const int_val = try RuntimeValue.initNative(allocator, .{ .int = @intCast(value) }); try results.append(allocator, World{ .value = int_val, .guard = combined_guard }); } return results.toOwnedSlice(allocator);}Source: lib/pluck/src/dist.zig:58
pub fn intDistAtInt(int_dist: IntDist, value: u64, manager: *Manager) Allocator.Error!Bdd { var result = Bdd.TRUE; for (0..int_dist.bits.len) |i| { const bit_val = (value >> @intCast(i)) & 1 == 1; const bit_bdd = int_dist.bits[i]; if (bit_val) { result = try manager.bddAnd(result, bit_bdd); } else { result = try manager.bddAnd(result, manager.bddNot(bit_bdd)); } if (result.isFalse()) { return Bdd.FALSE; } } return result;}Source: lib/pluck/src/dist.zig:109
pub fn processIntDistWorlds( allocator: Allocator, worlds: []World, manager: *Manager,) !?[]World { if (worlds.len == 0) { return null; } const first_int_dist = worlds[0].value.maybeIntDist() orelse return null; const width = first_int_dist.bits.len; if (width > 20) { return null; } for (worlds) |world| { const int_dist = world.value.maybeIntDist() orelse return null; if (int_dist.bits.len != width) { return null; } } const int_dist_pairs = try allocator.alloc(IntDistWithGuard, worlds.len); defer allocator.free(int_dist_pairs); for (worlds, 0..) |world, i| { int_dist_pairs[i] = .{ .int_dist = world.value.maybeIntDist().?, .guard = world.guard, }; } const combined = try combineIntDists(allocator, int_dist_pairs, manager); errdefer allocator.free(combined.int_dist.bits); const result = try enumerateIntDist(allocator, combined.int_dist, combined.overall_guard, manager); allocator.free(combined.int_dist.bits); for (worlds) |world| { world.value.deinit(allocator); } return result;}Source: lib/pluck/src/monad.zig:95
pub fn bindMonad( allocator: Allocator, pre_worlds: WorldsResult, path_condition: Bdd, state: *LazyKCState, comptime ContType: type, ctx: anytype,) CompileError!WorldsResult { defer freeWorldsSlice(allocator, pre_worlds.worlds); var nested_worlds: std.ArrayList(NestedWorld) = .empty; defer nested_worlds.deinit(allocator); for (pre_worlds.worlds) |pre_world| { if (state.stats.limit_reason != null) { return inferenceErrorWorlds(state); } const inner_pc = if (state.cfg.disable_path_conditions) Bdd.TRUE else try state.manager.bddAnd(path_condition, pre_world.guard); if (inner_pc.isFalse()) { try nested_worlds.append(allocator, NestedWorld{ .result = falsePathConditionWorlds(state), .guard = pre_world.guard, }); continue; } const post_result = try ContType.cont(allocator, pre_world.value, inner_pc, state, ctx); try nested_worlds.append(allocator, NestedWorld{ .result = post_result, .guard = pre_world.guard, }); } return joinMonad(allocator, nested_worlds.items, pre_worlds.validity_guard, state);}Source: lib/pluck/src/monad.zig:84
pub fn conditionWorlds(allocator: Allocator, worlds: []World, condition: Bdd, manager: *Manager) ![]World { const result = try allocator.alloc(World, worlds.len); for (worlds, 0..) |world, i| { result[i] = World{ .value = world.value, .guard = manager.bddAnd(world.guard, condition), }; } return result;}Source: lib/pluck/src/monad.zig:45
pub fn falsePathConditionWorlds(_: *LazyKCState) WorldsResult { return WorldsResult{ .worlds = &[_]World{}, .validity_guard = Bdd.FALSE, };}Source: lib/pluck/src/monad.zig:52
pub fn freeWorldsSlice(allocator: Allocator, worlds: []World) void { if (worlds.len > 0) { allocator.free(worlds); }}Source: lib/pluck/src/monad.zig:67
pub fn ifThenElseMonad( allocator: Allocator, val_if_true: *RuntimeValue, val_if_false: *RuntimeValue, condition: Bdd, state: *LazyKCState,) !WorldsResult { _ = state; const worlds = try allocator.alloc(World, 2); worlds[0] = World{ .value = val_if_true, .guard = condition }; worlds[1] = World{ .value = val_if_false, .guard = condition.neg() }; return WorldsResult{ .worlds = worlds, .validity_guard = Bdd.TRUE, };}Source: lib/pluck/src/monad.zig:38
pub fn inferenceErrorWorlds(_: *LazyKCState) WorldsResult { return WorldsResult{ .worlds = &[_]World{}, .validity_guard = Bdd.TRUE, };}Source: lib/pluck/src/monad.zig:136
pub fn joinMonad( allocator: Allocator, nested_worlds: []const NestedWorld, pre_validity_guard: Bdd, state: *LazyKCState,) CompileError!WorldsResult { var validity_guard = pre_validity_guard; if (!state.cfg.disable_validity_tracking) { for (nested_worlds) |nested| { const branch_validity_guard = try state.manager.bddImplies(nested.guard, nested.result.validity_guard); validity_guard = try state.manager.bddAnd(validity_guard, branch_validity_guard); } } var join_results: std.ArrayList(World) = .empty; defer join_results.deinit(allocator); var index_of_result = std.HashMap(*RuntimeValue, usize, RuntimeValueContext, 80).init(allocator); defer index_of_result.deinit(); var constructor_worlds = std.StringHashMap(std.ArrayList(World)).init(allocator); defer { if (state.cfg.use_thunk_unions) { var it = constructor_worlds.iterator(); while (it.next()) |entry| { entry.value_ptr.deinit(allocator); } } constructor_worlds.deinit(); } for (nested_worlds) |nested| { defer freeWorldsSlice(allocator, nested.result.worlds); for (nested.result.worlds) |post_world| { const pre_and_post = try state.manager.bddAnd(post_world.guard, nested.guard); if (state.cfg.use_thunk_unions and post_world.value.data == .constructed) { const constructor = post_world.value.data.constructed.constructor; const entry = try constructor_worlds.getOrPut(constructor); if (!entry.found_existing) { entry.value_ptr.* = std.ArrayList(World).empty; } try entry.value_ptr.append(allocator, World{ .value = post_world.value, .guard = pre_and_post, }); continue; } if (index_of_result.get(post_world.value)) |result_idx| { const old_guard = join_results.items[result_idx].guard; const new_guard = try state.manager.bddOr(old_guard, pre_and_post); join_results.items[result_idx].guard = new_guard; } else { const idx = join_results.items.len; try index_of_result.put(post_world.value, idx); try join_results.append(allocator, World{ .value = post_world.value, .guard = pre_and_post, }); } } } if (state.cfg.use_thunk_unions) { var ctor_iter = constructor_worlds.iterator(); while (ctor_iter.next()) |entry| { const constructor = entry.key_ptr.*; const worlds = entry.value_ptr.items; if (worlds.len == 0) { continue; } var value_to_guard = std.HashMap(*RuntimeValue, Bdd, RuntimeValueContext, 80).init(allocator); defer value_to_guard.deinit(); for (worlds) |world| { if (value_to_guard.get(world.value)) |old_guard| { const new_guard = try state.manager.bddOr(old_guard, world.guard); try value_to_guard.put(world.value, new_guard); } else { try value_to_guard.put(world.value, world.guard); } } if (value_to_guard.count() <= 1) { var single_iter = value_to_guard.iterator(); while (single_iter.next()) |val_entry| { try join_results.append(allocator, World{ .value = val_entry.key_ptr.*, .guard = val_entry.value_ptr.*, }); } continue; } var first_iter = value_to_guard.iterator(); const first_entry = first_iter.next() orelse continue; const first_val = first_entry.key_ptr.*; if (first_val.data != .constructed) { var fallback_iter = value_to_guard.iterator(); while (fallback_iter.next()) |val_entry| { try join_results.append(allocator, World{ .value = val_entry.key_ptr.*, .guard = val_entry.value_ptr.*, }); } continue; } const arg_count = first_val.data.constructed.args.len; var arg_inputs = try allocator.alloc(std.ArrayList(LazyKCThunkUnion.ThunkInput), arg_count); defer { for (arg_inputs) |*list| { list.deinit(allocator); } allocator.free(arg_inputs); } for (arg_inputs) |*list| { list.* = .empty; } var can_union = true; var overall_guard = Bdd.FALSE; var value_iter = value_to_guard.iterator(); while (value_iter.next()) |val_entry| { const val = val_entry.key_ptr.*; const guard = val_entry.value_ptr.*; overall_guard = try state.manager.bddOr(overall_guard, guard); if (val.data != .constructed) { can_union = false; break; } const c = val.data.constructed; if (c.args.len != arg_count) { can_union = false; break; } for (c.args, 0..) |arg, i| { const can_union_arg = switch (arg.data) { .lazy_kc_thunk, .lazy_kc_thunk_union => true, else => false, }; if (!can_union_arg) { can_union = false; break; } try arg_inputs[i].append(allocator, .{ .value = arg, .outer_guard = guard, }); } if (!can_union) { break; } } if (!can_union) { var fallback_iter = value_to_guard.iterator(); while (fallback_iter.next()) |val_entry| { try join_results.append(allocator, World{ .value = val_entry.key_ptr.*, .guard = val_entry.value_ptr.*, }); } continue; } const union_args = try allocator.alloc(*RuntimeValue, arg_count); var created: usize = 0; errdefer { for (union_args[0..created]) |arg| { arg.deinit(allocator); } allocator.free(union_args); } for (arg_inputs, 0..) |*list, i| { const thunk_union = try LazyKCThunkUnion.init(allocator, state.manager, list.items); const union_val = try RuntimeValue.initLazyKCThunkUnion(allocator, thunk_union); union_args[i] = union_val; created += 1; } const combined_val = try RuntimeValue.initConstructed(allocator, constructor, union_args); try join_results.append(allocator, World{ .value = combined_val, .guard = overall_guard, }); } } return WorldsResult{ .worlds = try join_results.toOwnedSlice(allocator), .validity_guard = validity_guard, };}Source: lib/pluck/src/monad.zig:31
pub fn programErrorWorlds(_: *LazyKCState) WorldsResult { return WorldsResult{ .worlds = &[_]World{}, .validity_guard = Bdd.TRUE, };}Source: lib/pluck/src/monad.zig:58
pub fn pureMonad(allocator: Allocator, val: *RuntimeValue, _: *LazyKCState) !WorldsResult { const worlds = try allocator.alloc(World, 1); worlds[0] = World{ .value = val, .guard = Bdd.TRUE }; return WorldsResult{ .worlds = worlds, .validity_guard = Bdd.TRUE, };}Source: lib/pluck/src/state/callstack.zig:25
pub fn compareCallstacks(a: []const i32, b: []const i32) std.math.Order { const min_len = @min(a.len, b.len); for (0..min_len) |i| { if (a[i] < b[i]) return .lt; if (a[i] > b[i]) return .gt; } if (a.len < b.len) return .lt; if (a.len > b.len) return .gt; return .eq;}Source: lib/pluck/src/evaluator.zig
const std = @import("std");const time = @import("time.zig");const Allocator = std.mem.Allocator;const pexpr = @import("pexpr.zig");const PExpr = pexpr.PExpr;const Head = pexpr.Head;const Symbol = pexpr.Symbol;const Definitions = pexpr.Definitions;const runtime = @import("runtime.zig");const Env = runtime.Env;const RuntimeValue = runtime.RuntimeValue;const Closure = runtime.Closure;const LazyKCThunk = runtime.LazyKCThunk;const LazyKCThunkUnion = runtime.LazyKCThunkUnion;const StateVars = runtime.StateVars;const GuardedWorld = runtime.GuardedWorld;const GuardedWorlds = runtime.GuardedWorlds;const bdd = @import("bdd.zig");const Bdd = bdd.Bdd;const Manager = bdd.Manager;const WmcParams = bdd.WmcParams;const VarLabel = bdd.VarLabel;const weight_dd = @import("weight.zig");const GuardedWeight = weight_dd.GuardedWeight;const wmc_module = @import("wmc.zig");const thunk_registry = @import("registry.zig");const lpsmc_module = @import("lpsmc.zig");const int_dist_module = @import("dist.zig");const state_module = @import("state/root.zig");const monad_ops = @import("monad.zig");pub const Callstack = []const i32;pub const World = GuardedWorld;pub const WorldsResult = GuardedWorlds;pub const CompileError = monad_ops.CompileError;pub const RuntimeValueContext = runtime.RuntimeValueContext;pub const NestedWorld = runtime.NestedWorld;pub const WeightedResult = wmc_module.WeightedResult;const DeferredWmcCaches = wmc_module.DeferredCaches;pub const computeWmcParallel = wmc_module.computeWmcParallel;pub const computeWmcSequential = wmc_module.computeWmcSequential;pub const ThunkId = thunk_registry.ThunkId;pub const ThunkIdContext = thunk_registry.ThunkIdContext;pub const ThunkRegistry = thunk_registry.ThunkRegistry;pub const ThunkIdSet = thunk_registry.ThunkIdSet;pub const ThunkDependencies = thunk_registry.ThunkDependencies;pub const VarLabelSet = bdd.VarLabelSet;pub const PathChoice = lpsmc_module.PathChoice;pub const SubproblemCache = lpsmc_module.SubproblemCache;pub const LPSMCVarianceStats = lpsmc_module.LPSMCVarianceStats;pub const AdaptiveKPolicy = lpsmc_module.AdaptiveKPolicy;pub const LpsmcRunStats = lpsmc_module.LpsmcRunStats;pub const EvaluatorOps = lpsmc_module.EvaluatorOps;pub const IncrementalLPSMC = lpsmc_module.IncrementalLPSMC;pub const IntDistWithGuard = int_dist_module.IntDistWithGuard;pub const CombinedIntDist = int_dist_module.CombinedIntDist;pub const combineIntDists = int_dist_module.combineIntDists;pub const intDistAtInt = int_dist_module.intDistAtInt;pub const enumerateIntDist = int_dist_module.enumerateIntDist;pub const processIntDistWorlds = int_dist_module.processIntDistWorlds;pub const LazyKCConfig = state_module.LazyKCConfig;pub const LimitReason = state_module.LimitReason;pub const LazyKCStats = state_module.LazyKCStats;pub const LazyKCState = state_module.LazyKCState;pub const compareCallstacks = state_module.compareCallstacks;pub const FallbackMode = state_module.FallbackMode;pub const InferenceMode = state_module.InferenceMode;pub const programErrorWorlds = monad_ops.programErrorWorlds;pub const inferenceErrorWorlds = monad_ops.inferenceErrorWorlds;pub const falsePathConditionWorlds = monad_ops.falsePathConditionWorlds;pub const freeWorldsSlice = monad_ops.freeWorldsSlice;pub const pureMonad = monad_ops.pureMonad;pub const ifThenElseMonad = monad_ops.ifThenElseMonad;pub const conditionWorlds = monad_ops.conditionWorlds;pub const bindMonad = monad_ops.bindMonad;pub const joinMonad = monad_ops.joinMonad;const DeferredWmcError = error{ OutOfMemory, NodeLimitExceeded,};fn wmcForState(state: *LazyKCState, guard: Bdd) DeferredWmcError!f64 { return wmcWithDeferred(state, guard, state.allocator);}fn wmcForStateWithAllocator(state: *LazyKCState, guard: Bdd, allocator: Allocator) DeferredWmcError!f64 { return wmcWithDeferred(state, guard, allocator);}fn wmcWithDeferred(state: *LazyKCState, guard: Bdd, allocator: Allocator) DeferredWmcError!f64 { var caches = DeferredWmcCaches.init(allocator); defer caches.deinit(); return wmcWithDeferredCached(state, guard, &caches);}fn wmcWithDeferredCached(state: *LazyKCState, guard: Bdd, caches: *DeferredWmcCaches) DeferredWmcError!f64 { if (state.deferred_weights.items.len == 0) { return weight_dd.wmcWeightedWithCache( &state.weight_dd, guard, state.weight_dd_root, &state.wmc_params, &caches.weighted, ); } return wmcWithDeferredInner(state, guard, 0, &caches.deferred, &caches.weighted);}fn wmcWithDeferredInner( state: *LazyKCState, guard: Bdd, index: usize, deferred_cache: *std.AutoHashMap(u64, f64), weighted_cache: *std.AutoHashMap(u64, f64),) DeferredWmcError!f64 { if (guard.isFalse()) return 0.0; if (index >= state.deferred_weights.items.len) { return weight_dd.wmcWeightedWithCache( &state.weight_dd, guard, state.weight_dd_root, &state.wmc_params, weighted_cache, ); } const cache_key: u64 = (@as(u64, @intCast(index)) << 32) | @as(u64, guard.toRaw()); if (deferred_cache.get(cache_key)) |cached| { return cached; } const deferred = state.deferred_weights.items[index]; const limit = state.cfg.weight_dd_max_nodes; if (limit != 0 and deferred.guards.len > limit) { return error.NodeLimitExceeded; } var total: f64 = 0.0; for (deferred.guards) |entry| { if (entry.weight == 0.0) continue; if (entry.guard.isFalse()) continue; const combined = state.manager.bddAnd(guard, entry.guard) catch return error.OutOfMemory; if (combined.isFalse()) continue; const sub = try wmcWithDeferredInner(state, combined, index + 1, deferred_cache, weighted_cache); total += entry.weight * sub; } deferred_cache.put(cache_key, total) catch {}; return total;}fn evaluateThunkOp( allocator: Allocator, val: *RuntimeValue, path_condition: Bdd, state_ptr: *anyopaque,) anyerror!lpsmc_module.WorldsResult { const state: *LazyKCState = @ptrCast(@alignCast(state_ptr)); return evaluateThunk(allocator, val, path_condition, state);}fn freeWorldsSliceOp(allocator: Allocator, worlds: []World) void { freeWorldsSlice(allocator, worlds);}fn setWeightOp(wmc_params: *WmcParams, variable: VarLabel, low: f64, high: f64) Allocator.Error!void { return wmc_params.setWeight(variable, low, high);}pub fn createEvaluatorOps(state: *LazyKCState) EvaluatorOps { return EvaluatorOps{ .evaluateThunk = evaluateThunkOp, .freeWorldsSlice = freeWorldsSliceOp, .setWeight = setWeightOp, .state = state, .wmc_params = &state.wmc_params, };}pub fn subproblemMonteCarloImpl( allocator: Allocator, suspendible_thunk: *RuntimeValue, evidence_thunk: ?*RuntimeValue, k: usize, k_policy: AdaptiveKPolicy, state: *LazyKCState, manager: *Manager, external_rng: ?std.Random,) ![]World { const ops = createEvaluatorOps(state); const raw_worlds = try lpsmc_module.subproblemMonteCarloImpl( allocator, suspendible_thunk, evidence_thunk, k, k_policy, ops, manager, external_rng, ); return inferFullDistribution(allocator, raw_worlds, state);}pub fn tracedCompileInner( expr: *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState, strict_order_index: i32,) CompileError!WorldsResult { if (path_condition.isFalse()) { return falsePathConditionWorlds(state); } if (state_module.checkLimits(state)) { return inferenceErrorWorlds(state); } state.depth += 1; defer state.depth -= 1; try state_module.pushCallstack(state, strict_order_index); defer state_module.popCallstack(state); if (state.cfg.stacktrace) { try state.stacktrace_buf.append(state.allocator, expr); } defer { if (state.cfg.stacktrace and state.stacktrace_buf.items.len > 0) { _ = state.stacktrace_buf.pop(); } } const result = try compileInner(expr, env, path_condition, state); state.stats.num_forward_calls += 1; state_module.maybeSampleBdd(state); if (state_module.checkLimits(state)) { return inferenceErrorWorlds(state); } return result;}pub fn compileInner( expr: *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState,) CompileError!WorldsResult { const allocator = state.allocator; return switch (expr.head) { .app => compileApp(expr, env, path_condition, state), .abs => compileAbs(allocator, expr, env, path_condition, state), .var_ref => |v| compileVar(allocator, v.name, env, path_condition, state), .defined => |d| compileDefined(d.name, env, path_condition, state), .construct => |c| compileConstruct(allocator, c.constructor, expr.args, env, path_condition, state), .case_of => compileCaseOf(expr, env, path_condition, state), .y_combinator => compileY(allocator, expr, env, path_condition, state), .flip => compileFlip(allocator, expr, env, path_condition, state), .factor => compileFactor(allocator, expr, env, path_condition, state), .const_native => |n| compileConstNative(allocator, n, state), .native_eq => compileNativeEq(allocator, expr, env, path_condition, state), .get_args => compileGetArgs(allocator, expr, env, path_condition, state), .get_constructor => compileGetConstructor(allocator, expr, env, path_condition, state), .f_div => compileFloatBinop(allocator, .div, expr, env, path_condition, state), .f_mul => compileFloatBinop(allocator, .mul, expr, env, path_condition, state), .f_add => compileFloatBinop(allocator, .add, expr, env, path_condition, state), .f_sub => compileFloatBinop(allocator, .sub, expr, env, path_condition, state), .print_op => tracedCompileInner(expr.args[0], env, path_condition, state, 0), .error_op => error.PluckError, .pbool => compilePBool(allocator, expr, env, path_condition, state), .mk_int => compileMkInt(allocator, expr, state), .mk_int_weighted => compileMkIntWeighted(allocator, expr, env, path_condition, state), .int_dist_eq => compileIntDistEq(allocator, expr, env, path_condition, state), .get_config => compileGetConfig(allocator, state), .type_def => error.InvalidExpression, };}const CompileAppContinuation = struct { pub fn cont( alloc: Allocator, f: *RuntimeValue, inner_pc: Bdd, s: *LazyKCState, ctx: anytype, ) !WorldsResult { const thunk = ctx.thunk; const orig_env = ctx.env; _ = orig_env; switch (f.data) { .closure => |closure| { const thunk_val = try RuntimeValue.initLazyKCThunk(alloc, thunk); const new_env = try closure.env.extend(alloc, closure.name, thunk_val); return switch (closure.expr) { .pexpr => |body| tracedCompileInner(body, new_env, inner_pc, s, 2), .thunk => error.NotImplemented, }; }, else => return programErrorWorlds(s), } }};fn compileApp( expr: *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState,) CompileError!WorldsResult { const allocator = state.allocator; const arg_thunk = try makeThunk(allocator, expr.args[1], env, 1, state); const func_result = try tracedCompileInner(expr.args[0], env, path_condition, state, 0); return bindMonad( allocator, func_result, path_condition, state, CompileAppContinuation, .{ .thunk = arg_thunk, .env = env }, );}fn compileAbs( allocator: Allocator, expr: *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { _ = path_condition; const var_name = expr.head.abs.var_name; const closure = try Closure.init(allocator, expr.args[0], env, var_name); const val = try RuntimeValue.initClosure(allocator, closure); return pureMonad(allocator, val, state);}fn compileVar( allocator: Allocator, name: Symbol, env: Env, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { const v = env.get(name) orelse return programErrorWorlds(state); if (v.isThunk()) { return evaluateThunk(allocator, v, path_condition, state); } return pureMonad(allocator, v, state);}fn compileDefined( name: Symbol, env: Env, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { const allocator = state.allocator; if (env.get(name)) |v| { if (v.isThunk()) { return evaluateThunk(allocator, v, path_condition, state); } return pureMonad(allocator, v, state); } const def_expr = state.definitions.lookup(name) orelse return programErrorWorlds(state); const saved_def = state.current_def_name; state.current_def_name = name; defer state.current_def_name = saved_def; if (state.def_thunks.get(name)) |thunk_val| { return evaluateThunk(allocator, thunk_val, path_condition, state); } var strict_index: i32 = 0; if (state.cfg.definition_order) |order| { if (order.getIndex(name)) |idx| { strict_index = idx; } } const thunk = try makeThunk(allocator, def_expr, Env.empty, strict_index, state); const thunk_val = try RuntimeValue.initLazyKCThunk(allocator, thunk); try state.def_thunks.put(allocator, name, thunk_val); return evaluateThunk(allocator, thunk_val, path_condition, state);}fn compileConstruct( allocator: Allocator, constructor: Symbol, args: []const *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { _ = path_condition; const thunked_args = try allocator.alloc(*RuntimeValue, args.len); for (args, 0..) |arg, i| { const thunk = try makeThunk(allocator, arg, env, @intCast(i + 1), state); thunked_args[i] = try RuntimeValue.initLazyKCThunk(allocator, thunk); } const val = try RuntimeValue.initConstructed(allocator, constructor, thunked_args); return pureMonad(allocator, val, state);}const CaseGuardMatchKind = enum { constructor, wildcard, capture,};fn isWildcardCaseGuard(guard: pexpr.CaseOfGuard) bool { return guard.args.len == 0 and std.mem.eql(u8, guard.constructor, "_");}fn isCaptureCaseGuard(guard: pexpr.CaseOfGuard) bool { return guard.args.len == 0 and guard.constructor.len > 0 and !std.ascii.isUpper(guard.constructor[0]) and !std.mem.eql(u8, guard.constructor, "_");}const CompileCaseContinuation = struct { pub fn cont( alloc: Allocator, scrutinee: *RuntimeValue, inner_pc: Bdd, s: *LazyKCState, ctx: anytype, ) !WorldsResult { const e = ctx.expr; const branches_inner = ctx.branches; const orig_env = ctx.env; const constructed = if (scrutinee.data == .constructed) scrutinee.data.constructed else null; var branch_idx: ?usize = null; var match_kind: CaseGuardMatchKind = .constructor; for (branches_inner, 0..) |guard, i| { if (constructed) |c| { if (std.mem.eql(u8, guard.constructor, c.constructor)) { branch_idx = i; match_kind = .constructor; break; } } if (isWildcardCaseGuard(guard)) { branch_idx = i; match_kind = .wildcard; break; } if (isCaptureCaseGuard(guard)) { branch_idx = i; match_kind = .capture; break; } } if (branch_idx == null) { return programErrorWorlds(s); } const idx = branch_idx.?; const guard = branches_inner[idx]; const case_expr = e.args[idx + 1]; var new_env = orig_env; switch (match_kind) { .constructor => { const c = constructed orelse return programErrorWorlds(s); if (guard.args.len != c.args.len) { return programErrorWorlds(s); } for (guard.args, c.args) |name, arg| { new_env = try new_env.extend(alloc, name, arg); } }, .wildcard => {}, .capture => { new_env = try new_env.extend(alloc, guard.constructor, scrutinee); }, } return tracedCompileInner(case_expr, new_env, inner_pc, s, @intCast(idx + 1)); }};fn compileCaseOf( expr: *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { const allocator = state.allocator; const branches = expr.head.case_of.branches; const scrutinee_result = try tracedCompileInner(expr.args[0], env, path_condition, state, 0); return bindMonad( allocator, scrutinee_result, path_condition, state, CompileCaseContinuation, .{ .expr = expr, .branches = branches, .env = env }, );}fn compileY( allocator: Allocator, expr: *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { _ = path_condition; const rec_lambda = expr.args[0]; if (rec_lambda.head != .abs) return programErrorWorlds(state); const arg_lambda = rec_lambda.args[0]; if (arg_lambda.head != .abs) return programErrorWorlds(state); const rec_name = rec_lambda.head.abs.var_name; const arg_name = arg_lambda.head.abs.var_name; const body = arg_lambda.args[0]; const closure = try Closure.makeSelfLoop(allocator, body, env, rec_name, arg_name); const val = try RuntimeValue.initClosure(allocator, closure); return pureMonad(allocator, val, state);}const FlipOutcome = union(enum) { addr: Bdd, sampled: bool,};fn sampleFlipForCallstack( allocator: Allocator, callstack: []const i32, p: f64, state: *LazyKCState,) CompileError!bool { const lookup_key = LazyKCState.CallstackKey{ .callstack = callstack, .prob = p, }; if (state.sampled_flips.get(lookup_key)) |sampled_true| { return sampled_true; } const random = state.prng.random(); const sampled_true = random.float(f64) < p; const callstack_copy = try allocator.dupe(i32, callstack); const store_key = LazyKCState.CallstackKey{ .callstack = callstack_copy, .prob = p, }; state.sampled_flips.put(allocator, store_key, sampled_true) catch { allocator.free(callstack_copy); return CompileError.OutOfMemory; }; return sampled_true;}fn resolveFlipForCallstack( allocator: Allocator, callstack: []const i32, p: f64, state: *LazyKCState,) CompileError!FlipOutcome { if (state.cfg.sample_after_max_depth) { if (state.cfg.max_depth) |max| { if (state.depth > max) { const lookup_key = LazyKCState.CallstackKey{ .callstack = callstack, .prob = p, }; if (!state.var_of_callstack.contains(lookup_key)) { return .{ .sampled = try sampleFlipForCallstack(allocator, callstack, p, state) }; } } } } const addr = try state_module.currentAddressForCallstack(state, callstack, p); return .{ .addr = addr };}const CompileFlipContinuation = struct { pub fn cont( alloc: Allocator, p_val: *RuntimeValue, inner_pc: Bdd, s: *LazyKCState, ctx: anytype, ) CompileError!WorldsResult { _ = ctx; _ = inner_pc; const p = switch (p_val.data) { .native => |n| switch (n) { .float => |f| f, else => return programErrorWorlds(s), }, else => return programErrorWorlds(s), }; if (p < 0.0 or p > 1.0) { return programErrorWorlds(s); } if (@abs(p) < 1e-10) { const false_val = try RuntimeValue.initFalse(alloc); return pureMonad(alloc, false_val, s); } if (@abs(p - 1.0) < 1e-10) { const true_val = try RuntimeValue.initTrue(alloc); return pureMonad(alloc, true_val, s); } try state_module.pushCallstack(s, 1); defer state_module.popCallstack(s); if (s.cfg.sample_constraint) |constraint| { const addr = try state_module.currentAddress(s, p); const implies_true = try s.manager.bddImplies(constraint, addr); if (implies_true.isTrue()) { const true_val = try RuntimeValue.initTrue(alloc); return pureMonad(alloc, true_val, s); } const implies_false = try s.manager.bddImplies(constraint, addr.neg()); if (implies_false.isTrue()) { const false_val = try RuntimeValue.initFalse(alloc); return pureMonad(alloc, false_val, s); } const sampled_true = try sampleFlipForCallstack(alloc, s.callstack.items, p, s); if (sampled_true) { const true_val = try RuntimeValue.initTrue(alloc); return pureMonad(alloc, true_val, s); } else { const false_val = try RuntimeValue.initFalse(alloc); return pureMonad(alloc, false_val, s); } } if (s.cfg.sample_after_max_depth) { if (s.cfg.max_depth) |max| { if (s.depth > max) { const lookup_key = LazyKCState.CallstackKey{ .callstack = s.callstack.items, .prob = p, }; if (!s.var_of_callstack.contains(lookup_key)) { const sampled_true = try sampleFlipForCallstack( alloc, s.callstack.items, p, s, ); if (sampled_true) { const true_val = try RuntimeValue.initTrue(alloc); return pureMonad(alloc, true_val, s); } else { const false_val = try RuntimeValue.initFalse(alloc); return pureMonad(alloc, false_val, s); } } } } } const addr = try state_module.currentAddress(s, p); const true_val = try RuntimeValue.initTrue(alloc); const false_val = try RuntimeValue.initFalse(alloc); const worlds = try alloc.alloc(World, 2); worlds[0] = World{ .value = true_val, .guard = addr }; worlds[1] = World{ .value = false_val, .guard = addr.neg() }; return WorldsResult{ .worlds = worlds, .validity_guard = Bdd.TRUE, }; }};fn compileFlip( allocator: Allocator, expr: *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { const prob_result = try tracedCompileInner(expr.args[0], env, path_condition, state, 0); return bindMonad(allocator, prob_result, path_condition, state, CompileFlipContinuation, {});}fn collectFactorWeightWorlds( allocator: Allocator, out: *std.ArrayList(World), value: *RuntimeValue, guard: Bdd, path_condition: Bdd, state: *LazyKCState,) CompileError!void { if (state.stats.limit_reason != null) return; if (guard.isFalse()) return; switch (value.data) { .lazy_kc_thunk, .lazy_kc_thunk_union => { const inner_pc = if (state.cfg.disable_path_conditions) Bdd.TRUE else try state.manager.bddAnd(path_condition, guard); const result = try evaluateThunk(allocator, value, inner_pc, state); defer freeWorldsSlice(allocator, result.worlds); for (result.worlds) |world| { const combined_guard = try state.manager.bddAnd(guard, world.guard); try collectFactorWeightWorlds( allocator, out, world.value, combined_guard, path_condition, state, ); if (state.stats.limit_reason != null) return; } }, else => { try out.append(allocator, World{ .value = value, .guard = guard }); }, }}const WeightSymbolicResult = union(enum) { ok: []GuardedWeight, fail, invalid,};fn compileWeightSymbolic( allocator: Allocator, worlds: []const World,) CompileError!WeightSymbolicResult { var guards: std.ArrayList(GuardedWeight) = .empty; defer guards.deinit(allocator); for (worlds) |world| { if (world.guard.isFalse()) continue; if (world.value.isThunk()) return .fail; const weight = valueToFloat(world.value) orelse return .invalid; if (!std.math.isFinite(weight) or weight < 0.0) return .invalid; if (weight == 0.0) continue; try guards.append(allocator, .{ .guard = world.guard, .weight = weight, }); } const owned = try guards.toOwnedSlice(allocator); return .{ .ok = owned };}fn emptyFactorResult(validity_guard: Bdd) WorldsResult { return WorldsResult{ .worlds = &[_]World{}, .validity_guard = validity_guard, };}fn unitFactorResult(allocator: Allocator, guard: Bdd, validity_guard: Bdd) CompileError!WorldsResult { const unit_val = try RuntimeValue.initConstructed(allocator, "Unit", &[_]*RuntimeValue{}); const worlds = try allocator.alloc(World, 1); worlds[0] = World{ .value = unit_val, .guard = guard }; return WorldsResult{ .worlds = worlds, .validity_guard = validity_guard, };}fn finishFactorFromGuardList( allocator: Allocator, guards: []const GuardedWeight, validity_guard: Bdd, state: *LazyKCState,) CompileError!WorldsResult { var output: std.ArrayList(World) = .empty; defer output.deinit(allocator); const unit_val = try RuntimeValue.initConstructed(allocator, "Unit", &[_]*RuntimeValue{}); for (guards) |entry| { if (entry.guard.isFalse()) continue; if (entry.weight == 0.0) continue; if (@abs(entry.weight - 1.0) < 1e-12) { try output.append(allocator, World{ .value = unit_val, .guard = entry.guard }); continue; } const factor_var = try state.manager.newVar(true); try state.wmc_params.setWeight(state.manager.topVar(factor_var), 1.0, entry.weight); const combined_guard = try state.manager.bddAnd(entry.guard, factor_var); try output.append(allocator, World{ .value = unit_val, .guard = combined_guard }); } if (output.items.len == 0) { return emptyFactorResult(validity_guard); } const worlds = try output.toOwnedSlice(allocator); return WorldsResult{ .worlds = worlds, .validity_guard = validity_guard, };}fn deferFactorGuards( allocator: Allocator, guards: []const GuardedWeight, path_condition: Bdd, validity_guard: Bdd, state: *LazyKCState,) CompileError!WorldsResult { var restricted: std.ArrayList(GuardedWeight) = .empty; defer restricted.deinit(allocator); try restricted.ensureTotalCapacity(allocator, guards.len); for (guards) |entry| { if (entry.weight == 0.0) continue; if (entry.guard.isFalse()) continue; const combined_guard = if (state.cfg.disable_path_conditions) entry.guard else state.manager.bddAnd(entry.guard, path_condition) catch return error.OutOfMemory; if (combined_guard.isFalse()) continue; try restricted.append(allocator, .{ .guard = combined_guard, .weight = entry.weight, }); } const owned = try restricted.toOwnedSlice(allocator); if (owned.len == 0) { allocator.free(owned); return emptyFactorResult(validity_guard); } try state.deferred_weights.append(allocator, .{ .guards = owned }); return unitFactorResult(allocator, Bdd.TRUE, validity_guard);}fn finishFactorWeightDdError( err: weight_dd.ApplyError, allocator: Allocator, guards: []const GuardedWeight, path_condition: Bdd, validity_guard: Bdd, state: *LazyKCState,) CompileError!WorldsResult { return switch (err) { error.OutOfMemory => error.OutOfMemory, error.NodeLimitExceeded => deferFactorGuards(allocator, guards, path_condition, validity_guard, state), error.NaNWeight, error.NonFiniteWeight => programErrorWorlds(state), };}fn finishFactorWithWeightDD( allocator: Allocator, guards: []const GuardedWeight, path_condition: Bdd, validity_guard: Bdd, state: *LazyKCState,) CompileError!WorldsResult { const node_limit = if (state.cfg.inference_mode == .lpsmc) 0 else state.cfg.weight_dd_max_nodes; const current_pc = if (state.cfg.disable_path_conditions) Bdd.TRUE else path_condition; const refine_start = time.nanoTimestamp(); defer { const elapsed = time.nanoTimestamp() - refine_start; state.stats.refinement_time_ns += @intCast(@max(0, elapsed)); state.stats.refinement_count += 1; } const weight_root = state.weight_dd.refineWeight(guards, node_limit) catch |err| { return finishFactorWeightDdError(err, allocator, guards, current_pc, validity_guard, state); }; if (node_limit != 0) { _ = state.weight_dd.nodeCountLimited(weight_root, node_limit) catch |err| { return finishFactorWeightDdError(err, allocator, guards, current_pc, validity_guard, state); }; } const gated_weight = if (node_limit != 0) state.weight_dd.iteLimited(current_pc, weight_root, state.weight_dd_one, node_limit) catch |err| { return finishFactorWeightDdError(err, allocator, guards, current_pc, validity_guard, state); } else state.weight_dd.ite(current_pc, weight_root, state.weight_dd_one) catch |err| { return finishFactorWeightDdError(err, allocator, guards, current_pc, validity_guard, state); }; const new_root = if (node_limit != 0) state.weight_dd.mulLimited(state.weight_dd_root, gated_weight, node_limit) catch |err| { return finishFactorWeightDdError(err, allocator, guards, current_pc, validity_guard, state); } else state.weight_dd.mul(state.weight_dd_root, gated_weight) catch |err| { return finishFactorWeightDdError(err, allocator, guards, current_pc, validity_guard, state); }; state.weight_dd_root = new_root; return unitFactorResult(allocator, Bdd.TRUE, validity_guard);}fn finishFactorFromGuards( allocator: Allocator, guards: []const GuardedWeight, path_condition: Bdd, validity_guard: Bdd, state: *LazyKCState,) CompileError!WorldsResult { if (guards.len == 0) { return emptyFactorResult(validity_guard); } const use_weight_dd = state.cfg.factor_max_branches != 0 and guards.len > state.cfg.factor_max_branches; if (guards.len > state.stats.max_factor_guard_branches) { state.stats.max_factor_guard_branches = guards.len; } if (!use_weight_dd) { return finishFactorFromGuardList(allocator, guards, validity_guard, state); } return finishFactorWithWeightDD(allocator, guards, path_condition, validity_guard, state);}fn compileFactor( allocator: Allocator, expr: *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState,) CompileError!WorldsResult { const weight_result = try tracedCompileInner(expr.args[0], env, path_condition, state, 0); defer freeWorldsSlice(allocator, weight_result.worlds); const validity_guard = weight_result.validity_guard; if (state.stats.limit_reason != null) { return inferenceErrorWorlds(state); } if (weight_result.worlds.len == 0) { return WorldsResult{ .worlds = &[_]World{}, .validity_guard = validity_guard, }; } const symbolic = try compileWeightSymbolic(allocator, weight_result.worlds); switch (symbolic) { .ok => |guards| { defer allocator.free(guards); return finishFactorFromGuards(allocator, guards, path_condition, validity_guard, state); }, .invalid => return programErrorWorlds(state), .fail => {}, } const fallback_limit = state.cfg.factor_max_branches; if (fallback_limit != 0 and weight_result.worlds.len > fallback_limit) { state.stats.limit_reason = .factor_weight_too_complex; return inferenceErrorWorlds(state); } var weight_worlds: std.ArrayList(World) = .empty; defer weight_worlds.deinit(allocator); for (weight_result.worlds) |world| { try collectFactorWeightWorlds( allocator, &weight_worlds, world.value, world.guard, path_condition, state, ); if (state.stats.limit_reason != null) { return inferenceErrorWorlds(state); } } if (fallback_limit != 0 and weight_worlds.items.len > fallback_limit) { state.stats.limit_reason = .factor_weight_too_complex; return inferenceErrorWorlds(state); } const fallback = try compileWeightSymbolic(allocator, weight_worlds.items); switch (fallback) { .ok => |guards| { defer allocator.free(guards); return finishFactorFromGuards(allocator, guards, path_condition, validity_guard, state); }, .invalid => return programErrorWorlds(state), .fail => { state.stats.limit_reason = .factor_weight_too_complex; return inferenceErrorWorlds(state); }, }}fn compileConstNative( allocator: Allocator, native: pexpr.NativeValue, state: *LazyKCState,) !WorldsResult { const data: runtime.NativeValueData = switch (native) { .int => |i| .{ .int = i }, .float => |f| .{ .float = f }, .symbol => |s| .{ .symbol = s }, .bool_val => |b| .{ .bool_val = b }, }; const val = try RuntimeValue.initNative(allocator, data); return pureMonad(allocator, val, state);}const NativeEqSecondContinuation = struct { pub fn cont( inner_alloc: Allocator, arg2: *RuntimeValue, _: Bdd, inner_s: *LazyKCState, inner_ctx: anytype, ) CompileError!WorldsResult { const a1 = inner_ctx.arg1; const eq = if (a1.data == .native and arg2.data == .native) a1.data.native.eql(arg2.data.native) else false; const result_val = if (eq) try RuntimeValue.initTrue(inner_alloc) else try RuntimeValue.initFalse(inner_alloc); return pureMonad(inner_alloc, result_val, inner_s); }};const NativeEqFirstContinuation = struct { pub fn cont( alloc: Allocator, arg1: *RuntimeValue, inner_pc: Bdd, s: *LazyKCState, ctx: anytype, ) CompileError!WorldsResult { const result2 = try tracedCompileInner(ctx.expr.args[1], ctx.env, inner_pc, s, 1); return bindMonad( alloc, result2, inner_pc, s, NativeEqSecondContinuation, .{ .arg1 = arg1 }, ); }};fn compileNativeEq( allocator: Allocator, expr: *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { const result1 = try tracedCompileInner(expr.args[0], env, path_condition, state, 0); return bindMonad( allocator, result1, path_condition, state, NativeEqFirstContinuation, .{ .expr = expr, .env = env }, );}const FloatBinopType = enum { div, mul, add, sub };fn valueToFloat(val: *RuntimeValue) ?f64 { return switch (val.data) { .native => |n| switch (n) { .float => |f| f, .int => |i| @as(f64, @floatFromInt(i)), else => null, }, .constructed => { if (val.maybeNat()) |nat_val| { return @as(f64, @floatFromInt(nat_val)); } return null; }, else => null, };}const FloatBinopSecondContinuation = struct { pub fn cont( inner_alloc: Allocator, arg2: *RuntimeValue, _: Bdd, inner_s: *LazyKCState, inner_ctx: anytype, ) CompileError!WorldsResult { const a1 = inner_ctx.arg1; const operation = inner_ctx.op; const v1 = valueToFloat(a1) orelse return programErrorWorlds(inner_s); const v2 = valueToFloat(arg2) orelse return programErrorWorlds(inner_s); const result_f = switch (operation) { .div => v1 / v2, .mul => v1 * v2, .add => v1 + v2, .sub => v1 - v2, }; const val = try RuntimeValue.initNative(inner_alloc, .{ .float = result_f }); return pureMonad(inner_alloc, val, inner_s); }};const FloatBinopFirstContinuation = struct { pub fn cont( alloc: Allocator, arg1: *RuntimeValue, inner_pc: Bdd, s: *LazyKCState, ctx: anytype, ) CompileError!WorldsResult { const result2 = try tracedCompileInner(ctx.expr.args[1], ctx.env, inner_pc, s, 1); return bindMonad( alloc, result2, inner_pc, s, FloatBinopSecondContinuation, .{ .arg1 = arg1, .op = ctx.op }, ); }};fn compileFloatBinop( allocator: Allocator, op: FloatBinopType, expr: *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { const result1 = try tracedCompileInner(expr.args[0], env, path_condition, state, 0); return bindMonad( allocator, result1, path_condition, state, FloatBinopFirstContinuation, .{ .expr = expr, .env = env, .op = op }, );}const GetArgsContinuation = struct { pub fn cont( alloc: Allocator, val: *RuntimeValue, _: Bdd, s: *LazyKCState, ctx: anytype, ) !WorldsResult { _ = ctx; if (val.data != .constructed) { return programErrorWorlds(s); } const c = val.data.constructed; var list = try RuntimeValue.initConstructed(alloc, "Nil", &[_]*RuntimeValue{}); var i = c.args.len; while (i > 0) { i -= 1; const args_slice = try alloc.alloc(*RuntimeValue, 2); args_slice[0] = c.args[i]; args_slice[1] = list; list = try RuntimeValue.initConstructed(alloc, "Cons", args_slice); } return pureMonad(alloc, list, s); }};fn compileGetArgs( allocator: Allocator, expr: *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { const result = try tracedCompileInner(expr.args[0], env, path_condition, state, 0); return bindMonad(allocator, result, path_condition, state, GetArgsContinuation, {});}const GetConstructorContinuation = struct { pub fn cont( alloc: Allocator, val: *RuntimeValue, _: Bdd, s: *LazyKCState, ctx: anytype, ) !WorldsResult { _ = ctx; if (val.data != .constructed) { return programErrorWorlds(s); } const c = val.data.constructed; const sym_val = try RuntimeValue.initNative(alloc, .{ .symbol = c.constructor }); return pureMonad(alloc, sym_val, s); }};fn compileGetConstructor( allocator: Allocator, expr: *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { const result = try tracedCompileInner(expr.args[0], env, path_condition, state, 0); return bindMonad(allocator, result, path_condition, state, GetConstructorContinuation, {});}fn logAddExp(a: f64, b: f64) f64 { if (a == -std.math.inf(f64)) return b; if (b == -std.math.inf(f64)) return a; if (a > b) { return a + @log(1.0 + @exp(b - a)); } else { return b + @log(1.0 + @exp(a - b)); }}const PBoolContinuation = struct { pub fn cont( alloc: Allocator, cond_val: *RuntimeValue, _: Bdd, s: *LazyKCState, ctx: anytype, ) CompileError!WorldsResult { const p_t = ctx; if (cond_val.data != .constructed) { return programErrorWorlds(s); } const c = cond_val.data.constructed; const prob_val = try RuntimeValue.initNative(alloc, .{ .float = p_t }); const args = try alloc.alloc(*RuntimeValue, 2); args[0] = prob_val; args[1] = cond_val; if (std.mem.eql(u8, c.constructor, "True") or std.mem.eql(u8, c.constructor, "False")) { const pbool_val = try RuntimeValue.initConstructed(alloc, "PBool", args); return pureMonad(alloc, pbool_val, s); } return programErrorWorlds(s); }};fn compilePBool( allocator: Allocator, expr: *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { const cond_result = try tracedCompileInner(expr.args[0], env, path_condition, state, 0); var log_p_true: f64 = -std.math.inf(f64); var log_p_false: f64 = -std.math.inf(f64); for (cond_result.worlds) |world| { if (world.value.data != .constructed) continue; const c = world.value.data.constructed; const prob = wmcForState(state, world.guard) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.NodeLimitExceeded => { state.stats.limit_reason = .factor_weight_too_complex; freeWorldsSlice(allocator, cond_result.worlds); return inferenceErrorWorlds(state); }, }; const log_prob = if (prob > 0) @log(prob) else -std.math.inf(f64); if (std.mem.eql(u8, c.constructor, "True")) { log_p_true = logAddExp(log_p_true, log_prob); } else if (std.mem.eql(u8, c.constructor, "False")) { log_p_false = logAddExp(log_p_false, log_prob); } } const log_total = logAddExp(log_p_true, log_p_false); const prob_true: f64 = if (log_total > -std.math.inf(f64)) @exp(log_p_true - log_total) else 0.0; return bindMonad(allocator, cond_result, path_condition, state, PBoolContinuation, prob_true);}fn compileMkInt( allocator: Allocator, expr: *PExpr, state: *LazyKCState,) !WorldsResult { const bitwidth_expr = expr.args[0]; const val_expr = expr.args[1]; const bitwidth: u6 = switch (bitwidth_expr.head) { .const_native => |n| switch (n) { .int => |i| @intCast(i), else => return programErrorWorlds(state), }, else => return programErrorWorlds(state), }; const value: u64 = switch (val_expr.head) { .const_native => |n| switch (n) { .int => |i| @bitCast(i), else => return programErrorWorlds(state), }, else => return programErrorWorlds(state), }; const bits = try allocator.alloc(Bdd, bitwidth); for (0..bitwidth) |i| { const bit_set = (value >> @intCast(i)) & 1 == 1; bits[i] = if (bit_set) Bdd.TRUE else Bdd.FALSE; } const int_dist = runtime.IntDist.init(bits); const val = try RuntimeValue.initNative(allocator, .{ .int_dist = int_dist }); return pureMonad(allocator, val, state);}const MkIntWeightedContinuation = struct { pub fn cont( alloc: Allocator, list_val: *RuntimeValue, inner_pc: Bdd, s: *LazyKCState, ctx: anytype, ) CompileError!WorldsResult { _ = inner_pc; const bw = ctx.bitwidth; const pairs = try extractListForcingThunks(alloc, list_val, s) orelse return programErrorWorlds(s); defer alloc.free(pairs); if (pairs.len == 0) { return programErrorWorlds(s); } var values = try alloc.alloc(u64, pairs.len); defer alloc.free(values); var probs = try alloc.alloc(f64, pairs.len); defer alloc.free(probs); for (pairs, 0..) |pair, i| { const forced_pair = try forceValue(alloc, pair, s); const p = forced_pair.maybePair() orelse return programErrorWorlds(s); const forced_fst = try forceValue(alloc, p.fst, s); values[i] = switch (forced_fst.data) { .native => |n| switch (n) { .int => |iv| @bitCast(iv), else => return programErrorWorlds(s), }, else => return programErrorWorlds(s), }; const forced_snd = try forceValue(alloc, p.snd, s); probs[i] = switch (forced_snd.data) { .native => |n| switch (n) { .float => |f| f, else => return programErrorWorlds(s), }, else => return programErrorWorlds(s), }; } return createWeightedIntDist(alloc, bw, values, probs, s); }};fn compileMkIntWeighted( allocator: Allocator, expr: *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { const bitwidth_expr = expr.args[0]; const bitwidth: u6 = switch (bitwidth_expr.head) { .const_native => |n| switch (n) { .int => |i| @intCast(i), else => return programErrorWorlds(state), }, else => return programErrorWorlds(state), }; const list_result = try tracedCompileInner(expr.args[1], env, path_condition, state, 0); return bindMonad( allocator, list_result, path_condition, state, MkIntWeightedContinuation, .{ .bitwidth = bitwidth }, );}pub fn forceValue( allocator: Allocator, val: *RuntimeValue, state: *LazyKCState,) !*RuntimeValue { switch (val.data) { .lazy_kc_thunk, .lazy_kc_thunk_union => { const result = try evaluateThunk(allocator, val, Bdd.TRUE, state); defer freeWorldsSlice(allocator, result.worlds); if (result.worlds.len == 0) return error.PluckError; return result.worlds[0].value; }, else => return val, }}pub fn forceValueDeterministic( allocator: Allocator, val: *RuntimeValue, state: *LazyKCState,) !*RuntimeValue { switch (val.data) { .lazy_kc_thunk, .lazy_kc_thunk_union => { const result = try evaluateThunk(allocator, val, Bdd.TRUE, state); defer freeWorldsSlice(allocator, result.worlds); if (result.worlds.len != 1) return error.PluckError; return result.worlds[0].value; }, else => return val, }}fn extractListForcingThunks( allocator: Allocator, val: *RuntimeValue, state: *LazyKCState,) !?[]*RuntimeValue { var items: std.ArrayList(*RuntimeValue) = .empty; errdefer items.deinit(allocator); var current = val; while (true) { current = try forceValue(allocator, current, state); switch (current.data) { .constructed => |c| { if (std.mem.eql(u8, c.constructor, "Nil") and c.args.len == 0) { const slice = try items.toOwnedSlice(allocator); return slice; } else if (std.mem.eql(u8, c.constructor, "Cons") and c.args.len == 2) { const head = try forceValue(allocator, c.args[0], state); try items.append(allocator, head); current = c.args[1]; } else { items.deinit(allocator); return null; } }, else => { items.deinit(allocator); return null; }, } }}pub fn extractNatForcingThunks( allocator: Allocator, val: *RuntimeValue, max_value: i64, state: *LazyKCState,) !?i64 { var current = val; var count: i64 = 0; while (true) { current = try forceValueDeterministic(allocator, current, state); switch (current.data) { .constructed => |c| { if (std.mem.eql(u8, c.constructor, "O") and c.args.len == 0) { return count; } else if (std.mem.eql(u8, c.constructor, "S") and c.args.len == 1) { count += 1; if (count > max_value) { return null; } current = c.args[0]; } else { return null; } }, else => return null, } }}fn createWeightedIntDist( allocator: Allocator, bitwidth: u6, values: []const u64, probs: []const f64, state: *LazyKCState,) !WorldsResult { const n = values.len; var total_prob: f64 = 0; for (probs) |p| { if (p < 0 or p != p or std.math.isInf(p)) { return programErrorWorlds(state); } total_prob += p; } if (total_prob <= 0 or @abs(total_prob - 1.0) > 1e-6) { if (total_prob <= 0) { return programErrorWorlds(state); } } const norm_probs = try allocator.alloc(f64, n); defer allocator.free(norm_probs); for (probs, 0..) |p, i| { norm_probs[i] = p / total_prob; } if (n == 1) { const bits = try allocator.alloc(Bdd, bitwidth); for (0..bitwidth) |i| { const bit_set = (values[0] >> @intCast(i)) & 1 == 1; bits[i] = if (bit_set) Bdd.TRUE else Bdd.FALSE; } const int_dist = runtime.IntDist.init(bits); const val = try RuntimeValue.initNative(allocator, .{ .int_dist = int_dist }); return pureMonad(allocator, val, state); } const selector_guards = try allocator.alloc(Bdd, n); defer allocator.free(selector_guards); var remaining_prob: f64 = 1.0; var not_selected_yet = Bdd.TRUE; for (0..n) |i| { try state_module.pushCallstack(state, @intCast(i + 100)); defer state_module.popCallstack(state); if (i == n - 1) { selector_guards[i] = not_selected_yet; } else { const cond_prob: f64 = if (remaining_prob > 1e-10) norm_probs[i] / remaining_prob else 0.5; const flip_var = try state_module.currentAddress(state, cond_prob); selector_guards[i] = try state.manager.bddAnd(not_selected_yet, flip_var); not_selected_yet = try state.manager.bddAnd(not_selected_yet, flip_var.neg()); remaining_prob -= norm_probs[i]; } } const bits = try allocator.alloc(Bdd, bitwidth); for (0..bitwidth) |bit_pos| { var bit_bdd = Bdd.FALSE; for (values, 0..) |value, val_idx| { const bit_set = (value >> @intCast(bit_pos)) & 1 == 1; if (bit_set) { bit_bdd = try state.manager.bddOr(bit_bdd, selector_guards[val_idx]); } } bits[bit_pos] = bit_bdd; } const int_dist = runtime.IntDist.init(bits); const val = try RuntimeValue.initNative(allocator, .{ .int_dist = int_dist }); return pureMonad(allocator, val, state);}const IntDistEqSecondContinuation = struct { pub fn cont( inner_alloc: Allocator, second_val: *RuntimeValue, _: Bdd, inner_state: *LazyKCState, inner_ctx: anytype, ) CompileError!WorldsResult { const second_int_dist = switch (second_val.data) { .native => |n| switch (n) { .int_dist => |d| d, else => return programErrorWorlds(inner_state), }, else => return programErrorWorlds(inner_state), }; const eq_bdd = try inner_ctx.first_dist.eql(second_int_dist, inner_state.manager); const true_val = try RuntimeValue.initTrue(inner_alloc); const false_val = try RuntimeValue.initFalse(inner_alloc); return ifThenElseMonad(inner_alloc, true_val, false_val, eq_bdd, inner_state); }};const IntDistEqFirstContinuation = struct { pub fn cont( alloc: Allocator, first_val: *RuntimeValue, inner_pc: Bdd, s: *LazyKCState, ctx: anytype, ) CompileError!WorldsResult { const first_int_dist = switch (first_val.data) { .native => |n| switch (n) { .int_dist => |d| d, else => return programErrorWorlds(s), }, else => return programErrorWorlds(s), }; const second_result = try tracedCompileInner(ctx.expr.args[1], ctx.env, inner_pc, s, 1); return bindMonad( alloc, second_result, inner_pc, s, IntDistEqSecondContinuation, .{ .first_dist = first_int_dist }, ); }};fn compileIntDistEq( allocator: Allocator, expr: *PExpr, env: Env, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { const first_result = try tracedCompileInner(expr.args[0], env, path_condition, state, 0); return bindMonad( allocator, first_result, path_condition, state, IntDistEqFirstContinuation, .{ .expr = expr, .env = env }, );}fn makePair(allocator: Allocator, key: *RuntimeValue, value: *RuntimeValue) !*RuntimeValue { const args = try allocator.alloc(*RuntimeValue, 2); args[0] = key; args[1] = value; return RuntimeValue.initConstructed(allocator, "Pair", args);}fn makeOptionalInt(allocator: Allocator, maybe_val: ?u64) !*RuntimeValue { if (maybe_val) |val| { const inner = try RuntimeValue.initNative(allocator, .{ .int = @intCast(val) }); const args = try allocator.alloc(*RuntimeValue, 1); args[0] = inner; return RuntimeValue.initConstructed(allocator, "Some", args); } else { return RuntimeValue.initConstructed(allocator, "None", &[_]*RuntimeValue{}); }}fn makeOptionalFloat(allocator: Allocator, maybe_val: ?f64) !*RuntimeValue { if (maybe_val) |val| { const inner = try RuntimeValue.initNative(allocator, .{ .float = val }); const args = try allocator.alloc(*RuntimeValue, 1); args[0] = inner; return RuntimeValue.initConstructed(allocator, "Some", args); } else { return RuntimeValue.initConstructed(allocator, "None", &[_]*RuntimeValue{}); }}const ConfigPrepender = struct { fn prepend( alloc: Allocator, l: *RuntimeValue, key_str: []const u8, val: *RuntimeValue, ) !*RuntimeValue { const key = try RuntimeValue.initNative(alloc, .{ .symbol = key_str }); const pair = try makePair(alloc, key, val); const cons_args = try alloc.alloc(*RuntimeValue, 2); cons_args[0] = pair; cons_args[1] = l; return RuntimeValue.initConstructed(alloc, "Cons", cons_args); }};fn compileGetConfig( allocator: Allocator, state: *LazyKCState,) !WorldsResult { const cfg = state.cfg; var list = try RuntimeValue.initConstructed(allocator, "Nil", &[_]*RuntimeValue{}); const parallel_wmc_val = if (cfg.parallel_wmc) try RuntimeValue.initTrue(allocator) else try RuntimeValue.initFalse(allocator); list = try ConfigPrepender.prepend(allocator, list, "parallel_wmc", parallel_wmc_val); const use_strict_order_val = if (cfg.use_strict_order) try RuntimeValue.initTrue(allocator) else try RuntimeValue.initFalse(allocator); list = try ConfigPrepender.prepend(allocator, list, "use_strict_order", use_strict_order_val); const use_reverse_order_val = if (cfg.use_reverse_order) try RuntimeValue.initTrue(allocator) else try RuntimeValue.initFalse(allocator); list = try ConfigPrepender.prepend(allocator, list, "use_reverse_order", use_reverse_order_val); const ite_limit_val = try makeOptionalInt(allocator, cfg.ite_limit); list = try ConfigPrepender.prepend(allocator, list, "ite_limit", ite_limit_val); const time_limit_val = try makeOptionalFloat(allocator, cfg.time_limit); list = try ConfigPrepender.prepend(allocator, list, "time_limit", time_limit_val); const sample_after_max_depth_val = if (cfg.sample_after_max_depth) try RuntimeValue.initTrue(allocator) else try RuntimeValue.initFalse(allocator); list = try ConfigPrepender.prepend( allocator, list, "sample_after_max_depth", sample_after_max_depth_val, ); const max_depth_u64: ?u64 = if (cfg.max_depth) |d| @as(u64, d) else null; const max_depth_val = try makeOptionalInt(allocator, max_depth_u64); list = try ConfigPrepender.prepend(allocator, list, "max_depth", max_depth_val); return pureMonad(allocator, list, state);}pub fn makeThunk( allocator: Allocator, expr: *PExpr, env: Env, strict_order_index: i32, state: *LazyKCState,) !*LazyKCThunk { if (expr.head == .var_ref) { if (env.get(expr.head.var_ref.name)) |v| { if (v.data == .lazy_kc_thunk) { state.stats.thunk_reuse_hits += 1; return v.data.lazy_kc_thunk; } } } state.stats.thunk_reuse_misses += 1; const thunk = try LazyKCThunk.init( allocator, expr, env, strict_order_index, state.callstack.items, ); if (state.registry) |registry| { try registry.registerWithContext(thunk, expr, state.callstack.items, state.current_def_name); } return thunk;}pub fn evaluateThunk( allocator: Allocator, val: *RuntimeValue, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { switch (val.data) { .lazy_kc_thunk => |thunk| return evaluateLazyKCThunk(allocator, thunk, path_condition, state), .lazy_kc_thunk_union => |union_thunk| return evaluateThunkUnion(allocator, union_thunk, path_condition, state), else => return pureMonad(allocator, val, state), }}const ThunkCacheContinuation = struct { pub fn cont( alloc: Allocator, hit_cache: *RuntimeValue, inner_pc: Bdd, s: *LazyKCState, ctx: anytype, ) CompileError!WorldsResult { const t = ctx.thunk; const cached_result = ctx.cached; const is_true = switch (hit_cache.data) { .constructed => |c| std.mem.eql(u8, c.constructor, "True"), else => false, }; if (is_true) { const worlds_copy = try alloc.alloc(World, cached_result.worlds.len); @memcpy(worlds_copy, cached_result.worlds); return WorldsResult{ .worlds = worlds_copy, .validity_guard = cached_result.validity_guard, }; } else { return evaluateThunkNoCache(alloc, t, inner_pc, s); } }};fn evaluateLazyKCThunk( allocator: Allocator, thunk: *LazyKCThunk, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { state.stats.thunk_evaluations += 1; if (path_condition.isFalse()) { return falsePathConditionWorlds(state); } if (state.cfg.sample_constraint != null) { return evaluateThunkNoCache(allocator, thunk, path_condition, state); } for (thunk.cache.items) |cached| { const cache_valid_for_path = try state.manager.bddImplies(path_condition, cached.validity_guard); if (cache_valid_for_path.isTrue()) { state.stats.thunk_cache_hits += 1; const worlds_copy = try allocator.alloc(World, cached.worlds.len); @memcpy(worlds_copy, cached.worlds); return WorldsResult{ .worlds = worlds_copy, .validity_guard = cached.validity_guard, }; } } if (thunk.cache.items.len == 0) { const result = try evaluateThunkNoCache(allocator, thunk, path_condition, state); const cache_copy = try allocator.alloc(World, result.worlds.len); @memcpy(cache_copy, result.worlds); try thunk.cache.append(allocator, GuardedWorlds{ .worlds = cache_copy, .validity_guard = result.validity_guard, }); return result; } const cached = thunk.cache.items[0]; const cached_validity_guard = cached.validity_guard; const true_val = try RuntimeValue.initTrue(allocator); defer true_val.deinit(allocator); const false_val = try RuntimeValue.initFalse(allocator); defer false_val.deinit(allocator); const hit_cache_worlds = try ifThenElseMonad(allocator, true_val, false_val, cached_validity_guard, state); const extended_pc = try state.manager.bddOr(path_condition, cached_validity_guard); const result = try bindMonad( allocator, hit_cache_worlds, extended_pc, state, ThunkCacheContinuation, .{ .thunk = thunk, .cached = cached }, ); freeWorldsSlice(allocator, thunk.cache.items[0].worlds); const cache_copy = try allocator.alloc(World, result.worlds.len); @memcpy(cache_copy, result.worlds); thunk.cache.items[0] = GuardedWorlds{ .worlds = cache_copy, .validity_guard = result.validity_guard, }; return result;}fn evaluateThunkNoCache( allocator: Allocator, thunk: *LazyKCThunk, path_condition: Bdd, state: *LazyKCState,) CompileError!WorldsResult { const old_callstack = try state.allocator.dupe(i32, state.callstack.items); defer state.allocator.free(old_callstack); state.callstack.clearRetainingCapacity(); try state.callstack.appendSlice(state.allocator, thunk.callstack); defer { state.callstack.clearRetainingCapacity(); state.callstack.appendSlice(state.allocator, old_callstack) catch {}; } return switch (thunk.expr) { .pexpr => |e| tracedCompileInner(e, thunk.env, path_condition, state, thunk.strict_order_index), .thunk => |inner| evaluateLazyKCThunk(allocator, inner, path_condition, state), };}fn evaluateThunkUnion( allocator: Allocator, union_thunk: *LazyKCThunkUnion, path_condition: Bdd, state: *LazyKCState,) !WorldsResult { if (path_condition.isFalse()) { return falsePathConditionWorlds(state); } var all_worlds: std.ArrayList(World) = .empty; defer all_worlds.deinit(allocator); var overall_validity_guard = Bdd.TRUE; for (union_thunk.thunks) |tg| { const inner_pc = try state.manager.bddAnd(path_condition, tg.guard); if (inner_pc.isFalse()) { continue; } const result = try evaluateLazyKCThunk(allocator, tg.thunk, inner_pc, state); defer freeWorldsSlice(allocator, result.worlds); for (result.worlds) |world| { const combined_guard = try state.manager.bddAnd(world.guard, tg.guard); try all_worlds.append(allocator, World{ .value = world.value, .guard = combined_guard }); } if (!state.cfg.disable_validity_tracking) { const branch_validity_guard = try state.manager.bddImplies(tg.guard, result.validity_guard); overall_validity_guard = try state.manager.bddAnd(overall_validity_guard, branch_validity_guard); } } const worlds = try all_worlds.toOwnedSlice(allocator); return WorldsResult{ .worlds = worlds, .validity_guard = overall_validity_guard, };}pub const CompileResult = struct { weighted_results: []WeightedResult, stats: LazyKCStats, raw_worlds: ?[]World,};fn computeWmcDeferredSequential( allocator: Allocator, worlds: []const World, state: *LazyKCState,) DeferredWmcError![]WeightedResult { var weighted_results = try allocator.alloc(WeightedResult, worlds.len); errdefer allocator.free(weighted_results); var caches = DeferredWmcCaches.init(allocator); defer caches.deinit(); for (worlds, 0..) |world, i| { const prob = try wmcWithDeferredCached(state, world.guard, &caches); weighted_results[i] = WeightedResult{ .value = world.value, .probability = prob, }; } return weighted_results;}pub fn compile( allocator: Allocator, cache_allocator: Allocator, expr: *PExpr, definitions: *const Definitions, manager: *Manager, cfg: LazyKCConfig,) !CompileResult { const start_time = time.nanoTimestamp(); var state = try state_module.initChecked(allocator, manager, definitions, cfg); defer state_module.deinit(&state); state.query = expr; state_module.startTimeLimit(&state); defer state_module.stopTimeLimit(&state); const worlds_result = tracedCompileInner(expr, Env.empty, Bdd.TRUE, &state, 0) catch |err| switch (err) { error.PluckError => { state.stats.program_error = true; state.stats.time_ns = @intCast(@max(0, time.nanoTimestamp() - start_time)); return CompileResult{ .weighted_results = &[_]WeightedResult{}, .stats = state.stats, .raw_worlds = null, }; }, else => return err, }; if (state.stats.limit_reason != null) { freeWorldsSlice(allocator, worlds_result.worlds); state.stats.time_ns = @intCast(@max(0, time.nanoTimestamp() - start_time)); return CompileResult{ .weighted_results = &[_]WeightedResult{}, .stats = state.stats, .raw_worlds = null, }; } var worlds = worlds_result.worlds; if (cfg.full_dist) { const new_worlds = try inferFullDistribution(allocator, worlds, &state); freeWorldsSlice(allocator, worlds); worlds = new_worlds; } if (try processIntDistWorlds(allocator, worlds, manager)) |int_worlds| { freeWorldsSlice(allocator, worlds); worlds = int_worlds; } const has_deferred = state.deferred_weights.items.len > 0; const use_parallel = !has_deferred and cfg.parallel_wmc and worlds.len >= cfg.parallel_wmc_threshold; const wmc_start = time.nanoTimestamp(); defer { const elapsed = time.nanoTimestamp() - wmc_start; state.stats.wmc_time_ns = @intCast(@max(0, elapsed)); } const weighted_results = if (has_deferred) computeWmcDeferredSequential(allocator, worlds, &state) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.NodeLimitExceeded => { state.stats.limit_reason = .factor_weight_too_complex; freeWorldsSlice(allocator, worlds); state.stats.time_ns = @intCast(@max(0, time.nanoTimestamp() - start_time)); return CompileResult{ .weighted_results = &[_]WeightedResult{}, .stats = state.stats, .raw_worlds = null, }; }, } else if (use_parallel) try computeWmcParallel( allocator, cache_allocator, worlds, &state.wmc_params, &state.weight_dd, state.weight_dd_root, cfg.parallel_wmc_threads, ) else try computeWmcSequential( allocator, worlds, &state.wmc_params, &state.weight_dd, state.weight_dd_root, ); state_module.recordFinalBddSample(&state); state_module.recordManagerStats(&state); state.stats.time_ns = @intCast(@max(0, time.nanoTimestamp() - start_time)); return CompileResult{ .weighted_results = weighted_results, .stats = state.stats, .raw_worlds = worlds, };}pub fn processPosteriorQuery( allocator: Allocator, query_thunk: *RuntimeValue, evidence_thunk: *RuntimeValue, state: *LazyKCState,) ![]World { const evidence_result = try evaluateThunk(allocator, evidence_thunk, Bdd.TRUE, state); defer freeWorldsSlice(allocator, evidence_result.worlds); var query_worlds: std.ArrayList(World) = .empty; errdefer { for (query_worlds.items) |w| { _ = w; } query_worlds.deinit(allocator); } for (evidence_result.worlds) |evidence_world| { if (evidence_world.value.data == .constructed) { const c = evidence_world.value.data.constructed; if (std.mem.eql(u8, c.constructor, "True") and c.args.len == 0) { const query_result = try evaluateThunk( allocator, query_thunk, evidence_world.guard, state, ); defer freeWorldsSlice(allocator, query_result.worlds); for (query_result.worlds) |query_world| { const combined_guard = try state.manager.bddAnd(evidence_world.guard, query_world.guard); if (!combined_guard.isFalse()) { try query_worlds.append(allocator, World{ .value = query_world.value, .guard = combined_guard, }); } } } } } const initial_worlds = try query_worlds.toOwnedSlice(allocator); defer allocator.free(initial_worlds); return inferFullDistribution(allocator, initial_worlds, state);}pub fn processMarginalQuery( allocator: Allocator, query_thunk: *RuntimeValue, state: *LazyKCState,) ![]World { const query_result = try evaluateThunk(allocator, query_thunk, Bdd.TRUE, state); defer freeWorldsSlice(allocator, query_result.worlds); return inferFullDistribution(allocator, query_result.worlds, state);}pub fn inferFullDistribution( allocator: Allocator, initial_worlds: []World, state: *LazyKCState,) ![]World { var queue: std.ArrayList(World) = .empty; defer queue.deinit(allocator); var resolved: std.ArrayList(World) = .empty; defer resolved.deinit(allocator); var thunk_path: std.ArrayList(usize) = .empty; defer thunk_path.deinit(allocator); try queue.appendSlice(allocator, initial_worlds); while (queue.items.len > 0) { if (state.manager.limits.checkTimeLimit()) { state.stats.limit_reason = .time_limit; break; } const current = queue.pop().?; if (!try runtime.findFirstThunkInto(allocator, current.value, &thunk_path)) { try resolved.append(allocator, current); continue; } const path = thunk_path.items; const thunk_val = runtime.getValueAtPath(current.value, path) orelse { try resolved.append(allocator, current); continue; }; const sub_result = try evaluateThunk(allocator, thunk_val, current.guard, state); defer freeWorldsSlice(allocator, sub_result.worlds); for (sub_result.worlds) |sub_world| { const new_val = try runtime.replaceAtPath( allocator, current.value, path, sub_world.value, ); const combined_guard = try state.manager.bddAnd(current.guard, sub_world.guard); try queue.append(allocator, World{ .value = new_val, .guard = combined_guard }); } } return resolved.toOwnedSlice(allocator);}test "pure monad" { const allocator = std.testing.allocator; var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const val = try RuntimeValue.initTrue(allocator); defer val.deinit(allocator); const result = try pureMonad(allocator, val, &state); defer freeWorldsSlice(allocator, result.worlds); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); try std.testing.expect(result.worlds[0].guard.isTrue());}test "if then else monad" { const allocator = std.testing.allocator; var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const true_val = try RuntimeValue.initTrue(allocator); defer true_val.deinit(allocator); const false_val = try RuntimeValue.initFalse(allocator); defer false_val.deinit(allocator); const x = try manager.newVar(true); const result = try ifThenElseMonad(allocator, true_val, false_val, x, &state); defer freeWorldsSlice(allocator, result.worlds); try std.testing.expectEqual(@as(usize, 2), result.worlds.len);}test "joinMonad merges structurally identical values - pluck-rs-ui3" { const allocator = std.testing.allocator; var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const true_val1 = try RuntimeValue.initTrue(allocator); defer true_val1.deinit(allocator); const true_val2 = try RuntimeValue.initTrue(allocator); defer true_val2.deinit(allocator); try std.testing.expect(true_val1.eql(true_val2)); try std.testing.expect(true_val1 != true_val2); const x = try manager.newVar(true); const worlds1 = try allocator.alloc(World, 1); worlds1[0] = World{ .value = true_val1, .guard = Bdd.TRUE }; const worlds2 = try allocator.alloc(World, 1); worlds2[0] = World{ .value = true_val2, .guard = Bdd.TRUE }; const nested_worlds = [_]NestedWorld{ NestedWorld{ .result = WorldsResult{ .worlds = worlds1, .validity_guard = Bdd.TRUE, }, .guard = x, }, NestedWorld{ .result = WorldsResult{ .worlds = worlds2, .validity_guard = Bdd.TRUE, }, .guard = x.neg(), }, }; const result = try joinMonad(allocator, &nested_worlds, Bdd.TRUE, &state); defer allocator.free(result.worlds); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); try std.testing.expectEqual(Bdd.TRUE, result.worlds[0].guard);}test "joinMonad merges identical IntDist values - pluck-rs-ui3" { const allocator = std.testing.allocator; var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const bits1 = try allocator.alloc(Bdd, 8); for (bits1) |*bit| bit.* = Bdd.FALSE; bits1[1] = Bdd.TRUE; bits1[3] = Bdd.TRUE; bits1[5] = Bdd.TRUE; const bits2 = try allocator.alloc(Bdd, 8); for (bits2) |*bit| bit.* = Bdd.FALSE; bits2[1] = Bdd.TRUE; bits2[3] = Bdd.TRUE; bits2[5] = Bdd.TRUE; const intdist1 = try RuntimeValue.initNative(allocator, .{ .int_dist = runtime.IntDist.init(bits1) }); defer intdist1.deinit(allocator); const intdist2 = try RuntimeValue.initNative(allocator, .{ .int_dist = runtime.IntDist.init(bits2) }); defer intdist2.deinit(allocator); try std.testing.expect(intdist1.eql(intdist2)); try std.testing.expect(intdist1 != intdist2); const x = try manager.newVar(true); const worlds1 = try allocator.alloc(World, 1); worlds1[0] = World{ .value = intdist1, .guard = Bdd.TRUE }; const worlds2 = try allocator.alloc(World, 1); worlds2[0] = World{ .value = intdist2, .guard = Bdd.TRUE }; const nested_worlds = [_]NestedWorld{ NestedWorld{ .result = WorldsResult{ .worlds = worlds1, .validity_guard = Bdd.TRUE }, .guard = x, }, NestedWorld{ .result = WorldsResult{ .worlds = worlds2, .validity_guard = Bdd.TRUE }, .guard = x.neg(), }, }; const result = try joinMonad(allocator, &nested_worlds, Bdd.TRUE, &state); defer allocator.free(result.worlds); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); try std.testing.expectEqual(Bdd.TRUE, result.worlds[0].guard);}test "joinMonad collapses constructor worlds with thunk unions" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{ .use_thunk_unions = true }); defer state_module.deinit(&state); const expr1 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 1 } }); const expr2 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 2 } }); const thunk1 = try makeThunk(allocator, expr1, Env.empty, 0, &state); const thunk2 = try makeThunk(allocator, expr2, Env.empty, 1, &state); const thunk_val1 = try RuntimeValue.initLazyKCThunk(allocator, thunk1); const thunk_val2 = try RuntimeValue.initLazyKCThunk(allocator, thunk2); const args1 = try allocator.alloc(*RuntimeValue, 1); args1[0] = thunk_val1; const val1 = try RuntimeValue.initConstructed(allocator, "Box", args1); const args2 = try allocator.alloc(*RuntimeValue, 1); args2[0] = thunk_val2; const val2 = try RuntimeValue.initConstructed(allocator, "Box", args2); try std.testing.expect(!val1.eql(val2)); const x = try manager.newVar(true); const worlds1 = try allocator.alloc(World, 1); worlds1[0] = World{ .value = val1, .guard = Bdd.TRUE }; const worlds2 = try allocator.alloc(World, 1); worlds2[0] = World{ .value = val2, .guard = Bdd.TRUE }; const nested_worlds = [_]NestedWorld{ NestedWorld{ .result = WorldsResult{ .worlds = worlds1, .validity_guard = Bdd.TRUE }, .guard = x, }, NestedWorld{ .result = WorldsResult{ .worlds = worlds2, .validity_guard = Bdd.TRUE }, .guard = x.neg(), }, }; const result = try joinMonad(allocator, &nested_worlds, Bdd.TRUE, &state); defer allocator.free(result.worlds); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); try std.testing.expectEqual(Bdd.TRUE, result.worlds[0].guard); const combined = result.worlds[0].value; try std.testing.expect(combined.data == .constructed); const c = combined.data.constructed; try std.testing.expect(std.mem.eql(u8, c.constructor, "Box")); try std.testing.expectEqual(@as(usize, 1), c.args.len); try std.testing.expect(c.args[0].data == .lazy_kc_thunk_union); try std.testing.expectEqual(@as(usize, 2), c.args[0].data.lazy_kc_thunk_union.thunks.len);}fn contains_thunk(union_thunk: *LazyKCThunkUnion, thunk: *LazyKCThunk) bool { for (union_thunk.thunks) |tg| { if (tg.thunk == thunk) return true; } return false;}test "joinMonad collapses list constructors with thunk unions" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{ .use_thunk_unions = true }); defer state_module.deinit(&state); const head_expr1 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 1 } }); const head_expr2 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 2 } }); const tail_head1 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 3 } }); const tail_nil1 = try pexpr.PExpr.init(allocator, .{ .construct = .{ .constructor = "Nil" } }); const tail_expr1 = try pexpr.PExpr.initWithArgs( allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ tail_head1, tail_nil1 }, ); const tail_head2 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const tail_nil2 = try pexpr.PExpr.init(allocator, .{ .construct = .{ .constructor = "Nil" } }); const tail_expr2 = try pexpr.PExpr.initWithArgs( allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ tail_head2, tail_nil2 }, ); const head_thunk1 = try makeThunk(allocator, head_expr1, Env.empty, 0, &state); const head_thunk2 = try makeThunk(allocator, head_expr2, Env.empty, 1, &state); const tail_thunk1 = try makeThunk(allocator, tail_expr1, Env.empty, 2, &state); const tail_thunk2 = try makeThunk(allocator, tail_expr2, Env.empty, 3, &state); const head_val1 = try RuntimeValue.initLazyKCThunk(allocator, head_thunk1); const head_val2 = try RuntimeValue.initLazyKCThunk(allocator, head_thunk2); const tail_val1 = try RuntimeValue.initLazyKCThunk(allocator, tail_thunk1); const tail_val2 = try RuntimeValue.initLazyKCThunk(allocator, tail_thunk2); const args1 = try allocator.alloc(*RuntimeValue, 2); args1[0] = head_val1; args1[1] = tail_val1; const list1 = try RuntimeValue.initConstructed(allocator, "Cons", args1); const args2 = try allocator.alloc(*RuntimeValue, 2); args2[0] = head_val2; args2[1] = tail_val2; const list2 = try RuntimeValue.initConstructed(allocator, "Cons", args2); try std.testing.expect(!list1.eql(list2)); const x = try manager.newVar(true); const worlds1 = try allocator.alloc(World, 1); worlds1[0] = World{ .value = list1, .guard = Bdd.TRUE }; const worlds2 = try allocator.alloc(World, 1); worlds2[0] = World{ .value = list2, .guard = Bdd.TRUE }; const nested_worlds = [_]NestedWorld{ NestedWorld{ .result = WorldsResult{ .worlds = worlds1, .validity_guard = Bdd.TRUE }, .guard = x, }, NestedWorld{ .result = WorldsResult{ .worlds = worlds2, .validity_guard = Bdd.TRUE }, .guard = x.neg(), }, }; const result = try joinMonad(allocator, &nested_worlds, Bdd.TRUE, &state); defer allocator.free(result.worlds); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); try std.testing.expectEqual(Bdd.TRUE, result.worlds[0].guard); const combined = result.worlds[0].value; try std.testing.expect(combined.data == .constructed); const c = combined.data.constructed; try std.testing.expect(std.mem.eql(u8, c.constructor, "Cons")); try std.testing.expectEqual(@as(usize, 2), c.args.len); try std.testing.expect(c.args[0].data == .lazy_kc_thunk_union); try std.testing.expect(c.args[1].data == .lazy_kc_thunk_union); const head_union = c.args[0].data.lazy_kc_thunk_union; const tail_union = c.args[1].data.lazy_kc_thunk_union; try std.testing.expectEqual(@as(usize, 2), head_union.thunks.len); try std.testing.expectEqual(@as(usize, 2), tail_union.thunks.len); try std.testing.expect(contains_thunk(head_union, head_thunk1)); try std.testing.expect(contains_thunk(head_union, head_thunk2)); try std.testing.expect(contains_thunk(tail_union, tail_thunk1)); try std.testing.expect(contains_thunk(tail_union, tail_thunk2)); var formatted_buf = std.Io.Writer.Allocating.init(allocator); defer formatted_buf.deinit(); try combined.format("", .{}, &formatted_buf.writer); try std.testing.expect(std.mem.indexOf(u8, formatted_buf.written(), "LazyKCThunkUnion") != null);}test "joinMonad collapses nested constructors with thunk unions" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{ .use_thunk_unions = true }); defer state_module.deinit(&state); const list_head1 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 10 } }); const list_nil1 = try pexpr.PExpr.init(allocator, .{ .construct = .{ .constructor = "Nil" } }); const list_expr1 = try pexpr.PExpr.initWithArgs( allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ list_head1, list_nil1 }, ); const list_head2 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 20 } }); const list_nil2 = try pexpr.PExpr.init(allocator, .{ .construct = .{ .constructor = "Nil" } }); const list_expr2 = try pexpr.PExpr.initWithArgs( allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ list_head2, list_nil2 }, ); const pair_left1 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 1 } }); const pair_right1 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 2 } }); const pair_expr1 = try pexpr.PExpr.initWithArgs( allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair_left1, pair_right1 }, ); const pair_left2 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 3 } }); const pair_right2 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const pair_expr2 = try pexpr.PExpr.initWithArgs( allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair_left2, pair_right2 }, ); const left_thunk1 = try makeThunk(allocator, list_expr1, Env.empty, 0, &state); const left_thunk2 = try makeThunk(allocator, list_expr2, Env.empty, 1, &state); const right_thunk1 = try makeThunk(allocator, pair_expr1, Env.empty, 2, &state); const right_thunk2 = try makeThunk(allocator, pair_expr2, Env.empty, 3, &state); const left_val1 = try RuntimeValue.initLazyKCThunk(allocator, left_thunk1); const left_val2 = try RuntimeValue.initLazyKCThunk(allocator, left_thunk2); const right_val1 = try RuntimeValue.initLazyKCThunk(allocator, right_thunk1); const right_val2 = try RuntimeValue.initLazyKCThunk(allocator, right_thunk2); const node_args1 = try allocator.alloc(*RuntimeValue, 2); node_args1[0] = left_val1; node_args1[1] = right_val1; const node1 = try RuntimeValue.initConstructed(allocator, "Node", node_args1); const node_args2 = try allocator.alloc(*RuntimeValue, 2); node_args2[0] = left_val2; node_args2[1] = right_val2; const node2 = try RuntimeValue.initConstructed(allocator, "Node", node_args2); try std.testing.expect(!node1.eql(node2)); const x = try manager.newVar(true); const worlds1 = try allocator.alloc(World, 1); worlds1[0] = World{ .value = node1, .guard = Bdd.TRUE }; const worlds2 = try allocator.alloc(World, 1); worlds2[0] = World{ .value = node2, .guard = Bdd.TRUE }; const nested_worlds = [_]NestedWorld{ NestedWorld{ .result = WorldsResult{ .worlds = worlds1, .validity_guard = Bdd.TRUE }, .guard = x, }, NestedWorld{ .result = WorldsResult{ .worlds = worlds2, .validity_guard = Bdd.TRUE }, .guard = x.neg(), }, }; const result = try joinMonad(allocator, &nested_worlds, Bdd.TRUE, &state); defer allocator.free(result.worlds); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); try std.testing.expectEqual(Bdd.TRUE, result.worlds[0].guard); const combined = result.worlds[0].value; try std.testing.expect(combined.data == .constructed); const c = combined.data.constructed; try std.testing.expect(std.mem.eql(u8, c.constructor, "Node")); try std.testing.expectEqual(@as(usize, 2), c.args.len); try std.testing.expect(c.args[0].data == .lazy_kc_thunk_union); try std.testing.expect(c.args[1].data == .lazy_kc_thunk_union); try std.testing.expectEqual(@as(usize, 2), c.args[0].data.lazy_kc_thunk_union.thunks.len); try std.testing.expectEqual(@as(usize, 2), c.args[1].data.lazy_kc_thunk_union.thunks.len);}test "callstack ordering" { const a: []const i32 = &[_]i32{ 1, 2, 3 }; const b: []const i32 = &[_]i32{ 1, 2, 4 }; const c: []const i32 = &[_]i32{ 1, 2 }; try std.testing.expectEqual(std.math.Order.lt, compareCallstacks(a, b)); try std.testing.expectEqual(std.math.Order.gt, compareCallstacks(b, a)); try std.testing.expectEqual(std.math.Order.gt, compareCallstacks(a, c)); try std.testing.expectEqual(std.math.Order.eq, compareCallstacks(a, a));}fn make_callstack(allocator: Allocator, value: i32) ![]i32 { const slice = try allocator.alloc(i32, 1); slice[0] = value; return slice;}test "findInsertPosition respects use_reverse_order flag" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); { var state = try state_module.initChecked(allocator, &manager, &defs, .{ .use_reverse_order = false }); defer state_module.deinit(&state); const cs1 = try make_callstack(allocator, 1); const cs3 = try make_callstack(allocator, 3); const cs5 = try make_callstack(allocator, 5); try state.sorted_callstacks.append(allocator, .{ .callstack = cs1, .prob = 0.5 }); try state.sorted_callstacks.append(allocator, .{ .callstack = cs3, .prob = 0.5 }); try state.sorted_callstacks.append(allocator, .{ .callstack = cs5, .prob = 0.5 }); const cs2 = try make_callstack(allocator, 2); const pos2 = state_module.findInsertPosition(&state, .{ .callstack = cs2, .prob = 0.5 }); try std.testing.expectEqual(@as(usize, 1), pos2); const cs0 = try make_callstack(allocator, 0); const pos0 = state_module.findInsertPosition(&state, .{ .callstack = cs0, .prob = 0.5 }); try std.testing.expectEqual(@as(usize, 0), pos0); const cs6 = try make_callstack(allocator, 6); const pos6 = state_module.findInsertPosition(&state, .{ .callstack = cs6, .prob = 0.5 }); try std.testing.expectEqual(@as(usize, 3), pos6); } { var state = try state_module.initChecked(allocator, &manager, &defs, .{ .use_reverse_order = true }); defer state_module.deinit(&state); const cs5 = try make_callstack(allocator, 5); const cs3 = try make_callstack(allocator, 3); const cs1 = try make_callstack(allocator, 1); try state.sorted_callstacks.append(allocator, .{ .callstack = cs5, .prob = 0.5 }); try state.sorted_callstacks.append(allocator, .{ .callstack = cs3, .prob = 0.5 }); try state.sorted_callstacks.append(allocator, .{ .callstack = cs1, .prob = 0.5 }); const cs4 = try make_callstack(allocator, 4); const pos4 = state_module.findInsertPosition(&state, .{ .callstack = cs4, .prob = 0.5 }); try std.testing.expectEqual(@as(usize, 1), pos4); const cs6 = try make_callstack(allocator, 6); const pos6 = state_module.findInsertPosition(&state, .{ .callstack = cs6, .prob = 0.5 }); try std.testing.expectEqual(@as(usize, 0), pos6); const cs0 = try make_callstack(allocator, 0); const pos0 = state_module.findInsertPosition(&state, .{ .callstack = cs0, .prob = 0.5 }); try std.testing.expectEqual(@as(usize, 3), pos0); }}test "flip produces path-condition-independent guards" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const prob_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.5 } }); const flip_expr = try pexpr.PExpr.initWithArgs(allocator, .flip, &[_]*pexpr.PExpr{prob_expr}); const pc_var = try manager.newVar(true); const result = try compileInner(flip_expr, Env.empty, pc_var, &state); try std.testing.expectEqual(@as(usize, 2), result.worlds.len); const true_guard = result.worlds[0].guard; const false_guard = result.worlds[1].guard; try std.testing.expect(manager.eq(true_guard.neg(), false_guard)); const implies_pc = try manager.bddImplies(true_guard, pc_var); try std.testing.expect(!implies_pc.isTrue());}const BindIdentityContinuation = struct { pub fn cont( alloc: Allocator, value: *RuntimeValue, _: Bdd, state: *LazyKCState, _: anytype, ) CompileError!WorldsResult { return pureMonad(alloc, value, state); }};test "bindMonad frees input worlds slice" { const allocator = std.testing.allocator; var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const val1 = try RuntimeValue.initNative(allocator, .{ .int = 1 }); const val2 = try RuntimeValue.initNative(allocator, .{ .int = 2 }); defer val1.deinit(allocator); defer val2.deinit(allocator); const input_worlds = try allocator.alloc(World, 2); input_worlds[0] = World{ .value = val1, .guard = Bdd.TRUE }; input_worlds[1] = World{ .value = val2, .guard = Bdd.TRUE }; const input = WorldsResult{ .worlds = input_worlds, .validity_guard = Bdd.TRUE, }; const result = try bindMonad(allocator, input, Bdd.TRUE, &state, BindIdentityContinuation, {}); defer freeWorldsSlice(allocator, result.worlds); try std.testing.expectEqual(@as(usize, 2), result.worlds.len);}test "thunk cache returns equivalent guards on hit" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const val_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 42 } }); const thunk = try makeThunk(allocator, val_expr, Env.empty, 0, &state); const result1 = try evaluateLazyKCThunk(allocator, thunk, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), thunk.cache.items.len); const result2 = try evaluateLazyKCThunk(allocator, thunk, Bdd.TRUE, &state); try std.testing.expectEqual(result1.worlds.len, result2.worlds.len); for (result1.worlds, result2.worlds) |w1, w2| { try std.testing.expect(manager.eq(w1.guard, w2.guard)); }}test "mk_int creates IntDist with correct bits" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const bitwidth_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const value_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 5 } }); const mk_int_expr = try pexpr.PExpr.initWithArgs(allocator, .mk_int, &[_]*pexpr.PExpr{ bitwidth_expr, value_expr }); const result = try compileInner(mk_int_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); const val = result.worlds[0].value; try std.testing.expect(val.data == .native); try std.testing.expect(val.data.native == .int_dist); const int_dist = val.data.native.int_dist; try std.testing.expectEqual(@as(usize, 4), int_dist.bits.len); try std.testing.expect(int_dist.bits[0].isTrue()); try std.testing.expect(int_dist.bits[1].isFalse()); try std.testing.expect(int_dist.bits[2].isTrue()); try std.testing.expect(int_dist.bits[3].isFalse());}test "int_dist_eq with equal values returns True" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const bitwidth_expr1 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const value_expr1 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 7 } }); const mk_int_expr1 = try pexpr.PExpr.initWithArgs(allocator, .mk_int, &[_]*pexpr.PExpr{ bitwidth_expr1, value_expr1 }); const bitwidth_expr2 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const value_expr2 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 7 } }); const mk_int_expr2 = try pexpr.PExpr.initWithArgs(allocator, .mk_int, &[_]*pexpr.PExpr{ bitwidth_expr2, value_expr2 }); const eq_expr = try pexpr.PExpr.initWithArgs(allocator, .int_dist_eq, &[_]*pexpr.PExpr{ mk_int_expr1, mk_int_expr2 }); const result = try compileInner(eq_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expect(result.worlds.len >= 1); var found_true = false; for (result.worlds) |world| { if (world.value.data == .constructed) { const c = world.value.data.constructed; if (std.mem.eql(u8, c.constructor, "True") and world.guard.isTrue()) { found_true = true; } } } try std.testing.expect(found_true);}test "int_dist_eq with different values returns False" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const bitwidth_expr1 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const value_expr1 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 5 } }); const mk_int_expr1 = try pexpr.PExpr.initWithArgs(allocator, .mk_int, &[_]*pexpr.PExpr{ bitwidth_expr1, value_expr1 }); const bitwidth_expr2 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const value_expr2 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 7 } }); const mk_int_expr2 = try pexpr.PExpr.initWithArgs(allocator, .mk_int, &[_]*pexpr.PExpr{ bitwidth_expr2, value_expr2 }); const eq_expr = try pexpr.PExpr.initWithArgs(allocator, .int_dist_eq, &[_]*pexpr.PExpr{ mk_int_expr1, mk_int_expr2 }); const result = try compileInner(eq_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expect(result.worlds.len >= 1); var found_false = false; for (result.worlds) |world| { if (world.value.data == .constructed) { const c = world.value.data.constructed; if (std.mem.eql(u8, c.constructor, "False") and world.guard.isTrue()) { found_false = true; } } } try std.testing.expect(found_false);}test "pbool with deterministic True" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const true_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "True" } }, &[_]*pexpr.PExpr{}); const pbool_expr = try pexpr.PExpr.initWithArgs(allocator, .pbool, &[_]*pexpr.PExpr{true_expr}); const result = try compileInner(pbool_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); const val = result.worlds[0].value; try std.testing.expect(val.data == .constructed); try std.testing.expect(std.mem.eql(u8, val.data.constructed.constructor, "PBool")); const prob_val = val.data.constructed.args[0]; try std.testing.expect(prob_val.data == .native); try std.testing.expect(prob_val.data.native == .float); try std.testing.expectApproxEqAbs(@as(f64, 1.0), prob_val.data.native.float, 1e-10);}test "pbool with deterministic False" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const false_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "False" } }, &[_]*pexpr.PExpr{}); const pbool_expr = try pexpr.PExpr.initWithArgs(allocator, .pbool, &[_]*pexpr.PExpr{false_expr}); const result = try compileInner(pbool_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); const val = result.worlds[0].value; try std.testing.expect(val.data == .constructed); try std.testing.expect(std.mem.eql(u8, val.data.constructed.constructor, "PBool")); const prob_val = val.data.constructed.args[0]; try std.testing.expect(prob_val.data == .native); try std.testing.expect(prob_val.data.native == .float); try std.testing.expectApproxEqAbs(@as(f64, 0.0), prob_val.data.native.float, 1e-10);}test "pbool with flip(0.5)" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const prob_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.5 } }); const flip_expr = try pexpr.PExpr.initWithArgs(allocator, .flip, &[_]*pexpr.PExpr{prob_expr}); const pbool_expr = try pexpr.PExpr.initWithArgs(allocator, .pbool, &[_]*pexpr.PExpr{flip_expr}); const result = try compileInner(pbool_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 2), result.worlds.len); for (result.worlds) |world| { try std.testing.expect(world.value.data == .constructed); try std.testing.expect(std.mem.eql(u8, world.value.data.constructed.constructor, "PBool")); const prob_val = world.value.data.constructed.args[0]; try std.testing.expect(prob_val.data == .native); try std.testing.expect(prob_val.data.native == .float); try std.testing.expectApproxEqAbs(@as(f64, 0.5), prob_val.data.native.float, 1e-10); }}test "mk_int_weighted with single value is deterministic" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const bitwidth_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const pair_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 5 } }); const pair_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 1.0 } }); const pair_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair_val, pair_prob }); const nil_expr = try pexpr.PExpr.init(allocator, .{ .construct = .{ .constructor = "Nil" } }); const list_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair_expr, nil_expr }); const mk_int_weighted_expr = try pexpr.PExpr.initWithArgs(allocator, .mk_int_weighted, &[_]*pexpr.PExpr{ bitwidth_expr, list_expr }); const result = try compileInner(mk_int_weighted_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); const val = result.worlds[0].value; try std.testing.expect(val.data == .native); try std.testing.expect(val.data.native == .int_dist); const int_dist = val.data.native.int_dist; try std.testing.expectEqual(@as(usize, 4), int_dist.bits.len); try std.testing.expect(int_dist.bits[0].isTrue()); try std.testing.expect(int_dist.bits[1].isFalse()); try std.testing.expect(int_dist.bits[2].isTrue()); try std.testing.expect(int_dist.bits[3].isFalse());}test "mk_int_weighted with two equal probability values" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const bitwidth_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const pair1_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 1 } }); const pair1_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.5 } }); const pair1_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair1_val, pair1_prob }); const pair2_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 2 } }); const pair2_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.5 } }); const pair2_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair2_val, pair2_prob }); const nil_expr = try pexpr.PExpr.init(allocator, .{ .construct = .{ .constructor = "Nil" } }); const cons2 = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair2_expr, nil_expr }); const list_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair1_expr, cons2 }); const mk_int_weighted_expr = try pexpr.PExpr.initWithArgs(allocator, .mk_int_weighted, &[_]*pexpr.PExpr{ bitwidth_expr, list_expr }); const result = try compileInner(mk_int_weighted_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); const val = result.worlds[0].value; try std.testing.expect(val.data == .native); try std.testing.expect(val.data.native == .int_dist); const int_dist = val.data.native.int_dist; try std.testing.expectEqual(@as(usize, 4), int_dist.bits.len); try std.testing.expect(int_dist.bits[2].isFalse()); try std.testing.expect(int_dist.bits[3].isFalse()); try std.testing.expect(!int_dist.bits[0].isTrue() and !int_dist.bits[0].isFalse()); try std.testing.expect(!int_dist.bits[1].isTrue() and !int_dist.bits[1].isFalse()); try std.testing.expect(manager.eq(int_dist.bits[0], int_dist.bits[1].neg()));}test "mk_int_weighted WMC gives correct probabilities" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const bitwidth_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const pair1_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 3 } }); const pair1_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.7 } }); const pair1_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair1_val, pair1_prob }); const pair2_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 5 } }); const pair2_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.3 } }); const pair2_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair2_val, pair2_prob }); const nil_expr = try pexpr.PExpr.init(allocator, .{ .construct = .{ .constructor = "Nil" } }); const cons2 = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair2_expr, nil_expr }); const list_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair1_expr, cons2 }); const mk_int_weighted_expr = try pexpr.PExpr.initWithArgs(allocator, .mk_int_weighted, &[_]*pexpr.PExpr{ bitwidth_expr, list_expr }); const result = try compileInner(mk_int_weighted_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); const val = result.worlds[0].value; try std.testing.expect(val.data == .native); try std.testing.expect(val.data.native == .int_dist); const int_dist = val.data.native.int_dist; const p_bit0 = try wmcForState(&state, int_dist.bits[0]); const p_bit1 = try wmcForState(&state, int_dist.bits[1]); const p_bit2 = try wmcForState(&state, int_dist.bits[2]); const p_bit3 = try wmcForState(&state, int_dist.bits[3]); try std.testing.expectApproxEqAbs(@as(f64, 1.0), p_bit0, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.7), p_bit1, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.3), p_bit2, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.0), p_bit3, 1e-10);}test "mk_int_weighted int_dist_eq works correctly" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const bw1 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const p1_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 3 } }); const p1_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.6 } }); const p1_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ p1_val, p1_prob }); const p2_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 5 } }); const p2_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.4 } }); const p2_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ p2_val, p2_prob }); const nil1 = try pexpr.PExpr.init(allocator, .{ .construct = .{ .constructor = "Nil" } }); const cons2_1 = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ p2_expr, nil1 }); const list1 = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ p1_expr, cons2_1 }); const weighted_expr = try pexpr.PExpr.initWithArgs(allocator, .mk_int_weighted, &[_]*pexpr.PExpr{ bw1, list1 }); const bw2 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const v2 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 3 } }); const const_expr = try pexpr.PExpr.initWithArgs(allocator, .mk_int, &[_]*pexpr.PExpr{ bw2, v2 }); const eq_expr = try pexpr.PExpr.initWithArgs(allocator, .int_dist_eq, &[_]*pexpr.PExpr{ weighted_expr, const_expr }); const result = try compileInner(eq_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 2), result.worlds.len); var p_true: f64 = 0; var p_false: f64 = 0; for (result.worlds) |world| { if (world.value.data == .constructed) { const c = world.value.data.constructed; const prob = try wmcForState(&state, world.guard); if (std.mem.eql(u8, c.constructor, "True")) { p_true = prob; } else if (std.mem.eql(u8, c.constructor, "False")) { p_false = prob; } } } try std.testing.expectApproxEqAbs(@as(f64, 0.6), p_true, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.4), p_false, 1e-10);}test "mk_int_weighted with three values" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const bitwidth_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const pair1_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 1 } }); const pair1_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.5 } }); const pair1_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair1_val, pair1_prob }); const pair2_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 2 } }); const pair2_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.3 } }); const pair2_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair2_val, pair2_prob }); const pair3_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 3 } }); const pair3_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.2 } }); const pair3_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair3_val, pair3_prob }); const nil_expr = try pexpr.PExpr.init(allocator, .{ .construct = .{ .constructor = "Nil" } }); const cons3 = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair3_expr, nil_expr }); const cons2 = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair2_expr, cons3 }); const list_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair1_expr, cons2 }); const mk_int_weighted_expr = try pexpr.PExpr.initWithArgs(allocator, .mk_int_weighted, &[_]*pexpr.PExpr{ bitwidth_expr, list_expr }); const result = try compileInner(mk_int_weighted_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); const val = result.worlds[0].value; try std.testing.expect(val.data == .native); try std.testing.expect(val.data.native == .int_dist); const int_dist = val.data.native.int_dist; const p_bit0 = try wmcForState(&state, int_dist.bits[0]); const p_bit1 = try wmcForState(&state, int_dist.bits[1]); const p_bit2 = try wmcForState(&state, int_dist.bits[2]); const p_bit3 = try wmcForState(&state, int_dist.bits[3]); try std.testing.expectApproxEqAbs(@as(f64, 0.7), p_bit0, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.5), p_bit1, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.0), p_bit2, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.0), p_bit3, 1e-10);}test "mk_int_weighted with four values verifies cascade correctness" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const bitwidth_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const pair0_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 0 } }); const pair0_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.1 } }); const pair0_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair0_val, pair0_prob }); const pair1_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 1 } }); const pair1_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.2 } }); const pair1_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair1_val, pair1_prob }); const pair2_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 2 } }); const pair2_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.3 } }); const pair2_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair2_val, pair2_prob }); const pair3_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 3 } }); const pair3_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.4 } }); const pair3_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair3_val, pair3_prob }); const nil_expr = try pexpr.PExpr.init(allocator, .{ .construct = .{ .constructor = "Nil" } }); const cons3 = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair3_expr, nil_expr }); const cons2 = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair2_expr, cons3 }); const cons1 = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair1_expr, cons2 }); const list_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair0_expr, cons1 }); const mk_int_weighted_expr = try pexpr.PExpr.initWithArgs(allocator, .mk_int_weighted, &[_]*pexpr.PExpr{ bitwidth_expr, list_expr }); const result = try compileInner(mk_int_weighted_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); const val = result.worlds[0].value; try std.testing.expect(val.data == .native); try std.testing.expect(val.data.native == .int_dist); const int_dist = val.data.native.int_dist; const p_bit0 = try wmcForState(&state, int_dist.bits[0]); const p_bit1 = try wmcForState(&state, int_dist.bits[1]); const p_bit2 = try wmcForState(&state, int_dist.bits[2]); const p_bit3 = try wmcForState(&state, int_dist.bits[3]); try std.testing.expectApproxEqAbs(@as(f64, 0.6), p_bit0, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.7), p_bit1, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.0), p_bit2, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.0), p_bit3, 1e-10);}test "mk_int_weighted validates negative probabilities" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const bitwidth_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const pair1_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 1 } }); const pair1_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = -0.5 } }); const pair1_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair1_val, pair1_prob }); const pair2_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 2 } }); const pair2_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.5 } }); const pair2_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair2_val, pair2_prob }); const nil_expr = try pexpr.PExpr.init(allocator, .{ .construct = .{ .constructor = "Nil" } }); const cons2 = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair2_expr, nil_expr }); const list_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair1_expr, cons2 }); const mk_int_weighted_expr = try pexpr.PExpr.initWithArgs(allocator, .mk_int_weighted, &[_]*pexpr.PExpr{ bitwidth_expr, list_expr }); const result = try compileInner(mk_int_weighted_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 0), result.worlds.len);}test "mk_int_weighted validates infinite probabilities" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const bitwidth_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const pair1_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 1 } }); const pair1_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = std.math.inf(f64) } }); const pair1_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair1_val, pair1_prob }); const pair2_val = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 2 } }); const pair2_prob = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.5 } }); const pair2_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ pair2_val, pair2_prob }); const nil_expr = try pexpr.PExpr.init(allocator, .{ .construct = .{ .constructor = "Nil" } }); const cons2 = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair2_expr, nil_expr }); const list_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair1_expr, cons2 }); const mk_int_weighted_expr = try pexpr.PExpr.initWithArgs(allocator, .mk_int_weighted, &[_]*pexpr.PExpr{ bitwidth_expr, list_expr }); const result_inf = try compileInner(mk_int_weighted_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 0), result_inf.worlds.len);}test "parallel WMC respects threshold" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); const prob_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.5 } }); const flip_expr = try pexpr.PExpr.initWithArgs(allocator, .flip, &[_]*pexpr.PExpr{prob_expr}); const result_seq = try compile(allocator, allocator, flip_expr, &defs, &manager, .{ .parallel_wmc = false, }); const result_high_thresh = try compile(allocator, allocator, flip_expr, &defs, &manager, .{ .parallel_wmc = true, .parallel_wmc_threshold = 100, }); try std.testing.expectEqual(@as(usize, 2), result_seq.weighted_results.len); try std.testing.expectEqual(@as(usize, 2), result_high_thresh.weighted_results.len); var total_seq: f64 = 0; var total_high: f64 = 0; for (result_seq.weighted_results) |wr| { total_seq += wr.probability; } for (result_high_thresh.weighted_results) |wr| { total_high += wr.probability; } try std.testing.expectApproxEqAbs(@as(f64, 1.0), total_seq, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 1.0), total_high, 1e-10);}test "thunk cache respects path condition - regression for pluck-rs-0cb" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const b_var = try manager.newVar(true); const not_b_var = b_var.neg(); const expr1 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 1 } }); const thunk1 = try makeThunk(allocator, expr1, Env.empty, 0, &state); _ = try evaluateLazyKCThunk(allocator, thunk1, b_var, &state); try std.testing.expectEqual(@as(usize, 1), thunk1.cache.items.len); try std.testing.expect(thunk1.cache.items[0].validity_guard.isTrue()); const implies_check = try manager.bddImplies(not_b_var, thunk1.cache.items[0].validity_guard); try std.testing.expect(implies_check.isTrue()); const expr2 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 42 } }); const thunk2 = try makeThunk(allocator, expr2, Env.empty, 0, &state); _ = try evaluateLazyKCThunk(allocator, thunk2, b_var, &state); try std.testing.expectEqual(@as(usize, 1), thunk2.cache.items.len); const result2_notb_first = try evaluateLazyKCThunk(allocator, thunk2, not_b_var, &state); try std.testing.expectEqual(@as(usize, 1), thunk2.cache.items.len); try std.testing.expect(result2_notb_first.worlds.len >= 1); for (result2_notb_first.worlds) |world| { try std.testing.expectEqual(@as(i64, 42), world.value.data.native.int); } const result2_b_after = try evaluateLazyKCThunk(allocator, thunk2, b_var, &state); try std.testing.expect(result2_b_after.worlds.len >= 1); for (result2_b_after.worlds) |world| { try std.testing.expectEqual(@as(i64, 42), world.value.data.native.int); } const d_var = try manager.newVar(true); const e_var = try manager.newVar(true); const expr4 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 77 } }); const thunk4 = try makeThunk(allocator, expr4, Env.empty, 0, &state); _ = try evaluateLazyKCThunk(allocator, thunk4, d_var, &state); try std.testing.expect(thunk4.cache.items[0].validity_guard.isTrue()); const implies_e = try manager.bddImplies(e_var, thunk4.cache.items[0].validity_guard); try std.testing.expect(implies_e.isTrue()); const result4_e = try evaluateLazyKCThunk(allocator, thunk4, e_var, &state); for (result4_e.worlds) |world| { try std.testing.expectEqual(@as(i64, 77), world.value.data.native.int); } try std.testing.expect(thunk4.cache.items[0].validity_guard.isTrue()); const not_d = d_var.neg(); const not_e = e_var.neg(); const third_path = try state.manager.bddAnd(not_d, not_e); const implies_third = try state.manager.bddImplies(third_path, thunk4.cache.items[0].validity_guard); try std.testing.expect(implies_third.isTrue()); const result4_third = try evaluateLazyKCThunk(allocator, thunk4, third_path, &state); for (result4_third.worlds) |world| { try std.testing.expectEqual(@as(i64, 77), world.value.data.native.int); }}test "thunk cache - repro program level regression for pluck-rs-0cb" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var types = try pexpr.TypeRegistry.initWithDefaults(allocator); defer types.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); const expr = try pexpr.parseExpr(allocator, "(let [b (flip 0.5) x (if b 1 2)] (case b of True => x | False => x))", &types, &defs); const result = try compile(allocator, allocator, expr, &defs, &manager, .{ .full_dist = true }); var prob_1: f64 = 0.0; var prob_2: f64 = 0.0; var found_count: usize = 0; for (result.weighted_results) |wr| { const val = wr.value; if (val.data == .native) { switch (val.data.native) { .int => |int_val| { found_count += 1; if (int_val == 1) { prob_1 += wr.probability; } else if (int_val == 2) { prob_2 += wr.probability; } }, else => {}, } } else if (val.data == .constructed) { const ctor = val.data.constructed.constructor; if (std.mem.eql(u8, ctor, "S") or std.mem.eql(u8, ctor, "O")) { var nat_val: i64 = 0; var current = val; while (current.data == .constructed and std.mem.eql(u8, current.data.constructed.constructor, "S")) { nat_val += 1; current = current.data.constructed.args[0]; } found_count += 1; if (nat_val == 1) { prob_1 += wr.probability; } else if (nat_val == 2) { prob_2 += wr.probability; } } } } try std.testing.expectEqual(@as(usize, 2), found_count); try std.testing.expectApproxEqAbs(@as(f64, 0.5), prob_1, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.5), prob_2, 1e-10);}test "factor supports guarded weights" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var types = try pexpr.TypeRegistry.initWithDefaults(allocator); defer types.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); const expr_src = "(let [b (flip 0.5)] " ++ "(case (factor (if b 0.2 0.8)) of Unit => b))"; const expr = try pexpr.parseExpr(allocator, expr_src, &types, &defs); const result = try compile(allocator, allocator, expr, &defs, &manager, .{ .full_dist = true }); var prob_true: f64 = 0.0; var prob_false: f64 = 0.0; var total: f64 = 0.0; for (result.weighted_results) |wr| { total += wr.probability; if (wr.value.isTrue()) prob_true += wr.probability; if (wr.value.isFalse()) prob_false += wr.probability; } try std.testing.expect(total > 0.0); try std.testing.expectApproxEqAbs(@as(f64, 0.2), prob_true / total, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.8), prob_false / total, 1e-10);}test "factor defers WeightDD refinement when node limit is small" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var types = try pexpr.TypeRegistry.initWithDefaults(allocator); defer types.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); const expr_src = "(let [b (flip 0.5)] " ++ "(case (factor (if b 0.2 0.8)) of Unit => b))"; const expr = try pexpr.parseExpr(allocator, expr_src, &types, &defs); const result = try compile(allocator, allocator, expr, &defs, &manager, .{ .full_dist = true, .factor_max_branches = 1, .weight_dd_max_nodes = 2, }); try std.testing.expect(result.stats.limit_reason == null); var prob_true: f64 = 0.0; var prob_false: f64 = 0.0; var total: f64 = 0.0; for (result.weighted_results) |wr| { total += wr.probability; if (wr.value.isTrue()) prob_true += wr.probability; if (wr.value.isFalse()) prob_false += wr.probability; } try std.testing.expect(total > 0.0); try std.testing.expectApproxEqAbs(@as(f64, 0.2), prob_true / total, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.8), prob_false / total, 1e-10);}test "factor prunes zero-weight branch" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var types = try pexpr.TypeRegistry.initWithDefaults(allocator); defer types.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); const expr_src = "(let [b (flip 0.5)] " ++ "(case (factor (if b 0.0 1.0)) of Unit => b))"; const expr = try pexpr.parseExpr(allocator, expr_src, &types, &defs); const result = try compile(allocator, allocator, expr, &defs, &manager, .{ .full_dist = true }); var prob_true: f64 = 0.0; var prob_false: f64 = 0.0; var total: f64 = 0.0; for (result.weighted_results) |wr| { total += wr.probability; if (wr.value.isTrue()) prob_true += wr.probability; if (wr.value.isFalse()) prob_false += wr.probability; } try std.testing.expect(total > 0.0); try std.testing.expectApproxEqAbs(@as(f64, 0.0), prob_true / total, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 1.0), prob_false / total, 1e-10);}test "factor WeightDD composes multiple factors" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var types = try pexpr.TypeRegistry.initWithDefaults(allocator); defer types.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); const expr_src = "(let [b (flip 0.5)] " ++ "(case (factor (if b 0.2 0.8)) of Unit => " ++ "(case (factor (if b 0.5 1.5)) of Unit => b)))"; const expr = try pexpr.parseExpr(allocator, expr_src, &types, &defs); const result = try compile(allocator, allocator, expr, &defs, &manager, .{ .full_dist = true, .factor_max_branches = 1, }); var prob_true: f64 = 0.0; var prob_false: f64 = 0.0; var total: f64 = 0.0; for (result.weighted_results) |wr| { total += wr.probability; if (wr.value.isTrue()) prob_true += wr.probability; if (wr.value.isFalse()) prob_false += wr.probability; } try std.testing.expect(total > 0.0); const norm_true = prob_true / total; const norm_false = prob_false / total; try std.testing.expectApproxEqAbs(@as(f64, 0.07692307692307693), norm_true, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.9230769230769231), norm_false, 1e-10);}test "factor WeightDD respects branch guards" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var types = try pexpr.TypeRegistry.initWithDefaults(allocator); defer types.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); const expr_src = "(let [b (flip 0.5) c (flip 0.5)] " ++ "(if c (case (factor (if b 0.2 0.8)) of Unit => b) b))"; const expr = try pexpr.parseExpr(allocator, expr_src, &types, &defs); const result = try compile(allocator, allocator, expr, &defs, &manager, .{ .full_dist = true, .factor_max_branches = 1, }); var prob_true: f64 = 0.0; var prob_false: f64 = 0.0; var total: f64 = 0.0; for (result.weighted_results) |wr| { total += wr.probability; if (wr.value.isTrue()) prob_true += wr.probability; if (wr.value.isFalse()) prob_false += wr.probability; } try std.testing.expect(total > 0.0); const norm_true = prob_true / total; const norm_false = prob_false / total; try std.testing.expectApproxEqAbs(@as(f64, 0.4), norm_true, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.6), norm_false, 1e-10);}fn expectGuardsCoverAndDisjoint(manager: *Manager, guards: []const GuardedWeight) !void { var coverage = Bdd.FALSE; for (guards) |entry| { coverage = try manager.bddOr(coverage, entry.guard); } try std.testing.expect(coverage.isTrue()); for (guards, 0..) |entry, i| { var j: usize = i + 1; while (j < guards.len) : (j += 1) { const overlap = try manager.bddAnd(entry.guard, guards[j].guard); try std.testing.expect(overlap.isFalse()); } }}fn expectWeightSet(guards: []const GuardedWeight, expected: []const f64) !void { for (expected) |target| { var found = false; for (guards) |entry| { if (@abs(entry.weight - target) < 1e-12) { found = true; break; } } try std.testing.expect(found); }}test "symbolic weight compiler handles boolean chain" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var types = try pexpr.TypeRegistry.initWithDefaults(allocator); defer types.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); const expr_src = "(let [a (flip 0.5) b (flip 0.5)] " ++ "(if a 0.2 (if b 0.3 0.5)))"; const expr = try pexpr.parseExpr(allocator, expr_src, &types, &defs); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const result = try compileInner(expr, Env.empty, Bdd.TRUE, &state); defer freeWorldsSlice(allocator, result.worlds); const symbolic = try compileWeightSymbolic(allocator, result.worlds); switch (symbolic) { .ok => |guards| { defer allocator.free(guards); try std.testing.expectEqual(@as(usize, 3), guards.len); try expectWeightSet(guards, &[_]f64{ 0.2, 0.3, 0.5 }); try expectGuardsCoverAndDisjoint(&manager, guards); }, else => try std.testing.expect(false), }}test "symbolic weight compiler handles int_dist_eq" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var types = try pexpr.TypeRegistry.initWithDefaults(allocator); defer types.deinit(); try types.defineType("pair", &.{ .{ .name = "Pair", .args = &.{ "nat", "nat" } }, }); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); const expr_src = "(let [x (mk_int_weighted @2 [(Pair @1 0.5) (Pair @3 0.5)])] " ++ "(if (int_dist_eq x (mk_int @2 @1)) 0.1 0.9))"; const expr = try pexpr.parseExpr(allocator, expr_src, &types, &defs); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const result = try compileInner(expr, Env.empty, Bdd.TRUE, &state); defer freeWorldsSlice(allocator, result.worlds); const symbolic = try compileWeightSymbolic(allocator, result.worlds); switch (symbolic) { .ok => |guards| { defer allocator.free(guards); try std.testing.expectEqual(@as(usize, 2), guards.len); try expectWeightSet(guards, &[_]f64{ 0.1, 0.9 }); try expectGuardsCoverAndDisjoint(&manager, guards); }, else => try std.testing.expect(false), }}test "symbolic weight compiler handles nested if/case" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var types = try pexpr.TypeRegistry.initWithDefaults(allocator); defer types.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); const expr_src = "(let [b (flip 0.5) c (flip 0.5)] " ++ "(case b of True => (if c 0.2 0.4) | " ++ "False => (case c of True => 0.6 | False => 0.8)))"; const expr = try pexpr.parseExpr(allocator, expr_src, &types, &defs); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const result = try compileInner(expr, Env.empty, Bdd.TRUE, &state); defer freeWorldsSlice(allocator, result.worlds); const symbolic = try compileWeightSymbolic(allocator, result.worlds); switch (symbolic) { .ok => |guards| { defer allocator.free(guards); try std.testing.expectEqual(@as(usize, 4), guards.len); try expectWeightSet(guards, &[_]f64{ 0.2, 0.4, 0.6, 0.8 }); try expectGuardsCoverAndDisjoint(&manager, guards); }, else => try std.testing.expect(false), }}test "IntDist enumeration - deterministic value" { const allocator = std.testing.allocator; var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); const bitwidth_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 4 } }); const value_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 5 } }); const mk_int_expr = try pexpr.PExpr.initWithArgs(allocator, .mk_int, &[_]*pexpr.PExpr{ bitwidth_expr, value_expr }); defer mk_int_expr.deinit(allocator); const result = try compile(allocator, allocator, mk_int_expr, &defs, &manager, .{}); defer { for (result.weighted_results) |wr| { wr.value.deinit(allocator); } allocator.free(result.weighted_results); if (result.raw_worlds) |worlds| { allocator.free(worlds); } } try std.testing.expectEqual(@as(usize, 1), result.weighted_results.len); const wr = result.weighted_results[0]; try std.testing.expect(wr.value.data == .native); try std.testing.expect(wr.value.data.native == .int); try std.testing.expectEqual(@as(i64, 5), wr.value.data.native.int); try std.testing.expectApproxEqAbs(@as(f64, 1.0), wr.probability, 1e-10);}test "IntDist enumeration - weighted values" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); const bitwidth_expr = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 3 } }); const val1 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 2 } }); const prob1 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.3 } }); const pair1 = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ val1, prob1 }); const val2 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .int = 5 } }); const prob2 = try pexpr.PExpr.init(allocator, .{ .const_native = .{ .float = 0.7 } }); const pair2 = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Pair" } }, &[_]*pexpr.PExpr{ val2, prob2 }); const nil = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Nil" } }, &[_]*pexpr.PExpr{}); const cons2 = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair2, nil }); const pairs_list = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ pair1, cons2 }); const mk_int_weighted_expr = try pexpr.PExpr.initWithArgs(allocator, .mk_int_weighted, &[_]*pexpr.PExpr{ bitwidth_expr, pairs_list }); const result = try compile(allocator, allocator, mk_int_weighted_expr, &defs, &manager, .{}); try std.testing.expectEqual(@as(usize, 2), result.weighted_results.len); var prob_2: f64 = 0; var prob_5: f64 = 0; for (result.weighted_results) |wr| { if (wr.value.data == .native and wr.value.data.native == .int) { if (wr.value.data.native.int == 2) { prob_2 = wr.probability; } else if (wr.value.data.native.int == 5) { prob_5 = wr.probability; } } } try std.testing.expectApproxEqAbs(@as(f64, 0.3), prob_2, 1e-10); try std.testing.expectApproxEqAbs(@as(f64, 0.7), prob_5, 1e-10);}test "combineIntDists - combines two IntDists under different guards" { const allocator = std.testing.allocator; var manager = try Manager.init(allocator); defer manager.deinit(); const g = try manager.newVar(true); const bits1 = try allocator.alloc(Bdd, 3); defer allocator.free(bits1); bits1[0] = Bdd.FALSE; bits1[1] = Bdd.TRUE; bits1[2] = Bdd.FALSE; const bits2 = try allocator.alloc(Bdd, 3); defer allocator.free(bits2); bits2[0] = Bdd.TRUE; bits2[1] = Bdd.FALSE; bits2[2] = Bdd.TRUE; const pairs = try allocator.alloc(IntDistWithGuard, 2); defer allocator.free(pairs); pairs[0] = .{ .int_dist = runtime.IntDist.init(bits1), .guard = g }; pairs[1] = .{ .int_dist = runtime.IntDist.init(bits2), .guard = g.neg() }; const combined = try combineIntDists(allocator, pairs, &manager); defer allocator.free(combined.int_dist.bits); try std.testing.expect(combined.overall_guard.isTrue()); try std.testing.expectEqual(combined.int_dist.bits.len, 3); try std.testing.expectEqual(combined.int_dist.bits[0].toRaw(), g.neg().toRaw()); try std.testing.expectEqual(combined.int_dist.bits[1].toRaw(), g.toRaw()); try std.testing.expectEqual(combined.int_dist.bits[2].toRaw(), g.neg().toRaw());}test "enumerateIntDist - deterministic value" { const allocator = std.testing.allocator; var manager = try Manager.init(allocator); defer manager.deinit(); const bits = try allocator.alloc(Bdd, 2); defer allocator.free(bits); bits[0] = Bdd.TRUE; bits[1] = Bdd.TRUE; const int_dist = runtime.IntDist.init(bits); const results = try enumerateIntDist(allocator, int_dist, Bdd.TRUE, &manager); defer { for (results) |world| { world.value.deinit(allocator); } allocator.free(results); } try std.testing.expectEqual(@as(usize, 1), results.len); try std.testing.expect(results[0].value.data == .native); try std.testing.expect(results[0].value.data.native == .int); try std.testing.expectEqual(@as(i64, 3), results[0].value.data.native.int); try std.testing.expect(results[0].guard.isTrue());}test "enumerateIntDist - non-deterministic" { const allocator = std.testing.allocator; var manager = try Manager.init(allocator); defer manager.deinit(); const x = try manager.newVar(true); const bits = try allocator.alloc(Bdd, 1); defer allocator.free(bits); bits[0] = x; const int_dist = runtime.IntDist.init(bits); const results = try enumerateIntDist(allocator, int_dist, Bdd.TRUE, &manager); defer { for (results) |world| { world.value.deinit(allocator); } allocator.free(results); } try std.testing.expectEqual(@as(usize, 2), results.len); var found_0 = false; var found_1 = false; for (results) |world| { const val = world.value.data.native.int; if (val == 0) { try std.testing.expectEqual(world.guard.toRaw(), x.neg().toRaw()); found_0 = true; } else if (val == 1) { try std.testing.expectEqual(world.guard.toRaw(), x.toRaw()); found_1 = true; } } try std.testing.expect(found_0); try std.testing.expect(found_1);}test "get_constructor extracts constructor name from ADT value" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const true_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "True" } }, &[_]*pexpr.PExpr{}); const get_ctor_expr = try pexpr.PExpr.initWithArgs(allocator, .get_constructor, &[_]*pexpr.PExpr{true_expr}); const result = try compileInner(get_ctor_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); const val = result.worlds[0].value; try std.testing.expect(val.data == .native); try std.testing.expect(val.data.native == .symbol); try std.testing.expectEqualStrings("True", val.data.native.symbol);}test "get_constructor extracts constructor name from Cons value" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const o_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "O" } }, &[_]*pexpr.PExpr{}); const nil_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Nil" } }, &[_]*pexpr.PExpr{}); const cons_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ o_expr, nil_expr }); const get_ctor_expr = try pexpr.PExpr.initWithArgs(allocator, .get_constructor, &[_]*pexpr.PExpr{cons_expr}); const result = try compileInner(get_ctor_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); const val = result.worlds[0].value; try std.testing.expect(val.data == .native); try std.testing.expect(val.data.native == .symbol); try std.testing.expectEqualStrings("Cons", val.data.native.symbol);}test "get_args extracts empty arguments from nullary constructor" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const true_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "True" } }, &[_]*pexpr.PExpr{}); const get_args_expr = try pexpr.PExpr.initWithArgs(allocator, .get_args, &[_]*pexpr.PExpr{true_expr}); const result = try compileInner(get_args_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); const val = result.worlds[0].value; try std.testing.expect(val.data == .constructed); try std.testing.expectEqualStrings("Nil", val.data.constructed.constructor); try std.testing.expectEqual(@as(usize, 0), val.data.constructed.args.len);}test "get_args extracts arguments from Cons constructor" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const o_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "O" } }, &[_]*pexpr.PExpr{}); const nil_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Nil" } }, &[_]*pexpr.PExpr{}); const cons_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "Cons" } }, &[_]*pexpr.PExpr{ o_expr, nil_expr }); const get_args_expr = try pexpr.PExpr.initWithArgs(allocator, .get_args, &[_]*pexpr.PExpr{cons_expr}); const result = try compileInner(get_args_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); const val = result.worlds[0].value; try std.testing.expect(val.data == .constructed); try std.testing.expectEqualStrings("Cons", val.data.constructed.constructor); try std.testing.expectEqual(@as(usize, 2), val.data.constructed.args.len); const first = val.data.constructed.args[0]; const first_result = try evaluateThunk(allocator, first, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), first_result.worlds.len); const first_val = first_result.worlds[0].value; try std.testing.expect(first_val.data == .constructed); try std.testing.expectEqualStrings("O", first_val.data.constructed.constructor); const rest = val.data.constructed.args[1]; const rest_result = try evaluateThunk(allocator, rest, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), rest_result.worlds.len); const rest_val = rest_result.worlds[0].value; try std.testing.expect(rest_val.data == .constructed); try std.testing.expectEqualStrings("Cons", rest_val.data.constructed.constructor); const second = rest_val.data.constructed.args[0]; const second_result = try evaluateThunk(allocator, second, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), second_result.worlds.len); const second_val = second_result.worlds[0].value; try std.testing.expect(second_val.data == .constructed); try std.testing.expectEqualStrings("Nil", second_val.data.constructed.constructor); const tail = rest_val.data.constructed.args[1]; const tail_result = try evaluateThunk(allocator, tail, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), tail_result.worlds.len); const tail_val = tail_result.worlds[0].value; try std.testing.expect(tail_val.data == .constructed); try std.testing.expectEqualStrings("Nil", tail_val.data.constructed.constructor);}test "get_args with S(O) returns single-element list" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var manager = try Manager.init(allocator); defer manager.deinit(); var defs = pexpr.Definitions.init(allocator); defer defs.deinit(); var state = try state_module.initChecked(allocator, &manager, &defs, .{}); defer state_module.deinit(&state); const o_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "O" } }, &[_]*pexpr.PExpr{}); const s_expr = try pexpr.PExpr.initWithArgs(allocator, .{ .construct = .{ .constructor = "S" } }, &[_]*pexpr.PExpr{o_expr}); const get_args_expr = try pexpr.PExpr.initWithArgs(allocator, .get_args, &[_]*pexpr.PExpr{s_expr}); const result = try compileInner(get_args_expr, Env.empty, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), result.worlds.len); const val = result.worlds[0].value; try std.testing.expect(val.data == .constructed); try std.testing.expectEqualStrings("Cons", val.data.constructed.constructor); try std.testing.expectEqual(@as(usize, 2), val.data.constructed.args.len); const first = val.data.constructed.args[0]; const first_result = try evaluateThunk(allocator, first, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), first_result.worlds.len); const first_val = first_result.worlds[0].value; try std.testing.expect(first_val.data == .constructed); try std.testing.expectEqualStrings("O", first_val.data.constructed.constructor); const rest = val.data.constructed.args[1]; const rest_result = try evaluateThunk(allocator, rest, Bdd.TRUE, &state); try std.testing.expectEqual(@as(usize, 1), rest_result.worlds.len); const rest_val = rest_result.worlds[0].value; try std.testing.expect(rest_val.data == .constructed); try std.testing.expectEqualStrings("Nil", rest_val.data.constructed.constructor);}test "ThunkRegistry basic operations" { const allocator = std.testing.allocator; var registry = ThunkRegistry.init(allocator); defer registry.deinit(); var manager = try Manager.init(allocator); defer manager.deinit(); const expr = try PExpr.init(allocator, .{ .const_native = .{ .float = 1.0 } }); defer expr.deinit(allocator); const callstack: []const i32 = &[_]i32{ 0, 1, 2 }; const thunk = try LazyKCThunk.init(allocator, expr, Env.empty, 0, callstack); defer thunk.deinit(allocator); try registry.register(thunk, expr, callstack); try std.testing.expectEqual(@as(usize, 1), registry.count()); const id = ThunkId.init(expr, callstack); const found = registry.get(id); try std.testing.expect(found != null); try std.testing.expect(found.? == thunk);}test "ThunkId stability across lookups" { const allocator = std.testing.allocator; const expr = try PExpr.init(allocator, .{ .const_native = .{ .float = 42.0 } }); defer expr.deinit(allocator); const callstack1: []const i32 = &[_]i32{ 1, 2, 3 }; const callstack2: []const i32 = &[_]i32{ 1, 2, 3 }; const id1 = ThunkId.init(expr, callstack1); const id2 = ThunkId.init(expr, callstack2); try std.testing.expect(id1.eql(id2)); try std.testing.expectEqual(id1.hash(), id2.hash()); const callstack3: []const i32 = &[_]i32{ 1, 2, 4 }; const id3 = ThunkId.init(expr, callstack3); try std.testing.expect(!id1.eql(id3));}test "ThunkRegistry refineVariable restricts guards" { const allocator = std.testing.allocator; var manager = try Manager.init(allocator); defer manager.deinit(); var registry = ThunkRegistry.init(allocator); defer registry.deinit(); const expr = try PExpr.init(allocator, .{ .const_native = .{ .float = 1.0 } }); defer expr.deinit(allocator); const callstack: []const i32 = &[_]i32{}; const thunk = try LazyKCThunk.init(allocator, expr, Env.empty, 0, callstack); defer thunk.deinit(allocator); try registry.register(thunk, expr, callstack); const x = try manager.newVar(true); const y = try manager.newVar(true); const guard = try manager.bddAnd(x, y); const dummy_val = try RuntimeValue.initNative(allocator, .{ .int = 42 }); defer dummy_val.deinit(allocator); const worlds_slice = try allocator.alloc(World, 1); worlds_slice[0] = .{ .value = dummy_val, .guard = guard }; try thunk.cache.append(allocator, .{ .worlds = worlds_slice, .validity_guard = guard, }); try registry.refineVariable(&manager, 0, true); try std.testing.expectEqual(@as(usize, 1), thunk.cache.items.len); const new_validity_guard = thunk.cache.items[0].validity_guard; try std.testing.expect(manager.eq(new_validity_guard, y)); const new_world_guard = thunk.cache.items[0].worlds[0].guard; try std.testing.expect(manager.eq(new_world_guard, y));}test "ThunkDependencies basic operations" { const allocator = std.testing.allocator; var deps = ThunkDependencies.init(allocator); defer deps.deinit(); const id1 = ThunkId{ .session = .{ .expr_ptr = 0x1000, .callstack_hash = 100 } }; const id2 = ThunkId{ .session = .{ .expr_ptr = 0x2000, .callstack_hash = 200 } }; try deps.addDependency(id1, 0); try deps.addDependency(id1, 1); try deps.addDependency(id2, 1); try deps.addDependency(id2, 2); const vars1 = deps.getVariables(id1).?; try std.testing.expect(vars1.contains(0)); try std.testing.expect(vars1.contains(1)); try std.testing.expect(!vars1.contains(2)); const thunks_for_var1 = deps.getThunks(1).?; try std.testing.expect(thunks_for_var1.contains(id1)); try std.testing.expect(thunks_for_var1.contains(id2)); const thunks_for_var0 = deps.getThunks(0).?; try std.testing.expect(thunks_for_var0.contains(id1)); try std.testing.expect(!thunks_for_var0.contains(id2));}test "ThunkDependencies dirty marking" { const allocator = std.testing.allocator; var deps = ThunkDependencies.init(allocator); defer deps.deinit(); const id1 = ThunkId{ .session = .{ .expr_ptr = 0x1000, .callstack_hash = 100 } }; const id2 = ThunkId{ .session = .{ .expr_ptr = 0x2000, .callstack_hash = 200 } }; try deps.addDependency(id1, 0); try deps.addDependency(id1, 1); try deps.addDependency(id2, 1); try std.testing.expectEqual(@as(usize, 0), deps.dirtyCount()); try std.testing.expect(!deps.isDirty(id1)); try deps.markDirty(0); try std.testing.expectEqual(@as(usize, 1), deps.dirtyCount()); try std.testing.expect(deps.isDirty(id1)); try std.testing.expect(!deps.isDirty(id2)); deps.clearDirty(id1); try std.testing.expectEqual(@as(usize, 0), deps.dirtyCount()); try deps.markDirty(1); try std.testing.expectEqual(@as(usize, 2), deps.dirtyCount()); try std.testing.expect(deps.isDirty(id1)); try std.testing.expect(deps.isDirty(id2)); deps.clearAllDirty(); try std.testing.expectEqual(@as(usize, 0), deps.dirtyCount());}test "ThunkDependencies removeThunk cleans up correctly" { const allocator = std.testing.allocator; var deps = ThunkDependencies.init(allocator); defer deps.deinit(); const id1 = ThunkId{ .session = .{ .expr_ptr = 0x1000, .callstack_hash = 100 } }; const id2 = ThunkId{ .session = .{ .expr_ptr = 0x2000, .callstack_hash = 200 } }; try deps.addDependency(id1, 0); try deps.addDependency(id1, 1); try deps.addDependency(id2, 1); try deps.markDirty(0); try std.testing.expect(deps.isDirty(id1)); deps.removeThunk(id1); try std.testing.expect(deps.getVariables(id1) == null); const thunks_for_var0 = deps.getThunks(0).?; try std.testing.expect(!thunks_for_var0.contains(id1)); const thunks_for_var1 = deps.getThunks(1).?; try std.testing.expect(!thunks_for_var1.contains(id1)); try std.testing.expect(thunks_for_var1.contains(id2)); try std.testing.expect(!deps.isDirty(id1));}test "IncrementalLPSMC init and deinit" { const allocator = std.testing.allocator; var lpsmc = lpsmc_module.init(allocator); defer lpsmc_module.deinit(&lpsmc); try std.testing.expectEqual(@as(u32, 0), lpsmc.last_iteration_count); try std.testing.expectEqual(@as(f64, 1.0), lpsmc.final_multiplier); try std.testing.expectEqual(@as(usize, 0), lpsmc.subproblem_cache.count()); try std.testing.expectEqual(@as(usize, 0), lpsmc.path_choices.count());}test "IncrementalLPSMC affectsPathChoices" { const allocator = std.testing.allocator; var lpsmc = lpsmc_module.init(allocator); defer lpsmc_module.deinit(&lpsmc); var deps = VarLabelSet{}; try deps.put(allocator, 1, {}); try deps.put(allocator, 2, {}); try lpsmc.path_choices.put(allocator, 0, PathChoice{ .top_k_bdd = Bdd.TRUE, .sampled_bdd = null, .sampled_probability = 0.0, .k_used = 1, .ess_ratio = 1.0, .depends_on_vars = deps, }); try std.testing.expect(lpsmc_module.affectsPathChoices(&lpsmc, 1)); try std.testing.expect(lpsmc_module.affectsPathChoices(&lpsmc, 2)); try std.testing.expect(!lpsmc_module.affectsPathChoices(&lpsmc, 3));}test "IncrementalLPSMC getAffectedSubproblems" { const allocator = std.testing.allocator; var lpsmc = lpsmc_module.init(allocator); defer lpsmc_module.deinit(&lpsmc); try lpsmc_module.recordDependency(&lpsmc, 0, 1); try lpsmc_module.recordDependency(&lpsmc, 1, 1); try lpsmc_module.recordDependency(&lpsmc, 2, 2); var affected1: std.ArrayList(u32) = .empty; defer affected1.deinit(allocator); try lpsmc_module.getAffectedSubproblems(&lpsmc, 1, &affected1); try std.testing.expectEqual(@as(usize, 2), affected1.items.len); var affected2: std.ArrayList(u32) = .empty; defer affected2.deinit(allocator); try lpsmc_module.getAffectedSubproblems(&lpsmc, 2, &affected2); try std.testing.expectEqual(@as(usize, 1), affected2.items.len); var affected3: std.ArrayList(u32) = .empty; defer affected3.deinit(allocator); try lpsmc_module.getAffectedSubproblems(&lpsmc, 3, &affected3); try std.testing.expectEqual(@as(usize, 0), affected3.items.len);}test "IncrementalLPSMC clearCaches" { const allocator = std.testing.allocator; var lpsmc = lpsmc_module.init(allocator); defer lpsmc_module.deinit(&lpsmc); var deps = VarLabelSet{}; try deps.put(allocator, 1, {}); try lpsmc.path_choices.put(allocator, 0, PathChoice{ .top_k_bdd = Bdd.TRUE, .sampled_bdd = null, .sampled_probability = 0.0, .k_used = 1, .ess_ratio = 1.0, .depends_on_vars = deps, }); try lpsmc_module.recordDependency(&lpsmc, 0, 1); lpsmc.last_iteration_count = 5; lpsmc.final_multiplier = 2.5; lpsmc_module.clearCaches(&lpsmc); try std.testing.expectEqual(@as(u32, 0), lpsmc.last_iteration_count); try std.testing.expectEqual(@as(f64, 1.0), lpsmc.final_multiplier); try std.testing.expectEqual(@as(usize, 0), lpsmc.path_choices.count()); try std.testing.expectEqual(@as(usize, 0), lpsmc.var_to_subproblems.count());}test "LPSMCVarianceStats effective sample size" { var stats = LPSMCVarianceStats{}; try std.testing.expectEqual(@as(f64, 0.0), stats.effectiveSampleSize()); try std.testing.expectEqual(@as(f64, 1.0), stats.essRatio()); stats.recordWeight(1.0); stats.recordWeight(1.0); stats.recordWeight(1.0); try std.testing.expectApproxEqAbs(@as(f64, 3.0), stats.effectiveSampleSize(), 0.01); try std.testing.expectApproxEqAbs(@as(f64, 1.0), stats.essRatio(), 0.01);}test "LPSMCVarianceStats high variance detection" { var stats = LPSMCVarianceStats{}; stats.recordWeight(1.0); stats.recordWeight(1.0); try std.testing.expect(!stats.isHighVariance(0.5)); var high_var_stats = LPSMCVarianceStats{}; high_var_stats.recordWeight(0.1); high_var_stats.recordWeight(0.1); high_var_stats.recordWeight(100.0); try std.testing.expect(high_var_stats.essRatio() < 0.5);}test "LPSMCVarianceStats max multiplier tracking" { var stats = LPSMCVarianceStats{}; try std.testing.expectEqual(@as(f64, 1.0), stats.max_multiplier); stats.recordWeight(2.0); try std.testing.expectEqual(@as(f64, 2.0), stats.max_multiplier); stats.recordWeight(1.0); try std.testing.expectEqual(@as(f64, 2.0), stats.max_multiplier); stats.recordWeight(10.0); try std.testing.expectEqual(@as(f64, 10.0), stats.max_multiplier);}test "IncrementalLPSMC clearCaches resets variance stats" { const allocator = std.testing.allocator; var lpsmc = lpsmc_module.init(allocator); defer lpsmc_module.deinit(&lpsmc); lpsmc.variance_stats.recordWeight(5.0); lpsmc.variance_stats.recordWeight(10.0); lpsmc.variance_stats.high_variance_warning = true; try std.testing.expectEqual(@as(u32, 2), lpsmc.variance_stats.num_samples); try std.testing.expect(lpsmc.variance_stats.high_variance_warning); lpsmc_module.clearCaches(&lpsmc); try std.testing.expectEqual(@as(u32, 0), lpsmc.variance_stats.num_samples); try std.testing.expectEqual(@as(f64, 0.0), lpsmc.variance_stats.sum_weights); try std.testing.expect(!lpsmc.variance_stats.high_variance_warning);}Source: lib/pluck/src/root.zig:13
pub const evaluator = @import("evaluator.zig");Complete caller list for evaluator.compile
9 direct callers.
lib.pluck.src.evaluator.test_IntDist_enumeration_-_deterministic_value[function] — test source atlib/pluck/src/evaluator.zig:4035in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_IntDist_enumeration_-_weighted_values[function] — test source atlib/pluck/src/evaluator.zig:4070in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_factor_WeightDD_composes_multiple_factors[function] — test source atlib/pluck/src/evaluator.zig:3810in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_factor_WeightDD_respects_branch_guards[function] — test source atlib/pluck/src/evaluator.zig:3851in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_factor_defers_WeightDD_refinement_when_node_limit_is_small[function] — test source atlib/pluck/src/evaluator.zig:3734in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_factor_prunes_zero-weight_branch[function] — test source atlib/pluck/src/evaluator.zig:3775in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_factor_supports_guarded_weights[function] — test source atlib/pluck/src/evaluator.zig:3699in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_parallel_WMC_respects_threshold[function] — test source atlib/pluck/src/evaluator.zig:3518in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_thunk_cache_-_repro_program_level_regression_for_pluck-rs-0cb[function] — test source atlib/pluck/src/evaluator.zig:3636in nearest public ownertiny.pluck.evaluator
Complete call list for evaluator.compile
7 direct calls.
tiny.pluck.evaluator.processIntDistWorlds[function] atlib/pluck/src/dist.zig:109lib.pluck.src.evaluator.computeWmcDeferredSequential[function] — private source atlib/pluck/src/evaluator.zig:2053in nearest public ownertiny.pluck.evaluatortiny.pluck.evaluator.inferFullDistribution[function] atlib/pluck/src/evaluator.zig:2238tiny.pluck.evaluator.tracedCompileInner[function] atlib/pluck/src/evaluator.zig:223tiny.pluck.evaluator.freeWorldsSlice[function] atlib/pluck/src/monad.zig:52tiny.pluck.wmc.computeWmcParallel[function] atlib/pluck/src/wmc.zig:68tiny.pluck.wmc.computeWmcSequential[function] atlib/pluck/src/wmc.zig:139
Complete caller list for evaluator.compileInner
24 direct callers.
lib.pluck.src.evaluator.test_flip_produces_path-condition-independent_guards[function] — test source atlib/pluck/src/evaluator.zig:2811in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_get_args_extracts_arguments_from_Cons_constructor[function] — test source atlib/pluck/src/evaluator.zig:4301in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_get_args_extracts_empty_arguments_from_nullary_constructor[function] — test source atlib/pluck/src/evaluator.zig:4274in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_get_args_with_S(O)_returns_single-element_list[function] — test source atlib/pluck/src/evaluator.zig:4358in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_get_constructor_extracts_constructor_name_from_ADT_value[function] — test source atlib/pluck/src/evaluator.zig:4218in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_get_constructor_extracts_constructor_name_from_Cons_value[function] — test source atlib/pluck/src/evaluator.zig:4245in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_int_dist_eq_with_different_values_returns_False[function] — test source atlib/pluck/src/evaluator.zig:2992in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_int_dist_eq_with_equal_values_returns_True[function] — test source atlib/pluck/src/evaluator.zig:2952in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_mk_int_creates_IntDist_with_correct_bits[function] — test source atlib/pluck/src/evaluator.zig:2917in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_mk_int_weighted_WMC_gives_correct_probabilities[function] — test source atlib/pluck/src/evaluator.zig:3219in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_mk_int_weighted_int_dist_eq_works_correctly[function] — test source atlib/pluck/src/evaluator.zig:3273in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_mk_int_weighted_validates_infinite_probabilities[function] — test source atlib/pluck/src/evaluator.zig:3483in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_mk_int_weighted_validates_negative_probabilities[function] — test source atlib/pluck/src/evaluator.zig:3448in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_mk_int_weighted_with_four_values_verifies_cascade_correctness[function] — test source atlib/pluck/src/evaluator.zig:3387in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_mk_int_weighted_with_single_value_is_deterministic[function] — test source atlib/pluck/src/evaluator.zig:3127in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_mk_int_weighted_with_three_values[function] — test source atlib/pluck/src/evaluator.zig:3331in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_mk_int_weighted_with_two_equal_probability_values[function] — test source atlib/pluck/src/evaluator.zig:3169in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_pbool_with_deterministic_False[function] — test source atlib/pluck/src/evaluator.zig:3063in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_pbool_with_deterministic_True[function] — test source atlib/pluck/src/evaluator.zig:3032in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_pbool_with_flip(0.5)[function] — test source atlib/pluck/src/evaluator.zig:3094in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_symbolic_weight_compiler_handles_boolean_chain[function] — test source atlib/pluck/src/evaluator.zig:3920in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_symbolic_weight_compiler_handles_int_dist_eq[function] — test source atlib/pluck/src/evaluator.zig:3957in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_symbolic_weight_compiler_handles_nested_if/case[function] — test source atlib/pluck/src/evaluator.zig:3997in nearest public ownertiny.pluck.evaluatortiny.pluck.evaluator.tracedCompileInner[function] atlib/pluck/src/evaluator.zig:223
Complete call list for evaluator.compileInner
20 direct calls.
lib.pluck.src.evaluator.compileAbs[function] — private source atlib/pluck/src/evaluator.zig:350in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileApp[function] — private source atlib/pluck/src/evaluator.zig:328in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileCaseOf[function] — private source atlib/pluck/src/evaluator.zig:520in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileConstNative[function] — private source atlib/pluck/src/evaluator.zig:1054in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileConstruct[function] — private source atlib/pluck/src/evaluator.zig:419in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileDefined[function] — private source atlib/pluck/src/evaluator.zig:381in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileFactor[function] — private source atlib/pluck/src/evaluator.zig:979in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileFlip[function] — private source atlib/pluck/src/evaluator.zig:719in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileFloatBinop[function] — private source atlib/pluck/src/evaluator.zig:1197in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileGetArgs[function] — private source atlib/pluck/src/evaluator.zig:1247in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileGetConfig[function] — private source atlib/pluck/src/evaluator.zig:1788in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileGetConstructor[function] — private source atlib/pluck/src/evaluator.zig:1279in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileIntDistEq[function] — private source atlib/pluck/src/evaluator.zig:1724in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileMkInt[function] — private source atlib/pluck/src/evaluator.zig:1372in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileMkIntWeighted[function] — private source atlib/pluck/src/evaluator.zig:1460in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileNativeEq[function] — private source atlib/pluck/src/evaluator.zig:1113in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compilePBool[function] — private source atlib/pluck/src/evaluator.zig:1331in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileVar[function] — private source atlib/pluck/src/evaluator.zig:365in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileY[function] — private source atlib/pluck/src/evaluator.zig:541in nearest public ownertiny.pluck.evaluatortiny.pluck.evaluator.tracedCompileInner[function] atlib/pluck/src/evaluator.zig:223
Complete caller list for evaluator.evaluateThunk
13 direct callers.
lib.pluck.src.evaluator.collectFactorWeightWorlds[function] — private source atlib/pluck/src/evaluator.zig:731in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileDefined[function] — private source atlib/pluck/src/evaluator.zig:381in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileVar[function] — private source atlib/pluck/src/evaluator.zig:365in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.evaluateThunkOp[function] — private source atlib/pluck/src/evaluator.zig:171in nearest public ownertiny.pluck.evaluatortiny.pluck.evaluator.forceValue[function] atlib/pluck/src/evaluator.zig:1488tiny.pluck.evaluator.forceValueDeterministic[function] atlib/pluck/src/evaluator.zig:1504tiny.pluck.evaluator.inferFullDistribution[function] atlib/pluck/src/evaluator.zig:2238tiny.pluck.evaluator.processMarginalQuery[function] atlib/pluck/src/evaluator.zig:2227tiny.pluck.evaluator.processPosteriorQuery[function] atlib/pluck/src/evaluator.zig:2179lib.pluck.src.evaluator.test_get_args_extracts_arguments_from_Cons_constructor[function] — test source atlib/pluck/src/evaluator.zig:4301in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_get_args_with_S(O)_returns_single-element_list[function] — test source atlib/pluck/src/evaluator.zig:4358in nearest public ownertiny.pluck.evaluatorlib.pluck.src.toplevel.query.evidenceGuardForPosteriorSamples[function] — private source atlib/pluck/src/toplevel/query.zig:1050in nearest public ownertiny.pluck.toplevel.querylib.pluck.src.toplevel.query.forceSampledValue[function] — private source atlib/pluck/src/toplevel/query.zig:1084in nearest public ownertiny.pluck.toplevel.query
Complete caller list for evaluator.makeThunk
8 direct callers.
lib.pluck.src.evaluator.compileApp[function] — private source atlib/pluck/src/evaluator.zig:328in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileConstruct[function] — private source atlib/pluck/src/evaluator.zig:419in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileDefined[function] — private source atlib/pluck/src/evaluator.zig:381in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_joinMonad_collapses_constructor_worlds_with_thunk_unions[function] — test source atlib/pluck/src/evaluator.zig:2451in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_joinMonad_collapses_list_constructors_with_thunk_unions[function] — test source atlib/pluck/src/evaluator.zig:2525in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_joinMonad_collapses_nested_constructors_with_thunk_unions[function] — test source atlib/pluck/src/evaluator.zig:2630in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_thunk_cache_respects_path_condition_-_regression_for_pluck-rs-0cb[function] — test source atlib/pluck/src/evaluator.zig:3556in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_thunk_cache_returns_equivalent_guards_on_hit[function] — test source atlib/pluck/src/evaluator.zig:2887in nearest public ownertiny.pluck.evaluator
Complete caller list for evaluator.tracedCompileInner
21 direct callers.
lib.pluck.src.evaluator.CompileAppContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:303in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.CompileCaseContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:454in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.FloatBinopFirstContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1178in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.IntDistEqFirstContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1697in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.NativeEqFirstContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1094in nearest public ownertiny.pluck.evaluatortiny.pluck.evaluator.compile[function] atlib/pluck/src/evaluator.zig:2074lib.pluck.src.evaluator.compileApp[function] — private source atlib/pluck/src/evaluator.zig:328in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileCaseOf[function] — private source atlib/pluck/src/evaluator.zig:520in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileFactor[function] — private source atlib/pluck/src/evaluator.zig:979in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileFlip[function] — private source atlib/pluck/src/evaluator.zig:719in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileFloatBinop[function] — private source atlib/pluck/src/evaluator.zig:1197in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileGetArgs[function] — private source atlib/pluck/src/evaluator.zig:1247in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileGetConstructor[function] — private source atlib/pluck/src/evaluator.zig:1279in nearest public ownertiny.pluck.evaluatortiny.pluck.evaluator.compileInner[function] atlib/pluck/src/evaluator.zig:265lib.pluck.src.evaluator.compileIntDistEq[function] — private source atlib/pluck/src/evaluator.zig:1724in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileMkIntWeighted[function] — private source atlib/pluck/src/evaluator.zig:1460in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileNativeEq[function] — private source atlib/pluck/src/evaluator.zig:1113in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compilePBool[function] — private source atlib/pluck/src/evaluator.zig:1331in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.evaluateThunkNoCache[function] — private source atlib/pluck/src/evaluator.zig:1983in nearest public ownertiny.pluck.evaluatorlib.pluck.src.profiling.internal.factor.runExactFactorBenchParsed[function] — private source atlib/pluck/src/profiling/internal/factor.zig:281in nearest public ownerlib.pluck.src.profiling.internal.factorlib.pluck.src.profiling.internal.factor.runExactParsed[function] — private source atlib/pluck/src/profiling/internal/factor.zig:189in nearest public ownerlib.pluck.src.profiling.internal.factor
Complete caller list for evaluator.bindMonad
15 direct callers.
lib.pluck.src.evaluator.FloatBinopFirstContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1178in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.IntDistEqFirstContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1697in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.NativeEqFirstContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1094in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileApp[function] — private source atlib/pluck/src/evaluator.zig:328in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileCaseOf[function] — private source atlib/pluck/src/evaluator.zig:520in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileFlip[function] — private source atlib/pluck/src/evaluator.zig:719in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileFloatBinop[function] — private source atlib/pluck/src/evaluator.zig:1197in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileGetArgs[function] — private source atlib/pluck/src/evaluator.zig:1247in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileGetConstructor[function] — private source atlib/pluck/src/evaluator.zig:1279in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileIntDistEq[function] — private source atlib/pluck/src/evaluator.zig:1724in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileMkIntWeighted[function] — private source atlib/pluck/src/evaluator.zig:1460in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileNativeEq[function] — private source atlib/pluck/src/evaluator.zig:1113in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compilePBool[function] — private source atlib/pluck/src/evaluator.zig:1331in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.evaluateLazyKCThunk[function] — private source atlib/pluck/src/evaluator.zig:1912in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_bindMonad_frees_input_worlds_slice[function] — test source atlib/pluck/src/evaluator.zig:2855in nearest public ownertiny.pluck.evaluator
Complete caller list for evaluator.freeWorldsSlice
20 direct callers.
lib.pluck.src.evaluator.collectFactorWeightWorlds[function] — private source atlib/pluck/src/evaluator.zig:731in nearest public ownertiny.pluck.evaluatortiny.pluck.evaluator.compile[function] atlib/pluck/src/evaluator.zig:2074lib.pluck.src.evaluator.compileFactor[function] — private source atlib/pluck/src/evaluator.zig:979in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compilePBool[function] — private source atlib/pluck/src/evaluator.zig:1331in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.evaluateLazyKCThunk[function] — private source atlib/pluck/src/evaluator.zig:1912in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.evaluateThunkUnion[function] — private source atlib/pluck/src/evaluator.zig:2005in nearest public ownertiny.pluck.evaluatortiny.pluck.evaluator.forceValue[function] atlib/pluck/src/evaluator.zig:1488tiny.pluck.evaluator.forceValueDeterministic[function] atlib/pluck/src/evaluator.zig:1504lib.pluck.src.evaluator.freeWorldsSliceOp[function] — private source atlib/pluck/src/evaluator.zig:181in nearest public ownertiny.pluck.evaluatortiny.pluck.evaluator.inferFullDistribution[function] atlib/pluck/src/evaluator.zig:2238tiny.pluck.evaluator.processMarginalQuery[function] atlib/pluck/src/evaluator.zig:2227tiny.pluck.evaluator.processPosteriorQuery[function] atlib/pluck/src/evaluator.zig:2179lib.pluck.src.evaluator.test_bindMonad_frees_input_worlds_slice[function] — test source atlib/pluck/src/evaluator.zig:2855in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_if_then_else_monad[function] — test source atlib/pluck/src/evaluator.zig:2313in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_pure_monad[function] — test source atlib/pluck/src/evaluator.zig:2291in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_symbolic_weight_compiler_handles_boolean_chain[function] — test source atlib/pluck/src/evaluator.zig:3920in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_symbolic_weight_compiler_handles_int_dist_eq[function] — test source atlib/pluck/src/evaluator.zig:3957in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.test_symbolic_weight_compiler_handles_nested_if/case[function] — test source atlib/pluck/src/evaluator.zig:3997in nearest public ownertiny.pluck.evaluatortiny.pluck.evaluator.bindMonad[function] atlib/pluck/src/monad.zig:95tiny.pluck.evaluator.joinMonad[function] atlib/pluck/src/monad.zig:136
Complete caller list for evaluator.programErrorWorlds
18 direct callers.
lib.pluck.src.evaluator.CompileAppContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:303in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.CompileCaseContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:454in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.CompileFlipContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:623in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.FloatBinopSecondContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1152in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.GetArgsContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1218in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.GetConstructorContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1260in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.IntDistEqFirstContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1697in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.IntDistEqSecondContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1672in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.MkIntWeightedContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1409in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.PBoolContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1302in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileDefined[function] — private source atlib/pluck/src/evaluator.zig:381in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileFactor[function] — private source atlib/pluck/src/evaluator.zig:979in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileMkInt[function] — private source atlib/pluck/src/evaluator.zig:1372in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileMkIntWeighted[function] — private source atlib/pluck/src/evaluator.zig:1460in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileVar[function] — private source atlib/pluck/src/evaluator.zig:365in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileY[function] — private source atlib/pluck/src/evaluator.zig:541in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.createWeightedIntDist[function] — private source atlib/pluck/src/evaluator.zig:1585in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.finishFactorWeightDdError[function] — private source atlib/pluck/src/evaluator.zig:890in nearest public ownertiny.pluck.evaluator
Complete caller list for evaluator.pureMonad
18 direct callers.
lib.pluck.src.evaluator.BindIdentityContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:2844in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.CompileFlipContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:623in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.FloatBinopSecondContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1152in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.GetArgsContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1218in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.GetConstructorContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1260in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.NativeEqSecondContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1070in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.PBoolContinuation.cont[function] — private source atlib/pluck/src/evaluator.zig:1302in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileAbs[function] — private source atlib/pluck/src/evaluator.zig:350in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileConstNative[function] — private source atlib/pluck/src/evaluator.zig:1054in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileConstruct[function] — private source atlib/pluck/src/evaluator.zig:419in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileDefined[function] — private source atlib/pluck/src/evaluator.zig:381in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileGetConfig[function] — private source atlib/pluck/src/evaluator.zig:1788in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileMkInt[function] — private source atlib/pluck/src/evaluator.zig:1372in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileVar[function] — private source atlib/pluck/src/evaluator.zig:365in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.compileY[function] — private source atlib/pluck/src/evaluator.zig:541in nearest public ownertiny.pluck.evaluatorlib.pluck.src.evaluator.createWeightedIntDist[function] — private source atlib/pluck/src/evaluator.zig:1585in nearest public ownertiny.pluck.evaluatortiny.pluck.evaluator.evaluateThunk[function] atlib/pluck/src/evaluator.zig:1870lib.pluck.src.evaluator.test_pure_monad[function] — test source atlib/pluck/src/evaluator.zig:2291in nearest public ownertiny.pluck.evaluator
Audit
| Definitions | 44 |
|---|---|
| Public names | 56 |
| Members | 90 |
| Version | 26.7.0 |
| Revision | daab053ee433 |