source: trunk/coopr/pysp/wwphextension.py @ 1768

Last change on this file since 1768 was 1768, checked in by wehart, 11 years ago

Rework of Coopr to use the new PyUtilib? package decomposition.

NOTE: to use Coopr with this update, we need to work with a new version of coopr_install.

  • Property svn:executable set to *
File size: 41.4 KB
Line 
1#  _________________________________________________________________________
2#
3#  coopr: a common optimization python repository
4#  copyright (c) 2009 sandia corporation.
5#  this software is distributed under the bsd license.
6#  under the terms of contract de-ac04-94al85000 with sandia corporation,
7#  the u.s. government retains certain rights in this software.
8#  for more information, see the coopr readme.txt file.
9#  _________________________________________________________________________
10
11import types
12from pyutilib.plugin.core import *
13from coopr.pysp import phextension
14from coopr.pysp.phutils import *
15from coopr.pyomo.base import *
16
17from coopr.pyomo.base.set_types import *
18
19from coopr.pyomo.base.var import VarStatus
20
21import string
22import os
23
24#=========================
25def slam_priority_descend_compare(a, b):
26   # used to sort the variable-suffix map for slamming priority
27   value_a = getattr(a, "SlammingPriority")
28   value_b = getattr(b, "SlammingPriority")
29   return cmp(value_b, value_a)
30
31#==================================================
32#==================================================
33class wwphextension(SingletonPlugin):
34
35   implements (phextension.IPHExtension)
36
37   def __init__(self, *args, **kwds):
38
39      # TBD - migrate all of the self attributes defined on-the-fly
40      #       in the post-post-initialization routine here!
41
42      self._default_configuration_filename = "wwph.cfg"
43      self._configuration_filename = None # over-ride of the default
44      self._suffix_filename = "wwph.suffixes"
45
46
47#=========================
48   def process_suffix_file(self, ph):
49
50      # for each suffix, maintain an inverse map from the suffix name to a list of
51      # objects with that suffix. an object could be a variable (_VarBase) or a
52      # variable value, depending on the resolution of the object for which the
53      # suffix is defined.
54      self._suffix_to_variable_map = {}
55
56      self.slam_list = []
57     
58      if os.path.exists(self._suffix_filename) is False:
59         return
60
61      print "WW PH Extension: Loading variable suffixes from file=", self._suffix_filename
62
63      reference_instance = ph._model_instance
64
65      suffix_file = open(self._suffix_filename,'r')
66      for line in suffix_file.readlines():
67         line = line.strip()
68         if len(line) > 0 and line[0] != '#':
69            pieces = line.split()
70            if len(pieces) != 3:
71               raise runtimeerror, "Illegal line=["+line+"] encountered in ww ph extension suffix file="+self._suffix_filename+"; format is variable, suffix, suffix-value."
72
73            variable_string = pieces[0]
74            suffix_name = pieces[1]
75            suffix_value = pieces[2]
76
77            # decide what type of suffix value we're dealing with.
78            is_int = False
79            is_bool = False
80            converted_value = None
81            try:
82               converted_value = bool(suffix_value)
83               is_bool = True
84            except valueerror:
85               pass
86            try:
87               converted_value = int(suffix_value)
88               is_int = True
89            except ValueError:
90               pass
91
92            if (is_int is False) and (is_bool is False):
93               raise runtimeerror, "WW PH Extension unable to deduce type of variable="+variable_name+" referenced in ww ph extension suffix file="+self._suffix_filename+"; value="+suffix_value
94
95            # determine if we're dealing with complete variables or indexed variables.
96            if isVariableNameIndexed(variable_string) is True:
97
98               variable_name, index_template = extractVariableNameAndIndex(variable_string)
99               
100               # verify that the root variable exists and grab it.
101               if variable_name not in reference_instance._component[var._VarBase].keys():
102                  raise RuntimeError, "Unknown variable="+variable_name+" referenced in ww ph extension suffix file="+self._suffix_filename
103               variable = reference_instance._component[var._VarBase][variable_name]
104
105               # extract all "real", i.e., fully specified, indices matching the index template.
106               match_indices = extractVariableIndices(variable, index_template)
107
108               # there is a possibility that no indices match the input template.
109               # if so, let the user know about it.
110               if len(match_indices) == 0:
111                  raise RuntimeError, "No indices match template="+str(index_template)+" for variable="+variable_name               
112
113               # add the suffix to all variable values identified.
114               for index in match_indices:
115
116                  variable_value = variable[index]
117
118                  # if the suffix already exists, we don't care - we're stomping it.
119                  if hasattr(variable_value, suffix_name) is True:
120                     delattr(variable_value, suffix_name)
121 
122                  # set the suffix on the variable value.
123                  setattr(variable_value, suffix_name, converted_value)
124
125                  # place the variable value in the suffix->variable map, for easy searching elsewhere in this plugin.
126                  if suffix_name not in self._suffix_to_variable_map.keys():
127                     self._suffix_to_variable_map[suffix_name] = []
128                  self._suffix_to_variable_map[suffix_name].append(variable_value)                 
129
130            else:
131
132               # verify that the variable exists.
133               if variable_string not in reference_instance._component[var._VarBase].keys():
134                  raise RuntimeError, "Unknown variable="+variable_string+" referenced in ww ph extension suffix file="+self._suffix_filename
135
136               variable = reference_instance._component[var._VarBase][variable_string]
137
138               # 9/14/2009 - now forcing the user to explicit specify the full
139               # match template (e.g., "foo[*,*]") instead of just the variable
140               # name (e.g., "foo") to represent the set of all indices.
141               
142               # if the variable is a singleton - that is, non-indexed - no brackets is fine.
143               # we'll just tag the var[None] variable value with the (suffix,value) pair.
144               if None not in variable._index:
145                  raise RuntimeError, "Illegal match template="+variable_string+" specified in ww ph extension suffix file="+self._suffix_filename                 
146
147               # if the suffix already exists, we don't care - we're stomping it.
148               if hasattr(variable, suffix_name) is True:
149                  delattr(variable, suffix_name)
150 
151               # set the suffix on the variable.
152               setattr(variable, suffix_name, converted_value)
153
154               # place the variable in the suffix->variable map, for easy searching elsewhere in this plugin.
155               if suffix_name not in self._suffix_to_variable_map.keys():
156                  self._suffix_to_variable_map[suffix_name] = []
157               self._suffix_to_variable_map[suffix_name].append(variable)
158
159#      print "pre-sort suffix->variable map:"
160#      for key,value_list in self._suffix_to_variable_map.items():
161#         print "key=",key,":",
162#         for value in value_list:
163#            print value.name,"",
164#         print ""
165
166      if "SlammingPriority" in self._suffix_to_variable_map:
167         self.slam_list = self._suffix_to_variable_map["SlammingPriority"]
168
169      self.slam_list.sort(slam_priority_descend_compare)
170
171#==================================================
172   def post_ph_initialization(self, ph):
173
174      # set up "global" record keeping.
175      self.cumulative_discrete_fixed_count = 0
176      self.cumulative_continuous_fixed_count = 0
177
178      # we always track convergence of continuous variables, but we may not want to fix/slam them.                                                           
179      self.fix_continuous_variables = False
180
181      # set up the mipgap parameters (zero means ignore)
182      # note: because we lag one iteration, the final will be less than requested
183      # initial and final refer to PH iteration 1 and PH iteration X, where
184      # X is the iteration at which the convergence metric hits 0.
185      self.Iteration0MipGap = 0.0
186      self.InitialMipGap = 0.10
187      self.FinalMipGap = 0.001
188
189      # "Read" the defaults for parameters that control fixing
190      # (these defaults can be overridden at the variable level)
191      # for all six of these, zero means don't do it.
192      self.Iter0FixIfConvergedAtLB = 0 # 1 or 0
193      self.Iter0FixIfConvergedAtUB = 0  # 1 or 0
194      self.Iter0FixIfConvergedAtNB = 0  # 1 or 0 (converged to a non-bound)
195      # TBD: check for range errors for all six of these
196      self.FixWhenItersConvergedAtLB = 10 
197      self.FixWhenItersConvergedAtUB = 10
198      self.FixWhenItersConvergedAtNB = 12  # converged to a non-bound
199      self.FixWhenItersConvergedContinuous = 0
200     
201      # "default" slamming parms:
202      # TBD: These should get ovverides from suffixes
203      # notice that for a particular var, all could be False
204      # TBD: the user might also want to give slamming priorities
205      self.CanSlamToLB = False
206      self.CanSlamToMin = False
207      self.CanSlamToAnywhere = True
208      self.CanSlamToMax = False
209      self.CanSlamToUB = False
210      self.PH_Iters_Between_Cycle_Slams = 1  # zero means "slam at will"
211      self.SlamAfterIter = len(ph._scenario_tree._stages[-1]._tree_nodes)
212
213      self.CycleLengthSlamThreshold = len(ph._scenario_tree._stages[-1]._tree_nodes) 
214      self.W_hash_history_len = max(100, self.CycleLengthSlamThreshold+1) 
215
216      # end of parms
217
218      self._last_slam_iter = -1    # dynamic
219
220      # constants for W vector hashing (low cost rand() is all we need)
221      # note: July 09, dlw is planning to eschew pre-computed random vector
222      # another note: the top level reset is OK, but really it should
223      #   done way down at the index level with a serial number (stored)
224      #   to avoid correlated hash errors
225      # the hash seed was deleted in 1.1 and we seed with the
226      self.W_hash_seed = 17  # we will reset for dynamic rand vector generation
227      self.W_hash_rand_val = self.W_hash_seed # current rand
228      self.W_hash_a = 1317       # a,b, and c are for a simple generator
229      self.W_hash_b = 27699
230      self.W_hash_c = 131072  # that period should be usually long enough for us!
231                              # (assuming fewer than c scenarios)
232
233      # set up tree storage for statistics that we care about tracking.
234      for stage in ph._scenario_tree._stages:
235
236         if stage != ph._scenario_tree._stages[-1]:
237
238            # first, gather all unique variables referenced in self stage
239            # self "gather" step is currently required because we're being lazy
240            # in terms of index management in the scenario tree
241##            stage_variables = {}
242##            for (reference_variable, index_template, reference_index) in stage._variables:
243##               if reference_variable.name not in stage_variables.keys():
244##                  stage_variables[reference_variable.name] = reference_variable
245##
246            for tree_node in stage._tree_nodes:
247               tree_node._num_iters_converged = {}
248               tree_node._last_converged_val = {}
249               tree_node._w_hash = {}
250               tree_node._fixed_var_flag = {}
251
252               # next, create parameters for each variable in the corresponding tree node.
253
254               for (variable, index_template, variable_indices) in stage._variables:
255
256                  variable_name = variable.name
257                  variable_type = variable.domain
258                 
259                  for index in variable_indices:
260
261                     # IMPT: it has bitten us before in this plug-in, so I'll state the obvious:
262                     #       variable values in the last stage of a stochastic program will *not*
263                     #       have a defined _stage attribute.
264#                     print "dlw debug create stage association ",variable_name, index, stage._name                     
265                     variable[index]._stage = stage
266
267                     new_stat_index = variable._index
268                     new_stat_parameter_name = "NODESTAT_NUM_ITERS_CONVERGED_"+variable.name
269                     new_stat_parameter = Param(new_stat_index,name=new_stat_parameter_name)
270                     for newindex in new_stat_index:
271                        new_stat_parameter[newindex] = 0
272                     tree_node._num_iters_converged[variable.name] = new_stat_parameter
273                     
274                     # need to know to what we have most recently converged
275                     new_conv_index = variable._index
276                     new_conv_parameter_name = "NODESTAT_LAST_CONVERGED_VAL_"+variable.name
277                     new_conv_parameter = Param(new_conv_index,name=new_conv_parameter_name)
278                     for newindex in new_conv_index:
279                        new_conv_parameter[newindex] = 0.5 # not an int, so harmless
280                     tree_node._last_converged_val[variable.name] = new_conv_parameter
281                     
282                     # need to know to what has been fixed
283                     new_fix_index = variable._index
284                     new_fix_parameter_name = "NODESTAT_FIXED_FLAG_VAL_"+variable.name
285                     new_fix_parameter = Param(new_fix_index,name=new_fix_parameter_name)
286                     for newindex in new_fix_index:
287                        new_fix_parameter[newindex] = False
288                     tree_node._fixed_var_flag[variable.name] = new_fix_parameter
289                     
290                     # now make the w hash value storage array
291                     new_hash_index = variable._index
292                     new_hash_parameter_name = "W_HASH_STORAGE_"+variable.name
293                     new_hash_parameter = Param(new_hash_index, ph._iteration_index_set, name=new_hash_parameter_name)
294                     for newindex in new_hash_index:
295                        for i in range(0, ph._max_iterations+1):
296                           new_hash_parameter[newindex,i] = 0
297                     tree_node._w_hash[variable.name] = new_hash_parameter
298
299      # store the total variable counts for future reporting.
300      (self.total_discrete_vars,self.total_continuous_vars) = ph.compute_variable_counts()
301
302      if self._configuration_filename is not None:
303         if os.path.exists(self._configuration_filename) is True:
304            print "WW PH Extension: Loading user-specified configuration from file=" + self._configuration_filename
305            execfile(self._configuration_filename)
306         else:
307            raise RuntimeError, "***WW PH Extension: Failed to load user-specified configuration from file="+self._configuration_filename
308           
309      elif os.path.exists(self._default_configuration_filename) is True:
310         print "WW PH Extension: Loading user-specified configuration from file=" + self._default_configuration_filename
311         execfile(self._default_configuration_filename)         
312      else:
313         print "WW PH Extension: No user-specified configuration file supplied - using defaults"
314
315      # process any suffix information, if it exists.
316      self.process_suffix_file(ph)         
317
318      # set up the mip gap for iteration 0.
319      if self.Iteration0MipGap > 0.0:
320         print "Setting mipgap to "+str(self.Iteration0MipGap)
321         ph._solver.options.mip = "tolerances mipgap " + str(self.Iteration0MipGap)
322         
323#==================================================
324   def post_iteration_0_solves(self, ph):
325
326      for stage in ph._scenario_tree._stages:
327         
328         if stage != ph._scenario_tree._stages[-1]: # no blending over the final stage
329           
330            for tree_node in stage._tree_nodes:
331
332               for (variable, index_template, variable_indices) in stage._variables:
333                 
334                  variable_name = variable.name
335                  variable_type = variable.domain
336
337                  for index in variable_indices:
338
339                     # determine if this index is used - otherwise, don't waste time
340                     # fixing and cycle checking. for one, the code will crash :-) with
341                     # none values during the cycle checking computation!
342
343                     is_used = True # until proven otherwise                     
344                     for scenario in tree_node._scenarios:
345                        instance = ph._instances[scenario._name]
346                        if getattr(instance,variable_name)[index].status == VarStatus.unused:
347                           is_used = False
348
349                     if is_used is True:
350
351                        # unlikely, but variables might be fixed even at this stage, depending on
352                        # what weirdness users do prior to the iteration 0 solves.
353                        instance_fixed_count = 0
354                        for scenario in tree_node._scenarios:
355                           instance = ph._instances[scenario._name]
356                           if getattr(instance,variable_name)[index].fixed is True:
357                              instance_fixed_count += 1
358                        if ((instance_fixed_count > 0) and (instance_fixed_count < len(tree_node._scenarios))):
359                           raise RuntimeError, "Variable="+variable_name+str(index)+" is fixed in "+str(instance_fixed_count)+" scenarios, but less than the number of scenarios at tree node="+tree_node._name
360
361                        if instance_fixed_count == 0:
362
363                           if isinstance(variable_type, IntegerSet) or isinstance(variable_type, BooleanSet):                           
364                              node_min = self.Int_If_Close_Enough(ph, tree_node._minimums[variable_name][index]())
365                              node_max = self.Int_If_Close_Enough(ph, tree_node._maximums[variable_name][index]())
366
367                              # update convergence prior to checking for fixing.
368                              self._int_convergence_tracking(ph, tree_node, variable_name, index, node_min, node_max)
369                              attrvariable = ph._model_instance._component[var._VarBase][variable_name]
370                              if hasattr(attrvariable, 'Iter0FixIfConvergedAtLB'):
371                                 lb = getattr(attrvariable, 'Iter0FixIfConvergedAtLB')
372                              else:
373                                 lb = self.Iter0FixIfConvergedAtLB
374                              if hasattr(attrvariable, 'Iter0FixIfConvergedatUB'):
375                                 ub = getattr(attrvariable, 'Iter0FixIfConvergedAtUB')
376                              else:
377                                 ub = self.Iter0FixIfConvergedAtUB
378                              if hasattr(attrvariable, 'Iter0FixIfConvergedAtNB'):
379                                 nb = getattr(attrvariable, 'Iter0FixIfConvergedAtNB')
380                              else:
381                                 nb = self.Iter0FixIfConvergedAtNB
382                              if self._should_fix_discrete_due_to_conv(tree_node, variable, index, lb, ub, nb):
383                                 self._fix_var(ph, tree_node, variable, index, node_min)
384                              elif self.W_hash_history_len > 0:   # if not fixed, then hash - no slamming at iteration 0
385                                 self._w_hash_acct(ph, tree_node, variable_name, index) # obviously not checking for cycles at iteration 0!
386
387                           else:
388                             
389                              node_min = tree_node._minimums[variable_name][index]()
390                              node_max = tree_node._maximums[variable_name][index]()
391                             
392                              self._continuous_convergence_tracking(ph, tree_node, variable_name, index, node_min, node_max)
393                             
394# jpw: not sure if we care about cycle detection in continuous variables?
395#                              if self.W_hash_history_len > 0: 
396#                                 self._w_hash_acct(ph, tree_node, variable_name, index)
397                             
398
399#==================================================
400   def post_iteration_0(self, ph):
401 
402      self._met0 = ph._converger.lastMetric()
403
404      if (self.InitialMipGap > 0) and (self.FinalMipGap >= 0) and (self.InitialMipGap > self.FinalMipGap):
405         gap = self.InitialMipGap
406         print "Setting mipgap to "+str(gap)
407         ph._solver.options.mip = "tolerances mipgap " + str(gap)
408
409#==================================================
410   def post_iteration_k_solves(self, ph):
411
412      for stage in ph._scenario_tree._stages:
413         
414         if stage != ph._scenario_tree._stages[-1]: # no blending over the final stage
415           
416            for tree_node in stage._tree_nodes:
417
418               for (variable, index_template, variable_indices) in stage._variables:
419
420                  variable_name = variable.name
421                  variable_type = variable.domain
422
423                  for index in variable_indices:
424
425                     # determine if this index is used - otherwise, don't waste time
426                     # fixing and cycle checking. for one, the code will crash :-) with
427                     # None values during the cycle checking computation!
428
429                     is_used = True # until proven otherwise                     
430                     for scenario in tree_node._scenarios:
431                        instance = ph._instances[scenario._name]
432                        if getattr(instance,variable_name)[index].status == VarStatus.unused:
433                           is_used = False
434
435                     if is_used is True:                       
436
437                        # determine if the variable is already fixed.
438                        instance_fixed_count = 0
439                        for scenario in tree_node._scenarios:
440                           instance = ph._instances[scenario._name]
441                           if getattr(instance,variable_name)[index].fixed is True:
442                              instance_fixed_count += 1
443                        if ((instance_fixed_count > 0) and (instance_fixed_count < len(tree_node._scenarios))):
444                           raise RuntimeError, "Variable="+variable_name+str(index)+" is fixed in "+str(instance_fixed_count)+" scenarios, but less than the number of scenarios at tree node="+tree_node._name
445
446                        if instance_fixed_count == 0:
447
448                           if isinstance(variable_type, IntegerSet) or isinstance(variable_type, BooleanSet):                           
449                              node_min = self.Int_If_Close_Enough(ph, tree_node._minimums[variable_name][index]())
450                              node_max = self.Int_If_Close_Enough(ph, tree_node._maximums[variable_name][index]())
451
452                              # update convergence prior to checking for fixing.
453                              self._int_convergence_tracking(ph, tree_node, variable_name, index, node_min, node_max)
454
455                              # now check on permissions to converge to various placed (e.g., lb is lb permission)
456                              attrvariable = ph._model_instance._component[var._VarBase][variable_name][index]
457                              if hasattr(attrvariable, 'FixWhenItersConvergedAtLB'):
458                                 lb = getattr(attrvariable, 'FixWhenItersConvergedAtLB')
459                              else:
460                                 lb = self.FixWhenItersConvergedAtLB
461                              if hasattr(attrvariable, 'FixWhenItersConvergedAtUB'):
462                                 ub = getattr(attrvariable, 'FixWhenItersConvergedAtUB')
463                              else:
464                                 ub = self.FixWhenItersConvergedAtUB
465                              if hasattr(attrvariable, 'FixWhenItersConvergedAtNB'):
466                                 nb = getattr(attrvariable, 'FixWhenItersConvergedAtNB')
467                              else:
468                                 nb = self.FixWhenItersConvergedAtNB
469                                 
470                              if self._should_fix_discrete_due_to_conv(tree_node, variable, index, lb, ub, nb):
471                                 
472                                 self._fix_var(ph, tree_node, variable, index, node_min)
473                                 
474                              else: # if not fixed, then hash and slam as necessary.
475                                 
476                                 if self.W_hash_history_len > 0:   
477                                    self._w_hash_acct(ph, tree_node, variable_name, index)
478                                    computed_cycle_length = self.hash_hit_len(ph, tree_node, variable_name, index)
479                                    if (computed_cycle_length >= self.CycleLengthSlamThreshold) and ((ph._current_iteration - self._last_slam_iter) > self.PH_Iters_Between_Cycle_Slams):
480                                       # TBD: we may not want to slam immediately - it may disappear on it's own after a few iterations, depending on what other variables do.
481                                       # note: i am not slamming the offending variable, but a selected variable
482                                       print "Cycle length exceeds slam threshold="+str(self.CycleLengthSlamThreshold)+"; choosing variable to slam"
483                                       self._pick_one_and_slam_it(ph)
484                                    elif (computed_cycle_length > 1) and (computed_cycle_length < self.CycleLengthSlamThreshold):
485                                       # there was a (potential) cycle, but the slam threshold wasn't reached.
486                                       print "Taking no action to break cycle - length="+str(computed_cycle_length)+" does not exceed slam threshold="+str(self.CycleLengthSlamThreshold)
487                                    elif (computed_cycle_length >= self.CycleLengthSlamThreshold) and ((ph._current_iteration - self._last_slam_iter) > self.PH_Iters_Between_Cycle_Slams):
488                                       # we could have slammed, but we recently did and are taking a break to see if things settle down on their own.
489                                       print "Taking no action to break cycle - length="+str(computed_cycle_length)+" does exceed slam threshold="+str(self.CycleLengthSlamThreshold)+ \
490                                             ", but another variable was slammed within the past "+str(self.PH_Iters_Between_Cycle_Slams)+" iterations" 
491                           else:
492
493                              # obviously don't round in the continuous case.
494                              node_min = tree_node._minimums[variable_name][index]()
495                              node_max = tree_node._maximums[variable_name][index]()
496
497                              # update convergence prior to checking for fixing.
498                              self._continuous_convergence_tracking(ph, tree_node, variable_name, index, node_min, node_max)
499
500                              if self._should_fix_continuous_due_to_conv(tree_node, variable, index):
501                                 # fixing to max value for safety (could only be an issue due to tolerances).
502                                 self._fix_var(ph, tree_node, variable, index, node_max)
503                                 # note: we currently don't clam continuous variables!
504
505      # TBD: the 1 might need to be parameterized
506      if (ph._current_iteration > self.SlamAfterIter) and ((ph._current_iteration - self._last_slam_iter) > 1) and (ph._converger.isImproving(1)):
507         self._pick_one_and_slam_it(ph)
508         self._just_slammed_ = True
509      else:
510         self._just_slammed_ = False
511     
512#==================================================
513   def post_iteration_k(self, ph):
514
515      # note: we are lagging one iteration
516      # linear
517      if (self.InitialMipGap > 0 and self.FinalMipGap >= 0) and self.InitialMipGap > self.FinalMipGap:
518         m0 = self._met0
519         m = ph._converger.lastMetric()
520         mlast = ph._converger._convergence_threshold
521         g0 = self.InitialMipGap
522         glast = self.FinalMipGap
523         gap = ((m-m0)/(m0-mlast) + g0/(g0-glast))* (g0-glast)
524         if gap > g0:
525            print "***WARNING: Setting mipgap to thresholded maximal initial mapgap value="+str(g0)+"; unthresholded value="+str(gap)           
526            gap = g0
527         else:
528            print "Setting mipgap to "+str(gap)
529         ph._solver.options.mip = "tolerances mipgap " + str(gap)
530
531
532#==================================================
533   def post_ph_execution(self, ph):
534
535       pass         
536
537#=========================
538   def Int_If_Close_Enough(self, ph, x):
539       # if x is close enough to the nearest integer, return the integer
540       # else return x
541       if abs(round(x)-x) <= ph._integer_tolerance:
542          return int(round(x))
543       else:
544          return x
545
546#=========================   
547   def _int_convergence_tracking(self, ph, tree_node, variable_name, index, node_min, node_max):
548       # keep track of cumulative iters of convergence to the same int
549       if (node_min == node_max) and (type(node_min) is types.IntType): 
550          if node_min == tree_node._last_converged_val[variable_name][index]():
551             tree_node._num_iters_converged[variable_name][index].value = tree_node._num_iters_converged[variable_name][index].value + 1
552          else:
553             tree_node._num_iters_converged[variable_name][index].value = 1
554             tree_node._last_converged_val[variable_name][index] = node_min
555       else:
556          tree_node._num_iters_converged[variable_name][index].value = 0
557          tree_node._last_converged_val[variable_name][index].value = 0.5 
558
559#=========================   
560   def _continuous_convergence_tracking(self, ph, tree_node, variable_name, index, node_min, node_max):
561       # keep track of cumulative iters of convergence to the same value within tolerance.
562       if abs(node_max - node_min) <= ph._integer_tolerance:
563          if abs(node_min - tree_node._last_converged_val[variable_name][index]()) <= ph._integer_tolerance:
564             tree_node._num_iters_converged[variable_name][index].value  = tree_node._num_iters_converged[variable_name][index].value + 1
565          else:
566             tree_node._num_iters_converged[variable_name][index].value = 1
567             tree_node._last_converged_val[variable_name][index] = node_min
568       else:
569          tree_node._num_iters_converged[variable_name][index].value = 0
570          tree_node._last_converged_val[variable_name][index] = 0.2342343243223423 # TBD - avoid the magic constant!
571
572#=========================         
573   def _w_hash_acct(self, ph, tree_node, variable_name, index):
574      # do the w hash accounting work
575      # we hash on the variable ph weights, and not the values; the latter may not shift for some time, while the former should.
576      self.W_hash_rand_val = self.W_hash_seed
577      for scenario in tree_node._scenarios:
578         instance = ph._instances[scenario._name]
579         weight_parameter_name = "PHWEIGHT_"+variable_name
580         tree_node._w_hash[variable_name][index,ph._current_iteration].value += getattr(instance,weight_parameter_name)[index].value * self.W_hash_rand_val
581         self.W_hash_rand_val = (self.W_hash_b + self.W_hash_a * self.W_hash_rand_val) % self.W_hash_c
582
583#=========================
584   def dump_w_hash(self, ph, tree_node, stage):
585       # debug code
586      print "Stage=",stage._name," tree node=",tree_node._name
587      print "PH Iteration      Variable                          PH Weight Hash Value"
588      for (variable, index_template, variable_indices) in stage._variables:
589
590          variable_name = variable.name
591          variable_type = variable.domain
592
593          # TBD - should we cycle-detect on continuous vars?
594          if isinstance(variable_type, IntegerSet) or isinstance(variable_type, BooleanSet):
595             for index in variable_index:
596                print "%4d        %50ls %20.5f" % (ph._current_iteration, tree_node._w_hash[variable_name][index,ph._current_iteration], tree_node._w_hash[variable_name][index,ph._current_iteration]())
597                                                                                                                           
598#=========================
599   def hash_hit_len(self, ph, tree_node, variable_name, index):
600      # return cycles back to closest hash hit for hashval or 0 if no hash hit
601
602      # if the values are converged, then don't report a cycle - often, the weights at convergence are 0s, and even
603      # if they aren't, they won't move if the values are uniform.
604      if (tree_node._num_iters_converged[variable_name][index].value == 0) and (tree_node._fixed_var_flag[variable_name][index].value is False):
605         current_hash_value = tree_node._w_hash[variable_name][index,ph._current_iteration]()
606         # scan starting from the farthest point back in history to the closest - this is required to
607         # identify the longest possible cycles, which is what we want.
608         for i in range(max(ph._current_iteration - self.W_hash_history_len - 1, 1), ph._current_iteration - 1, 1):
609             this_hash_value = tree_node._w_hash[variable_name][index,i]()
610             if abs(this_hash_value - current_hash_value) <= ph._integer_tolerance:
611                if index is None:
612                   print "Possible cycle detected via PH weight hashing - variable="+variable_name+", node="+tree_node._name
613                else:
614                   print "Possible cycle detected via PH weight hashing - variable="+variable_name+indexToString(index)+" node="+ tree_node._name
615                print "Current hash value="+str(current_hash_value)+" matched (within tolerance) hash value="+str(this_hash_value)+" found at PH iteration="+str(i)+"; cycle length="+str(ph._current_iteration - i)
616                return ph._current_iteration - i
617      return 0
618
619#=========================
620   def _fix_var(self, ph, tree_node, variable, index, fix_value):
621       # fix the variable, account for it and maybe output some trace information
622       # note: whether you fix at current values or not can severly impact feasibility later
623       # in the game. my original thought was below - but that didn't work out. if you
624       # make integers, well, integers, all appears to be well.
625       # IMPT: we are leaving the values for individual variables alone, at potentially
626       #       smallish and heterogeneous values. if we fix/round, we run the risk of
627       #       infeasibilities due to numerical issues. the computed value below is
628       #       strictly for output purposes. dlw note: as of aug 1 '09,
629       #       node_min and node_max should be
630       #       int if they should be (so to speak)
631
632       fixing_reported = False # to track whether you have already output the fix message for one scenario.
633
634       for scenario in tree_node._scenarios:
635
636          instance = ph._instances[scenario._name]
637                       
638          getattr(instance,variable.name)[index].fixed = True
639          getattr(instance,variable.name)[index].value = fix_value
640          tree_node._fixed_var_flag[variable.name][index].value = True
641
642          variable_type = variable.domain         
643
644          if fixing_reported is False:
645             # pretty-print the index, string the trailing spaces from the strings.
646             if index is None:
647                print "Fixing variable="+variable.name+" at tree node="+tree_node._name+" to value="+str(fix_value)+"; converged for "+str(tree_node._num_iters_converged[variable.name][index]())+" iterations"
648             else:
649                print "Fixing variable="+variable.name+indexToString(index)+" at tree node="+tree_node._name+" to value="+str(fix_value)+"; converged for "+str(tree_node._num_iters_converged[variable.name][index]())+" iterations"               
650             fixing_reported = True
651             if isinstance(variable_type, IntegerSet) or isinstance(variable_type, BooleanSet):
652                self.cumulative_discrete_fixed_count = self.cumulative_discrete_fixed_count + 1
653             else:
654                self.cumulative_continuous_fixed_count = self.cumulative_continuous_fixed_count + 1                                                           
655
656#=========================
657   def _should_fix_discrete_due_to_conv(self, tree_node, variable, index, lb_iters, ub_iters, nb_iters):
658      # return True if this should be fixed due to convergence
659      variable_name = variable.name
660
661      # jpw: i don't think this logic is correct - shouldn't "non-bound" be moved after the lb/ub checks - this doesn't check a bound!
662      # dlw reply: i meant it to mean "without regard to bound" so i have updated the document
663      if nb_iters > 0 and tree_node._num_iters_converged[variable_name][index]() >= nb_iters:
664            return True
665      else:
666         lb = variable[index].lb
667         ub = variable[index].ub
668         conval = tree_node._last_converged_val[variable_name][index]()
669         # note: if they are converged node_max == node_min
670         if lb_iters > 0 and tree_node._num_iters_converged[variable_name][index]() >= lb_iters and conval == lb:
671            return True
672         elif ub_iters > 0 and tree_node._num_iters_converged[variable_name][index]() >= ub_iters and conval == ub:
673            return True
674      # if we are still here, nothing triggered fixing
675      return False
676
677#=========================
678   def _should_fix_continuous_due_to_conv(self, tree_node, variable, index):
679
680      if self.fix_continuous_variables is True:
681         if self.FixWhenItersConvergedContinuous > 0 and tree_node._num_iters_converged[variable.name][index]() >= self.FixWhenItersConvergedContinuous:
682               return True
683
684      # if we are still here, nothing triggered fixing
685      return False   
686
687#=========================
688   def _slam(self, ph, tree_node, variable, index):
689      # this function returns a boolean indicating if it slammed
690      # TBD in the distant future: also: slam it to somewhere it sort of wants to go
691      # e.g., the anywhere case could be to the mode
692      #   or if more than one dest is True, pick the one closest to the average
693      #   as of sept 09, it is written with the implicit assumption that only one
694      #   destination is True or that if not, then min/max trumps lb/ub and anywhere trumps all
695     
696      fix_value = False  # assume the worst
697      variable_type = variable.domain
698      variable_name = variable.name
699      if isinstance(variable_type, IntegerSet) or isinstance(variable_type, BooleanSet):
700         node_min = self.Int_If_Close_Enough(ph, tree_node._minimums[variable_name][index]())
701         node_max = self.Int_If_Close_Enough(ph, tree_node._maximums[variable_name][index]())
702         anywhere = round(tree_node._averages[variable.name][index].value)
703      else:   
704         node_min = tree_node._minimums[variable_name][index]()
705         node_max = tree_node._maximums[variable_name][index]()
706         anywhere = tree_node._averages[variable.name][index].value
707
708      if self.CanSlamToLB is True: fix_value = variable[index].lb()
709      if self.CanSlamToMin is True: fix_value = node_min
710      if self.CanSlamToUB is True: fix_value = variable[index].ub()
711      if self.CanSlamToMax is True: fix_value = node_max
712      if self.CanSlamToAnywhere is True: fix_value = anywhere
713      if fix_value is False:
714         print "Warning: Not allowed to slam variable="+variable.name+str(index)+" at tree node="+tree_node._name
715         return False
716      else:
717         if index is None:
718            print "Slamming variable="+variable.name+" at tree node="+tree_node._name+" to value="+str(fix_value)
719         else:
720            print "Slamming variable="+variable.name+indexToString(index)+" at tree node="+tree_node._name+" to value="+str(fix_value)
721         self._fix_var(ph, tree_node, variable, index, fix_value)
722         return True
723
724#=========================
725   def _pick_one_and_slam_it(self, ph):
726
727      reference_instance = ph._model_instance
728
729      for vl in self.slam_list:
730         variable_string = str(vl)
731         full_index = None # JPW is not entirely sure of python scoping rules, so I'm declaring this outside of the if-then block.
732         variable_name = None
733         if isVariableNameIndexed(variable_string) is True:
734            pieces = variable_string.split('[')
735            variable_name = string.strip(pieces[0])
736            full_index = pieces[1].rstrip(']')
737            # the full_index is a string - tuplize it!
738            full_index = tupleizeIndexString(full_index)
739         else:
740            variable_name = variable_string
741            full_index = None
742
743         # verify that the root variable exists and grab it.
744         if variable_name not in reference_instance._component[var._VarBase].keys():
745            raise RuntimeError, "Unknown variable="+variable_name+" referenced while slamming. "
746         variable = reference_instance._component[var._VarBase][variable_name]
747
748         didone = False;   # did we find at least one node to slam in?
749         # it is possible (even likely) that the slam list contains variable values that
750         # reside in the final stage - which, because of the initialization loops in
751         # the post_ph_initialization() method, will not have a _stage attribute defined.
752         # check for the presence of this attribute and skip if not present, as it
753         # doesn't make sense to slam variable values in the final stage anyway.
754         if hasattr(variable[full_index],'_stage') is True:
755            for tree_node in variable[full_index]._stage._tree_nodes:
756               # determine if the variable is already fixed (the trusting version...).
757               if tree_node._fixed_var_flag[variable_name][full_index].value is False:
758                  didone = self._slam(ph, tree_node, variable, full_index)
759            if didone:
760               self._last_slam_iter = ph._current_iteration
761               return
762         
763      print "Warning: Nothing free with a non-zero slam priority - no variable will be slammed"
Note: See TracBrowser for help on using the repository browser.