def cnet_binary_search(log, activity_window=0, exclusive_use_arcs=None, ignored_arcs=None, add_ignored_arcs_to_window=True, upper_bound=-1, randomized_names=True): """Finds a C-net for the log [log] using a binary search strategy than minimizes the number of arcs. The C-net returned contains a new field stpoutput that can be used to derive frequency information, bindings sequence, etc. [activity_window] Consider for obligation only the n activities around each activity. To consider them all use value 0 (default). [exclusive_use_arcs] List of exclusive obligations that can be used. [ignored_arcs] List of obligations not considered for arc minimization. [upper_bound] Upper bound of binary search, used to override default which is the number of arcs in the immediately follows C-net for [log].""" t0 = datetime.datetime.now() traces = log.get_uniq_cases() activity_positions = log.get_activity_positions() logfilename = log.filename if log.filename else 'tmp' if (randomized_names): sr = [ random.choice(string.ascii_letters + string.digits) for n in xrange(5) ] logfilename = logfilename.join(sr) var_info = cn_stp.boolean_variables(traces, activity_window, exclusive_use_arcs, ignored_arcs, add_ignored_arcs_to_window) net = immediately_follows_cnet_from_log(log) old_stdout = sys.stdout logprefix, logfileextension = os.path.splitext(logfilename) iteration = 0 if not ignored_arcs: max_bound = net.number_of_arcs() - 1 #new lower bound start_activities = set( [words[1] for words in traces if len(words) > 1]) end_activities = set([words[-2] for words in traces if len(words) > 1]) #print end_activities new_min_bound = (len(start_activities) + len(end_activities) + len(net.activities - (start_activities | end_activities)) - 2 + max(len(start_activities - end_activities), len(end_activities - start_activities))) print "General connectivity min bound:", len( net.activities), "New min bound:", new_min_bound min_bound = max(len(net.activities), new_min_bound) else: ignored_alphabet = set([x[0] for x in ignored_arcs]) | set( [x[1] for x in ignored_arcs]) #compute new activities in log to derive lower bound new_alphabet = log.get_alphabet() - ignored_alphabet max_bound = len(net.arcs() - set(ignored_arcs)) - 1 if len(new_alphabet) == 0: min_bound = 0 else: min_bound = len(new_alphabet) + 1 #min_bound = 0 #since the start and final activities cannot belong to the new activities print 'Some arcs are ignored, setting a conservative lower bound of {0}'.format( min_bound) if upper_bound >= 0: max_bound = min(max_bound, upper_bound) print 'Setting manually upper bound to', max_bound print "Reducing number of arcs" last_fine_net = net while min_bound <= max_bound: print "Bounds: [{0},{1}]".format(min_bound, max_bound) avg_bound = (min_bound + max_bound) / 2 print "Testing arcs =", avg_bound max_global_arcs = avg_bound stpfile = logprefix + '.it{0}.stp'.format(iteration) print stpfile print "Generating STP file ({0})...".format(stpfile) try: sys.stdout = open(stpfile, "w") except Exception as ex: print("Error. Cannot open file '%s'. %s" % (stpfile, ex)) quit() cn_stp.generate_stp_from_log(log, var_info, max_global_arcs=max_global_arcs) sys.stdout = old_stdout #call stp solver print "Calling STP solver..." t0_solver = datetime.datetime.now() stpoutput = subprocess.check_output([stp_solver, stpfile]) delta_t = datetime.datetime.now() - t0_solver print "Solver elapsed time:", delta_t.total_seconds(), "s." #print stpoutput if 'Invalid' in stpoutput: print "Model was feasible" net = stp_to_cnet.build_cnet_from_stp(stpoutput.split("\n")) # if options.file_output: # cn_file = logprefix+'.it{0}.cn'.format(iteration) # net.save(cn_file) last_fine_net = net last_fine_net.stpoutput = stpoutput #max_bound = avg_bound # if we do not want the hassle of having to perform the extraction if avg_bound > net.number_of_arcs(): print "Number of arcs of found C-net:", net.number_of_arcs() if not ignored_arcs: if avg_bound < net.number_of_arcs(): print "ERROR: equation restricting number of arcs is not working. Found C-net has {0} arcs".format( net.number_of_arcs()) quit() max_bound = net.number_of_arcs() - 1 else: # print "found arcs ({0}):".format(len(net.arcs())), net.arcs() # print "ignored arcs ({0}):".format(len(options.ignore_arcs)), set(options.ignore_arcs) # print "bounded arcs ({0}):".format(len(net.arcs() - set(options.ignore_arcs))), net.arcs() - set(options.ignore_arcs) max_bound = len(net.arcs() - set(ignored_arcs)) - 1 # print "new max bound:", max_bound #last_fine_stpoutput = stpoutput else: print "Model was unfeasible" min_bound = avg_bound + 1 iteration += 1 last_fine_net.print_cnet() print "Number of C-net arcs:", last_fine_net.number_of_arcs() print "Total number of iterations:", iteration delta_t = datetime.datetime.now() - t0 print "Elapsed time:", delta_t.total_seconds(), "s." # if options.file_output: # cn_file = logprefix+'.cn' # net.save(cn_file) # if options.interactive_output: # last_fine_net.interactive_output() return last_fine_net
def cnet_binary_search( log, activity_window=0, exclusive_use_arcs=None, ignored_arcs=None, add_ignored_arcs_to_window=True, upper_bound=-1,randomized_names=True): """Finds a C-net for the log [log] using a binary search strategy than minimizes the number of arcs. The C-net returned contains a new field stpoutput that can be used to derive frequency information, bindings sequence, etc. [activity_window] Consider for obligation only the n activities around each activity. To consider them all use value 0 (default). [exclusive_use_arcs] List of exclusive obligations that can be used. [ignored_arcs] List of obligations not considered for arc minimization. [upper_bound] Upper bound of binary search, used to override default which is the number of arcs in the immediately follows C-net for [log].""" t0 = datetime.datetime.now() traces = log.get_uniq_cases() activity_positions = log.get_activity_positions() logfilename = log.filename if log.filename else 'tmp' if (randomized_names): sr = [random.choice(string.ascii_letters + string.digits) for n in xrange(5)] logfilename = logfilename.join(sr) var_info = cn_stp.boolean_variables(traces, activity_window, exclusive_use_arcs, ignored_arcs, add_ignored_arcs_to_window) net = immediately_follows_cnet_from_log(log) old_stdout = sys.stdout logprefix, logfileextension = os.path.splitext(logfilename) iteration = 0 if not ignored_arcs: max_bound = net.number_of_arcs()-1 #new lower bound start_activities = set([words[1] for words in traces if len(words) > 1]) end_activities = set([words[-2] for words in traces if len(words) > 1]) #print end_activities new_min_bound = (len(start_activities) + len(end_activities) + len(net.activities - (start_activities | end_activities)) - 2 + max(len(start_activities - end_activities), len(end_activities - start_activities ))) print "General connectivity min bound:", len(net.activities),"New min bound:",new_min_bound min_bound = max(len(net.activities), new_min_bound) else: ignored_alphabet = set([x[0] for x in ignored_arcs]) | set([x[1] for x in ignored_arcs]) #compute new activities in log to derive lower bound new_alphabet = log.get_alphabet() - ignored_alphabet max_bound = len(net.arcs() - set(ignored_arcs))-1 if len(new_alphabet) == 0: min_bound = 0 else: min_bound = len(new_alphabet)+1 #min_bound = 0 #since the start and final activities cannot belong to the new activities print 'Some arcs are ignored, setting a conservative lower bound of {0}'.format(min_bound) if upper_bound >= 0: max_bound = min(max_bound, upper_bound) print 'Setting manually upper bound to', max_bound print "Reducing number of arcs" last_fine_net = net while min_bound <= max_bound: print "Bounds: [{0},{1}]".format(min_bound, max_bound) avg_bound = (min_bound+max_bound)/2 print "Testing arcs =",avg_bound max_global_arcs = avg_bound stpfile = logprefix+'.it{0}.stp'.format(iteration) print "Generating STP file ({0})...".format(stpfile) try: sys.stdout = open(stpfile,"w") except Exception as ex: print("Error. Cannot open file '%s'. %s" % (stpfile, ex)) quit() cn_stp.generate_stp_from_log( log, var_info, max_global_arcs=max_global_arcs ) sys.stdout = old_stdout #call stp solver print "Calling STP solver..." t0_solver = datetime.datetime.now() stpoutput = subprocess.check_output( [stp_solver, stpfile] ) delta_t = datetime.datetime.now() - t0_solver print "Solver elapsed time:", delta_t.total_seconds(),"s." #print stpoutput if 'Invalid' in stpoutput: print "Model was feasible" net = stp_to_cnet.build_cnet_from_stp( stpoutput.split("\n") ) # if options.file_output: # cn_file = logprefix+'.it{0}.cn'.format(iteration) # net.save(cn_file) last_fine_net = net last_fine_net.stpoutput = stpoutput #max_bound = avg_bound # if we do not want the hassle of having to perform the extraction if avg_bound > net.number_of_arcs(): print "Number of arcs of found C-net:", net.number_of_arcs() if not ignored_arcs: if avg_bound < net.number_of_arcs(): print "ERROR: equation restricting number of arcs is not working. Found C-net has {0} arcs".format(net.number_of_arcs()) quit() max_bound = net.number_of_arcs() - 1 else: # print "found arcs ({0}):".format(len(net.arcs())), net.arcs() # print "ignored arcs ({0}):".format(len(options.ignore_arcs)), set(options.ignore_arcs) # print "bounded arcs ({0}):".format(len(net.arcs() - set(options.ignore_arcs))), net.arcs() - set(options.ignore_arcs) max_bound = len(net.arcs() - set(ignored_arcs)) - 1 # print "new max bound:", max_bound #last_fine_stpoutput = stpoutput else: print "Model was unfeasible" min_bound = avg_bound + 1 iteration += 1 last_fine_net.print_cnet() print "Number of C-net arcs:", last_fine_net.number_of_arcs() print "Total number of iterations:", iteration delta_t = datetime.datetime.now() - t0 print "Elapsed time:", delta_t.total_seconds(),"s." # if options.file_output: # cn_file = logprefix+'.cn' # net.save(cn_file) # if options.interactive_output: # last_fine_net.interactive_output() return last_fine_net
def fitting_cases_in_skeleton(log, skeleton): """Returns a log containing the sequences that would fit in any C-net using the arcs in [skeleton] (regardless of the actual bindings that would be required). To speed up the checking, all sequences whose immediately follows relation is inside [skeleton] are automatically accepted. Similarly if none of its predecessors or successors appears in the skeleton, then the case is sure non-fitting.""" # Be careful with extended strategies. For instance, assume that we check # that, for each activity, it must have a relation with at least one # predecessor activity in the case, and the same for successors). # # This is an overapproximation, since activities could 'steal' obligations # needed by other occurrences of the same activity. e.g., saa with skeleton # (s,a) would pass the check, but will not be really fitting. However, cases = log.get_uniq_cases() set_skeleton = set(skeleton) new_cases = defaultdict(int) old_stdout = sys.stdout stpfile = 'simulate.stp' for case_num, (case, occ) in enumerate(cases.iteritems()): imm_follows = True for i, act in enumerate(case[:-1]): if (act,case[i+1]) not in set_skeleton: imm_follows = False break if not imm_follows: #see if it is directly non-fitting possible = True for i, act in enumerate(case): if i < len(case)-1: shared_succ = [(a,b) for a,b in skeleton if a==act and b in case[i+1:]] if not shared_succ: possible = False break if i > 0: shared_pred = [(a,b) for a,b in skeleton if b==act and a in case[:i]] if not shared_pred: possible = False break if possible: #check with SMT singleton_log = Log(cases=[case]) sys.stdout = open(stpfile,'w') var_info = boolean_variables({case:1}, exclusive_use_arcs=skeleton) variables = var_info.variables print "% Total number of variables:", len(variables) for i in xrange(0,len(variables),10): print ','.join(variables[i:i+10]),': BITVECTOR(1);' generate_structural_stp_inout(singleton_log, var_info) print print "QUERY (FALSE);" print "COUNTEREXAMPLE;" sys.stdout = old_stdout stpoutput = subprocess.check_output( [stp_solver, stpfile] ) smt_verified = 'Invalid' in stpoutput else: print 'Unique case {0} is trivially NON-replayable'.format(case_num) else: print 'Unique case {0} is trivially replayable'.format(case_num) if imm_follows or (possible and smt_verified): new_cases[copy.copy(case)] = occ return Log(uniq_cases=new_cases)