k-dot-p 0.1

digital_alloy_optimizer_1d_mpi.c++

Go to the documentation of this file.
00001 /*
00002  *Optimizes a material according to some number.
00003 
00004 
00005  Copyright (C) 2011 Joseph Pingenot
00006 
00007  This program is free software: you can redistribute it and/or modify
00008  it under the terms of the GNU Affero General Public License as published by
00009  the Free Software Foundation, either version 3 of the License, or
00010  (at your option) any later version.
00011 
00012  This program is distributed in the hope that it will be useful,
00013  but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  GNU Affero General Public License for more details.
00016 
00017  You should have received a copy of the GNU Affero General Public License
00018  along with this program.  If not, see <http://www.gnu.org/licenses/>.
00019 
00020 */
00021 
00022 #include <mpi.h>
00023 #include <gsl/gsl_errno.h>
00024 #include <libmetacalc/digital_alloy_optimizer_1d_mpi.h++>
00025 #include <libmodelxx/cartesian_grid.h++>
00026 #include <libmodelxx/randomness.h++>
00027 #include <libkdotp/gcc_hints.h>
00028 
00029 #ifndef HAVE_MPI
00030 #error 'digital_alloy_optimizer_1d_mpi should ONLY be built if MPI support is included, but you do not have MPI support!'
00031 #endif
00032 
00033 #define DIGITAL_ALLOY_OPTIMIZER_1D_MPI_GERROR_DOMAIN 74
00034 
00035 #define EPSILON 1e-9
00036 
00037 using namespace kdotp::libmodelxx::grid;
00038 using namespace kdotp::libmodelxx::structure;
00039 
00040 namespace kdotp {
00041   namespace libmetacalc {
00042     namespace optimizer {
00043 
00044       digital_alloy_optimizer_1d_mpi
00045       ::digital_alloy_optimizer_1d_mpi(
00046            kdotp::libmodelxx::structure::material* marker_material,
00047            kdotp::libmodelxx::structure::material* _mat1,
00048            kdotp::libmodelxx::structure::material* _mat0,
00049            kdotp::libmodelxx::structure::generic_structure* s,
00050            set_point_callback _set_point,
00051            unsigned _blocksize,
00052            enum goal _goal,
00053            GError** err,
00054            void* privdat,
00055            int _N_nodes,
00056            int _node,
00057            unsigned _N_blocks,
00058            long unsigned int rng_seed,
00059            GList* assignments,
00060            struct ::kdotp::libmodelxx::postprocessing::function_exp *assignment_expr,
00061            double temperature,
00062            GHashTable *global_symbols
00063                )
00064   : mat1(_mat1),
00065     mat0(_mat0),
00066     current_assignments(NULL),
00067     material_is_optimum(NULL),
00068     genstruct(s),
00069     call_set_point_callback(_set_point),
00070     blocksize(_blocksize),
00071     points_in_block(NULL),
00072     point_start(NULL),
00073     N_blocks(_N_blocks),
00074     previous_value(0),
00075     current_block(NULL),
00076     goal(_goal),
00077     seed(rng_seed),
00078     N_nodes(_N_nodes),
00079           node(_node),
00080     T(temperature),
00081     node_assignments(NULL),
00082     results(NULL),
00083     all_results(NULL)
00084       {
00085   cartesian_grid<material*, 1, 1>* matgrid = (cartesian_grid<material*, 1, 1>*)(genstruct->material_grid);
00086   /*First, we need to find the extents of the region.*/
00087   bool started=false;
00088   /*Easy way to make this a GList: start off with list=NULL, create a new (start,end) struct
00089    *  on start, save it to the list on end.  leave the started variable as it is.
00090    */
00091   for(unsigned X=0; X<matgrid->L[0]; X++) {
00092     if((**(matgrid->get_point_r(&X))) == *marker_material) {
00093       if(!started) {
00094         start_X = X;
00095         started=1;
00096       }
00097     }else if(started) {
00098       /*We've stopped*/
00099       stop_X = X;
00100       started=0;
00101     }
00102   }
00103   if(started) {
00104     /*We started but hit the end without switching out of the material. Odd, but solvable.*/
00105     stop_X = matgrid->L[0];
00106   }
00107   /*We could probably do this with rounding, but this only happens once and works.*/
00108   if(_N_blocks > 0) {
00109     N_blocks = (unsigned)_N_blocks;
00110     blocksize = ((stop_X-start_X) - (stop_X-start_X)%N_blocks)/N_blocks;
00111   }else{
00112     N_blocks = ((stop_X-start_X) - (stop_X-start_X)%blocksize)/blocksize;
00113   }
00114   /*Allocate arrays*/
00115   points_in_block = new unsigned[N_blocks];
00116   point_start = new unsigned[N_blocks];
00117   current_assignments = new short unsigned[N_blocks];
00118   /*Previous assignments is NULL in order to know that we have no previous assignment or value.*/
00119   material_is_optimum = new short unsigned[N_blocks];
00120   /*Map the points to the blocks*/
00121   for(unsigned i=0; i<N_blocks; i++) {
00122     points_in_block[i] = blocksize;
00123   }
00124   unsigned points_remaining = (stop_X - start_X)%blocksize;
00125   if(points_remaining != 0) {
00126     unsigned pos=points_remaining/N_blocks;
00127     for(unsigned pt=0; pt<points_remaining; pt++) {
00128       points_in_block[pos]++;
00129       pos += points_remaining/N_blocks;
00130       if(pos >= N_blocks) pos = pos%N_blocks;
00131     }
00132   }
00133   /*Put in the offsets.*/
00134   unsigned final_point = start_X;
00135   for(unsigned block=0; block<N_blocks; block++) {
00136     point_start[block] = final_point;
00137     final_point += points_in_block[block];
00138   }
00139 
00140   /*allocate and then seed the random number generator*/
00141   GError *e = NULL;
00142   gsl_rng_env_setup();
00143   rng = gsl_rng_alloc(gsl_rng_default);
00144   if(rng == NULL) {
00145     *err = g_error_new(DIGITAL_ALLOY_OPTIMIZER_1D_MPI_GERROR_DOMAIN, 0, "kdotp::libkdotp::optimizer::digital_alloy_optimizer_1d_mpi.c::digital_alloy_optimizer_1d_mpi(constructor): Failed to set up GSL random number generator.");
00146     return;
00147   }
00148   /*Set the seed randomly if it's not already set*/
00149   if(rng_seed == 0) {
00150     /*A seed value of 0 is special to GSL, so don't allow a seed of 0. Loop until it's not, or an error has occurred*/
00151     if(node == 0) {
00152       seed=0;
00153       while((seed == 0) && (e == NULL)) {
00154         seed = kdotp::libmodelxx::randomness::get_urandom_unsigned_long_int(&e);
00155       }
00156       if(e != NULL) {
00157         g_propagate_prefixed_error(err, e, "kdotp::libkdotp::optimizer::digital_alloy_optimizer_1d_mpi.c::digital_alloy_optimizer_1d_mpi(constructor): Failed to get random seed:\n\t");
00158         seed=0;
00159         MPI_Bcast(&seed, 1, MPI_UNSIGNED_LONG, 0, MPI_COMM_WORLD);
00160         return;
00161       }
00162       fprintf(stderr, "rng_seed was zero, so got new one (seed=%lu rng_seed=%lu)\n", seed, rng_seed);
00163       MPI_Bcast(&seed, 1, MPI_UNSIGNED_LONG, 0, MPI_COMM_WORLD);
00164     }else{
00165       int result = MPI_Bcast(&seed, 1, MPI_UNSIGNED_LONG, 0, MPI_COMM_WORLD);
00166       if((result != 0) || (seed == 0)) {
00167         g_propagate_prefixed_error(err, e, "kdotp::libkdotp::optimizer::digital_alloy_optimizer_1d_mpi.c::digital_alloy_optimizer_1d_mpi(constructor): Failed to get random seed (either MPI error or seed(%lu)==0):\n\t", seed);
00168         return;
00169       }
00170     }
00171   }else{
00172     fprintf(stderr, "rng_seed was non-zero, so keeping it (seed=%lu rng_seed=%lu)\n", seed, rng_seed);
00173   }
00174   fprintf(stderr, "\n\nseed is %lu\n\n", seed);
00175   /*We now seed the rng*/
00176   gsl_rng_set(rng, seed);
00177   /*Begin initialization.*/
00178   if((assignments != NULL) || (assignment_expr != NULL)) {
00179     /*NOTE that assignments takes precedence over assignment_expr*/
00180     short unsigned* state = new short unsigned[N_blocks];
00181     if(assignments != NULL) {
00182       if(assignments->next == NULL) {
00183         /*Assign each block based on the single user-supplied value.*/
00184         if(*(double*)(assignments->data) < 0.5) {
00185     for(unsigned i=0; i<N_blocks; i++) state[i] = 0;
00186         }else{
00187     for(unsigned i=0; i<N_blocks; i++) state[i] = 1;
00188         }
00189       }else{
00190         /*Assign each block based on the user's specification.*/
00191         GList* ll=assignments;
00192         for(unsigned i=0; i<N_blocks; i++) {
00193     state[i] = (*(double*)(ll->data) < 0.5)?0:1;
00194     ll = ll->next;
00195     if(ll == NULL) {
00196       *err = g_error_new(DIGITAL_ALLOY_OPTIMIZER_1D_MPI_GERROR_DOMAIN, 5, "kdotp::libkdotp::optimizer::digital_alloy_optimizer_1d_mpi.c::digital_alloy_optimizer_1d_mpi(constructor): ERROR while assigning materials according to user input: there were not enough values for each block (Got %u values but had %u blocks!\n", i+1, N_blocks);
00197       return;
00198     }
00199         }
00200       }
00201     }else{
00202       /*Evaluate the expression at each point in the blocks.*/
00203       struct ::kdotp::libmodelxx::postprocessing::symbol_table_entry_const start_x = {::kdotp::libmodelxx::postprocessing::CONSTANT_SYMBOL, ::kdotp::libmodelxx::postprocessing::SCALAR, NULL};
00204       struct ::kdotp::libmodelxx::postprocessing::symbol_table_entry_const mid_x = {::kdotp::libmodelxx::postprocessing::CONSTANT_SYMBOL, ::kdotp::libmodelxx::postprocessing::SCALAR, NULL};
00205       struct ::kdotp::libmodelxx::postprocessing::symbol_table_entry_const end_x = {::kdotp::libmodelxx::postprocessing::CONSTANT_SYMBOL, ::kdotp::libmodelxx::postprocessing::SCALAR, NULL};
00206       struct ::kdotp::libmodelxx::postprocessing::symbol_table_entry_const start_X = {::kdotp::libmodelxx::postprocessing::CONSTANT_SYMBOL, ::kdotp::libmodelxx::postprocessing::SCALAR, NULL};
00207       struct ::kdotp::libmodelxx::postprocessing::symbol_table_entry_const mid_X = {::kdotp::libmodelxx::postprocessing::CONSTANT_SYMBOL, ::kdotp::libmodelxx::postprocessing::SCALAR, NULL};
00208       struct ::kdotp::libmodelxx::postprocessing::symbol_table_entry_const end_X = {::kdotp::libmodelxx::postprocessing::CONSTANT_SYMBOL, ::kdotp::libmodelxx::postprocessing::SCALAR, NULL};
00209       /**TODO: need to finish symbol assignments, make the local symbol table, and then do the loop.*/
00210     }
00211     initialize(state, privdat);
00212   }else{
00213     initialize(privdat);
00214   }
00215   /*Set up the nodes' data*/
00216   node_assignments = new int[N_nodes+1];
00217   node_assignments[0] = 0;
00218   for(unsigned n=1; n<N_nodes+1; ++n) {
00219     node_assignments[n]=node_assignments[n-1]+N_blocks/N_nodes;
00220     /*we need <= because we're setting the upper limit for the *previous* node!*/
00221     if(n <= N_blocks%N_nodes) node_assignments[n]++;
00222   }
00223   if(node == 0) {
00224     fprintf(stderr, "Assignments for %u blocks across %u nodes (%u/%u=%u; %u%%%u=%u): \n", N_blocks, N_nodes, N_blocks, N_nodes, N_blocks/N_nodes, N_blocks, N_nodes, N_blocks%N_nodes);
00225     for(unsigned i=0; i<N_nodes; ++i) {
00226       fprintf(stderr, "node %u: %u\n", i, node_assignments[i]);
00227     }
00228     fprintf(stderr, "node %u: %u\n", N_nodes, node_assignments[N_nodes]);
00229   }
00230   results = new double[node_assignments[node+1]-node_assignments[node]];
00231   all_results = new double[N_blocks];
00232   /*At this point, we should be set up.
00233    *The user should perform their first calculation, and call us back using one of the do_*_step funx.*/
00234       }
00235 
00236       void digital_alloy_optimizer_1d_mpi::initialize(void* privdat) {
00237   if(current_block != NULL) {
00238     delete current_block;
00239     current_block = NULL;
00240   }
00241   randomize_array(current_assignments, N_blocks);
00242   set_material_from_current_assignments(privdat);
00243       }
00244 
00245       void digital_alloy_optimizer_1d_mpi::initialize(const short unsigned* state, void* privdat) {
00246   if(current_block != NULL) {
00247     delete current_block;
00248     current_block = NULL;
00249   }
00250   for(unsigned i=0; i<N_blocks; i++) current_assignments[i] = state[i];
00251   set_material_from_current_assignments(privdat);
00252       }
00253 
00254       digital_alloy_optimizer_1d_mpi::~digital_alloy_optimizer_1d_mpi() {
00255   if(mat1 != NULL) delete mat1;
00256   if(mat0 != NULL) delete mat0;
00257   if(current_assignments != NULL) delete[] current_assignments;
00258   if(material_is_optimum != NULL) delete[] material_is_optimum;
00259   if(points_in_block != NULL) delete[] points_in_block;
00260   if(current_block != NULL) delete current_block;
00261   if(results != NULL) delete[] results;
00262   if(node_assignments != NULL) delete[] node_assignments;
00263   if(all_results != NULL) delete[] all_results;
00264       }
00265 
00266       int digital_alloy_optimizer_1d_mpi::do_step(void* privdat) {
00267   int converged = 0;
00268   int* offsets = new int[N_nodes];
00269   int* receive_counts = new int[N_nodes];
00270   fprintf(stderr, "assignment for %u: %u (%u-%u)\n", node, node_assignments[node+1]-node_assignments[node], node_assignments[node], node_assignments[node+1]);
00271   int offset = 0;
00272   for(unsigned i=0; i<N_nodes; ++i) {
00273     offsets[i] = offset;
00274     receive_counts[i] = node_assignments[i+1]-node_assignments[i];
00275     offset += receive_counts[i];
00276   }
00277   int result = MPI_Gatherv(results, node_assignments[node+1]-node_assignments[node], MPI_DOUBLE, all_results, receive_counts, offsets, MPI_DOUBLE, 0, MPI_COMM_WORLD);
00278   unsigned selected_mod = 0;
00279   if(node == 0) {
00280     fprintf(stderr, "offsets\treceive_counts\n");
00281     for(unsigned i=0; i<N_nodes; ++i) {
00282       fprintf(stderr, "%d\t%d\n", offsets[i], receive_counts[i]);
00283     }
00284     for(unsigned i=0; i<N_blocks; ++i){
00285       fprintf(stderr, "%u %g\n", i, all_results[i]);
00286     }
00287   }
00288   delete[] offsets;
00289   delete[] receive_counts;
00290   if(result != 0) {
00291     fprintf(stderr, "Node %u: Got result %d from MPI_Gatherv!\n", node, result);
00292     /*If we got an MPI error there, all the nodes should've gotten the error*/
00293     return 5;
00294   }
00295   /*Check for convergence*/
00296   if(node == 0) {
00297     /*The mod which was selected*/
00298     /*A mod was selected*/
00299     int mod_was_selected=0;
00300     double sum = 0;
00301     /*These are in rank order, so they're in the right order; they represent the change at each block*/
00302     int N_improved=0;
00303     for(unsigned i=0; i<N_blocks; ++i) {
00304       double dist = dist_to_goal(all_results[i]);
00305       /*Improvements will be >= 0, regardless of the goal*/
00306       /*\note we put the n_improved incrementing up here because we need to check improvement independent of the sum (because of the annealing variant).*/
00307       if(dist > 0) {
00308         ++N_improved;
00309       }
00310       if(T != 0) {
00311         dist = exp(dist/T);
00312       }
00313       fprintf(stderr, "%u: sum=%g dist=%g ", i, sum, dist);
00314       if(dist > 0) {
00315         sum += dist;
00316         /*If this was the only one, this tells us which one it was*/
00317         selected_mod=i;
00318       }
00319       fprintf(stderr, " -> sum=%g\n", sum);
00320     }
00321     if(N_improved == 0) {
00322       converged=3;
00323     }else{
00324       if(N_improved == 1) {
00325         mod_was_selected=1;
00326         /*selected_mod was set in the loop above*/
00327       }else{
00328         double rand_selection = gsl_rng_uniform(rng)*sum;
00329         sum=0;
00330         for(unsigned i=0; i<N_blocks; ++i) {
00331     double dist = dist_to_goal(all_results[i]);
00332     if(T != 0) {
00333       dist = exp(dist/T);
00334     }
00335     fprintf(stderr, "%u: sum=%g dist=%g rand=%g ", i, sum, dist, rand_selection);
00336     if(dist > 0) {
00337       if((rand_selection >= sum) && (rand_selection <= (sum+dist))) {
00338         selected_mod = i;
00339         mod_was_selected=1;
00340         fprintf(stderr, "-> SELECTED\n");
00341         break;
00342       }
00343       sum += dist;
00344     }
00345     fprintf(stderr, " -> sum=%g\n", sum);
00346         }
00347         if(!mod_was_selected) {
00348     fprintf(stderr, "ERROR: no modification was selected!\n");
00349     converged=2;
00350         }
00351       }
00352     }
00353     /*Previous_value only matters on the root node; the others just calc what they're told.*/
00354     fprintf(stderr, "converged=%d mod_was_selected=%d selected_mod=%d previous_value=%g", converged, mod_was_selected, selected_mod, previous_value);
00355     previous_value = all_results[selected_mod];
00356     fprintf(stderr, "->%g\n", previous_value);
00357   }
00358   result = MPI_Bcast(&converged, 1, MPI_INT, 0, MPI_COMM_WORLD);
00359   if(result != 0) return result;
00360   fprintf(stderr, "node %u: converged=%d\n", node, converged);
00361   if(converged) {
00362     return converged;
00363   }
00364   /*If we've not yet converged, we need to send the modification to the nodes*/
00365   result = MPI_Bcast(&selected_mod, 1, MPI_INT, 0, MPI_COMM_WORLD);
00366   if(result != 0) {
00367     fprintf(stderr, "node %u: error %d bcasting the selected mod\n", node, result);
00368     return result;
00369   }
00370   toggle_block(selected_mod, privdat);
00371   return 0;
00372       }
00373 
00374       /*This performs the calculation without stepping through manually.
00375        *At each step, get_value is called with privdat
00376        *\param get_value callback to use after setting the materials.
00377        *\param privdat private data for the callback
00378        *\param anneal_start starting teperature for a simulated annealing program
00379        *\param anneal_end ending temperature for a simulated annealing program
00380        *\param n_anneal_steps number of annealing steps per annealing cycle
00381        *\param n_anneal_cyles number of annealing cycles to perform. Must be at least 1 to use a simulated annealing program
00382        *\param num_calculations_per_annealing_step
00383        *\return NULL if we converged; GError* if there was an error.
00384        *\note that if NaN is returned, we stop and return an error
00385        *\note that initialize() should have been called before calling this function, either directly or implicitly in the constructor.
00386        */
00387       GError* digital_alloy_optimizer_1d_mpi::do_optimization(double (*get_value)(void* privdat, unsigned assignment, long unsigned iteration), void* get_value_privdat, void* set_point_privdat, double anneal_start, double anneal_end, unsigned n_anneal_steps, unsigned n_anneal_cycles, unsigned num_calculations_per_annealing_step) {
00388   long unsigned iteration=0;
00389   if(node == 0) {
00390     double value = get_value(get_value_privdat, 0, iteration);
00391     if(unlikely(isnan(value))) {
00392       return g_error_new(DIGITAL_ALLOY_OPTIMIZER_1D_MPI_GERROR_DOMAIN, 101, "kdotp::libkdotp::optimizer::digital_alloy_optimizer_1d_mpi.c::do_optimization: Got NaN (%g) from get_value callback in the %lu%s iteration.  Iteration has been halted.\n", value, iteration, (iteration%10==1&&iteration!=11)?"st":(iteration%10==2&&iteration!=12)?"nd":(iteration%10==3&&iteration!=13)?"rd":"th");
00393     }else if(unlikely(isinf(value))) {
00394       return g_error_new(DIGITAL_ALLOY_OPTIMIZER_1D_MPI_GERROR_DOMAIN, 101, "kdotp::libkdotp::optimizer::digital_alloy_optimizer_1d_mpi.c::do_optimization: Got inf (%g) from get_value callback in the %lu%s iteration.  Iteration has been halted.\n", value, iteration, (iteration%10==1&&iteration!=11)?"st":(iteration%10==2&&iteration!=12)?"nd":(iteration%10==3&&iteration!=13)?"rd":"th");
00395     }
00396     previous_value = value;
00397   }
00398   if(n_anneal_cycles == 0) {
00399     fprintf(stderr, "(iter %lu) assignment for node %u: %u (%u-%u)\n", iteration, node, node_assignments[node+1]-node_assignments[node], node_assignments[node], node_assignments[node+1]);
00400     do {
00401       ++iteration;
00402       for(int i=0; i<(node_assignments[node+1]-node_assignments[node]); ++i) {
00403         toggle_block(node_assignments[node]+i, set_point_privdat);
00404         results[i] = get_value(get_value_privdat, i, iteration);
00405         toggle_block(node_assignments[node]+i, set_point_privdat);
00406         fprintf(stderr, "(iter %lu) node %u: assignment %u: %g\n", iteration, node, i, results[i]);
00407       }
00408     }while(!do_step(set_point_privdat));
00409   }else{
00410     for(unsigned anneal_cycle=0; anneal_cycle < n_anneal_cycles; ++anneal_cycle) {
00411       T = anneal_start;
00412       /*We iterate over a cycle-total calculation step; temperature is set based on the annealing step*/
00413       for(unsigned calc_step=0; calc_step < n_anneal_steps*num_calculations_per_annealing_step; ++calc_step) {
00414         ++iteration;
00415         /*Parentheses around division ensures it's done as integer, leading to rounding down*/
00416         T = anneal_start + (calc_step/num_calculations_per_annealing_step)*(anneal_end-anneal_start)/(n_anneal_steps-1);
00417         for(int i=0; i<=(node_assignments[node+1]-node_assignments[node]); ++i) {
00418     toggle_block(node_assignments[node]+i, set_point_privdat);
00419     results[i] = get_value(get_value_privdat, i, iteration);
00420     toggle_block(node_assignments[node]+i, set_point_privdat);
00421         }
00422         /*If we converge before we hit the end of an annealing cycle, terminate the cycle early.*/
00423         if(do_step(set_point_privdat)) break;
00424       }
00425     }
00426   }
00427   return NULL;
00428       }
00429     }
00430   }
00431 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines