Complete.Org: Mailing Lists: Archives: freeciv-dev: November 2003:
[Freeciv-Dev] (PR#7007) CM speedup patch: pruning on primary stat produc
Home

[Freeciv-Dev] (PR#7007) CM speedup patch: pruning on primary stat produc

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: undisclosed-recipients: ;
Subject: [Freeciv-Dev] (PR#7007) CM speedup patch: pruning on primary stat production
From: "Benoit Hudson" <bh@xxxxxxxxxxxxxxxxxxx>
Date: Sun, 30 Nov 2003 15:40:14 -0800
Reply-to: rt@xxxxxxxxxxx

<URL: http://rt.freeciv.org/Ticket/Display.html?id=7007 >

This patch provides pruning based on primary stats not being
statisfiable.

What it adds:
- enum cm_answer = { YES,NO, MAYBE}
        Useful for questions like "can I satisfy this constraint?"
        I use it in this patch, and I'll use it in other patches.

- optimize_final runs in backwards order (more fields to less);
  If no combination using i fields produces enough primary stats, then
  we know that no combination using i-1 fields works so we can just quit
  the loop now.  I take care of the problem of disorder correctly (far
  as I know; see the comments in the new function
  result_has_sufficient_primary for details).

- more comments in optimize_final.

Profiling shows that we thusly ignore slightly less than 10% of the
combinations we would otherwise check, reducing the number of calls to
real_fill_out_result (the most costly per-call function) by over 10%.
This is in an all-AI game to 500 AD ; humans building big cities should
see larger savings.

        -- Benoît

Index: cm.c
===================================================================
RCS file: /home/freeciv/CVS/freeciv/common/aicore/cm.c,v
retrieving revision 1.18
diff -b -c -3 -p -u -r1.18 cm.c
--- cm.c        2003/11/22 16:32:01     1.18
+++ cm.c        2003/11/30 23:14:36
@@ -152,6 +152,42 @@
 #define MAX_COMBINATIONS                               150

 /*
+ * Useful in asking cheap questions to avoid asking an expensive one:
+ * YES/NO are definitive, MAYBE means we have to ask the expensive
+ * question.
+ * Note: none of these is zero.  This means we can't usefully use
+ * operator ! on them as we can on a bool.  This is by design: what
+ * does !MAYBE mean?
+ */
+enum cm_answer { YES=1, NO=2, MAYBE=3 };
+
+/* Return the max of a1 and a2, according to NO < MAYBE < YES.
+   This roughly corresponds to a boolean OR. */
+static enum cm_answer answer_or(enum cm_answer a1, enum cm_answer a2) {
+  if(a1==YES || a2==YES) {
+    return YES;
+  }
+  if(a1==MAYBE || a2==MAYBE) {
+    return MAYBE;
+  }
+  return NO;
+}
+
+/* Return the max of a1 and a2, according to YES < MAYBE < NO.
+   This roughly corresponds to a boolean AND. */
+#if 0
+static enum cm_answer answer_and(enum cm_answer a1, enum cm_answer a2) {
+  if(a1==NO || a2==NO) {
+    return NO;
+  }
+  if(a1==MAYBE || a2==MAYBE) {
+    return MAYBE;
+  }
+  return YES;
+}
+#endif
+
+/*
  * Maps (trade, taxmen) -> (gold_production, gold_surplus)
  * Maps (trade, entertainers) -> (luxury_production, luxury_surplus)
  * Maps (trade, scientists) -> (science_production, science_surplus)
@@ -317,6 +353,54 @@ static bool can_use_specialist(struct ci
 }

 /****************************************************************************
+  Like is_valid but only looks at primary stats (food/shields/trade).
+  Mostly ignores disorder/happy, totally ignores secondary stats.
+  We only mostly ignore disorder in that a city in disorder may
+  have sufficient primary production, but the surplus may be capped to zero,
+  which may be insufficient.  In that case we return MAYBE.
+*****************************************************************************/
+static enum cm_answer result_has_sufficient_primary(
+        const struct cm_parameter *const parameter,
+        const struct cm_result *const result)
+{
+  enum cm_stat stat;
+  for(stat=0; stat<NUM_PRIMARY_STATS; ++stat) {
+    if(result->surplus[stat] < parameter->minimal_surplus[stat]) {
+      if(!result->disorder) {
+        return NO;
+      }
+      else {
+        /* city is in disorder: food/shields capped to zero. */
+        if(result->surplus[stat]!=0) {
+          /* this is the true surplus; if it's not sufficient we're hosed. */
+          return NO;
+        } else {
+          /* surplus is zero; so it could actually be anything positive */
+          return MAYBE;
+        }
+      }
+    }
+  }
+  return YES;
+}
+
+/****************************************************************************
+  Return TRUE iff the city is sufficiently happy.
+*****************************************************************************/
+static bool result_has_sufficient_happiness(
+        const struct cm_parameter *const parameter,
+        const struct cm_result *const result)
+{
+  if (!parameter->allow_disorder && result->disorder) {
+    return FALSE;
+  }
+  if (parameter->require_happy && !result->happy) {
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/****************************************************************************
  Returns TRUE iff is the result has the required surplus and the city
  isn't in disorder and the city is happy if this is required.
 *****************************************************************************/
@@ -331,10 +415,7 @@ static bool is_valid_result(const struct
     return FALSE;
   }

-  if (!parameter->allow_disorder && result->disorder) {
-    return FALSE;
-  }
-  if (parameter->require_happy && !result->happy) {
+  if(result_has_sufficient_happiness(parameter, result) == FALSE) {
     return FALSE;
   }

@@ -1327,6 +1408,7 @@ static void optimize_final(struct city *
 {
   int fields_used, i;
   int results_used = 0, not_enough_primary = 0, not_enough_secondary = 0;
+  int not_enough_happiness = 0;
   /* Just for the compiler. Guarded by best_result->found_a_valid */
   int best_major_fitness = 0, best_minor_fitness = 0;

@@ -1334,10 +1416,13 @@ static void optimize_final(struct city *

   best_result->found_a_valid = FALSE;

-  /* Loop over all combinations */
-  for (fields_used = 0;
-       fields_used <= MIN(cache3.fields_available_total, pcity->size);
-       fields_used++) {
+  /* Loop over all combinations, backwards so we can prune. */
+  for (fields_used = MIN(cache3.fields_available_total, pcity->size);
+       fields_used >= 0;
+       fields_used--) {
+    /* if no combo had enough primary with i fields, then no combo
+       with i-1 fields can possibly have enough primary stats. */
+    enum cm_answer one_combo_had_enough_primary = NO;
     freelog(OPTIMIZE_FINAL_LOG_LEVEL,
            "there are %d combinations which use %d fields",
            count_valid_combinations(fields_used), fields_used);
@@ -1346,6 +1431,7 @@ static void optimize_final(struct city *
          &cache3.results[fields_used].combinations[i];
       int stat, major_fitness, minor_fitness;
       struct cm_result result;
+      enum cm_answer sufficient;

       if (!current->is_valid) {
        continue;
@@ -1371,19 +1457,44 @@ static void optimize_final(struct city *
       }

       /*
-       * the secondary stats aren't calculated yet but we want to use
-       * is_valid_result()
+       * Check whether we have enough primary.
+       * If NO, we don't, so skip to the next combo.
+       * If MAYBE, we might if we could make the city content, but we
+       *        already have all elvises so we can't.
+       * If YES, we do, so keep going.
+       *
+       * In any case, update whether some combo had enough primary.
+       * It's in updating this that MAYBE is useful.
        */
-      result.surplus[GOLD] = parameter->minimal_surplus[GOLD];
-      result.surplus[LUXURY] = parameter->minimal_surplus[LUXURY];
-      result.surplus[SCIENCE] = parameter->minimal_surplus[SCIENCE];
+      sufficient = result_has_sufficient_primary(parameter, &result);

-      if (!is_valid_result(parameter, &result)) {
+      one_combo_had_enough_primary = answer_or(sufficient,
+          one_combo_had_enough_primary);
+
+      switch(sufficient) {
+        case NO:
+        case MAYBE:
        not_enough_primary++;
        freelog(OPTIMIZE_FINAL_LOG_LEVEL2, "    not enough primary");
+          continue; /* the for loop. */
+        case YES:
+          break; /* the switch statement. */
+      }
+
+      /*
+       * Check for sufficient happiness.  If we're not
+       * sufficiently happy with all elvises we're hosed.
+       */
+      if(result_has_sufficient_happiness(parameter, &result)==FALSE) {
+       freelog(OPTIMIZE_FINAL_LOG_LEVEL2, "    not enough happiness");
+        not_enough_happiness++;
        continue;
       }

+
+      /*
+       * Find how to best set up specialists (rather than all elvises).
+       */
       find_best_specialist_arrangement(pcity, parameter, current, &result,
                                       &major_fitness, &minor_fitness);
       if (!result.found_a_valid) {
@@ -1406,15 +1517,26 @@ static void optimize_final(struct city *
       } else {
        freelog(OPTIMIZE_FINAL_LOG_LEVEL2,
                "    isn't better than the best result");
-      }
     }
+    } /* end loop over combinations, for a fixed number of fields. */
+
+    /* If no combination had enough primary, don't bother trying with
+       fewer workers.  A MAYBE result means fewer workers might
+       be OK as long as we can eliminate disorder. */
+    if(one_combo_had_enough_primary==NO) {
+      break;
   }
+  } /* end loop over number of fields. */

   freelog(OPTIMIZE_FINAL_LOG_LEVEL,
          "%d combinations don't have the required minimal primary surplus",
          not_enough_primary);

   freelog(OPTIMIZE_FINAL_LOG_LEVEL,
+         "%d combinations don't have the required minimal happiness",
+         not_enough_happiness);
+
+  freelog(OPTIMIZE_FINAL_LOG_LEVEL,
          "%d combinations don't have the required minimal secondary surplus",
          not_enough_secondary);

[Prev in Thread] Current Thread [Next in Thread]
  • [Freeciv-Dev] (PR#7007) CM speedup patch: pruning on primary stat production, Benoit Hudson <=