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 | |
---|
11 | import types |
---|
12 | from pyutilib.component.core import * |
---|
13 | from coopr.pysp import phextension |
---|
14 | from coopr.pysp.phutils import * |
---|
15 | from coopr.pyomo.base import * |
---|
16 | |
---|
17 | from coopr.pyomo.base.set_types import * |
---|
18 | |
---|
19 | from coopr.pyomo.base.var import VarStatus |
---|
20 | |
---|
21 | import string |
---|
22 | import os |
---|
23 | |
---|
24 | #========================= |
---|
25 | def 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 | class wwphextension(SingletonPlugin): |
---|
33 | |
---|
34 | implements (phextension.IPHExtension) |
---|
35 | |
---|
36 | def __init__(self, *args, **kwds): |
---|
37 | |
---|
38 | # TBD - migrate all of the self attributes defined on-the-fly |
---|
39 | # in the post-post-initialization routine here! |
---|
40 | |
---|
41 | self._configuration_filename = None |
---|
42 | self._suffix_filename = None |
---|
43 | |
---|
44 | #========================= |
---|
45 | def process_suffix_file(self, ph): |
---|
46 | |
---|
47 | # for each suffix, maintain an inverse map from the suffix name to a list of |
---|
48 | # objects with that suffix. an object could be a variable (_VarBase) or a |
---|
49 | # variable value, depending on the resolution of the object for which the |
---|
50 | # suffix is defined. |
---|
51 | self._suffix_to_variable_map = {} |
---|
52 | |
---|
53 | self.slam_list = [] |
---|
54 | |
---|
55 | if self._suffix_filename is None: |
---|
56 | return |
---|
57 | |
---|
58 | if os.path.exists(self._suffix_filename) is False: |
---|
59 | raise RuntimeError, "***WW PH Extension: The suffix file "+self._suffix_filename+" either does not exist or cannot be read" |
---|
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 data referenced in ww ph extension suffix file="+self._suffix_filename+"; value="+suffix_value+" for "+variable_name |
---|
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.active_components(Var).keys(): |
---|
102 | raise RuntimeError, "Unknown variable="+variable_name+" referenced in ww ph extension suffix file="+self._suffix_filename |
---|
103 | variable = reference_instance.active_components(Var)[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.active_components(Var).keys(): |
---|
134 | raise RuntimeError, "Unknown variable="+variable_string+" referenced in ww ph extension suffix file="+self._suffix_filename |
---|
135 | |
---|
136 | variable = reference_instance.active_components(Var)[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_instance_creation(self, ph): |
---|
173 | """ Called after PH initialization has created the scenario instances, but before any PH-related weights/variables/parameters/etc are defined!""" |
---|
174 | # we don't muck with the instances. |
---|
175 | pass |
---|
176 | |
---|
177 | #================================================== |
---|
178 | def post_ph_initialization(self, ph): |
---|
179 | |
---|
180 | # set up "global" record keeping. |
---|
181 | self.cumulative_discrete_fixed_count = 0 |
---|
182 | self.cumulative_continuous_fixed_count = 0 |
---|
183 | |
---|
184 | # we always track convergence of continuous variables, but we may not want to fix/slam them. |
---|
185 | self.fix_continuous_variables = False |
---|
186 | |
---|
187 | # there are occasions where we might want to fix any values at the end of the |
---|
188 | # run if the scenarios agree - even if the normal fixing criterion (e.g., |
---|
189 | # converged for N iterations) don't apply. one example is when the term-diff |
---|
190 | # is 0, at which point you really do have a solution. currently only applies |
---|
191 | # to discrete variables. |
---|
192 | self.fix_discrete_variables_at_exit = False |
---|
193 | |
---|
194 | # set up the mipgap parameters (zero means ignore) |
---|
195 | # note: because we lag one iteration, the final will be less than requested |
---|
196 | # initial and final refer to PH iteration 1 and PH iteration X, where |
---|
197 | # X is the iteration at which the convergence metric hits 0. |
---|
198 | self.Iteration0MipGap = 0.0 |
---|
199 | self.InitialMipGap = 0.10 |
---|
200 | self.FinalMipGap = 0.001 |
---|
201 | |
---|
202 | # "Read" the defaults for parameters that control fixing |
---|
203 | # (these defaults can be overridden at the variable level) |
---|
204 | # for all six of these, zero means don't do it. |
---|
205 | self.Iter0FixIfConvergedAtLB = 0 # 1 or 0 |
---|
206 | self.Iter0FixIfConvergedAtUB = 0 # 1 or 0 |
---|
207 | self.Iter0FixIfConvergedAtNB = 0 # 1 or 0 (converged to a non-bound) |
---|
208 | # TBD: check for range errors for all six of these |
---|
209 | self.FixWhenItersConvergedAtLB = 10 |
---|
210 | self.FixWhenItersConvergedAtUB = 10 |
---|
211 | self.FixWhenItersConvergedAtNB = 12 # converged to a non-bound |
---|
212 | self.FixWhenItersConvergedContinuous = 0 |
---|
213 | |
---|
214 | # "default" slamming parms: |
---|
215 | # TBD: These should get ovverides from suffixes |
---|
216 | # notice that for a particular var, all could be False |
---|
217 | # TBD: the user might also want to give slamming priorities |
---|
218 | self.CanSlamToLB = False |
---|
219 | self.CanSlamToMin = False |
---|
220 | self.CanSlamToAnywhere = True |
---|
221 | self.CanSlamToMax = False |
---|
222 | self.CanSlamToUB = False |
---|
223 | self.PH_Iters_Between_Cycle_Slams = 1 # zero means "slam at will" |
---|
224 | self.SlamAfterIter = len(ph._scenario_tree._stages[-1]._tree_nodes) |
---|
225 | |
---|
226 | self.CycleLengthSlamThreshold = len(ph._scenario_tree._stages[-1]._tree_nodes) |
---|
227 | self.W_hash_history_len = max(100, self.CycleLengthSlamThreshold+1) |
---|
228 | |
---|
229 | self.ReportPotentialCycles = 0 # do I report potential cycles, i.e., those too short to base slamming on? |
---|
230 | |
---|
231 | # end of parms |
---|
232 | |
---|
233 | self._last_slam_iter = -1 # dynamic |
---|
234 | |
---|
235 | # constants for W vector hashing (low cost rand() is all we need) |
---|
236 | # note: July 09, dlw is planning to eschew pre-computed random vector |
---|
237 | # another note: the top level reset is OK, but really it should |
---|
238 | # done way down at the index level with a serial number (stored) |
---|
239 | # to avoid correlated hash errors |
---|
240 | # the hash seed was deleted in 1.1 and we seed with the |
---|
241 | self.W_hash_seed = 17 # we will reset for dynamic rand vector generation |
---|
242 | self.W_hash_rand_val = self.W_hash_seed # current rand |
---|
243 | self.W_hash_a = 1317 # a,b, and c are for a simple generator |
---|
244 | self.W_hash_b = 27699 |
---|
245 | self.W_hash_c = 131072 # that period should be usually long enough for us! |
---|
246 | # (assuming fewer than c scenarios) |
---|
247 | |
---|
248 | # set up tree storage for statistics that we care about tracking. |
---|
249 | for stage in ph._scenario_tree._stages[:-1]: |
---|
250 | |
---|
251 | # first, gather all unique variables referenced in self stage |
---|
252 | # self "gather" step is currently required because we're being lazy |
---|
253 | # in terms of index management in the scenario tree |
---|
254 | ## stage_variables = {} |
---|
255 | ## for (reference_variable, index_template, reference_index) in stage._variables: |
---|
256 | ## if reference_variable.name not in stage_variables.keys(): |
---|
257 | ## stage_variables[reference_variable.name] = reference_variable |
---|
258 | ## |
---|
259 | for tree_node in stage._tree_nodes: |
---|
260 | |
---|
261 | tree_node._num_iters_converged = {} |
---|
262 | tree_node._last_converged_val = {} |
---|
263 | tree_node._w_hash = {} |
---|
264 | tree_node._fixed_var_flag = {} |
---|
265 | |
---|
266 | for (reference_variable, index_template) in stage._variables: |
---|
267 | |
---|
268 | variable_name = reference_variable.name |
---|
269 | variable_type = reference_variable.domain |
---|
270 | |
---|
271 | variable_indices = tree_node._variable_indices[variable_name] |
---|
272 | |
---|
273 | new_param_index = getattr(ph._instances[tree_node._scenarios[0]._name], variable_name)._index |
---|
274 | |
---|
275 | # create the parameters for all indices, even though this might be a bit |
---|
276 | # wasteful. in practice, this probably won't be the case, and determining |
---|
277 | # the reduced index set would probably take longer that it is worth. |
---|
278 | |
---|
279 | new_stat_index = new_param_index |
---|
280 | new_stat_parameter_name = "NODESTAT_NUM_ITERS_CONVERGED_"+reference_variable.name |
---|
281 | new_stat_parameter = None |
---|
282 | # this bit of ugliness is due to Pyomo not correctly handling the Param construction |
---|
283 | # case when the supplied index set consists strictly of None, i.e., the source variable |
---|
284 | # is a singleton. this case be cleaned up when the source issue in Pyomo is fixed. |
---|
285 | if (len(new_stat_index) is 1) and (None in new_stat_index): |
---|
286 | new_stat_parameter = Param(name=new_stat_parameter_name, mutable=True) |
---|
287 | else: |
---|
288 | new_stat_parameter = Param(new_stat_index, name=new_stat_parameter_name, mutable=True) |
---|
289 | for newindex in new_stat_index: |
---|
290 | new_stat_parameter[newindex] = 0 |
---|
291 | tree_node._num_iters_converged[reference_variable.name] = new_stat_parameter |
---|
292 | |
---|
293 | # need to know to what we have most recently converged |
---|
294 | new_conv_index = new_param_index |
---|
295 | new_conv_parameter_name = "NODESTAT_LAST_CONVERGED_VAL_"+reference_variable.name |
---|
296 | new_conv_parameter = None |
---|
297 | if (len(new_conv_index) is 1) and (None in new_conv_index): |
---|
298 | new_conv_parameter = Param(name=new_conv_parameter_name, mutable=True) |
---|
299 | else: |
---|
300 | new_conv_parameter = Param(new_conv_index, name=new_conv_parameter_name, mutable=True) |
---|
301 | for newindex in new_conv_index: |
---|
302 | new_conv_parameter[newindex] = 0.5 # not an int, so harmless |
---|
303 | tree_node._last_converged_val[reference_variable.name] = new_conv_parameter |
---|
304 | |
---|
305 | # need to know to what has been fixed |
---|
306 | new_fix_index = new_param_index |
---|
307 | new_fix_parameter_name = "NODESTAT_FIXED_FLAG_VAL_"+reference_variable.name |
---|
308 | new_fix_parameter = None |
---|
309 | if (len(new_fix_index) is 1) and (None in new_fix_index): |
---|
310 | new_fix_parameter = Param(name=new_fix_parameter_name, mutable=True) |
---|
311 | else: |
---|
312 | new_fix_parameter = Param(new_fix_index, name=new_fix_parameter_name, mutable=True) |
---|
313 | for newindex in new_fix_index: |
---|
314 | new_fix_parameter[newindex] = False |
---|
315 | tree_node._fixed_var_flag[reference_variable.name] = new_fix_parameter |
---|
316 | |
---|
317 | # now make the w hash value storage array |
---|
318 | new_hash_index = new_param_index |
---|
319 | new_hash_parameter_name = "W_HASH_STORAGE_"+reference_variable.name |
---|
320 | new_hash_parameter = None |
---|
321 | if (len(new_hash_index) is 1) and (None in new_hash_index): |
---|
322 | new_hash_parameter = Param(ph._iteration_index_set, name=new_hash_parameter_name, mutable=True) |
---|
323 | else: |
---|
324 | new_hash_parameter = Param(new_hash_index, ph._iteration_index_set, name=new_hash_parameter_name, mutable=True) |
---|
325 | for newindex in new_hash_index: |
---|
326 | for i in range(0, ph._max_iterations+1): |
---|
327 | # the following if-then block is a complete hack, due to the |
---|
328 | # fact that we can't index by None if the Param is unary. |
---|
329 | if new_hash_parameter.dim() == 1: |
---|
330 | new_hash_parameter[i] = 0 |
---|
331 | else: |
---|
332 | new_hash_parameter[newindex,i] = 0 |
---|
333 | tree_node._w_hash[reference_variable.name] = new_hash_parameter |
---|
334 | |
---|
335 | # JPW has no idea why the following code block is here, or if it is necessary. |
---|
336 | for index in variable_indices: |
---|
337 | |
---|
338 | # IMPT: it has bitten us before in this plug-in, so I'll state the obvious: |
---|
339 | # variable values in the last stage of a stochastic program will *not* |
---|
340 | # have a defined _stage attribute. |
---|
341 | # NOTE: the use of the reference variable here, in contrast to a instance-specific |
---|
342 | # variable, is potentially dangerous. |
---|
343 | reference_variable[index]._stage = stage |
---|
344 | |
---|
345 | |
---|
346 | # store the total variable counts for future reporting. |
---|
347 | (self.total_discrete_vars,self.total_continuous_vars) = ph.compute_variable_counts() |
---|
348 | |
---|
349 | if self._configuration_filename is not None: |
---|
350 | if os.path.exists(self._configuration_filename) is True: |
---|
351 | print "WW PH Extension: Loading user-specified configuration from file=" + self._configuration_filename |
---|
352 | execfile(self._configuration_filename) |
---|
353 | else: |
---|
354 | raise RuntimeError, "***WW PH Extension: The configuration file "+self._configuration_filename+" either does not exist or cannot be read" |
---|
355 | else: |
---|
356 | print "WW PH Extension: No user-specified configuration file supplied - using defaults" |
---|
357 | |
---|
358 | # process any suffix information, if it exists. |
---|
359 | self.process_suffix_file(ph) |
---|
360 | |
---|
361 | # set up the mip gap for iteration 0. |
---|
362 | if self.Iteration0MipGap > 0.0: |
---|
363 | print "Setting mipgap to "+str(self.Iteration0MipGap) |
---|
364 | ph._mipgap = self.Iteration0MipGap |
---|
365 | |
---|
366 | #================================================== |
---|
367 | def post_iteration_0_solves(self, ph): |
---|
368 | |
---|
369 | for stage in ph._scenario_tree._stages[:-1]: |
---|
370 | |
---|
371 | for tree_node in stage._tree_nodes: |
---|
372 | |
---|
373 | for (variable, index_template) in stage._variables: |
---|
374 | |
---|
375 | variable_name = variable.name |
---|
376 | variable_type = variable.domain |
---|
377 | |
---|
378 | variable_indices = tree_node._variable_indices[variable_name] |
---|
379 | |
---|
380 | for index in variable_indices: |
---|
381 | |
---|
382 | # determine if this index is used - otherwise, don't waste time |
---|
383 | # fixing and cycle checking. for one, the code will crash :-) with |
---|
384 | # none values during the cycle checking computation! |
---|
385 | |
---|
386 | is_used = True # until proven otherwise |
---|
387 | for scenario in tree_node._scenarios: |
---|
388 | instance = ph._instances[scenario._name] |
---|
389 | if getattr(instance,variable_name)[index].status == VarStatus.unused: |
---|
390 | is_used = False |
---|
391 | |
---|
392 | if is_used is True: |
---|
393 | |
---|
394 | # unlikely, but variables might be fixed even at this stage, depending on |
---|
395 | # what weirdness users do prior to the iteration 0 solves. |
---|
396 | instance_fixed_count = 0 |
---|
397 | for scenario in tree_node._scenarios: |
---|
398 | instance = ph._instances[scenario._name] |
---|
399 | if getattr(instance,variable_name)[index].fixed is True: |
---|
400 | instance_fixed_count += 1 |
---|
401 | if ((instance_fixed_count > 0) and (instance_fixed_count < len(tree_node._scenarios))): |
---|
402 | 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 |
---|
403 | |
---|
404 | if instance_fixed_count == 0: |
---|
405 | |
---|
406 | if isinstance(variable_type, IntegerSet) or isinstance(variable_type, BooleanSet): |
---|
407 | node_min = self.Int_If_Close_Enough(ph, value(tree_node._minimums[variable_name][index])) |
---|
408 | node_max = self.Int_If_Close_Enough(ph, value(tree_node._maximums[variable_name][index])) |
---|
409 | |
---|
410 | # update convergence prior to checking for fixing. |
---|
411 | self._int_convergence_tracking(ph, tree_node, variable_name, index, node_min, node_max) |
---|
412 | attrvariable = ph._model_instance.active_components(Var)[variable_name][index] |
---|
413 | if hasattr(attrvariable, 'Iter0FixIfConvergedAtLB'): |
---|
414 | lb = getattr(attrvariable, 'Iter0FixIfConvergedAtLB') |
---|
415 | else: |
---|
416 | lb = self.Iter0FixIfConvergedAtLB |
---|
417 | if hasattr(attrvariable, 'Iter0FixIfConvergedatUB'): |
---|
418 | ub = getattr(attrvariable, 'Iter0FixIfConvergedAtUB') |
---|
419 | else: |
---|
420 | ub = self.Iter0FixIfConvergedAtUB |
---|
421 | if hasattr(attrvariable, 'Iter0FixIfConvergedAtNB'): |
---|
422 | nb = getattr(attrvariable, 'Iter0FixIfConvergedAtNB') |
---|
423 | else: |
---|
424 | nb = self.Iter0FixIfConvergedAtNB |
---|
425 | if self._should_fix_discrete_due_to_conv(tree_node, variable, index, lb, ub, nb): |
---|
426 | self._fix_var(ph, tree_node, variable, index, node_min) |
---|
427 | elif self.W_hash_history_len > 0: # if not fixed, then hash - no slamming at iteration 0 |
---|
428 | self._w_hash_acct(ph, tree_node, variable_name, index) # obviously not checking for cycles at iteration 0! |
---|
429 | |
---|
430 | else: |
---|
431 | |
---|
432 | node_min = value(tree_node._minimums[variable_name][index]) |
---|
433 | node_max = value(tree_node._maximums[variable_name][index]) |
---|
434 | |
---|
435 | self._continuous_convergence_tracking(ph, tree_node, variable_name, index, node_min, node_max) |
---|
436 | |
---|
437 | # jpw: not sure if we care about cycle detection in continuous variables? |
---|
438 | # if self.W_hash_history_len > 0: |
---|
439 | # self._w_hash_acct(ph, tree_node, variable_name, index) |
---|
440 | |
---|
441 | |
---|
442 | #================================================== |
---|
443 | def post_iteration_0(self, ph): |
---|
444 | |
---|
445 | self._met0 = ph._converger.lastMetric() |
---|
446 | |
---|
447 | if (self.InitialMipGap > 0) and (self.FinalMipGap >= 0) and (self.InitialMipGap > self.FinalMipGap): |
---|
448 | gap = self.InitialMipGap |
---|
449 | print "Setting mipgap to "+str(gap) |
---|
450 | ph._mipgap = gap |
---|
451 | |
---|
452 | #================================================== |
---|
453 | |
---|
454 | def pre_iteration_k_solves(self, ph): |
---|
455 | """ Called immediately before the iteration k solves!""" |
---|
456 | # we don't muck with the PH objectives |
---|
457 | pass |
---|
458 | |
---|
459 | #================================================== |
---|
460 | def post_iteration_k_solves(self, ph): |
---|
461 | |
---|
462 | for stage in ph._scenario_tree._stages[:-1]: |
---|
463 | |
---|
464 | for tree_node in stage._tree_nodes: |
---|
465 | |
---|
466 | for (variable, index_template) in stage._variables: |
---|
467 | |
---|
468 | variable_name = variable.name |
---|
469 | variable_type = variable.domain |
---|
470 | |
---|
471 | variable_indices = tree_node._variable_indices[variable_name] |
---|
472 | |
---|
473 | for index in variable_indices: |
---|
474 | |
---|
475 | # determine if this index is used - otherwise, don't waste time |
---|
476 | # fixing and cycle checking. for one, the code will crash :-) with |
---|
477 | # None values during the cycle checking computation! |
---|
478 | |
---|
479 | is_used = True # until proven otherwise |
---|
480 | for scenario in tree_node._scenarios: |
---|
481 | instance = ph._instances[scenario._name] |
---|
482 | if getattr(instance,variable_name)[index].status == VarStatus.unused: |
---|
483 | is_used = False |
---|
484 | |
---|
485 | if is_used is True: |
---|
486 | |
---|
487 | # determine if the variable is already fixed. |
---|
488 | instance_fixed_count = 0 |
---|
489 | for scenario in tree_node._scenarios: |
---|
490 | instance = ph._instances[scenario._name] |
---|
491 | if getattr(instance,variable_name)[index].fixed is True: |
---|
492 | instance_fixed_count += 1 |
---|
493 | if ((instance_fixed_count > 0) and (instance_fixed_count < len(tree_node._scenarios))): |
---|
494 | 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 |
---|
495 | |
---|
496 | if instance_fixed_count == 0: |
---|
497 | |
---|
498 | if isinstance(variable_type, IntegerSet) or isinstance(variable_type, BooleanSet): |
---|
499 | node_min = self.Int_If_Close_Enough(ph, value(tree_node._minimums[variable_name][index])) |
---|
500 | node_max = self.Int_If_Close_Enough(ph, value(tree_node._maximums[variable_name][index])) |
---|
501 | |
---|
502 | # update convergence prior to checking for fixing. |
---|
503 | self._int_convergence_tracking(ph, tree_node, variable_name, index, node_min, node_max) |
---|
504 | |
---|
505 | # now check on permissions to converge to various placed (e.g., lb is lb permission) |
---|
506 | attrvariable = ph._model_instance.active_components(Var)[variable_name][index] |
---|
507 | if hasattr(attrvariable, 'FixWhenItersConvergedAtLB'): |
---|
508 | lb = getattr(attrvariable, 'FixWhenItersConvergedAtLB') |
---|
509 | else: |
---|
510 | lb = self.FixWhenItersConvergedAtLB |
---|
511 | if hasattr(attrvariable, 'FixWhenItersConvergedAtUB'): |
---|
512 | ub = getattr(attrvariable, 'FixWhenItersConvergedAtUB') |
---|
513 | else: |
---|
514 | ub = self.FixWhenItersConvergedAtUB |
---|
515 | if hasattr(attrvariable, 'FixWhenItersConvergedAtNB'): |
---|
516 | nb = getattr(attrvariable, 'FixWhenItersConvergedAtNB') |
---|
517 | else: |
---|
518 | nb = self.FixWhenItersConvergedAtNB |
---|
519 | |
---|
520 | if self._should_fix_discrete_due_to_conv(tree_node, variable, index, lb, ub, nb): |
---|
521 | |
---|
522 | self._fix_var(ph, tree_node, variable, index, node_min) |
---|
523 | |
---|
524 | else: # if not fixed, then hash and slam as necessary. |
---|
525 | |
---|
526 | if self.W_hash_history_len > 0: |
---|
527 | self._w_hash_acct(ph, tree_node, variable_name, index) |
---|
528 | computed_cycle_length,msg = self.hash_hit_len(ph, tree_node, variable_name, index, False) |
---|
529 | if (computed_cycle_length >= self.CycleLengthSlamThreshold) and ((ph._current_iteration - self._last_slam_iter) > self.PH_Iters_Between_Cycle_Slams): |
---|
530 | # 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. |
---|
531 | # note: we are *not* slamming the offending variable, but a selected variable |
---|
532 | if index is None: |
---|
533 | print "Cycle issue detected with variable="+variable_name |
---|
534 | else: |
---|
535 | print "Cycle issue detected with variable="+variable_name+"["+str(index)+"]" |
---|
536 | print msg |
---|
537 | print "Cycle length exceeds iteration slamming threshold="+str(self.CycleLengthSlamThreshold)+"; choosing a variable to slam" |
---|
538 | self._pick_one_and_slam_it(ph) |
---|
539 | elif (computed_cycle_length > 1) and (computed_cycle_length < self.CycleLengthSlamThreshold): |
---|
540 | # there was a (potential) cycle, but the slam threshold wasn't reached. |
---|
541 | if self.ReportPotentialCycles is True: |
---|
542 | if index is None: |
---|
543 | print "Cycle issue detected with variable="+variable_name |
---|
544 | else: |
---|
545 | print "Cycle issue detected with variable="+variable_name+"["+str(index)+"]" |
---|
546 | print msg |
---|
547 | print "Taking no action to break cycle - length="+str(computed_cycle_length)+" does not exceed slam threshold="+str(self.CycleLengthSlamThreshold) |
---|
548 | elif (computed_cycle_length >= self.CycleLengthSlamThreshold) and ((ph._current_iteration - self._last_slam_iter) > self.PH_Iters_Between_Cycle_Slams): |
---|
549 | # we could have slammed, but we recently did and are taking a break to see if things settle down on their own. |
---|
550 | if index is None: |
---|
551 | print "Cycle issue detected with variable="+variable_name |
---|
552 | else: |
---|
553 | print "Cycle issue detected with variable="+variable_name+"["+str(index)+"]" |
---|
554 | print msg |
---|
555 | print "Taking no action to break cycle - length="+str(computed_cycle_length)+" does exceed slam threshold="+str(self.CycleLengthSlamThreshold)+ \ |
---|
556 | ", but another variable was slammed within the past "+str(self.PH_Iters_Between_Cycle_Slams)+" iterations" |
---|
557 | else: |
---|
558 | |
---|
559 | # obviously don't round in the continuous case. |
---|
560 | node_min = value(tree_node._minimums[variable_name][index]) |
---|
561 | node_max = value(tree_node._maximums[variable_name][index]) |
---|
562 | |
---|
563 | # update convergence prior to checking for fixing. |
---|
564 | self._continuous_convergence_tracking(ph, tree_node, variable_name, index, node_min, node_max) |
---|
565 | |
---|
566 | if self._should_fix_continuous_due_to_conv(tree_node, variable, index): |
---|
567 | # fixing to max value for safety (could only be an issue due to tolerances). |
---|
568 | self._fix_var(ph, tree_node, variable, index, node_max) |
---|
569 | # note: we currently don't clam continuous variables! |
---|
570 | |
---|
571 | # TBD: the 1 might need to be parameterized - TBD - the 1 should be the PH ITERATIONS BETWEEN CYCLE SLAMS |
---|
572 | if (ph._current_iteration > self.SlamAfterIter) and \ |
---|
573 | ((ph._current_iteration - self._last_slam_iter) > self.PH_Iters_Between_Cycle_Slams) and \ |
---|
574 | (ph._converger.isImproving(self.PH_Iters_Between_Cycle_Slams)): |
---|
575 | print "Slamming criteria are satisifed - accelerating convergence" |
---|
576 | self._pick_one_and_slam_it(ph) |
---|
577 | self._just_slammed_ = True |
---|
578 | else: |
---|
579 | self._just_slammed_ = False |
---|
580 | |
---|
581 | #================================================== |
---|
582 | def post_iteration_k(self, ph): |
---|
583 | |
---|
584 | # note: we are lagging one iteration |
---|
585 | # linear |
---|
586 | if (self.InitialMipGap > 0 and self.FinalMipGap >= 0) and self.InitialMipGap > self.FinalMipGap: |
---|
587 | m0 = self._met0 |
---|
588 | m = ph._converger.lastMetric() |
---|
589 | mlast = ph._converger._convergence_threshold |
---|
590 | g0 = self.InitialMipGap |
---|
591 | glast = self.FinalMipGap |
---|
592 | gap = ((m-m0)/(m0-mlast) + g0/(g0-glast))* (g0-glast) |
---|
593 | if gap > g0: |
---|
594 | print "***WARNING: Setting mipgap to thresholded maximal initial mapgap value="+str(g0)+"; unthresholded value="+str(gap) |
---|
595 | gap = g0 |
---|
596 | else: |
---|
597 | print "Setting mipgap to "+str(gap) |
---|
598 | ph._mipgap = gap |
---|
599 | |
---|
600 | |
---|
601 | #================================================== |
---|
602 | def post_ph_execution(self, ph): |
---|
603 | |
---|
604 | if self.fix_discrete_variables_at_exit is True: |
---|
605 | print "WW PH extension: Fixing all discrete variables that are converged at termination" |
---|
606 | self._fix_all_converged_discrete_variables(ph) |
---|
607 | |
---|
608 | #========================= |
---|
609 | def Int_If_Close_Enough(self, ph, x): |
---|
610 | # if x is close enough to the nearest integer, return the integer |
---|
611 | # else return x |
---|
612 | if abs(round(x)-x) <= ph._integer_tolerance: |
---|
613 | return int(round(x)) |
---|
614 | else: |
---|
615 | return x |
---|
616 | |
---|
617 | #========================= |
---|
618 | def _int_convergence_tracking(self, ph, tree_node, variable_name, index, node_min, node_max): |
---|
619 | # keep track of cumulative iters of convergence to the same int |
---|
620 | if (node_min == node_max) and (type(node_min) is types.IntType): |
---|
621 | if node_min == value(tree_node._last_converged_val[variable_name][index]): |
---|
622 | tree_node._num_iters_converged[variable_name][index] = value(tree_node._num_iters_converged[variable_name][index]) + 1 |
---|
623 | else: |
---|
624 | tree_node._num_iters_converged[variable_name][index] = 1 |
---|
625 | tree_node._last_converged_val[variable_name][index] = node_min |
---|
626 | else: |
---|
627 | tree_node._num_iters_converged[variable_name][index] = 0 |
---|
628 | tree_node._last_converged_val[variable_name][index] = 0.5 |
---|
629 | |
---|
630 | #========================= |
---|
631 | def _continuous_convergence_tracking(self, ph, tree_node, variable_name, index, node_min, node_max): |
---|
632 | # keep track of cumulative iters of convergence to the same value within tolerance. |
---|
633 | if abs(node_max - node_min) <= ph._integer_tolerance: |
---|
634 | if abs(node_min - value(tree_node._last_converged_val[variable_name][index])) <= ph._integer_tolerance: |
---|
635 | tree_node._num_iters_converged[variable_name][index] = value(tree_node._num_iters_converged[variable_name][index]) + 1 |
---|
636 | else: |
---|
637 | tree_node._num_iters_converged[variable_name][index] = 1 |
---|
638 | tree_node._last_converged_val[variable_name][index] = node_min |
---|
639 | else: |
---|
640 | tree_node._num_iters_converged[variable_name][index] = 0 |
---|
641 | tree_node._last_converged_val[variable_name][index] = 0.2342343243223423 # TBD - avoid the magic constant! |
---|
642 | |
---|
643 | #========================= |
---|
644 | def _w_hash_acct(self, ph, tree_node, variable_name, index): |
---|
645 | # do the w hash accounting work |
---|
646 | # we hash on the variable ph weights, and not the values; the latter may not shift for some time, while the former should. |
---|
647 | self.W_hash_rand_val = self.W_hash_seed |
---|
648 | for scenario in tree_node._scenarios: |
---|
649 | instance = ph._instances[scenario._name] |
---|
650 | weight_parameter_name = "PHWEIGHT_"+variable_name |
---|
651 | if index is None: |
---|
652 | tree_node._w_hash[variable_name][ph._current_iteration] += value(getattr(instance,weight_parameter_name)[index]) * self.W_hash_rand_val |
---|
653 | else: |
---|
654 | tree_node._w_hash[variable_name][index,ph._current_iteration] += value(getattr(instance,weight_parameter_name)[index]) * self.W_hash_rand_val |
---|
655 | self.W_hash_rand_val = (self.W_hash_b + self.W_hash_a * self.W_hash_rand_val) % self.W_hash_c |
---|
656 | |
---|
657 | #========================= |
---|
658 | def dump_w_hash(self, ph, tree_node, stage): |
---|
659 | # debug code |
---|
660 | print "Stage=",stage._name," tree node=",tree_node._name |
---|
661 | print "PH Iteration Variable PH Weight Hash Value" |
---|
662 | for (variable, index_template, variable_indices) in stage._variables: |
---|
663 | |
---|
664 | variable_name = variable.name |
---|
665 | variable_type = variable.domain |
---|
666 | |
---|
667 | # TBD - should we cycle-detect on continuous vars? |
---|
668 | if isinstance(variable_type, IntegerSet) or isinstance(variable_type, BooleanSet): |
---|
669 | for index in variable_index: |
---|
670 | if index is None: |
---|
671 | print "%4d %50ls %20.5f" % (ph._current_iteration, tree_node._w_hash[variable_name][ph._current_iteration], value(tree_node._w_hash[variable_name][ph._current_iteration])) |
---|
672 | else: |
---|
673 | print "%4d %50ls %20.5f" % (ph._current_iteration, tree_node._w_hash[variable_name][index,ph._current_iteration], value(tree_node._w_hash[variable_name][index,ph._current_iteration])) |
---|
674 | |
---|
675 | #========================= |
---|
676 | def hash_hit_len(self, ph, tree_node, variable_name, index, report_possible_cycles): |
---|
677 | # return cycles back to closest hash hit for hashval or 0 if no hash hit |
---|
678 | |
---|
679 | # if the values are converged, then don't report a cycle - often, the weights at convergence are 0s, and even |
---|
680 | # if they aren't, they won't move if the values are uniform. |
---|
681 | if (value(tree_node._num_iters_converged[variable_name][index]) == 0) and (value(tree_node._fixed_var_flag[variable_name][index]) is False): |
---|
682 | current_hash_value = None |
---|
683 | if index is None: |
---|
684 | current_hash_value = value(tree_node._w_hash[variable_name][ph._current_iteration]) |
---|
685 | else: |
---|
686 | current_hash_value = value(tree_node._w_hash[variable_name][index,ph._current_iteration]) |
---|
687 | # scan starting from the farthest point back in history to the closest - this is required to |
---|
688 | # identify the longest possible cycles, which is what we want. |
---|
689 | for i in range(max(ph._current_iteration - self.W_hash_history_len - 1, 1), ph._current_iteration - 1, 1): |
---|
690 | this_hash_value = None |
---|
691 | if index is None: |
---|
692 | this_hash_value = value(tree_node._w_hash[variable_name][i]) |
---|
693 | else: |
---|
694 | this_hash_value = value(tree_node._w_hash[variable_name][index,i]) |
---|
695 | if abs(this_hash_value - current_hash_value) <= ph._integer_tolerance: |
---|
696 | if report_possible_cycles is True: |
---|
697 | if index is None: |
---|
698 | print "Possible cycle detected via PH weight hashing - variable="+variable_name+", node="+tree_node._name |
---|
699 | else: |
---|
700 | print "Possible cycle detected via PH weight hashing - variable="+variable_name+indexToString(index)+" node="+ tree_node._name |
---|
701 | msg = "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) |
---|
702 | return ph._current_iteration - i, msg |
---|
703 | return 0, "" |
---|
704 | |
---|
705 | #========================= |
---|
706 | def _fix_var(self, ph, tree_node, variable, index, fix_value): |
---|
707 | # fix the variable, account for it and maybe output some trace information |
---|
708 | # note: whether you fix at current values or not can severly impact feasibility later |
---|
709 | # in the game. my original thought was below - but that didn't work out. if you |
---|
710 | # make integers, well, integers, all appears to be well. |
---|
711 | # IMPT: we are leaving the values for individual variables alone, at potentially |
---|
712 | # smallish and heterogeneous values. if we fix/round, we run the risk of |
---|
713 | # infeasibilities due to numerical issues. the computed value below is |
---|
714 | # strictly for output purposes. dlw note: as of aug 1 '09, |
---|
715 | # node_min and node_max should be |
---|
716 | # int if they should be (so to speak) |
---|
717 | |
---|
718 | fixing_reported = False # to track whether you have already output the fix message for one scenario. |
---|
719 | |
---|
720 | for scenario in tree_node._scenarios: |
---|
721 | |
---|
722 | instance = ph._instances[scenario._name] |
---|
723 | |
---|
724 | getattr(instance,variable.name)[index].fixed = True |
---|
725 | getattr(instance,variable.name)[index].value = fix_value |
---|
726 | tree_node._fixed_var_flag[variable.name][index] = True |
---|
727 | |
---|
728 | variable_type = variable.domain |
---|
729 | |
---|
730 | if fixing_reported is False: |
---|
731 | # pretty-print the index, string the trailing spaces from the strings. |
---|
732 | if index is None: |
---|
733 | print "Fixing variable="+variable.name+" at tree node="+tree_node._name+" to value="+str(fix_value)+"; converged for "+str(value(tree_node._num_iters_converged[variable.name][index]))+" iterations" |
---|
734 | else: |
---|
735 | print "Fixing variable="+variable.name+indexToString(index)+" at tree node="+tree_node._name+" to value="+str(fix_value)+"; converged for "+str(value(tree_node._num_iters_converged[variable.name][index]))+" iterations" |
---|
736 | fixing_reported = True |
---|
737 | if isinstance(variable_type, IntegerSet) or isinstance(variable_type, BooleanSet): |
---|
738 | self.cumulative_discrete_fixed_count = self.cumulative_discrete_fixed_count + 1 |
---|
739 | else: |
---|
740 | self.cumulative_continuous_fixed_count = self.cumulative_continuous_fixed_count + 1 |
---|
741 | |
---|
742 | #========================= |
---|
743 | # the last 3 input arguments are the number of iterations the variable is required to |
---|
744 | # be at the respective bound (or lack thereof) before fixing can be initiated. |
---|
745 | def _should_fix_discrete_due_to_conv(self, tree_node, variable, index, lb_iters, ub_iters, nb_iters): |
---|
746 | # return True if this should be fixed due to convergence |
---|
747 | variable_name = variable.name |
---|
748 | |
---|
749 | # 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! |
---|
750 | # dlw reply: i meant it to mean "without regard to bound" so i have updated the document |
---|
751 | if nb_iters > 0 and value(tree_node._num_iters_converged[variable_name][index]) >= nb_iters: |
---|
752 | return True |
---|
753 | else: |
---|
754 | # there is a possibility that the variable doesn't have a bound specified, in which |
---|
755 | # case we should obviously ignore the corresponding lb_iters/ub_iters/nb_iters - which |
---|
756 | # should be none as well! |
---|
757 | lb = None |
---|
758 | ub = None |
---|
759 | if variable[index].lb is not None: |
---|
760 | lb = variable[index].lb() |
---|
761 | if variable[index].ub is not None: |
---|
762 | ub = variable[index].ub() |
---|
763 | conval = value(tree_node._last_converged_val[variable_name][index]) |
---|
764 | # note: if they are converged node_max == node_min |
---|
765 | if (lb is not None) and (lb_iters > 0) and (value(tree_node._num_iters_converged[variable_name][index]) >= lb_iters) and (conval == lb): |
---|
766 | return True |
---|
767 | elif (ub is not None) and (ub_iters > 0) and (value(tree_node._num_iters_converged[variable_name][index]) >= ub_iters) and (conval == ub): |
---|
768 | return True |
---|
769 | # if we are still here, nothing triggered fixing |
---|
770 | return False |
---|
771 | |
---|
772 | #========================= |
---|
773 | def _should_fix_continuous_due_to_conv(self, tree_node, variable, index): |
---|
774 | |
---|
775 | if self.fix_continuous_variables is True: |
---|
776 | if self.FixWhenItersConvergedContinuous > 0 and value(tree_node._num_iters_converged[variable.name][index]) >= self.FixWhenItersConvergedContinuous: |
---|
777 | return True |
---|
778 | |
---|
779 | # if we are still here, nothing triggered fixing |
---|
780 | return False |
---|
781 | |
---|
782 | #========================= |
---|
783 | def _slam(self, ph, tree_node, variable, index): |
---|
784 | # this function returns a boolean indicating if it slammed |
---|
785 | # TBD in the distant future: also: slam it to somewhere it sort of wants to go |
---|
786 | # e.g., the anywhere case could be to the mode |
---|
787 | # or if more than one dest is True, pick the one closest to the average |
---|
788 | # as of sept 09, it is written with the implicit assumption that only one |
---|
789 | # destination is True or that if not, then min/max trumps lb/ub and anywhere trumps all |
---|
790 | |
---|
791 | fix_value = False # assume the worst |
---|
792 | variable_type = variable.domain |
---|
793 | variable_name = variable.name |
---|
794 | if isinstance(variable_type, IntegerSet) or isinstance(variable_type, BooleanSet): |
---|
795 | node_min = self.Int_If_Close_Enough(ph, value(tree_node._minimums[variable_name][index])) |
---|
796 | node_max = self.Int_If_Close_Enough(ph, value(tree_node._maximums[variable_name][index])) |
---|
797 | anywhere = round(value(tree_node._averages[variable.name][index])) |
---|
798 | else: |
---|
799 | node_min = value(tree_node._minimums[variable_name][index]) |
---|
800 | node_max = value(tree_node._maximums[variable_name][index]) |
---|
801 | anywhere = value(tree_node._averages[variable.name][index]) |
---|
802 | |
---|
803 | slam_basis_string = "" |
---|
804 | if self.CanSlamToLB is True: |
---|
805 | fix_value = variable[index].lb() |
---|
806 | slam_basis_string = "lower bound" |
---|
807 | if self.CanSlamToMin is True: |
---|
808 | fix_value = node_min |
---|
809 | slam_basis_string = "node minimum" |
---|
810 | if self.CanSlamToUB is True: |
---|
811 | fix_value = variable[index].ub() |
---|
812 | slam_basis_string = "upper bound" |
---|
813 | if self.CanSlamToMax is True: |
---|
814 | fix_value = node_max |
---|
815 | slam_basis_string = "node maximum" |
---|
816 | if self.CanSlamToAnywhere is True: |
---|
817 | fix_value = anywhere |
---|
818 | slam_basis_string = "node average (anywhere)" |
---|
819 | if fix_value is False: |
---|
820 | print "Warning: Not allowed to slam variable="+variable.name+str(index)+" at tree node="+tree_node._name |
---|
821 | return False |
---|
822 | else: |
---|
823 | if index is None: |
---|
824 | print "Slamming variable="+variable.name+" at tree node="+tree_node._name+" to value="+str(fix_value)+"; value="+slam_basis_string |
---|
825 | else: |
---|
826 | print "Slamming variable="+variable.name+indexToString(index)+" at tree node="+tree_node._name+" to value="+str(fix_value)+"; value="+slam_basis_string |
---|
827 | self._fix_var(ph, tree_node, variable, index, fix_value) |
---|
828 | return True |
---|
829 | |
---|
830 | #========================= |
---|
831 | def _pick_one_and_slam_it(self, ph): |
---|
832 | |
---|
833 | reference_instance = ph._model_instance |
---|
834 | |
---|
835 | for vl in self.slam_list: |
---|
836 | variable_string = str(vl) |
---|
837 | full_index = None # JPW is not entirely sure of python scoping rules, so I'm declaring this outside of the if-then block. |
---|
838 | variable_name = None |
---|
839 | if isVariableNameIndexed(variable_string) is True: |
---|
840 | pieces = variable_string.split('[') |
---|
841 | variable_name = string.strip(pieces[0]) |
---|
842 | full_index = pieces[1].rstrip(']') |
---|
843 | # the full_index is a string - tuplize it! |
---|
844 | full_index = tupleizeIndexString(full_index) |
---|
845 | else: |
---|
846 | variable_name = variable_string |
---|
847 | full_index = None |
---|
848 | |
---|
849 | # verify that the root variable exists and grab it. |
---|
850 | if variable_name not in reference_instance.active_components(Var).keys(): |
---|
851 | raise RuntimeError, "Unknown variable="+variable_name+" referenced while slamming. " |
---|
852 | variable = reference_instance.active_components(Var)[variable_name] |
---|
853 | |
---|
854 | didone = False; # did we find at least one node to slam in? |
---|
855 | # it is possible (even likely) that the slam list contains variable values that |
---|
856 | # reside in the final stage - which, because of the initialization loops in |
---|
857 | # the post_ph_initialization() method, will not have a _stage attribute defined. |
---|
858 | # check for the presence of this attribute and skip if not present, as it |
---|
859 | # doesn't make sense to slam variable values in the final stage anyway. |
---|
860 | if hasattr(variable[full_index],'_stage') is True: |
---|
861 | for tree_node in variable[full_index]._stage._tree_nodes: |
---|
862 | # determine if the variable is already fixed (the trusting version...). |
---|
863 | if value(tree_node._fixed_var_flag[variable_name][full_index]) is False: |
---|
864 | didone = self._slam(ph, tree_node, variable, full_index) |
---|
865 | if didone: |
---|
866 | self._last_slam_iter = ph._current_iteration |
---|
867 | return |
---|
868 | |
---|
869 | print "Warning: Nothing free with a non-zero slam priority - no variable will be slammed" |
---|
870 | |
---|
871 | #========================== |
---|
872 | # a simple utility to fix any discrete variables to their common value, assuming they |
---|
873 | # are at a common value |
---|
874 | #========================== |
---|
875 | |
---|
876 | def _fix_all_converged_discrete_variables(self, ph): |
---|
877 | |
---|
878 | num_variables_fixed = 0 |
---|
879 | |
---|
880 | for stage in ph._scenario_tree._stages[:-1]: # no blending over the final stage |
---|
881 | |
---|
882 | for tree_node in stage._tree_nodes: |
---|
883 | |
---|
884 | for (variable, index_template, variable_indices) in stage._variables: |
---|
885 | |
---|
886 | variable_name = variable.name |
---|
887 | variable_type = variable.domain |
---|
888 | |
---|
889 | for index in variable_indices: |
---|
890 | |
---|
891 | # determine if this index is used - otherwise, don't waste time |
---|
892 | # fixing and cycle checking. for one, the code will crash :-) with |
---|
893 | # None values during the cycle checking computation! |
---|
894 | |
---|
895 | is_used = True # until proven otherwise |
---|
896 | for scenario in tree_node._scenarios: |
---|
897 | instance = ph._instances[scenario._name] |
---|
898 | if getattr(instance,variable_name)[index].status == VarStatus.unused: |
---|
899 | is_used = False |
---|
900 | |
---|
901 | if is_used is True: |
---|
902 | |
---|
903 | # determine if the variable is already fixed. |
---|
904 | instance_fixed_count = 0 |
---|
905 | for scenario in tree_node._scenarios: |
---|
906 | instance = ph._instances[scenario._name] |
---|
907 | if getattr(instance,variable_name)[index].fixed is True: |
---|
908 | instance_fixed_count += 1 |
---|
909 | if ((instance_fixed_count > 0) and (instance_fixed_count < len(tree_node._scenarios))): |
---|
910 | 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 |
---|
911 | |
---|
912 | if instance_fixed_count == 0: |
---|
913 | |
---|
914 | if isinstance(variable_type, IntegerSet) or isinstance(variable_type, BooleanSet): |
---|
915 | node_min = self.Int_If_Close_Enough(ph, value(tree_node._minimums[variable_name][index])) |
---|
916 | node_max = self.Int_If_Close_Enough(ph, value(tree_node._maximums[variable_name][index])) |
---|
917 | |
---|
918 | if node_min == node_max: |
---|
919 | self._fix_var(ph, tree_node, variable, index, node_min) |
---|
920 | num_variables_fixed = num_variables_fixed + 1 |
---|
921 | |
---|
922 | print "Total number of variables fixed at PH termination due to convergence="+str(num_variables_fixed) |
---|