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)
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