async def solutions( self, timeout: Optional[timedelta] = None, nr_solutions: Optional[int] = None, processes: Optional[int] = None, random_seed: Optional[int] = None, all_solutions=False, intermediate_solutions=False, free_search: bool = False, optimisation_level: Optional[int] = None, verbose: bool = False, debug_output: Optional[Path] = None, **kwargs, ): # Set standard command line arguments cmd: List[Any] = [ "--output-mode", "json", "--output-time", "--output-objective", "--output-output-item", ] # Enable statistics cmd.append("-s") # Process number of solutions to be generated if all_solutions: if nr_solutions is not None: raise ValueError( "The number of solutions cannot be limited when looking " "for all solutions") if self.method != Method.SATISFY: raise NotImplementedError( "Finding all optimal solutions is not yet implemented") if "-a" not in self._solver.stdFlags: raise NotImplementedError( "Solver does not support the -a flag") cmd.append("-a") elif nr_solutions is not None: if nr_solutions <= 0: raise ValueError( "The number of solutions can only be set to a positive " "integer number") if self.method != Method.SATISFY: raise NotImplementedError( "Finding multiple optimal solutions is not yet implemented" ) if "-n" not in self._solver.stdFlags: raise NotImplementedError( "Solver does not support the -n flag") cmd.extend(["-n", str(nr_solutions)]) if "-a" in self._solver.stdFlags and self.method != Method.SATISFY: cmd.append("-a") # Set number of processes to be used if processes is not None: if "-p" not in self._solver.stdFlags: raise NotImplementedError( "Solver does not support the -p flag") cmd.extend(["-p", str(processes)]) # Set random seed to be used if random_seed is not None: if "-r" not in self._solver.stdFlags: raise NotImplementedError( "Solver does not support the -r flag") cmd.extend(["-r", str(random_seed)]) # Enable free search if specified if free_search: if "-f" not in self._solver.stdFlags: raise NotImplementedError( "Solver does not support the -f flag") cmd.append("-f") # Set compiler optimisation level if specified if optimisation_level: cmd.extend(["-O", str(optimisation_level)]) # Set time limit for the MiniZinc solving if timeout is not None: cmd.extend( ["--time-limit", str(int(timeout.total_seconds() * 1000))]) if verbose: cmd.append("-v") for flag, value in kwargs.items(): if not flag.startswith("-"): flag = "--" + flag if type(value) is bool: if value: cmd.append(flag) else: cmd.extend([flag, value]) # Add files as last arguments with self.files() as files, self._solver.configuration() as solver: assert self.output_type is not None cmd.extend(files) # Run the MiniZinc process proc = await self._driver.create_process(cmd, solver=solver) assert isinstance(proc.stderr, StreamReader) assert isinstance(proc.stdout, StreamReader) # Python 3.7+: replace with asyncio.create_task read_stderr = asyncio.ensure_future(_read_all(proc.stderr)) status = Status.UNKNOWN code = 0 remainder: bytes = b"" try: async for raw_sol in _seperate_solutions(proc.stdout, timeout): status = Status.SATISFIED solution, statistics = parse_solution( raw_sol, self.output_type, self._enum_map) yield Result(Status.SATISFIED, solution, statistics) code = await proc.wait() except asyncio.IncompleteReadError as err: # End of Stream has been reached # Read remaining text in buffer code = await proc.wait() remainder = err.partial except (asyncio.TimeoutError, asyncio.CancelledError) as e: # Process was reached hard deadline (timeout + 1 sec) or was # cancelled by the user. # Terminate process and read remaining output proc.terminate() remainder = await _read_all(proc.stdout) if isinstance(e, asyncio.CancelledError): raise finally: # parse the remaining statistics for res in filter(None, remainder.split(SEPARATOR)): new_status = Status.from_output(res, self.method) if new_status is not None: status = new_status solution, statistics = parse_solution( res, self.output_type, self._enum_map) yield Result(status, solution, statistics) # Raise error if required stderr = None if code != 0 or status == Status.ERROR: stderr = await read_stderr raise parse_error(stderr) if debug_output is not None: if stderr is None: stderr = await read_stderr debug_output.write_bytes(stderr)
async def solutions( self, timeout: Optional[timedelta] = None, nr_solutions: Optional[int] = None, processes: Optional[int] = None, random_seed: Optional[int] = None, all_solutions=False, intermediate_solutions=False, free_search: bool = False, optimisation_level: Optional[int] = None, ignore_errors=False, **kwargs, ): # Set standard command line arguments cmd: List[Any] = [ "--output-mode", "json", "--output-time", "--output-objective", "--output-output-item", ] # Enable statistics cmd.append("-s") # Process number of solutions to be generated if all_solutions: if nr_solutions is not None: raise ValueError( "The number of solutions cannot be limited when looking " "for all solutions") if self.method != Method.SATISFY: raise NotImplementedError( "Finding all optimal solutions is not yet implemented") if "-a" not in self._solver.stdFlags: raise NotImplementedError( "Solver does not support the -a flag") cmd.append("-a") elif nr_solutions is not None: if nr_solutions <= 0: raise ValueError( "The number of solutions can only be set to a positive " "integer number") if self.method != Method.SATISFY: raise NotImplementedError( "Finding multiple optimal solutions is not yet implemented" ) if "-n" not in self._solver.stdFlags: raise NotImplementedError( "Solver does not support the -n flag") cmd.extend(["-n", str(nr_solutions)]) if "-a" in self._solver.stdFlags and self.method != Method.SATISFY: cmd.append("-a") # Set number of processes to be used if processes is not None: if "-p" not in self._solver.stdFlags: raise NotImplementedError( "Solver does not support the -p flag") cmd.extend(["-p", str(processes)]) # Set random seed to be used if random_seed is not None: if "-r" not in self._solver.stdFlags: raise NotImplementedError( "Solver does not support the -r flag") cmd.extend(["-r", str(random_seed)]) # Enable free search if specified if free_search: if "-f" not in self._solver.stdFlags: raise NotImplementedError( "Solver does not support the -f flag") cmd.append("-f") # Set compiler optimisation level if specified if optimisation_level: cmd.extend(["-O", str(optimisation_level)]) # Set time limit for the MiniZinc solving if timeout is not None: cmd.extend( ["--time-limit", str(int(timeout.total_seconds() * 1000))]) for flag, value in kwargs.items(): if not flag.startswith("-"): flag = "--" + flag if type(value) is bool: if value: cmd.append(flag) else: cmd.extend([flag, value]) # Add files as last arguments with self.files() as files, self._solver.configuration() as solver: assert self.output_type is not None cmd.extend(files) # Run the MiniZinc process proc = await self._driver.create_process(cmd, solver=solver) assert proc.stderr is not None assert proc.stdout is not None status = Status.UNKNOWN code = 0 deadline = None if timeout is not None: deadline = datetime.now() + timeout + timedelta(seconds=5) remainder: Optional[bytes] = None try: raw_sol: bytes = b"" while not proc.stdout.at_eof(): try: if deadline is None: raw_sol += await proc.stdout.readuntil(SEPARATOR) else: t = deadline - datetime.now() raw_sol += await asyncio.wait_for( proc.stdout.readuntil(SEPARATOR), t.total_seconds()) status = Status.SATISFIED solution, statistics = parse_solution( raw_sol, self.output_type, self._enum_map) yield Result(Status.SATISFIED, solution, statistics) raw_sol = b"" except asyncio.LimitOverrunError as err: raw_sol += await proc.stdout.readexactly(err.consumed) code = await proc.wait() except asyncio.IncompleteReadError as err: # End of Stream has been reached # Read remaining text in buffer code = await proc.wait() remainder = err.partial except asyncio.TimeoutError: # Process was reached hard deadline (timeout + 5 sec) # Kill process and read remaining output proc.kill() await proc.wait() remainder = await proc.stdout.read() finally: # parse the remaining statistics if remainder is not None: final_status = Status.from_output(remainder, self.method) if final_status is not None: status = final_status solution, statistics = parse_solution( remainder, self.output_type, self._enum_map) yield Result(status, solution, statistics) # Raise error if required if code != 0 or status == Status.ERROR: stderr = await proc.stderr.read() raise parse_error(stderr)