Пример #1
0
 def __init__(self):
     self._config = CbcConfig()
     self._solver_options = dict()
     self._writer = LPWriter()
     self._filename = None
     self._dual_sol = dict()
     self._primal_sol = dict()
     self._reduced_costs = dict()
Пример #2
0
 def __init__(self):
     self._config = CbcConfig()
     self._solver_options = dict()
     self._writer = LPWriter()
     self._filename = None
     self._dual_sol = dict()
     self._primal_sol = dict()
     self._reduced_costs = dict()
     self._last_results_object: Optional[Results] = None
Пример #3
0
    def __init__(self):
        self._config = CplexConfig()
        self._solver_options = dict()
        self._writer = LPWriter()
        self._filename = None

        try:
            import cplex
            self._cplex = cplex
            self._cplex_model: Optional[cplex.Cplex] = None
            self._cplex_available = True
        except ImportError:
            self._cplex = None
            self._cplex_model = None
            self._cplex_available = False
Пример #4
0
class Cbc(PersistentSolver):
    def __init__(self):
        self._config = CbcConfig()
        self._solver_options = dict()
        self._writer = LPWriter()
        self._filename = None
        self._dual_sol = dict()
        self._primal_sol = dict()
        self._reduced_costs = dict()

    def available(self, exception_flag=False):
        if self.config.executable.path() is None:
            if exception_flag:
                raise RuntimeError('Cbc is not available')
            return False
        return True

    def version(self):
        results = subprocess.run([str(self.config.executable), '-stop'],
                                 timeout=1,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.STDOUT,
                                 universal_newlines=True)
        version = results.stdout.splitlines()[1]
        version = version.split(':')[1]
        version = version.strip()
        version = tuple(int(i) for i in version.split('.'))
        return version

    def lp_filename(self):
        if self._filename is None:
            return None
        else:
            return self._filename + '.lp'

    def log_filename(self):
        if self._filename is None:
            return None
        else:
            return self._filename + '.log'

    def soln_filename(self):
        if self._filename is None:
            return None
        else:
            return self._filename + '.soln'

    @property
    def config(self):
        return self._config

    @property
    def solver_options(self):
        return self._solver_options

    @property
    def update_config(self):
        return self._writer.update_config

    def set_instance(self, model):
        self._writer.set_instance(model)

    def add_variables(self, variables: List[_GeneralVarData]):
        self._writer.add_variables(variables)

    def add_params(self, params: List[_ParamData]):
        self._writer.add_params(params)

    def add_constraints(self, cons: List[_GeneralConstraintData]):
        self._writer.add_constraints(cons)

    def add_block(self, block: _BlockData):
        self._writer.add_block(block)

    def remove_variables(self, variables: List[_GeneralVarData]):
        self._writer.remove_variables(variables)

    def remove_params(self, params: List[_ParamData]):
        self._writer.remove_params(params)

    def remove_constraints(self, cons: List[_GeneralConstraintData]):
        self._writer.remove_constraints(cons)

    def remove_block(self, block: _BlockData):
        self._writer.remove_block(block)

    def set_objective(self, obj: _GeneralObjectiveData):
        self._writer.set_objective(obj)

    def update_variables(self, variables: List[_GeneralVarData]):
        self._writer.update_variables(variables)

    def update_params(self):
        self._writer.update_params()

    def solve(self, model, timer: HierarchicalTimer = None):
        self.available(exception_flag=True)
        if timer is None:
            timer = HierarchicalTimer()
        try:
            TempfileManager.push()
            if self.config.filename is None:
                self._filename = TempfileManager.create_tempfile()
            else:
                self._filename = self.config.filename
            TempfileManager.add_tempfile(self._filename + '.lp', exists=False)
            TempfileManager.add_tempfile(self._filename + '.soln', exists=False)
            TempfileManager.add_tempfile(self._filename + '.log', exists=False)
            timer.start('write lp file')
            self._writer.write(model, self._filename+'.lp', timer=timer)
            timer.stop('write lp file')
            res = self._apply_solver(timer)
            if self.config.report_timing:
                logger.info('\n' + str(timer))
            return res
        finally:
            # finally, clean any temporary files registered with the
            # temp file manager, created/populated *directly* by this
            # plugin.
            TempfileManager.pop(remove=not self.config.keepfiles)
            if not self.config.keepfiles:
                self._filename = None
            if self.config.report_timing:
                print(timer)

    def _parse_soln(self):
        results = Results()

        f = open(self._filename + '.soln', 'r')
        all_lines = list(f.readlines())
        f.close()

        termination_line = all_lines[0].lower()
        obj_val = None
        if termination_line.startswith('optimal'):
            results.termination_condition = TerminationCondition.optimal
            obj_val = float(termination_line.split()[-1])
        elif 'infeasible' in termination_line:
            results.termination_condition = TerminationCondition.infeasible
        elif 'unbounded' in termination_line:
            results.termination_condition = TerminationCondition.unbounded
        elif termination_line.startswith('stopped on time'):
            results.termination_condition = TerminationCondition.maxTimeLimit
            obj_val = float(termination_line.split()[-1])
        elif termination_line.startswith('stopped on iterations'):
            results.termination_condition = TerminationCondition.maxIterations
            obj_val = float(termination_line.split()[-1])
        else:
            results.termination_condition = TerminationCondition.unknown

        first_con_line = None
        last_con_line = None
        first_var_line = None
        last_var_line = None

        for ndx, line in enumerate(all_lines):
            if line.strip('*').strip().startswith('0'):
                if first_con_line is None:
                    first_con_line = ndx
                else:
                    last_con_line = ndx - 1
                    first_var_line = ndx
        last_var_line = len(all_lines) - 1

        self._dual_sol = dict()
        self._primal_sol = dict()
        self._reduced_costs = dict()

        symbol_map = self._writer.symbol_map

        for line in all_lines[first_con_line:last_con_line+1]:
            split_line = line.strip('*')
            split_line = split_line.split()
            name = split_line[1]
            orig_name = name[:-3]
            if orig_name == 'obj_const_con':
                continue
            con = symbol_map.bySymbol[orig_name]()
            dual_val = float(split_line[-1])
            if con in self._dual_sol:
                if abs(dual_val) > abs(self._dual_sol[con]):
                    self._dual_sol[con] = dual_val
            else:
                self._dual_sol[con] = dual_val

        for line in all_lines[first_var_line:last_var_line+1]:
            split_line = line.strip('*')
            split_line = split_line.split()
            name = split_line[1]
            if name == 'obj_const':
                continue
            val = float(split_line[2])
            rc = float(split_line[3])
            var = symbol_map.bySymbol[name]()
            self._primal_sol[id(var)] = (var, val)
            self._reduced_costs[id(var)] = (var, rc)

        if (self.version() < (2, 10, 2) and
                self._writer.get_active_objective() is not None and
                self._writer.get_active_objective().sense == maximize):
            obj_val = -obj_val
            for con, dual_val in self._dual_sol.items():
                self._dual_sol[con] = -dual_val
            for v_id, (v, rc_val) in self._reduced_costs.items():
                self._reduced_costs[v_id] = (v, -rc_val)

        if results.termination_condition == TerminationCondition.optimal and self.config.load_solution:
            for v_id, (v, val) in self._primal_sol.items():
                v.value = val
            if self._writer.get_active_objective() is None:
                results.best_feasible_objective = None
            else:
                results.best_feasible_objective = obj_val
        elif results.termination_condition == TerminationCondition.optimal:
            if self._writer.get_active_objective() is None:
                results.best_feasible_objective = None
            else:
                results.best_feasible_objective = obj_val
        elif self.config.load_solution:
            raise RuntimeError('A feasible solution was not found, so no solution can be loaded.'
                               'Please set opt.config.load_solution=False and check '
                               'results.termination_condition and '
                               'resutls.best_feasible_objective before loading a solution.')

        return results

    def _apply_solver(self, timer: HierarchicalTimer):
        config = self.config

        if config.time_limit is not None:
            timeout = config.time_limit + min(max(1, 0.01 * config.time_limit), 100)
        else:
            timeout = None

        def _check_and_escape_options():
            for key, val in self.solver_options.items():
                tmp_k = str(key)
                _bad = ' ' in tmp_k

                tmp_v = str(val)
                if ' ' in tmp_v:
                    if '"' in tmp_v:
                        if "'" in tmp_v:
                            _bad = True
                        else:
                            tmp_v = "'" + tmp_v + "'"
                    else:
                        tmp_v = '"' + tmp_v + '"'

                if _bad:
                    raise ValueError("Unable to properly escape solver option:"
                                     "\n\t%s=%s" % (key, val) )
                yield tmp_k, tmp_v

        cmd = [str(config.executable)]
        action_options = list()
        if config.time_limit is not None:
            cmd.extend(['-sec', str(config.time_limit)])
            cmd.extend(['-timeMode', 'elapsed'])
        for key, val in _check_and_escape_options():
            if val.strip() != '':
                cmd.append('-'+key, val)
            else:
                action_options.append('-'+key)
        cmd.extend(['-printingOptions', 'all'])
        cmd.extend(['-import', self._filename + '.lp'])
        cmd.extend(action_options)
        cmd.extend(['-stat=1'])
        cmd.extend(['-solve'])
        cmd.extend(['-solu', self._filename + '.soln'])

        ostreams = [LogStream(level=self.config.log_level, logger=self.config.solver_output_logger)]
        if self.config.stream_solver:
            ostreams.append(sys.stdout)

        with TeeStream(*ostreams) as t:
            timer.start('subprocess')
            cp = subprocess.run(cmd,
                                timeout=timeout,
                                stdout=t.STDOUT,
                                stderr=t.STDERR,
                                universal_newlines=True)
            timer.stop('subprocess')

        if cp.returncode != 0:
            if self.config.load_solution:
                raise RuntimeError('A feasible solution was not found, so no solution can be loaded.'
                                   'Please set opt.config.load_solution=False and check '
                                   'results.termination_condition and '
                                   'results.best_feasible_objective before loading a solution.')
            results = Results()
            results.termination_condition = TerminationCondition.error
            results.best_feasible_objective = None
            self._primal_sol = None
            self._dual_sol = None
            self._reduced_costs = None
        else:
            timer.start('parse solution')
            results = self._parse_soln()
            timer.stop('parse solution')

        if self._writer.get_active_objective() is None:
            results.best_feasible_objective = None
            results.best_objective_bound = None
        else:
            if self._writer.get_active_objective().sense == minimize:
                results.best_objective_bound = -math.inf
            else:
                results.best_objective_bound = math.inf

        return results

    def load_vars(self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None) -> NoReturn:
        if vars_to_load is None:
            for v_id, (v, val) in self._primal_sol.items():
                v.value = val
        else:
            for v in vars_to_load:
                v.value = self._primal_sol[id(v)][1]

    def get_duals(self, cons_to_load = None):
        if cons_to_load is None:
            return {k: v for k, v in self._dual_sol.items()}
        else:
            return {c: self._dual_sol[c] for c in cons_to_load}

    def get_reduced_costs(self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None) -> Mapping[_GeneralVarData, float]:
        if vars_to_load is None:
            return ComponentMap((k, v) for k, v in self._reduced_costs.values())
        else:
            return ComponentMap((v, self._reduced_costs[id(v)][1]) for v in vars_to_load)
Пример #5
0
class Cplex(PersistentSolver):
    _available = None

    def __init__(self):
        self._config = CplexConfig()
        self._solver_options = dict()
        self._writer = LPWriter()
        self._filename = None
        self._last_results_object: Optional[CplexResults] = None

        try:
            import cplex
            self._cplex = cplex
            self._cplex_model: Optional[cplex.Cplex] = None
            self._cplex_available = True
        except ImportError:
            self._cplex = None
            self._cplex_model = None
            self._cplex_available = False

    @property
    def writer(self):
        return self._writer

    @property
    def symbol_map(self):
        return self._writer.symbol_map

    def available(self):
        if Cplex._available is None:
            self._check_license()
        return Cplex._available

    def _check_license(self):
        if self._cplex_available:
            try:
                m = self._cplex.Cplex()
                m.variables.add(lb=[0] * 1001)
                m.solve()
                Cplex._available = self.Availability.FullLicense
            except self._cplex.exceptions.errors.CplexSolverError:
                try:
                    m = self._cplex.Cplex()
                    m.variables.add(lb=[0])
                    m.solve()
                    Cplex._available = self.Availability.LimitedLicense
                except:
                    Cplex._available = self.Availability.BadLicense
        else:
            Cplex._available = self.Availability.NotFound

    def version(self):
        return tuple(
            int(k) for k in self._cplex.Cplex().get_version().split('.'))

    def lp_filename(self):
        if self._filename is None:
            return None
        else:
            return self._filename + '.lp'

    def log_filename(self):
        if self._filename is None:
            return None
        else:
            return self._filename + '.log'

    @property
    def config(self):
        return self._config

    @config.setter
    def config(self, val):
        self._config = val

    @property
    def cplex_options(self):
        """
        Returns
        -------
        cplex_options: dict
            A dictionary mapping solver options to values for those options. These
            are solver specific.
        """
        return self._solver_options

    @cplex_options.setter
    def cplex_options(self, val: Dict):
        self._solver_options = val

    @property
    def update_config(self):
        return self._writer.update_config

    def set_instance(self, model):
        self._writer.set_instance(model)

    def add_variables(self, variables: List[_GeneralVarData]):
        self._writer.add_variables(variables)

    def add_params(self, params: List[_ParamData]):
        self._writer.add_params(params)

    def add_constraints(self, cons: List[_GeneralConstraintData]):
        self._writer.add_constraints(cons)

    def add_block(self, block: _BlockData):
        self._writer.add_block(block)

    def remove_variables(self, variables: List[_GeneralVarData]):
        self._writer.remove_variables(variables)

    def remove_params(self, params: List[_ParamData]):
        self._writer.remove_params(params)

    def remove_constraints(self, cons: List[_GeneralConstraintData]):
        self._writer.remove_constraints(cons)

    def remove_block(self, block: _BlockData):
        self._writer.remove_block(block)

    def set_objective(self, obj: _GeneralObjectiveData):
        self._writer.set_objective(obj)

    def update_variables(self, variables: List[_GeneralVarData]):
        self._writer.update_variables(variables)

    def update_params(self):
        self._writer.update_params()

    def solve(self, model, timer: HierarchicalTimer = None):
        avail = self.available()
        if not avail:
            raise PyomoException(
                f'Solver {self.__class__} is not available ({avail}).')
        if self._last_results_object is not None:
            self._last_results_object.solution_loader.invalidate()
        if timer is None:
            timer = HierarchicalTimer()
        try:
            TempfileManager.push()
            if self.config.filename is None:
                self._filename = TempfileManager.create_tempfile()
            else:
                self._filename = self.config.filename
            TempfileManager.add_tempfile(self._filename + '.lp', exists=False)
            TempfileManager.add_tempfile(self._filename + '.log', exists=False)
            timer.start('write lp file')
            self._writer.write(model, self._filename + '.lp', timer=timer)
            timer.stop('write lp file')
            res = self._apply_solver(timer)
            self._last_results_object = res
            if self.config.report_timing:
                logger.info('\n' + str(timer))
            return res
        finally:
            # finally, clean any temporary files registered with the
            # temp file manager, created/populated *directly* by this
            # plugin.
            TempfileManager.pop(remove=not self.config.keepfiles)
            if not self.config.keepfiles:
                self._filename = None

    def _apply_solver(self, timer: HierarchicalTimer):
        config = self.config

        timer.start('cplex read lp')
        self._cplex_model = cplex_model = self._cplex.Cplex()
        cplex_model.read(self._filename + '.lp')
        timer.stop('cplex read lp')

        log_stream = LogStream(level=self.config.log_level,
                               logger=self.config.solver_output_logger)
        if config.stream_solver:

            def _process_stream(arg):
                sys.stdout.write(arg)
                return arg

            cplex_model.set_results_stream(log_stream, _process_stream)
        else:
            cplex_model.set_results_stream(log_stream)

        for key, option in self.cplex_options.items():
            opt_cmd = cplex_model.parameters
            key_pieces = key.split('_')
            for key_piece in key_pieces:
                opt_cmd = getattr(opt_cmd, key_piece)
            opt_cmd.set(option)

        if config.time_limit is not None:
            cplex_model.parameters.timelimit.set(config.time_limit)
        if config.mip_gap is not None:
            cplex_model.parameters.mip.tolerances.mipgap.set(config.mip_gap)

        timer.start('cplex solve')
        t0 = time.time()
        cplex_model.solve()
        t1 = time.time()
        timer.stop('cplex solve')

        return self._postsolve(timer, t1 - t0)

    def _postsolve(self, timer: HierarchicalTimer, solve_time):
        config = self.config
        cpxprob = self._cplex_model

        results = CplexResults(solver=self)
        results.wallclock_time = solve_time
        status = cpxprob.solution.get_status()

        if status in [1, 101, 102]:
            results.termination_condition = TerminationCondition.optimal
        elif status in [2, 40, 118, 133, 134]:
            results.termination_condition = TerminationCondition.unbounded
        elif status in [4, 119, 134]:
            results.termination_condition = TerminationCondition.infeasibleOrUnbounded
        elif status in [3, 103]:
            results.termination_condition = TerminationCondition.infeasible
        elif status in [10]:
            results.termination_condition = TerminationCondition.maxIterations
        elif status in [11, 25, 107, 131]:
            results.termination_condition = TerminationCondition.maxTimeLimit
        else:
            results.termination_condition = TerminationCondition.unknown

        if self._writer.get_active_objective() is None:
            results.best_feasible_objective = None
            results.best_objective_bound = None
        else:
            if cpxprob.solution.get_solution_type(
            ) != cpxprob.solution.type.none:
                if (cpxprob.variables.get_num_binary() +
                        cpxprob.variables.get_num_integer()) == 0:
                    results.best_feasible_objective = cpxprob.solution.get_objective_value(
                    )
                    results.best_objective_bound = cpxprob.solution.get_objective_value(
                    )
                else:
                    results.best_feasible_objective = cpxprob.solution.get_objective_value(
                    )
                    results.best_objective_bound = cpxprob.solution.MIP.get_best_objective(
                    )
            else:
                results.best_feasible_objective = None
                if cpxprob.objective.get_sense(
                ) == cpxprob.objective.sense.minimize:
                    results.best_objective_bound = -math.inf
                else:
                    results.best_objective_bound = math.inf

        if config.load_solution:
            if cpxprob.solution.get_solution_type(
            ) == cpxprob.solution.type.none:
                raise RuntimeError(
                    'A feasible solution was not found, so no solution can be loades. '
                    'Please set opt.config.load_solution=False and check '
                    'results.termination_condition and '
                    'results.best_feasible_objective before loading a solution.'
                )
            else:
                if results.termination_condition != TerminationCondition.optimal:
                    logger.warning(
                        'Loading a feasible but suboptimal solution. '
                        'Please set load_solution=False and check '
                        'results.termination_condition before loading a solution.'
                    )
                timer.start('load solution')
                self.load_vars()
                timer.stop('load solution')

        return results

    def get_primals(
        self,
        vars_to_load: Optional[Sequence[_GeneralVarData]] = None
    ) -> Mapping[_GeneralVarData, float]:
        if self._cplex_model.solution.get_solution_type(
        ) == self._cplex_model.solution.type.none:
            raise RuntimeError(
                'Cannot load variable values - no feasible solution was found.'
            )
        symbol_map = self._writer.symbol_map
        if vars_to_load is None:
            var_names = self._cplex_model.variables.get_names()
        else:
            var_names = [symbol_map.byObject[id(v)] for v in vars_to_load]
        var_vals = self._cplex_model.solution.get_values(var_names)
        res = ComponentMap()
        for name, val in zip(var_names, var_vals):
            if name == 'obj_const':
                continue
            v = symbol_map.bySymbol[name]()
            res[v] = val
        return res

    def get_duals(
        self,
        cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None
    ) -> Dict[_GeneralConstraintData, float]:
        if self._cplex_model.solution.get_solution_type(
        ) == self._cplex_model.solution.type.none:
            raise RuntimeError(
                'Cannot get duals - no feasible solution was found.')
        if self._cplex_model.get_problem_type() in [
                self._cplex_model.problem_type.MILP,
                self._cplex_model.problem_type.MIQP,
                self._cplex_model.problem_type.MIQCP
        ]:
            raise RuntimeError(
                'Cannot get get duals for mixed-integer problems')

        symbol_map = self._writer.symbol_map

        if cons_to_load is None:
            con_names = self._cplex_model.linear_constraints.get_names()
            dual_values = self._cplex_model.solution.get_dual_values()
        else:
            con_names = list()
            for con in cons_to_load:
                orig_name = symbol_map.byObject[id(con)]
                if con.equality:
                    con_names.append(orig_name + '_eq')
                else:
                    if con.lower is not None:
                        con_names.append(orig_name + '_lb')
                    if con.upper is not None:
                        con_names.append(orig_name + '_ub')
            dual_values = self._cplex_model.solution.get_dual_values(con_names)

        res = dict()
        for name, val in zip(con_names, dual_values):
            orig_name = name[:-3]
            if orig_name == 'obj_const_con':
                continue
            _con = symbol_map.bySymbol[orig_name]()
            if _con in res:
                if abs(val) > abs(res[_con]):
                    res[_con] = val
            else:
                res[_con] = val

        return res

    def get_reduced_costs(
        self,
        vars_to_load: Optional[Sequence[_GeneralVarData]] = None
    ) -> Mapping[_GeneralVarData, float]:
        if self._cplex_model.solution.get_solution_type(
        ) == self._cplex_model.solution.type.none:
            raise RuntimeError(
                'Cannot get reduced costs - no feasible solution was found.')
        if self._cplex_model.get_problem_type() in [
                self._cplex_model.problem_type.MILP,
                self._cplex_model.problem_type.MIQP,
                self._cplex_model.problem_type.MIQCP
        ]:
            raise RuntimeError(
                'Cannot get get reduced costs for mixed-integer problems')

        symbol_map = self._writer.symbol_map
        if vars_to_load is None:
            var_names = self._cplex_model.variables.get_names()
        else:
            var_names = [symbol_map.byObject[id(v)] for v in vars_to_load]
        rc = self._cplex_model.solution.get_reduced_costs(var_names)
        res = ComponentMap()
        for name, val in zip(var_names, rc):
            if name == 'obj_const':
                continue
            v = symbol_map.bySymbol[name]()
            res[v] = val
        return res