class PyomoDataCommands(object): def __init__(self): self._info = [] self.options = Bunch() def available(self): return True def initialize(self, **kwds): self.filename = kwds.pop('filename') self.add_options(**kwds) def add_options(self, **kwds): self.options.update(kwds) def open(self): if self.filename is None: #pragma:nocover raise IOError("No filename specified") if not os.path.exists(self.filename): #pragma:nocover raise IOError("Cannot find file '%s'" % self.filename) def close(self): pass def read(self): """ This function does nothing, since executing Pyomo data commands both reads and processes the data all at once. """ pass def write(self, data): #pragma:nocover """ This function does nothing, because we cannot write to a *.dat file. """ pass def process(self, model, data, default): """ Read Pyomo data commands and process the data. """ _process_include(['include', self.filename], model, data, default, self.options) def clear(self): self._info = []
class TableData(object): """ A class used to read/write data from/to a table in an external data source. """ def __init__(self): """ Constructor """ self._info = None self._data = None self.options = Bunch() self.options.ncolumns = 1 def available(self): """ Returns: Return :const:`True` if the data manager is available. """ return True def initialize(self, **kwds): """ Initialize the data manager with keyword arguments. The `filename` argument is recognized here, and other arguments are passed to the :func:`add_options` method. """ self.filename = kwds.pop('filename') self.add_options(**kwds) def add_options(self, **kwds): """ Add the keyword options to the :class:`Options` object in this object. """ self.options.update(kwds) def open(self): #pragma:nocover """ Open the data manager. """ pass def read(self): #pragma:nocover """ Read data from the data manager. """ return False def write(self, data): #pragma:nocover """ Write data to the data manager. """ return False def close(self): #pragma:nocover """ Close the data manager. """ pass def process(self, model, data, default): """ Process the data that was extracted from this data manager and return it. """ if model is None: model = self.options.model if not self.options.namespace in data: data[self.options.namespace] = {} return _process_data(self._info, model, data[self.options.namespace], default, self.filename, index=self.options.index, set=self.options.set, param=self.options.param, ncolumns=self.options.ncolumns) def clear(self): """ Clear the data that was extracted from this table """ self._info = None def _set_data(self, headers, rows): from pyomo.core.base.set import Set from pyomo.core.base.param import Param header_index = [] if self.options.select is None: for i in xrange(len(headers)): header_index.append(i) else: for i in self.options.select: try: header_index.append(headers.index(str(i))) except: print( "Model declaration '%s' not found in returned query columns" % str(i)) raise self.options.ncolumns = len(headers) if not self.options.param is None: if not type(self.options.param) in (list, tuple): self.options.param = (self.options.param, ) _params = [] for p in self.options.param: if isinstance(p, Param): self.options.model = p.model() _params.append(p.local_name) else: _params.append(p) self.options.param = tuple(_params) if isinstance(self.options.set, Set): self.options.model = self.options.set.model() self.options.set = self.options.set.local_name if isinstance(self.options.index, Set): self.options.model = self.options.index.model() self.options.index = self.options.index.local_name elif type(self.options.index) in [tuple, list]: tmp = [] for val in self.options.index: if isinstance(val, Set): tmp.append(val.local_name) self.options.model = val.model() else: tmp.append(val) self.options.index = tuple(tmp) if self.options.format is None: if not self.options.set is None: self.options.format = 'set' elif not self.options.param is None: self.options.format = 'table' if self.options.format is None: raise ValueError("Unspecified format and data option") elif self.options.set is None and self.options.param is None: msg = "Must specify the set or parameter option for data" raise IOError(msg) if self.options.format == 'set': if not self.options.index is None: msg = "Cannot specify index for data with the 'set' format: %s" raise IOError(msg % str(self.options.index)) self._info = ["set", self.options.set, ":="] for row in rows: if self.options.ncolumns > 1: self._info.append(tuple(row)) else: self._info.extend(row) elif self.options.format == 'set_array': if not self.options.index is None: msg = "Cannot specify index for data with the 'set_array' " \ 'format: %s' raise IOError(msg % str(self.options.index)) self._info = ["set", self.options.set, ":"] self._info.extend(headers[1:]) self._info.append(":=") for row in rows: self._info.extend(row) elif self.options.format == 'transposed_array': self._info = ["param", self.options.param[0], "(tr)", ":"] self._info.extend(headers[1:]) self._info.append(":=") for row in rows: self._info.extend(row) elif self.options.format == 'array': self._info = ["param", self.options.param[0], ":"] self._info.extend(headers[1:]) self._info.append(":=") for row in rows: self._info.extend(row) elif self.options.format == 'table': if self.options.index is not None: self._info = ["param", ":", self.options.index, ":"] else: self._info = ["param", ":"] for param in self.options.param: self._info.append(param) self._info.append(":=") for row in rows: for i in header_index: self._info.append(row[i]) self.options.ncolumns = len(header_index) else: msg = "Unknown parameter format: '%s'" raise ValueError(msg % self.options.format) def _get_table(self): from pyomo.core.expr import value tmp = [] if self.options.columns is not None: tmp.append(self.options.columns) if self.options.set is not None: # Create column names if self.options.columns is None: cols = [] for i in xrange(self.options.set.dimen): cols.append(self.options.set.local_name + str(i)) tmp.append(cols) # Get rows if self.options.sort is not None: for data in sorted(self.options.set): if self.options.set.dimen > 1: tmp.append(list(data)) else: tmp.append([data]) else: for data in self.options.set: if self.options.set.dimen > 1: tmp.append(list(data)) else: tmp.append([data]) elif self.options.param is not None: if type(self.options.param) in (list, tuple): _param = self.options.param else: _param = [self.options.param] # Collect data for index in _param[0]: if index is None: row = [] elif type(index) in (list, tuple): row = list(index) else: row = [index] for param in _param: row.append(value(param[index])) tmp.append(row) # Create column names if self.options.columns is None: cols = [] for i in xrange(len(tmp[0]) - len(_param)): cols.append('I' + str(i)) for param in _param: cols.append(param) tmp.insert(0, cols) return tmp
class OptSolver(object): """A generic optimization solver""" # # Support "with" statements. Forgetting to call deactivate # on Plugins is a common source of memory leaks # def __enter__(self): return self def __exit__(self, t, v, traceback): pass # # Adding to help track down invalid code after making # the following attributes private # @property def tee(self): _raise_ephemeral_error('tee') @tee.setter def tee(self, val): _raise_ephemeral_error('tee') @property def suffixes(self): _raise_ephemeral_error('suffixes') @suffixes.setter def suffixes(self, val): _raise_ephemeral_error('suffixes') @property def keepfiles(self): _raise_ephemeral_error('keepfiles') @keepfiles.setter def keepfiles(self, val): _raise_ephemeral_error('keepfiles') @property def soln_file(self): _raise_ephemeral_error('soln_file') @soln_file.setter def soln_file(self, val): _raise_ephemeral_error('soln_file') @property def log_file(self): _raise_ephemeral_error('log_file') @log_file.setter def log_file(self, val): _raise_ephemeral_error('log_file') @property def symbolic_solver_labels(self): _raise_ephemeral_error('symbolic_solver_labels') @symbolic_solver_labels.setter def symbolic_solver_labels(self, val): _raise_ephemeral_error('symbolic_solver_labels') @property def warm_start_solve(self): _raise_ephemeral_error('warm_start_solve', keyword=" (warmstart)") @warm_start_solve.setter def warm_start_solve(self, val): _raise_ephemeral_error('warm_start_solve', keyword=" (warmstart)") @property def warm_start_file_name(self): _raise_ephemeral_error('warm_start_file_name', keyword=" (warmstart_file)") @warm_start_file_name.setter def warm_start_file_name(self, val): _raise_ephemeral_error('warm_start_file_name', keyword=" (warmstart_file)") def __init__(self, **kwds): """ Constructor """ # # The 'type' is the class type of the solver instance # if "type" in kwds: self.type = kwds["type"] else: #pragma:nocover raise ValueError("Expected option 'type' for OptSolver constructor") # # The 'name' is either the class type of the solver instance, or a # assigned name. # if "name" in kwds: self.name = kwds["name"] else: self.name = self.type if "doc" in kwds: self._doc = kwds["doc"] else: if self.type is None: # pragma:nocover self._doc = "" elif self.name == self.type: self._doc = "%s OptSolver" % self.name else: self._doc = "%s OptSolver (type %s)" % (self.name,self.type) # # Options are persistent, meaning users must modify the # options dict directly rather than pass them into _presolve # through the solve command. Everything else is reset inside # presolve # self.options = Bunch() if 'options' in kwds and not kwds['options'] is None: for key in kwds['options']: setattr(self.options, key, kwds['options'][key]) # the symbol map is an attribute of the solver plugin only # because it is generated in presolve and used to tag results # so they are interpretable - basically, it persists across # multiple methods. self._smap_id = None # These are ephimeral options that can be set by the user during # the call to solve, but will be reset to defaults if not given self._load_solutions = True self._select_index = 0 self._report_timing = False self._suffixes = [] self._log_file = None self._soln_file = None # overridden by a solver plugin when it returns sparse results self._default_variable_value = None # overridden by a solver plugin when it is always available self._assert_available = False # overridden by a solver plugin to indicate its input file format self._problem_format = None self._valid_problem_formats = [] # overridden by a solver plugin to indicate its results file format self._results_format = None self._valid_result_formats = {} self._results_reader = None self._problem = None self._problem_files = None # # Used to document meta solvers # self._metasolver = False self._version = None # # Data for solver callbacks # self._allow_callbacks = False self._callback = {} # We define no capabilities for the generic solver; base # classes must override this self._capabilities = Bunch() @staticmethod def _options_string_to_dict(istr): ans = {} istr = istr.strip() if not istr: return ans if istr[0] == "'" or istr[0] == '"': istr = eval(istr) tokens = shlex.split(istr) for token in tokens: index = token.find('=') if index == -1: raise ValueError( "Solver options must have the form option=value: '%s'" % istr) try: val = eval(token[(index+1):]) except: val = token[(index+1):] ans[token[:index]] = val return ans def default_variable_value(self): return self._default_variable_value def __bool__(self): return self.available() def version(self): """ Returns a 4-tuple describing the solver executable version. """ if self._version is None: self._version = self._get_version() return self._version def _get_version(self): return None def problem_format(self): """ Returns the current problem format. """ return self._problem_format def set_problem_format(self, format): """ Set the current problem format (if it's valid) and update the results format to something valid for this problem format. """ if format in self._valid_problem_formats: self._problem_format = format else: raise ValueError("%s is not a valid problem format for solver plugin %s" % (format, self)) self._results_format = self._default_results_format(self._problem_format) def results_format(self): """ Returns the current results format. """ return self._results_format def set_results_format(self,format): """ Set the current results format (if it's valid for the current problem format). """ if (self._problem_format in self._valid_results_formats) and \ (format in self._valid_results_formats[self._problem_format]): self._results_format = format else: raise ValueError("%s is not a valid results format for " "problem format %s with solver plugin %s" % (format, self._problem_format, self)) def has_capability(self, cap): """ Returns a boolean value representing whether a solver supports a specific feature. Defaults to 'False' if the solver is unaware of an option. Expects a string. Example: # prints True if solver supports sos1 constraints, and False otherwise print(solver.has_capability('sos1') # prints True is solver supports 'feature', and False otherwise print(solver.has_capability('feature') Parameters ---------- cap: str The feature Returns ------- val: bool Whether or not the solver has the specified capability. """ if not isinstance(cap, str): raise TypeError("Expected argument to be of type '%s', not " "'%s'." % (type(str()), type(cap))) else: val = self._capabilities[str(cap)] if val is None: return False else: return val def available(self, exception_flag=True): """ True if the solver is available """ return True def license_is_valid(self): "True if the solver is present and has a valid license (if applicable)" return True def warm_start_capable(self): """ True is the solver can accept a warm-start solution """ return False def solve(self, *args, **kwds): """ Solve the problem """ self.available(exception_flag=True) # # If the inputs are models, then validate that they have been # constructed! Collect suffix names to try and import from solution. # from pyomo.core.base.block import _BlockData import pyomo.core.base.suffix from pyomo.core.kernel.block import IBlock import pyomo.core.kernel.suffix _model = None for arg in args: if isinstance(arg, (_BlockData, IBlock)): if isinstance(arg, _BlockData): if not arg.is_constructed(): raise RuntimeError( "Attempting to solve model=%s with unconstructed " "component(s)" % (arg.name,) ) _model = arg # import suffixes must be on the top-level model if isinstance(arg, _BlockData): model_suffixes = list(name for (name,comp) \ in pyomo.core.base.suffix.\ active_import_suffix_generator(arg)) else: assert isinstance(arg, IBlock) model_suffixes = list(comp.storage_key for comp in pyomo.core.kernel.suffix.\ import_suffix_generator(arg, active=True, descend_into=False)) if len(model_suffixes) > 0: kwds_suffixes = kwds.setdefault('suffixes',[]) for name in model_suffixes: if name not in kwds_suffixes: kwds_suffixes.append(name) # # Handle ephemeral solvers options here. These # will override whatever is currently in the options # dictionary, but we will reset these options to # their original value at the end of this method. # orig_options = self.options self.options = Bunch() self.options.update(orig_options) self.options.update(kwds.pop('options', {})) self.options.update( self._options_string_to_dict(kwds.pop('options_string', ''))) try: # we're good to go. initial_time = time.time() self._presolve(*args, **kwds) presolve_completion_time = time.time() if self._report_timing: print(" %6.2f seconds required for presolve" % (presolve_completion_time - initial_time)) if not _model is None: self._initialize_callbacks(_model) _status = self._apply_solver() if hasattr(self, '_transformation_data'): del self._transformation_data if not hasattr(_status, 'rc'): logger.warning( "Solver (%s) did not return a solver status code.\n" "This is indicative of an internal solver plugin error.\n" "Please report this to the Pyomo developers." ) elif _status.rc: logger.error( "Solver (%s) returned non-zero return code (%s)" % (self.name, _status.rc,)) if self._tee: logger.error( "See the solver log above for diagnostic information." ) elif hasattr(_status, 'log') and _status.log: logger.error("Solver log:\n" + str(_status.log)) raise ApplicationError( "Solver (%s) did not exit normally" % self.name) solve_completion_time = time.time() if self._report_timing: print(" %6.2f seconds required for solver" % (solve_completion_time - presolve_completion_time)) result = self._postsolve() result._smap_id = self._smap_id result._smap = None if _model: if isinstance(_model, IBlock): if len(result.solution) == 1: result.solution(0).symbol_map = \ getattr(_model, "._symbol_maps")[result._smap_id] result.solution(0).default_variable_value = \ self._default_variable_value if self._load_solutions: _model.load_solution(result.solution(0)) else: assert len(result.solution) == 0 # see the hack in the write method # we don't want this to stick around on the model # after the solve assert len(getattr(_model, "._symbol_maps")) == 1 delattr(_model, "._symbol_maps") del result._smap_id if self._load_solutions and \ (len(result.solution) == 0): logger.error("No solution is available") else: if self._load_solutions: _model.solutions.load_from( result, select=self._select_index, default_variable_value=self._default_variable_value) result._smap_id = None result.solution.clear() else: result._smap = _model.solutions.symbol_map[self._smap_id] _model.solutions.delete_symbol_map(self._smap_id) postsolve_completion_time = time.time() if self._report_timing: print(" %6.2f seconds required for postsolve" % (postsolve_completion_time - solve_completion_time)) finally: # # Reset the options dict # self.options = orig_options return result def _presolve(self, *args, **kwds): self._log_file = kwds.pop("logfile", None) self._soln_file = kwds.pop("solnfile", None) self._select_index = kwds.pop("select", 0) self._load_solutions = kwds.pop("load_solutions", True) self._timelimit = kwds.pop("timelimit", None) self._report_timing = kwds.pop("report_timing", False) self._tee = kwds.pop("tee", False) self._assert_available = kwds.pop("available", True) self._suffixes = kwds.pop("suffixes", []) self.available() if self._problem_format: write_start_time = time.time() (self._problem_files, self._problem_format, self._smap_id) = \ self._convert_problem(args, self._problem_format, self._valid_problem_formats, **kwds) total_time = time.time() - write_start_time if self._report_timing: print(" %6.2f seconds required to write file" % total_time) else: if len(kwds): raise ValueError( "Solver="+self.type+" passed unrecognized keywords: \n\t" +("\n\t".join("%s = %s" % (k,v) for k,v in kwds.items()))) if (type(self._problem_files) in (list,tuple)) and \ (not isinstance(self._problem_files[0], str)): self._problem_files = self._problem_files[0]._problem_files() if self._results_format is None: self._results_format = self._default_results_format(self._problem_format) # # Disabling this check for now. A solver doesn't have just # _one_ results format. # #if self._results_format not in \ # self._valid_result_formats[self._problem_format]: # raise ValueError("Results format '"+str(self._results_format)+"' " # "cannot be used with problem format '" # +str(self._problem_format)+"' in solver "+self.name) if self._results_format == ResultsFormat.soln: self._results_reader = None else: self._results_reader = \ pyomo.opt.base.results.ReaderFactory(self._results_format) def _initialize_callbacks(self, model): """Initialize call-back functions""" pass def _apply_solver(self): """The routine that performs the solve""" raise NotImplementedError #pragma:nocover def _postsolve(self): """The routine that does solve post-processing""" return self.results def _convert_problem(self, args, problem_format, valid_problem_formats, **kwds): return convert_problem(args, problem_format, valid_problem_formats, self.has_capability, **kwds) def _default_results_format(self, prob_format): """Returns the default results format for different problem formats. """ return ResultsFormat.results def reset(self): """ Reset the state of the solver """ pass def _get_options_string(self, options=None): if options is None: options = self.options ans = [] for key in options: val = options[key] if isinstance(val, str) and ' ' in val: ans.append("%s=\"%s\"" % (str(key), str(val))) else: ans.append("%s=%s" % (str(key), str(val))) return ' '.join(ans) def set_options(self, istr): if isinstance(istr, str): istr = self._options_string_to_dict(istr) for key in istr: if not istr[key] is None: setattr(self.options, key, istr[key]) def set_callback(self, name, callback_fn=None): """ Set the callback function for a named callback. A call-back function has the form: def fn(solver, model): pass where 'solver' is the native solver interface object and 'model' is a Pyomo model instance object. """ if not self._allow_callbacks: raise ApplicationError( "Callbacks disabled for solver %s" % self.name) if callback_fn is None: if name in self._callback: del self._callback[name] else: self._callback[name] = callback_fn def config_block(self, init=False): config, blocks = default_config_block(self, init=init) return config
class DirectSolver(DirectOrPersistentSolver): """ Subclasses need to: 1.) Initialize self._solver_model during _presolve before calling DirectSolver._presolve """ def _presolve(self, *args, **kwds): """ kwds not consumed here or at the beginning of OptSolver._presolve will raise an error in OptSolver._presolve. args ---- pyomo Model or IBlock kwds ---- warmstart: bool can only be True if the subclass is warmstart capable; if not, an error will be raised symbolic_solver_labels: bool if True, the model will be translated using the names from the pyomo model; otherwise, the variables and constraints will be numbered with a generic xi skip_trivial_constraints: bool if True, any trivial constraints (e.g., 1 == 1) will be skipped (i.e., not passed to the solver). output_fixed_variable_bounds: bool if False, an error will be raised if a fixed variable is used in any expression rather than the value of the fixed variable. keepfiles: bool if True, the solver log file will be saved and the name of the file will be printed. kwds accepted by OptSolver._presolve """ model = args[0] if len(args) != 1: msg = ( "The {0} plugin method '_presolve' must be supplied a single problem instance - {1} were " + "supplied.").format(type(self), len(args)) raise ValueError(msg) self._set_instance(model, kwds) DirectOrPersistentSolver._presolve(self, **kwds) def solve(self, *args, **kwds): """ Solve the problem """ self.available(exception_flag=True) # # If the inputs are models, then validate that they have been # constructed! Collect suffix names to try and import from solution. # _model = None for arg in args: if isinstance(arg, (_BlockData, IBlock)): if isinstance(arg, _BlockData): if not arg.is_constructed(): raise RuntimeError( "Attempting to solve model=%s with unconstructed " "component(s)" % (arg.name, )) _model = arg # import suffixes must be on the top-level model if isinstance(arg, _BlockData): model_suffixes = list( name for (name, comp) in active_import_suffix_generator(arg)) else: assert isinstance(arg, IBlock) model_suffixes = list( comp.storage_key for comp in import_suffix_generator( arg, active=True, descend_into=False)) if len(model_suffixes) > 0: kwds_suffixes = kwds.setdefault('suffixes', []) for name in model_suffixes: if name not in kwds_suffixes: kwds_suffixes.append(name) # # Handle ephemeral solvers options here. These # will override whatever is currently in the options # dictionary, but we will reset these options to # their original value at the end of this method. # orig_options = self.options self.options = Bunch() self.options.update(orig_options) self.options.update(kwds.pop('options', {})) self.options.update( self._options_string_to_dict(kwds.pop('options_string', ''))) try: # we're good to go. initial_time = time.time() self._presolve(*args, **kwds) presolve_completion_time = time.time() if self._report_timing: print(" %6.2f seconds required for presolve" % (presolve_completion_time - initial_time)) if not _model is None: self._initialize_callbacks(_model) _status = self._apply_solver() if hasattr(self, '_transformation_data'): del self._transformation_data if not hasattr(_status, 'rc'): logger.warning( "Solver (%s) did not return a solver status code.\n" "This is indicative of an internal solver plugin error.\n" "Please report this to the Pyomo developers.") elif _status.rc: logger.error("Solver (%s) returned non-zero return code (%s)" % ( self.name, _status.rc, )) if self._tee: logger.error( "See the solver log above for diagnostic information.") elif hasattr(_status, 'log') and _status.log: logger.error("Solver log:\n" + str(_status.log)) raise ApplicationError("Solver (%s) did not exit normally" % self.name) solve_completion_time = time.time() if self._report_timing: print(" %6.2f seconds required for solver" % (solve_completion_time - presolve_completion_time)) result = self._postsolve() # *********************************************************** # The following code is only needed for backwards compatability of load_solutions=False. # If we ever only want to support the load_vars, load_duals, etc. methods, then this can be deleted. if self._save_results: result._smap_id = self._smap_id result._smap = None if _model: if isinstance(_model, IBlock): if len(result.solution) == 1: result.solution(0).symbol_map = \ getattr(_model, "._symbol_maps")[result._smap_id] result.solution(0).default_variable_value = \ self._default_variable_value if self._load_solutions: _model.load_solution(result.solution(0)) else: assert len(result.solution) == 0 # see the hack in the write method # we don't want this to stick around on the model # after the solve assert len(getattr(_model, "._symbol_maps")) == 1 delattr(_model, "._symbol_maps") del result._smap_id if self._load_solutions and \ (len(result.solution) == 0): logger.error("No solution is available") else: if self._load_solutions: _model.solutions.load_from( result, select=self._select_index, default_variable_value=self. _default_variable_value) result._smap_id = None result.solution.clear() else: result._smap = _model.solutions.symbol_map[ self._smap_id] _model.solutions.delete_symbol_map(self._smap_id) # ******************************************************** postsolve_completion_time = time.time() if self._report_timing: print(" %6.2f seconds required for postsolve" % (postsolve_completion_time - solve_completion_time)) finally: # # Reset the options dict # self.options = orig_options return result
class PersistentSolver(DirectOrPersistentSolver): """ A base class for persistent solvers. Direct solver interfaces do not use any file io. Rather, they interface directly with the python bindings for the specific solver. Persistent solver interfaces are similar except that they "remember" their model. Thus, persistent solver interfaces allow incremental changes to the solver model (e.g., the gurobi python model or the cplex python model). Note that users are responsible for notifying the persistent solver interfaces when changes are made to the corresponding pyomo model. Keyword Arguments ----------------- type: str String indicating the class type of the solver instance. name: str String representing either the class type of the solver instance or an assigned name. doc: str Documentation for the solver options: dict Dictionary of solver options """ def _presolve(self, **kwds): DirectOrPersistentSolver._presolve(self, **kwds) def set_instance(self, model, **kwds): """ This method is used to translate the Pyomo model provided to an instance of the solver's Python model. This discards any existing model and starts from scratch. Parameters ---------- model: ConcreteModel The pyomo model to be used with the solver. Keyword Arguments ----------------- symbolic_solver_labels: bool If True, the solver's components (e.g., variables, constraints) will be given names that correspond to the Pyomo component names. skip_trivial_constraints: bool If True, then any constraints with a constant body will not be added to the solver model. Be careful with this. If a trivial constraint is skipped then that constraint cannot be removed from a persistent solver (an error will be raised if a user tries to remove a non-existent constraint). output_fixed_variable_bounds: bool If False then an error will be raised if a fixed variable is used in one of the solver constraints. This is useful for catching bugs. Ordinarily a fixed variable should appear as a constant value in the solver constraints. If True, then the error will not be raised. """ return self._set_instance(model, kwds) def add_block(self, block): """Add a single Pyomo Block to the solver's model. This will keep any existing model components intact. Parameters ---------- block: Block (scalar Block or single _BlockData) """ if self._pyomo_model is None: raise RuntimeError('You must call set_instance before calling add_block.') # see PR #366 for discussion about handling indexed # objects and keeping compatibility with the # pyomo.kernel objects #if block.is_indexed(): # for sub_block in block.values(): # self._add_block(block) # return self._add_block(block) def set_objective(self, obj): """ Set the solver's objective. Note that, at least for now, any existing objective will be discarded. Other than that, any existing model components will remain intact. Parameters ---------- obj: Objective """ if self._pyomo_model is None: raise RuntimeError('You must call set_instance before calling set_objective.') return self._set_objective(obj) def add_constraint(self, con): """Add a single constraint to the solver's model. This will keep any existing model components intact. Parameters ---------- con: Constraint (scalar Constraint or single _ConstraintData) """ if self._pyomo_model is None: raise RuntimeError('You must call set_instance before calling add_constraint.') # see PR #366 for discussion about handling indexed # objects and keeping compatibility with the # pyomo.kernel objects #if con.is_indexed(): # for child_con in con.values(): # self._add_constraint(child_con) #else: self._add_constraint(con) def add_var(self, var): """Add a single variable to the solver's model. This will keep any existing model components intact. Parameters ---------- var: Var """ if self._pyomo_model is None: raise RuntimeError('You must call set_instance before calling add_var.') if id(self._pyomo_model) != id(var.model()): raise RuntimeError('The pyomo var must be attached to the solver model') # see PR #366 for discussion about handling indexed # objects and keeping compatibility with the # pyomo.kernel objects #if var.is_indexed(): # for child_var in var.values(): # self._add_var(child_var) #else: self._add_var(var) def add_sos_constraint(self, con): """Add a single SOS constraint to the solver's model (if supported). This will keep any existing model components intact. Parameters ---------- con: SOSConstraint """ if self._pyomo_model is None: raise RuntimeError('You must call set_instance before calling add_sos_constraint.') # see PR #366 for discussion about handling indexed # objects and keeping compatibility with the # pyomo.kernel objects #if con.is_indexed(): # for child_con in con.values(): # self._add_sos_constraint(child_con) #else: self._add_sos_constraint(con) def add_column(self, model, var, obj_coef, constraints, coefficients): """Add a column to the solver's and Pyomo model This will add the Pyomo variable var to the solver's model, and put the coefficients on the associated constraints in the solver model. If the obj_coef is not zero, it will add obj_coef*var to the objective of both the Pyomo and solver's model. Parameters ---------- model: pyomo ConcreteModel to which the column will be added var: Var (scalar Var or single _VarData) obj_coef: float, pyo.Param constraints: list of scalar Constraints of single _ConstraintDatas coefficients: list of the coefficient to put on var in the associated constraint """ if self._pyomo_model is None: raise RuntimeError('You must call set_instance before calling add_column.') if id(self._pyomo_model) != id(model): raise RuntimeError('The pyomo model which the column is being added to ' 'must be the same as the pyomo model attached to this ' 'PersistentSolver instance; i.e., the same pyomo model ' 'used in set_instance.') if id(self._pyomo_model) != id(var.model()): raise RuntimeError('The pyomo var must be attached to the solver model') if var in self._pyomo_var_to_solver_var_map: raise RuntimeError('The pyomo var must not have been already added to ' 'the solver model') if len(constraints) != len(coefficients): raise RuntimeError('The list of constraints and the list of coefficents ' 'be of equal length') obj_coef, constraints, coefficients = self._add_and_collect_column_data( var, obj_coef, constraints, coefficients) self._add_column(var, obj_coef, constraints, coefficients) """ This method should be implemented by subclasses.""" def _add_column(self, var, obj_coef, constraints, coefficients): raise NotImplementedError('This method should be implemented by subclasses.') def _add_and_collect_column_data(self, var, obj_coef, constraints, coefficients): """ Update the objective Pyomo objective function and constraints, and update the _vars_referenced_by Maps Returns the column and objective coefficient data to pass to the solver """ ## process the objective if obj_coef.__class__ in native_numeric_types and obj_coef == 0.: pass ## nothing to do else: self._objective.expr += obj_coef*var self._vars_referenced_by_obj.add(var) obj_coef = _convert_to_const(obj_coef) ## add the constraints, collect the ## column information coeff_list = list() constr_list = list() for val,c in zip(coefficients,constraints): c._body += val*var self._vars_referenced_by_con[c].add(var) cval = _convert_to_const(val) coeff_list.append(cval) constr_list.append(self._pyomo_con_to_solver_con_map[c]) return obj_coef, constr_list, coeff_list """ This method should be implemented by subclasses.""" def _remove_constraint(self, solver_con): raise NotImplementedError('This method should be implemented by subclasses.') """ This method should be implemented by subclasses.""" def _remove_sos_constraint(self, solver_sos_con): raise NotImplementedError('This method should be implemented by subclasses.') """ This method should be implemented by subclasses.""" def _remove_var(self, solver_var): raise NotImplementedError('This method should be implemented by subclasses.') def remove_block(self, block): """Remove a single block from the solver's model. This will keep any other model components intact. WARNING: Users must call remove_block BEFORE modifying the block. Parameters ---------- block: Block (scalar Block or a single _BlockData) """ # see PR #366 for discussion about handling indexed # objects and keeping compatibility with the # pyomo.kernel objects #if block.is_indexed(): # for sub_block in block.values(): # self.remove_block(sub_block) # return for sub_block in block.block_data_objects(descend_into=True, active=True): for con in sub_block.component_data_objects(ctype=Constraint, descend_into=False, active=True): self.remove_constraint(con) for con in sub_block.component_data_objects(ctype=SOSConstraint, descend_into=False, active=True): self.remove_sos_constraint(con) for var in block.component_data_objects(ctype=Var, descend_into=True, active=True): self.remove_var(var) def remove_constraint(self, con): """Remove a single constraint from the solver's model. This will keep any other model components intact. Parameters ---------- con: Constraint (scalar Constraint or single _ConstraintData) """ # see PR #366 for discussion about handling indexed # objects and keeping compatibility with the # pyomo.kernel objects #if con.is_indexed(): # for child_con in con.values(): # self.remove_constraint(child_con) # return solver_con = self._pyomo_con_to_solver_con_map[con] self._remove_constraint(solver_con) self._symbol_map.removeSymbol(con) for var in self._vars_referenced_by_con[con]: self._referenced_variables[var] -= 1 del self._vars_referenced_by_con[con] del self._pyomo_con_to_solver_con_map[con] del self._solver_con_to_pyomo_con_map[solver_con] def remove_sos_constraint(self, con): """Remove a single SOS constraint from the solver's model. This will keep any other model components intact. Parameters ---------- con: SOSConstraint """ # see PR #366 for discussion about handling indexed # objects and keeping compatibility with the # pyomo.kernel objects #if con.is_indexed(): # for child_con in con.values(): # self.remove_sos_constraint(child_con) # return solver_con = self._pyomo_con_to_solver_con_map[con] self._remove_sos_constraint(solver_con) self._symbol_map.removeSymbol(con) for var in self._vars_referenced_by_con[con]: self._referenced_variables[var] -= 1 del self._vars_referenced_by_con[con] del self._pyomo_con_to_solver_con_map[con] del self._solver_con_to_pyomo_con_map[solver_con] def remove_var(self, var): """Remove a single variable from the solver's model. This will keep any other model components intact. Parameters ---------- var: Var (scalar Var or single _VarData) """ # see PR #366 for discussion about handling indexed # objects and keeping compatibility with the # pyomo.kernel objects #if var.is_indexed(): # for child_var in var.values(): # self.remove_var(child_var) # return if self._referenced_variables[var] != 0: raise ValueError('Cannot remove Var {0} because it is still referenced by the '.format(var) + 'objective or one or more constraints') solver_var = self._pyomo_var_to_solver_var_map[var] self._remove_var(solver_var) self._symbol_map.removeSymbol(var) del self._referenced_variables[var] del self._pyomo_var_to_solver_var_map[var] del self._solver_var_to_pyomo_var_map[solver_var] """ This method should be implemented by subclasses.""" def update_var(self, var): """ Update a variable in the solver's model. This will update bounds, fix/unfix the variable as needed, and update the variable type. Parameters ---------- var: Var """ raise NotImplementedError('This method should be implemented by subclasses.') def solve(self, *args, **kwds): """ Solve the model. Keyword Arguments ----------------- suffixes: list of str The strings should represnt suffixes support by the solver. Examples include 'dual', 'slack', and 'rc'. options: dict Dictionary of solver options. See the solver documentation for possible solver options. warmstart: bool If True, the solver will be warmstarted. keepfiles: bool If True, the solver log file will be saved. logfile: str Name to use for the solver log file. load_solutions: bool If True and a solution exists, the solution will be loaded into the Pyomo model. report_timing: bool If True, then timing information will be printed. tee: bool If True, then the solver log will be printed. """ if self._pyomo_model is None: msg = 'Please use set_instance to set the instance before calling solve with the persistent' msg += ' solver interface.' raise RuntimeError(msg) if len(args) != 0: if self._pyomo_model is not args[0]: msg = 'The problem instance provided to the solve method is not the same as the instance provided' msg += ' to the set_instance method in the persistent solver interface. ' raise ValueError(msg) self.available(exception_flag=True) # Collect suffix names to try and import from solution. if isinstance(self._pyomo_model, _BlockData): model_suffixes = list(name for (name, comp) in active_import_suffix_generator(self._pyomo_model)) else: assert isinstance(self._pyomo_model, IBlock) model_suffixes = list(comp.storage_key for comp in import_suffix_generator(self._pyomo_model, active=True, descend_into=False)) if len(model_suffixes) > 0: kwds_suffixes = kwds.setdefault('suffixes', []) for name in model_suffixes: if name not in kwds_suffixes: kwds_suffixes.append(name) # # Handle ephemeral solvers options here. These # will override whatever is currently in the options # dictionary, but we will reset these options to # their original value at the end of this method. # orig_options = self.options self.options = Bunch() self.options.update(orig_options) self.options.update(kwds.pop('options', {})) self.options.update(self._options_string_to_dict(kwds.pop('options_string', ''))) try: # we're good to go. initial_time = time.time() self._presolve(**kwds) presolve_completion_time = time.time() if self._report_timing: print(" %6.2f seconds required for presolve" % (presolve_completion_time - initial_time)) if self._pyomo_model is not None: self._initialize_callbacks(self._pyomo_model) _status = self._apply_solver() if hasattr(self, '_transformation_data'): del self._transformation_data if not hasattr(_status, 'rc'): logger.warning( "Solver (%s) did not return a solver status code.\n" "This is indicative of an internal solver plugin error.\n" "Please report this to the Pyomo developers.") elif _status.rc: logger.error( "Solver (%s) returned non-zero return code (%s)" % (self.name, _status.rc,)) if self._tee: logger.error( "See the solver log above for diagnostic information.") elif hasattr(_status, 'log') and _status.log: logger.error("Solver log:\n" + str(_status.log)) raise ApplicationError( "Solver (%s) did not exit normally" % self.name) solve_completion_time = time.time() if self._report_timing: print(" %6.2f seconds required for solver" % (solve_completion_time - presolve_completion_time)) result = self._postsolve() # *********************************************************** # The following code is only needed for backwards compatability of load_solutions=False. # If we ever only want to support the load_vars, load_duals, etc. methods, then this can be deleted. if self._save_results: result._smap_id = self._smap_id result._smap = None _model = self._pyomo_model if _model: if isinstance(_model, IBlock): if len(result.solution) == 1: result.solution(0).symbol_map = \ getattr(_model, "._symbol_maps")[result._smap_id] result.solution(0).default_variable_value = \ self._default_variable_value if self._load_solutions: _model.load_solution(result.solution(0)) else: assert len(result.solution) == 0 # see the hack in the write method # we don't want this to stick around on the model # after the solve assert len(getattr(_model, "._symbol_maps")) == 1 delattr(_model, "._symbol_maps") del result._smap_id if self._load_solutions and \ (len(result.solution) == 0): logger.error("No solution is available") else: if self._load_solutions: _model.solutions.load_from( result, select=self._select_index, default_variable_value=self._default_variable_value) result._smap_id = None result.solution.clear() else: result._smap = _model.solutions.symbol_map[self._smap_id] _model.solutions.delete_symbol_map(self._smap_id) # ******************************************************** postsolve_completion_time = time.time() if self._report_timing: print(" %6.2f seconds required for postsolve" % (postsolve_completion_time - solve_completion_time)) finally: # # Reset the options dict # self.options = orig_options return result def has_instance(self): """ True if set_instance has been called and this solver interface has a pyomo model and a solver model. Returns ------- tmp: bool """ return self._pyomo_model is not None
class JSONDictionary(object): def __init__(self): self._info = {} self.options = Bunch() def available(self): return True def initialize(self, **kwds): self.filename = kwds.pop('filename') self.add_options(**kwds) def add_options(self, **kwds): self.options.update(kwds) def open(self): if self.filename is None: raise IOError("No filename specified") def close(self): pass def read(self): """ This function loads data from a JSON file and tuplizes the nested dictionaries and lists of lists. """ if not os.path.exists(self.filename): raise IOError("Cannot find file '%s'" % self.filename) INPUT = open(self.filename, 'r') jdata = json.load(INPUT) INPUT.close() if jdata is None or len(jdata) == 0: raise IOError("Empty JSON data file") self._info = {} for k,v in jdata.items(): self._info[k] = tuplize(v) def write(self, data): """ This function creates a JSON file for the specified data. """ with open(self.filename, 'w') as OUTPUT: jdata = {} if self.options.data is None: for k,v in data.items(): jdata[k] = detuplize(v) elif type(self.options.data) in (list, tuple): for k in self.options.data: jdata[k] = detuplize(data[k], sort=self.options.sort) else: k = self.options.data jdata[k] = detuplize(data[k]) json.dump(jdata, OUTPUT) def process(self, model, data, default): """ Set the data for the selected components """ if not self.options.namespace in data: data[self.options.namespace] = {} # try: if self.options.data is None: for key in self._info: self._set_data(data, self.options.namespace, key, self._info[key]) elif type(self.options.data) in (list, tuple): for key in self.options.data: self._set_data(data, self.options.namespace, key, self._info[key]) else: key = self.options.data self._set_data(data, self.options.namespace, key, self._info[key]) except KeyError: raise IOError("Data value for '%s' is not available in JSON file '%s'" % (key, self.filename)) def _set_data(self, data, namespace, name, value): if type(value) is dict: data[namespace][name] = value else: data[namespace][name] = {None: value} def clear(self): self._info = {}