<commit_before>"""Implement the GeometricProgram class"""
import sys
from time import time
import numpy as np
from ..nomials import NomialData
from ..small_classes import CootMatrix, SolverLog, Numbers
from ..keydict import KeyDict
from ..small_scripts import mag
from ..solution_array import SolutionArray
from .costed import CostedConstraintSet
from .set import ConstraintSet


DEFAULT_SOLVER_KWARGS = {"cvxopt": {"kktsolver": "ldl"}}


class GeometricProgram(CostedConstraintSet, NomialData):
    # pylint: disable=too-many-instance-attributes
    """Standard mathematical representation of a GP.

    Arguments
    ---------
    cost : Constraint
        Posynomial to minimize when solving
    constraints : list of Posynomials
        Constraints to maintain when solving (implicitly Posynomials <= 1)
        GeometricProgram does not accept equality constraints (e.g. x == 1);
         instead use two inequality constraints (e.g. x <= 1, 1/x <= 1)
    verbosity : int (optional)
        If verbosity is greater than zero, warns about missing bounds
        on creation.

    Attributes with side effects
    ----------------------------
    `solver_out` and `solver_log` are set during a solve
    `result` is set at the end of a solve if solution status is optimal

    Examples
    --------
    >>> gp = gpkit.geometric_program.GeometricProgram(
                        # minimize
                        x,
                        [   # subject to
                            1/x  # <= 1, implicitly
                        ])
    >>> gp.solve()
    """
    def __init__(self, cost, constraints, substitutions=None, verbosity=1):
        # pylint:disable=super-init-not-called
        # initialize attributes modified by internal methods
        self.result = None
        self.solver_log = None
        self.solver_out = None

        # barebones ConstraintSet init
        self.cost = cost
        if not isinstance(constraints, ConstraintSet):
            constraints = ConstraintSet(constraints)
        list.__init__(self, [constraints])  # pylint:disable=non-parent-init-called
        self.substitutions = substitutions if substitutions else {}
        for key, sub in self.substitutions.items():
            if not isinstance(sub, (Numbers, np.ndarray)):
                raise ValueError("substitution {%s: %s} with value type %s is"
                                 " not allowed in .substitutions; such"
                                 " substitutions must be done by using"
                                 " .subinplace()." % (key, sub, type(sub)))
        # sideways NomialData init to create self.exps, self.cs, etc
        self.posynomials = [cost.sub(self.substitutions)]
        self.posynomials.extend(self.as_posyslt1(self.substitutions))
        NomialData.init_from_nomials(self, self.posynomials)
        if self.any_nonpositive_cs:
            raise ValueError("GeometricPrograms cannot contain Signomials.")

        ## Generate various maps into the posy- and monomials
        # k [j]: number of monomials (columns of F) present in each constraint
        self.k = [len(p.cs) for p in self.posynomials]
        # p_idxs [i]: posynomial index of each monomial
        p_idxs = []
        # m_idxs [i]: monomial indices of each posynomial
        self.m_idxs = []
        for i, p_len in enumerate(self.k):
            self.m_idxs.append(list(range(len(p_idxs), len(p_idxs) + p_len)))
            p_idxs += [i]*p_len
        self.p_idxs = np.array(p_idxs)
        # A [i, v]: sparse matrix of variable's powers in each monomial
        self.A, self.missingbounds = genA(self.exps, self.varlocs)
        if verbosity > 0:
            for var, bound in sorted(self.missingbounds.items()):
                print("%s has no %s bound" % (var, bound))

    # pylint: disable=too-many-statements, too-many-locals
    def solve(self, solver=None, verbosity=1, warn_on_check=False,
              *args, **kwargs):
        """Solves a GeometricProgram and returns the solution.

        Arguments
        ---------
        solver : str or function (optional)
            By default uses one of the solvers found during installation.
            If set to "mosek", "mosek_cli", or "cvxopt", uses that solver.
            If set to a function, passes that function cs, A, p_idxs, and k.
        verbosity : int (optional)
            If greater than 0, prints solver name and solve time.
        *args, **kwargs :
            Passed to solver constructor and solver function.


        Returns
        -------
        result : dict
            A dictionary containing the translated solver result; keys below.

            cost : float
                The value of the objective at the solution.
            variables : dict
                The value of each variable at the solution.
            sensitivities : dict
                monomials : array of floats
                    Each monomial's dual variable value at the solution.
                posynomials : array of floats
                    Each posynomials's dual variable value at the solution.
        """
        def _get_solver(solver):
            """Get the solverfn and solvername associated with solver"""
            if solver is None:
                from .. import settings
                solver = settings.get("default_solver", None)
                if not solver:
                    raise ValueError(
                        "No solver was given; perhaps gpkit was not properly"
                        " installed, or found no solvers during the"
                        " installation process.")

            if solver == "cvxopt":
                from .._cvxopt import cvxoptimize
                solverfn = cvxoptimize
            elif solver == "mosek_cli":
                from .._mosek import cli_expopt
                solverfn = cli_expopt.imize_fn(*args, **kwargs)
            elif solver == "mosek":
                from .._mosek import expopt
                solverfn = expopt.imize
            elif hasattr(solver, "__call__"):
                solverfn = solver
                solver = solver.__name__
            else:
                raise ValueError("Unknown solver '%s'." % solver)
            return solverfn, solver

        solverfn, solvername = _get_solver(solver)

        starttime = time()
        if verbosity > 0:
            print("Using solver '%s'" % solvername)
            print("Solving for %i variables." % len(self.varlocs))

        default_kwargs = DEFAULT_SOLVER_KWARGS.get(solvername, {})
        for k in default_kwargs:
            kwargs.setdefault(k, default_kwargs[k])

        # NOTE: SIDE EFFECTS AS WE LOG SOLVER'S STDOUT AND OUTPUT
        original_stdout = sys.stdout
        self.solver_log = SolverLog(verbosity-1, original_stdout)
        try:
            sys.stdout = self.solver_log   # CAPTURED
            solver_out = solverfn(c=self.cs, A=self.A, p_idxs=self.p_idxs,
                                  k=self.k, *args, **kwargs)
            self.solver_out = solver_out
        finally:
            sys.stdout = original_stdout
         # STDOUT HAS BEEN RETURNED. ENDING SIDE EFFECTS.

        soltime = time() - starttime
        if verbosity > 0:
            print("Solving took %.3g seconds." % (soltime,))
            tic = time()

        if solver_out.get("status", "").lower() != "optimal":
            raise RuntimeWarning(
                "final status of solver '%s' was '%s', not 'optimal'.\n\n"
                "The solver's result is stored in model.program.solver_out. "
                "A result dict can be generated via "
                "program._compile_result(program.solver_out)." %
                (solvername, solver_out.get("status", None)))

        self._generate_nula(solver_out)
        self.result = self._compile_result(solver_out)  # NOTE: SIDE EFFECTS
        if verbosity > 1:
            print("result packing took %.2g%% of solve time" %
                  ((time() - tic) / soltime * 100))
            tic = time()
        try:
            self.check_solution(self.result["cost"], solver_out['primal'],
                                nu=solver_out["nu"], la=solver_out["la"])
        except RuntimeWarning as e:
            if warn_on_check:
                print "Solution check warning:", str(e)
            else:
                raise e
        if verbosity > 1:
            print("solution checking took %.2g%% of solve time" %
                  ((time() - tic) / soltime * 100))

        self.process_result(self.result)
        self.result["soltime"] = soltime
        return self.result

    def _generate_nula(self, solver_out):
        solver_out["primal"] = np.ravel(solver_out['primal'])

        if "nu" in solver_out:<commit_msg>allow GP solves to return near_dual_feas solutions (#1069)<commit_after>"""Implement the GeometricProgram class"""
import sys
from time import time
import numpy as np
from ..nomials import NomialData
from ..small_classes import CootMatrix, SolverLog, Numbers
from ..keydict import KeyDict
from ..small_scripts import mag
from ..solution_array import SolutionArray
from .costed import CostedConstraintSet
from .set import ConstraintSet


DEFAULT_SOLVER_KWARGS = {"cvxopt": {"kktsolver": "ldl"}}


class GeometricProgram(CostedConstraintSet, NomialData):
    # pylint: disable=too-many-instance-attributes
    """Standard mathematical representation of a GP.

    Arguments
    ---------
    cost : Constraint
        Posynomial to minimize when solving
    constraints : list of Posynomials
        Constraints to maintain when solving (implicitly Posynomials <= 1)
        GeometricProgram does not accept equality constraints (e.g. x == 1);
         instead use two inequality constraints (e.g. x <= 1, 1/x <= 1)
    verbosity : int (optional)
        If verbosity is greater than zero, warns about missing bounds
        on creation.

    Attributes with side effects
    ----------------------------
    `solver_out` and `solver_log` are set during a solve
    `result` is set at the end of a solve if solution status is optimal

    Examples
    --------
    >>> gp = gpkit.geometric_program.GeometricProgram(
                        # minimize
                        x,
                        [   # subject to
                            1/x  # <= 1, implicitly
                        ])
    >>> gp.solve()
    """
    def __init__(self, cost, constraints, substitutions=None, verbosity=1):
        # pylint:disable=super-init-not-called
        # initialize attributes modified by internal methods
        self.result = None
        self.solver_log = None
        self.solver_out = None

        # barebones ConstraintSet init
        self.cost = cost
        if not isinstance(constraints, ConstraintSet):
            constraints = ConstraintSet(constraints)
        list.__init__(self, [constraints])  # pylint:disable=non-parent-init-called
        self.substitutions = substitutions if substitutions else {}
        for key, sub in self.substitutions.items():
            if not isinstance(sub, (Numbers, np.ndarray)):
                raise ValueError("substitution {%s: %s} with value type %s is"
                                 " not allowed in .substitutions; such"
                                 " substitutions must be done by using"
                                 " .subinplace()." % (key, sub, type(sub)))
        # sideways NomialData init to create self.exps, self.cs, etc
        self.posynomials = [cost.sub(self.substitutions)]
        self.posynomials.extend(self.as_posyslt1(self.substitutions))
        NomialData.init_from_nomials(self, self.posynomials)
        if self.any_nonpositive_cs:
            raise ValueError("GeometricPrograms cannot contain Signomials.")

        ## Generate various maps into the posy- and monomials
        # k [j]: number of monomials (columns of F) present in each constraint
        self.k = [len(p.cs) for p in self.posynomials]
        # p_idxs [i]: posynomial index of each monomial
        p_idxs = []
        # m_idxs [i]: monomial indices of each posynomial
        self.m_idxs = []
        for i, p_len in enumerate(self.k):
            self.m_idxs.append(list(range(len(p_idxs), len(p_idxs) + p_len)))
            p_idxs += [i]*p_len
        self.p_idxs = np.array(p_idxs)
        # A [i, v]: sparse matrix of variable's powers in each monomial
        self.A, self.missingbounds = genA(self.exps, self.varlocs)
        if verbosity > 0:
            for var, bound in sorted(self.missingbounds.items()):
                print("%s has no %s bound" % (var, bound))

    # pylint: disable=too-many-statements, too-many-locals
    def solve(self, solver=None, verbosity=1, warn_on_check=False,
              *args, **kwargs):
        """Solves a GeometricProgram and returns the solution.

        Arguments
        ---------
        solver : str or function (optional)
            By default uses one of the solvers found during installation.
            If set to "mosek", "mosek_cli", or "cvxopt", uses that solver.
            If set to a function, passes that function cs, A, p_idxs, and k.
        verbosity : int (optional)
            If greater than 0, prints solver name and solve time.
        *args, **kwargs :
            Passed to solver constructor and solver function.


        Returns
        -------
        result : dict
            A dictionary containing the translated solver result; keys below.

            cost : float
                The value of the objective at the solution.
            variables : dict
                The value of each variable at the solution.
            sensitivities : dict
                monomials : array of floats
                    Each monomial's dual variable value at the solution.
                posynomials : array of floats
                    Each posynomials's dual variable value at the solution.
        """
        def _get_solver(solver):
            """Get the solverfn and solvername associated with solver"""
            if solver is None:
                from .. import settings
                solver = settings.get("default_solver", None)
                if not solver:
                    raise ValueError(
                        "No solver was given; perhaps gpkit was not properly"
                        " installed, or found no solvers during the"
                        " installation process.")

            if solver == "cvxopt":
                from .._cvxopt import cvxoptimize
                solverfn = cvxoptimize
            elif solver == "mosek_cli":
                from .._mosek import cli_expopt
                solverfn = cli_expopt.imize_fn(*args, **kwargs)
            elif solver == "mosek":
                from .._mosek import expopt
                solverfn = expopt.imize
            elif hasattr(solver, "__call__"):
                solverfn = solver
                solver = solver.__name__
            else:
                raise ValueError("Unknown solver '%s'." % solver)
            return solverfn, solver

        solverfn, solvername = _get_solver(solver)

        starttime = time()
        if verbosity > 0:
            print("Using solver '%s'" % solvername)
            print("Solving for %i variables." % len(self.varlocs))

        default_kwargs = DEFAULT_SOLVER_KWARGS.get(solvername, {})
        for k in default_kwargs:
            kwargs.setdefault(k, default_kwargs[k])

        # NOTE: SIDE EFFECTS AS WE LOG SOLVER'S STDOUT AND OUTPUT
        original_stdout = sys.stdout
        self.solver_log = SolverLog(verbosity-1, original_stdout)
        try:
            sys.stdout = self.solver_log   # CAPTURED
            solver_out = solverfn(c=self.cs, A=self.A, p_idxs=self.p_idxs,
                                  k=self.k, *args, **kwargs)
            self.solver_out = solver_out
        finally:
            sys.stdout = original_stdout
         # STDOUT HAS BEEN RETURNED. ENDING SIDE EFFECTS.

        soltime = time() - starttime
        if verbosity > 0:
            print("Solving took %.3g seconds." % (soltime,))
            tic = time()

        # allow mosek's NEAR_DUAL_FEAS solution status, because our check
        # will catch anything that's not actually near enough.
        solver_status = str(solver_out.get("status", None))
        if solver_status.lower() not in ["optimal", "near_dual_feas"]:
            raise RuntimeWarning(
                "final status of solver '%s' was '%s', not 'optimal'.\n\n"
                "The solver's result is stored in model.program.solver_out. "
                "A result dict can be generated via "
                "program._compile_result(program.solver_out)." %
                (solvername, solver_status))

        if solver_status.lower() == "near_dual_feas":
            print RuntimeWarning(
                "final status of solver '%s' was '%s', not 'optimal'.\n\n" %
                (solvername, solver_status))

        self._generate_nula(solver_out)
        self.result = self._compile_result(solver_out)  # NOTE: SIDE EFFECTS
        if verbosity > 1:
            print("result packing took %.2g%% of solve time" %
                  ((time() - tic) / soltime * 100))
            tic = time()
        try:
            self.check_solution(self.result["cost"], solver_out['primal'],
                                nu=solver_out["nu"], la=solver_out["la"])
        except RuntimeWarning as e:
            if warn_on_check:
                print "Solution check warning:", str(e)
            else:
                raise e
        if verbosity > 1:
            print("solution checking took %.2g%% of solve time" %
                  ((time() - tic) / soltime * 100))

        self.process_result(self.result)
        self.result["soltime"] = soltime
        return self.result

    def _generate_nula(self, solver_out):
        solver_out["primal"] = np.ravel(solver_out['primal'])

        if "nu" in solver_out:
{'fraction_non_alphanumeric': 0.09424697282517716, 'fraction_numerical': 0.0031248256235701134, 'mean_word_length': 1.8579173975442513, 'pattern_counts': {'":': 12, '<': 11, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2123510', 'n_tokens_mistral': 5142, 'n_tokens_neox': 4781, 'n_words': 1498}