// Particle swarm optimization for measured aerosol optical depths at 13 wavelengths: // 412, 440, 463, 479, 500, 520, 556, 610, 675, 750, 778, 870, and 1020 nm // (If using 6 wavelengths consider only 440, 555, 674, 778, 869, 1019 with new solar spectrophotometer) // version 4 adds scouts // added bimodal lognormal distribution 15 Dec 2015 // add option to fix alpha or beta parameters 17 Dec 2015 // update 12/29/2015 to include testing for parameter fixed and only updating those that vary - affects init_swarm, init_particle, and update_swarm // 12/29/2015 changed global parameter stop condition from 1.0E-10 to 1.0E-5 // c++ includes #include #include #include #include #include #include #include #include #include #include #include using namespace std; // required for c++ file routines // Put #defines here #define channels 13 // number of wavelength channels to use for optimization #define particles 200 // number of particles in the swarm #define PI 3.1415192654 // the constant PI #define CONV_FACT 1.0E-4 /* convert microns to cm */ #define CONV_FACT1 1.0E-3 // convert nanometers to micrometers #define CONV_FACT2 1.0E-7 /* convert nanometers to cm */ #define c1 1.8 // acceleration coefficient 1 #define c2 1.8 // acceleration coefficient 2 #define pop_max 10 // define the maximum number of populations to get parameters for #define gen_max 5000 // define the maximum number of generations to go through #define N_tmax 100 // define the stop condition for number of trials with no significant change #define scouts 10 // define the number of scouts used for additional global minimum checking // required definition of matrix at top of source code typedef vector > matrix; // define matrix object // function prototype definitions to be placed before main() source code // generates a random value between low and high numbers using log10 space double random(double low, double high); // calculates aod for each particle in the swarm -- call with calc_aod(swarm[x], mie[y], aodc[x][y]) void calc_aod(vector &part, vector &Qext, double &tauc, int dist); // initialize mie extinction matrix by filling it with values from files void initQ(vector &Qext, string fname); void init_mie(matrix &mie); // calculate the reduced chi^2 value for each particle in the swarm void calc_red_chi2(double aodm[], matrix &aodc, double f[], int dist); // initialize the swarm by passing by passing range limits for parameters void init_swarm(matrix &swarm, double param_min[], double param_max[], vector ¶m, int num, int param_fix); // generates a random particle using log10 space for parameter ranges - called by init_swarm() void init_particle(vector &part, double param_min[], double param_max[], vector ¶m, int param_fix); // find global reduced chi^2 minimum int find_global_min(double f[], double Pg[], matrix &swarm, int num); // find local reduced chi^2 minimum for each particle void find_local_min(double f[], double f0[], matrix &Pl, matrix &swarm); void update_swarm(matrix &swarm, matrix &Pl, double Pg[], double param_min[], double param_max[], vector ¶m, int param_fix); int main() { bool d_global_small=false; // boolean variable to test for significant changes in the global parameters int i,j; // generic indices for loops int population=1; // index for number of populations int dist; // type of distribution to fit to int i_g,gen=0; // indices for global minimum and generation number int i_j; // index for best extra particle's reduced chi^2 int N_t=0; // numbers of trials with no significant change in parameters used for stop condition int param_fix=0; // holds info as to whether any parameters are to be fixed float day; string mystr; // holds typed in filenames char *fname1,*fname2,*fname3,dataline[500]; // holds input and output filenames and datalines from input file double aodm[channels]; // holds measured aerosol optical depths (AODs) for a particular date & time double f0[particles]; // initial reduced chi^2 vector double f[particles]; // current reduced chi^2 vector matrix swarm(particles,vector(6,0.0)); // matrix of particles each with 6 dimensions N0, a, b, N1, a1, b1 matrix aodc(particles,vector(channels,0.0)); // matrix of calculated AODs for each particle at each wavelength matrix Pl(particles,vector(6,0.0)); // matrix of local minimum adjustments double Pg[6]; // vector for global minimum adjustments for all particles double Pg0[6] = {1.0E12,0.1,0.307,1.0E11,1.0,0.307}; // initial global minima for checking stop conditions matrix Pj(scouts,vector(6,0.0)); // vector for randomly generated particle so we don't get stuck in a local minimum - use matrix object to be compatible with swarm for calls to init_swarm double Pj0[6]; // holds the global minima for scouts // vector aodj(channels,0.0); matrix aodj(scouts, vector(channels,0.0)); //vector for extra particle's aods for global minimum searching improvement double fj[scouts]; // reduced chi^2 for extra randomly generated particles // float r[9961]; // vector of radii from 0.040-10.000 um in 0.001 increments matrix mie(channels,vector(9961,0.0)); // matrix of extinction coefficients for each wavelength at each radius vector param(6,0.0); // holds initial set of N0, alpha, and beta in order to get initial reduced chi^2 functions // double Nmin, Nmax, amin, amax, bmin, bmax; // range for parameters: N0, alpha, and beta // replace above line with arrays double param_min[6], param_max[6]; // minima and maxima for N0, alpha, and beta double red_chi2_goal; // relative eroor between calculated and measured aods fstream aodfile,outfile,outfile1; srand(time(NULL)); // seed the random number algorithm init_mie(mie); cout << "Enter measured AOD file to process (e.g., bimodal1.txt): "; getline(cin,mystr); fname1 = new char [mystr.size()+1]; /* c_string method */ strcpy(fname1,mystr.c_str()); cout << endl << "Enter Distribution type to fit:" << endl << "1 = Junge" << endl << "2 = Gamma" << endl << "3 = Lognormal" << endl << "4 = Bimodal Lognormal" << endl; cin >> dist; if(dist==1){red_chi2_goal=1.0E-6; cout << "Junge selected" << endl;} // Junge distribution else if(dist==2){red_chi2_goal=1.0E-5; cout << "Gamma selected" << endl;} // Gamma distribution else if(dist==3){red_chi2_goal=1.0E-5; cout << "Lognormal selected" << endl;} // Lognormal distribution else if(dist==4){red_chi2_goal=1.0E-3; cout << "Bimodal Lognormal selected" << endl;} // Bimodal Lognormal distribution else{cout << "Bad distribution!" << endl; return 0;} cout << "Enter output file name for best number distribution parameters N0, alpha, beta, N1, alpha1, beta1 (e.g., results2bimodal.txt): "; cin >> mystr; fname2 = new char [mystr.size()+1]; /* c_string method */ strcpy(fname2,mystr.c_str()); cout << "Enter output file name summary results: "; cin >> mystr; fname3 = new char [mystr.size()+1]; /* c_string method */ strcpy(fname3,mystr.c_str()); aodfile.open(fname1); // open input file of aod values if (!aodfile.is_open()){ cout << "\nUnable to open file " << fname1 << "\n"; return 0; } outfile.open(fname2,ios::app | ios::out); // open output file for number distribution parameters N0, alpha, beta if (!outfile.is_open()){ cout << "\nUnable to open file " << fname2 << "\n"; return 0; } outfile << "N0, alpha, beta, N1, alpha1, beta1" << endl; // write header line to output file outfile1.open(fname3,ios::app | ios::out); // open output file for summary results if (!outfile1.is_open()){ cout << "\nUnable to open file " << fname3 << "\n"; return 0; } outfile1 << "red. chi^2, N0, alpha, beta, N1, alpha1, beta1" << endl; // write header line to output file /* This kept giving segmentation faults, so replaced with stringstream while(!aodfile.eof()){ getline(aodfile,mystr); // get a line of data strcpy(dataline,mystr.c_str()); // extract data fields sscanf(dataline,"%f %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf",&day,&aodm[0],&aodm[1],&aodm[2],&aodm[3],&aodm[4],&aodm[5],&aodm[6],&aodm[7],&aodm[8],&aodm[9],aodm[10],&aodm[11],&aodm[12]); } MAKE SURE aod files don't have commas, only spaces separating the values */ while(getline(aodfile,mystr)){ cout << mystr << endl; stringstream(mystr) >> day >> aodm[0] >> aodm[1] >> aodm[2] >> aodm[3] >> aodm[4] >> aodm[5] >> aodm[6] >> aodm[7] >> aodm[8] >> aodm[9] >> aodm[10] >> aodm[11] >> aodm[12]; } for(i=0;i> param_fix; switch (param_fix) { case 0: cout << "both alpha and beta will vary" << endl; outfile << "both alpha and beta will vary" << endl; break; case 1: param_min[1]=param[1]; param_max[1]=param[1]; param_min[4]=param[4]; param_max[4]=param[4]; cout << "alphas will remain fixed at " << param[1] << " and " << param[4] << endl; outfile << "alphas will remain fixed at " << param[1] << " and " << param[4] << endl; break; case 2: param_min[2]=param[2]; param_max[2]=param[2]; param_min[5]=param[5]; param_max[5]=param[5]; cout << "betas will remain fixed at " << param[2] << " and " << param[5] << endl; outfile << "betas will remain fixed at " << param[2] << " and " << param[5] << endl; break; case 3: param_min[1]=param[1]; param_max[1]=param[1]; param_min[4]=param[4]; param_max[4]=param[4]; cout << "alphas will remain fixed at " << param[1] << " and " << param[4] << endl; outfile << "alphas will remain fixed at " << param[1] << " and " << param[4] << endl; param_min[2]=param[2]; param_max[2]=param[2]; param_min[5]=param[5]; param_max[5]=param[5]; cout << "betas will remain fixed at " << param[2] << " and " << param[5] << endl; outfile << "betas will remain fixed at " << param[2] << " and " << param[5] << endl; break; case 4: param_min[1]=param[1]; param_max[1]=param[1]; param_min[2]=param[2]; param_max[2]=param[2]; cout << "alpha0 will remain fixed at " << param[1] << "; beta0 will remain fixed at " << param[2] << endl; outfile << "alpha0 fixed at " << param[1] << "; beta0 fixed at " << param[2] << endl; break; default: cout << "invalid selection - both alpha and beta will vary" << endl; outfile << "invalid selection - both alpha and beta will vary" << endl; } // do{ //ignore the reduced chi^2 goal for now // do initial aod calculations to get initial reduced chi^2 values // start with a random swarm do{ // run multiple times (populations) cout << "initializing swarm..." << endl; init_swarm(swarm,param_min,param_max,param,particles,param_fix); // initialize the swarm cout << "initializing aods..." << endl; for(i=0;iN_tmax){ // stop condition reached? cout << "delta N0: " << (Pg[0]-Pg0[0])/Pg0[0] << " delta a: " << (Pg[1]-Pg0[1])/Pg0[1] << " delta b: " << (Pg[2]-Pg0[2])/Pg0[2] << endl; if(dist==4){ cout << "delta N1: " << (Pg[3]-Pg0[3])/Pg0[3] << " delta a1: " << (Pg[4]-Pg0[4])/Pg0[4] << " delta b1: " << (Pg[5]-Pg0[5])/Pg0[5] << endl; } cout << "reduced chi^2 = " << f[i_g] << endl; break; // if stop condition met, break out of evolution loop } else{ for(i=0;i<6;i++){ // cycle through parameters N0, a, b Pg0[i]=Pg[i]; // to update global min } } gen++; } // end of evolution loop // N_t=0; // reset number of trials // population += 1; // gen=0; // reset generation // } while(f[i_g]>reduced chi^2_goal && population < 11); // limit to 10 populations or desired reduced chi^2 goal outfile << Pg[0] << ", " << Pg[1] << ", " << Pg[2] << endl; if(dist==4){ outfile << Pg[3] << ", " << Pg[4] << ", " << Pg[5] << endl; } outfile << "Generations: " << gen << "\t" << "Population: " << population << endl; cout << "Generation # " << gen << ", N0 = " << Pg[0] << ", alpha = " << Pg[1] << ", beta = " << Pg[2] << endl; if(dist==4){ cout << "N1 = " << Pg[3] << ", alpha1 = " << Pg[4] << ", beta1 = " << Pg[5] << endl; } for(i=0;i &part, vector &Qext, double &tauc, int dist) { int i=0; double tau1=0.0,tau2=0.0; tauc=0; while(i<9962){ // cycle through all radii if(dist==1){ // Junge distribution tauc += pow((i+40)*CONV_FACT2,2.0)*Qext[i]*pow((i+40.5)*CONV_FACT2,-(part[1]+1)); // Junge distribution } else if(dist==2){ // Gamma distribution tauc += pow((i+40)*CONV_FACT2,2.0)*Qext[i]*pow((i+40.5)*CONV_FACT2,part[2])*exp(-part[1]*(i+40.5)*CONV_FACT1); // Gamma distribution } else if(dist==3){ // Lognormal distribution tauc += pow((i+40),2.0)/(i+40.5)*CONV_FACT2*Qext[i]*exp(-pow(log((i+40.5)*CONV_FACT1/part[1])/part[2],2.0)/2.0); // Lognormal distribution } else if(dist==4){ // Bimodal Lognormal distribution tau1 += pow((i+40),2.0)/(i+40.5)*CONV_FACT2*Qext[i]*exp(-pow(log((i+40.5)*CONV_FACT1/part[1])/part[2],2.0)/2.0); // Aitkin mode tau2 += pow((i+40),2.0)/(i+40.5)*CONV_FACT2*Qext[i]*exp(-pow(log((i+40.5)*CONV_FACT1/part[4])/part[5],2.0)/2.0); // accumulation mode } i++; } if(dist==3){ // lognormal distribution tauc *= sqrt(PI/2.0)*1.0E-7*part[0]/part[2]; // multiply by the constants from the sum: sqrt(pi/2), 2*delta r, N0/beta } else if(dist==4){ // bimodal lognormal distribution tauc = tau1*sqrt(PI/2.0)*1.0E-7*part[0]/part[2]+tau2*sqrt(PI/2.0)*1.0E-7*part[3]/part[5]; } else if(dist==2){ // modified gamma distribution tauc *= 2.0*PI*5.0E-8*part[0]; // multiply by the constants from the sum: pi, 2*delta r, N0, beta } else{ tauc *= 2.0*PI*5.0E-8*part[0]*part[2]*1.0E-12; // multiply by the constants from the sum: pi, 2*delta r, N0, beta and convert from number at 1cm to number at 1um } return; } void init_mie(matrix &mie) // fills the matrix mie[wavelength][radius] { string fname; fname="mie-data/mie412nm.csv"; initQ(mie[0],fname); fname="mie-data/mie440nm.csv"; initQ(mie[1],fname); fname="mie-data/mie463nm.csv"; initQ(mie[2],fname); fname="mie-data/mie479nm.csv"; initQ(mie[3],fname); fname="mie-data/mie500nm.csv"; initQ(mie[4],fname); fname="mie-data/mie520nm.csv"; initQ(mie[5],fname); fname="mie-data/mie556nm.csv"; initQ(mie[6],fname); fname="mie-data/mie610nm.csv"; initQ(mie[7],fname); fname="mie-data/mie675nm.csv"; initQ(mie[8],fname); fname="mie-data/mie750nm.csv"; initQ(mie[9],fname); fname="mie-data/mie778nm.csv"; initQ(mie[10],fname); fname="mie-data/mie870nm.csv"; initQ(mie[11],fname); fname="mie-data/mie1020nm.csv"; initQ(mie[12],fname); return; } void initQ(vector &Qext, string fname) // fills an individual row (i.e., wavelength) of mie[wavelength][radius] { int i; double r, Qsca, Qabs, Gsca; // data in Mie files but not needed char *fname1,dataline[100]; string mystr; fstream miefile; fname1 = new char [fname.size()+1]; /* c_string method */ strcpy(fname1,fname.c_str()); miefile.open(fname1); if (miefile.is_open()) { i=0; getline(miefile,mystr); // throw away header line while(i<9962) { getline(miefile,mystr); strcpy(dataline,mystr.c_str()); sscanf(dataline,"%lf,%lf,%lf,%lf,%lf",&r,&Qext[i],&Qsca,&Qabs,&Gsca); // fills the Qext[] array ignoring other values i++; } miefile.close(); cout << "Processed mie file " << fname << "\n"; } else cout << "\nUnable to open file " << fname << "\n"; return; } void calc_red_chi2(double aodm[], matrix &aodc, double f[], int dist) { int i=0,j=0,rows; double a; rows=aodc.size(); for(i=0;i ¶m, int num, int param_fix) { int i=0; while(i &part, double param_min[], double param_max[], vector ¶m, int param_fix) { int j; switch (param_fix) { /* case 0: // all parameters vary for(j=0;j<6;j++){ part[j]=random(param_min[j],param_max[j]); // set N0, a, and b parameters } break; */ case 1: // alphas are fixed part[0]=random(param_min[0],param_max[0]); // set N0 parameter part[1]=param[1]; // fix a parameter part[2]=random(param_min[2],param_max[2]); // set b parameter part[3]=random(param_min[3],param_max[3]); // set N1 parameter part[4]=param[4]; // fix a1 parameter part[5]=random(param_min[5],param_max[5]); // set b1 parameter break; case 2: // betas are fixed part[0]=random(param_min[0],param_max[0]); // set N0 parameter part[1]=random(param_min[1],param_max[1]); // set a parameter part[2]=param[2]; // fix b parameter part[3]=random(param_min[3],param_max[3]); // set N1 parameter part[4]=random(param_min[4],param_max[4]); // set a1 parameter part[5]=param[5]; // fix b1 parameter break; case 3: // alphas and betas are fixed part[0]=random(param_min[0],param_max[0]); // set N0 parameter part[1]=param[1]; // fix a parameter part[2]=param[2]; // fix b parameter part[3]=random(param_min[3],param_max[3]); // set N1 parameter part[4]=param[4]; // fix a1 parameter part[5]=param[5]; // fix b1 parameter break; case 4: // alpha0 and beta0 are fixed part[0]=random(param_min[0],param_max[0]); // set N0 parameter part[1]=param[1]; // fix a parameter part[2]=param[2]; // fix b parameter part[3]=random(param_min[3],param_max[3]); // set N1 parameter part[4]=random(param_min[4],param_max[4]); // set a1 parameter part[5]=random(param_min[5],param_max[5]); // set b1 parameter break; default: for(j=0;j<6;j++){ part[j]=random(param_min[j],param_max[j]); // set N0, a, and b parameters } break; } return; } int find_global_min(double f[], double Pg[], matrix &swarm, int num) { int i; int g=0; for(i=0;i ¶m, int param_fix) /* Implements Eq (10) from Yuan's article: X_i(t+1)=X_i(t)+c1*r1*[P_i(t)-X_i(t)]+c2*r2*[P_g(t)-X_i(t)] */ { int i,j; // particle and parameter indices double r1,r2; // random numbers in interval [0,1] bool out_of_bounds; // variable to test if particle is out of the domain i=0; while(i param_max[j]){ // check to see if out bounds init_particle(swarm[i], param_min, param_max, param_fix); //if so, generate random postion in bounds } */ j++; } break; } for(j=0;j<6;j++){ out_of_bounds=out_of_bounds || (swarm[i][j]param_max[j]); // test for parameters out of bounds } if(out_of_bounds){init_particle(swarm[i], param_min, param_max, param, param_fix);} // if any parameter out of bounds, generate new particle i++; } return; }