Source code for utils.option_generator

'''
This module provides functions to generate options based on Z3 solver models.
'''


import itertools
from z3 import *
from random import choice, sample
from utils.auxiliary_operator import CustomSym, generate_random_indices
import time

[docs] def get_TF_events_for_each_solution(events, solutions, require=True): """ Return the events that are true/false in each feasible solution from solutions. :param events: Array of z3.Bool type. :param solutions: Array composed of multiple Solver.model() instances. :param require: Whether the events should be true (True) or false (False). """ return [[var for var in events if (model[var] == require) or (model[var] == None) ] for model in solutions]
[docs] def get_required_non_solutions(solutions, conditions = [], num = 5): """ Find num infeasible solutions that satisfy the conditions (conditions) but are not in solutions, for use as incorrect options. :param solutions: Array composed of multiple Solver.model() instances. :param conditions: Conditions that the incorrect options need to satisfy. :param num: The number of groups of incorrect options to return. """ # 创建求解器 solver = Solver() # 排除可行解 for model in solutions: solver.add(Or([var() != model[var] for var in model])) # 添加额外约束 if len(conditions): solver.add(And(*conditions)) current_num = 0 non_solutions = [] while current_num < num and solver.check() == sat: model = solver.model() # 获取当前解 non_solutions.append(model) # 保存解 # 添加排除条件:排除当前解的所有变量赋值 block = [var() != model[var] for var in model] # model[var()] 等价 model[var] solver.add(Or(block)) # 要求至少有一个变量与当前解不同 current_num += 1 satisfied_res, unsatisfied_res = solutions, non_solutions def work(res): _ans = [] for sol in res: _res = [] for var in sol: if sol[var] == True: _res.append(var()) _ans.append(tuple(_res)) return _ans return work(satisfied_res), work(unsatisfied_res)
[docs] def find_always_TF_events(events, solutions, require=True): """ Identify events (events) that are always True/False in all feasible solutions (solutions). :param events: Array of z3.Bool type. :param solutions: Array composed of multiple Solver.model() instances. :param require: Whether the events should be true (True) or false (False). :return: List of events that are always True in all solutions, `satisfied_res`, and the results that do not meet the condition, `unsatisfied_res`. """ satisfied_res = [] unsatisfied_res = [] for event in events: # 检查 event 是否在所有 solutions 里都为 True # if all(solution.eval(event, model_completion=True) == True for solution in solutions): if all(model[event] == require for model in solutions): satisfied_res.append(event) else: unsatisfied_res.append(event) return satisfied_res, unsatisfied_res
[docs] def find_required_event_groups(events, correct_num, incorrect_num, solutions, formula, cond = "all", event_num = [2], order = None, duplicate = None, custom_cond = [], env = {}): """ Find event_num events that satisfy the condition among all feasible solutions. By default, it identifies all pairs of events where at most one is true. :param events: Array of events, similar to DerivedSymbol.source in DSL. :param correct_num: Number of correct items. :param incorrect_num: Number of incorrect items. :param solutions: Array of feasible solutions (array of Z3 models). :param formula: Similar to formula in DSL. :param cond: Whether all solutions must satisfy ("all") or if at least one satisfying solution is enough ("any"), similar to DerivedSymbol.cond in DSL. :param event_num: Array specifying the number of sub-events, similar to DerivedSymbol.amount in DSL. :param order: Similar to DerivedSymbol.order in DSL, default is all False. :param duplicate: Similar to duplicate in DSL, default is all False. :param custom_cond: Similar to custom_cond in DSL. :param env: Environment variables imported from the program generated by the translator. :return: Returns a list of satisfied event pairs `satisfied` and their index list `satisfied_indices`, as well as the unsatisfied combinations `unsatisfied` and their index list `unsatisfied_indices`. """ if duplicate is None: duplicate = [False for i in range(len(events))] if order is None: order = [False if i == 0 else True for i in range(len(events))] for i in range(len(events)): if len(events[i]) < event_num[i] and duplicate[i] == False: raise Exception(f"Cannot select {event_num[i]} distinct items from {events[i]}") if type(events[i]) != list: if isinstance(events[i], CustomSym): events[i] = events[i].to_list() else: events[i] = list(events[i]) # 生成正确项 satisfied_indices, satisfied_indices_config = generate_random_indices(events, event_num, correct_num, domain_cond=True, custom_cond=custom_cond + [{"scope": "option", "cond": cond, "formula": formula, "result": True}], duplicate=duplicate, order=order, env={**env, **globals(), "_solutions": solutions}) # 生成错误项 unsatisfied_indices, unsatisfied_indices_config = generate_random_indices(events, event_num, incorrect_num, domain_cond=True, custom_cond=custom_cond + [{"scope": "option", "cond": cond, "formula": formula, "result": False}], duplicate=duplicate, order=order, env={**env, **globals(), "_solutions": solutions}) # print(satisfied_indices, unsatisfied_indices) # 转换为事件对象列表 satisfied = [ tuple(tuple(events[i][idx] for idx in sub_indices) for i, sub_indices in enumerate(comb)) for comb in satisfied_indices ] unsatisfied = [ tuple(tuple(events[i][idx] for idx in sub_indices) for i, sub_indices in enumerate(comb)) for comb in unsatisfied_indices ] return satisfied_indices_config, unsatisfied_indices_config, satisfied, unsatisfied
[docs] def find_always_TF_combined_events(events, solutions, require=True, num=2): """ Analyze which combinations of num events in solutions are always true or false. Parameters: events: List[BoolRef], array of Z3 boolean variables (event array). solutions: List[Model], multiple feasible solutions (array of solver.model()). require: bool, whether the events should be true (True) or false (False). num: int, the number of event combinations. When num = 1, it degenerates into find_always_TF_events. Returns: List[Tuple[BoolRef]], list of combinations of num events that satisfy the condition. """ result = [] # 遍历所有 num 个 events 的组合 for event_comb in itertools.combinations(events, num): # 检查该组合是否在所有解中同时满足 require 条件 if all(all(model[event] == require for event in event_comb) for model in solutions): result.append(event_comb) return result
if __name__ == "__main__": print(find_always_TF_events("abc", [{"a": True, "b": True, "c": None}, {"a": True, "b": False, "c": True}])) print(find_always_TF_combined_events("abc", [{"a": True, "b": True, "c": True}, {"a": True, "b": False, "c": True}]))