|
k-dot-p 0.1
|
00001 /* 00002 *Optimizes a material according to some number. 00003 00004 00005 Copyright (C) 2010 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 <gsl/gsl_errno.h> 00023 #include <libmetacalc/continuous_alloy_optimizer_1d.h++> 00024 #include <libmodelxx/cartesian_grid.h++> 00025 #include <libmodelxx/randomness.h++> 00026 #include <libkdotp/gcc_hints.h> 00027 #include <libmodelxx/function_specification.h++> 00028 00029 #define CONTINUOUS_ALLOY_OPTIMIZER_1D_GERROR_DOMAIN 73 00030 00031 #define EPSILON 1e-9 00032 00033 using namespace kdotp::libmodelxx::grid; 00034 using namespace kdotp::libmodelxx::structure; 00035 00036 namespace kdotp { 00037 namespace libmetacalc { 00038 namespace optimizer { 00039 00040 continuous_alloy_optimizer_1d 00041 ::continuous_alloy_optimizer_1d( 00042 kdotp::libmodelxx::structure::material* marker_material, 00043 const char* _mat1, 00044 const char* _mat0, 00045 kdotp::libmodelxx::structure::generic_structure* s, 00046 set_point_callback _set_point, 00047 unsigned _blocksize, 00048 enum goal _goal, 00049 GError** err, 00050 void* privdat, 00051 struct matdb *_mdb, 00052 double _minx, 00053 double _maxx, 00054 long unsigned int rng_seed, 00055 double starting_stepsize, 00056 unsigned _N_blocks, 00057 GList* assignments, 00058 struct ::kdotp::libmodelxx::postprocessing::function_exp *assignment_expr, 00059 GHashTable *global_symbols 00060 ) 00061 : mat1(NULL), 00062 mat0(NULL), 00063 current_assignments(NULL), 00064 material_is_optimum(NULL), 00065 genstruct(s), 00066 call_set_point_callback(_set_point), 00067 blocksize(_blocksize), 00068 points_in_block(NULL), 00069 point_start(NULL), 00070 previous_value(0), 00071 current_block(NULL), 00072 goal(_goal), 00073 seed(rng_seed), 00074 stepsize(starting_stepsize), 00075 mdb(_mdb), 00076 minx(_minx), 00077 maxx(_maxx) 00078 { 00079 cartesian_grid<material*, 1, 1>* matgrid = (cartesian_grid<material*, 1, 1>*)(genstruct->material_grid); 00080 /*Assign material strings.*/ 00081 GString *str = g_string_new(_mat1); 00082 mat1=str->str; 00083 g_string_free(str, FALSE); 00084 str = g_string_new(_mat0); 00085 mat0=str->str; 00086 g_string_free(str, FALSE); 00087 /*Easy way to make this a GList: start off with list=NULL, create a new (start,end) struct 00088 * on start, save it to the list on end. leave the started variable as it is. 00089 */ 00090 /*If marker_mat_in_grid is NULL, we've not started yet.*/ 00091 material** marker_mat_in_grid = NULL; 00092 material* nullmat = NULL; 00093 for(unsigned X=0; X<matgrid->L[0]; X++) { 00094 if((**(matgrid->get_point_r(&X))) == *marker_material) { 00095 /*Set this point to NULL to prevent deleting this material*/ 00096 matgrid->set_point_(&X, nullmat); 00097 if(marker_mat_in_grid == NULL) { 00098 start_X = X; 00099 marker_mat_in_grid = matgrid->get_point_r(&X); 00100 } 00101 }else if(marker_mat_in_grid != NULL) { 00102 /*We've stopped*/ 00103 stop_X = X; 00104 /*Mark this region with NULL so we don't re-free the material.*/ 00105 delete *marker_mat_in_grid; 00106 marker_mat_in_grid = NULL; 00107 } 00108 } 00109 if(marker_mat_in_grid != NULL) { 00110 /*We started but hit the end without switching out of the material. Odd, but solvable.*/ 00111 stop_X = matgrid->L[0]; 00112 /*Mark this region with NULL so we don't re-free the material.*/ 00113 delete *marker_mat_in_grid; 00114 } 00115 /*We could probably do this with rounding, but this only happens once and works.*/ 00116 if(_N_blocks > 0) { 00117 N_blocks = (unsigned)_N_blocks; 00118 blocksize = ((stop_X-start_X) - (stop_X-start_X)%N_blocks)/N_blocks; 00119 }else{ 00120 N_blocks = ((stop_X-start_X) - (stop_X-start_X)%blocksize)/blocksize; 00121 } 00122 /*Allocate arrays*/ 00123 points_in_block = new unsigned[N_blocks]; 00124 point_start = new unsigned[N_blocks]; 00125 current_assignments = new double[N_blocks]; 00126 /*Previous assignments is NULL in order to know that we have no previous assignment or value. 00127 */ 00128 material_is_optimum = new short unsigned[2*N_blocks]; 00129 /*Map the points to the blocks*/ 00130 for(unsigned i=0; i<N_blocks; i++) { 00131 points_in_block[i] = blocksize; 00132 } 00133 unsigned points_remaining = (stop_X - start_X)-blocksize*N_blocks; 00134 if(points_remaining != 0) { 00135 unsigned pos=points_remaining/N_blocks; 00136 for(unsigned pt=0; pt<points_remaining; pt++) { 00137 points_in_block[pos]++; 00138 pos += points_remaining/N_blocks; 00139 if(pos >= N_blocks) pos = pos%N_blocks; 00140 } 00141 } 00142 /*Put in the offsets.*/ 00143 unsigned final_point = start_X; 00144 for(unsigned block=0; block<N_blocks; block++) { 00145 point_start[block] = final_point; 00146 final_point += points_in_block[block]; 00147 } 00148 00149 /*allocate and then seed the random number generator*/ 00150 GError *e = NULL; 00151 gsl_rng_env_setup(); 00152 rng = gsl_rng_alloc(gsl_rng_default); 00153 if(rng == NULL) { 00154 *err = g_error_new(CONTINUOUS_ALLOY_OPTIMIZER_1D_GERROR_DOMAIN, 0, "kdotp::libkdotp::optimizer::continuous_alloy_optimizer_1d.c::continuous_alloy_optimizer_1d(constructor): Failed to set up GSL random number generator."); 00155 return; 00156 } 00157 /*Set the seed randomly if it's not already set*/ 00158 if(rng_seed == 0) { 00159 /*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*/ 00160 seed=0; 00161 while((seed == 0) && (e == NULL)) { 00162 seed = kdotp::libmodelxx::randomness::get_urandom_unsigned_long_int(&e); 00163 } 00164 if(e != NULL) { 00165 g_propagate_prefixed_error(err, e, "kdotp::libkdotp::optimizer::continuous_alloy_optimizer_1d.c::continuous_alloy_optimizer_1d(constructor): Failed to get random seed:\n\t"); 00166 return; 00167 } 00168 fprintf(stderr, "rng_seed was zero, so got new one (seed=%lu rng_seed=%lu)\n", seed, rng_seed); 00169 }else{ 00170 fprintf(stderr, "rng_seed was non-zero, so keeping it (seed=%lu rng_seed=%lu)\n", seed, rng_seed); 00171 } 00172 fprintf(stderr, "\n\nseed is %lu\n\n", seed); 00173 /*We now seed the rng*/ 00174 gsl_rng_set(rng, seed); 00175 /*Begin initialization.*/ 00176 initialize(privdat); 00177 /*At this point, we should be set up. 00178 *The user should perform their first calculation, and call us back using one of the do_*_step funx.*/ 00179 } 00180 00181 void continuous_alloy_optimizer_1d::initialize(void* privdat) { 00182 if(current_block != NULL) { 00183 delete current_block; 00184 current_block = NULL; 00185 } 00186 reset_optimum_array(); 00187 randomize_array(current_assignments, N_blocks); 00188 set_material_from_current_assignments(privdat); 00189 } 00190 00191 void continuous_alloy_optimizer_1d::initialize(const short unsigned* state, void* privdat) { 00192 if(current_block != NULL) { 00193 delete current_block; 00194 current_block = NULL; 00195 } 00196 reset_optimum_array(); 00197 for(unsigned i=0; i<N_blocks; i++) current_assignments[i] = state[i]; 00198 set_material_from_current_assignments(privdat); 00199 } 00200 00201 continuous_alloy_optimizer_1d::~continuous_alloy_optimizer_1d() { 00202 if(mat1 != NULL) g_free(mat1); 00203 if(mat0 != NULL) g_free(mat0); 00204 if(current_assignments != NULL) delete[] current_assignments; 00205 if(material_is_optimum != NULL) delete[] material_is_optimum; 00206 if(points_in_block != NULL) delete[] points_in_block; 00207 if(current_block != NULL) delete current_block; 00208 } 00209 00210 /**Does the first step in the algorithm. 00211 *\param value the value that we're tracking 00212 *\param privdat pointer to data for the set_point callback. 00213 *\return 0 if we've not converged, or 1 if we've converged. 00214 */ 00215 int continuous_alloy_optimizer_1d::do_first_step(double value, void* privdat) { 00216 /*There's no way we can yet be converged. We don't even know if this 00217 * point is optimal. This is our first point.*/ 00218 previous_value=value; 00219 current_block = new unsigned; 00220 double sign; 00221 *current_block = get_random_unoptimized_block(&sign); 00222 /*Save this block's current composition.*/ 00223 prev_x = get_x(*current_block); 00224 step_block(*current_block, privdat); 00225 return 0; 00226 } 00227 /**Does the later steps in the algorithm 00228 *\param value the value that we're tracking 00229 *\param privdat pointer to data for the set_point callback. 00230 *\return 0 if we've not converged, or 1 if we've converged. 00231 */ 00232 int continuous_alloy_optimizer_1d::do_subsequent_step(double value, void* privdat) { 00233 if(is_improved(value)) { 00234 /*This is an improved point!*/ 00235 /*We don't know right now if any other points are optimized.*/ 00236 reset_optimum_array(); 00237 /*This direction (+ or -) succeeded, so the *other* direction will fail and therefore is 00238 * already optimized. 00239 *So set that the *other* direction is optimized. 00240 */ 00241 set_block_as_optimum((*current_block + N_blocks)%(2*N_blocks)); 00242 previous_value = value; 00243 fprintf(stderr, "RESET ARRAY: previous_value=%g (value=%g)\n", previous_value, value); 00244 }else{ 00245 /*Choosing this point worsened the structure. Go back, set this as optimized, and choose a new point.*/ 00246 /*Algorithm to get sign without a branch: 00247 *1) Get the real block 00248 *2) Get the offset between the signed and real block. This will either be 0 or N_blocks 00249 *3) Divide by N_blocks/2 (maps to range [0, 2]) 00250 *4) Subtract 1 (maps to range [-1, 1]) 00251 *\note that we need -1 times this, to undo the previous step 00252 *\note that we now reset the block to its previous x instead of adding and then subtracting 00253 */ 00254 set_block(*current_block, privdat, prev_x); 00255 fprintf(stderr, "REVERT BLOCK %u: previous_value=%g (value=%g)\n", *current_block, previous_value, value); 00256 /**We know that the block that just got tested was already optimal. 00257 *\note that we don't know whether the other direction (+ or -) is optimized! */ 00258 set_block_as_optimum(*current_block); 00259 /*Note that we don't set the previous value to this current value; 00260 *it's not an improvement! 00261 */ 00262 } 00263 /*Check to see if they're all optimized.*/ 00264 if(N_optimum == 2*N_blocks) { 00265 fprintf(stderr, "ALPHA IS LOCAL OPTIMUM: N_optimum=%u 2*N_blocks=%u\n", N_optimum, 2*N_blocks); 00266 return 1; 00267 }else{ 00268 fprintf(stderr, "ALPHA IS (NOT) LOCAL OPTIMUM: N_optimum=%u 2*N_blocks=%u\n", N_optimum, 2*N_blocks); 00269 } 00270 /*Get the randomly-selected new block.*/ 00271 double sign; 00272 bool success; 00273 do { 00274 *current_block = get_random_unoptimized_block(&sign); 00275 prev_x = get_x(*current_block); 00276 /*Set the block.*/ 00277 success = step_block(*current_block, privdat); 00278 if(!success) { 00279 /*If we failed, this block is optimum!*/ 00280 set_block_as_optimum(*current_block); 00281 /*We may have finished optimizing!*/ 00282 if(N_optimum == 2*N_blocks) { 00283 fprintf(stderr, "stepping block %u failed, and ALPHA IS LOCAL OPTIMUM: N_optimum=%u 2*N_blocks=%u\n", *current_block, N_optimum, 2*N_blocks); 00284 return 1; 00285 }else{ 00286 fprintf(stderr, "stepping block %u failed, but ALPHA IS (NOT) LOCAL OPTIMUM: N_optimum=%u 2*N_blocks=%u\n", *current_block, N_optimum, 2*N_blocks); 00287 } 00288 } 00289 }while(!success); 00290 return 0; 00291 } 00292 /**Does any step in the algorithm, but checks to see if we've taken the first step yet 00293 *\param value the value that we're tracking 00294 *\param privdat pointer to data for the set_point callback. 00295 *\return 0 if we've not converged, or 1 if we've converged. 00296 */ 00297 int continuous_alloy_optimizer_1d::do_generic_step(double value, void* privdat) { 00298 if(current_block == NULL) { 00299 return do_first_step(value, privdat); 00300 } 00301 return do_subsequent_step(value, privdat); 00302 } 00303 00304 /*This performs the calculation without stepping through manually. 00305 *At each step, get_value is called with privdat 00306 *\param get_value callback to use after setting the materials. 00307 *\param privdat private data for the callback 00308 *\param step_fraction when we're optimized at the current stepsize, we multiply the stepsize by this factor to get the new stepsize 00309 *\param min_stepsize minimum stepsize to iterate over. When stepsize gets below this value, we stop. 00310 *\return NULL if we converged; GError* if there was an error. 00311 *\note that if NaN is returned, we stop and return an error 00312 *\note that initialize() should have been called before calling this function, either directly or implicitly in the constructor. 00313 */ 00314 GError* continuous_alloy_optimizer_1d::do_optimization(double (*get_value)(void* privdat, unsigned assignment, long unsigned iteration), void* get_value_privdat, void* set_point_privdat, double step_fraction, double min_stepsize) { 00315 long unsigned iteration=0; 00316 do { 00317 double value = get_value(get_value_privdat, 0, iteration++); 00318 if(unlikely(isnan(value))) { 00319 return g_error_new(CONTINUOUS_ALLOY_OPTIMIZER_1D_GERROR_DOMAIN, 101, "kdotp::libkdotp::optimizer::continuous_alloy_optimizer_1d.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"); 00320 }else if(unlikely(isinf(value))) { 00321 return g_error_new(CONTINUOUS_ALLOY_OPTIMIZER_1D_GERROR_DOMAIN, 101, "kdotp::libkdotp::optimizer::continuous_alloy_optimizer_1d.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"); 00322 } 00323 do_first_step(value, set_point_privdat); 00324 do { 00325 value = get_value(get_value_privdat, 0, iteration++); 00326 if(unlikely(isnan(value))) { 00327 return g_error_new(CONTINUOUS_ALLOY_OPTIMIZER_1D_GERROR_DOMAIN, 101, "kdotp::libkdotp::optimizer::continuous_alloy_optimizer_1d.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"); 00328 }else if(unlikely(isinf(value))) { 00329 return g_error_new(CONTINUOUS_ALLOY_OPTIMIZER_1D_GERROR_DOMAIN, 101, "kdotp::libkdotp::optimizer::continuous_alloy_optimizer_1d.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"); 00330 } 00331 }while(!(do_subsequent_step(value, set_point_privdat))); 00332 /*We can't optimize anymore at this step size.*/ 00333 stepsize *= step_fraction; 00334 /*Free the current_block so it'll be reallocated after first iteration 00335 *Then reset our optimized steps.. 00336 */ 00337 delete current_block; 00338 reset_optimum_array(); 00339 }while(stepsize >= min_stepsize); 00340 return NULL; 00341 } 00342 00343 } 00344 } 00345 }