crown
1.0.0
|
00001 /* 00002 Copyright 2015 Nicolas Melot 00003 00004 This file is part of Crown. 00005 00006 Crown is free software: you can redistribute it and/or modify 00007 it under the terms of the GNU General Public License as published by 00008 the Free Software Foundation, either version 3 of the License, or 00009 (at your option) any later version. 00010 00011 Crown is distributed in the hope that it will be useful, 00012 but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 GNU General Public License for more details. 00015 00016 You should have received a copy of the GNU General Public License 00017 along with Crown. If not, see <http://www.gnu.org/licenses/>. 00018 00019 */ 00020 00021 00022 #include <fstream> 00023 #include <set> 00024 00025 #include <cstdlib> 00026 00027 #include <crown/allocation.h> 00028 #include <crown/mapping.h> 00029 #include <crown/scaling.h> 00030 #include <crown/annealing.h> 00031 #include <crown/crown.h> 00032 00033 #include <pelib/AmplInput.hpp> 00034 #include <pelib/AmplOutput.hpp> 00035 #include <pelib/Vector.hpp> 00036 #include <pelib/Matrix.hpp> 00037 #include <pelib/Set.hpp> 00038 00039 #ifdef debug 00040 #undef debug 00041 #endif 00042 00043 #if 01 00044 #define debug(var) cout << "[" << __FILE__ << ":" << __FUNCTION__ << ":" << __LINE__ << "] " << #var << " = \"" << var << "\"" << endl; 00045 #else 00046 #define debug(var) 00047 #endif 00048 00049 using namespace std; 00050 00051 namespace pelib 00052 { 00053 namespace crown 00054 { 00055 00056 // Quality assessment parameters 00057 const long long int nsec_in_sec = 1000000000; 00058 00059 static int 00060 timespec_subtract(struct timespec *result, struct timespec *x, struct timespec *y) 00061 { 00062 /* Perform the carry for the later subtraction by updating y. */ 00063 if (x->tv_nsec < y->tv_nsec) 00064 { 00065 int nsec = (y->tv_nsec - x->tv_nsec) / nsec_in_sec + 1; 00066 y->tv_nsec -= nsec_in_sec * nsec; 00067 y->tv_sec += nsec; 00068 } 00069 if (x->tv_nsec - y->tv_nsec > nsec_in_sec) 00070 { 00071 int nsec = (x->tv_nsec - y->tv_nsec) / nsec_in_sec; 00072 y->tv_nsec += nsec_in_sec * nsec; 00073 y->tv_sec -= nsec; 00074 } 00075 00076 /* Compute the time remaining to wait. tv_nsec is certainly positive. */ 00077 result->tv_sec = x->tv_sec - y->tv_sec; 00078 result->tv_nsec = x->tv_nsec - y->tv_nsec; 00079 00080 /* Return 1 if result is negative. */ 00081 return x->tv_sec < y->tv_sec; 00082 } 00083 00084 // This is not valid if we consider swithing off unused cores 00085 #if RETURN_EARLY && 0 00086 #define return_now_if_best(solution) if(solution.find<Scalar<float> >("all_sequential")->getValue() == 1 && solution.find<Scalar<float> >("frequency_optimal")->getValue() == 1) return solution 00087 #else 00088 #define return_now_if_best(solution) 00089 #endif 00090 00091 static Algebra 00092 crown_binary_quality(const Algebra &taskgraph, float (*quality)(const Algebra &solution), unsigned int precision) 00093 { 00094 Algebra schedule = taskgraph; 00095 float threshold = 0.5; 00096 float delta = 0.25; 00097 float derivative, min_derivative = 1; 00098 unsigned int round = 0; 00099 //int need_schedule = 1; 00100 float m = 0; 00101 Algebra current, best; 00102 float power = 0, best_power; 00103 00104 // Compute allocation for maximal parallelization 00105 // This is is to get an allocation if nothing else works 00106 // Yet it is not guaranteed to produce a valid schedule 00107 // because of a still too high makespan presure 00108 best = schedule; 00109 00110 best = allocation_fastest(best); 00111 best = mapping_ltlg(best); 00112 best = frequency_height(best); 00113 best_power = quality(best); 00114 00115 //return_now_if_best(best); 00116 00117 float emax = 0, emin = 1; 00118 float previous = 1; 00119 map<int, map<int, float> > map_e = schedule.find<Matrix<int, int, float> >("e")->getValues(); 00120 map<int, float> map_Wi = schedule.find<Vector<int, float> >("Wi")->getValues(); 00121 for(map<int, map<int, float> >::iterator row_iter = map_e.begin(); row_iter != map_e.end(); row_iter++) 00122 { 00123 int task = row_iter->first; 00124 int Wi = (int)map_Wi[task]; 00125 map<int, float> map_row = row_iter->second; 00126 00127 for(map<int, float>::iterator col_iter = map_row.begin(); col_iter != map_row.end() && col_iter->first <= Wi; col_iter++) 00128 { 00129 if(col_iter->second < 1) 00130 { 00131 if(col_iter->second > emax) 00132 { 00133 emax = col_iter->second; 00134 } 00135 00136 if(col_iter->second < emin) 00137 { 00138 emin = col_iter->second; 00139 } 00140 00141 derivative = previous - col_iter->second; 00142 if(derivative < min_derivative) 00143 { 00144 min_derivative = derivative; 00145 } 00146 } 00147 } 00148 } 00149 00150 // Binary search for maximal minimum efficiency for all tasks 00151 //bool threshold_ok = true; 00152 bool retry = true; 00153 00154 // Try a whole sequential schedule 00155 current = schedule; 00156 current = allocation_gemin(current, 1); 00157 current = mapping_ltlg(current); 00158 current = frequency_height(current); 00159 00160 //AmplOutput(AmplOutput::floatHandlers()).dump(cerr, current); 00161 //return_now_if_best(current); 00162 00163 // Check if this allocation could produce a valid mapping 00164 // m is the minimal slack time 00165 m = current.find<Scalar<float> >("m")->getValue(); 00166 if(m > 0) 00167 { 00168 power = quality(current); 00169 if(power <= best_power) 00170 { 00171 best_power = power; 00172 best = current; 00173 } 00174 } 00175 00176 // Loop while the current threshold is lower than the maximum non-sequential efficiency 00177 // and delta is higher than the minimal efficiency gap between two allocations for any task 00178 // And, if any maximum round was given, this threshold was not reached 00179 while(retry && delta * 2 > min_derivative && ((precision != 0 && round < precision) || precision == 0)) 00180 { 00181 // Reset the current solution to input 00182 current = schedule; 00183 00184 // Allocate with current threshold then run mapping and frequency scaling 00185 //cerr << "######################################" << endl; 00186 //AmplInput().dump(cerr, current); 00187 //cerr << endl; 00188 00189 current = allocation_gemin(current, threshold); 00190 00191 //AmplInput().dump(cerr, current); 00192 00193 current = mapping_ltlg(current); 00194 current = frequency_height(current); 00195 00196 //return_now_if_best(current); 00197 00198 // Check if this allocation could produce a valid mapping 00199 // m is the minimal slack time 00200 m = current.find<Scalar<float> >("m")->getValue(); 00201 00202 // Update the threshold after the feasibility 00203 if(m > 0) 00204 { 00205 power = quality(current); 00206 #if SHOWPOWER 00207 // Output the energy of current schedule 00208 cout << "# Statistics: " << threshold << " " << power << endl; 00209 #endif 00210 // This is a valid schedule 00211 // Retry if current threshold is lower than maximal efficiency 00212 retry = threshold < emax; 00213 00214 // Increase threshold by delta 00215 threshold = threshold + delta; 00216 00217 // Keep this schedule as the best found so far 00218 if(power < best_power) 00219 { 00220 best = current; 00221 } 00222 } 00223 else 00224 { 00225 // This was an invalid schedule 00226 retry = true; 00227 00228 // Allow lower efficiency 00229 threshold = threshold - delta; 00230 } 00231 00232 // Reduce the delta 00233 delta = delta / 2; 00234 00235 // Count the number of rounds 00236 round++; 00237 } 00238 00239 Scalar<float> best_quality_scalar("quality", best_power); 00240 best.remove("best_quality"); 00241 best.insert(&best_quality_scalar); 00242 00243 /* 00244 Scalar<float> scalar_threshold = Scalar<float>("gemin", threshold); 00245 best.insert(&scalar_threshold); 00246 Scalar<float> scalar_gemax = Scalar<float>("gemax", emax); 00247 best.insert(&scalar_gemax); 00248 */ 00249 00250 // Compute and insert scheduling time 00251 /* 00252 Scalar<float> scalar_round = Scalar<float>("round", round); 00253 best.insert(&scalar_round); 00254 */ 00255 00256 return best; 00257 } 00258 00259 Algebra 00260 crown_binary_simple(const Algebra &taskgraph, unsigned int precision) 00261 { 00262 return crown_binary_quality(taskgraph, quality_simple, precision); 00263 } 00264 00265 Algebra 00266 crown_binary_idle(const Algebra &taskgraph, unsigned int precision) 00267 { 00268 return crown_binary_quality(taskgraph, quality_idle, precision); 00269 } 00270 00271 Algebra 00272 crown_binary_off(const Algebra &taskgraph, unsigned int precision) 00273 { 00274 return crown_binary_quality(taskgraph, quality_off, precision); 00275 } 00276 00277 Algebra 00278 crown_binary(const Algebra &taskgraph, unsigned int precision) 00279 { 00280 return crown_binary_quality(taskgraph, energy_static_dynamic_busy_idle_active, precision); 00281 } 00282 00283 Algebra 00284 crown_binary_annealing_allocation(const Algebra &initial, float temperature, float cooling, float final, int max_transform, int max_new_state, float distance) 00285 { 00286 Algebra schedule = initial; 00287 Algebra best = schedule; 00288 // Compute an initial best solution 00289 best = crown_binary(best, 0); 00290 00291 return annealing(best, best, energy_static_dynamic_busy_idle_active, neighbor_allocation_tasks, temperature, cooling, final, max_transform, max_new_state, distance); 00292 } 00293 00294 Algebra 00295 crown_annealing_allocation_idle(const Algebra &initial, float temperature, float cooling, float final, int max_transform, int max_new_state, float distance) 00296 { 00297 struct timespec start, stop, total_time; 00298 Algebra schedule = initial; 00299 00300 Algebra best = schedule; 00301 clock_gettime(CLOCK_MONOTONIC, &start); 00302 // Compute an initial best solution 00303 best = allocation_fastest(best); 00304 best = mapping_ltlg(best); 00305 best = frequency_height(best); 00306 00307 // Generate a random initial solution 00308 schedule = allocation_random(schedule); 00309 schedule = mapping_ltlg(schedule); 00310 schedule = frequency_height(schedule); 00311 00312 schedule = annealing(schedule, best, quality_idle, neighbor_allocation_full, temperature, cooling, final, max_transform, max_new_state, distance); 00313 clock_gettime(CLOCK_MONOTONIC, &stop); 00314 00315 timespec_subtract(&total_time, &stop, &start); 00316 Scalar<float> param_time("_total_solve_elapsed_time", (float)(total_time.tv_sec * nsec_in_sec + total_time.tv_nsec) / (float)nsec_in_sec); 00317 schedule.insert(¶m_time); 00318 00319 return schedule; 00320 } 00321 00322 Algebra 00323 crown_annealing_allocation_off(const Algebra &initial, float temperature, float cooling, float final, int max_transform, int max_new_state, float distance) 00324 { 00325 struct timespec start, stop, total_time; 00326 Algebra schedule = initial; 00327 00328 Algebra best = schedule; 00329 clock_gettime(CLOCK_MONOTONIC, &start); 00330 // Compute an initial best solution 00331 best = allocation_fastest(best); 00332 best = mapping_ltlg(best); 00333 best = frequency_height(best); 00334 00335 // Generate a random initial solution 00336 schedule = allocation_random(schedule); 00337 schedule = mapping_ltlg(schedule); 00338 schedule = frequency_height(schedule); 00339 00340 schedule = annealing(schedule, best, quality_off, neighbor_allocation_full, temperature, cooling, final, max_transform, max_new_state, distance); 00341 clock_gettime(CLOCK_MONOTONIC, &stop); 00342 00343 timespec_subtract(&total_time, &stop, &start); 00344 Scalar<float> param_time("_total_solve_elapsed_time", (float)(total_time.tv_sec * nsec_in_sec + total_time.tv_nsec) / (float)nsec_in_sec); 00345 schedule.insert(¶m_time); 00346 00347 return schedule; 00348 } 00349 00350 Algebra 00351 crown_annealing_allocation_simple(const Algebra &initial, float temperature, float cooling, float final, int max_transform, int max_new_state, float distance) 00352 { 00353 struct timespec start, stop, total_time; 00354 Algebra schedule = initial; 00355 Algebra best = schedule; 00356 00357 clock_gettime(CLOCK_MONOTONIC, &start); 00358 00359 // Compute an initial best solution 00360 best = allocation_fastest(best); 00361 best = mapping_ltlg(best); 00362 best = frequency_height(best); 00363 00364 // Generate a random initial solution 00365 schedule = allocation_random(schedule); 00366 schedule = mapping_ltlg(schedule); 00367 schedule = frequency_height(schedule); 00368 00369 schedule = annealing(schedule, best, quality_simple, neighbor_allocation_full, temperature, cooling, final, max_transform, max_new_state, distance); 00370 clock_gettime(CLOCK_MONOTONIC, &stop); 00371 00372 timespec_subtract(&total_time, &stop, &start); 00373 Scalar<float> param_time("_total_solve_elapsed_time", (float)(total_time.tv_sec * nsec_in_sec + total_time.tv_nsec) / (float)nsec_in_sec); 00374 schedule.insert(¶m_time); 00375 00376 return schedule; 00377 } 00378 00379 Algebra 00380 crown_binary_annealing_allocation_idle(const Algebra &initial, float temperature, float cooling, float final, int max_transform, int max_new_state, float distance) 00381 { 00382 struct timespec start, stop, total_time; 00383 Algebra schedule = initial; 00384 00385 Algebra best = schedule; 00386 clock_gettime(CLOCK_MONOTONIC, &start); 00387 // Compute an initial best solution 00388 best = crown_binary_idle(best, 0); 00389 00390 schedule = annealing(best, best, quality_idle, neighbor_allocation_tasks, temperature, cooling, final, max_transform, max_new_state, distance); 00391 clock_gettime(CLOCK_MONOTONIC, &stop); 00392 00393 timespec_subtract(&total_time, &stop, &start); 00394 Scalar<float> param_time("_total_solve_elapsed_time", (float)(total_time.tv_sec * nsec_in_sec + total_time.tv_nsec) / (float)nsec_in_sec); 00395 schedule.insert(¶m_time); 00396 00397 return schedule; 00398 } 00399 00400 Algebra 00401 crown_binary_annealing_allocation_off(const Algebra &initial, float temperature, float cooling, float final, int max_transform, int max_new_state, float distance) 00402 { 00403 struct timespec start, stop, total_time; 00404 Algebra schedule = initial; 00405 00406 Algebra best = schedule; 00407 clock_gettime(CLOCK_MONOTONIC, &start); 00408 // Compute an initial best solution 00409 best = crown_binary_off(best, 0); 00410 00411 schedule = annealing(best, best, quality_off, neighbor_allocation_tasks, temperature, cooling, final, max_transform, max_new_state, distance); 00412 clock_gettime(CLOCK_MONOTONIC, &stop); 00413 00414 timespec_subtract(&total_time, &stop, &start); 00415 Scalar<float> param_time("_total_solve_elapsed_time", (float)(total_time.tv_sec * nsec_in_sec + total_time.tv_nsec) / (float)nsec_in_sec); 00416 schedule.insert(¶m_time); 00417 00418 return schedule; 00419 } 00420 00421 Algebra 00422 crown_binary_annealing_allocation_simple(const Algebra &initial, float temperature, float cooling, float final, int max_transform, int max_new_state, float distance) 00423 { 00424 struct timespec start, stop, total_time; 00425 Algebra schedule = initial; 00426 00427 Algebra best = schedule; 00428 clock_gettime(CLOCK_MONOTONIC, &start); 00429 // Compute an initial best solution 00430 best = crown_binary_simple(best, 0); 00431 00432 schedule = annealing(best, best, quality_simple, neighbor_allocation_full, temperature, cooling, final, max_transform, max_new_state, distance); 00433 clock_gettime(CLOCK_MONOTONIC, &stop); 00434 00435 timespec_subtract(&total_time, &stop, &start); 00436 Scalar<float> param_time("_total_solve_elapsed_time", (float)(total_time.tv_sec * nsec_in_sec + total_time.tv_nsec) / (float)nsec_in_sec); 00437 schedule.insert(¶m_time); 00438 00439 return schedule; 00440 } 00441 00442 static Algebra 00443 add_extreme_e(const Algebra &schedule) 00444 { 00445 float emax = 0, emin = 1; 00446 //float previous = 1; 00447 map<int, map<int, float> > map_e = schedule.find<Matrix<int, int, float> >("e")->getValues(); 00448 map<int, float> map_Wi = schedule.find<Vector<int, float> >("Wi")->getValues(); 00449 for(map<int, map<int, float> >::iterator row_iter = map_e.begin(); row_iter != map_e.end(); row_iter++) 00450 { 00451 int task = row_iter->first; 00452 int Wi = (int)map_Wi[task]; 00453 map<int, float> map_row = row_iter->second; 00454 00455 for(map<int, float>::iterator col_iter = map_row.begin(); col_iter != map_row.end() && col_iter->first <= Wi; col_iter++) 00456 { 00457 if(col_iter->second < 1) 00458 { 00459 if(col_iter->second > emax) 00460 { 00461 emax = col_iter->second; 00462 } 00463 00464 if(col_iter->second < emin) 00465 { 00466 emin = col_iter->second; 00467 } 00468 } 00469 } 00470 } 00471 00472 Algebra input = schedule; 00473 Scalar<float> scalar_threshold = Scalar<float>("gemin", emin); 00474 input.insert(&scalar_threshold); 00475 Scalar<float> scalar_gemax = Scalar<float>("gemax", emax); 00476 input.insert(&scalar_gemax); 00477 00478 return input; 00479 } 00480 00481 Algebra 00482 crown_binary_annealing_efficiency_simple(const Algebra &schedule, float temperature, float cooling, float final, int max_transform, int max_new_state, float distance) 00483 { 00484 struct timespec start, stop, total_time; 00485 00486 Algebra initial, best; 00487 clock_gettime(CLOCK_MONOTONIC, &start); 00488 00489 // Compute an initial solution 00490 initial = add_extreme_e(schedule); 00491 float gemin = initial.find<Scalar<float> >("gemin")->getValue(); 00492 float gemax = initial.find<Scalar<float> >("gemax")->getValue(); 00493 float random = (float)rand() / RAND_MAX; 00494 initial = allocation_gemin(initial, gemin + random * (gemax - gemin)); 00495 initial = mapping_ltlg(initial); 00496 initial = frequency_height(initial); 00497 00498 // Compute an initial best solution 00499 best = allocation_fastest(schedule); 00500 best = mapping_ltlg(best); 00501 best = frequency_height(best); 00502 00503 best = annealing(initial, best, quality_simple, neighbor_efficiency, temperature, cooling, final, max_transform, max_new_state, distance); 00504 clock_gettime(CLOCK_MONOTONIC, &stop); 00505 00506 timespec_subtract(&total_time, &stop, &start); 00507 Scalar<float> param_time("_total_solve_elapsed_time", (float)(total_time.tv_sec * nsec_in_sec + total_time.tv_nsec) / (float)nsec_in_sec); 00508 best.insert(¶m_time); 00509 00510 return best; 00511 } 00512 00513 Algebra 00514 crown_binary_annealing_efficiency_idle(const Algebra &schedule, float temperature, float cooling, float final, int max_transform, int max_new_state, float distance) 00515 { 00516 struct timespec start, stop, total_time; 00517 00518 Algebra initial, best; 00519 clock_gettime(CLOCK_MONOTONIC, &start); 00520 00521 // Compute an initial solution 00522 initial = add_extreme_e(schedule); 00523 float gemin = initial.find<Scalar<float> >("gemin")->getValue(); 00524 float gemax = initial.find<Scalar<float> >("gemax")->getValue(); 00525 float random = (float)rand() / RAND_MAX; 00526 initial = allocation_gemin(initial, gemin + random * (gemax - gemin)); 00527 initial = mapping_ltlg(initial); 00528 initial = frequency_height(initial); 00529 00530 // Compute an initial best solution 00531 best = allocation_fastest(schedule); 00532 best = mapping_ltlg(best); 00533 best = frequency_height(best); 00534 00535 best = annealing(initial, best, quality_idle, neighbor_efficiency, temperature, cooling, final, max_transform, max_new_state, distance); 00536 clock_gettime(CLOCK_MONOTONIC, &stop); 00537 00538 timespec_subtract(&total_time, &stop, &start); 00539 Scalar<float> param_time("_total_solve_elapsed_time", (float)(total_time.tv_sec * nsec_in_sec + total_time.tv_nsec) / (float)nsec_in_sec); 00540 best.insert(¶m_time); 00541 00542 return best; 00543 } 00544 00545 Algebra 00546 crown_binary_annealing_efficiency_off(const Algebra &schedule, float temperature, float cooling, float final, int max_transform, int max_new_state, float distance) 00547 { 00548 struct timespec start, stop, total_time; 00549 00550 Algebra initial, best; 00551 clock_gettime(CLOCK_MONOTONIC, &start); 00552 00553 // Compute an initial solution 00554 initial = add_extreme_e(schedule); 00555 float gemin = initial.find<Scalar<float> >("gemin")->getValue(); 00556 float gemax = initial.find<Scalar<float> >("gemax")->getValue(); 00557 float random = (float)rand() / RAND_MAX; 00558 initial = allocation_gemin(initial, gemin + random * (gemax - gemin)); 00559 initial = mapping_ltlg(initial); 00560 initial = frequency_height(initial); 00561 00562 // Compute an initial best solution 00563 best = allocation_fastest(schedule); 00564 best = mapping_ltlg(best); 00565 best = frequency_height(best); 00566 00567 best = annealing(initial, best, quality_off, neighbor_efficiency, temperature, cooling, final, max_transform, max_new_state, distance); 00568 clock_gettime(CLOCK_MONOTONIC, &stop); 00569 00570 timespec_subtract(&total_time, &stop, &start); 00571 Scalar<float> param_time("_total_solve_elapsed_time", (float)(total_time.tv_sec * nsec_in_sec + total_time.tv_nsec) / (float)nsec_in_sec); 00572 best.insert(¶m_time); 00573 00574 return best; 00575 } 00576 00577 float 00578 crown_binary_complexity(const Algebra &schedule, unsigned int precision) 00579 { 00580 if(precision > 0) 00581 { 00582 return precision; 00583 } 00584 else 00585 { 00586 float emax = 0, emin = 1; 00587 float previous = 1; 00588 float derivative, min_derivative = 0.25; 00589 map<int, map<int, float> > map_e = schedule.find<Matrix<int, int, float> >("e")->getValues(); 00590 map<int, float> map_Wi = schedule.find<Vector<int, float> >("Wi")->getValues(); 00591 00592 for(map<int, map<int, float> >::iterator row_iter = map_e.begin(); row_iter != map_e.end(); row_iter++) 00593 { 00594 int task = row_iter->first; 00595 int Wi = (int)map_Wi[task]; 00596 map<int, float> map_row = row_iter->second; 00597 00598 for(map<int, float>::iterator col_iter = map_row.begin(); col_iter != map_row.end() && col_iter->first <= Wi; col_iter++) 00599 { 00600 if(col_iter->first > 1) 00601 { 00602 if(col_iter->second > emax) 00603 { 00604 emax = col_iter->second; 00605 } 00606 00607 if(col_iter->second < emin) 00608 { 00609 emin = col_iter->second; 00610 } 00611 00612 derivative = previous - col_iter->second; 00613 //cerr << "derivative = " << derivative << endl; 00614 previous = col_iter->second; 00615 if(derivative < min_derivative) 00616 { 00617 min_derivative = derivative; 00618 //cerr << "min_derivative = " << min_derivative << endl; 00619 } 00620 //cerr << "previous = " << previous << endl; 00621 } 00622 else 00623 { 00624 previous = col_iter->second; // ... which should be always 1. 00625 //min_derivative = 0.25; // If a task is sequential, then the complexity will be 1. 00626 //cerr << "min_derivative = " << min_derivative << endl; 00627 } 00628 } 00629 } 00630 00631 //cerr << "min_derivative = " << min_derivative << endl; 00632 //cerr << ceil(log2 (0.5 / min_derivative)) << endl; 00633 return ceil(log2 (0.5 / min_derivative)); 00634 } 00635 } 00636 00637 } 00638 }