def post(self): logger.info("GenerateConstraints post") iterations = int(self.get_argument('iterations')) generator_type = self.get_argument('generator') if not generator_type in ['planes', 'rectangles', 'quads']: return self._json_error("Invalid generator set", 400) result = self._getResultData(self._get_session('current_result', None)) if result is None: return self._json_error("Unable to load result data", 500) smt2interface, generator = self.make_gen(generator_type) new_samples, unsat = yield self.executor.submit( self.analyze, smt2interface, generator, iterations) if len(new_samples) == 0 and len(unsat) == 0: return self._json_error("SMT solver did not return an answer") raise NotImplementedError() samples = self._get_session('samples', InstantiationResultDict()) # FIXME # Clear all regions, resumption not supported (yet) constraints = [] #self._get_session('regions', []) samples.update(new_samples) constraints += unsat self._set_session('samples', samples) self._set_session('regions', constraints) return self._json_ok({ 'sat': _jsonSamples(new_samples), 'unsat': unsat })
def post(self): logger.debug("Samples post request") #print(self.request.body) sampling_information = json_decode(self.request.body) #print(sampling_information) coordinates = sampling_information # Get the current prism file and save it temporarily prism_files = self._get_session("prism-files", {}) #assert sampling_information["prism_file"] in prism_files #prism_file = PrismFile(prism_files[sampling_information["prism_file"]]) if coordinates is None: return self._json_error("Unable to read coordinates", 400) result = self._getResultData(self._get_session('current_result', None)) samples = self._get_session( 'samples', InstantiationResultDict(parameters=result.parameters)) socket = self._get_socket() sampling_interface = getSampler(self._get_session('sampler'), result) coordinates = [Point(Rational(x), Rational(y)) for x, y in coordinates] sample_points = result.parameters.instantiate(coordinates) new_samples = sampling_interface.perform_sampling(sample_points) if socket is not None: socket.send_samples(new_samples) samples.update(new_samples) self._set_session('samples', samples) return self._json_ok(_jsonSamples(new_samples))
def get(self): logger.debug("Samples get request") result = self._getResultData(self._get_session('current_result', None)) flattenedsamples = _jsonSamples( self._get_session( 'samples', InstantiationResultDict(parameters=result.parameters))) return self._json_ok(flattenedsamples)
def __init__(self, sampler, parameters, region, samples=None): """ @param sampler Sampler used to generate new samples @param parameters VariableOrder @param samples SampleDict pre-existing samples, which is copied. None is allowed """ self.sampler = sampler self.parameters = parameters self.region = region self.samples = samples.copy() if samples else InstantiationResultDict( parameters=parameters) assert prophesy.config is not None self.distance = prophesy.config.configuration.get_sampling_min_distance( )
def perform_sampling(self, sample_points, surely_welldefined=False): if not surely_welldefined: logger.warning( "Sampling assumes (without any checks) that the point is welldefined" ) # Perform sampling with model instantiator logger.debug("Call stormpy for sampling") parameter_mapping = self.get_parameter_mapping( sample_points[0].get_parameters()) samples = InstantiationResultDict({ p: self.sample_single_point(p, parameter_mapping) for p in sample_points }) logger.debug("Sampling with stormpy successfully finished") return samples
def analyze(self, smt2interface, generator, iterations=-1): if iterations == 0: return ({}, []) socket = self._get_socket() #smt2interface.run() unsat = [] result = self._getResultData(self._get_session('current_result', None)) new_samples = InstantiationResultDict(parameters=result.parameters) for result in generator: (check_result, data) = result if check_result is RegionCheckResult.Satisfied: (poly, safe) = data unsat.append((_jsonPoly(poly), bool(safe))) if socket is not None: socket.send_constraints([unsat[-1]]) elif check_result is RegionCheckResult.CounterExample: (sample, safe) = data new_samples[sample.pt] = sample.val if socket is not None: socket.send_samples({sample.pt: sample.val}) else: assert check_result is RegionCheckResult.Unknown print("Check result 'unknown' not considered.") #TODO refine pass if self._check_canceled(): break iterations -= 1 if iterations == 0: break smt2interface.stop() return (new_samples, unsat)
def post(self): #request = json_decode(self.request.body) #safe = bool(request['safe']) #coordinates = request['coordinates'] safe = self.get_argument('constr-mode') == "safe" coordinates = json_decode(self.get_argument('coordinates')) if coordinates is None: return self._json_error("Unable to read coordinates", 400) coordinates = [(float(x), float(y)) for x, y in coordinates] if coordinates[0] == coordinates[-1]: # Strip connecting point if any coordinates = coordinates[:-1] smt2interface, generator = self.make_gen("poly") generator.add_polygon(Polygon(coordinates), safe) new_samples, unsat = yield self.executor.submit( self.analyze, smt2interface, generator) if len(new_samples) == 0 and len(unsat) == 0: return self._json_error("SMT solver did not return an answer") result = self._getResultData(self._get_session('current_result', None)) if result is None: return self._json_error("Unable to load result data", 500) samples = self._get_session( 'samples', InstantiationResultDict(parameters=result.parameters)) constraints = self._get_session('regions', []) samples.update(new_samples) constraints += unsat self._set_session('samples', samples) self._set_session('regions', constraints) return self._json_ok({ 'sat': _jsonSamples(new_samples), 'unsat': unsat })
def make_gen(self, type): result = self._getResultData(self._get_session('current_result', None)) if result is None: return self._json_error("Unable to load result data", 500) samples = self._get_session( 'samples', InstantiationResultDict(parameters=result.parameters)) threshold = self._get_session('threshold', Rational("1/2")) smt2interface = getSat(self._get_session('sat')) smt2interface.run() problem_description = ProblemDescription() problem_description.solution_function = result.ratfunc problem_description.parameters = result.parameters problem_description.threshold = threshold checker = SolutionFunctionRegionChecker(smt2interface) checker.initialize(problem_description) if type == 'planes': return self._json_error("Planes generator was dropped in v2") elif type == 'rectangles': return self._json_error( "Rectangles generator was temporarily dropped in v2") elif type == 'quads': generator = HyperRectangleRegions( samples, result.parameters, threshold, checker, problem_description.welldefined_constraints, problem_description.graph_preserving_constraints) elif type == 'poly': raise NotImplementedError( "We do no longer support arbitrary polygons.") # generator = ConstraintPolygon(samples, result.parameters, threshold, checker, problem_description.welldefined_constraints, problem_description.graph_preserving_constraints) else: return self._json_error("Bad generator") generator.plot = False return (smt2interface, generator)
def post(self): logger.debug("GenerateSamples post request") iterations = int(self.get_argument('iterations')) if iterations < 0: return self._json_error("Number of iterations must be >= 0", 400) threshold = self._get_session('threshold', 0.5) threshold = Rational(threshold) #TODO make sure threshold is a rational all the time. generator_type = self.get_argument('generator') if not generator_type in ['uniform', 'linear', 'delaunay']: return self._json_error("Invalid generator set " + generator_type, 400) if generator_type == 'uniform' and iterations < 2: return self._json_error( "Number of iterations must be >= 2 for uniform generation", 400) if iterations == 0: # Nothing to do return self._json_ok(_jsonSamples({})) result = self._getResultData(self._get_session('current_result', None)) if result is None: return self._json_error("Unable to load result data", 500) socket = self._get_socket() parameters = result.parameters samples = self._get_session( 'samples', InstantiationResultDict(parameters=parameters)) new_samples = InstantiationResultDict(parameters=parameters) sampling_interface = getSampler(self._get_session('sampler'), result) if generator_type == 'uniform': intervals = result.parameters.get_parameter_bounds() samples_generator = UniformSampleGenerator(sampling_interface, result.parameters, samples, iterations) elif generator_type == "linear": samples_generator = LinearRefinement(sampling_interface, result.parameters, samples, threshold) elif generator_type == "delaunay": return self._json_error("Delaunay refinement was dropped in v2", 400) else: assert False, "Bad generator" def generate_samples(samples_generator, iterations): for (generated_samples, _) in zip(samples_generator, range(0, iterations)): new_samples.update(generated_samples) if socket is not None: socket.send_samples(generated_samples) if self._check_canceled(): break return new_samples new_samples = yield self.executor.submit(generate_samples, samples_generator, iterations) samples.update(new_samples) self._set_session('samples', samples) return self._json_ok(_jsonSamples(new_samples))
def delete(self): raise NotImplementedError() self._set_session("samples", InstantiationResultDict()) # FIXME return self._json_ok()
def read_samples_file(path, parameters): """ Reads sample files. The first line specifies the parameters (with an optional "Result" for the last column). The second line optionally specifies a threshold. This is important if we have binary samples, (for which we do not know the value, but just whether it is above or below the threshold). The remaining lines give the parameter values and the value. This value is either a number or "above" or "below". :param path: :return: """ threshold = None with open(path, 'r') as f: lines = [l.strip() for l in f.readlines()] if len(lines) <= 2: raise RuntimeError("Samples file is empty or malformed") # read first line with variable names parameter_names = lines[0].split() if parameter_names[-1] == "Result": parameter_names = parameter_names[:-1] start = 1 for par_name, par in zip(parameter_names, parameters): if par_name != par.name: raise ValueError( "Parameter names {} do not coincide with given parameters {}" .format(parameter_names, parameters)) #Ignore thresholds if lines[1].startswith("Threshold"): if len(lines[1].split()) != 2: raise IOError("Invalid input on line 2") threshold = Rational(lines[1].split()[1]) start += 1 samples = InstantiationResultDict(parameters=parameters) skip_next = False for i, line in enumerate(lines[start:]): if skip_next: skip_next = False continue items = line.split() if len(items) - 1 != len(parameter_names): # Prism reports that probs are negative: if line.find("are negative") > 0: coords = map(Rational, items[:len(parameter_names)]) samples[ParameterInstantiation.from_point( Point(*coords), parameters)] = InstantiationResultFlag.NOT_WELLDEFINED skip_next = True continue logger.error("Invalid input in %s on line %s: '%s'", path, str(i + start), line) continue if items[-1] == "below": #TODO raise NotImplementedError( "Inexact sampling results are not yet supported in v2") #value = SAMPLE_BELOW elif items[-1] == "above": #TODO raise NotImplementedError( "Inexact sampling results are not yet supported in v2") elif items[-1] == "InstantiationResultFlag.NOT_WELLDEFINED": value = InstantiationResultFlag.NOT_WELLDEFINED else: value = Rational(items[-1]) coords = map(Rational, items[:-1]) samples[ParameterInstantiation.from_point(Point(*coords), parameters)] = value logger.debug("Parameters: %s", str(parameters)) return parameters, threshold, samples
def perform_sampling(self, sample_points, surely_welldefined=False): logger.info("Perform batch sampling") if self.pctlformula is None: raise NotEnoughInformationError("pctl formula missing") if not self._has_model_set(): raise NotEnoughInformationError("model missing") # create a temporary file for the result. ensure_dir_exists(prophesy.config.configuration.get_intermediate_dir()) def sample_single_point(parameter_instantiation): fd, resultfile = tempfile.mkstemp( suffix=".txt", dir=prophesy.config.configuration.get_intermediate_dir(), text=True) os.close(fd) const_values_string = ",".join([ "{}={}".format(parameter.name, val) for parameter, val in parameter_instantiation.items() ]) constants_string = self.constants.to_key_value_string( to_float=False) if self.constants else "" if constants_string != "": const_values_string = const_values_string + "," + constants_string args = [ self. main_location, # Parametric DRN not supported with main version. '--prop', str(self.pctlformula), "-const", const_values_string ] if self.drnfile: args += ['-drn', self.drnfile.location] elif self.prismfile: args += ['--prism', self.prismfile.location] if self.prismfile.model_type == ModelType.CTMC: args += ['-pc'] if self.bisimulation == BisimulationType.strong: args.append('--bisimulation') logger.info("Call storm") ret_code = run_tool(args, quiet=False, outputfile=resultfile) if ret_code != 0: logger.debug("Storm output logged in %s", resultfile) # Do not crash here else: logger.info("Storm call finished successfully") logger.debug("Storm output logged in %s", resultfile) result = None with open(resultfile) as f: result_in_next_line = False for line in f: if result_in_next_line: result = pc.Rational(line) break if "Substitution yielding negative" in line: result = InstantiationResultFlag.NOT_WELLDEFINED ret_code = 0 break match = re.search(r"Result (.*):(.*)", line) if match: # Check for exact result match_exact = re.search(r"(.*) \(approx. .*\)", match.group(2)) if match_exact: result = pc.Rational(match_exact.group(1)) break else: if match.group(2).strip() == "": result_in_next_line = True continue result = pc.Rational(match.group(2)) break if ret_code != 0: raise RuntimeError("Storm crashed.") if result is None: raise RuntimeError( "Could not find result from storm in {}".format( resultfile)) os.remove(resultfile) return result samples = InstantiationResultDict( {p: sample_single_point(p) for p in sample_points}) return samples
def _analyse_region(self, region, welldefined, safe, check_for_eq = False): """ Analyse the given region. :param region: Region. :param welldefined: Flag iff the region is welldefined. :param safe: Flag iff the region should be considered safe. :return: Tuple (RegionCheckResult, (region/counterexample, safe)) """ logger.debug("Analyse region %s", region) if welldefined == WelldefinednessResult.Illdefined: self.ignore_region() self.record_illdefined(region) return WelldefinednessResult.Illdefined, region assert welldefined == WelldefinednessResult.Welldefined checkresult, additional = self.checker.analyse_region(region, safe, check_for_eq) if checkresult == RegionCheckResult.Satisfied: # remove unnecessary samples which are covered already by regions # TODO region might contain this info, why not use that. self.safe_samples = InstantiationResultDict( {k: v for k, v in self.safe_samples.items() if not region.contains(k.get_point(self.parameters))}, parameters=self.parameters) self.bad_samples = InstantiationResultDict( {k: v for k, v in self.bad_samples.items() if not region.contains(k.get_point(self.parameters))}, parameters=self.parameters) # update everything self.accept_region() self.record_accepted(region, safe) return checkresult, (region, safe) elif checkresult == RegionCheckResult.CounterExample: # add new point as counter example to existing regions self.reject_region(additional) self.record_cex(region, safe, additional) return checkresult, (additional, safe) elif checkresult == RegionCheckResult.Splitted: safe_regions, bad_regions, remaining_regions = additional self.record_results(safe_regions, bad_regions) self.refine_region(remaining_regions) return checkresult, (region, safe) #TODO why do we need to return this? elif checkresult == RegionCheckResult.Refined: # We refined the existing region. # additional should contain the candidate for the counterexample. # compute setminus operation to get accepted constraints: #check if additional actually is a list of hyperrects. if isinstance(additional, HyperRectangle) and isinstance(region, HyperRectangle): accepted_regions = region.setminus(additional) # calculate the accepted regions self.refine_region([additional]) self.record_accepted(accepted_regions, safe) return checkresult, (accepted_regions, safe) elif checkresult == RegionCheckResult.Homogenous: logger.warning("Still have to check that there is at least one sample here.") self.safe_samples = InstantiationResultDict( {k: v for k, v in self.safe_samples.items() if not region.contains(k.get_point(self.parameters))}, parameters=self.parameters) self.bad_samples = InstantiationResultDict( {k: v for k, v in self.bad_samples.items() if not region.contains(k.get_point(self.parameters))}, parameters=self.parameters) self.accept_region() self.record_accepted(region, safe) return checkresult, (region, safe) elif checkresult == RegionCheckResult.Inhomogenous: if additional: self.reject_region(additional) self.record_border(region, additional) else: self.record_contains_border(region, safe) self.fail_region(homogeneity=True) return checkresult, (region, safe) else: self.fail_region(homogeneity=check_for_eq) self.record_unknown(region, safe) return RegionCheckResult.Unknown, (region, safe)
class RegionGenerator: """ A generator for regions. This class acts as an iterable that generates new regions (or counterexamples), until the search space is exhausted (which possibly never happens). """ __metaclass__ = ABCMeta def __init__(self, samples, parameters, region, threshold, checker, wd_constraints, gp_constraints, generate_plot=True, allow_homogeneity=False): """ Constructor. :param samples: List of samples. :param parameters: Parameters of the model. :param threshold: Threshold. :param checker: Region checker. :param wd_constraints: Well-defined constraints. :param gp_constraints: Graph-preserving constraints. """ self.safe_samples, self.bad_samples, self.illdefined_samples = samples.copy().split(threshold) self.parameters = parameters self.threshold = threshold self.max_area_sum = region.size() self.checker = checker # Stores all regions as triple ([constraint], polygon representation, bad/safe) self.all_polys = [] self.safe_polys = [] self.bad_polys = [] self.illdefined_polys = [] self.new_samples = {} self.wd_constraints = wd_constraints self.gp_constraints = gp_constraints self._records = [] self._iteration_timer = None # Options for plotting. self._plot_candidates = False self.plot = generate_plot self.plot_source_dir = None self._source_index = 1 if generate_plot and len(self.parameters) > 2: logger.warning("Plotting for more than two dimensions not supported") self.plot = False self.first_pdf = True from prophesy.config import configuration if self.plot: ensure_dir_exists(configuration.get_plots_dir()) _, self.result_file = tempfile.mkstemp(suffix=".pdf", prefix="result_", dir=configuration.get_plots_dir()) self.allow_homogenous_check = allow_homogeneity def __iter__(self): # Prime the generator return next(self) def __next__(self): self.start_iteration() self.start_generation() region_info = self.next_region() self.stop_generation() while region_info is not None: polygon, welldefined, area_safe, check_for_eq = region_info if self._plot_candidates: self.plot_candidate() self.start_analysis() res = self._analyse_region(polygon, welldefined, area_safe, check_for_eq) self.stop_analysis() self.stop_iteration() yield res self.start_iteration() # get next constraint depending on algorithm self.start_generation() region_info = self.next_region() self.stop_generation() # Remove last record as there is no next region self._records.pop(-1) def _add_pdf(self, name): """ Add PDF with name to result.pdf in tmp directory. """ from prophesy.config import modules if not modules.is_module_available("pypdf2"): logging.warning("Module 'PyPDF2' is not available. PDF export is not supported.") return # Load module as it is available from PyPDF2 import PdfFileMerger, PdfFileReader if self.first_pdf: self.first_pdf = False shutil.copyfile(name, self.result_file) logger.debug("Plot file located at {0}".format(self.result_file)) else: merger = PdfFileMerger() merger.append(PdfFileReader(self.result_file, 'rb')) merger.append(PdfFileReader(name, 'rb')) merger.write(self.result_file) @abstractmethod def plot_candidate(self): """ Plot the current candidate. """ raise NotImplementedError("Abstract parent method") def plot_results(self, *args, **kwargs): """ Plot results. :param args: Arguments. :param kwargs: Arguments. """ if not self.plot: return from prophesy.output.plot import Plot from prophesy.config import configuration if self.plot_source_dir is None: self.plot_source_dir = tempfile.mkdtemp(suffix=None, prefix="src_", dir=configuration.get_plots_dir()) # Extend arguments poly_green = kwargs.get('poly_green', []) kwargs['poly_green'] = poly_green + self.safe_polys poly_red = kwargs.get('poly_red', []) kwargs['poly_red'] = poly_red + self.bad_polys poly_black = kwargs.get('poly_black', []) kwargs['poly_black'] = poly_black + self.illdefined_polys # Split samples appropriately samples_green = [instantiation.get_point(self.parameters) for instantiation in self.safe_samples.keys()] samples_red = [instantiation.get_point(self.parameters) for instantiation in self.bad_samples.keys()] samples_black = [instantiation.get_point(self.parameters) for instantiation in self.illdefined_samples.keys()] _, result_tmp_file = tempfile.mkstemp(".pdf", dir=configuration.get_plots_dir()) _, result_src_file = tempfile.mkstemp(".pgf", prefix="{:03d}_".format(self._source_index), dir=self.plot_source_dir) Plot.plot_results(parameters=self.parameters, samples_green=samples_green, samples_red=samples_red, samples_black=samples_black, path_to_pdf=result_tmp_file, path_to_src=result_src_file, *args, **kwargs) self._source_index += 1 self._add_pdf(result_tmp_file) os.unlink(result_tmp_file) def export_results(self, path): logger.debug("Write results to %s", path) with open(path, 'w+') as file: for idx, r in enumerate(self._records): for reg in r.safe_regions: file.write("{}: {}\n".format(reg, "safe")) for reg in r.bad_regions: file.write("{}: {}\n".format(reg, "unsafe")) @abstractmethod def next_region(self): """ Generate a new region. :return Tuple (new region, well-definedness, safe/unsafe) or None if no next region exists. """ raise NotImplementedError("Abstract parent method") @abstractmethod def fail_region(self, homogeneity=False): """ Called after a region could not be checked, usually due to memout or timeout. Updates the current set of regions. """ raise NotImplementedError("Abstract parent method") @abstractmethod def reject_region(self, sample): """ Called after a region is rejected (sample found). :param sample: Sample acting as a counterexample for the constraint. """ raise NotImplementedError("Abstract parent method") @abstractmethod def ignore_region(self): """ Called for a region which is overall ill-defined. Skip it. """ raise NotImplementedError("Abstract parent method") @abstractmethod def accept_region(self): """ Called after a region is accepted. """ raise NotImplementedError("Abstract parent method") def record_results(self, safe_regions, bad_regions): logger.info("Partial results for region.") if not isinstance(safe_regions, list): safe_regions = [safe_regions] if not isinstance(bad_regions, list): safe_regions = [bad_regions] for r in safe_regions: self.all_polys.append((r, RegionCheckResult.Satisfied)) self.safe_polys.append(r) for r in bad_regions: self.all_polys.append((r, RegionCheckResult.Satisfied)) self.bad_polys.append(r) self._records[-1].set_regions(safe_regions, bad_regions) self._records[-1].set_result(RegionCheckResult.Satisfied) def record_accepted(self, region, safe): """ Record the accepted region. :return: """ logger.info("Region accepted") if not isinstance(region, list): region = [region] for r in region: self.all_polys.append((r, RegionCheckResult.Satisfied)) if safe: self.safe_polys.append(r) else: self.bad_polys.append(r) self._records[-1].set_region(region, safe) self._records[-1].set_result(RegionCheckResult.Satisfied) def record_cex(self, region, safe, additional): """ :param additional: An additional sample. :return: """ logger.info("Counterexample found") if additional.result >= self.threshold: self.safe_samples[additional.instantiation] = additional.result else: self.bad_samples[additional.instantiation] = additional.result self._records[-1].set_region(region, safe) self._records[-1].set_result(RegionCheckResult.CounterExample) def record_border(self, region, additional): logger.info("Border found") self.border_samples[additional.instantiation] = additional.result self._records[-1].set_region(region, None) self._records[-1].set_result(RegionCheckResult.CounterExample) def record_contains_border(self, region, safe): logger.info("Contains border (no particular point found)") self._records[-1].set_region(region, safe) self._records[-1].set_result(RegionCheckResult.Unknown) def record_unknown(self, region, safe): logger.info("No result found") self._records[-1].set_region(region, safe) self._records[-1].set_result(RegionCheckResult.Unknown) def record_illdefined(self, region): logger.info("Region is illdefined") self.all_polys.append((region, WelldefinednessResult.Illdefined)) self.illdefined_polys.append(region) self._records[-1].set_result(WelldefinednessResult.Illdefined) def start_iteration(self): logger.info("Start next iteration") self._records.append(GenerationRecord()) self._records[-1].start_iteration_timer() def start_analysis(self): self._records[-1].start_analysis_timer() def stop_analysis(self): self._records[-1].stop_analysis_timer() def start_generation(self): self._records[-1].start_generation_timer() def stop_generation(self): self._records[-1].stop_generation_timer() def stop_iteration(self): self._records[-1].stop_iteration_timer() logger.debug("Done with iteration: took %s", str(self._records[-1].iteration_time)) def generate_constraints(self, max_iter=-1, max_area=1, plot_every_n=1, plot_candidates=True, export_statistics=None): """ Iteratively generate new regions, heuristically, attempting to find the largest safe or unsafe area. :param max_iter: Number of regions to generate/check at most (not counting SMT failures), -1 for unbounded :param max_area: Maximal area percentage that should be covered. :param plot_every_n: How often should the plot be appended to the PDF. :param plot_candidates: True, iff candidates should be plotted. :return Tuple (safe regions, unsafe regions, samples) """ if max_iter == 0: return self.safe_polys, self.bad_polys, self.new_samples self._plot_candidates = plot_candidates for result, additional_info in self: if export_statistics: self.export_stats(export_statistics, update=True) max_iter -= 1 # Check termination criteria area_sum = sum(poly.size() for poly, safe in self.all_polys) logger.debug("Current area is {}, remaining number of iterations is {}".format(area_sum, max_iter)) if area_sum >= max_area * self.max_area_sum: break if max_iter == 0: break # Plot intermediate result if result != RegionCheckResult.Unknown: # and len(self.all_polys) % plot_every_n == 0: self.plot_results(display=True) # Plot the final outcome if self.plot: self.plot_results(display=False) logger.info("Generation complete, plot located at {} and sources at {}".format(self.result_file, self.plot_source_dir)) # Print results logger.info(self.generate_header()) logger.info(self.generate_stats(update=True)) return self.safe_polys, self.bad_polys, self.new_samples def generate_header(self): return "\t".join( ["N", "cons. area", "res", "safe", "gentime", "anatime", "ttime", "cov. area", "cov. safe area", "cumgentime", "cumanatime", "cumttime"]) def generate_stats(self, update=False): stats = "" cov_area = 0.0 safe_area = 0.0 cumulative_generation_time = 0.0 cumulative_analysis_time = 0.0 cumulative_total_time = 0.0 for idx, r in enumerate(self._records): cov_area += float(r.covered_area) safe_area += float(r.covered_safe_area) cumulative_generation_time += r.generation_time cumulative_analysis_time += r.analysis_time cumulative_total_time += r.iteration_time if not update or len(self._records) == idx + 1: stats += "{}\t{:.5f}\t\t{}\t{}\t{:.2f}\t{:.2f}\t{:.2f}\t{:.2f}\t\t{:.2f}\t\t{:.2f}\t\t{:.2f}\t\t{:.2f}\n".format( idx, float(r.area), r.result, r.safe, r.generation_time, r.analysis_time, r.iteration_time, cov_area, safe_area, cumulative_generation_time, cumulative_analysis_time, cumulative_total_time) return stats def export_stats(self, filename, update=False): logger.debug("Write stats to %s (update == %s)", filename, update) with open(filename, 'a') as file: if not update or len(self._records) == 1: file.write(self.generate_header() + "\n") file.write(self.generate_stats(update)) def _analyse_region(self, region, welldefined, safe, check_for_eq = False): """ Analyse the given region. :param region: Region. :param welldefined: Flag iff the region is welldefined. :param safe: Flag iff the region should be considered safe. :return: Tuple (RegionCheckResult, (region/counterexample, safe)) """ logger.debug("Analyse region %s", region) if welldefined == WelldefinednessResult.Illdefined: self.ignore_region() self.record_illdefined(region) return WelldefinednessResult.Illdefined, region assert welldefined == WelldefinednessResult.Welldefined checkresult, additional = self.checker.analyse_region(region, safe, check_for_eq) if checkresult == RegionCheckResult.Satisfied: # remove unnecessary samples which are covered already by regions # TODO region might contain this info, why not use that. self.safe_samples = InstantiationResultDict( {k: v for k, v in self.safe_samples.items() if not region.contains(k.get_point(self.parameters))}, parameters=self.parameters) self.bad_samples = InstantiationResultDict( {k: v for k, v in self.bad_samples.items() if not region.contains(k.get_point(self.parameters))}, parameters=self.parameters) # update everything self.accept_region() self.record_accepted(region, safe) return checkresult, (region, safe) elif checkresult == RegionCheckResult.CounterExample: # add new point as counter example to existing regions self.reject_region(additional) self.record_cex(region, safe, additional) return checkresult, (additional, safe) elif checkresult == RegionCheckResult.Splitted: safe_regions, bad_regions, remaining_regions = additional self.record_results(safe_regions, bad_regions) self.refine_region(remaining_regions) return checkresult, (region, safe) #TODO why do we need to return this? elif checkresult == RegionCheckResult.Refined: # We refined the existing region. # additional should contain the candidate for the counterexample. # compute setminus operation to get accepted constraints: #check if additional actually is a list of hyperrects. if isinstance(additional, HyperRectangle) and isinstance(region, HyperRectangle): accepted_regions = region.setminus(additional) # calculate the accepted regions self.refine_region([additional]) self.record_accepted(accepted_regions, safe) return checkresult, (accepted_regions, safe) elif checkresult == RegionCheckResult.Homogenous: logger.warning("Still have to check that there is at least one sample here.") self.safe_samples = InstantiationResultDict( {k: v for k, v in self.safe_samples.items() if not region.contains(k.get_point(self.parameters))}, parameters=self.parameters) self.bad_samples = InstantiationResultDict( {k: v for k, v in self.bad_samples.items() if not region.contains(k.get_point(self.parameters))}, parameters=self.parameters) self.accept_region() self.record_accepted(region, safe) return checkresult, (region, safe) elif checkresult == RegionCheckResult.Inhomogenous: if additional: self.reject_region(additional) self.record_border(region, additional) else: self.record_contains_border(region, safe) self.fail_region(homogeneity=True) return checkresult, (region, safe) else: self.fail_region(homogeneity=check_for_eq) self.record_unknown(region, safe) return RegionCheckResult.Unknown, (region, safe)