def test_import_suffix_generator(self): m = block() m.s0 = suffix(direction=suffix.LOCAL) m.s0i = suffix(direction=suffix.LOCAL, datatype=suffix.INT) m.s1 = suffix(direction=suffix.IMPORT_EXPORT) m.s1i = suffix(direction=suffix.IMPORT_EXPORT, datatype=suffix.INT) m.s2 = suffix(direction=suffix.IMPORT) m.s2i = suffix(direction=suffix.IMPORT, datatype=suffix.INT) m.s3 = suffix(direction=suffix.EXPORT) m.s3i = suffix(direction=suffix.EXPORT, datatype=suffix.INT) m.b = block() m.b.s0 = suffix(direction=suffix.LOCAL) m.b.s0i = suffix(direction=suffix.LOCAL, datatype=suffix.INT) m.b.s1 = suffix(direction=suffix.IMPORT_EXPORT) m.b.s1i = suffix(direction=suffix.IMPORT_EXPORT, datatype=suffix.INT) m.b.s2 = suffix(direction=suffix.IMPORT) m.b.s2i = suffix(direction=suffix.IMPORT, datatype=suffix.INT) m.b.s3 = suffix(direction=suffix.EXPORT) m.b.s3i = suffix(direction=suffix.EXPORT, datatype=suffix.INT) # default self.assertEqual([id(c_) for c_ in import_suffix_generator(m)], [id(m.s1), id(m.s1i), id(m.s2), id(m.s2i), id(m.b.s1), id(m.b.s1i), id(m.b.s2), id(m.b.s2i)]) # descend_into=False self.assertEqual([id(c_) for c_ in import_suffix_generator(m, descend_into=False)], [id(m.s1), id(m.s1i), id(m.s2), id(m.s2i)]) # datatype=INT self.assertEqual([id(c_) for c_ in import_suffix_generator(m, datatype=suffix.INT)], [id(m.s1i), id(m.s2i), id(m.b.s1i), id(m.b.s2i)]) # active=True m.s1.deactivate() m.b.deactivate() self.assertEqual([id(c_) for c_ in import_suffix_generator(m, active=True)], [id(m.s1i), id(m.s2), id(m.s2i)])
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, IBlockStorage)): 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, IBlockStorage) model_suffixes = list(name for ( name, comp ) in import_suffix_generator( arg, active=True, descend_into=False, return_key=True)) 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 = pyutilib.misc.Options() 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 pyutilib.common.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, IBlockStorage): 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 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, IBlockStorage) model_suffixes = list( name for (name, comp) in import_suffix_generator(self._pyomo_model, active=True, descend_into=False, return_key=True)) 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 = pyutilib.misc.Options() 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 pyutilib.common.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, IBlockStorage): 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)) result.solution.clear() 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 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 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. """ 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 = \ dict((obj.storage_key, obj) for obj in import_suffix_generator(self, active=True)) # 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 itervalues(valid_import_suffixes): 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 iteritems(solution.problem): attr_key = _attr_key[0].lower() + _attr_key[1:] if attr_key in valid_import_suffixes: valid_import_suffixes[attr_key][self] = attr_value # # Load variable data # self._flag_vars_as_stale() var_skip_attrs = ['id','canonical_label'] seen_var_ids = set() for label, entry in iteritems(solution.variable): 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 iteritems(entry): 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.value = attr_value var.stale = False 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 iteritems(solution.objective): 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)) unseen_var_ids.remove(id(obj)) for _attr_key, attr_value in iteritems(entry): 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 iteritems(solution.constraint): 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 iteritems(entry): 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 variable.ctype: 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.value = default_variable_value var.stale = False
def test_import_suffix_generator(self): m = block() m.s0 = suffix(direction=suffix.LOCAL) m.s0i = suffix(direction=suffix.LOCAL, datatype=suffix.INT) m.s1 = suffix(direction=suffix.IMPORT_EXPORT) m.s1i = suffix(direction=suffix.IMPORT_EXPORT, datatype=suffix.INT) m.s2 = suffix(direction=suffix.IMPORT) m.s2i = suffix(direction=suffix.IMPORT, datatype=suffix.INT) m.s3 = suffix(direction=suffix.EXPORT) m.s3i = suffix(direction=suffix.EXPORT, datatype=suffix.INT) m.b = block() m.b.s0 = suffix(direction=suffix.LOCAL) m.b.s0i = suffix(direction=suffix.LOCAL, datatype=suffix.INT) m.b.s1 = suffix(direction=suffix.IMPORT_EXPORT) m.b.s1i = suffix(direction=suffix.IMPORT_EXPORT, datatype=suffix.INT) m.b.s2 = suffix(direction=suffix.IMPORT) m.b.s2i = suffix(direction=suffix.IMPORT, datatype=suffix.INT) m.b.s3 = suffix(direction=suffix.EXPORT) m.b.s3i = suffix(direction=suffix.EXPORT, datatype=suffix.INT) # default self.assertEqual( [(ck_, c_.name) for ck_, c_ in import_suffix_generator(m, return_key=True)], [("s1", m.s1.name), ("s1i", m.s1i.name), ("s2", m.s2.name), ("s2i", m.s2i.name), ("s1", m.b.s1.name), ("s1i", m.b.s1i.name), ("s2", m.b.s2.name), ("s2i", m.b.s2i.name)]) self.assertEqual( [id(c_) for c_ in import_suffix_generator(m, return_key=False)], [ id(m.s1), id(m.s1i), id(m.s2), id(m.s2i), id(m.b.s1), id(m.b.s1i), id(m.b.s2), id(m.b.s2i) ]) # descend_into=False self.assertEqual([(ck_, c_.name) for ck_, c_ in import_suffix_generator( m, descend_into=False, return_key=True)], [("s1", m.s1.name), ("s1i", m.s1i.name), ("s2", m.s2.name), ("s2i", m.s2i.name)]) self.assertEqual([ id(c_) for c_ in import_suffix_generator( m, descend_into=False, return_key=False) ], [id(m.s1), id(m.s1i), id(m.s2), id(m.s2i)]) # datatype=INT self.assertEqual([(ck_, c_.name) for ck_, c_ in import_suffix_generator( m, datatype=suffix.INT, return_key=True)], [("s1i", m.s1i.name), ("s2i", m.s2i.name), ("s1i", m.b.s1i.name), ("s2i", m.b.s2i.name)]) self.assertEqual([ id(c_) for c_ in import_suffix_generator( m, datatype=suffix.INT, return_key=False) ], [id(m.s1i), id(m.s2i), id(m.b.s1i), id(m.b.s2i)]) # active=True m.s1.deactivate() m.b.deactivate() self.assertEqual([(ck_, c_.name) for ck_, c_ in import_suffix_generator( m, active=True, return_key=True)], [("s1i", m.s1i.name), ("s2", m.s2.name), ("s2i", m.s2i.name)]) self.assertEqual([ id(c_) for c_ in import_suffix_generator(m, active=True, return_key=False) ], [id(m.s1i), id(m.s2), id(m.s2i)])