From aaa4a2a49011c8016cb564a9a6d173553476fc9d Mon Sep 17 00:00:00 2001 From: emil Date: Sun, 1 Dec 2024 22:42:57 +0300 Subject: [PATCH] final commit --- best/Main.java | 375 ++++++++++++++++++ other/Main.java | 44 ++ .../demo/src/main/java/com/example/Main.java | 50 ++- .../classes/com/example/Main$Chromosome.class | Bin 3549 -> 3549 bytes .../target/classes/com/example/Main.class | Bin 9579 -> 7937 bytes 5 files changed, 466 insertions(+), 3 deletions(-) create mode 100644 best/Main.java diff --git a/best/Main.java b/best/Main.java new file mode 100644 index 0000000..2344403 --- /dev/null +++ b/best/Main.java @@ -0,0 +1,375 @@ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class Main { + // Initalizing variables + public static int POPULATIONSIZE = 0; + public static int TOURNAMENTSIZE = 0; + public static double MUTATIONRATE = 0; + // Threshold of number of mutable positions for easy level sudoku + public static int EASYTHRESHOLD = 60; + public static int HARDTHRESHOLD = 70; + // List to store the population of chromosomes + List population = new ArrayList<>(); + Random random = new Random(); + + public static void main(String[] args) { + // Base Sudoku matrix (input matrix) + int[][] baseSudoku = new int[9][9]; + // List to track positions in Sudoku that are mutable + List mutablePositions = new ArrayList<>(); + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + + try { + // Reading the Sudoku matrix from the console input + for (int i = 0; i < 9; i++) { + // Split the input by space + String[] tokens = reader.readLine().split(" "); + for (int j = 0; j < 9; j++) { + if (tokens[j].equals("-")) { + // Empty cells are marked as 0 + baseSudoku[i][j] = 0; + // Add mutable positions (i, j) to the list + mutablePositions.add(new int[]{i, j}); + } else { + // Set fixed value from the input + baseSudoku[i][j] = Integer.parseInt(tokens[j]); + } + } + } + } catch (IOException e) { + // If an error occurs during input reading, print the error and stop the program + System.err.println("Error reading input: " + e.getMessage()); + return; + } + + Main mainInstance = new Main(); // Create instance of Main class + + // Choosing variables for different sudoku difficulties + if (mutablePositions.size() < EASYTHRESHOLD) { // Easy sudoku + POPULATIONSIZE = 100; + TOURNAMENTSIZE = 5; + MUTATIONRATE = 0.05; + } else if (mutablePositions.size() < HARDTHRESHOLD) { // Hard sudoku + POPULATIONSIZE = 100; + TOURNAMENTSIZE = 5; + MUTATIONRATE = 0.057; + } + else { // Ultra-hard sudoku + POPULATIONSIZE = 20000; + TOURNAMENTSIZE = 4; + MUTATIONRATE = 0.032; + } + + // Generate initial population of 100 chromosomes + mainInstance.generateInitialChromosomes(POPULATIONSIZE, baseSudoku, mutablePositions); + + Chromosome bestSolution = null; + + int generation = 0; // Track the number of generations + while (true) { + // Evaluate the fitness of each chromosome in the population + mainInstance.evaluatePopulation(); + + List newPopulation = new ArrayList<>(); + for (int i = 0; i < mainInstance.population.size() / 2; i++) { + // Select parents using tournament selection + List parents = mainInstance.tournamentSelection(TOURNAMENTSIZE); + // Perform crossover to create two children from the selected parents + Chromosome child1 = mainInstance.crossoverBySubgrids(parents.get(0), parents.get(1)); + Chromosome child2 = mainInstance.crossoverBySubgrids(parents.get(1), parents.get(0)); + + // Apply mutation to both children + mainInstance.mutateChromosome(child1, MUTATIONRATE); + mainInstance.mutateChromosome(child2, MUTATIONRATE); + + // Add both children to the new population + newPopulation.add(child1); + newPopulation.add(child2); + } + + // Replace the old population with the new population + mainInstance.population = newPopulation; + + // Get the best chromosome from the current population + bestSolution = mainInstance.getBestChromosome(); + generation++; + + // If the best solution found has a fitness of 0, print it and end the program + if (bestSolution.getFitness() == 0) { + bestSolution.printChromosome(false); + return; + } + } + } + + // Generate initial population of chromosomes + public void generateInitialChromosomes(int numberOfChromosomes, int[][] baseSudoku, List mutablePositions) { + for (int i = 0; i < numberOfChromosomes; i++) { + // Create a copy of the base Sudoku + int[][] sudoku = copyMatrix(baseSudoku); + // Randomly fill mutable positions + for (int[] pos : mutablePositions) { + int row = pos[0]; + int col = pos[1]; + sudoku[row][col] = random.nextInt(9) + 1; + } + // Create a new chromosome with the generated Sudoku and mutable positions + Chromosome chromosome = new Chromosome(sudoku, new ArrayList<>(mutablePositions)); + chromosome.evaluateFitness(); // Evaluate its fitness + population.add(chromosome); // Add to the population + } + } + + // Create a deep copy of a matrix + private int[][] copyMatrix(int[][] original) { + int[][] copy = new int[original.length][original[0].length]; + for (int i = 0; i < original.length; i++) { + System.arraycopy(original[i], 0, copy[i], 0, original[i].length); + } + return copy; + } + + + public List tournamentSelection(int tournamentSize) { + List selectedParents = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + List tournament = new ArrayList<>(); + // Randomly select chromosomes for the tournament + for (int j = 0; j < tournamentSize; j++) { + Chromosome randomChromosome = population.get(random.nextInt(population.size())); + tournament.add(randomChromosome); + } + // Determine the best chromosome in the tournament based on fitness + Chromosome best = tournament.get(0); + for (Chromosome chromosome : tournament) { + if (chromosome.getFitness() < best.getFitness()) { + best = chromosome; + } + } + // Add the best chromosome to the list of selected parents + selectedParents.add(best); + } + return selectedParents; + } + + public Chromosome crossoverBySubgrids(Chromosome parent1, Chromosome parent2) { + int[][] childSudoku = new int[9][9]; + + // Copy the entire Sudoku grid from parent1 to the child + for (int row = 0; row < 9; row++) { + System.arraycopy(parent1.getSudoku()[row], 0, childSudoku[row], 0, 9); + } + + // Determine the number of subgrids to swap from parent2 to child (1-5) + int numSubgridsToSwap = random.nextInt(5) + 1; + List selectedSubgrids = new ArrayList<>(); + while (selectedSubgrids.size() < numSubgridsToSwap) { + int subgridIndex = random.nextInt(9); + if (!selectedSubgrids.contains(subgridIndex)) { + selectedSubgrids.add(subgridIndex); + } + } + + // Swap the selected subgrids from parent2 into the child + for (int subgrid : selectedSubgrids) { + int rowStart = (subgrid / 3) * 3; + int colStart = (subgrid % 3) * 3; + for (int row = rowStart; row < rowStart + 3; row++) { + for (int col = colStart; col < colStart + 3; col++) { + childSudoku[row][col] = parent2.getSudoku()[row][col]; + } + } + } + + // Create a new chromosome with the resulting child Sudoku and evaluate its fitness + Chromosome child = new Chromosome(childSudoku, parent1.getMutablePositions()); + child.evaluateFitness(); + return child; + } + + public void printPopulation(boolean printMutPos) { + System.out.println("Generated Population:"); + int count = 1; + // Print each chromosome's Sudoku and fitness value + for (Chromosome chromosome : population) { + System.out.println("Chromosome " + count + ":"); + chromosome.printChromosome(printMutPos); + System.out.println("Fitness: " + chromosome.getFitness()); + System.out.println(); + count++; + } + } + + public void mutateChromosome(Chromosome chromosome, double mutationRate) { + int[][] sudoku = chromosome.getSudoku(); + List mutablePositions = chromosome.getMutablePositions(); + + // Mutate each mutable position with a probability defined by mutationRate + for (int[] pos : mutablePositions) { + if (random.nextDouble() < mutationRate) { + int row = pos[0]; + int col = pos[1]; + int newValue = random.nextInt(9) + 1; // Assign a new value between 1 and 9 + sudoku[row][col] = newValue; + } + } + + // Update the chromosome's Sudoku and recalculate its fitness + chromosome.setSudoku(sudoku); + chromosome.evaluateFitness(); + } + + public void evaluatePopulation() { + // Evaluate the fitness of each chromosome in the population + for (Chromosome chromosome : population) { + chromosome.evaluateFitness(); + } + } + + public Chromosome getBestChromosome() { + // Find and return the chromosome with the best (lowest) fitness in the population + Chromosome best = population.get(0); + for (Chromosome chromosome : population) { + if (chromosome.getFitness() < best.getFitness()) { + best = chromosome; + } + } + return best; + } + + // Chromosome class representing an individual solution + public class Chromosome { + private int[][] sudoku; // Sudoku grid representing the chromosome + private List mutablePositions; // Positions that can be changed (mutable) + private int fitness; // Fitness value representing the number of conflicts + + // Constructor for initializing a Chromosome with a Sudoku grid and mutable positions + public Chromosome(int[][] sudoku, List mutablePositions) { + this.sudoku = sudoku; + this.mutablePositions = mutablePositions; + } + + public int[][] getSudoku() { + return sudoku; + } + + public void setSudoku(int[][] sudoku) { + this.sudoku = sudoku; + } + + public List getMutablePositions() { + return mutablePositions; + } + + public int getFitness() { + return fitness; + } + + // Evaluate the fitness of the Sudoku by counting the number of row, column, and subgrid violations + public void evaluateFitness() { + fitness = countRowViolations() + countColumnViolations() + countSubgridViolations(); + } + + private int countRowViolations() { + int violations = 0; + // Iterate through each row to count conflicts + for (int i = 0; i < 9; i++) { + boolean[] present = new boolean[10]; + for (int j = 0; j < 9; j++) { + int value = sudoku[i][j]; + if (value != 0) { + if (present[value]) { + violations++; // Increment violations if the value is already seen + } else { + present[value] = true; // Mark the value as seen + } + } + } + } + return violations; + } + private int countColumnViolations() { + int violations = 0; + // Loop through each column + for (int j = 0; j < 9; j++) { + boolean[] present = new boolean[10]; // Track numbers present in the column + for (int i = 0; i < 9; i++) { + int value = sudoku[i][j]; + if (value != 0) { + // If the number is already present, increment the violations count + if (present[value]) { + violations++; + } else { + // Mark the number as present + present[value] = true; + } + } + } + } + return violations; + } + + private int countSubgridViolations() { + int violations = 0; + // Loop through each 3x3 subgrid + for (int gridRow = 0; gridRow < 3; gridRow++) { + for (int gridCol = 0; gridCol < 3; gridCol++) { + boolean[] present = new boolean[10]; // Track numbers present in the subgrid + // Loop through cells in the 3x3 subgrid + for (int row = gridRow * 3; row < gridRow * 3 + 3; row++) { + for (int col = gridCol * 3; col < gridCol * 3 + 3; col++) { + int value = sudoku[row][col]; + if (value != 0) { + // If the number is already present, increment the violations count + if (present[value]) { + violations++; + } else { + // Mark the number as present + present[value] = true; + } + } + } + } + } + } + return violations; + } + + public void printChromosome(boolean printMutPos) { + // Print the Sudoku matrix + for (int[] row : sudoku) { + for (int j = 0; j < row.length; j++) { + System.out.print(row[j]); + if (j < row.length - 1) { + System.out.print(" "); + } + } + System.out.println(); + } + // If requested, print mutable positions + if (printMutPos) { + System.out.println("Mutable Positions:"); + for (int i = 0; i < mutablePositions.size(); i++) { + int[] pos = mutablePositions.get(i); + System.out.print("(" + pos[0] + ", " + pos[1] + ")"); + if (i < mutablePositions.size() - 1) { + System.out.print(" "); + } + } + System.out.println(); + } + } + } +} + + + + + + + diff --git a/other/Main.java b/other/Main.java index 06e48f9..82ab558 100644 --- a/other/Main.java +++ b/other/Main.java @@ -69,12 +69,24 @@ public class Main { mainInstance.generateInitialChromosomes(POPULATIONSIZE, baseSudoku, mutablePositions); Chromosome bestSolution = null; + List fitnessValues = new ArrayList<>(); + + int stuckCounter = 0; + int tryUnstuckCount = 0; + boolean unstucking = false; + + int INITIAL_POPULATIONSIZE = POPULATIONSIZE; + int INITIAL_TOURNAMENTSIZE = TOURNAMENTSIZE; + double INITIAL_MUTATIONRATE = MUTATIONRATE; int generation = 0; // Track the number of generations while (true) { // Evaluate the fitness of each chromosome in the population mainInstance.evaluatePopulation(); + bestSolution = mainInstance.getBestChromosome(); + fitnessValues.add(bestSolution.getFitness()); + List newPopulation = new ArrayList<>(); for (int i = 0; i < mainInstance.population.size() / 2; i++) { // Select parents using tournament selection @@ -104,6 +116,38 @@ public class Main { bestSolution.printChromosome(false); return; } + + //change arguments when algorithm is stuck + if (fitnessValues.size() > 2) { + if (fitnessValues.get(fitnessValues.size()-1) == fitnessValues.get(fitnessValues.size()-2)){ + stuckCounter++; + //System.out.println("StuckCount: " + String.valueOf(stuckCounter)); + if (stuckCounter > 50){ + if (MUTATIONRATE < 1){ + unstucking = true; + System.out.println(MUTATIONRATE); + System.out.println(POPULATIONSIZE); + MUTATIONRATE = MUTATIONRATE * 1.1 + 0.01; + POPULATIONSIZE /= 2; + } + stuckCounter = 0; + } + } else{ + stuckCounter = 0; + } + } + if (unstucking) { + //System.out.println("Unstucking"); + tryUnstuckCount++; + if (tryUnstuckCount > 100){ + unstucking = false; + tryUnstuckCount = 0; + POPULATIONSIZE = INITIAL_POPULATIONSIZE; + TOURNAMENTSIZE = INITIAL_TOURNAMENTSIZE; + MUTATIONRATE = INITIAL_MUTATIONRATE; + } + } + //System.out.println("UnstuckCount: " + String.valueOf(tryUnstuckCount)); } } diff --git a/statistical/demo/src/main/java/com/example/Main.java b/statistical/demo/src/main/java/com/example/Main.java index edf5245..6b10dd6 100644 --- a/statistical/demo/src/main/java/com/example/Main.java +++ b/statistical/demo/src/main/java/com/example/Main.java @@ -68,7 +68,7 @@ public class Main { else { // Ultra-hard sudoku POPULATIONSIZE = 100000; TOURNAMENTSIZE = 10; - MUTATIONRATE = 0.1; + MUTATIONRATE = 0.0; } // Generate initial population of chromosomes @@ -78,6 +78,17 @@ public class Main { List fitnessValues = new ArrayList<>(); int generation = 0; // Track the number of generations + int stuckCounter = 0; + int tryUnstuckCount = 0; + boolean unstucking = false; + + int INITIAL_POPULATIONSIZE = POPULATIONSIZE; + int INITIAL_TOURNAMENTSIZE = TOURNAMENTSIZE; + double INITIAL_MUTATIONRATE = MUTATIONRATE; + + + + while (true) { // Evaluate the fitness of each chromosome in the population mainInstance.evaluatePopulation(); @@ -112,13 +123,46 @@ public class Main { if (generation % 10 == 0) { mainInstance.plotFitness(fitnessValues); } - + // If the best solution found has a fitness of 0, print it and end the program if (bestSolution.getFitness() == 0) { bestSolution.printChromosome(false); return; } - } + /* + // Check if algorithm is stuck + if (fitnessValues.size() > 2) { + if (fitnessValues.get(fitnessValues.size()-1) == fitnessValues.get(fitnessValues.size()-2)){ + stuckCounter++; + System.out.println("StuckCount: " + stuckCounter); + if (stuckCounter > 20){ + if (MUTATIONRATE < 1){ + unstucking = true; + System.out.println(MUTATIONRATE); + System.out.println(POPULATIONSIZE); + MUTATIONRATE = MUTATIONRATE * 1.1 + 0.01; + POPULATIONSIZE /= 2; + TOURNAMENTSIZE /= 2; + } + stuckCounter = 0; + } + } else { + stuckCounter = 0; + } + } + if (unstucking) { + System.out.println("Unstucking"); + tryUnstuckCount++; + if (tryUnstuckCount > 100){ + unstucking = false; + tryUnstuckCount = 0; + POPULATIONSIZE = INITIAL_POPULATIONSIZE; + TOURNAMENTSIZE = INITIAL_TOURNAMENTSIZE; + MUTATIONRATE = INITIAL_MUTATIONRATE; + } + } + System.out.println("UnstuckCount: " + tryUnstuckCount); + }*/ } // Generate initial population of chromosomes diff --git a/statistical/demo/target/classes/com/example/Main$Chromosome.class b/statistical/demo/target/classes/com/example/Main$Chromosome.class index 815de1c9a1695fdfcaffdc8d7ea065f061ca795a..b054448f4c7ed5434b6c5ca8ddc6851728077a95 100644 GIT binary patch delta 300 zcmWN|&nv@m9LMq3OMQL5!yIt3rZ^$dH=Jy=gOtM#a|?JsfhI=$-sem=^9a-bMA{xSF%LoMT|=eTV*)10*XChW|>;jH~JQO?;W zH^c=ScZZ!t)G=WT?v}TN3NFKN1vOmNHHjeC(86`JF@+9ppo^R6(XU_K5V!3_RkKa` zir$W7Msim&E18qrlRS_-lsu9=k<3bFBu^#(BIi;UQkPO!QrBo?Ug`#&yhS(f(8qg; z2Siwq%41Z`l=^wO1RtNyszdZjXy7wi`GQ{U0ADf45~6&=2;ULohtf}^wK;y_nBTV2 G|N9S9 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 CUtpC0 literal 9579 zcmb7K3w)H-mH(g2d*%xX2?GoWM0uDeu@QqN2=XSH2a@mzSj&(MVPG;7XC?^UwpyjN zm9}o(ZmZa;!D_T?wSWc?m(|+W_F-ME)^^*iw!7`_w%cvDk1ZC>{?Gls$qW$aZ}FFW z_q+Fg=bn4c|NPInH-7V-XHEjRT5k8DNKo37>aUOOiS`d9V)bp&c+v+qdv`{6N9z;O zWM6&9wwn22WMsU#O}H7gHh_vVu?7nLdvC7xg6K$#{0L zz*SkbLEv7V>Wu{;m~6oVuePRI@Y$_t7W@ck-$fRJcHc}3A$SEPt?^{6eXxI9EZr5| zmY}X>t*M@9VnZ|?*LSDWyT~jTgbTART#Rxb<_gO5n_ikuNB8Q^gP4Qax>ltH3&lQE z=iFk9V{ajdDtl6$g;GqU{@ESzjIMpQUAS3yKi$inRc^6OVcgr9O~;dc%~czGxSU4V zvZcj`D>%t$6i?MJ8{ED>Z*>FdwOC68m1QFYTsaAEZY{#WTJhsGBV083kfjLru0X5#g?a%J<;sOcyeilS&{6W~*6uTeul}i2h9c zy+i`Tyu}6IP=$BN@q6{(0T<-M4|Ignm)*;2GhxCe~jRWC722fPnVB)Cbc`3UbeS$a9dwG-b>qdS6c45>Tlh9R<5}g46nS-WF5?(D zv^f3EA-sr}eE2T8Ay4$VQ49Zsm&sJg*d7MYnK{de9Us2OB)X+V6aQ-#zK_>wMxAd+ z-_3ZVLNDTn7XBGO;?Rz0rrk&*?`_tS;$Jj*{)9=q5UtBo2`92m3f+%4b=FT=dz>2& z&F5jg{rFc4|E8>0l4CuGdI;^LDU3R{=h3fD&p5F5Y1pqz-AsM8MQs!t=~#x~(A{n( z@-sF$6d>P~)-PN=I@@Cyjc)@f3icCc27v0bv2xIdUj~`}{$Syc_+OTlQLVJprGo0h z6yjLgp>_kHTvv;Rt3Q>F_r;S@F6*{GZy2MwvDp91h3SL>UNxDiBxC3`*1I+r6~X)z zTEH%3^A_at8k?jT5lf21#XXUAb6nx6bkHdtOT1b{_Uoq?)obAnjD=P7E`2ofcy^WTH8z$V56nN9|py&YPo5?4`NA<_1VK zj)z;4y)o+Lb6%+-{mh_E6AIix!fATU8aYB2wf!rX%js**a~SYtpEJoEg|1$k{>L zxqh9K>v!gNY~RlH?EYNa&0?G$U}WbBNPW3zT*BnJK*+9BQ|Z3?UCGqV{q=i#cF-5~ zo4S|lGbl}RHOWGFg7Q?hFhKlr4d*+dU!j@3!m++4ES$`Keo1Je!F5WhKp) zpQV#lTe3zc6?4+2rF-HTXXdqpMy{mcv{p;na?`rUrm27qOV-Nu+&fF>&R9CG*{Gv1 z&K)10WrpUn->X{3*}E;UIUt>~-X~pxy7Nw&v#Hyjy)H&%r#rKI6ERCR$VSGmeK@H< z{|e)M+#Hl{+2T|1Mb5jbB{v993X|1(=dLekt^9v3dLc9Tvu^{Uky=UFVe*No+p?4J6J zu2p~Ssx%8#;+5=R2xHoKyR-#s{&8S2eY{zYiwVq57Qj8RRdGGk2lR?$zD5p7Y*{Lm z&1AX3wpeyYs+ZtS$xS|Cbj``1(BjG6ti0-NwAsg^RnZ>S2z!-!(t>*mLFvr41+L*W zMIx4&)0$%SH{c+%@bFm>vS)nQ#d2w!>4gQRlhO*Se@DdfXd=kW#F#i_k6HQQE;@qgrK^r$ z#vu+BVHST+=CUfnO(814;^z|)MunLHio1AB==D@p*gw7cC@!f!fjQkrG4E+y%4QXt zHBX~9FSv{%K`q^wLM^9a8rv$=vPED%>iHJHWq6mZv%8F{6m!KlO9Q2WucD!-)mftL z1Qv9kKx6k|TzM3Wk78-NtI<7-8|p@|qHN6wu5I$T8ok3<8u5g^4P|RbuEKvT!8{$bI2byN$Os-_%W_)& zNXT9=;*VH%YhOUZfr$GlTxzV~Neqor%ZQ(N*yoe5uQWJt0IG*~1P>Xl4~#C1cw8eG zK17Gl#z*ln-tM>!3SYw&1j9vy!%U)Y7Ds3EU@@1MX!Fp7O9|&HbfAV8Mz#EAJ_gx; zi;>a58-@kApWi=#EAc3r@f2_Pj^k?lJr?5yEWyjThUb%|_z{-jr&!Lj!AkrmZ|44+ zQvblKu~D>15!xk$wK4&nQjQHW4I8Bz-EuiL%T=sWm*Ym+iucI-u}$tkk9-o_L!s!Lx6nzUf$;$-A53yU`;C+t*{&5XYc@uxf76C7>`9A9Z1WvHeFO9g}NDgo>+wd&=g51$cTIm=<+*c(|vSr~PsADx( zu(f-Rg{Lj|dCIk=l~9^3qlA~wU*`O7l#J3r9v@!tp}~iZJ}e+lyp2-!NX>+zQwWUG zx9-9T5FfUeoyI&LWM;`bnCioRekyOHS|5K#@-4~~y#b?%Y_|J~a+^yTQhB08NkBZ+ zjG3=G1n^D9%P>xq2A_2|`E$V$2z$Ld8iT{QHtb^~G>pp`^}&X4Xnvy=w(f-$@rNyU z*D$6=0yb9!l`BFHSL_Q(IHaF?w+@YdV_#6hL6xX)ha!RF_(wq#gdLjYWmPSe^%hy=D2^F^kmn0LWNT-iu3#-H#D zj(Yg@OZazw;bm5O9{<6ok2*ieX#FK~Qzah3ulQ|%^4H_PI2vSZEXJ?d3UM#98PQ%M z_&&nsH~1b$AEh0BYx;iA$oU`Q+iUPWc}$b%sEIB2Im%TmKaP^qg!2FeMJSK0cL;ja*9oibi6#&qLLm$6_F zMJDnaWFj@=-R)TT(r!e+6I|0zi4W$oyDE8CE?C{JM$a(jE5bE~^l9=7Ln)WT7zE5J z4*ES3_DmldeL3PDOLt6@%x)T$gD+tNxX&6ZI@8H(yAcqvdp`Zgx$qk5E?*1?q`mz2HV2HZuXtp3Q<3T2)R=`g(^ zw=q_98&|d1cPdw+*@f8Dnh{=IxEow``l~dcD9aU5zfd>&Z6V!bAZo4*sqLV9S$hp zPpLC>U#nUg-tVwEJ)j)^bDPoq+J0pW5Dyvht>eamdESk9HDqj1+{n5XEHDE28sYJE zmdRg34X@hbo7@f!au@qNm;Wi5K2E}y7&RV zc%A>f%JSq564sl%82qUL%_mIWJ2eKHQ!;^$SErmJr5>Z31Ne=UG6-FC%Pd2RZffvl zhMtnaH8?J11d@lETqNb}^HPV^GRd^=#S#g#<)=OiWind<{ypJxhgu(}1~#?+mb5p; zKu?PUt>10A&r_~~s~^F)QKIZO=`>~%HSCEa4ilrQ%%|+Fe_cgymSR~ z(h8dFS{kd3{TrEkDmc?-%}ryj+az;jF0~G^+MGvI7g0i$$w@YAmZNBt;m#UL4PkPF z7K!`=k-b&T{z5xKPTp3mYyK9W57rZ>!>dSLYDVUGz6AD8QWQm0KcOF{>a1TTbMx_tfbrPNoflSfF<1gY8JH}EG*WG7h4F8 zUE;?b62NEp{vi7wBle#p>W>iRYU+JNIPEfK>ZgoZDGX0ZJsnoW)t)dVhMVhsOfKWQ zhpT>2-o=)e@sgGXK7Bka>|~Lv5wV(u{sOiFERHtI6>J4rB5jmLwnEs1HL{Q`3%gJ& zSF%-XBH=1{Pos=@ALUuYLAlG61$3&7Z;vcy?jEHA%CQU(9jB_If2p-yjrZ}XmjZLg zpxic$i9Ao*KyFo&^36c>S9#l-6F1{{3Zr#8pK~N)>|@Fu9;WOBK83uXrH~knCo1e% zk5LyJbb7VGKMM1=))bG?c$)o9P>m(n)-k=>hcT@#r&k*b@jI#8od@BmZDqGn@Mkso zGGiJo1(|^fjKaAx3zta+xnU-nWwz0@!fg9$N7D%g6{_i6s;D{_8J#Do&eL=Dv(=u= zS^QflXPA?MTxHZhM&(ugWhbPmd!nmiV$r6fvZ$@LT$YT;Qa)CU$SOWsM&vqu2xF_g zz3#BA8<9;LPsokkwMS*^i1cjCTlo^A1M)fU;eLeW0rKjDJira1P7d;aSa}Fb<@07Q zHUTUfL*+7aIMaKZ1=1%wsA(O)kIPQ(ImfHil(U}aUfi;adKXDT`uUv1aydg{$eQ1~ o&F?+(UXD!T*ZVp0SNwUOd_ZnRgm=N+^1*8&avR6pl(ql=0Aix|!vFvP