Home Page Downloads Contact Us

Free Open Source Artificial Intelligence C++ Parts

aiParts Open Source Project  

Home Page
Open Source
Contact
Free Downloads
Releases and Credits
Software Status
License
v0.9.3 README File
About rrproj
assigning people and/or equipment to projects
High-Hope Technique
History of the AI
View Source Files
and how they are organized
Making Programs
Development Notes
Using aiParts
Your Application
Support and Services

aiParts Source File

//**********************************************************************
//  aipHighHope.cpp  -  function bodies for aipHighHope.h
//
//  Copyright (c)  1999, 2005, 2008  Brian Marshall
//
//  See the license at end of this file.
//
//  Developers/Contributers:
//    [BRM] Brian Marshall - Calgary - bmarshal@agt.net
//
//  08/06/16  [BRM] added decision is_decided(), 
//                  removed decision groups, modified deciding,
//                  new next_decision(), added aipHHImportance,
//                  decisions may be decided more than once,
//                  removed dcsn.m_opt_prev; small changes
//  05/11/18  [BRM] added decision groups
//                  worst decision optionally has extra weight
//                  a failed decision gets increase in fear
//                  moved call disable_slowly_degrade() to aipHHHope
//                  fixed minor bug re: problem members: m_is_many_xxx
//                  fixed minor bug re: m_tries_since_change_count
//  05/11/12  [BRM] moved logic from problem to hope aspects
//  05/11/07  [BRM] moved random_num() to class aipBase
//  05/11/01  [BRM] count tries to get to best try 
//  05/10/20  [BRM] fixed bug re: count since improvement
//                  disabled hope emotions slowly degrading
//                  modified how decision and option hopes change
//  05/10/02  [BRM] split out from aipProblem and aipDecision
//  05/09/26  [BRM] development began
//
//----------------------------------------------------------------------
//  The key functions are...
//
//  aipG aipHHProblem::solve()
//    implements the High-Hope problem-solving technique
//
//  void aipHHDecision::take_msg (aipMsg *m)
//  void aipHHOption::take_msg (aipMsg *m)
//  void aipHHFearAspect::take_msg (aipMsg *m)
//  void aipHHGreedAspect::take_msg (aipMsg *m)
//  void aipHHCuriosityAspect::take_msg (aipMsg *m)
//     decisions and options are affected by a received message
//
//  Maybe:
//  void aipHHProblem::normalize_option_goodnesses()
//    something like this might be used in some kinds of problems
//    in which (some) options have a constant goodness that should
//    affect which option is chosen.
//
//  Note that shortest-path-from-A-to-B problems, for example,
//  do not use  normalize_option_goodnesses()  - a short step is
//  not favored over a long step - people like non-stop flights.
//
//----------------------------------------------------------------------

#include "aipHighHope.h"

#include <string.h>
#include <iostream>
using namespace std;

//======================================================================
//  aipHHImportance  -  importance for choosing decisions
//
//----------------------------------------------------------------------
//  take_msg  -  take a message and maybe do something because of it

void aipHHImportance::take_msg (aipMsg *mm) {

  aipHHMsg *m = (aipHHMsg *)mm;

  if (m->typ() == HH_Try_Has_Ended) {

    if (m->is_the_failure()) m_been_prob += 16;
    long imp_val = val();
    if (imp_val > 64) {
      m_been_prob -= 12;
    } else if (imp_val > 16) {
      m_been_prob -= 4;
    }

  } else if (m->typ() == HH_No_Recent_Improve) {

    m_been_prob /= 2;

  }

  aipImportance::take_msg(mm);

}

//======================================================================
//  aipHHHope  -  Hope with High-Hope aspects
//
//----------------------------------------------------------------------
//  Constructor

aipHHHope::aipHHHope () {

  aipHHFearAspect *fear_aspect = new aipHHFearAspect;
  aipHHGreedAspect *greed_aspect = new aipHHGreedAspect;
  aipHHCuriosityAspect *curiosity_aspect = new aipHHCuriosityAspect;

  if (fear_aspect && greed_aspect && curiosity_aspect) {
    if (fear())           fear()->add_aspect(fear_aspect);
    if (greed())         greed()->add_aspect(greed_aspect);
    if (curiosity()) curiosity()->add_aspect(curiosity_aspect);
  }

  disable_slowly_degrade();

}

//======================================================================
//  aipHHFearAspect  -  how messages affect fear in hope
//
//----------------------------------------------------------------------
// take_msg

void aipHHFearAspect::take_msg (aipMsg *mm) {

  aipHHMsg *m = (aipHHMsg *)mm;

  aipHHSolveStat *ss = m->solve_stat();

  if (!fear() || !ss) return;

  if (m->typ() == HH_Starting_Solve) {

    fear()->set_g(aipNeutral);

  } else if (m->typ() == HH_No_Recent_Change) {


  } else if (m->typ() == HH_No_Recent_Improve) {

    fear()->weaken(aipIntensity_A_Little);

  } else if (m->typ() == HH_No_Recent_Best_So_Far) {

    fear()->weaken(aipIntensity_Somewhat);

  } else if (m->typ() == HH_Try_Has_Ended) {

    if ( ss->try_result() == HH_Try_Has_Failed ) {

      if (m->is_in_cur_solution()) {
        fear()->strengthen(aipIntensity_Slightly);
      } else if (m->is_the_failure()) {
        fear()->strengthen(aipIntensity_A_Little);
      }

    } else {   // a complete solution was found

      if (m->is_in_cur_solution()) {
        fear()->weaken(aipIntensity_A_Little);
      }

    }  // end of block for when a complete solution was found

  }

  aipAspect::take_msg(mm);

}

//======================================================================
//  aipHHGreedAspect  -  how messages affect greed in hope
//
//----------------------------------------------------------------------
// take_msg

void aipHHGreedAspect::take_msg (aipMsg *mm) {

  aipHHMsg *m = (aipHHMsg *)mm;

  aipHHSolveStat *ss = m->solve_stat();

  if (m->typ() == HH_Starting_Solve) {

    greed()->set_g(aipNeutral);

  } else if (m->typ() == HH_Try_Has_Ended) {

    if ( ss->try_result() == HH_Try_Is_New_Best ) {
      if (greed()->g() >= Greed_For_Best) {
        greed()->set_g(Greed_For_Was_Best);
      }
    }

    if ( ss->try_result() == HH_Try_Is_Improved ) {
      if (greed()->g() == Greed_For_Improved) {
        greed()->set_g(Greed_For_Changed);
      }
    }

    if ( ss->try_result() == HH_Try_Is_Changed ) {
      if (greed()->g() == Greed_For_Changed) {
        greed()->set_g(Greed_For_Complete);
      }
    }

    if ( ss->try_result() == HH_Try_Has_Failed ) {

    } else {   // a complete solution was found

      if (m->is_in_cur_solution()) {

        if ( ss->try_result() == HH_Try_Is_New_Best ) {
          greed()->set_g(Greed_For_New_Best);
        } else if ( ss->try_result()  == HH_Try_Is_A_Best ) {
          greed()->set_g(Greed_For_Best);
        } else if ( ss->try_result() == HH_Try_Is_Improved &&
                    greed()->g() < Greed_For_Improved ) {
          greed()->set_g(Greed_For_Improved);
        } else if ( ss->try_result() == HH_Try_Is_Changed &&
                    greed()->g() < Greed_For_Changed ) {
          greed()->set_g(Greed_For_Changed);
        } else if ( ss->try_result() == HH_Try_Is_Complete &&
                    greed()->g() < Greed_For_Complete ) {
          greed()->set_g(Greed_For_Complete);
        }

      }   // end of block for decision in current solution

    }  // end of block for when a complete solution was found

  }

  aipAspect::take_msg(mm);

}

//======================================================================
//  aipHHCuriosityAspect  -  how messages affect curiosityin hope
//
//----------------------------------------------------------------------
// take_msg

void aipHHCuriosityAspect::take_msg (aipMsg *mm) {

  aipHHMsg *m = (aipHHMsg *)mm;

  aipHHSolveStat *ss = m->solve_stat();

  if (m->typ() == HH_Starting_Solve) {

    curiosity()->set_g(Curiosity_Starting);

  } else if (m->typ() == HH_No_Recent_Change) {

    if ( ! m->is_in_cur_solution() ) {
      curiosity()->set_g(Curiosity_Starting);
    }

  } else if (m->typ() == HH_No_Recent_Improve) {

    curiosity()->set_g(Curiosity_Starting);

  } else if (m->typ() == HH_No_Recent_Best_So_Far) {

    curiosity()->set_g(Curiosity_Starting);
    curiosity()->strengthen(aipIntensity_Somewhat);

  } else if (m->typ() == HH_Try_Has_Ended) {

    if ( ss->try_result() == HH_Try_Has_Failed ) {

      if (m->is_in_cur_solution()) {
        curiosity()->weaken(aipIntensity_A_Little);
      }

    } else {   // a complete solution was found

      if (m->is_in_cur_solution()) {

        curiosity()->weaken(aipIntensity_Somewhat);

        if ( ss->try_result() == HH_Try_Is_New_Best ) {
          // Greed will go up, curiosity comes down...
          curiosity()->weaken(aipIntensity_Somewhat);    // more
        }

      }   // end of block for decision in current solution

    }  // end of block for when a complete solution was found

  }

  aipAspect::take_msg(mm);

}

//======================================================================
//  aipHHProblem
//
//----------------------------------------------------------------------
//  Constructor

aipHHProblem::aipHHProblem () {

  m_num_try = 1000;
  m_dcsn_is_progress = m_worst_is_extra_bad = 0;
  m_should_log_tries = 1;  // default to: write to log about tries

  m_solve_stat = new aipHHSolveStat (this);

}

//----------------------------------------------------------------------
//  Destructor

aipHHProblem::~aipHHProblem () {

  if (m_solve_stat) delete m_solve_stat;

}

//----------------------------------------------------------------------
//  add_hh_decision

void aipHHProblem::add_hh_decision (aipHHDecision *x) {

  aipProblem::add_decision(x);

}

//----------------------------------------------------------------------
//  hh_decision_iterator

aipHHDecisionItr aipHHProblem::hh_decision_iterator() const { 

  aipHHDecisionItr i(decision_pandemonium());

  return i;

}

//----------------------------------------------------------------------
//  return the number of decisions to be made

long aipHHProblem::num_decisions() const {

  long num_dcsn = 0;

  aipHHDecisionItr itr = hh_decision_iterator();
  aipHHDecision *d;
  for ( d = itr.first(); d; d = itr.next() ) {
    num_dcsn += d->num_to_decide();
  }

  return num_dcsn;

}

//----------------------------------------------------------------------
//  g  -  goodness meaningful to user

aipG aipHHProblem::g() { 

  return solve_stat()->g_usr_bsf(); 

}

//----------------------------------------------------------------------
//  normalize_option_goodnesses  -  sample - for some problems
//
//  A function like this SAMPLE might be used in some types of 
//  problems, in which the goodness of an option (to the user) 
//  should be considered in picking options.
//
//  This function is used to convert values meaningful to the user
//  (ex. miles, dollars, minutes) to goodness values that can be 
//  combined and compared with other goodness values.
//
//  A normalize_option_goodnesses() function (that might or might not
//  look like this sample) might be used in staff-scheduling, where
//  a supervisor could work a shift as a clerk, but only if it was
//  absolutely necessary.
//
//  A normalize_option_goodnesses() function would not generally
//  be used in a find-the-shortest-path-from-A-to-B problem, where
//  a short step is not favored over a longer step; taking a short
//  step can simply mean another longer step is required.
//  
//  Option goodness m_g_constant is initially set to m_g_user_constant.
//  If these values are normal goodnesses, this function is not used.
//
//  Note that m_g_constant and m_g_user_constant may contain negative
//  values because:
//   - they are values the user considers to be negative, or,
//   - they are value that the user wants solve() to minimize.
//
//  This can really be improved, but for a first version...
//
//  Calculate a divisor as: max-absolute-value / 16, and then:
//   - for positive values:  x = (x / divisor) + 1
//   - for negative values:  x = (x / divisor) - 1
//
//  This could work ok.  It will work very poorly if most of the
//  values are between, say, 1 and 100, and there are a few values
//  that are greater than 100000; all the smaller values will be 
//  normalized to 1.

void aipHHProblem::normalize_option_goodnesses () {

  aipHHDecisionItr d_itr = hh_decision_iterator();
  aipHHDecision *dec;  // used with the decision-iterator
  aipHHOption   *opt;  // used with the option-iterator

  aipG g_min = aipManditory;  // Find min and max
  aipG g_max = aipForbidden;
  for ( dec = d_itr.first(); dec; dec = d_itr.next() ) {

    aipHHOptionItr o_itr = dec->hh_option_iterator();
    for ( opt = o_itr.first(); opt; opt = o_itr.next() ) {
      if (opt->g_user_constant() < g_min) 
                        g_min = opt->g_user_constant();
      if (opt->g_user_constant() > g_max) 
                        g_max = opt->g_user_constant();
    }   // end of loop through options in the decision
  }   // end of loop through decisions

  long max_abs_val = g_min.absolute_value();
  if (g_max.numeric_value() > max_abs_val) {
    max_abs_val = g_max.numeric_value();
  }

  if (max_abs_val < 20) return;

  long divisor = max_abs_val / 16;

  for ( dec = d_itr.first(); dec; dec = d_itr.next() ) {
    aipHHOptionItr o_itr = dec->hh_option_iterator();
    for ( opt = o_itr.first(); opt; opt = o_itr.next() ) {
      long val = opt->g_user_constant().numeric_value();
      if (val > 0) {
        val = (val / divisor) + 1;
      } else if (val < 0) {
        val = (val / divisor) - 1;
      }
      if (val) opt->set_norm_g_constant(val);
    }
  }

}


//----------------------------------------------------------------------
//  solve
//
//  Return a goodness that is meaningful to the user - this might
//  be a goodness, or it might be dollars, miles, hours, etc.
//
//  This function, plus the take_msg() function on decisions and
//  options, are the implementation of the High-Hope technique.
//
//  It would seem that it would be appropriate to have a separate 
//  try() function, but it would be only a small proportion of this
//  function, and it would change variables set in this function.
//  Having it all in this function is the clearest way.

aipG aipHHProblem::solve() {

  aipHHSolveStat * ss = solve_stat();

  aipHHMsg  msg_starting_solve     (this, HH_Starting_Solve);
  aipHHMsg  msg_solve_has_ended    (this, HH_Solve_Has_Ended);
  aipHHMsg  msg_starting_try       (this, HH_Starting_New_Try);
  aipHHMsg  msg_try_has_ended      (this, HH_Try_Has_Ended);
  aipHHMsg  msg_this_is_bsf        (this, HH_This_Is_Best_So_Far);
  aipHHMsg  msg_no_recent_bsf      (this, HH_No_Recent_Best_So_Far);
  aipHHMsg  msg_no_recent_improve  (this, HH_No_Recent_Improve);
  aipHHMsg  msg_no_recent_change   (this, HH_No_Recent_Change);

  take_msg(&msg_starting_solve);

  ss->m_g_cmp_cur = ss->m_g_cmp_bsf = ss->m_g_usr_bsf = aipForbidden;

  for (ss->m_i_try=0; ss->m_i_trym_i_try++) {

    ss->m_g_cmp_prev = ss->m_g_cmp_cur;
    ss->m_g_cmp_cur = aipNeutral;

    ss->m_num_decisions_prev = ss->m_num_decisions_made;
    ss->m_num_decisions_made = 0;
    ss->m_failed_decision = 0;
    ss->m_worst_decision = 0;
    ss->m_worst_decision_g_cmp = aipForbidden;

    take_msg(&msg_starting_try);

    try_to_find_solution();                   // try

    take_msg(&msg_try_has_ended);

    if (ss->try_is_new_best()) {
      ss->m_g_cmp_bsf = ss->m_g_cmp_cur;
      ss->m_g_usr_bsf = ss->m_g_usr_cur;
      ss->m_num_tries_to_best = ss->m_i_try + 1;
      take_msg(&msg_this_is_bsf);
      ss->m_tries_since_bsf_count = 0;
    } else {
      ss->m_tries_since_bsf_count++;
      if (ss->m_tries_since_bsf_count > many_tries_since_bsf()) {
        take_msg(&msg_no_recent_bsf);
        ss->m_tries_since_bsf_count = 0;
        ss->m_tries_since_change_count = 0;    // reset this count
        ss->m_tries_since_improve_count = 0;   // reset this count
        ss->m_is_many_since_new_best = 1;
      }
    }

    if (ss->try_is_improvement()) {
      ss->m_tries_since_improve_count = 0;
    } else {
      ss->m_tries_since_improve_count++;
      if (ss->m_tries_since_improve_count > 
                                     many_tries_since_improve()) {
        take_msg(&msg_no_recent_improve);
        ss->m_tries_since_improve_count = 0;
        ss->m_tries_since_change_count = 0;  // reset this count
        ss->m_tries_since_bsf_count = 0;     // reset this count
        ss->m_is_many_since_improve = 1;
      }
    }

    if (ss->try_is_a_change() || !ss->try_is_a_success()) {
      ss->m_tries_since_change_count = 0;
    } else {
      ss->m_tries_since_change_count++;
      if (ss->m_tries_since_change_count > 
                                     many_tries_since_change()) {
        take_msg(&msg_no_recent_change);
        ss->m_tries_since_change_count = 0;  // reset only this count
        ss->m_is_many_since_change = 1;
      }
    }
    if (should_log_tries()) log_this_try();

  }                                                   // try loop end

  take_msg(&msg_solve_has_ended);

  return ss->m_g_usr_bsf;    // best-so-far: goodness or miles, etc.

}

//----------------------------------------------------------------------
//  try_to_find_solution  -  try to find a solution

void aipHHProblem::try_to_find_solution () {

  aipHHSolveStat * ss = solve_stat();

  ss->m_g_usr_cur = aipNeutral;    // goodness or dollars or something

  ss->m_is_too_bad = 0;
  ss->m_is_many_since_new_best = 0;
  ss->m_is_many_since_improve = 0;
  ss->m_is_many_since_change = 0;

  aipHHDecision *decision;

  while ( 1 ) {

    decision = next_decision();
    if (!decision) break;

    aipHHOption *opt = decision->hh_decide();

    note_decide (decision, opt);

    if (!opt) {
      ss->m_failed_decision = decision;
      break;
    }

    ss->m_g_cmp_cur += opt->g_opt_cmp();
    ss->m_g_usr_cur += opt->g_opt_usr();
    if ( ss->m_g_cmp_cur.is_forbidden() ) break;
    if ( ss->m_g_cmp_cur < too_bad_to_go_on() ) {
      ss->m_is_too_bad = 1;
      break;
    }
    ss->m_num_decisions_made++;
    if ( !opt->g_opt_cmp().is_forbidden() &&
          opt->g_opt_cmp() < ss->m_worst_decision_g_cmp ) {
      ss->m_worst_decision = decision;
      ss->m_worst_decision_g_cmp = opt->g_opt_cmp();
    }
    if ( solution_is_complete() ) break;
  }   // end of loop through decisions

  if (ss->m_problem && ss->m_problem->worst_is_extra_bad() &&
      !ss->m_worst_decision_g_cmp.is_forbidden() ) {
    ss->m_g_cmp_cur += (ss->m_worst_decision_g_cmp +
                        ss->m_worst_decision_g_cmp);
  }

  if ( solution_is_complete() ) {
    ss->m_solution_is_complete = 1;
    ss->m_solution_is_new_best = ss->m_g_cmp_cur > ss->m_g_cmp_bsf;
  } else {
    ss->m_solution_is_complete = 0;
    ss->m_solution_is_new_best = 0;
    ss->m_g_cmp_cur = ss->m_g_usr_cur = aipForbidden;
  }

  ss->m_try_result = 0;
  if (ss->try_is_new_best()) {
    ss->m_try_result = HH_Try_Is_New_Best;
  } else if (ss->try_is_a_best()) {
    ss->m_try_result = HH_Try_Is_A_Best;
  } else if (ss->try_is_improvement()) {
    ss->m_try_result = HH_Try_Is_Improved;
  } else if (!ss->try_is_a_success()) {
    ss->m_try_result = HH_Try_Has_Failed;
  } else if (ss->try_is_a_change()) {
    ss->m_try_result = HH_Try_Is_Changed;
  } else {
    ss->m_try_result = HH_Try_Is_Complete;
  }
}

//----------------------------------------------------------------------
//  next_decision  -  the next decision to be decided (or zero)
//
//  Return zero if there are no unmade decisions with hope better
//  than aipForbidden.  Otherwise, choose the unmade decision with 
//  the highest importance.

aipHHDecision * aipHHProblem::next_decision() {

  aipHHDecision *dnext = 0;
  long imp_max = -9999999;

  aipHHDecisionItr itr = hh_decision_iterator();

  for ( aipHHDecision *d = itr.first(); d; d = itr.next() ) {
    if ( d && !(d->is_decided()) ) {
      if (!dnext || dnext->imp() > imp_max) {
        dnext = d;
        imp_max = dnext->imp();
      }
    }
  }

  return dnext;

}

//----------------------------------------------------------------------
//  solution_is_complete  -  true if solution is complete
//
//  By default, a solution is complete when all decisions have
//  been decided; subclasses may override this.

int aipHHProblem::solution_is_complete () const {

  int done = m_solve_stat->m_num_decisions_made >= num_decisions();

  return done;

}

//======================================================================
//  aipHHDecision
//
//----------------------------------------------------------------------
//  Constructor

aipHHDecision::aipHHDecision (long the_num_to_decide) {

  m_hope = new aipHHHope;

  m_opt_cur = m_opt_bsf = 0;

  m_num_to_decide = the_num_to_decide;
  m_num_decided = 0;
  m_chsn_opts_cur = m_chsn_opts_bsf = 0;

}

//----------------------------------------------------------------------
//  Destructor

aipHHDecision::~aipHHDecision () {

  if (m_hope) delete m_hope;

  if (m_chsn_opts_cur)  delete m_chsn_opts_cur;
  if (m_chsn_opts_bsf)  delete m_chsn_opts_bsf;

}

//----------------------------------------------------------------------
//  add_hh_option

void aipHHDecision::add_hh_option(aipHHOption *x) { 

  aipDecision::add_option(x); 

  if ( num_to_decide() > 1 ) x->set_is_shared();

}

//----------------------------------------------------------------------
//  take_msg  - take and react to a message

void aipHHDecision::take_msg (aipMsg *mm) {

  aipHHMsg *m = (aipHHMsg *)mm;

  if (m->typ() == HH_Starting_New_Try) {

    m_opt_cur = 0;
    m_num_decided = 0;
    if (m_chsn_opts_cur) delete m_chsn_opts_cur;
    m_chsn_opts_cur = new aipPandemonium;

  } else if (m->typ() == HH_Try_Has_Ended) {

    int dcsn_in_cur_solution = opt_cur() ? 1 : 0;
    m->set_is_in_cur_solution(dcsn_in_cur_solution);

    aipHHSolveStat *ss = m->prob() ? m->prob()->solve_stat() : 0;
    int is_the_failure = ss ? ss->failed_dcsn() == this : 0;
    m->set_is_the_failure(is_the_failure);

  } else if (m->typ() == HH_This_Is_Best_So_Far) {

    if (is_decided()) {           // ie. dcsn is in the solution
      m_opt_bsf = m_opt_cur;
        if (m_chsn_opts_bsf) delete m_chsn_opts_bsf;
        m_chsn_opts_bsf = m_chsn_opts_cur;
        m_chsn_opts_cur = 0;  // so bsf does not get deleted
    }

  }

  m_importance.take_msg(mm);
  if (m_hope) m_hope->take_msg(mm);

  aipDecision::take_msg(mm);  // decision gives message to options

}

//----------------------------------------------------------------------
//  hh_option_iterator

aipHHOptionItr aipHHDecision::hh_option_iterator () const { 

  aipHHOptionItr i(option_pandemonium());

  return i;

}


//----------------------------------------------------------------------
//  current-option iterator

aipHHOptionItr aipHHDecision::cur_opt_iterator () const {

  aipHHOptionItr i(m_chsn_opts_cur);

  return i;

}

//----------------------------------------------------------------------
//  best-so-far-option iterator

aipHHOptionItr aipHHDecision::bsf_opt_iterator () const {

  aipHHOptionItr i(m_chsn_opts_bsf);

  return i;

}

//----------------------------------------------------------------------
//  g_hope

aipG aipHHDecision::g_hope () const {

  return (m_hope ? m_hope->g() : aipNeutral);

}

//----------------------------------------------------------------------
//  is_decided

int aipHHDecision::is_decided () const {

  return (m_num_decided >= m_num_to_decide) ? 1 : 0;

}

//----------------------------------------------------------------------
//  Return the number of unchosen, unforbidden options

long aipHHDecision::num_opt_remaining () const {

  long num = 0;

  aipHHOptionItr itr = hh_option_iterator();
  for ( aipHHOption * opt = itr.first(); opt; opt = itr.next() ) {
    if ( !(opt->is_chosen()) && 
         !(opt->g_opt_cmp().is_forbidden()) ) num++;
  }

  return num;

}


//----------------------------------------------------------------------
//  decide

aipHHOption * aipHHDecision::hh_decide() {

  m_opt_cur = (aipHHOption*)decide();

  if (m_opt_cur) {
    if (m_chsn_opts_cur) m_chsn_opts_cur->add(m_opt_cur);
    m_num_decided++;
  }

  return m_opt_cur;

}


//======================================================================
//  aipHHOption  -  an option with an aipHHHope emotion
//
//----------------------------------------------------------------------
//  Constructor

aipHHOption::aipHHOption (aipG g_user_const) {

  m_g_user_constant = m_g_constant = g_user_const;

  m_bsf_chooser = 0;

  m_hope = new aipHHHope;

}

//----------------------------------------------------------------------
//  Destructor

aipHHOption::~aipHHOption () {

  if (m_hope) delete m_hope;

}


//----------------------------------------------------------------------
//  take_msg  - take and react to a message

void aipHHOption::take_msg (aipMsg *mm) {

  aipHHMsg *m = (aipHHMsg *)mm;

  int opt_in_cur_solution  = hh_opt_chooser() != 0;

  m->set_is_in_cur_solution(opt_in_cur_solution);
  if (m_hope) m_hope->take_msg(mm);

  if (m->typ() == HH_Starting_New_Try) {
    reset_chooser();
  } else if (m->typ() == HH_This_Is_Best_So_Far) {
    m_bsf_chooser = hh_opt_chooser();
  }

  aipOption::take_msg(mm);

}

//----------------------------------------------------------------------
//  g_hope

aipG aipHHOption::g_hope () const {

  return (m_hope ? m_hope->g() : aipNeutral);

}

//----------------------------------------------------------------------
//  g_opt

aipG aipHHOption::g_opt() {

  return (g_constant() + g_hope() + random_num(-2,2));

}

//======================================================================
//  aipHHDecisionItr  -  aipHHDecision iterator
//
//    All function bodies are in the header file
//
//======================================================================
//  aipHHOptionItr  -  aipHHOption iterator
//
//    All function bodies are in the header file
//
//======================================================================
//  aipHHSolveStat  -  solve() status and statistics
//
//----------------------------------------------------------------------
//  Constructor

aipHHSolveStat::aipHHSolveStat (aipHHProblem *p) { 

  m_problem = p; 
  if (!p) return;

  m_should_log_tries = p->should_log_tries();

  m_g_cmp_cur = m_g_cmp_bsf = m_g_cmp_prev = aipForbidden;
  m_g_usr_cur = m_g_usr_bsf = aipForbidden;

  m_i_try = 0;

    // The following assignments are not necessary, but just
    // in case someone looks at this before calling solve()...
    
  m_solution_is_complete = m_solution_is_new_best = 0;
  m_num_decisions_made   = m_num_decisions_prev   = 0;
  m_num_tries_to_best    = 0;

  m_failed_decision = 0;

  m_worst_decision = 0;
  m_worst_decision_g_cmp = aipForbidden;

  m_tries_since_bsf_count     = 0;
  m_tries_since_improve_count = 0;
  m_tries_since_change_count  = 0;

  m_is_too_bad = 0;
  m_is_many_since_new_best = 0;
  m_is_many_since_improve  = m_is_many_since_change = 0;

}

//======================================================================
//  aipHHMsg  -  High-Hope problem-solving message
//
//----------------------------------------------------------------------
//  Constructor

aipHHMsg::aipHHMsg (aipHHProblem *p, short typ)
                                          : aipMsg(typ) {

  m_prob = p;

  m_is_in_cur_solution = m_is_the_failure = 0;

}

//======================================================================
//                           License
//
//   Permission is hereby granted, free of charge, to any 
//   person obtaining a copy of this software and associated 
//   documentation files (the "Software"), to deal in the Software 
//   without restriction, including without limitation the rights 
//   to use, copy, modify, merge, publish, distribute, sublicense, 
//   and/or sell copies of the Software, and to permit persons to 
//   whom the Software is furnished to do so, subject to the 
//   following conditions:
//
//   The copyright notice and this license shall be included in all 
//   copies or substantial portions of the Software.
//
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
//   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
//   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
//   NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
//   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
//   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
//   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
//   OTHER DEALINGS IN THE SOFTWARE.
//
//
//**********************************************************************