def _pprint_base_impl(self, ostream, verbose, prefix, _name, _doc, _constructed, _attr, _data, _header, _fcn): if ostream is None: ostream = sys.stdout if prefix: ostream = StreamIndenter(ostream, prefix) # FIXME: HACK for backwards compatability with suppressing the # header for the top block if not _attr and self.parent_block() is None: _name = '' # We only indent everything if we printed the header if _attr or _name or _doc: ostream = StreamIndenter(ostream, self._PPRINT_INDENT) # The first line should be a hanging indent (i.e., not indented) ostream.newline = False if _name: ostream.write(_name + " : ") if _doc: ostream.write(_doc + '\n') if _attr: ostream.write(", ".join("%s=%s" % (k, v) for k, v in _attr)) if _attr or _name or _doc: ostream.write("\n") if not _constructed: # HACK: for backwards compatability, Abstract blocks will # still print their assigned components. Should we instead # always pprint unconstructed components (possibly # suppressing the table header if the table is empty)? if self.parent_block() is not None: ostream.write("Not constructed\n") return if type(_fcn) is tuple: _fcn, _fcn2 = _fcn else: _fcn2 = None if _header is not None: if _fcn2 is not None: _data_dict = dict(_data) _data = iteritems(_data_dict) tabular_writer(ostream, '', _data, _header, _fcn) if _fcn2 is not None: for _key in sorted_robust(_data_dict): _fcn2(ostream, _key, _data_dict[_key]) elif _fcn is not None: _data_dict = dict(_data) for _key in sorted_robust(_data_dict): _fcn(ostream, _key, _data_dict[_key]) elif _data is not None: ostream.write(_data)
def test_sorted_robust(self): # Note: as types are sorted by name, int < str < tuple a = sorted_robust([3, 2, 1]) self.assertEqual(a, [1, 2, 3]) # Testthat ints and floats are sorted as "numbers" a = sorted_robust([3, 2.1, 1]) self.assertEqual(a, [1, 2.1, 3]) a = sorted_robust([3, '2', 1]) self.assertEqual(a, [1, 3, '2']) a = sorted_robust([('str1', 'str1'), (1, 'str2')]) self.assertEqual(a, [(1, 'str2'), ('str1', 'str1')]) a = sorted_robust([((1, ), 'str2'), ('str1', 'str1')]) self.assertEqual(a, [('str1', 'str1'), ((1, ), 'str2')]) a = sorted_robust([('str1', 'str1'), ((1, ), 'str2')]) self.assertEqual(a, [('str1', 'str1'), ((1, ), 'str2')]) # ensure it doesn't throw an error # Test for issue https://github.com/Pyomo/pyomo/issues/2019 sorted_robust([(("10_1", 2), None), ((10, 2), None)], key=lambda x: x[0])
def test_sorted_robust(self): # Note: as types are sorted by name, int < str < tuple a = sorted_robust([3,2,1]) self.assertEqual(a, [1,2,3]) a = sorted_robust([3,'2',1]) self.assertEqual(a, [1,3,'2']) a = sorted_robust([('str1','str1'), (1, 'str2')]) self.assertEqual(a, [(1, 'str2'), ('str1','str1')]) a = sorted_robust([((1,), 'str2'), ('str1','str1')]) self.assertEqual(a, [('str1','str1'), ((1,), 'str2')]) a = sorted_robust([('str1','str1'), ((1,), 'str2')]) self.assertEqual(a, [('str1','str1'), ((1,), 'str2')])
def _generate_component_items(components): if type(components) not in {list, tuple}: components = (components, ) for comp in components: if comp.is_indexed(): for idx in sorted_robust(comp): yield idx, comp[idx] else: yield _NotAnIndex, comp
def test_sorted_robust(self): # Note: as types are sorted by name, int < str < tuple a = sorted_robust([3, 2, 1]) self.assertEqual(a, [1, 2, 3]) # Testthat ints and floats are sorted as "numbers" a = sorted_robust([3, 2.1, 1]) self.assertEqual(a, [1, 2.1, 3]) a = sorted_robust([3, '2', 1]) self.assertEqual(a, [1, 3, '2']) a = sorted_robust([('str1', 'str1'), (1, 'str2')]) self.assertEqual(a, [(1, 'str2'), ('str1', 'str1')]) a = sorted_robust([((1,), 'str2'), ('str1', 'str1')]) self.assertEqual(a, [('str1', 'str1'), ((1,), 'str2')]) a = sorted_robust([('str1', 'str1'), ((1,), 'str2')]) self.assertEqual(a, [('str1', 'str1'), ((1,), 'str2')])
def sipopt(instance, paramSubList, perturbList, cloneModel=True, streamSoln=False, keepfiles=False): """This function accepts a Pyomo ConcreteModel, a list of parameters, along with their corresponding perterbation list. The model is then converted into the design structure required to call sipopt to get an approximation perturbed solution with updated bounds on the decision variable. Parameters ---------- instance: ConcreteModel pyomo model object paramSubList: list list of mutable parameters perturbList: list list of perturbed parameter values cloneModel: bool, optional indicator to clone the model. If set to False, the original model will be altered streamSoln: bool, optional indicator to stream IPOPT solution keepfiles: bool, optional preserve solver interface files Returns ------- model: ConcreteModel The model modified for use with sipopt. The returned model has three :class:`Suffix` members defined: - ``model.sol_state_1``: the approximated results at the perturbation point - ``model.sol_state_1_z_L``: the updated lower bound - ``model.sol_state_1_z_U``: the updated upper bound Raises ------ ValueError perturbList argument is expecting a List of Params ValueError length(paramSubList) must equal length(perturbList) ValueError paramSubList will not map to perturbList """ #Verify User Inputs if len(paramSubList) != len(perturbList): raise ValueError("Length of paramSubList argument does not equal " "length of perturbList") for pp in paramSubList: if pp.ctype is not Param: raise ValueError( "paramSubList argument is expecting a list of Params") for pp in paramSubList: if not pp._mutable: raise ValueError("parameters within paramSubList must be mutable") for pp in perturbList: if pp.ctype is not Param: raise ValueError( "perturbList argument is expecting a list of Params") #Add model block to compartmentalize all sipopt data b = Block() block_name = unique_component_name(instance, '_sipopt_data') instance.add_component(block_name, b) #Based on user input clone model or use orignal model for anlaysis if cloneModel: b.tmp_lists = (paramSubList, perturbList) m = instance.clone() instance.del_component(block_name) b = getattr(m, block_name) paramSubList, perturbList = b.tmp_lists del b.tmp_lists else: m = instance #Generate component maps for associating Variables to perturbations varSubList = [] for parameter in paramSubList: tempName = unique_component_name(b, parameter.local_name) b.add_component(tempName, Var(parameter.index_set())) myVar = b.component(tempName) varSubList.append(myVar) #Note: substitutions are not currently compatible with # ComponentMap [ECSA 2018/11/23], this relates to Issue #755 paramCompMap = ComponentMap(zip(paramSubList, varSubList)) variableSubMap = {} #variableSubMap = ComponentMap() paramPerturbMap = ComponentMap(zip(paramSubList, perturbList)) perturbSubMap = {} #perturbSubMap = ComponentMap() paramDataList = [] for parameter in paramSubList: # Loop over each ParamData in the Param Component # # Note: Sets are unordered in Pyomo. For this to be # deterministic, we need to sort the index (otherwise, the # ordering of things in the paramDataList may change). We use # sorted_robust to guard against mixed-type Sets in Python 3.x for kk in sorted_robust(parameter): variableSubMap[id(parameter[kk])] = paramCompMap[parameter][kk] perturbSubMap[id(parameter[kk])] = paramPerturbMap[parameter][kk] paramDataList.append(parameter[kk]) #clone Objective, add to Block, and update any Expressions for cc in list( m.component_data_objects(Objective, active=True, descend_into=True)): tempName = unique_component_name(m, cc.local_name) b.add_component( tempName, Objective(expr=ExpressionReplacementVisitor( substitute=variableSubMap, remove_named_expressions=True).dfs_postorder_stack(cc.expr))) cc.deactivate() #clone Constraints, add to Block, and update any Expressions b.constList = ConstraintList() for cc in list( m.component_data_objects(Constraint, active=True, descend_into=True)): if cc.equality: b.constList.add(expr=ExpressionReplacementVisitor( substitute=variableSubMap, remove_named_expressions=True).dfs_postorder_stack(cc.expr)) else: try: b.constList.add(expr=ExpresssionReplacementVisitor( substitute=variableSubMap, remove_named_expressions=True). dfs_postorder_stack(cc.expr)) except: # Params in either the upper or lower bounds of a ranged # inequaltiy will result in an invalid expression (you cannot # have variables in the bounds of a constraint). If we hit that # problem, we will break up the ranged inequality into separate # constraints # Note that the test for lower / upper == None is probably not # necessary, as the only way we should get here (especially if # we are more careful about the exception that we catch) is if # this is a ranged inequality and we are attempting to insert a # variable into either the lower or upper bound. if cc.lower is not None: b.constList.add(expr=ExpressionReplacementVisitor( substitute=variableSubMap, remove_named_expressions=True).dfs_postorder_stack( cc.lower) <= ExpressionReplacementVisitor( substitute=variableSubMap, remove_named_expressions=True). dfs_postorder_stack(cc.body)) #if cc.lower is not None: # b.constList.add(expr=0<=ExpressionReplacementVisitor( # substitute=variableSubMap, # remove_named_expressions=True).dfs_postorder_stack( # cc.lower) - ExpressionReplacementVisitor( # substitute=variableSubMap, # remove_named_expressions= # True).dfs_postorder_stack(cc.body) # ) if cc.upper is not None: b.constList.add(expr=ExpressionReplacementVisitor( substitute=variableSubMap, remove_named_expressions=True).dfs_postorder_stack( cc.upper) >= ExpressionReplacementVisitor( substitute=variableSubMap, remove_named_expressions=True). dfs_postorder_stack(cc.body)) cc.deactivate() #paramData to varData constraint list b.paramConst = ConstraintList() for ii in paramDataList: jj = variableSubMap[id(ii)] b.paramConst.add(ii == jj) #Create the ipopt_sens (aka sIPOPT) solver plugin using the ASL interface opt = SolverFactory('ipopt_sens', solver_io='nl') if not opt.available(False): raise ImportError('ipopt_sens is not available') #Declare Suffixes m.sens_state_0 = Suffix(direction=Suffix.EXPORT) m.sens_state_1 = Suffix(direction=Suffix.EXPORT) m.sens_state_value_1 = Suffix(direction=Suffix.EXPORT) m.sens_init_constr = Suffix(direction=Suffix.EXPORT) m.sens_sol_state_1 = Suffix(direction=Suffix.IMPORT) m.sens_sol_state_1_z_L = Suffix(direction=Suffix.IMPORT) m.sens_sol_state_1_z_U = Suffix(direction=Suffix.IMPORT) #set sIPOPT data opt.options['run_sens'] = 'yes' # for reasons that are not entirely clear, # ipopt_sens requires the indices to start at 1 kk = 1 for ii in paramDataList: m.sens_state_0[variableSubMap[id(ii)]] = kk m.sens_state_1[variableSubMap[id(ii)]] = kk m.sens_state_value_1[variableSubMap[id(ii)]] = \ value(perturbSubMap[id(ii)]) m.sens_init_constr[b.paramConst[kk]] = kk kk += 1 #Send the model to the ipopt_sens and collect the solution results = opt.solve(m, keepfiles=keepfiles, tee=streamSoln) return m
def sipopt(instance,paramSubList,perturbList,cloneModel=True, streamSoln=False, keepfiles=False): """ This function accepts a Pyomo ConcreteModel, a list of parameters, along with their corresponding perterbation list. The model is then converted into the design structure required to call sipopt to get an approximation perturbed solution with updated bounds on the decision variable. Arguments: instance : ConcreteModel: Expectation No Exceptions pyomo model object paramSubList : Param list of mutable parameters Exception : "paramSubList argument is expecting a List of Params" perturbList : Param list of perturbed parameter values Exception : "perturbList argument is expecting a List of Params" length(paramSubList) must equal length(perturbList) Exception : "paramSubList will not map to perturbList" cloneModel : boolean : default=True indicator to clone the model -if set to False, the original model will be altered streamSoln : boolean : default=False indicator to stream IPOPT solution keepfiles : boolean : default=False indicator to print intermediate file names Returns: m : ConcreteModel converted model for sipopt m.sol_state_1 : Suffix approximated results at perturbation m.sol_state_1_z_L : Suffix updated lower bound m.sol_state_1_z_U : Suffix updated upper bound """ #Verify User Inputs if len(paramSubList)!=len(perturbList): raise ValueError("Length of paramSubList argument does not equal " "length of perturbList") for pp in paramSubList: if pp.type() is not Param: raise ValueError("paramSubList argument is expecting a list of Params") for pp in paramSubList: if not pp._mutable: raise ValueError("parameters within paramSubList must be mutable") for pp in perturbList: if pp.type() is not Param: raise ValueError("perturbList argument is expecting a list of Params") #Add model block to compartmentalize all sipopt data b=Block() block_name = unique_component_name(instance, '_sipopt_data') instance.add_component(block_name, b) #Based on user input clone model or use orignal model for anlaysis if cloneModel: b.tmp_lists = (paramSubList, perturbList) m = instance.clone() instance.del_component(block_name) b = getattr(m, block_name) paramSubList, perturbList = b.tmp_lists del b.tmp_lists else: m = instance #Generate component maps for associating Variables to perturbations varSubList = [] for parameter in paramSubList: tempName = unique_component_name(b,parameter.local_name) b.add_component(tempName,Var(parameter.index_set())) myVar = b.component(tempName) varSubList.append(myVar) #Note: substitutions are not currently compatible with # ComponentMap [ECSA 2018/11/23], this relates to Issue #755 paramCompMap = ComponentMap(zip(paramSubList, varSubList)) variableSubMap = {} #variableSubMap = ComponentMap() paramPerturbMap = ComponentMap(zip(paramSubList,perturbList)) perturbSubMap = {} #perturbSubMap = ComponentMap() paramDataList = [] for parameter in paramSubList: # Loop over each ParamData in the Param Component # # Note: Sets are unordered in Pyomo. For this to be # deterministic, we need to sort the index (otherwise, the # ordering of things in the paramDataList may change). We use # sorted_robust to guard against mixed-type Sets in Python 3.x for kk in sorted_robust(parameter): variableSubMap[id(parameter[kk])]=paramCompMap[parameter][kk] perturbSubMap[id(parameter[kk])]=paramPerturbMap[parameter][kk] paramDataList.append(parameter[kk]) #clone Objective, add to Block, and update any Expressions for cc in list(m.component_data_objects(Objective, active=True, descend_into=True)): tempName=unique_component_name(m,cc.local_name) b.add_component(tempName, Objective(expr=ExpressionReplacementVisitor( substitute=variableSubMap, remove_named_expressions=True).dfs_postorder_stack(cc.expr))) cc.deactivate() #clone Constraints, add to Block, and update any Expressions b.constList = ConstraintList() for cc in list(m.component_data_objects(Constraint, active=True, descend_into=True)): if cc.equality: b.constList.add(expr= ExpressionReplacementVisitor( substitute=variableSubMap, remove_named_expressions=True).dfs_postorder_stack(cc.expr)) else: try: b.constList.add(expr=ExpresssionReplacementVisitor( substitute=variableSubMap, remove_named_expressions=True).dfs_postorder_stack(cc.expr)) except: # Params in either the upper or lower bounds of a ranged # inequaltiy will result in an invalid expression (you cannot # have variables in the bounds of a constraint). If we hit that # problem, we will break up the ranged inequality into separate # constraints # Note that the test for lower / upper == None is probably not # necessary, as the only way we should get here (especially if # we are more careful about the exception that we catch) is if # this is a ranged inequality and we are attempting to insert a # variable into either the lower or upper bound. if cc.lower is not None: b.constList.add(expr=ExpressionReplacementVisitor( substitute=variableSubMap, remove_named_expressions=True).dfs_postorder_stack( cc.lower) <= ExpressionReplacementVisitor( substitute=variableSubMap, remove_named_expressions= True).dfs_postorder_stack(cc.body) ) #if cc.lower is not None: # b.constList.add(expr=0<=ExpressionReplacementVisitor( # substitute=variableSubMap, # remove_named_expressions=True).dfs_postorder_stack( # cc.lower) - ExpressionReplacementVisitor( # substitute=variableSubMap, # remove_named_expressions= # True).dfs_postorder_stack(cc.body) # ) if cc.upper is not None: b.constList.add(expr=ExpressionReplacementVisitor( substitute=variableSubMap, remove_named_expressions=True).dfs_postorder_stack( cc.upper) >= ExpressionReplacementVisitor( substitute=variableSubMap, remove_named_expressions= True).dfs_postorder_stack(cc.body) ) cc.deactivate() #paramData to varData constraint list b.paramConst = ConstraintList() for ii in paramDataList: jj=variableSubMap[id(ii)] b.paramConst.add(ii==jj) #Create the ipopt_sens (aka sIPOPT) solver plugin using the ASL interface opt = SolverFactory('ipopt_sens', solver_io='nl') if not opt.available(False): raise ImportError('ipopt_sens is not available') #Declare Suffixes m.sens_state_0 = Suffix(direction=Suffix.EXPORT) m.sens_state_1 = Suffix(direction=Suffix.EXPORT) m.sens_state_value_1 = Suffix(direction=Suffix.EXPORT) m.sens_init_constr = Suffix(direction=Suffix.EXPORT) m.sens_sol_state_1 = Suffix(direction=Suffix.IMPORT) m.sens_sol_state_1_z_L = Suffix(direction=Suffix.IMPORT) m.sens_sol_state_1_z_U = Suffix(direction=Suffix.IMPORT) #set sIPOPT data opt.options['run_sens'] = 'yes' # for reasons that are not entirely clear, # ipopt_sens requires the indices to start at 1 kk=1 for ii in paramDataList: m.sens_state_0[variableSubMap[id(ii)]] = kk m.sens_state_1[variableSubMap[id(ii)]] = kk m.sens_state_value_1[variableSubMap[id(ii)]] = \ value(perturbSubMap[id(ii)]) m.sens_init_constr[b.paramConst[kk]] = kk kk += 1 #Send the model to the ipopt_sens and collect the solution results = opt.solve(m, keepfiles=keepfiles, tee=streamSoln) return m