/*
 * Title: geneticproc.c
 * Author: Jed Marti KI7NNP
 * Description: Genetic optimization control but with multiple processes.
 * Revision History: (Created Fri Jan 27 17:15:18 2023)
 *   Mon Jan 30 20:35:43 2023 - JBM Version 4, better process use. One set per process.
 */


#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#include "equations.h"
#include "indequ.h"

// The symbol table for constants.
extern symtab S[];

// The list of measured inductors.
extern anInductor *theInductors;

// Process control block (see process.c).
extern procDetail P[];

// Library size (see library.c).
extern uint64_t libCnt;
extern uint64_t rejects;

// This is the current collected list. We always try to keep at least 4 (S[s_save]) values in
// it at any time.
result *collected;

// Best result.
double   bestGoodness;

// Tested count. 
uint64_t tested = 0;
uint64_t lastTested;

// Last generation that did something. Used to tell when to quit.
int32_t gen;
int32_t lastgen;

// Timing.
extern double accumSeconds;
extern uint32_t accumCnt;



// The top 5 values collected as macros on a list.
#define c1 collected
#define c2 ((result*) (collected->next))
#define c3 ((result*) (((result*) (collected->next))->next))
#define c4 ((result*) (((result*) (((result*) (collected->next))->next))->next))
#define c5 (((result*) c4)->next)

static void runOne(result *r)
/*     Run a single major test on another process.
 */
{
  int32_t i;
  FILE    *tfh;
  result  *l;
  result  *rval;
  result  *prev;
  int32_t procsel;
  int32_t rv;
  int32_t j;
  uint8_t chr;
  
  // First check to see if any are done. Service the results and update all their status.
 busywait:
  for (i = 0; i < S[s_processes].value.iv; ++i)
  {
    if (P[i].status == busy && hasFinished(i))
    {
      // Time accumulated by this process.
      accumSeconds += timeus() - P[i].startTime;
      ++accumCnt;;
      
    tryagain:
      tfh = fopen(P[i].fname, "rb");
      if (tfh == NULL) printf("%s fails because %s\n", P[i].fname, strerror(errno));
      ASSERT(tfh != NULL);
      rval = readResult(tfh);
      fclose(tfh);
      //      ASSERT(rval != NULL);
      if (rval == NULL)
        { printf("*****rval for %s is NULL\n", P[i].fname); usleep(1000); goto tryagain; }

      // Some general tests.
      rval->next = NULL;
      ++tested;

      // Add this new value to the list in its proper place.
      collected = sortgen(collected, rval);

      // Delete the output file from the process, mark it idle.
      rv = system(P[i].rmfname);
      P[i].status = idle;

      // Remove the one at the end - it's now worthless. This could be done better but the
      // main process isn't doing much. If the result didn't get added, then l should be NULL.
      // Hopefully this will speed up convergence.
      prev = NULL;
      for (l = collected, j = 0; j < S[s_save].value.iv; ++j, l = l->next) prev = l;
      if (l != NULL)
      {
        freeResult(l);
        ASSERT(prev != NULL)
        prev->next = NULL;
      }

      // New goodness.
      if (c1->goodness < bestGoodness)
      {
        
        printf("%lu, %g, %g, %g, %g\n", tested, c1->goodness, c2->goodness, c3->goodness,
               c4->goodness);
        bestGoodness = c1->goodness;
        lastTested = tested;
        lastgen = gen;
      }
    }
  }

  // Find the first idle process and send it there.
  procsel = -1;
  for (i = 0; i < S[s_processes].value.iv; ++i) if (P[i].status == idle) procsel = i;

  // No processes idle. Wait for a while second and try again.
  if (procsel == -1)
  {
    // If any keyboard input, then show the results.
    if (chr = kbhit())
    {
      if (chr == 3) exit(0);
      printf("%s: time/process: %g us\n", EQUATION, 1000000.0 * accumSeconds / (double) accumCnt);
      accumSeconds = 0.0;
      accumCnt = 0;
      showResult(stdout, c1);
      dumpRangeTable(stdout, "range table\n");
      if (chr == 27)
      {
	printf("Quitting early\n");
	showInductorResults(c1);
	exit(-1);
      }
    }
    usleep(1000);
    goto busywait;
  }

  // Write a file to temporary that will cause the process to process.
  tfh = fopen(P[procsel].ifname, "wb");
  ASSERT(tfh != NULL)
  writeResult(r, tfh);
  fclose(tfh);
  r->next = NULL;
  P[procsel].status = busy;
  P[procsel].startTime = timeus();
}

    

void optimizeProc(void)
/*     The guts. Generate a random starting set, then run generations until we're told to stop.
 *   Liberal use of the macros above.
 * Parameters: none
 * External References:
 *   collected: The current sorted list of best results.
 */
{
  result  *r;
  result  *l;
  int32_t genc;
  int32_t i;
  int32_t j;
  int32_t cnt;
  uint32_t flag;

  // This is the current best list.
  collected = NULL;

  // Timing.
  accumSeconds = 0.0;
  accumCnt = 0;

  // See if we have a starting value.
  printf("LAST %d, startup %g\n", LAST, S[LAST].value.dv);
  if (S[LAST].value.dv != 1.0E208)
  {
    printf("Starting with best result\n");
    r = newResult();
#define X(nme) r->nme = S[s_best##nme].value.dv;
    VARS
#undef X
    evalInductors(theInductors, r);
    collected = sortgen(collected, r);
    printf("goodness = %g\n", c1->goodness);
  }

  // First make a bunch of random values as a starting value. These are sorted in order best
  // to worst.
  genc = S[s_save].value.iv;
  if (isNoisy(1)) printf("%s (%s:%d) Generating %d random\n", __FILE__, __func__, __LINE__, genc);
  for (i = 0; i <= genc; ++i)
  {
    // Keep trying until we get a good value.
    for (r = genRandResult(); doneAlready(r); r = genRandResult()) freeResult(r);
    evalInductors(theInductors, r);
    collected = sortgen(collected, r);
  }

  printf("Generated %d random values %g %g %g %g\n", genc, c1->goodness, c2->goodness, c3->goodness,
         c4->goodness);
  bestGoodness = c1->goodness;
  lastTested = 0;

  // Run for this many generations.
  for (gen = lastgen = 0;  gen < S[s_maxgens].value.iv; ++gen)
  {

    // The total number of results to make for this generation.
    // Breed best 4 against each other.
    for (j = 0; j < 3; ++j)
    {
      runOne(breed(c1, c2, 0.30));
      runOne(breed(c1, c3, 0.28));
      runOne(breed(c1, c4, 0.26));
      runOne(breed(c2, c3, 0.24));
      runOne(breed(c2, c4, 0.22));
      runOne(breed(c3, c4, 0.20));
      if (S[s_save].value.iv > 4)
      {
        runOne(breed(c1, c5, 0.24));
        runOne(breed(c2, c5, 0.26));
        runOne(breed(c3, c5, 0.28));
        runOne(breed(c4, c5, 0.30));
      }
      if (S[s_save].value.iv > 6)
      {
        for (j = 0, l = collected; j < S[s_save].value.iv - 1 && l; ++j, l = l->next);
          if (l)
          {
            runOne(breed(c1, l, 0.2));
            runOne(breed(c2, l, 0.18));
            runOne(breed(c3, l, 0.16));
          }
      }
    }
    

    // A few with random values.
    runOne(mutate(c1, 0.25));
    runOne(mutate(c2, 0.35));
    runOne(mutate(c3, 0.44));
    runOne(mutate(c4, 0.55));

    runOne(mutate(c1, 0.25));
    runOne(mutate(c2, 0.3));
    runOne(mutate(c3, 0.35));
    runOne(mutate(c4, 0.4));

    runOne(mutate(c1, 0.25));
    runOne(mutate(c2, 0.35));
    runOne(mutate(c3, 0.25));
    runOne(mutate(c4, 0.2));

    if (S[s_save].value.iv > 4)
    {
      runOne(mutate(c5, 0.5));
      runOne(mutate(c5, 0.4));
      runOne(mutate(c5, 0.3));
    }

    // Now move parameters in directions instead of just random.
    for (j = 0; j < 5; ++j)
    {
      runOne(goodDir(c1, 0.1));
      runOne(goodDir(c2, 0.2));
      runOne(goodDir(c3, 0.3));
      runOne(goodDir(c4, 0.4));
    }
	   
    
    // And now some totally random values. Crank up how many if we're getting stuck.
    cnt = 4;
    if ((tested - lastTested) > 10) cnt += 2;
    if ((tested - lastTested) > 50) cnt += 2;
    if ((tested - lastTested) > 100) cnt += 2;
    if ((tested - lastTested) > 150) cnt += 3;
    for (i = 0; i < S[s_tries].value.iv; ++i)
    {
    lg:
      l = genRandResult();
      flag = 0;
      for (j = 0; j <= LAST; ++j) if (uniform() < S[s_randminor].value.dv) flag |= 1 << j;
      l->flags = j;
      if (doneAlready(l)) { freeResult(l); goto lg; }
      runOne(l);
    }
    printf("gen = %d done already %ld, rejected %ld\n", gen, libCnt, rejects);
    if ((gen % 100) == 0) saveTempResults(c1);

    // See if we need to extend ranges.
    if (gen != 0 && (gen % S[s_raiselower].value.iv) == 0) fixOneRange(collected);

    // See if we've gone too long.
    if (S[s_quitat].value.iv >= 0 && (gen - lastgen) > S[s_quitat].value.iv)
    {
      printf("Gone to long without results\n");
      break;
    }
  }

  showInductorResults(c1);
}

 

// EOF
