1 /*  _________________________________________________________________________
2  *
3  *  Acro: A Common Repository for Optimizers
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 README.txt file in the top Acro directory.
9  *  _________________________________________________________________________
10  */
11 
12 #include <colin/Utilities.h>
13 #include <colin/solver/Base.h>
14 #include <colin/SolverMngr.h>
15 #include <colin/StatusCodes.h>
16 #include <colin/ApplicationMngr.h>
17 #include <colin/TinyXML_data_parser.h>
18 #include <utilib/TinyXML_helper.h>
19 
20 using std::string;
21 using std::endl;
22 
23 using std::list;
24 using std::set;
25 using std::map;
26 using std::make_pair;
27 
28 using utilib::Any;
29 
30 
31 namespace colin {
32 
33 struct Solver_Base::Data
34 {
Datacolin::Solver_Base::Data35    Data()
36       : construct_map(),
37         init_cache(false),
38         final_cache(true),
39         optimal_cache(true)
40    {}
41 
42    struct CacheInfo {
CacheInfocolin::Solver_Base::Data::CacheInfo43       CacheInfo(bool reset_)
44          : has_data(false),
45            reset(reset_),
46            name(cache::ImplicitInterSolverCacheName),
47            points()
48       {}
49 
50       bool       has_data;
51       bool       reset;
52       string     name;
53       list<Any>  points;
54    };
55 
56 /// Container to hold the XML sub-element processors
57    /** Container to hold the XML sub-element processors
58     *
59     *  This is a trick: boost::signals are not copyable, and thus cannot
60     *  be put inside STL containers.  However, if we first put the
61     *  signal * into a non-copyable Any, and then put the Any into the
62     *  STL * container, all is well and good in the world!
63     */
64    typedef map<string, Any>  ConstructMap_t;
65 
66    ///
67    ConstructMap_t construct_map;
68    ///
69    CacheInfo init_cache;
70    ///
71    CacheInfo final_cache;
72    ///
73    CacheInfo optimal_cache;
74 };
75 
76 
77 //============================================================================
78 //
79 //
Solver_Base()80 Solver_Base:: Solver_Base()
81    : solver_statistics(true),
82      data(new Data)
83 {
84    solver_status.solver_status=solver_unknown;
85    solver_status.model_status=model_status_unknown;
86    solver_status.termination_condition=termination_unknown;
87 
88    properties.normalizeKeys() = true;
89 
90    // register the local reset function
91    reset_signal.connect
92       (boost::bind(&Solver_Base::reset_Solver_Base, this));
93 
94    // register the results generator
95    results_signal.connect
96       (boost::bind(&Solver_Base::cb_results, this, _1, _2));
97 
98    // register the default XML components
99    register_construct("Problem").connect
100       (boost::bind(&Solver_Base::process_xml_problem, this, _1, _2));
101 
102    register_construct("InitialPoint").connect
103       (boost::bind(&Solver_Base::process_xml_initialPoint, this, _1, _2));
104 
105    register_construct("FinalPoint").connect
106       (boost::bind(&Solver_Base::process_xml_finalPoint, this, _1, _2));
107 
108    //register_construct("OptimalPoint").connect
109    //   (boost::bind(&Solver_Base::process_xml_optimalPoint, this, _1, _2));
110 
111    register_construct("Options").connect
112       (boost::bind(&Solver_Base::process_xml_options, this, _1, _2));
113 }
114 
115 
116 
117 /// Virtual destructor
~Solver_Base()118 Solver_Base::~Solver_Base()
119 {
120    delete data;
121 }
122 
123 
124 std::string
type() const125 Solver_Base::type() const
126 {
127    string ans = SolverMngr().get_solver_type(this);
128    if ( ans.empty() )
129       return define_solver_type();
130    else
131       return ans;
132 }
133 
134 
construct(TiXmlElement * root,bool describe)135 void Solver_Base::construct(TiXmlElement* root, bool describe)
136 {
137    if ( root == NULL )
138       return;
139    if ( describe )
140    {
141       root->SetAttribute("id", "string");
142 
143       // Now, describe all registered sub-elements
144       Data::ConstructMap_t::iterator it = data->construct_map.begin();
145       Data::ConstructMap_t::iterator itEnd = data->construct_map.end();
146       for ( ; it != itEnd; ++it )
147       {
148          TiXmlElement* node = new TiXmlElement(it->first);
149          root->LinkEndChild(node);
150          it->second.expose<ConstructSignal_t>()(node, true);
151       }
152       return;
153    }
154 
155    // Parse my attributes first
156    const char* name = root->Attribute("id");
157    if ( name != NULL )
158       SolverMngr().reregister_solver(this, name);
159 
160    // Now, parse all valid sub-elements
161    Data::ConstructMap_t &init = data->construct_map;
162    Data::ConstructMap_t::iterator it;
163    Data::ConstructMap_t::iterator itEnd = init.end();
164    TiXmlElement* node = root->FirstChildElement();
165    while ( node != NULL )
166    {
167       it = init.find(node->ValueStr());
168       if ( it == itEnd )
169          EXCEPTION_MNGR(std::runtime_error, "Solver_Base::construct(): "
170                         "No handler registered for " <<
171                         utilib::get_element_info(node));
172 
173       it->second.expose<ConstructSignal_t>()(node, false);
174       node = node->NextSiblingElement();
175    }
176 }
177 
178 
property(std::string name)179 utilib::Property& Solver_Base::property( std::string name )
180 {
181    utilib::PropertyDict::iterator it = properties.find(name);
182    if ( it == properties.end() )
183       EXCEPTION_MNGR(std::runtime_error, "Solver_Base::property(): "
184                      "Attempt to retrieve nonexistent property, '"
185                      << name << "'");
186 
187    return it->second();
188 }
189 
190 
set_initial_points(PointSet ps)191 void Solver_Base::set_initial_points(PointSet ps)
192 {
193    initial_points = ps;
194 
195    // This action overrides any intial point info specified through configure()
196    data->init_cache.name = "";
197    data->init_cache.points.clear();
198    data->init_cache.has_data = false;
199 }
200 
201 
add_initial_point(const utilib::AnyRef point)202 void Solver_Base::add_initial_point(const utilib::AnyRef point)
203 {
204    // If there was info specified through configure(), parse it now.
205    if ( data->init_cache.has_data )
206       initialize_xml_init_cache();
207 
208    if ( point.is_type(typeid(AppResponse)) )
209    {
210       initial_points->insert(point.expose<AppResponse>());
211       return;
212    }
213 
214    ApplicationHandle app = get_problem_handle();
215    if ( app.empty() )
216       EXCEPTION_MNGR(std::logic_error, "Solver_Base::add_initial_point(): "
217                      "Cannot add initial domain points before setting "
218                      "the problem.");
219 
220    // Should we force an evaluation of the objective function here?
221    // Right now we are just inserting an empty response.
222    initial_points->insert
223       ( eval_mngr().perform_evaluation(app->set_domain(point)) );
224 }
225 
226 
get_final_points() const227 PointSet Solver_Base::get_final_points() const
228 {
229    return final_points;
230 }
231 
232 
233 //PointSet Solver_Base::get_optimal_points() const
234 //{
235 //   return optimal_points;
236 //}
237 
238 
set_statistic(std::string name,utilib::Any value)239 void Solver_Base::set_statistic(std::string name, utilib::Any value)
240 {
241    if ( solver_statistics.exists(name) )
242    {
243       ucerr << "WARNING: overwriting solver statistic '"
244             << name << "' = " << solver_statistics[name] << endl
245             << "         with new value = " << value << endl;
246    }
247    solver_statistics[name] = value;
248 }
249 
250 
251 //========================================================================
252 // Solver_Base protected members
253 //========================================================================
254 
255 boost::signals2::signal<void(TiXmlElement*,bool)>&
register_construct(std::string element)256 Solver_Base::register_construct(std::string element)
257 {
258    Any& ans = data->construct_map[element];
259    if ( ans.empty() )
260       ans.set<ConstructSignal_t, Any::NonCopyable<ConstructSignal_t> >();
261    return ans.expose<ConstructSignal_t>();
262 }
263 
264 
265 
266 //========================================================================
267 // Solver_Base private members
268 //========================================================================
269 
270 void
reset_Solver_Base()271 Solver_Base::reset_Solver_Base()
272 {
273    // If we got cache configuration information through configure(), but
274    // are not being executed by the XML parser (i.e. the ExecuteMngr),
275    // we need to actually setup / initialize our caches based on that
276    // information.  As initialize_xml_*_cache() clears the incoming
277    // data, this will only be executed te first time reset is called.
278    // Additionally, if the initial data was parsed using the
279    // SolverExecuteFunctor, SolverExecuteFunctor::execute() sets
280    // has_data to false, so nothing will happen here.
281 
282    if ( data->init_cache.has_data )
283       initialize_xml_init_cache();
284 
285    if ( data->final_cache.has_data )
286       initialize_xml_final_cache();
287 
288    //if ( data->optimal_cache.has_data )
289    //   initialize_xml_optimal_cache();
290 }
291 
292 void
cb_results(utilib::PropertyDict & pd,int verbosity)293 Solver_Base::cb_results(utilib::PropertyDict &pd, int verbosity)
294 {
295    pd["problem"] = get_problem_handle()->describe(verbosity);
296    utilib::PropertyDict solver_pd = utilib::PropertyDict(true);
297    pd["solver"] = solver_pd;
298    solver_pd["name"] = define_solver_type();
299    solver_pd["status"] = solver_status.describe(verbosity);
300    solver_pd["statistics"] = solver_statistics;
301 }
302 
303 
304 void
process_xml_problem(TiXmlElement * node,bool describe)305 Solver_Base::process_xml_problem( TiXmlElement* node, bool describe )
306 {
307    if ( describe )
308    {
309       node->SetAttribute("id", "string");
310       return;
311    }
312 
313    string problem;
314    utilib::get_string_attribute( node, "id", problem, "" );
315    if ( ! problem.empty() )
316       set_problem(ApplicationMngr().get_application(problem));
317    if ( get_problem_handle().empty() )
318    {
319       // Attempt to use the most recently created application
320       problem = ApplicationMngr().get_newest_application();
321       if ( ! problem.empty() )
322          set_problem( ApplicationMngr().get_application(problem) );
323    }
324 
325    if ( ! get_problem_handle().empty() )
326       get_problem_handle()->initialize(node);
327 }
328 
329 void
process_xml_initialPoint(TiXmlElement * node,bool describe)330 Solver_Base::process_xml_initialPoint( TiXmlElement* node, bool describe )
331 {
332    if ( describe )
333       return;
334 
335    bool cache_specified = false;
336    cache_specified |= utilib::get_string_attribute
337       ( node, "cache", data->init_cache.name,
338         cache::ImplicitInterSolverCacheName );
339    cache_specified |= utilib::get_bool_attribute
340       (node, "clear", data->init_cache.reset, false);
341 
342    data->init_cache.points.clear();
343    TiXmlElement* elt = node->FirstChildElement();
344    if ( elt == NULL )
345    {
346       // Convenience: if there are no child elements, but it has a
347       // single text child, assume it is a point
348       const char* text = node->GetText();
349       if ( text != NULL )
350          data->init_cache.points.push_back(parse_xml_data(node));
351    }
352    for( ; elt != NULL; elt = elt->NextSiblingElement() )
353    {
354       if ( elt->ValueStr().compare("Point") != 0 )
355          EXCEPTION_MNGR(std::runtime_error, "[Solver_Base] "
356                         "process_xml_options(): invalid element "
357                         << elt->ValueStr() << " in "
358                         << utilib::get_element_info(elt));
359 
360       data->init_cache.points.push_back(parse_xml_data(elt));
361    }
362 
363    if ( ! cache_specified && ! data->init_cache.points.empty() )
364       data->init_cache.reset = true;
365 
366    data->init_cache.has_data = true;
367 }
368 
369 
370 void
process_xml_finalPoint(TiXmlElement * node,bool describe)371 Solver_Base::process_xml_finalPoint( TiXmlElement* node, bool describe )
372 {
373    if ( describe )
374       return;
375 
376    utilib::get_string_attribute
377       ( node, "cache", data->final_cache.name, "" );
378    utilib::get_bool_attribute
379       (node, "clear", data->final_cache.reset, true);
380 
381    data->final_cache.has_data = true;
382 }
383 
384 
385 //void
386 //Solver_Base::process_xml_optimalPoint( TiXmlElement* node, bool describe )
387 //{
388 //   if ( describe )
389 //      return;
390 //
391 //   utilib::get_string_attribute
392 //      ( node, "cache", data->optimal_cache.name, ImplicitInterSolverCacheName );
393 //   utilib::get_bool_attribute
394 //      (node, "clear", data->optimal_cache.reset, true);
395 //
396 //   data->optimal_cache.has_data = true;
397 //}
398 
399 
400 void
process_xml_options(TiXmlElement * node,bool describe)401 Solver_Base::process_xml_options( TiXmlElement* node, bool describe )
402 {
403    if ( describe )
404    {
405       TiXmlElement *opt = new TiXmlElement("Option");
406       opt->SetAttribute("name", "string");
407       node->LinkEndChild(opt);
408       return;
409    }
410 
411    TiXmlElement* n = node->FirstChildElement();
412    for( ; n != NULL; n = n->NextSiblingElement() )
413    {
414       if ( n->ValueStr().compare("Option") != 0 )
415          EXCEPTION_MNGR(std::runtime_error, "[Solver_Base] "
416                         "process_xml_options(): invalid element "
417                         << n->ValueStr() << " in "
418                         << utilib::get_element_info(n));
419       string name = "";
420       utilib::get_string_attribute(n, "name", name);
421       properties[name] = parse_xml_data(n);
422    }
423 }
424 
425 
426 void
initialize_xml_init_cache()427 Solver_Base::initialize_xml_init_cache()
428 {
429    //
430    // Now we actually set up everything for the initial cache...
431    //
432    if ( ! data->init_cache.name.empty() )
433    {
434       CacheHandle cache = CacheFactory().get_cache(data->init_cache.name);
435       if ( cache.empty() )
436       {
437          // register a new instance of the cache
438          cache = PointSet().operator->()->get_handle();
439          CacheFactory().register_cache(cache, data->init_cache.name);
440       }
441 
442       if ( data->init_cache.reset )
443          cache->clear();
444 
445       initial_points = PointSet(cache);
446    }
447 
448    // Must set has_data to false, or add_initial_point will infinitely recurse
449    data->init_cache.has_data = false;
450 
451    list<Any> &points = data->init_cache.points;
452    for (; ! points.empty(); points.pop_front() )
453       add_initial_point(points.front());
454 
455    data->init_cache = Data::CacheInfo(false);
456 }
457 
458 
459 void
initialize_xml_final_cache()460 Solver_Base::initialize_xml_final_cache()
461 {
462    CacheHandle cache = CacheFactory().get_cache(data->final_cache.name);
463    if ( data->final_cache.reset && ! cache.empty() )
464    {
465       // Don't clear the cache (we might be using it for our input
466       // points!).  Rather, unregister it and let the CacheHandle take
467       // care of deleting it when the last one falls out of scope.
468       CacheFactory().unregister_cache(data->final_cache.name);
469       cache = CacheHandle();
470    }
471    if ( cache.empty() )
472    {
473       // defer to the default cache used by PointSets
474       cache = PointSet().operator->()->get_handle();
475       CacheFactory().register_cache(cache, data->final_cache.name);
476    }
477    final_points = PointSet(cache);
478    data->final_cache = Data::CacheInfo(true);
479 }
480 
481 
482 //void
483 //Solver_Base::initialize_xml_optimal_cache()
484 //{
485 //   CacheHandle cache = CacheFactory().get_cache(data->optimal_cache.name);
486 //   if ( data->optimal_cache.reset && ! cache.empty() )
487 //   {
488 //      // Don't clear the cache (we might be using it for our input
489 //      // points!).  Rather, unregister it and let the CacheHandle take
490 //      // care of deleting it when the last one falls out of scope.
491 //      CacheFactory().unregister_cache(data->optimal_cache.name);
492 //      cache = CacheHandle();
493 //   }
494 //   if ( cache.empty() )
495 //   {
496 //      // defer to the default cache used by PointSets
497 //      cache = PointSet().operator->()->get_handle();
498 //      CacheFactory().register_cache(cache, data->optimal_cache.name);
499 //   }
500 //   optimal_points = PointSet(cache);
501 //   data->optimal_cache = Data::CacheInfo(true);
502 //}
503 
504 } // namespace colin
505