file jswarm/particle_swarm.cpp

[No description available] More…

Namespaces

Name
Gambit
TODO: see if we can use this one:
Gambit::jswarm_1_0_0

Detailed Description

Author: Pat Scott (pat.scott@uq.edu.au)

Date: 2019 Oct

j-Swarm: particle swarm optimisation with meta-optimisation a la jDE.

Implementations of particle_swarm class


Authors (add name and date if you modify):


Source code

//   GAMBIT: Global and Modular BSM Inference Tool
//   *********************************************
///  \file
///
///  j-Swarm: particle swarm optimisation with
///  meta-optimisation a la jDE.
///
///  Implementations of particle_swarm class
///
///  *********************************************
///
///  Authors (add name and date if you modify):
///
///  \author Pat Scott
///          (pat.scott@uq.edu.au)
///  \date 2019 Oct
///
///  *********************************************

#include <limits>
#include <fstream>
#include <algorithm>
#include <iterator>

#include "gambit/Utils/mpiwrapper.hpp"
#include "gambit/ScannerBit/scanners/jswarm/jswarm.hpp"

namespace Gambit
{

  namespace jswarm_1_0_0
  {

    /// Constructor
    particle_swarm::particle_swarm()
    : rank(0)
    , nprocs(1)
    , global_best_value(-std::numeric_limits<double>::max())
    , mean_lnlike(-std::numeric_limits<double>::max())
    , sfim(1.0)
    , nPar_total(0)
    , phi1_index(0)
    , phi2_index(0)
    , omega_index(0)
    , path("")
    , nPar(0)
    , nDerived(0)
    , nDiscrete(0)
    , maxgen(100)
    , NP(100)
    , bndry(1)
    , convsteps(10)
    , savecount(10)
    , init_pop_strategy(1)
    , max_ini_attempts(1)
    , verbose(0)
    , seed(-1)
    , fcall(0)
    , omega(1)
    , phi1(1.5)
    , phi2(1.5)
    , convthresh(1e-2)
    , min_acceptable_value(-std::numeric_limits<double>::max())
    , adapt_phi(false)
    , adapt_omega(false)
    , init_stationary(false)
    , resume(false)
    , save_particles_natively(false)
    {}

    /// Initialise the swarm
    void particle_swarm::init()
    {
      // Read the saved settings if resuming and forbidding new settings.
      if (resume and not allow_new_settings) read_settings(true);

      // Make sure that there are actually positive numbers of particles and parameters
      if (NP <= 0 or nPar <= 0) Scanner::scan_error().raise(LOCAL_INFO, "You must set NP and nPar positive before initialising a particle swarm!");

      // Add adaptive parameters (if any) to the total size of the parameter vector
      nPar_total = nPar;
      if (adapt_phi)
      {
        nPar_total += 2;
        phi1_index = nPar_total - 2;
        phi2_index = nPar_total - 1;
      }
      if (adapt_omega)
      {
        nPar_total += 1;
        omega_index = nPar_total - 1;
      }

      // Size the vectors to hold the upper and lower parameter boundaries
      lowerbounds.resize(nPar_total);
      upperbounds.resize(nPar_total);

      // Set the limits for the parameters of the algorithm to be determined adaptively
      if (adapt_phi)
      {
        lowerbounds.at(phi1_index) = lowerbounds.at(phi2_index) = 1.5;
        upperbounds.at(phi1_index) = upperbounds.at(phi2_index) = 3.0;
      }
      if (adapt_omega)
      {
        lowerbounds.at(omega_index) = 0.0;
        upperbounds.at(omega_index) = 1.0;
      }

      #ifdef WITH_MPI
        // Get the MPI process rank and total number of MPI processes
        rank = GMPI::Comm().Get_rank();

        // Determine the total number of MPI processes, and make sure NP is a multiple of it
        nprocs = GMPI::Comm().Get_size();
        if (NP%nprocs != 0)
        {
          int new_NP = (NP/nprocs + 1) * nprocs;
          if (rank == 0 and verbose > 0)
          {
            cout << "j-Swarm: WARNING The requested NP (" << NP << ") is not a multiple of the number of MPI processes (" << nprocs << ")." << endl
                 << "         Will instead use NP = " << new_NP <<  "." << endl;
          }
          NP = new_NP;
          if (rank == 0) save_settings();
        }
      #endif

      // Work out the local population size
      NP_per_rank = NP/nprocs;

      // Seed the random number generator
      input_seed = seed;
      if (seed == -1 and rank == 0) seed = std::random_device()();
      #ifdef WITH_MPI
        GMPI::Comm().Bcast_single(seed, 0);
        seed += rank;
      #endif
      if (rank == 0 and verbose > 2) cout << "j-Swarm:  seeding RNG on rank " << rank << " with " << seed << endl;
      rng.seed(seed);

      // Create the particles
      if (rank == 0) particles_global.resize(NP, particle(nPar_total, lowerbounds, upperbounds, rng));
      particles.resize(NP_per_rank, particle(nPar_total, lowerbounds, upperbounds, rng));

      // Create an array to hold the indices of the discrete parameters
      if (nDiscrete != 0) discrete.resize(nDiscrete);

      if (rank == 0)
      {
        // Initialise the convergence measures
        conv_progress.resize(convsteps);
        for (auto& x : conv_progress) x = 1.0;

        // Done
        if (verbose > 1) cout << "j-Swarm: successfully initialised swarm with NP = " << NP
                                            << ", nPar = " << nPar << ", nDiscrete = " << nDiscrete << endl;
      }
    }

    /// Release the swarm
    void particle_swarm::run()
    {
      if (rank == 0 and verbose > 0) cout << "j-Swarm: beginning run." << endl;
      int gen = 1;

      if (resume)
      {
        // Read the saved settings again if resuming and forbidding new settings (in case the user overwrote them between init and now).
        if (not allow_new_settings) read_settings(false);
        gen = read_generation();
      }
      else
      {
        if (rank == 0 and verbose > 2) cout << "  j-Swarm: initialising first generation of particles." << endl;

        // Initialise the first population
        for (int i = 0; i < NP_per_rank; i++)
        {
          // Extract a reference to the particle we are trying to intialise
          particle& p = particles.at(i);

          // Attempt to initialise the particle's position and velocity
          p.init(init_stationary);
          p.lnlike = likelihood_function(discrete.empty() ? p.x : p.discretised_x(discrete));
          fcall += 1;

          // Check that unrecognised init_pop_strategy hasn't been issued
          if (init_pop_strategy < 1 or init_pop_strategy > 3)
          {
            Scanner::scan_error().raise(LOCAL_INFO, "Unrecognised init_pop_strategy setting for j-Swarm. Please set init_pop_strategy = 1, 2 or 3.");
          }

          // Do nothing more if init_pop_strategy = 1; otherwise do n-shot.
          else if (init_pop_strategy > 1)
          {
            int j;
            // Do n-shot initialisation
            for (j = 1; j < max_ini_attempts and p.lnlike < min_acceptable_value; j++)
            {
              // Try again to initialise the particle's position and velocity
              p.init(init_stationary);
              p.lnlike = likelihood_function(discrete.empty() ? p.x : p.discretised_x(discrete));
              fcall += 1;
            }

            // Throw an error if n-shot failed and using fatal n-shot initialisation
            if (p.lnlike < min_acceptable_value and init_pop_strategy == 3) Scanner::scan_error().raise(LOCAL_INFO,
            "Failed to initialise particle to a valid starting vector (using init_pop_strategy = 3).");

            if (verbose > 2)
            {
              int n = NP_per_rank * rank + i;
              cout << "    j-Swarm: took " << j << " attempts to initialise particle number " << n + 1 << "." << endl;
            }

          }

          // Sort out the personal bests and global best for the new population
          update_best_fits(p);
        }
        // Collect the data from all processes to rank 0
        collect_data();

        if (rank == 0)
        {
          if (converged()) Scanner::scan_error().raise(LOCAL_INFO, "j-Swarm converged immediately! This is a bug, please report it.");
          if (verbose > 1) cout << "  j-Swarm: successfully tested first generation." << endl;

          // Save the run settings and first generation
          save_settings();
          save_generation(gen);
        }
      }

      // Begin the generation loop
      gen += 1;
      for (; gen <= maxgen and sfim > convthresh; gen++)
      {
        if (rank == 0 and verbose > 1) cout << "  j-Swarm: moving on to generation " << gen << "." << endl;

        // Loop over the population of this generation
        for (int i = 0; i < NP_per_rank; i++)
        {

          // Work out which particle number this really is
          int n = NP_per_rank * rank + i;
          if (verbose > 2) cout << "    j-Swarm: working on particle " << n+1 << "." << endl;

          // Get the particle
          particle& p = particles.at(i);

          // Update the particle's position and velocity
          update_particle(p);

          if (verbose > 2) cout << "      j-Swarm: updated velocity and position for particle " << n + 1 << "." << endl;

          // Check if the particle is now outside the prior box, and fix it if so (when bndry = 2 or 3)
          if (implement_boundary_policy(p))
          {
            // Call the likelihood function, being sure to discretise any discrete parameters
            p.lnlike = likelihood_function(discrete.empty() ? p.x : p.discretised_x(discrete));

            // Update the particle's personal best and the global best if necessary
            update_best_fits(p);

            // Increment the number of function calls
            fcall += 1;

            if (verbose > 2) cout << "      j-Swarm: new objective value for particle " << n + 1 << ": " << p.lnlike << endl;
          }
          else
          {
            // Return the worst possible likelihood if the point is outside the prior box and bndry = 1
            p.lnlike = -std::numeric_limits<double>::max();
          }

        }

        // Collect the data from all processes to rank 0
        collect_data();

        // Check for convergence or early shutdown requested by the calling code
        bool complete;
        if (rank == 0) complete = converged();
        #ifdef WITH_MPI
          GMPI::Comm().Bcast_single(complete, 0);
        #endif
        if (complete or Scanner::Plugins::plugin_info.early_shutdown_in_progress())
        {
          if (rank == 0)
          {
            if (verbose > 0)
            {
              if (complete) cout << "  j-Swarm: swarm converged." << endl;
              else cout << "  j-Swarm: quit requested by objective function; saving and exiting..." << endl;
            }
            save_generation(gen);
          }
          break;
        }

        // Save generation
        if (rank == 0 and gen%savecount == 0) save_generation(gen);

      }

      if (rank == 0 and verbose > 0) cout << "j-Swarm: run complete." << endl << endl;

    }

    /// Collect all particles and related data to rank 0
    void particle_swarm::collect_data()
    {
      #ifdef WITH_MPI

        // Determine the size of temporary arrays
        int size = (rank == 0 ? nprocs : 1);

        // Collect the particles from all processes to rank 0
        // This could be sped up using MPI_Gatherv if the array temporaries seem to be causing a bottleneck.
        for (int i = 0; i < NP_per_rank; i++)
        {
          std::vector<double> local_lnlike(size);
          std::vector<double> local_personal_best_value(size);
          std::vector<std::vector<double>> local_x(size, std::vector<double>(nPar_total));
          std::vector<std::vector<double>> local_v(size, std::vector<double>(nPar_total));
          std::vector<std::vector<double>> local_personal_best_x(size, std::vector<double>(nPar_total));
          GMPI::Comm().Gather_single(particles.at(i).lnlike, local_lnlike, 0);
          GMPI::Comm().Gather_single(particles.at(i).personal_best_value, local_personal_best_value, 0);
          GMPI::Comm().Gather_arrays(particles.at(i).x[0], local_x[0][0], nPar_total, 0);
          GMPI::Comm().Gather_arrays(particles.at(i).v[0], local_v[0][0], nPar_total, 0);
          GMPI::Comm().Gather_arrays(particles.at(i).personal_best_x[0], local_personal_best_x[0][0], nPar_total, 0);

          if (rank == 0)
          {
            for (int j = 0; j < nprocs; j++)
            {
              particles_global.at(NP_per_rank * j + i).lnlike = local_lnlike[j];
              particles_global.at(NP_per_rank * j + i).personal_best_value = local_personal_best_value[j];
              for (int k = 0; k < nPar_total; k++)
              {
                particles_global.at(NP_per_rank * j + i).x.at(k) = local_x[j][k];
                particles_global.at(NP_per_rank * j + i).v.at(k) = local_v[j][k];
                particles_global.at(NP_per_rank * j + i).personal_best_x.at(k) = local_personal_best_x[j][k];
              }
            }
          }
        }

        // Sum fcall from all processes
        GMPI::Comm().Reduce(fcall, fcall_global, MPI_SUM, 0);

        // Collect the global best fit across all processes
        int global_best_rank;
        std::vector<double> global_best_values(size);
        GMPI::Comm().Gather_single(global_best_value, global_best_values, 0);
        if (rank == 0)
        {
          auto max_it = std::max_element(global_best_values.begin(), global_best_values.end());
          global_best_rank = std::distance(global_best_values.begin(), max_it);
        }
        GMPI::Comm().Bcast_single(global_best_rank, 0);
        GMPI::Comm().Bcast_single(global_best_value, global_best_rank);
        GMPI::Comm().Bcast(global_best_x, nPar_total, global_best_rank);

      #else

        fcall_global = fcall;
        particles_global = particles;

      #endif

    }

    /// Update a particle's velocity and use that to update its position
    void particle_swarm::update_particle(particle& p)
    {
      if (adapt_omega) omega = p.x.at(omega_index);
      if (adapt_phi)
      {
        phi1 = p.x.at(phi1_index);
        phi2 = p.x.at(phi2_index);
      }
      for (int i = 0; i < nPar_total; i++)
      {
        double r1 = std::generate_canonical<double, 32>(rng);
        double r2 = std::generate_canonical<double, 32>(rng);
        p.v.at(i) = omega*p.v.at(i) + phi1*r1*(p.personal_best_x.at(i)-p.x.at(i)) + phi2*r2*(global_best_x.at(i)-p.x.at(i));
        p.x.at(i) = p.x.at(i) + p.v.at(i);
      }
    }

    /// Update a particle's own best fit and the global best fit
    void particle_swarm::update_best_fits(particle& p)
    {
      p.update_personal_best();
      if (p.lnlike > global_best_value)
      {
        global_best_value = p.lnlike;
        global_best_x = p.x;
      }
    }

    /// Deal with vectors outside the prior box according to the value of bndry
    bool particle_swarm::implement_boundary_policy(particle& p)
    {
      // Test if the particle has a valid position
      bool validVector = true;
      for (int i = 0; i < nPar_total; i++)
      {
        if (p.x.at(i) < lowerbounds.at(i) or p.x.at(i) > upperbounds.at(i)) validVector = false;
      }
      // Return true immediately if it has a valid position, or false if it does not and the brick wall boundary condition is in use.
      if (validVector or bndry == 1) return validVector;

      // Modify the particle position and velocity if other boundary strategies are in use.
      switch(bndry)
      {
        // Randomly choose new values somewhere in the prior box, and reset the velocity.
        case 2:
          p.init(init_stationary);
          break;

        // Reflect the position and velocity about the borders violated
        case 3:
          p.reflect();
          break;

        // Something went wrong
        default:
          Scanner::scan_error().raise(LOCAL_INFO, "Unrecognised bndry setting for j-Swarm. Please set bndry = 1, 2 or 3.");
      }
      return true;
    }

    /// Check whether the swarm has converged; note that this will fail if the lnlike is ever >= 0!
    bool particle_swarm::converged()
    {

      // Find the mean value of the personal best likelihoods
      double current_mean = std::accumulate(particles_global.begin(), particles_global.end(), 0.0, [](int i, particle& p){return i+p.personal_best_value;})/NP;
      // Find the fractional improvement between this generation and last generation
      double mean_ratio = current_mean/mean_lnlike;
      double fractional_diff = (mean_ratio <= 1.0) ? 1.0 - mean_ratio : 1.0;
      mean_lnlike = current_mean;

      // Discard oldest improvement and store new improvement
      conv_progress.erase(conv_progress.begin());
      conv_progress.push_back(fractional_diff);
      // Average over the generations stored
      sfim = std::accumulate(conv_progress.begin(), conv_progress.end(), 0.0)/convsteps;
      if (verbose > 1) cout << "  j-Swarm: Smoothed fractional improvement of the mean personal best: " << sfim << endl;

      // Compare to threshold value
      return (sfim < convthresh);
    }

    /// Save swarm settings
    void particle_swarm::save_settings()
    {
      std::ofstream settings;
      settings.open(path + ".settings.yaml");
      settings << "# Dimensionality of the parameter space (int)" << endl
       << "nPar: " << nPar << endl
       << "# Number of derived quantities to output (GAMBIT printers handle these). (int)" << endl
       << "nDerived: " << nDerived << endl
       << "# Number of parameters that are to be treated as discrete (int)" << endl
       << "nDiscrete: " << nDiscrete << endl
       << "# Maximum number of generations (int)" << endl
       << "maxgen: " << maxgen << endl
       << "# Population size (individuals per generation) (int)" << endl
       << "NP: " << NP << endl
       << "# Boundary constraint: 1=brick wall, 2=random re-initialization, 3=reflection (int)" << endl
       << "bndry: " << bndry << endl
       << "# Number of steps to smooth over when checking convergence (int)" << endl
       << "convsteps: " << convsteps << endl
       << "# Save progress every savecount generations (int)" << endl
       << "savecount: " << savecount << endl
       << "# Initialisation strategy: 0=one shot, 1=n-shot, 2=n-shot with error if no valid vectors found. (int)" << endl
       << "init_pop_strategy: " << init_pop_strategy << endl
       << "# Maximum number of times to try to find a valid vector for each slot in the initial population. (int)" << endl
       << "max_ini_attempts: " << max_ini_attempts << endl
       << "# Output verbosity: 0=only error messages, 1=basic info, 2=civ-level info, 3+=population info (int)" << endl
       << "verbose: " << verbose << endl
       << "# Input base seed for random number generation; non-positive means seed from the system clock (int)" << endl
       << "seed: " << input_seed << endl
       << "# Base seed actually used for random number generation in last run (int)" << endl
       << "actual_seed: " << seed << endl
       << "# Inertial weight (double)" << endl
       << "omega: " << omega << endl
       << "# Cognitive weight (double)" << endl
       << "phi1: " << phi1 << endl
       << "# Social weight (double)" << endl
       << "phi2: " << phi2 << endl
       << "# Threshold for gen-level convergence: smoothed fractional improvement in the mean personal best population value (double)" << endl
       << "convthresh: " << convthresh << endl
       << "# Minimum function value to accept for the initial generation if init_population_strategy > 0. (double)" << endl
       << "min_acceptable_value: " << min_acceptable_value << endl
       << "# Use self-optimising adaptive choices for phi1 and phi2 (bool)" << endl
       << "adapt_phi: " << YAML::Node(adapt_phi) << endl
       << "# Use self-optimising adaptive choices for omega (bool)" << endl
       << "adapt_omega: " << YAML::Node(adapt_omega) << endl
       << "# Initialise particle velocities to zero (bool)" << endl
       << "init_stationary: " << YAML::Node(init_stationary) << endl
       << "# Parameter space boundaries (std::vector<double>)" << endl
       << "upperbounds: " << endl << YAML::Node(upperbounds) << endl
       << "lowerbounds: " << endl << YAML::Node(lowerbounds) << endl
       << "# Indices of parameters to be treated as discrete (std::vector<int>) (bool)" << endl
       << "discrete: " << YAML::Node(discrete) << endl;
    }

    /// Read swarm settings
    void particle_swarm::read_settings(bool init)
    {
      if (verbose > 0 and init)
      {
        cout << "  j-Swarm: WARNING using settings from resumed run and ignoring any new ones!" << endl
             << "           Set allow_new_settings=true to try changing settings when resuming." << endl;
      }
      YAML::Node settings;
      try
      {
        settings = YAML::LoadFile(path + ".settings.yaml");
        nPar = settings["nPar"].as<int>();
        nDerived = settings["nDerived"].as<int>();
        nDiscrete = settings["nDiscrete"].as<int>();
        maxgen = settings["maxgen"].as<int>();
        NP = settings["NP"].as<int>();
        bndry = settings["bndry"].as<int>();
        convsteps = settings["convsteps"].as<int>();
        savecount = settings["savecount"].as<int>();
        init_pop_strategy = settings["init_pop_strategy"].as<int>();
        max_ini_attempts = settings["max_ini_attempts"].as<int>();
        verbose = settings["verbose"].as<int>();
        omega = settings["omega"].as<double>();
        phi1 = settings["phi1"].as<double>();
        phi2 = settings["phi2"].as<double>();
        convthresh = settings["convthresh"].as<double>();
        min_acceptable_value = settings["min_acceptable_value"].as<double>();
        adapt_phi = settings["adapt_phi"].as<bool>();
        adapt_omega = settings["adapt_omega"].as<bool>();
        init_stationary = settings["init_stationary"].as<bool>();
        upperbounds = settings["upperbounds"].as<std::vector<double>>();
        lowerbounds = settings["lowerbounds"].as<std::vector<double>>();
        discrete = settings["discrete"].as<std::vector<int>>();
        if (init) seed = settings["seed"].as<int>();
      }
      catch (YAML::Exception &e)
      {
        std::ostringstream msg;
        msg << "Error reading file \""<<path + ".settings.yaml"<<"\"! " << endl;
        msg << "Please check that file exists and is properly formatted." << endl;
        msg << "(yaml-cpp error: "<<e.what()<<" )";
        Scanner::scan_error().raise(LOCAL_INFO,msg.str());
      }
    }

    /// Save data from the last generation
    void particle_swarm::save_generation(int gen)
    {
      // Save the information about the last generation needed for resuming (and for tracking progress)
      std::ofstream lastgen;
      lastgen.open(path + ".lastgen");
      lastgen << "# Number of calls to the objective function so far (int)" << endl << fcall_global << endl;
      lastgen << "# Generation number (int)" << endl << gen << endl;
      lastgen << "# Global best fit achieved so far (double)" << endl << global_best_value << endl;
      lastgen << "# Location of global best fit achieved so far (" << nPar_total << " doubles)" << endl;
      for (int i = 0; i < nPar_total; i++) lastgen << global_best_x.at(i) << " ";
      lastgen << endl << "# Mean personal best lnlike in last generation (double)" << endl << mean_lnlike << endl;
      lastgen << "# Smoothed fractional improvement in mean personal best lnlike over last [convsteps] generations." << endl << sfim << endl;
      lastgen << "# Fractional improvements in mean personal best lnlike in last [convsteps] generations (" << convsteps << " doubles)" << endl;
      for (int i = 0; i < convsteps; i++) lastgen << conv_progress.at(i) << " ";
      lastgen << endl << "# [NP] particles in last completed generation (" << NP
              << " x " << 2 + 3*nPar_total << " doubles {lnlike, personal best lnlike, "
              << nPar_total << " positions, " << nPar_total << " velocities, "
              << nPar_total << " personal best positions})";
      lastgen.precision(8);
      lastgen << std::scientific;
      for (int i = 0; i < NP; i++)
      {
        particle& p = particles_global.at(i);
        lastgen << endl << p.lnlike << " " << p.personal_best_value;
        for (int j = 0; j < nPar_total; j++) lastgen << " " << p.x.at(j);
        for (int j = 0; j < nPar_total; j++) lastgen << " " << p.v.at(j);
        for (int j = 0; j < nPar_total; j++) lastgen << " " << p.personal_best_x.at(j);
      }

       // Add the full info about the current generation to the native output
      if (save_particles_natively)
      {
        std::ofstream pfile;
        if (gen == 1)
        {
          pfile.open(path + ".particles");
          pfile << "# Gen lnlike           position (" << nPar_total << " doubles)          velocity (" <<  nPar_total << " doubles)";
        }
        else pfile.open(path + ".particles", std::ios_base::app);
        pfile.precision(8);
        pfile << std::scientific;
        for (int i = 0; i < NP; i++)
        {
          particle& p = particles_global.at(i);
          pfile << endl << gen << "    " << p.lnlike << " ";
          if (discrete.empty())
          {
            for (int j = 0; j < nPar_total; j++) pfile << " " << p.x.at(j);
          }
          else
          {
            std::vector<double> true_x = p.discretised_x(discrete);
            for (int j = 0; j < nPar_total; j++) pfile << " " << true_x.at(j);
          }
          for (int j = 0; j < nPar_total; j++) pfile << " " << p.v.at(j);
        }
      }
    }

    /// Read data from the last generation
    int particle_swarm::read_generation()
    {
      int gen;
      std::ifstream lastgen;
      const int len = 200;
      char line[len];
      lastgen.open(path + ".lastgen");
      // Read fcall
      lastgen.getline(line,len);
      lastgen >> fcall_global;
      fcall = (rank == 0 ? fcall_global : 0);
      lastgen.getline(line,len);
      // Read generation number
      lastgen.getline(line,len);
      lastgen >> gen;
      lastgen.getline(line,len);
      // Read global best-fit lnlike
      lastgen.getline(line,len);
      lastgen >> global_best_value;
      lastgen.getline(line,len);
      // Read position of global best-fit lnlike
      lastgen.getline(line,len);
      global_best_x.resize(nPar_total);
      for (int i = 0; i < nPar_total; i++) lastgen >> global_best_x.at(i);
      lastgen.getline(line,len);
      // Read mean personal best lnlike for the current generation
      lastgen.getline(line,len);
      lastgen >> mean_lnlike;
      lastgen.getline(line,len);
      // Read smoothed fractional improvement in mean personal best lnlike over last [convsteps] generations
      lastgen.getline(line,len);
      lastgen >> sfim;
      lastgen.getline(line,len);
      // Read fractional improvements in mean personal best lnlike during last [convsteps] generations
      conv_progress.resize(convsteps);
      lastgen.getline(line,len);
      for (int i = 0; i < convsteps; i++) lastgen >> conv_progress.at(i);
      lastgen.getline(line,len);
      // Read particle data
      lastgen.getline(line,len);
      for (int n = 0; n < NP; n++)
      {
        particle p(nPar_total, lowerbounds, upperbounds, rng);
        lastgen >> p.lnlike >> p.personal_best_value;
        p.x.resize(nPar_total);
        p.v.resize(nPar_total);
        p.personal_best_x.resize(nPar_total);
        for (int j = 0; j < nPar_total; j++) lastgen >> p.x.at(j);
        for (int j = 0; j < nPar_total; j++) lastgen >> p.v.at(j);
        for (int j = 0; j < nPar_total; j++) lastgen >> p.personal_best_x.at(j);
        lastgen.getline(line,len);
        if (rank == 0) particles_global.at(n) = p;
        int i = n - NP_per_rank * rank;
        if (i >= 0 and i < NP_per_rank) particles.at(i) = p;
      }
      return gen;
    }

  }

}

Updated on 2025-02-12 at 15:36:40 +0000