diff --git a/smp/tutorial/BaseLesson.h b/smp/tutorial/BaseLesson.h new file mode 100644 index 000000000..92d14e274 --- /dev/null +++ b/smp/tutorial/BaseLesson.h @@ -0,0 +1,272 @@ +#ifndef _BASELESSON_H +#define _BASELESSON_H +#include +#include +#include +#include +#include +#include + +using namespace std; + +int n = 10; +int** a; +int** b; + +int bkv; //best known value + +struct parameters +{ + unsigned seed ; + int popSize; + int tSize; + string inst; + string loadName; + string schema; + double pCross; + double pMut; + int minGen; + int maxGen; +}; + +class Indi : public EO { + +public: + + int* solution; + int evalNb = 0; + + Indi () { + solution = new int[n]; + create(); + } + + Indi (const Indi & _problem){ // copy constructor + solution = new int[n]; + for (int i = 0; i < n ; i++){ + solution[i] = _problem.solution[i]; + } + if (!_problem.invalid()) // if the solution has already been evaluated + fitness(_problem.fitness()); // copy the fitness + } + + ~Indi(){ // destructor + delete[] solution; + } + + void operator= (const Indi & _problem){ // copy assignment operator + for (int i = 0; i < n ; i++){ + solution[i] = _problem.solution[i]; + } + fitness(_problem.fitness()); // copy the fitness + } + + int& operator[] (unsigned i) + { + return solution[i]; + } + + + void create(){ // create and initialize a solution + int random, temp; + for (int i=0; i< n; i++) + solution[i]=i; + + // we want a random permutation so we shuffle + for (int i = 0; i < n; i++){ + random = rand()%(n-i) + i; + temp = solution[i]; + solution[i] = solution[random]; + solution[random] = temp; + } + } + + int evaluate() { // evaluate the solution + int cost=0; + for (int i=0; i +{ +public: + + void operator()(Indi & _problem) + { + _problem.create(); + } +}; + +class IndiEvalFunc : public eoEvalFunc +{ +public: + + void operator()(Indi & _problem) + { + _problem.fitness(_problem.evaluate()); + + } +}; + +class IndiXover : public eoQuadOp { +public: + + /* The two parameters in input are the parents. + The first parameter is also the output ie the child + */ + bool operator()(Indi & _problem1, Indi & _problem2) + { + int i; + int random, temp; + std::vector unassigned_positions(n); + std::vector remaining_items(n); + int j = 0; + + /* 1) find the items assigned in different positions for the 2 parents */ + for (i = 0 ; i < n ; i++){ + if (_problem1.solution[i] != _problem2.solution[i]){ + unassigned_positions[j] = i; + remaining_items[j] = _problem1.solution[i]; + j++; + } + } + + /* 2) shuffle the remaining items to ensure that remaining items + will be assigned at random positions */ + for (i = 0; i < j; i++){ + random = rand()%(j-i) + i; + temp = remaining_items[i]; + remaining_items[i] = remaining_items[random]; + remaining_items[random] = temp; + } + + /* 3) copy the shuffled remaining items at unassigned positions */ + for (i = 0; i < j ; i++) + _problem1.solution[unassigned_positions[i]] = remaining_items[i]; + + // crossover in our case is always possible + return true; + } +}; + +class IndiSwapMutation: public eoMonOp { +public: + + bool operator()(Indi& _problem) { + int i,j; + int temp; + + // generate two different indices + i=rand()%n; + do + j = rand()%n; + while (i == j); + + // swap + temp = _problem.solution[i]; + _problem.solution[i] = _problem.solution[j]; + _problem.solution[j] = temp; + + return true; + + } + +}; + +void parseFile(eoParser & parser, parameters & param) +{ + + // For each parameter, you can in on single line + // define the parameter, read it through the parser, and assign it + + param.seed = parser.createParam(unsigned(time(0)), "seed", "Random number seed", 'S').value(); // will be in default section General + + // init and stop + param.loadName = parser.createParam(string(""), "Load","A save file to restart from",'L', "Persistence" ).value(); + + param.inst = parser.createParam(string(""), "inst","a dat file to read instances from",'i', "Persistence" ).value(); + + param.schema = parser.createParam(string(""), "schema","an xml file mapping process",'s', "Persistence" ).value(); + + param.popSize = parser.createParam(unsigned(10), "popSize", "Population size",'P', "Evolution engine" ).value(); + + param.tSize = parser.createParam(unsigned(2), "tSize", "Tournament size",'T', "Evolution Engine" ).value(); + + param.minGen = parser.createParam(unsigned(100), "minGen", "Minimum number of iterations",'g', "Stopping criterion" ).value(); + + param.maxGen = parser.createParam(unsigned(300), "maxGen", "Maximum number of iterations",'G', "Stopping criterion" ).value(); + + param.pCross = parser.createParam(double(0.6), "pCross", "Probability of Crossover", 'C', "Genetic Operators" ).value(); + + param.pMut = parser.createParam(double(0.1), "pMut", "Probability of Mutation", 'M', "Genetic Operators" ).value(); + + + // the name of the "status" file where all actual parameter values will be saved + string str_status = parser.ProgramName() + ".status"; // default value + string statusName = parser.createParam(str_status, "status","Status file",'S', "Persistence" ).value(); + + // do the following AFTER ALL PARAMETERS HAVE BEEN PROCESSED + // i.e. in case you need parameters somewhere else, postpone these + if (parser.userNeedsHelp()) + { + parser.printHelp(cout); + exit(1); + } + if (statusName != "") + { + ofstream os(statusName.c_str()); + os << parser; // and you can use that file as parameter file + } +} + +void loadInstances(const char* filename, int& n, int& bkv, int** & a, int** & b) +{ + + ifstream data_file; + int i, j; + data_file.open(filename); + if (! data_file.is_open()) + { + cout << "\n Error while reading the file " << filename << ". Please check if it exists !" << endl; + exit (1); + } + data_file >> n; + data_file >> bkv; // best known value + // ****************** dynamic memory allocation ****************** / + a = new int* [n]; + b = new int* [n]; + for (i = 0; i < n; i++) + { + a[i] = new int[n]; + b[i] = new int[n]; + } + + // ************** read flows and distanceMatrixs matrices ************** / + for (i = 0; i < n; i++) + for (j = 0; j < n; j++) + data_file >> a[i][j]; + + for (i = 0; i < n; i++) + for (j = 0; j < n; j++) + data_file >> b[i][j]; + + data_file.close(); +} + +#endif diff --git a/smp/tutorial/CMakeLists.txt b/smp/tutorial/CMakeLists.txt index d4392ac2b..4f6087a69 100644 --- a/smp/tutorial/CMakeLists.txt +++ b/smp/tutorial/CMakeLists.txt @@ -3,3 +3,18 @@ ###################################################################################### add_subdirectory(Lesson1) +add_subdirectory(Lesson2) +add_subdirectory(Lesson3) +add_subdirectory(Lesson4) + +###################################################################################### +### 0) Install BaseFile for all lessons and data +###################################################################################### + +install(FILES BaseLesson.h DESTINATION include${INSTALL_SUB_DIR}/smp/tutorial COMPONENT tutorial) + +execute_process( + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/lessonData.dat + ${CMAKE_CURRENT_BINARY_DIR}/lessonData.dat) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/lessonData.dat DESTINATION share${INSTALL_SUB_DIR}/smp/tutorial COMPONENT tutorial) diff --git a/smp/tutorial/Lesson2/CMakeLists.txt b/smp/tutorial/Lesson2/CMakeLists.txt new file mode 100644 index 000000000..ad673e3c6 --- /dev/null +++ b/smp/tutorial/Lesson2/CMakeLists.txt @@ -0,0 +1,23 @@ +# Lesson 2 + +###################################################################################### +### 0) Define files +###################################################################################### + +set(files + lesson2_homogeneous + ) + +###################################################################################### +### 1) Create the lesson +###################################################################################### + +add_lesson(smp Lesson2 "${files}") + +###################################################################################### +### 2) Include dependencies +###################################################################################### + +include_directories(${EO_SRC_DIR}/src + ${SMP_SRC_DIR}/src + ${PROBLEMS_SRC_DIR}) diff --git a/smp/tutorial/Lesson2/lesson2_homogeneous.cpp b/smp/tutorial/Lesson2/lesson2_homogeneous.cpp new file mode 100644 index 000000000..9a908ce59 --- /dev/null +++ b/smp/tutorial/Lesson2/lesson2_homogeneous.cpp @@ -0,0 +1,153 @@ +/* + +Copyright (C) DOLPHIN Project-Team, INRIA Lille - Nord Europe, 2006-2012 + +Alexandre Quemy, Thibault Lasnier - INSA Rouen + +This software is governed by the CeCILL license under French law and +abiding by the rules of distribution of free software. You can ue, +modify and/ or redistribute the software under the terms of the CeCILL +license as circulated by CEA, CNRS and INRIA at the following URL +"http://www.cecill.info". + +In this respect, the user's attention is drawn to the risks associated +with loading, using, modifying and/or developing or reproducing the +software by the user in light of its specific status of free software, +that may mean that it is complicated to manipulate, and that also +therefore means that it is reserved for developers and experienced +professionals having in-depth computer knowledge. Users are therefore +encouraged to load and test the software's suitability as regards their +requirements in conditions enabling the security of their systems and/or +data to be ensured and, more generally, to use and operate it in the +same conditions as regards security. +The fact that you are presently reading this means that you have had +knowledge of the CeCILL license and that you accept its terms. + +ParadisEO WebSite : http://paradiseo.gforge.inria.fr +Contact: paradiseo-help@lists.gforge.inria.fr +*/ + +/*/////////////////////////////////////////////////////////////////// +// SMP Tutorial 2 : Homogeneous Model +// This file shows how to create an homogeneous +// island model with 3 islands, a complete topology +// and different parameters for each island. +/*/////////////////////////////////////////////////////////////////// + +#include +#include + +#include "../BaseLesson.h" + +using namespace paradiseo::smp; +using namespace std; + +int main(void) +{ + // Defining parameters + typedef struct { + unsigned popSize = 1000; + unsigned tSize = 2; + double pCross = 0.8; + double pMut = 0.7; + unsigned maxGen = 1000; + } Param; + + Param param; + + // Fixing the seed + rng.reseed(42); + + // Load instance + loadInstances("../lessonData.dat", n, bkv, a, b); + + //Common part to all islands + IndiEvalFunc plainEval; + IndiInit chromInit; + eoDetTournamentSelect selectOne(param.tSize); + eoSelectPerc select(selectOne);// by default rate==1 + IndiXover Xover; // CROSSOVER + IndiSwapMutation mutationSwap; // MUTATION + eoSGATransform transform(Xover, param.pCross, mutationSwap, param.pMut); + eoPlusReplacement replace; + + // MODEL + // Topologies + Topology topo; + IslandModel model(topo); + + // ISLAND 1 + // // Algorithm part + eoGenContinue genCont(param.maxGen+100); + eoPop pop(param.popSize, chromInit); + // // Emigration policy + // // // Element 1 + eoPeriodicContinue criteria(5); + eoDetTournamentSelect selectOne1(20); + eoSelectNumber who(selectOne1, 3); + + MigPolicy migPolicy; + migPolicy.push_back(PolicyElement(who, criteria)); + + // // Integration policy + eoPlusReplacement intPolicy; + + Island test(pop, intPolicy, migPolicy, genCont, plainEval, select, transform, replace); + + // ISLAND 1 + // // Algorithm part + eoGenContinue genCont_2(param.maxGen); // generation continuation + eoPop pop2(30, chromInit); + // // Emigration policy + // // // Element 1 + eoPeriodicContinue criteria_2(5); + eoDetTournamentSelect selectOne_2(25); + eoSelectNumber who_2(selectOne_2, 5); + + MigPolicy migPolicy_2; + migPolicy_2.push_back(PolicyElement(who_2, criteria_2)); + + // // Integration policy + eoPlusReplacement intPolicy_2; + + Island test2(pop2, intPolicy_2, migPolicy_2, genCont_2, plainEval, select, transform, replace); + + // Island 3 + // // Algorithm part + eoGenContinue genCont_3(param.maxGen); + eoPop pop3(30, chromInit); + // // Emigration policy + // // // Element 1 + eoPeriodicContinue criteria_3(10); + eoDetTournamentSelect selectOne_3(15); + eoSelectNumber who_3(selectOne_3, 1); + + MigPolicy migPolicy_3; + migPolicy.push_back(PolicyElement(who_3, criteria_3)); + + // // Integration policy + eoPlusReplacement intPolicy_3; + + Island test3(pop3, intPolicy_3, migPolicy_3, genCont_3, plainEval, select, transform, replace); + + + try + { + + model.add(test); + model.add(test2); + model.add(test3); + + model(); + + cout << test.getPop() << endl; + cout << test2.getPop() << endl; + cout << test3.getPop() << endl; + } + catch(exception& e) + { + cout << "Exception: " << e.what() << '\n'; + } + + return 0; +} diff --git a/smp/tutorial/Lesson3/CMakeLists.txt b/smp/tutorial/Lesson3/CMakeLists.txt new file mode 100644 index 000000000..9e199afd4 --- /dev/null +++ b/smp/tutorial/Lesson3/CMakeLists.txt @@ -0,0 +1,23 @@ +# Lesson 3 + +###################################################################################### +### 0) Define files +###################################################################################### + +set(files + lesson3_heterogeneous + ) + +###################################################################################### +### 1) Create the lesson +###################################################################################### + +add_lesson(smp Lesson3 "${files}") + +###################################################################################### +### 2) Include dependencies +###################################################################################### + +include_directories(${EO_SRC_DIR}/src + ${SMP_SRC_DIR}/src + ${PROBLEMS_SRC_DIR}) diff --git a/smp/tutorial/Lesson3/lesson3_heterogeneous.cpp b/smp/tutorial/Lesson3/lesson3_heterogeneous.cpp new file mode 100644 index 000000000..d573d8444 --- /dev/null +++ b/smp/tutorial/Lesson3/lesson3_heterogeneous.cpp @@ -0,0 +1,226 @@ +/* + +Copyright (C) DOLPHIN Project-Team, INRIA Lille - Nord Europe, 2006-2012 + +Alexandre Quemy, Thibault Lasnier - INSA Rouen + +This software is governed by the CeCILL license under French law and +abiding by the rules of distribution of free software. You can ue, +modify and/ or redistribute the software under the terms of the CeCILL +license as circulated by CEA, CNRS and INRIA at the following URL +"http://www.cecill.info". + +In this respect, the user's attention is drawn to the risks associated +with loading, using, modifying and/or developing or reproducing the +software by the user in light of its specific status of free software, +that may mean that it is complicated to manipulate, and that also +therefore means that it is reserved for developers and experienced +professionals having in-depth computer knowledge. Users are therefore +encouraged to load and test the software's suitability as regards their +requirements in conditions enabling the security of their systems and/or +data to be ensured and, more generally, to use and operate it in the +same conditions as regards security. +The fact that you are presently reading this means that you have had +knowledge of the CeCILL license and that you accept its terms. + +ParadisEO WebSite : http://paradiseo.gforge.inria.fr +Contact: paradiseo-help@lists.gforge.inria.fr +*/ + +/*/////////////////////////////////////////////////////////////////// +// SMP Tutorial 3 : Heterogeneous Model +// This file shows how to create an heterogeneous +// island model with 2 islands : an eoEasyEA island and a PSO. +// The file can be broken down into 3 parts : +// The PSO part, with algorithm parameters and PSO Island creatation. +// Then, the eoEasyEA part. +// And finally, the model creation. +// On top of that, you conversion functions are defined at the +// begining of this file. +/*/////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "../BaseLesson.h" + +using namespace paradiseo::smp; +using namespace std; + +typedef eoBit Indi2; // A bitstring with fitness double + +// Conversion functions +Indi2 fromBase(Indi& i, unsigned size) +{ + (void)i; + // Dummy conversion. We just create a new Indi2 + Indi2 v; + for (unsigned ivar=0; ivar eval(binary_value); + + // PSO population initialization + eoPop pop; + + for(unsigned int igeno=0; igeno select(T_SIZE); // T_SIZE in [2,POP_SIZE] + eo1PtBitXover xover; + eoBitMutation mutation(P_MUT_PER_BIT); + eoGenContinue continuator(MAX_GEN); + + // // Emigration policy + // // // Element 1 + eoPeriodicContinue criteria(1); + eoDetTournamentSelect selectOne(2); + eoSelectNumber who(selectOne, 1); + + MigPolicy migPolicy; + migPolicy.push_back(PolicyElement(who, criteria)); + + // // Integration policy + eoPlusReplacement intPolicy; + + // We bind conversion functions + auto frombase = std::bind(fromBase, std::placeholders::_1, VEC_SIZE); + auto tobase = std::bind(toBase, std::placeholders::_1); + + Island gga(frombase, tobase, pop, intPolicy, migPolicy, select, xover, CROSS_RATE, mutation, MUT_RATE, eval, continuator); + +////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////// +// EasyEA PART +////////////////////////////////////////////////////////////////// + // EA general parameters + typedef struct { + unsigned popSize = 10; + unsigned tSize = 2; + double pCross = 0.8; + double pMut = 0.7; + unsigned maxGen = 10; + } Param; + + Param param; + loadInstances("../lessonData.dat", n, bkv, a, b); + + // Evaluation function + IndiEvalFunc plainEval; + + // Init a solution + IndiInit chromInit; + + // Define selection + eoDetTournamentSelect selectOne2(param.tSize); + eoSelectPerc select2(selectOne2);// by default rate==1 + + // Define operators for crossover and mutation + IndiXover Xover; // CROSSOVER + IndiSwapMutation mutationSwap; // MUTATION + + // Encapsule in a tranform operator + eoSGATransform transform(Xover, param.pCross, mutationSwap, param.pMut); + + // Define replace operator + eoPlusReplacement replace; + eoGenContinue genCont(param.maxGen); // generation continuation + eoPop pop2(param.popSize, chromInit); + + // ISLAND 2 : EasyEA + // // Emigration policy + // // // Element 1 + eoPeriodicContinue criteria2(1); + eoDetTournamentSelect selectOne3(5); + eoSelectNumber who2(selectOne3, 2); + + MigPolicy migPolicy2; + migPolicy2.push_back(PolicyElement(who2, criteria2)); + + // // Integration policy + eoPlusReplacement intPolicy2; + + Island test(pop2, intPolicy2, migPolicy2, genCont, plainEval, select2, transform, replace); + +////////////////////////////////////////////////////////////////// + + // MODEL CREATION + Topology topo; + IslandModel model(topo); + + try + { + + model.add(test); + model.add(gga); + + model(); + + cout << test.getPop() << endl; + cout << gga.getPop() << endl; + + } + catch(exception& e) + { + cout << "Exception: " << e.what() << '\n'; + } + + return 0; +} diff --git a/smp/tutorial/Lesson4/CMakeLists.txt b/smp/tutorial/Lesson4/CMakeLists.txt new file mode 100644 index 000000000..68e0af08b --- /dev/null +++ b/smp/tutorial/Lesson4/CMakeLists.txt @@ -0,0 +1,23 @@ +# Lesson 4 + +###################################################################################### +### 0) Define files +###################################################################################### + +set(files + lesson4_topology + ) + +###################################################################################### +### 1) Create the lesson +###################################################################################### + +add_lesson(smp Lesson4 "${files}") + +###################################################################################### +### 2) Include dependencies +###################################################################################### + +include_directories(${EO_SRC_DIR}/src + ${SMP_SRC_DIR}/src + ${PROBLEMS_SRC_DIR}) diff --git a/smp/tutorial/Lesson4/lesson4_topology.cpp b/smp/tutorial/Lesson4/lesson4_topology.cpp new file mode 100644 index 000000000..780b4e4b0 --- /dev/null +++ b/smp/tutorial/Lesson4/lesson4_topology.cpp @@ -0,0 +1,139 @@ +/* + +Copyright (C) DOLPHIN Project-Team, INRIA Lille - Nord Europe, 2006-2012 + +Alexandre Quemy, Thibault Lasnier - INSA Rouen + +This software is governed by the CeCILL license under French law and +abiding by the rules of distribution of free software. You can ue, +modify and/ or redistribute the software under the terms of the CeCILL +license as circulated by CEA, CNRS and INRIA at the following URL +"http://www.cecill.info". + +In this respect, the user's attention is drawn to the risks associated +with loading, using, modifying and/or developing or reproducing the +software by the user in light of its specific status of free software, +that may mean that it is complicated to manipulate, and that also +therefore means that it is reserved for developers and experienced +professionals having in-depth computer knowledge. Users are therefore +encouraged to load and test the software's suitability as regards their +requirements in conditions enabling the security of their systems and/or +data to be ensured and, more generally, to use and operate it in the +same conditions as regards security. +The fact that you are presently reading this means that you have had +knowledge of the CeCILL license and that you accept its terms. + +ParadisEO WebSite : http://paradiseo.gforge.inria.fr +Contact: paradiseo-help@lists.gforge.inria.fr +*/ + +/*/////////////////////////////////////////////////////////////////// +// SMP Tutorial 4 : Advanced Island Model Mecanisms +// This file shows how to create events called by islands. +// In our case, we would like to change the topology after 10 seconds +// of computation. +/*/////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "../BaseLesson.h" + +using namespace paradiseo::smp; +using namespace std; + +/* + Simple function to change topology of a given model, once, after 10 seconds. +*/ +void changeTopo(IslandModel* _model, AbstractTopology& _topo) +{ + static auto start = std::chrono::system_clock::now(); + auto now = std::chrono::system_clock::now(); + auto elapsed = now - start; + static bool first = false; + if(std::chrono::duration_cast(elapsed).count() > 10000 && !first) + { + std::cout << "Changing topology !" << std::endl; + _model->setTopology(_topo); + first = true; + } +} + +int main(void) +{ + // Defining parameters + typedef struct { + unsigned popSize = 1000; + unsigned tSize = 2; + double pCross = 0.8; + double pMut = 0.7; + unsigned maxGen = 1000; + } Param; + + Param param; + + // Load instance + loadInstances("../lessonData.dat", n, bkv, a, b); + + //Common part to all islands + IndiEvalFunc plainEval; + IndiInit chromInit; + eoDetTournamentSelect selectOne(param.tSize); + eoSelectPerc select(selectOne);// by default rate==1 + IndiXover Xover; // CROSSOVER + IndiSwapMutation mutationSwap; // MUTATION + eoSGATransform transform(Xover, param.pCross, mutationSwap, param.pMut); + eoPlusReplacement replace; + + // MODEL + // Topologies + Topology topo; + IslandModel model(topo); + + // ISLAND 1 + // // Algorithm part + eoGenContinue genCont(param.maxGen+100); + eoPop pop(param.popSize, chromInit); + // // Emigration policy + // // // Element 1 + eoPeriodicContinue criteria(5); + eoDetTournamentSelect selectOne1(20); + eoSelectNumber who(selectOne1, 3); + + Topology topo2; + + /* + We need to bind our function in a std::function object. + Then, we create a Notifier that we add to our island thanks + to an eoCheckPoint. + */ + auto task = std::bind(changeTopo, &model, topo2); + Notifier topoChanger(task); + eoCheckPoint ck(genCont); + ck.add(topoChanger); + + MigPolicy migPolicy; + migPolicy.push_back(PolicyElement(who, criteria)); + + // // Integration policy + eoPlusReplacement intPolicy; + + Island test(pop, intPolicy, migPolicy, ck, plainEval, select, transform, replace); + + try + { + + model.add(test); + + // The topology will change after 10 seconds of computation + model(); + + } + catch(exception& e) + { + cout << "Exception: " << e.what() << '\n'; + } + + return 0; +} diff --git a/smp/tutorial/lessonData.dat b/smp/tutorial/lessonData.dat new file mode 100644 index 000000000..19efeb4b0 --- /dev/null +++ b/smp/tutorial/lessonData.dat @@ -0,0 +1,27 @@ + 12 224416 + + 0 27 85 2 1 15 11 35 11 20 21 61 + 27 0 80 58 21 76 72 44 85 94 90 51 + 85 80 0 3 48 29 90 66 41 15 83 96 + 2 58 3 0 74 45 65 40 54 83 14 71 + 1 21 48 74 0 77 36 53 37 26 87 76 + 15 76 29 45 77 0 91 13 29 11 77 32 + 11 72 90 65 36 91 0 87 67 94 79 2 + 35 44 66 40 53 13 87 0 10 99 56 70 + 11 85 41 54 37 29 67 10 0 99 60 4 + 20 94 15 83 26 11 94 99 99 0 56 2 + 21 90 83 14 87 77 79 56 60 56 0 60 + 61 51 96 71 76 32 2 70 4 2 60 0 + + 0 21 95 82 56 41 6 25 10 4 63 6 + 21 0 44 40 75 79 0 89 35 9 1 85 + 95 44 0 84 12 0 26 91 11 35 82 26 + 82 40 84 0 69 56 86 45 91 59 18 76 + 56 75 12 69 0 39 18 57 36 61 36 21 + 41 79 0 56 39 0 71 11 29 82 82 6 + 6 0 26 86 18 71 0 71 8 77 74 30 + 25 89 91 45 57 11 71 0 89 76 76 40 + 10 35 11 91 36 29 8 89 0 93 56 1 + 4 9 35 59 61 82 77 76 93 0 50 4 + 63 1 82 18 36 82 74 76 56 50 0 36 + 6 85 26 76 21 6 30 40 1 4 36 0