Lumiera  0.pre.03
»edit your freedom«
index.hpp
Go to the documentation of this file.
1 /*
2  INDEX.hpp - data structure for organising advice solutions and matching
3 
4  Copyright (C)
5  2010, Hermann Vosseler <Ichthyostega@web.de>
6 
7   **Lumiera** is free software; you can redistribute it and/or modify it
8   under the terms of the GNU General Public License as published by the
9   Free Software Foundation; either version 2 of the License, or (at your
10   option) any later version. See the file COPYING for further details.
11 
12 */
13 
14 
77 #ifndef LUMIERA_ADVICE_INDEX_H
78 #define LUMIERA_ADVICE_INDEX_H
79 
80 
81 #include "lib/error.hpp"
82 #include "lib/symbol.hpp"
83 #include "include/logging.h"
84 #include "lib/iter-adapter-stl.hpp"
85 #include "lib/format-obj.hpp"
86 #include "lib/util-foreach.hpp"
87 #include "lib/util.hpp"
89 
90 #include <boost/operators.hpp>
91 #include <unordered_map>
92 #include <string>
93 
94 namespace lumiera{
95 namespace advice {
96 
97  namespace error = lumiera::error;
98 
99  using std::placeholders::_1;
100  using std::unordered_map;
103  using util::toString;
104  using util::for_each;
105  using util::contains;
106  using util::unConst;
107  using lib::Literal;
108  using std::string;
109  using std::vector;
110  using std::pair;
111 
112 
113 
114 
115 
140  template<class POA>
141  class Index
142  {
143 
144 
145  struct Entry
146  : pair<Binding::Matcher, POA*>
147  , boost::equality_comparable<Entry, POA,
148  boost::equality_comparable<Entry>
149  >
150  {
151  explicit
152  Entry (POA& elm)
153  : pair<Binding::Matcher, POA*> (elm.getMatcher(), &elm)
154  { }
155 
156  // using default-copy, thus assuming copy is NO_THROW
157 
158 
159  operator string() const
160  {
161  return "E-" +hash_value(this->first) +"--> "+ this->second ;
162  }
163 
164  friend bool
165  operator== (Entry const& a, Entry const& b)
166  {
167  return a.second == b.second;
168  }
169 
170  friend bool
171  operator== (Entry const& a, POA const& p)
172  {
173  return a.second == &p;
174  }
175  };
176 
177 
178  typedef vector<Entry> EntryList;
179  typedef typename EntryList::iterator EIter;
180 
181 
182  struct Cluster
183  {
184  EntryList elms_;
185 
186  size_t
187  size() const
188  {
189  return elms_.size();
190  }
191 
192  void
193  append (POA& elm)
194  {
195  REQUIRE (!contains (elm), "Duplicate entry");
196  try { elms_.push_back (Entry(elm)); }
197 
198  catch(std::bad_alloc&)
199  {
200  throw error::Fatal("AdviceSystem failure due to exhausted memory");
201  }
202  }
203 
204  void
205  overwrite (POA const& oldRef, POA& newElm)
206  {
207  EIter pos = std::find (elms_.begin(),elms_.end(), oldRef);
208  REQUIRE (pos!=elms_.end(), "Attempt to overwrite an entry which isn't there.");
209  REQUIRE_IF (&oldRef != &newElm, !contains (newElm), "Duplicate entry");
210 
211  *pos = Entry(newElm);
212 
213  REQUIRE_IF (&oldRef != &newElm, !contains (oldRef), "Duplicate entry");
214  }
215 
216  void
217  remove (POA const& refElm)
218  {
219  EIter pos = std::find (elms_.begin(),elms_.end(), refElm);
220  if (pos!=elms_.end())
221  elms_.erase(pos);
222 
223  ENSURE (!contains (refElm), "Duplicate entry");
224  }
225 
226  bool
227  contains (POA const& refElm)
228  {
229  for (EIter i=elms_.begin(); i!=elms_.end(); ++i)
230  if (*i == refElm) return true;
231  return false;
232  }
233 
234  operator string() const
235  {
236  string dump{"elmList("+toString(elms_.size())+")\n"};
237  for (auto const& entry : elms_)
238  dump += "E...:"+entry+"\n";
239  return dump;
240  }
241 
243  allElms ()
244  {
245  return eachElm (elms_);
246  }
247  };
248 
249 
251  : Cluster
252  {
253  POA*
254  find_latest_solution (POA& requestElm)
255  {
256  typedef typename EntryList::reverse_iterator RIter;
257  Binding::Matcher pattern (requestElm.getMatcher());
258  for (RIter ii=this->elms_.rbegin();
259  ii!=this->elms_.rend();
260  ++ii )
261  if (ii->first.matches (pattern))
262  return ii->second;
263 
264  return NULL;
265  }
266 
267  void
268  publish_latest_solution (POA& requestElm)
269  {
270  POA* solution = find_latest_solution (requestElm);
271  if (solution)
272  // found the most recent advice provision satisfying the (new) request
273  // thus publish this new advice solution into the request object
274  requestElm.setSolution (solution);
275  else
276  requestElm.setSolution ( NULL );
277  // report "no solution" which causes a default solution to be used
278  }
279  };
280 
282  : Cluster
283  {
284  void
285  publish_all_solutions (POA& provisionElm)
286  {
287  Binding::Matcher pattern (provisionElm.getMatcher());
288  for (EIter ii=this->elms_.begin();
289  ii!=this->elms_.end();
290  ++ii )
291  if (pattern.matches (ii->first))
292  // the given (new) advice provision satisfies this request
293  // thus publish this new advice solution into the request object
294  ii->second->setSolution (&provisionElm);
295  }
296 
297  void
298  retract_all_solutions (POA const& oldProv, ProvisionCluster& possibleReplacementSolutions)
299  {
300  Binding::Matcher pattern (oldProv.getMatcher());
301  for (EIter ii=this->elms_.begin();
302  ii!=this->elms_.end();
303  ++ii )
304  if (pattern.matches (ii->first))
305  // the old advice provision (to be dropped) satisfied this request
306  // which thus needs to be treated anew (could cause quadratic complexity)
307  possibleReplacementSolutions.publish_latest_solution (*(ii->second));
308  }
309 
310  void
311  rewrite_all_solutions (POA const& oldProv, POA& newProv, ProvisionCluster& possibleReplacementSolutions)
312  {
313  Binding::Matcher oldPattern (oldProv.getMatcher());
314  Binding::Matcher newPattern (newProv.getMatcher ());
315  for (EIter ii=this->elms_.begin();
316  ii!=this->elms_.end();
317  ++ii )
318  if (newPattern.matches (ii->first))
319  ii->second->setSolution (&newProv);
320  else
321  if (oldPattern.matches (ii->first))
322  possibleReplacementSolutions.publish_latest_solution (*(ii->second));
323  }
324  };
325 
326 
327 
328  /* ==== Index Tables ===== */
329 
330  typedef unordered_map<HashVal, RequestCluster> RTable;
331  typedef unordered_map<HashVal, ProvisionCluster> PTable;
332 
333  mutable RTable requestEntries_;
334  mutable PTable provisionEntries_;
335 
336 
337 
338  public:
339 
340  void
341  addRequest (POA& entry)
342  {
343  HashVal key (hash_value(entry));
344  requestEntries_[key].append (entry); // might throw
345  provisionEntries_[key].publish_latest_solution (entry);
346  }
347 
354  void
355  modifyRequest (HashVal oKey, POA& entry)
356  {
357  HashVal nKey (hash_value(entry));
358  if (oKey != nKey)
359  {
360  requestEntries_[nKey].append (entry); // might throw
361  requestEntries_[oKey].remove (entry);
362  }
363  else
364  { // rewrite Entry to include the new binding
365  requestEntries_[nKey].overwrite (entry, entry);
366  }
367  provisionEntries_[nKey].publish_latest_solution (entry);
368  }
369 
370  void
371  removeRequest (POA const& refEntry)
372  {
373  HashVal oKey (hash_value(refEntry));
374  requestEntries_[oKey].remove (refEntry);
375  }
376 
377 
378  void
379  addProvision (POA& entry)
380  {
381  HashVal key (hash_value(entry));
382  provisionEntries_[key].append (entry); // might throw
383  requestEntries_[key].publish_all_solutions (entry);
384  }
385 
386  void
387  modifyProvision (POA const& oldRef, POA& newEntry)
388  {
389  HashVal oKey (hash_value(oldRef));
390  HashVal nKey (hash_value(newEntry));
391  if (oKey != nKey)
392  {
393  provisionEntries_[nKey].append (newEntry); // might throw, in which case it has no effect
394  provisionEntries_[oKey].remove (oldRef);
395  requestEntries_[nKey].publish_all_solutions (newEntry);
396  requestEntries_[oKey].retract_all_solutions (oldRef, provisionEntries_[oKey]);
397  }
398  else
399  {
400  provisionEntries_[nKey].overwrite (oldRef, newEntry);
401  requestEntries_[nKey].rewrite_all_solutions (oldRef,newEntry, provisionEntries_[nKey]);
402  }
403  }
404 
405  void
406  removeProvision (POA const& refEntry)
407  {
408  HashVal key (hash_value(refEntry));
409  provisionEntries_[key].remove (refEntry); // NO_THROW
410  requestEntries_[key].retract_all_solutions (refEntry, provisionEntries_[key]);
411  }
412 
413 
418  void
419  clear ()
420  {
421  WARN (library, "Purging Advice Binding Index...");
422  requestEntries_.clear();
423  provisionEntries_.clear();
424  }
425 
426 
427 
428  /* == diagnostics == */
429 
431  bool isValid() const;
432 
433  size_t
434  size() const
435  {
436  return request_count() + provision_count();
437  }
438 
439  size_t
440  request_count() const
441  {
442  return sumClusters (eachVal (requestEntries_));
443  }
444 
445  size_t
446  provision_count() const
447  {
448  return sumClusters (eachVal (provisionEntries_));
449  }
450 
451  bool
452  hasRequest (POA const& refEntry) const
453  {
454  return requestEntries_[hash_value(refEntry)].contains (refEntry);
455  }
456 
457  bool
458  hasProvision (POA const& refEntry) const
459  {
460  return provisionEntries_[hash_value(refEntry)].contains (refEntry);
461  } // note: even just lookup might create a new (empty) cluster;
462  // thus the tables are defined as mutable
463 
464 
465  private:
468  template<class IT>
469  static size_t
470  sumClusters (IT ii)
471  {
472  size_t sum=0;
473  for ( ; ii; ++ii )
474  sum += ii->size();
475  return sum;
476  }
477 
478  void verify_Entry (Entry const&, HashVal) const;
479  void verify_Request (Entry const&, HashVal) const;
480  };
481 
482 
483 
484 
485 
486 
487 
488 
489 
490 
491 
492  /* == Self-Verification == */
493 
494  namespace { // self-check implementation helpers...
495 
496  LUMIERA_ERROR_DEFINE(INDEX_CORRUPTED, "Advice-Index corrupted");
497 
499  : error::Fatal
500  {
501  SelfCheckFailure (Literal failure)
502  : error::Fatal {string("Failed test: ")+failure
503  ,LUMIERA_ERROR_INDEX_CORRUPTED}
504  { }
505  };
506  }
507 
508 
509 
510 
517  template<class POA>
518  bool
520  {
521  typedef typename RTable::const_iterator RTIter;
522  typedef typename PTable::const_iterator PTIter;
523 
524  try {
525  for (PTIter ii =provisionEntries_.begin();
526  ii != provisionEntries_.end(); ++ii)
527  {
528  HashVal hash (ii->first);
529  Cluster& clu = unConst(ii->second);
530  for_each (clu.allElms(), &Index::verify_Entry, this, _1, hash);
531  }
532  for (RTIter ii=requestEntries_.begin();
533  ii != requestEntries_.end(); ++ii)
534  {
535  HashVal hash (ii->first);
536  Cluster& clu = unConst(ii->second);
537  for_each (clu.allElms(), &Index::verify_Request, this, _1, hash);
538  }
539  return true;
540  }
541 
542  catch(SelfCheckFailure& failure)
543  {
544  lumiera_error();
545  ERROR (library, "%s", failure.what());
546  }
547  return false;
548  }
549 
550 
551 
552 #define VERIFY(_CHECK_, DESCRIPTION) \
553  if (!(_CHECK_)) \
554  throw SelfCheckFailure ((DESCRIPTION));
555 
556 
557  template<class POA>
558  void
559  Index<POA>::verify_Entry (Entry const& e, HashVal hash) const
560  {
561  VERIFY (hash == hash_value(e.first), "Wrong bucket, hash doesn't match bucket");
562  VERIFY (e.second, "Invalid Entry: back-link is NULL");
563  }
564 
565  template<class POA>
566  void
567  Index<POA>::verify_Request (Entry const& e, HashVal hash) const
568  {
569  verify_Entry (e,hash);
570  POA& request = *(e.second);
571  const POA* solution (request.getSolution());
572  if (solution && hasProvision(*solution))
573  {
574  POA* currentSolution = provisionEntries_[hash].find_latest_solution (request);
575  VERIFY (e.first.matches (solution->getMatcher()),"stored advice solution not supported by binding match");
576  VERIFY (bool(currentSolution), "unable to reproduce stored solution with the current provisions")
577  VERIFY (solution == currentSolution, "stored advice solution isn't the topmost solution for this request")
578  }
579  }
580 
581 #undef VERIFY
582 
583 
584 }} // namespace lumiera::advice
585 #endif
Functor object for matching against another Binding.
AnyPair entry(Query< TY > const &query, typename WrapReturn< TY >::Wrapper &obj)
helper to simplify creating mock table entries, wrapped correctly
inline string literal This is a marker type to indicate that
Definition: symbol.hpp:76
This header is for including and configuring NoBug.
static size_t sumClusters(IT ii)
internal: sum element count over all clusters in the given hashtable
Definition: index.hpp:470
Derived specific exceptions within Lumiera&#39;s exception hierarchy.
Definition: error.hpp:190
A pattern to define and identify a specific attachment to the Advice system.
Marker types to indicate a literal string and a Symbol.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
bool isValid() const
validity self-check
Definition: index.hpp:519
lumiera_err lumiera_error(void)
Get and clear current error state.
Definition: error-state.c:115
std::string toString(TY const &val) noexcept
get some string representation of any object, reliably.
Definition: format-obj.hpp:191
void for_each(CON const &elements, FUN function, P1 &&bind1, ARGS &&...args)
Accept binding for arbitrary function arguments.
_SeqT< CON >::Range eachElm(CON &coll)
Lumiera error handling (C++ interface).
Simple functions to represent objects, for debugging and diagnostics.
size_t HashVal
a STL compatible hash value
Definition: hash-value.h:52
Lumiera public interface.
Definition: advice.cpp:104
void modifyRequest(HashVal oKey, POA &entry)
Definition: index.hpp:355
Accessing a STL element range through a Lumiera forward iterator, An instance of this iterator adapte...
Preconfigured adapters for some STL container standard usage situations.
Index datastructure for organising advice solutions.
Definition: index.hpp:141
Perform operations "for each element" of a collection.
bool contains(SEQ const &cont, typename SEQ::const_reference val)
shortcut for brute-force containment test in any sequential container
Definition: util.hpp:255
_MapIterT< IT >::ValIter eachVal(IT const &begin, IT const &end)
#define LUMIERA_ERROR_DEFINE(err, msg)
Definition and initialisation of an error constant.
Definition: error.h:71