async def solve_async(
        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,
        **kwargs,
    ):
        status = Status.UNKNOWN
        solution = None
        statistics: Dict[str, Any] = {}

        multiple_solutions = (all_solutions or intermediate_solutions
                              or nr_solutions is not None)
        if multiple_solutions:
            solution = []

        async for result in self.solutions(
                timeout=timeout,
                nr_solutions=nr_solutions,
                processes=processes,
                random_seed=random_seed,
                all_solutions=all_solutions,
                free_search=free_search,
                optimisation_level=optimisation_level,
                **kwargs,
        ):
            status = result.status
            statistics.update(result.statistics)
            if result.solution is not None:
                if multiple_solutions:
                    solution.append(result.solution)
                else:
                    solution = result.solution
        return Result(status, solution, statistics)
    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)
Exemple #3
0
    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)