source: trunk/coopr/opt/colin/problem.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.

File size: 8.5 KB
Line 
1#  _________________________________________________________________________
2#
3#  Coopr: A COmmon Optimization Python Repository
4#  Copyright (c) 2008 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"""
12Utilities to support the definition of optimization applications that
13can be optimized with the Acro COLIN optimizers.
14"""
15
16import re
17import xml.dom.minidom
18import sys
19from pyutilib.enum import Enum
20from pyutilib.misc import tostr, get_xml_text
21
22
23response_enum = Enum("FunctionValue", "FunctionValues", "Gradient", "NonlinearConstraintValues", "Jacobian")
24
25class MixedIntVars(object):
26  """
27  A class that defines a commonly used domain type.
28  """
29
30  def __init__(self, node=None):
31      self.reals = []
32      self.ints = []
33      self.bits = []
34      if node is not None:
35         self.process(node)
36
37  def display(self):
38      print "Reals",
39      for val in self.reals:
40        print val,
41      print ""
42      print "Integers",
43      for val in self.ints:
44        print val,
45      print ""
46      print "Binary",
47      for val in self.bits:
48        print val,
49      print ""
50
51  def process(self,node):
52      for child in node.childNodes:
53        if child.nodeType == node.ELEMENT_NODE:
54            child_text = get_xml_text(child)
55            if child_text == "":
56              continue
57            if child.nodeName == "Real":
58              for val in re.split('[\t ]+',child_text):
59                self.reals.append(1.0*eval(val))
60            elif child.nodeName == "Integer":
61              for val in re.split('[\t ]+',child_text):
62                self.ints.append(eval(val))
63            elif child.nodeName == "Binary":
64              for val in child_text:
65                if val == '1':
66                   self.bits.append(1)
67                elif val == '0':
68                   self.bits.append(0)
69
70
71
72class OptProblem(object):
73    """
74    A class that defines an application that can be optimized
75    by a COLIN optimizer via system calls.
76    """
77
78    def __init__(self):
79        """
80        The constructor.  Derived classes should define the response types.
81
82        By default, only function evaluations are supported in an OptProblem
83        instance.
84        """
85        self.response_types = [response_enum.FunctionValue]
86
87
88    def main(self, argv):
89        """
90        The main routine for parsing the command-line and executing
91        the evaluation.
92        """
93        if len(argv) < 3:
94            print argv[0 ]+ " <input> <output> <log>"
95            sys.exit(1)
96        #
97        # Get enum strings
98        #
99        self.response_str = map(str,self.response_types)
100        #
101        # Parse XML input file
102        #
103        input_doc = xml.dom.minidom.parse(argv[1])
104        point = self.create_point(input_doc.getElementsByTagName("Domain")[0])
105        self.validate(point)
106        requests = self._handleRequests(input_doc.getElementsByTagName("Requests")[0])
107        #
108        # Create output XML object
109        #
110        output_doc = self._process(point,requests)
111        OUTPUT = open(sys.argv[2],"w")
112        output_doc.writexml(OUTPUT," "," ","\n","UTF-8")
113        OUTPUT.close()
114
115
116    def create_point(self, node):
117        """
118        Create a point given an XML node with the domain info.
119
120        By default, this generates a MixedIntVars class object, but
121        this method could be over-written to customized an OptProblem
122        for other search domains.
123        """
124        return MixedIntVars(node)
125
126
127    def function_value(self, point):
128        """
129        Compute a function value.
130        """
131        return None
132
133    def function_values(self, point):
134        """
135        Compute a list of function values.
136        """
137        return []
138
139    def gradient(self, point):
140        """
141        Compute a function gradient.
142        """
143        return []
144
145    def nonlinear_constraint_values(self, point):
146        """
147        Compute nonlinear constraint values.
148        """
149        return []
150
151    def jacobian(self, point):
152        """
153        Compute the Jacobian.
154        """
155        return []
156
157
158    def _compute_response(self, point, requests):
159        """
160        Iterate through the requested responses and compute them.
161        """
162        response = {}
163        for key in requests.keys():
164            if key not in self.response_str:
165                continue
166            #
167            if key == "FunctionValue":
168               response[key] = str(self.function_value(point))
169            elif key == "FunctionValues":
170               response[key] = tostr(self.function_values(point))
171            elif key == "Gradient":
172               response[key] = tostr(self.gradient(point))
173            elif key == "NonlinearConstraintValues":
174               response[key] = tostr(self.nonlinear_constraint_values(point))
175            elif key == "Jacobian":
176               response[key] = tostr(self.jacobian(point))
177            #
178        return response
179
180
181    def _process(self, point, requests):
182        """
183        Process the XML document
184        """
185        #
186        # Compute response info
187        #
188        response = self._compute_response(point,requests)
189        #
190        # Setup document
191        #
192        doc = xml.dom.minidom.Document()
193        root = doc.createElement("ColinResponse")
194        doc.appendChild(root)
195        for key in requests.keys():
196            if key in response.keys():
197                elt = doc.createElement(str(key))
198                root.appendChild(elt)
199                text_elt = doc.createTextNode( response[key] )
200                elt.appendChild(text_elt)
201            else:
202                elt = doc.createElement(str(key))
203                root.appendChild(elt)
204                text_elt = doc.createTextNode( "ERROR: Unsupported application request "+str(key) )
205                elt.appendChild(text_elt)
206        return doc
207
208
209    def _handleRequests(self, node):
210        """
211        A function that processes the requests
212        """
213        requests = {}
214        for child in node.childNodes:
215            if child.nodeType == node.ELEMENT_NODE:
216                tmp = {}
217                for (name,value) in child.attributes.items():
218                    tmp[name]=value
219                requests[str(child.nodeName)] = tmp
220        return requests
221
222    def validate(self, point):
223        """
224        This function should throw an exception if an error occurs
225        """
226        pass
227
228
229
230class MixedIntOptProblem(OptProblem):
231
232    def __init__(self):
233        OptProblem.__init__(self)
234        self.int_lower=[]
235        self.int_upper=[]
236        self.real_lower=[]
237        self.real_upper=[]
238        self.nreal=0
239        self.nint=0
240        self.nbinary=0
241       
242    def validate(self, point):
243        if len(point.reals) !=  self.nreal:
244            raise ValueError, "Number of reals is "+str(len(point.reals))+" but this problem is configured for "+str(self.nreal)
245        if len(point.ints) !=  self.nint:
246            raise ValueError, "Number of integers is "+str(len(point.ints))+" but this problem is configured for "+str(self.nint)
247        if len(point.bits) !=  self.nbinary:
248            raise ValueError, "Number of binaries is "+str(len(point.bits))+" but this problem is configured for "+str(self.nbinary)
249        if len(self.int_lower) > 0:
250            for i in range(0,self.nreal):
251                if self.int_lower[i] is not None and self.int_lower[i] > point.ints[i]:
252                    raise ValueError, "Integer "+str(i)+" has a value "+str(point.ints[i])+" that is lower than the integer lower bound "+str(self.int_lower[i])
253                if self.int_upper[i] is not None and self.int_upper[i] < point.ints[i]:
254                    raise ValueError, "Integer "+str(i)+" has a value "+str(point.ints[i])+" that is higher than the integer upper bound "+str(self.int_upper[i])
255
256        if len(self.real_lower) > 0:
257            for i in range(0,self.nreal):
258                if self.real_lower[i] is not None and self.real_lower[i] > point.reals[i]:
259                    raise ValueError, "Real "+str(i)+" has a value "+str(point.reals[i])+" that is lower than the real lower bound "+str(self.real_lower[i])
260                if self.real_upper[i] is not None and self.real_upper[i] < point.reals[i]:
261                    raise ValueError, "Real "+str(i)+" has a value "+str(point.reals[i])+" that is higher than the real upper bound "+str(self.real_upper[i])
262
263
264       
Note: See TracBrowser for help on using the repository browser.