<commit_before># -*- coding: utf-8 -*-
"""
PyMzn provides functions that mimic and enhance the tools from the libminizinc
library. With these tools, it is possible to compile a MiniZinc model into
FlatZinc, solve a given problem and get the output solutions directly into the
python code.

The main function that PyMzn provides is the ``minizinc`` function, which
executes the entire workflow for solving a CSP problem encoded in MiniZinc.
Solving a MiniZinc problem with PyMzn is as simple as:::

    import pymzn
    pymzn.minizinc('test.mzn')

The ``minizinc`` function is probably the way to go for most of the problems,
but the ``mzn2fzn`` and ``solns2out`` functions are in the public API to allow
for maximum flexibility. The latter two functions are wrappers of the two
homonym MiniZinc tools for, respectively, converting a MiniZinc model into a
FlatZinc one and getting custom output from the solution stream of a solver.
"""

import os
import itertools
import contextlib

from subprocess import CalledProcessError
from tempfile import NamedTemporaryFile

import pymzn.config as config

from pymzn.bin import run
from ._solvers import gecode
from ._model import MiniZincModel
from pymzn._utils import get_logger
from pymzn._dzn import dzn_eval, dzn


def minizinc(mzn, *dzn_files, data=None, keep=False, output_base=None,
             globals_dir=None, stdlib_dir=None, path=None, eval_output=True,
             output_vars=None, solver=gecode, check_complete=False,
             all_solutions=False, **solver_args):
    """Implements the workflow to solve a CSP problem encoded with MiniZinc.

    It first calls mzn2fzn to compile the fzn and ozn files, then it calls the
    provided solver and in the end it calls the solns2out utility on the
    output of the solver.

    Parameters
    ----------
    mzn : str or MiniZincModel
        The minizinc problem to be solved.  It can be either a string or an
        instance of MiniZincModel.  If it is a string, it can be either the path
        to the mzn file or the content of the model.
    *dzn_files
        A list of paths to dzn files to attach to the mzn2fzn execution,
        provided as positional arguments; by default no data file is attached.
        Data files are meant to be used when there is data that is static across
        several minizinc executions.
    data : dict
        Additional data as a dictionary of variables assignments to supply to
        the mzn2fnz function. The dictionary is then automatically converted to
        dzn format by the pymzn.dzn function. This property is meant to include
        data that dynamically changes across several minizinc executions.
    keep : bool
        Whether to keep the generated mzn, dzn, fzn and ozn files or not. If
        False, the generated files are created as temporary files which will be
        deleted right after the problem is solved. Though pymzn generated files
        are not originally intended to be kept, this property can be used for
        debugging purpose. Notice that in case of error the files are not
        deleted even if this parameter is False.  Default is False.
    output_base : str
        If ``keep=True``, this parameter is used as the base name for the output
        mzn, dzn, fzn and ozn files (extension are attached automatically). This
        name should include the parent directories if different from the working
        one.  Parent directories are not created automatically so they are
        required to exist. If None is provided (default) the name of the input
        file is used. If the mzn input was a content string, then the default
        name 'pymzn' and the working directory are used.
    globals_dir : str
        The name of the directory where to search for global included files in
        the standard library; by default the solver specific global library is
        used.
    stdlib_dir : str
        The name of the directory containing the standard library of minizinc.
        When None (default) the MiniZinc default is used.
    path : str or list
        One or more additional paths to search for included mzn files when
        running ``mzn2fzn``.
    eval_output : bool
        Whether to evaluate the output of the solver or solns2out function into
        Python objects with the ``pymzn.dzn_eval`` function. If the output is
        not evaluated, then a list of strings containing the solutions is
        returned. This is useful when a custom output statement is given in the
        mzn file. The default value is True. When this argument
    output_vars : list of str
        The list of output variables. If not provided, the default list is the
        list of free variables in the model, i.e. those variables that are
        declared but not defined in the model.  This argument is only used when
        ``eval_output`` is True.
    solver : Solver
        An instance of Solver to use to solve the minizinc problem. The default
        is pymzn.gecode.
    check_complete : bool
        If True, a boolean value is returned, in addition to
        the solutions of the problem, indicating the completion status of the
        problem. The returned boolean is True when the solver completed its work
        and, in case ``all_solutions=True``, returned all the solutions; it is
        False when the solver did not complete its work because e.g. was
        interrupted by a timeout.
    all_solutions : bool
        Whether all the solutions must be returned. Notice that this is only
        used if the solver supports returning all solutions, otherwise it is
        ignored. Default is False.
    **solver_args
        Additional arguments to pass to the solver, provided as additional
        keyword arguments to this function check the solver documentation for
        the available arguments.

    Returns
    -------
    list or tuple
        Returns a list of solutions. If eval_output is True, the solutions are
        returned as dictionaries of variable assignments, otherwise they are
        solution strings as returned from the solns2out function. If
        ``check_complete=True`` the result is a tuple containing the solution
        list as first argument and a boolean value indicating the completion
        status of the problem as second argument.
    """
    log = get_logger(__name__)

    if isinstance(mzn, MiniZincModel):
        mzn_model = mzn
    else:
        mzn_model = MiniZincModel(mzn)

    if eval_output:
        mzn_model.dzn_output_stmt(output_vars)

    _globals_dir = globals_dir or solver.globals_dir

    output_dir = None
    output_prefix = 'pymzn'
    if keep:
        if output_base:
            output_dir, output_prefix = os.path.split(output_base)
        elif mzn_model.mzn_file:
            output_dir, mzn_name = os.path.split(mzn_file)
            output_prefix, mzn_ext = os.path.split(mzn_name)
    output_prefix += '_'
    output_file = NamedTemporaryFile(dir=output_dir, prefix=output_prefix,
                                     suffix='.mzn', delete=False, mode='w+',
                                     buffering=1)
    mzn_model.compile(output_file)
    mzn_file = output_file.name

    fzn_file, ozn_file = mzn2fzn(mzn_file, *dzn_files, data=data,
                                 keep_data=keep, path=path,
                                 globals_dir=_globals_dir,
                                 stdlib_dir=stdlib_dir)

    if not solver.support_all and all_solutions:
        log.warning('Solver does not support returning all solutions.')

    solver_check_complete = not solver.support_ozn and check_complete

    out = solver.solve(fzn_file, check_complete=solver_check_complete,
                       all_solutions=all_solutions, **solver_args)
<commit_msg>minizinc: use current working directory if keep=True but a model string is used<commit_after># -*- coding: utf-8 -*-
"""
PyMzn provides functions that mimic and enhance the tools from the libminizinc
library. With these tools, it is possible to compile a MiniZinc model into
FlatZinc, solve a given problem and get the output solutions directly into the
python code.

The main function that PyMzn provides is the ``minizinc`` function, which
executes the entire workflow for solving a CSP problem encoded in MiniZinc.
Solving a MiniZinc problem with PyMzn is as simple as:::

    import pymzn
    pymzn.minizinc('test.mzn')

The ``minizinc`` function is probably the way to go for most of the problems,
but the ``mzn2fzn`` and ``solns2out`` functions are in the public API to allow
for maximum flexibility. The latter two functions are wrappers of the two
homonym MiniZinc tools for, respectively, converting a MiniZinc model into a
FlatZinc one and getting custom output from the solution stream of a solver.
"""

import os
import itertools
import contextlib

from subprocess import CalledProcessError
from tempfile import NamedTemporaryFile

import pymzn.config as config

from pymzn.bin import run
from ._solvers import gecode
from ._model import MiniZincModel
from pymzn._utils import get_logger
from pymzn._dzn import dzn_eval, dzn


def minizinc(mzn, *dzn_files, data=None, keep=False, output_base=None,
             globals_dir=None, stdlib_dir=None, path=None, eval_output=True,
             output_vars=None, solver=gecode, check_complete=False,
             all_solutions=False, **solver_args):
    """Implements the workflow to solve a CSP problem encoded with MiniZinc.

    It first calls mzn2fzn to compile the fzn and ozn files, then it calls the
    provided solver and in the end it calls the solns2out utility on the
    output of the solver.

    Parameters
    ----------
    mzn : str or MiniZincModel
        The minizinc problem to be solved.  It can be either a string or an
        instance of MiniZincModel.  If it is a string, it can be either the path
        to the mzn file or the content of the model.
    *dzn_files
        A list of paths to dzn files to attach to the mzn2fzn execution,
        provided as positional arguments; by default no data file is attached.
        Data files are meant to be used when there is data that is static across
        several minizinc executions.
    data : dict
        Additional data as a dictionary of variables assignments to supply to
        the mzn2fnz function. The dictionary is then automatically converted to
        dzn format by the pymzn.dzn function. This property is meant to include
        data that dynamically changes across several minizinc executions.
    keep : bool
        Whether to keep the generated mzn, dzn, fzn and ozn files or not. If
        False, the generated files are created as temporary files which will be
        deleted right after the problem is solved. Though pymzn generated files
        are not originally intended to be kept, this property can be used for
        debugging purpose. Notice that in case of error the files are not
        deleted even if this parameter is False.  Default is False.
    output_base : str
        If ``keep=True``, this parameter is used as the base name for the output
        mzn, dzn, fzn and ozn files (extension are attached automatically). This
        name should include the parent directories if different from the working
        one.  Parent directories are not created automatically so they are
        required to exist. If None is provided (default) the name of the input
        file is used. If the mzn input was a content string, then the default
        name 'pymzn' and the working directory are used.
    globals_dir : str
        The name of the directory where to search for global included files in
        the standard library; by default the solver specific global library is
        used.
    stdlib_dir : str
        The name of the directory containing the standard library of minizinc.
        When None (default) the MiniZinc default is used.
    path : str or list
        One or more additional paths to search for included mzn files when
        running ``mzn2fzn``.
    eval_output : bool
        Whether to evaluate the output of the solver or solns2out function into
        Python objects with the ``pymzn.dzn_eval`` function. If the output is
        not evaluated, then a list of strings containing the solutions is
        returned. This is useful when a custom output statement is given in the
        mzn file. The default value is True. When this argument
    output_vars : list of str
        The list of output variables. If not provided, the default list is the
        list of free variables in the model, i.e. those variables that are
        declared but not defined in the model.  This argument is only used when
        ``eval_output`` is True.
    solver : Solver
        An instance of Solver to use to solve the minizinc problem. The default
        is pymzn.gecode.
    check_complete : bool
        If True, a boolean value is returned, in addition to
        the solutions of the problem, indicating the completion status of the
        problem. The returned boolean is True when the solver completed its work
        and, in case ``all_solutions=True``, returned all the solutions; it is
        False when the solver did not complete its work because e.g. was
        interrupted by a timeout.
    all_solutions : bool
        Whether all the solutions must be returned. Notice that this is only
        used if the solver supports returning all solutions, otherwise it is
        ignored. Default is False.
    **solver_args
        Additional arguments to pass to the solver, provided as additional
        keyword arguments to this function check the solver documentation for
        the available arguments.

    Returns
    -------
    list or tuple
        Returns a list of solutions. If eval_output is True, the solutions are
        returned as dictionaries of variable assignments, otherwise they are
        solution strings as returned from the solns2out function. If
        ``check_complete=True`` the result is a tuple containing the solution
        list as first argument and a boolean value indicating the completion
        status of the problem as second argument.
    """
    log = get_logger(__name__)

    if isinstance(mzn, MiniZincModel):
        mzn_model = mzn
    else:
        mzn_model = MiniZincModel(mzn)

    if eval_output:
        mzn_model.dzn_output_stmt(output_vars)

    _globals_dir = globals_dir or solver.globals_dir

    output_dir = None
    output_prefix = 'pymzn'
    if keep:
        if output_base:
            output_dir, output_prefix = os.path.split(output_base)
        elif mzn_model.mzn_file:
            output_dir, mzn_name = os.path.split(mzn_file)
            output_prefix, mzn_ext = os.path.split(mzn_name)
        else:
            output_dir = os.getcwd()
    output_prefix += '_'
    output_file = NamedTemporaryFile(dir=output_dir, prefix=output_prefix,
                                     suffix='.mzn', delete=False, mode='w+',
                                     buffering=1)
    mzn_model.compile(output_file)
    mzn_file = output_file.name

    fzn_file, ozn_file = mzn2fzn(mzn_file, *dzn_files, data=data,
                                 keep_data=keep, path=path,
                                 globals_dir=_globals_dir,
                                 stdlib_dir=stdlib_dir)

    if not solver.support_all and all_solutions:
        log.warning('Solver does not support returning all solutions.')

    solver_check_complete = not solver.support_ozn and check_complete

    out = solver.solve(fzn_file, check_complete=solver_check_complete,
                       all_solutions=all_solutions, **solver_args)

{'fraction_non_alphanumeric': 0.04497033871276392, 'fraction_numerical': 0.001530905147668559, 'mean_word_length': 2.715165876777251, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6779446', 'n_tokens_mistral': 4108, 'n_tokens_neox': 4037, 'n_words': 1941}