Changeset 2413
- Timestamp:
- Mar 8, 2010 12:20:36 PM (11 years ago)
- Location:
- coopr.pysp/trunk/coopr/pysp
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
coopr.pysp/trunk/coopr/pysp/ef.py
r2412 r2413 15 15 from coopr.pyomo.base.var import _VarValue, _VarBase 16 16 17 # 17 18 # brain-dead utility for determing if there is a binary to write in the 18 19 # composite model - need to know this, because CPLEX doesn't like empty 19 20 # binary blocks in the LP file. 21 # 22 20 23 def binaries_present(master_model, scenario_instances): 21 24 … … 34 37 return False 35 38 39 # 36 40 # brain-dead utility for determing if there is a binary to write in the 37 41 # composite model - need to know this, because CPLEX doesn't like empty 38 42 # integer blocks in the LP file. 43 # 44 39 45 def integers_present(master_model, scenario_instances): 40 46 … … 53 59 return False 54 60 61 # 62 # a routine to create the extensive form, given an input scenario tree and instances. 63 # IMPT: unlike scenario instances, the extensive form instance is *not* self-contained. 64 # in particular, it has binding constraints that cross the binding instance and 65 # the scenario instances. it is up to the caller to keep track of which scenario 66 # instances are associated with the extensive form. this might be something we 67 # encapsulate at some later time. 68 # 69 70 def create_ef_instance(scenario_tree, scenario_instances, generate_weighted_cvar, verbose_output): 71 72 binding_instance = Model() 73 binding_instance.name = "MASTER" 74 75 # walk the scenario tree - create variables representing the common values for all scenarios 76 # associated with that node. the constraints will be created later. also create expected-cost 77 # variables for each node, to be computed via constraints/objectives defined in a subsequent pass. 78 # master variables are created for all nodes but those in the last stage. expected cost variables 79 # are, for no particularly good reason other than easy coding, created for nodes in all stages. 80 if verbose_output is True: 81 print "Creating variables for master binding instance" 82 83 for stage in scenario_tree._stages: 84 85 for (stage_variable, index_template, stage_variable_indices) in stage._variables: 86 87 if verbose_output is True: 88 print "Creating master variable and blending constraints for decision variable=", stage_variable, ", indices=", index_template 89 90 for tree_node in stage._tree_nodes: 91 92 if stage != scenario_tree._stages[-1]: 93 94 master_variable_name = tree_node._name + "_" + stage_variable.name 95 96 # because there may be a single stage variable and multiple indices, check 97 # for the existence of the variable at this node - if you don't, you'll 98 # inadvertently over-write what was there previously! 99 master_variable = None 100 try: 101 master_variable = getattr(binding_instance, master_variable_name) 102 except: 103 new_master_variable_index = stage_variable._index 104 new_master_variable = None 105 if (len(new_master_variable_index) is 1) and (None in new_master_variable_index): 106 new_master_variable = Var(name=stage_variable.name) 107 else: 108 new_master_variable = Var(new_master_variable_index, name=stage_variable.name) 109 new_master_variable.construct() 110 new_master_variable._model = binding_instance 111 setattr(master_binding_instance, master_variable_name, new_master_variable) 112 113 master_variable = new_master_variable 114 115 for index in stage_variable_indices: 116 117 is_used = True # until proven otherwise 118 for scenario in tree_node._scenarios: 119 instance = scenario_instances[scenario._name] 120 if getattr(instance,stage_variable.name)[index].status == VarStatus.unused: 121 is_used = False 122 123 is_fixed = False # until proven otherwise 124 for scenario in tree_node._scenarios: 125 instance = scenario_instances[scenario._name] 126 if getattr(instance,stage_variable.name)[index].fixed is True: 127 is_fixed = True 128 129 if (is_used is True) and (is_fixed is False): 130 131 # the following is necessary, specifically to get the name - deepcopy won't reset these attributes. 132 # and because presolve/simplification is name-based, the names *have* to be different. 133 master_variable[index].var = master_variable 134 master_variable[index].name = tree_node._name + "_" + master_variable[index].name 135 136 for scenario in tree_node._scenarios: 137 138 scenario_instance = scenario_instances[scenario._name] 139 scenario_variable = getattr(scenario_instance, stage_variable.name) 140 new_constraint_name = scenario._name + "_" + master_variable_name + "_" + str(index) 141 new_constraint = Constraint(name=new_constraint_name) 142 new_expr = master_variable[index] - scenario_variable[index] 143 new_constraint.add(None, (0.0, new_expr, 0.0)) 144 new_constraint._model = master_binding_instance 145 setattr(master_binding_instance, new_constraint_name, new_constraint) 146 147 # create a variable to represent the expected cost at this node - 148 # the constraint to compute this comes later. 149 expected_cost_variable_name = "EXPECTED_COST_" + tree_node._name 150 expected_cost_variable = Var(name=expected_cost_variable_name) 151 expected_cost_variable._model = master_binding_instance 152 setattr(master_binding_instance, expected_cost_variable_name, expected_cost_variable) 153 154 master_binding_instance.preprocess() 155 156 # ditto above for the (non-expected) cost variable. 157 for stage in scenario_tree._stages: 158 159 (cost_variable,cost_variable_index) = stage._cost_variable 160 161 if verbose_output: 162 print "Creating master variable and blending constraints for cost variable=", cost_variable, ", index=", cost_variable_index 163 164 for tree_node in stage._tree_nodes: 165 166 new_cost_variable_name = tree_node._name + "_" + cost_variable.name 167 168 # TBD - the following is bad - check to see if it's already there (I suspect some of them are!!!) 169 170 # this is undoubtedly wasteful, in that a cost variable 171 # for each tree node is created with *all* indices. 172 new_cost_variable_name = tree_node._name + "_" + cost_variable.name 173 new_cost_variable_index = cost_variable._index 174 new_cost_variable = None 175 if (len(new_cost_variable_index) is 1) and (None in new_cost_variable_index): 176 new_cost_variable = Var(name=new_cost_variable_name) 177 else: 178 new_cost_variable = Var(new_cost_variable_index, new_cost_variable_name) 179 new_cost_variable.construct() 180 new_cost_variable._model = master_binding_instance 181 setattr(master_binding_instance, new_cost_variable_name, new_cost_variable) 182 183 # the following is necessary, specifically to get the name - deepcopy won't reset these attributes. 184 new_cost_variable[cost_variable_index].var = new_cost_variable 185 if cost_variable_index is not None: 186 # if the variable index is None, the variable is derived from a VarValue, so the 187 # name gets updated automagically. 188 new_cost_variable[cost_variable_index].name = tree_node._name + "_" + new_cost_variable[cost_variable_index].name 189 190 for scenario in tree_node._scenarios: 191 192 scenario_instance = scenario_instances[scenario._name] 193 scenario_cost_variable = getattr(scenario_instance, cost_variable.name) 194 new_constraint_name = scenario._name + "_" + new_cost_variable_name + "_" + str(cost_variable_index) 195 new_constraint = Constraint(name=new_constraint_name) 196 new_expr = new_cost_variable[cost_variable_index] - scenario_cost_variable[cost_variable_index] 197 new_constraint.add(None, (0.0, new_expr, 0.0)) 198 new_constraint._model = master_binding_instance 199 setattr(master_binding_instance, new_constraint_name, new_constraint) 200 201 # create the constraints for computing the master per-node cost variables, 202 # i.e., the current node cost and the expected cost of the child nodes. 203 # if the root, then the constraint is just the objective. 204 205 for stage in scenario_tree._stages: 206 207 (stage_cost_variable,stage_cost_variable_index) = stage._cost_variable 208 209 for tree_node in stage._tree_nodes: 210 211 node_expected_cost_variable_name = "EXPECTED_COST_" + tree_node._name 212 node_expected_cost_variable = getattr(master_binding_instance, node_expected_cost_variable_name) 213 214 node_cost_variable_name = tree_node._name + "_" + stage_cost_variable.name 215 node_cost_variable = getattr(master_binding_instance, node_cost_variable_name) 216 217 constraint_expr = node_expected_cost_variable - node_cost_variable[stage_cost_variable_index] 218 219 for child_node in tree_node._children: 220 221 child_node_expected_cost_variable_name = "EXPECTED_COST_" + child_node._name 222 child_node_expected_cost_variable = getattr(master_binding_instance, child_node_expected_cost_variable_name) 223 constraint_expr = constraint_expr - (child_node._conditional_probability * child_node_expected_cost_variable) 224 225 new_constraint_name = "COST" + "_" + node_cost_variable_name + "_" + str(cost_variable_index) 226 new_constraint = Constraint(name=new_constraint_name) 227 new_constraint.add(None, (0.0, constraint_expr, 0.0)) 228 new_constraint._model = master_binding_instance 229 setattr(master_binding_instance, new_constraint_name, new_constraint) 230 231 if tree_node._parent is None: 232 233 an_instance = scenario_instances[scenario_instances.keys()[0]] 234 an_objective = an_instance.active_components(Objective) 235 opt_sense = an_objective[an_objective.keys()[0]].sense 236 237 new_objective = Objective(name="MASTER", sense=opt_sense) 238 new_objective._data[None].expr = node_expected_cost_variable 239 setattr(master_binding_instance, "MASTER", new_objective) 240 241 master_binding_instance.preprocess() 242 243 return master_binding_instance 244 245 # 55 246 # the main extensive-form writer routine - including read of scenarios/etc. 247 # 248 56 249 def write_ef_from_scratch(model_directory, instance_directory, output_filename, \ 57 250 generate_weighted_cvar, cvar_weight, risk_alpha): … … 605 798 print "" 606 799 607 def write_ef(scenario_tree,instances, output_filename):800 def create_and_write_ef(scenario_tree, scenario_instances, output_filename): 608 801 609 802 start_time = time.time() 610 803 611 ################################################################################################ 612 #### CREATE THE MASTER / BINDING INSTANCE ###################################################### 613 ################################################################################################ 614 615 master_binding_instance = Model() 616 master_binding_instance.name = "MASTER" 617 618 # walk the scenario tree - create variables representing the common values for all scenarios 619 # associated with that node. the constraints will be created later. also create expected-cost 620 # variables for each node, to be computed via constraints/objectives defined in a subsequent pass. 621 # master variables are created for all nodes but those in the last stage. expected cost variables 622 # are, for no particularly good reason other than easy coding, created for nodes in all stages. 623 print "Creating variables for master binding instance" 624 625 for stage in scenario_tree._stages: 626 627 for (stage_variable, index_template, stage_variable_indices) in stage._variables: 628 629 print "Creating master variable and blending constraints for decision variable=", stage_variable, ", indices=", index_template 630 631 for tree_node in stage._tree_nodes: 632 633 if stage != scenario_tree._stages[-1]: 634 635 master_variable_name = tree_node._name + "_" + stage_variable.name 636 637 # because there may be a single stage variable and multiple indices, check 638 # for the existence of the variable at this node - if you don't, you'll 639 # inadvertently over-write what was there previously! 640 master_variable = None 641 try: 642 master_variable = getattr(master_binding_instance, master_variable_name) 643 except: 644 new_master_variable_index = stage_variable._index 645 new_master_variable = None 646 if (len(new_master_variable_index) is 1) and (None in new_master_variable_index): 647 new_master_variable = Var(name=stage_variable.name) 648 else: 649 new_master_variable = Var(new_master_variable_index, name=stage_variable.name) 650 new_master_variable.construct() 651 new_master_variable._model = master_binding_instance 652 setattr(master_binding_instance, master_variable_name, new_master_variable) 653 654 # TBD - TECHNICALLY, WE NEED TO COPY BOUNDS - BUT WE REALLY DON'T, AS THEY ARE ON THE PER-INSTNACE VARS! 655 656 master_variable = new_master_variable 657 658 for index in stage_variable_indices: 659 660 is_used = True # until proven otherwise 661 for scenario in tree_node._scenarios: 662 instance = instances[scenario._name] 663 if getattr(instance,stage_variable.name)[index].status == VarStatus.unused: 664 is_used = False 665 666 is_fixed = False # until proven otherwise 667 for scenario in tree_node._scenarios: 668 instance = instances[scenario._name] 669 if getattr(instance,stage_variable.name)[index].fixed is True: 670 is_fixed = True 671 672 if (is_used is True) and (is_fixed is False): 673 674 # the following is necessary, specifically to get the name - deepcopy won't reset these attributes. 675 # and because presolve/simplification is name-based, the names *have* to be different. 676 master_variable[index].var = master_variable 677 master_variable[index].name = tree_node._name + "_" + master_variable[index].name 678 679 for scenario in tree_node._scenarios: 680 681 scenario_instance = instances[scenario._name] 682 scenario_variable = getattr(scenario_instance, stage_variable.name) 683 new_constraint_name = scenario._name + "_" + master_variable_name + "_" + str(index) 684 new_constraint = Constraint(name=new_constraint_name) 685 new_expr = master_variable[index] - scenario_variable[index] 686 new_constraint.add(None, (0.0, new_expr, 0.0)) 687 new_constraint._model = master_binding_instance 688 setattr(master_binding_instance, new_constraint_name, new_constraint) 689 690 # create a variable to represent the expected cost at this node - 691 # the constraint to compute this comes later. 692 expected_cost_variable_name = "EXPECTED_COST_" + tree_node._name 693 expected_cost_variable = Var(name=expected_cost_variable_name) 694 expected_cost_variable._model = master_binding_instance 695 setattr(master_binding_instance, expected_cost_variable_name, expected_cost_variable) 696 697 master_binding_instance.preprocess() 698 699 # ditto above for the (non-expected) cost variable. 700 for stage in scenario_tree._stages: 701 702 (cost_variable,cost_variable_index) = stage._cost_variable 703 704 print "Creating master variable and blending constraints for cost variable=", cost_variable, ", index=", cost_variable_index 705 706 for tree_node in stage._tree_nodes: 707 708 new_cost_variable_name = tree_node._name + "_" + cost_variable.name 709 710 # TBD - the following is bad - check to see if it's already there (I suspect some of them are!!!) 711 712 # this is undoubtedly wasteful, in that a cost variable 713 # for each tree node is created with *all* indices. 714 new_cost_variable_name = tree_node._name + "_" + cost_variable.name 715 new_cost_variable_index = cost_variable._index 716 new_cost_variable = None 717 if (len(new_cost_variable_index) is 1) and (None in new_cost_variable_index): 718 new_cost_variable = Var(name=new_cost_variable_name) 719 else: 720 new_cost_variable = Var(new_cost_variable_index, new_cost_variable_name) 721 new_cost_variable.construct() 722 new_cost_variable._model = master_binding_instance 723 setattr(master_binding_instance, new_cost_variable_name, new_cost_variable) 724 725 # the following is necessary, specifically to get the name - deepcopy won't reset these attributes. 726 new_cost_variable[cost_variable_index].var = new_cost_variable 727 if cost_variable_index is not None: 728 # if the variable index is None, the variable is derived from a VarValue, so the 729 # name gets updated automagically. 730 new_cost_variable[cost_variable_index].name = tree_node._name + "_" + new_cost_variable[cost_variable_index].name 731 732 for scenario in tree_node._scenarios: 733 734 scenario_instance = instances[scenario._name] 735 scenario_cost_variable = getattr(scenario_instance, cost_variable.name) 736 new_constraint_name = scenario._name + "_" + new_cost_variable_name + "_" + str(cost_variable_index) 737 new_constraint = Constraint(name=new_constraint_name) 738 new_expr = new_cost_variable[cost_variable_index] - scenario_cost_variable[cost_variable_index] 739 new_constraint.add(None, (0.0, new_expr, 0.0)) 740 new_constraint._model = master_binding_instance 741 setattr(master_binding_instance, new_constraint_name, new_constraint) 742 743 # create the constraints for computing the master per-node cost variables, 744 # i.e., the current node cost and the expected cost of the child nodes. 745 # if the root, then the constraint is just the objective. 746 747 for stage in scenario_tree._stages: 748 749 (stage_cost_variable,stage_cost_variable_index) = stage._cost_variable 750 751 for tree_node in stage._tree_nodes: 752 753 node_expected_cost_variable_name = "EXPECTED_COST_" + tree_node._name 754 node_expected_cost_variable = getattr(master_binding_instance, node_expected_cost_variable_name) 755 756 node_cost_variable_name = tree_node._name + "_" + stage_cost_variable.name 757 node_cost_variable = getattr(master_binding_instance, node_cost_variable_name) 758 759 constraint_expr = node_expected_cost_variable - node_cost_variable[stage_cost_variable_index] 760 761 for child_node in tree_node._children: 762 763 child_node_expected_cost_variable_name = "EXPECTED_COST_" + child_node._name 764 child_node_expected_cost_variable = getattr(master_binding_instance, child_node_expected_cost_variable_name) 765 constraint_expr = constraint_expr - (child_node._conditional_probability * child_node_expected_cost_variable) 766 767 new_constraint_name = "COST" + "_" + node_cost_variable_name + "_" + str(cost_variable_index) 768 new_constraint = Constraint(name=new_constraint_name) 769 new_constraint.add(None, (0.0, constraint_expr, 0.0)) 770 new_constraint._model = master_binding_instance 771 setattr(master_binding_instance, new_constraint_name, new_constraint) 772 773 if tree_node._parent is None: 774 775 an_instance = instances[instances.keys()[0]] 776 an_objective = an_instance.active_components(Objective) 777 opt_sense = an_objective[an_objective.keys()[0]].sense 778 779 new_objective = Objective(name="MASTER", sense=opt_sense) 780 new_objective._data[None].expr = node_expected_cost_variable 781 setattr(master_binding_instance, "MASTER", new_objective) 782 783 master_binding_instance.preprocess() 804 binding_instance = create_ef_instance(scenario_tree, scenario_instances, False, True) 784 805 785 806 ################################################################################################ … … 806 827 807 828 print >>output_file, "\\ Begin objective block for master" 808 problem_writer._print_model_LP( master_binding_instance, output_file)829 problem_writer._print_model_LP(binding_instance, output_file) 809 830 print >>output_file, "\\ End objective block for master" 810 831 print >>output_file, "" … … 822 843 823 844 print >>output_file, "\\ Begin constraint block for master" 824 problem_writer._print_model_LP( master_binding_instance, output_file)845 problem_writer._print_model_LP(binding_instance, output_file) 825 846 print >>output_file, "\\ End constraint block for master", 826 847 print >>output_file, "" 827 848 828 for scenario_name in instances.keys():829 instance = instances[scenario_name]849 for scenario_name in scenario_instances.keys(): 850 instance = scenario_instances[scenario_name] 830 851 print >>output_file, "\\ Begin constraint block for scenario",scenario_name 831 852 problem_writer._print_model_LP(instance, output_file) … … 852 873 853 874 print >>output_file, "\\ Begin variable bounds block for master" 854 problem_writer._print_model_LP( master_binding_instance, output_file)875 problem_writer._print_model_LP(binding_instance, output_file) 855 876 print >>output_file, "\\ End variable bounds block for master" 856 877 print >>output_file, "" 857 878 858 for scenario_name in instances.keys():859 instance = instances[scenario_name]879 for scenario_name in scenario_instances.keys(): 880 instance = scenario_instances[scenario_name] 860 881 print >>output_file, "\\ Begin variable bounds block for scenario",scenario_name 861 882 problem_writer._print_model_LP(instance, output_file) … … 868 889 problem_writer._output_integer_variables = True 869 890 870 if integers_present( master_binding_instance,instances) is True:891 if integers_present(binding_instance, scenario_instances) is True: 871 892 872 893 print >>output_file, "integer" … … 874 895 875 896 print >>output_file, "\\ Begin integer variable block for master" 876 problem_writer._print_model_LP( master_binding_instance, output_file)897 problem_writer._print_model_LP(binding_instance, output_file) 877 898 print >>output_file, "\\ End integer variable block for master" 878 899 print >>output_file, "" 879 900 880 for scenario_name in instances.keys():881 instance = instances[scenario_name]901 for scenario_name in scenario_instances.keys(): 902 instance = scenario_instances[scenario_name] 882 903 print >>output_file, "\\ Begin integer variable block for scenario",scenario_name 883 904 problem_writer._print_model_LP(instance, output_file) … … 890 911 problem_writer._output_binary_variables = True 891 912 892 if binaries_present( master_binding_instance,instances) is True:913 if binaries_present(binding_instance, scenario_instances) is True: 893 914 894 915 print >>output_file, "binary" … … 896 917 897 918 print >>output_file, "\\ Begin binary variable block for master" 898 problem_writer._print_model_LP( master_binding_instance, output_file)919 problem_writer._print_model_LP(binding_instance, output_file) 899 920 print >>output_file, "\\ End binary variable block for master" 900 921 print >>output_file, "" 901 922 902 for scenario_name in instances.keys():903 instance = instances[scenario_name]923 for scenario_name in scenario_instances.keys(): 924 instance = scenario_instances[scenario_name] 904 925 print >>output_file, "\\ Begin binary variable block for scenario",scenario_name 905 926 problem_writer._print_model_LP(instance, output_file) -
coopr.pysp/trunk/coopr/pysp/scenariotree.py
r2405 r2413 11 11 import sys 12 12 import types 13 from coopr.pyomo import *14 13 import copy 15 14 import os.path 16 15 import traceback 17 16 17 from coopr.pyomo import * 18 18 from phutils import * 19 19 … … 23 23 24 24 """ 25 def __init__(self, *args, **kwds):26 27 self._name = ""28 self._stage = None25 def __init__(self, name, conditional_probability, stage, reference_instance): 26 27 self._name = name 28 self._stage = stage 29 29 self._parent = None 30 30 self._children = [] # a collection of ScenarioTreeNodes 31 self._conditional_probability = None# conditional on parent31 self._conditional_probability = conditional_probability # conditional on parent 32 32 self._scenarios = [] # a collection of all Scenarios passing through this node in the tree 33 33 … … 43 43 self._minimums = {} 44 44 self._maximums = {} 45 46 # solution (variable) values for this node. assumed to be distinct 47 # from self._averages, as the latter are not necessarily feasible. 48 # objects in the map are actual variables. 49 self._solution = {} 50 51 # for each variable referenced in the stage, clone the variable 52 # for purposes of storing solutions. we are being wasteful in 53 # terms copying indices that may not be referenced in the stage. 54 # this is something that we might revisit if space/performance 55 # is an issue (space is the most likely issue) 56 for variable, match_template, variable_indices in self._stage._variables: 57 58 # don't bother copying bounds for variables, as the values stored 59 # here are computed elsewhere - and that entity is responsible for 60 # ensuring feasibility. this also leaves room for specifying infeasible 61 # or partial solutions. 62 63 new_variable_index = variable._index 64 new_variable_name = variable._name 65 new_variable = None 66 if (len(new_variable_index) is 1) and (None in new_variable_index): 67 new_variable = Var(name=new_variable_name) 68 else: 69 new_variable = Var(new_variable_index, name=new_variable_name) 70 71 self._solution[new_variable_name] = new_variable 45 72 46 73 # … … 91 118 """ Constructor 92 119 Arguments: 93 scenarioinstance - the referencescenario instance.120 scenarioinstance - the reference (deterministic) scenario instance. 94 121 scenariotreeinstance - the pyomo model specifying all scenario tree (text) data. 95 122 scenariobundlelist - a list of scenario names to retain, i.e., cull the rest to create a reduced tree! … … 173 200 # can't do a single pass because the objects may not exist. 174 201 for tree_node_name in node_ids: 175 new_tree_node = ScenarioTreeNode() 176 new_tree_node._name = tree_node_name 202 203 if tree_node_name not in node_stage_ids: 204 raise ValueError, "No stage is assigned to tree node=" + tree_node._name 205 206 stage_name = node_stage_ids[tree_node_name].value 207 if stage_name not in self._stage_map.keys(): 208 raise ValueError, "Unknown stage=" + stage_name + " assigned to tree node=" + tree_node._name 209 210 new_tree_node = ScenarioTreeNode(tree_node_name, 211 node_probability_map[tree_node_name].value, 212 self._stage_map[stage_name], 213 self._reference_instance) 177 214 178 215 self._tree_nodes.append(new_tree_node) 179 216 self._tree_node_map[tree_node_name] = new_tree_node 180 181 new_tree_node._conditional_probability = node_probability_map[tree_node_name].value 182 183 if tree_node_name not in node_stage_ids: 184 raise ValueError, "No stage is assigned to tree node=" + tree_node._name 185 else: 186 stage_name = node_stage_ids[new_tree_node._name].value 187 if stage_name not in self._stage_map.keys(): 188 raise ValueError, "Unknown stage=" + stage_name + " assigned to tree node=" + tree_node._name 189 else: 190 new_tree_node._stage = self._stage_map[stage_name] 191 self._stage_map[stage_name]._tree_nodes.append(new_tree_node) 217 self._stage_map[stage_name]._tree_nodes.append(new_tree_node) 192 218 193 219 # link up the tree nodes objects based on the child id sets.
Note: See TracChangeset
for help on using the changeset viewer.