|
k-dot-p 0.1
|
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 }