Exemplo n.º 1
0
 def solve(self, model, timer: HierarchicalTimer = None):
     StaleFlagManager.mark_all_as_stale()
     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
Exemplo n.º 2
0
    def _apply_solver(self):
        StaleFlagManager.mark_all_as_stale()

        self._solver_model.setlogfile(self._log_file)
        if self._keepfiles:
            print("Solver log file: " + self._log_file)

        # Setting a log file in xpress disables all output
        # in xpress versions less than 36.
        # This callback prints all messages to stdout
        # when using those xpress versions.
        if self._tee and XpressDirect._version[0] < 36:
            self._solver_model.addcbmessage(_print_message, None, 0)

        # set xpress options
        # if the user specifies a 'mipgap', set it, and
        # set xpress's related options to 0.
        if self.options.mipgap is not None:
            self._solver_model.setControl('miprelstop',
                                          float(self.options.mipgap))
            self._solver_model.setControl('miprelcutoff', 0.0)
            self._solver_model.setControl('mipaddcutoff', 0.0)
        # xpress is picky about the type which is passed
        # into a control. So we will infer and cast
        # get the xpress valid controls
        xp_controls = xpress.controls
        for key, option in self.options.items():
            if key == 'mipgap':  # handled above
                continue
            try:
                self._solver_model.setControl(key, option)
            except XpressDirect.XpressException:
                # take another try, converting to its type
                # we'll wrap this in a function to raise the
                # xpress error
                contr_type = type(getattr(xp_controls, key))
                if not _is_convertable(contr_type, option):
                    raise
                self._solver_model.setControl(key, contr_type(option))

        start_time = time.time()
        if self._tee:
            self._solver_model.solve()
        else:
            # In xpress versions greater than or equal 36,
            # it seems difficult to completely suppress console
            # output without disabling logging altogether.
            # As a work around, we capature all screen output
            # when tee is False.
            with capture_output() as OUT:
                self._solver_model.solve()
        self._opt_time = time.time() - start_time

        self._solver_model.setlogfile('')
        if self._tee and XpressDirect._version[0] < 36:
            self._solver_model.removecbmessage(_print_message, None)

        # FIXME: can we get a return code indicating if XPRESS had a significant failure?
        return Bunch(rc=None, log=None)
Exemplo n.º 3
0
    def load_vars(self, vars_to_load=None):
        """
        Load the values from the solver's variables into the corresponding pyomo variables.

        Parameters
        ----------
        vars_to_load: list of Var
        """
        self._load_vars(vars_to_load)
        StaleFlagManager.mark_all_as_stale(delayed=True)
Exemplo n.º 4
0
 def cbGetSolution(self, vars):
     """
     Parameters
     ----------
     vars: iterable of vars
     """
     StaleFlagManager.mark_all_as_stale()
     if not isinstance(vars, Iterable):
         vars = [vars]
     gurobi_vars = [self._pyomo_var_to_solver_var_map[i] for i in vars]
     var_values = self._solver_model.cbGetSolution(gurobi_vars)
     for i, v in enumerate(vars):
         v.set_value(var_values[i], skip_validation=True)
Exemplo n.º 5
0
    def _apply_solver(self):
        StaleFlagManager.mark_all_as_stale()

        if self._tee:

            def _process_stream(msg):
                sys.stdout.write(msg)
                sys.stdout.flush()

            self._solver_model.set_Stream(self._mosek.streamtype.log,
                                          _process_stream)

        if self._keepfiles:
            logger.info("Solver log file: {}".format(self._log_file))

        for key, option in self.options.items():
            try:
                param = key.split('.')
                if param[0] == 'mosek':
                    param.pop(0)
                param = getattr(self._mosek, param[0])(param[1])
                if 'sparam' in key.split('.'):
                    self._solver_model.putstrparam(param, option)
                elif 'dparam' in key.split('.'):
                    self._solver_model.putdouparam(param, option)
                elif 'iparam' in key.split('.'):
                    if isinstance(option, str):
                        option = option.split('.')
                        if option[0] == 'mosek':
                            option.pop('mosek')
                        option = getattr(self._mosek, option[0])(option[1])
                    else:
                        self._solver_model.putintparam(param, option)
            except (TypeError, AttributeError):
                raise
        try:
            self._termcode = self._solver_model.optimize()
            self._solver_model.solutionsummary(self._mosek.streamtype.msg)
        except self._mosek.Error as e:
            logger.error(e)
            raise
        return Bunch(rc=None, log=None)
Exemplo n.º 6
0
    def select(self,
               index=0,
               allow_consistent_values_for_fixed_vars=False,
               comparison_tolerance_for_fixed_vars=1e-5,
               ignore_invalid_labels=False,
               ignore_fixed_vars=True):
        """
        Select a solution from the model's solutions.

        allow_consistent_values_for_fixed_vars: a flag that
        indicates whether a solution can specify consistent
        values for variables in the model that are fixed.

        ignore_invalid_labels: a flag that indicates whether
        labels in the solution that don't appear in the model
        yield an error. This allows for loading a results object
        generated from one model into another related, but not
        identical, model.
        """
        instance = self._instance()
        #
        # Set the "stale" flag of each variable in the model prior to
        # loading the solution, so you known which variables have "real"
        # values and which ones don't.
        #
        StaleFlagManager.mark_all_as_stale()

        if index is not None:
            self.index = index
        soln = self.solutions[self.index]

        #
        # Generate the list of active import suffixes on this top level model
        #
        valid_import_suffixes = dict(active_import_suffix_generator(instance))
        #
        # To ensure that import suffix data gets properly overwritten (e.g.,
        # the case where nonzero dual values exist on the suffix and but only
        # sparse dual values exist in the results object) we clear all active
        # import suffixes.
        #
        for suffix in valid_import_suffixes.values():
            suffix.clear_all_values()
        #
        # Load problem (model) level suffixes. These would only come from ampl
        # interfaced solution suffixes at this point in time.
        #
        for id_, (pobj, entry) in soln._entry['problem'].items():
            for _attr_key, attr_value in entry.items():
                attr_key = _attr_key[0].lower() + _attr_key[1:]
                if attr_key in valid_import_suffixes:
                    valid_import_suffixes[attr_key][pobj] = attr_value
        #
        # Load objective data (suffixes)
        #
        for id_, (odata, entry) in soln._entry['objective'].items():
            odata = odata()
            for _attr_key, attr_value in entry.items():
                attr_key = _attr_key[0].lower() + _attr_key[1:]
                if attr_key in valid_import_suffixes:
                    valid_import_suffixes[attr_key][odata] = attr_value
        #
        # Load variable data (suffixes and values)
        #
        for id_, (vdata, entry) in soln._entry['variable'].items():
            vdata = vdata()
            val = entry['Value']
            if vdata.fixed is True:
                if ignore_fixed_vars:
                    continue
                if not allow_consistent_values_for_fixed_vars:
                    msg = "Variable '%s' in model '%s' is currently fixed - new" \
                          ' value is not expected in solution'
                    raise TypeError(msg % (vdata.name, instance.name))
                if math.fabs(val - vdata.value
                             ) > comparison_tolerance_for_fixed_vars:
                    raise TypeError("Variable '%s' in model '%s' is currently "
                                    "fixed - a value of '%s' in solution is "
                                    "not within tolerance=%s of the current "
                                    "value of '%s'" %
                                    (vdata.name, instance.name, str(val),
                                     str(comparison_tolerance_for_fixed_vars),
                                     str(vdata.value)))

            vdata.set_value(val, skip_validation=True)

            for _attr_key, attr_value in entry.items():
                attr_key = _attr_key[0].lower() + _attr_key[1:]
                if attr_key == 'value':
                    continue
                elif attr_key in valid_import_suffixes:
                    valid_import_suffixes[attr_key][vdata] = attr_value
        #
        # Load constraint data (suffixes)
        #
        for id_, (cdata, entry) in soln._entry['constraint'].items():
            cdata = cdata()
            for _attr_key, attr_value in entry.items():
                attr_key = _attr_key[0].lower() + _attr_key[1:]
                if attr_key in valid_import_suffixes:
                    valid_import_suffixes[attr_key][cdata] = attr_value

        # Set the state flag to "delayed advance": it will auto-advance
        # if a non-stale variable is updated (causing all non-stale
        # variables to be marked as stale).
        StaleFlagManager.mark_all_as_stale(delayed=True)
Exemplo n.º 7
0
    def load_solution(self,
                      solution,
                      allow_consistent_values_for_fixed_vars=False,
                      comparison_tolerance_for_fixed_vars=1e-5):
        """
        Load a solution.

        Args:
            solution: A :class:`pyomo.opt.Solution` object with a
                symbol map. Optionally, the solution can be tagged
                with a default variable value (e.g., 0) that will be
                applied to those variables in the symbol map that do
                not have a value in the solution.
            allow_consistent_values_for_fixed_vars:
                Indicates whether a solution can specify
                consistent values for variables that are
                fixed.
            comparison_tolerance_for_fixed_vars: The
                tolerance used to define whether or not a
                value in the solution is consistent with the
                value of a fixed variable.
        """
        from pyomo.core.kernel.suffix import \
            import_suffix_generator

        symbol_map = solution.symbol_map
        default_variable_value = getattr(solution, "default_variable_value",
                                         None)

        # Generate the list of active import suffixes on
        # this top level model
        valid_import_suffixes = \
            {obj.storage_key:obj
                 for obj in import_suffix_generator(self)}

        # To ensure that import suffix data gets properly
        # overwritten (e.g., the case where nonzero dual
        # values exist on the suffix and but only sparse
        # dual values exist in the results object) we clear
        # all active import suffixes.
        for suffix in valid_import_suffixes.values():
            suffix.clear()

        # Load problem (model) level suffixes. These would
        # only come from ampl interfaced solution suffixes
        # at this point in time.
        for _attr_key, attr_value in solution.problem.items():
            attr_key = _attr_key[0].lower() + _attr_key[1:]
            if attr_key in valid_import_suffixes:
                valid_import_suffixes[attr_key][self] = attr_value

        #
        # Set the "stale" flag of each variable in the model prior to
        # loading the solution, so you known which variables have "real"
        # values and which ones don't.
        #
        StaleFlagManager.mark_all_as_stale()
        #
        # Load variable data
        #
        from pyomo.core.kernel.variable import IVariable
        var_skip_attrs = ['id', 'canonical_label']
        seen_var_ids = set()
        for label, entry in solution.variable.items():
            var = symbol_map.getObject(label)
            if (var is None) or \
               (var is SymbolMap.UnknownSymbol):
                # NOTE: the following is a hack, to handle
                #    the ONE_VAR_CONSTANT variable that is
                #    necessary for the objective
                #    constant-offset terms.  probably should
                #    create a dummy variable in the model
                #    map at the same time the objective
                #    expression is being constructed.
                if "ONE_VAR_CONST" in label:
                    continue
                else:
                    raise KeyError("Variable associated with symbol '%s' "
                                   "is not found on this block" % (label))

            seen_var_ids.add(id(var))

            if (not allow_consistent_values_for_fixed_vars) and \
               var.fixed:
                raise ValueError("Variable '%s' is currently fixed. "
                                 "A new value is not expected "
                                 "in solution" % (var.name))

            for _attr_key, attr_value in entry.items():
                attr_key = _attr_key[0].lower() + _attr_key[1:]
                if attr_key == 'value':
                    if allow_consistent_values_for_fixed_vars and \
                       var.fixed and \
                       (math.fabs(attr_value - var.value) > \
                        comparison_tolerance_for_fixed_vars):
                        raise ValueError(
                            "Variable %s is currently fixed. "
                            "A value of '%s' in solution is "
                            "not within tolerance=%s of the current "
                            "value of '%s'" %
                            (var.name, attr_value,
                             comparison_tolerance_for_fixed_vars, var.value))
                    var.set_value(attr_value, skip_validation=True)
                elif attr_key in valid_import_suffixes:
                    valid_import_suffixes[attr_key][var] = attr_value

        # start to build up the set of unseen variable ids
        unseen_var_ids = set(symbol_map.byObject.keys())
        # at this point it contains ids for non-variable types
        unseen_var_ids.difference_update(seen_var_ids)

        #
        # Load objective solution (should simply be suffixes if
        # they exist)
        #
        objective_skip_attrs = ['id', 'canonical_label', 'value']
        for label, entry in solution.objective.items():
            obj = symbol_map.getObject(label)
            if (obj is None) or \
               (obj is SymbolMap.UnknownSymbol):
                raise KeyError("Objective associated with symbol '%s' "
                               "is not found on this block" % (label))
            # Because of __default_objective__, an objective might
            # appear twice in the objective dictionary.
            unseen_var_ids.discard(id(obj))
            for _attr_key, attr_value in entry.items():
                attr_key = _attr_key[0].lower() + _attr_key[1:]
                if attr_key in valid_import_suffixes:
                    valid_import_suffixes[attr_key][obj] = \
                        attr_value

        #
        # Load constraint solution
        #
        con_skip_attrs = ['id', 'canonical_label']
        for label, entry in solution.constraint.items():
            con = symbol_map.getObject(label)
            if con is SymbolMap.UnknownSymbol:
                #
                # This is a hack - see above.
                #
                if "ONE_VAR_CONST" in label:
                    continue
                else:
                    raise KeyError("Constraint associated with symbol '%s' "
                                   "is not found on this block" % (label))
            unseen_var_ids.remove(id(con))
            for _attr_key, attr_value in entry.items():
                attr_key = _attr_key[0].lower() + _attr_key[1:]
                if attr_key in valid_import_suffixes:
                    valid_import_suffixes[attr_key][con] = \
                        attr_value

        #
        # Load sparse variable solution
        #
        if default_variable_value is not None:
            for var_id in unseen_var_ids:
                var = symbol_map.getObject(symbol_map.byObject[var_id])
                if var.ctype is not IVariable:
                    continue
                if (not allow_consistent_values_for_fixed_vars) and \
                   var.fixed:
                    raise ValueError("Variable '%s' is currently fixed. "
                                     "A new value is not expected "
                                     "in solution" % (var.name))

                if allow_consistent_values_for_fixed_vars and \
                   var.fixed and \
                   (math.fabs(default_variable_value - var.value) > \
                    comparison_tolerance_for_fixed_vars):
                    raise ValueError(
                        "Variable %s is currently fixed. "
                        "A value of '%s' in solution is "
                        "not within tolerance=%s of the current "
                        "value of '%s'" %
                        (var.name, default_variable_value,
                         comparison_tolerance_for_fixed_vars, var.value))
                var.set_value(default_variable_value, skip_validation=True)

        # Set the state flag to "delayed advance": it will auto-advance
        # if a non-stale variable is updated (causing all non-stale
        # variables to be marked as stale).
        StaleFlagManager.mark_all_as_stale(delayed=True)
Exemplo n.º 8
0
    def _apply_solver(self):
        StaleFlagManager.mark_all_as_stale()

        # In recent versions of CPLEX it is helpful to manually open the
        # log file and then explicitly close it after CPLEX is finished.
        # This ensures that the file is closed (and unlocked) on Windows
        # before the TempfileManager (or user) attempts to delete the
        # log file.  Passing in an opened file object is supported at
        # least as far back as CPLEX 12.5.1 [the oldest version
        # supported by IBM as of 1 Oct 2020]
        if self.version() >= (12, 5, 1) \
           and isinstance(self._log_file, str):
            _log_file = (open(self._log_file, 'a'),)
            _close_log_file = True
        else:
            _log_file = (self._log_file,)
            _close_log_file = False
        if self._tee:
            def _process_stream(arg):
                sys.stdout.write(arg)
                return arg
            _log_file += (_process_stream,)
        try:
            self._solver_model.set_results_stream(*_log_file)
            if self._keepfiles:
                print("Solver log file: "+self._log_file)
            
            obj_degree = self._objective.expr.polynomial_degree()
            if obj_degree is None or obj_degree > 2:
                raise DegreeError('CPLEXDirect does not support expressions of degree {0}.'\
                                  .format(obj_degree))
            elif obj_degree == 2:
                quadratic_objective = True
            else:
                quadratic_objective = False
            
            num_integer_vars = self._solver_model.variables.get_num_integer()
            num_binary_vars = self._solver_model.variables.get_num_binary()
            num_sos = self._solver_model.SOS.get_num()
            
            if self._solver_model.quadratic_constraints.get_num() != 0:
                quadratic_cons = True
            else:
                quadratic_cons = False
            
            if (num_integer_vars + num_binary_vars + num_sos) > 0:
                integer = True
            else:
                integer = False
            
            if integer:
                if quadratic_cons:
                    self._solver_model.set_problem_type(self._solver_model.problem_type.MIQCP)
                elif quadratic_objective:
                    self._solver_model.set_problem_type(self._solver_model.problem_type.MIQP)
                else:
                    self._solver_model.set_problem_type(self._solver_model.problem_type.MILP)
            else:
                if quadratic_cons:
                    self._solver_model.set_problem_type(self._solver_model.problem_type.QCP)
                elif quadratic_objective:
                    self._solver_model.set_problem_type(self._solver_model.problem_type.QP)
                else:
                    self._solver_model.set_problem_type(self._solver_model.problem_type.LP)

            # if the user specifies a 'mipgap'
            # set cplex's mip.tolerances.mipgap
            if self.options.mipgap is not None:
                self._solver_model.parameters.mip.tolerances.mipgap.set(float(self.options.mipgap))
            
            for key, option in self.options.items():
                if key == 'mipgap': # handled above
                    continue
                opt_cmd = self._solver_model.parameters
                key_pieces = key.split('_')
                for key_piece in key_pieces:
                    opt_cmd = getattr(opt_cmd, key_piece)
                # When options come from the pyomo command, all
                # values are string types, so we try to cast
                # them to a numeric value in the event that
                # setting the parameter fails.
                try:
                    opt_cmd.set(option)
                except self._cplex.exceptions.CplexError:
                    # we place the exception handling for
                    # checking the cast of option to a float in
                    # another function so that we can simply
                    # call raise here instead of except
                    # TypeError as e / raise e, because the
                    # latter does not preserve the Cplex stack
                    # trace
                    if not _is_numeric(option):
                        raise
                    opt_cmd.set(float(option))
            
            t0 = time.time()
            self._solver_model.solve()
            t1 = time.time()
            self._wallclock_time = t1 - t0
        finally:
            self._solver_model.set_results_stream(None)
            if _close_log_file:
                _log_file[0].close()

        # FIXME: can we get a return code indicating if CPLEX had a significant failure?
        return Bunch(rc=None, log=None)
Exemplo n.º 9
0
    def _apply_solver(self):
        StaleFlagManager.mark_all_as_stale()

        if self._tee:
            self._solver_model.setParam('OutputFlag', 1)
        else:
            self._solver_model.setParam('OutputFlag', 0)

        if self._keepfiles:
            # Only save log file when the user wants to keep it.
            self._solver_model.setParam('LogFile', self._log_file)
            print("Solver log file: " + self._log_file)

        # Options accepted by gurobi (case insensitive):
        # ['Cutoff', 'IterationLimit', 'NodeLimit', 'SolutionLimit', 'TimeLimit',
        #  'FeasibilityTol', 'IntFeasTol', 'MarkowitzTol', 'MIPGap', 'MIPGapAbs',
        #  'OptimalityTol', 'PSDTol', 'Method', 'PerturbValue', 'ObjScale', 'ScaleFlag',
        #  'SimplexPricing', 'Quad', 'NormAdjust', 'BarIterLimit', 'BarConvTol',
        #  'BarCorrectors', 'BarOrder', 'Crossover', 'CrossoverBasis', 'BranchDir',
        #  'Heuristics', 'MinRelNodes', 'MIPFocus', 'NodefileStart', 'NodefileDir',
        #  'NodeMethod', 'PumpPasses', 'RINS', 'SolutionNumber', 'SubMIPNodes', 'Symmetry',
        #  'VarBranch', 'Cuts', 'CutPasses', 'CliqueCuts', 'CoverCuts', 'CutAggPasses',
        #  'FlowCoverCuts', 'FlowPathCuts', 'GomoryPasses', 'GUBCoverCuts', 'ImpliedCuts',
        #  'MIPSepCuts', 'MIRCuts', 'NetworkCuts', 'SubMIPCuts', 'ZeroHalfCuts', 'ModKCuts',
        #  'Aggregate', 'AggFill', 'PreDual', 'DisplayInterval', 'IISMethod', 'InfUnbdInfo',
        #  'LogFile', 'PreCrush', 'PreDepRow', 'PreMIQPMethod', 'PrePasses', 'Presolve',
        #  'ResultFile', 'ImproveStartTime', 'ImproveStartGap', 'Threads', 'Dummy', 'OutputFlag']
        for key, option in self.options.items():
            # When options come from the pyomo command, all
            # values are string types, so we try to cast
            # them to a numeric value in the event that
            # setting the parameter fails.
            try:
                self._solver_model.setParam(key, option)
            except TypeError:
                # we place the exception handling for
                # checking the cast of option to a float in
                # another function so that we can simply
                # call raise here instead of except
                # TypeError as e / raise e, because the
                # latter does not preserve the Gurobi stack
                # trace
                if not _is_numeric(option):
                    raise
                self._solver_model.setParam(key, float(option))

        if self._version_major >= 5:
            for suffix in self._suffixes:
                if re.match(suffix, "dual"):
                    self._solver_model.setParam(gurobipy.GRB.Param.QCPDual, 1)

        self._solver_model.optimize(self._callback)
        self._needs_updated = False

        if self._keepfiles:
            # Change LogFile to make Gurobi close the original log file.
            # May not work for all Gurobi versions, like ver. 9.5.0.
            self._solver_model.setParam('LogFile', 'default')

        # FIXME: can we get a return code indicating if Gurobi had a significant failure?
        return Bunch(rc=None, log=None)