From 0a94b65038a0a6bc9a919afeae35d0bc651995b3 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Tue, 19 Feb 2019 20:44:23 +0100 Subject: [PATCH] Add stackoverflow assignment --- .gitignore | 20 ++ .gitlab-ci.yml | 36 ++ .vscode/settings.json | 8 + assignment.sbt | 9 + build.sbt | 20 ++ grading-tests.jar | Bin 0 -> 26510 bytes project/MOOCSettings.scala | 46 +++ project/StudentTasks.scala | 318 ++++++++++++++++++ project/build.properties | 1 + project/buildSettings.sbt | 5 + project/plugins.sbt | 2 + src/main/resources/stackoverflow/.keep | 0 .../scala/stackoverflow/StackOverflow.scala | 308 +++++++++++++++++ .../stackoverflow/StackOverflowSuite.scala | 47 +++ winutils/hadoop-2.7.4/bin/winutils.exe | Bin 0 -> 109568 bytes 15 files changed, 820 insertions(+) create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .vscode/settings.json create mode 100644 assignment.sbt create mode 100644 build.sbt create mode 100644 grading-tests.jar create mode 100644 project/MOOCSettings.scala create mode 100644 project/StudentTasks.scala create mode 100644 project/build.properties create mode 100644 project/buildSettings.sbt create mode 100644 project/plugins.sbt create mode 100644 src/main/resources/stackoverflow/.keep create mode 100644 src/main/scala/stackoverflow/StackOverflow.scala create mode 100644 src/test/scala/stackoverflow/StackOverflowSuite.scala create mode 100644 winutils/hadoop-2.7.4/bin/winutils.exe diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..996b5d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# General +*.DS_Store +*.swp +*~ + +# Dotty +*.class +*.tasty +*.hasTasty + +# sbt +target/ + +# Dotty IDE +/.dotty-ide-artifact +/.dotty-ide.json + +# datasets +stackoverflow-grading.csv +wikipedia-grading.dat diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..d074697 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,36 @@ +# DO NOT EDIT THIS FILE + +stages: + - build + - grade + +compile: + stage: build + image: lampepfl/moocs:dotty-2020-02-12 + except: + - tags + tags: + - cs206 + script: + - sbt packageSubmission + artifacts: + expire_in: 1 day + paths: + - submission.jar + +grade: + stage: grade + except: + - tags + tags: + - cs206 + image: + name: smarter3/moocs:bigdata-stackoverflow-2020-05-11-2 + entrypoint: [""] + allow_failure: true + before_script: + - mkdir -p /shared/submission/ + - cp submission.jar /shared/submission/submission.jar + script: + - cd /grader + - /grader/grade | /grader/feedback-printer diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a35362b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "dotty": { + "trace": { + "remoteTracingUrl": "wss://lamppc36.epfl.ch/dotty-remote-tracer/upload/lsp.log", + "server": { "format": "JSON", "verbosity": "verbose" } + } + } +} diff --git a/assignment.sbt b/assignment.sbt new file mode 100644 index 0000000..54a90fe --- /dev/null +++ b/assignment.sbt @@ -0,0 +1,9 @@ +// Student tasks (i.e. submit, packageSubmission) +enablePlugins(StudentTasks) + +courseraId := ch.epfl.lamp.CourseraId( + key = "7ByAoS4kEea1yxIfJA1CUw", + itemId = "QhzMw", + premiumItemId = Some("FWGnz"), + partId = "OY5fJ" +) diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..32b23dd --- /dev/null +++ b/build.sbt @@ -0,0 +1,20 @@ +course := "bigdata" +assignment := "stackoverflow" + +scalaVersion := "0.24.0-RC1" +scalacOptions ++= Seq("-language:implicitConversions", "-deprecation") +libraryDependencies ++= Seq( + "com.novocode" % "junit-interface" % "0.11" % Test, + ("org.apache.spark" %% "spark-core" % "3.0.0-X1").withDottyCompat(scalaVersion.value), +) + +// Contains Spark 3 snapshot built against 2.13: https://github.com/smarter/spark/tree/scala-2.13 +resolvers += Resolver.bintrayRepo("smarter", "maven") + +testOptions in Test += Tests.Argument(TestFrameworks.JUnit, "-a", "-v", "-s") + +testSuite := "stackoverflow.StackOverflowSuite" + +// Without forking, ctrl-c doesn't actually fully stop Spark +fork in run := true +fork in Test := true diff --git a/grading-tests.jar b/grading-tests.jar new file mode 100644 index 0000000000000000000000000000000000000000..5564ba877fafe839c3721143a73f4b10a64d47b8 GIT binary patch literal 26510 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 JPV)bFU090u^^UZ}RYU&bM#V%cQFpHG*~=OtsMq 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 literal 0 HcmV?d00001 diff --git a/project/MOOCSettings.scala b/project/MOOCSettings.scala new file mode 100644 index 0000000..171244f --- /dev/null +++ b/project/MOOCSettings.scala @@ -0,0 +1,46 @@ +package ch.epfl.lamp + +import sbt._ +import sbt.Keys._ + +/** + * Coursera uses two versions of each assignment. They both have the same assignment key and part id but have + * different item ids. + * + * @param key Assignment key + * @param partId Assignment partId + * @param itemId Item id of the non premium version + * @param premiumItemId Item id of the premium version (`None` if the assignment is optional) + */ +case class CourseraId(key: String, partId: String, itemId: String, premiumItemId: Option[String]) + +/** + * Settings shared by all assignments, reused in various tasks. + */ +object MOOCSettings extends AutoPlugin { + + object autoImport { + val course = SettingKey[String]("course") + val assignment = SettingKey[String]("assignment") + val options = SettingKey[Map[String, Map[String, String]]]("options") + val courseraId = settingKey[CourseraId]("Coursera-specific information identifying the assignment") + val testSuite = settingKey[String]("Fully qualified name of the test suite of this assignment") + // Convenient alias + type CourseraId = ch.epfl.lamp.CourseraId + val CourseraId = ch.epfl.lamp.CourseraId + } + + import autoImport._ + + override val globalSettings: Seq[Def.Setting[_]] = Seq( + // supershell is verbose, buggy and useless. + useSuperShell := false + ) + + override val projectSettings: Seq[Def.Setting[_]] = Seq( + parallelExecution in Test := false, + // Report test result after each test instead of waiting for every test to finish + logBuffered in Test := false, + name := s"${course.value}-${assignment.value}" + ) +} diff --git a/project/StudentTasks.scala b/project/StudentTasks.scala new file mode 100644 index 0000000..7604830 --- /dev/null +++ b/project/StudentTasks.scala @@ -0,0 +1,318 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ + +// import scalaj.http._ +import java.io.{File, FileInputStream, IOException} +import org.apache.commons.codec.binary.Base64 +// import play.api.libs.json.{Json, JsObject, JsPath} +import scala.util.{Failure, Success, Try} + +/** + * Provides tasks for submitting the assignment + */ +object StudentTasks extends AutoPlugin { + + override def requires = super.requires && MOOCSettings + + object autoImport { + val packageSourcesOnly = TaskKey[File]("packageSourcesOnly", "Package the sources of the project") + val packageBinWithoutResources = TaskKey[File]("packageBinWithoutResources", "Like packageBin, but without the resources") + val packageSubmissionZip = TaskKey[File]("packageSubmissionZip") + val packageSubmission = inputKey[Unit]("package solution as an archive file") + val runGradingTests = taskKey[Unit]("run black-box tests used for final grading") + } + + + import autoImport._ + import MOOCSettings.autoImport._ + + override lazy val projectSettings = Seq( + packageSubmissionSetting, + // submitSetting, + runGradingTestsSettings, + + fork := true, + connectInput in run := true, + outputStrategy := Some(StdoutOutput), + ) ++ packageSubmissionZipSettings + + lazy val runGradingTestsSettings = runGradingTests := { + val testSuiteJar = "grading-tests.jar" + if (!new File(testSuiteJar).exists) { + throw new MessageOnlyException(s"Could not find tests JarFile: $testSuiteJar") + } + + val classPath = s"${(Test / dependencyClasspath).value.map(_.data).mkString(File.pathSeparator)}${File.pathSeparator}$testSuiteJar" + val junitProcess = + Fork.java.fork( + ForkOptions(), + "-cp" :: classPath :: + "org.junit.runner.JUnitCore" :: + (Test / testSuite).value :: + Nil + ) + + // Wait for tests to complete. + junitProcess.exitValue() + } + + + /** ********************************************************** + * SUBMITTING A SOLUTION TO COURSERA + */ + + val packageSubmissionZipSettings = Seq( + packageSubmissionZip := { + val submission = crossTarget.value / "submission.zip" + val sources = (packageSourcesOnly in Compile).value + val binaries = (packageBinWithoutResources in Compile).value + IO.zip(Seq(sources -> "sources.zip", binaries -> "binaries.jar"), submission) + submission + }, + artifactClassifier in packageSourcesOnly := Some("sources"), + artifact in (Compile, packageBinWithoutResources) ~= (art => art.withName(art.name + "-without-resources")) + ) ++ + inConfig(Compile)( + Defaults.packageTaskSettings(packageSourcesOnly, Defaults.sourceMappings) ++ + Defaults.packageTaskSettings(packageBinWithoutResources, Def.task { + val relativePaths = + (unmanagedResources in Compile).value.flatMap(Path.relativeTo((unmanagedResourceDirectories in Compile).value)(_)) + (mappings in (Compile, packageBin)).value.filterNot { case (_, path) => relativePaths.contains(path) } + }) + ) + + val maxSubmitFileSize = { + val mb = 1024 * 1024 + 10 * mb + } + + /** Check that the jar exists, isn't empty, isn't crazy big, and can be read + * If so, encode jar as base64 so we can send it to Coursera + */ + def prepareJar(jar: File, s: TaskStreams): String = { + val errPrefix = "Error submitting assignment jar: " + val fileLength = jar.length() + if (!jar.exists()) { + s.log.error(errPrefix + "jar archive does not exist\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength == 0L) { + s.log.error(errPrefix + "jar archive is empty\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength > maxSubmitFileSize) { + s.log.error(errPrefix + "jar archive is too big. Allowed size: " + + maxSubmitFileSize + " bytes, found " + fileLength + " bytes.\n" + + jar.getAbsolutePath) + failSubmit() + } else { + val bytes = new Array[Byte](fileLength.toInt) + val sizeRead = try { + val is = new FileInputStream(jar) + val read = is.read(bytes) + is.close() + read + } catch { + case ex: IOException => + s.log.error(errPrefix + "failed to read sources jar archive\n" + ex.toString) + failSubmit() + } + if (sizeRead != bytes.length) { + s.log.error(errPrefix + "failed to read the sources jar archive, size read: " + sizeRead) + failSubmit() + } else encodeBase64(bytes) + } + } + + /** Task to package solution to a given file path */ + lazy val packageSubmissionSetting = packageSubmission := { + val args: Seq[String] = Def.spaceDelimited("[path]").parsed + val s: TaskStreams = streams.value // for logging + val jar = (packageSubmissionZip in Compile).value + + val base64Jar = prepareJar(jar, s) + + val path = args.headOption.getOrElse((baseDirectory.value / "submission.jar").absolutePath) + scala.tools.nsc.io.File(path).writeAll(base64Jar) + } + +/* + /** Task to submit a solution to coursera */ + val submit = inputKey[Unit]("submit solution to Coursera") + lazy val submitSetting = submit := { + // Fail if scalafix linting does not pass. + scalafixLinting.value + + val args: Seq[String] = Def.spaceDelimited("").parsed + val s: TaskStreams = streams.value // for logging + val jar = (packageSubmissionZip in Compile).value + + val assignmentDetails = + courseraId.?.value.getOrElse(throw new MessageOnlyException("This assignment can not be submitted to Coursera because the `courseraId` setting is undefined")) + val assignmentKey = assignmentDetails.key + val courseName = + course.value match { + case "capstone" => "scala-capstone" + case "bigdata" => "scala-spark-big-data" + case other => other + } + + val partId = assignmentDetails.partId + val itemId = assignmentDetails.itemId + val premiumItemId = assignmentDetails.premiumItemId + + val (email, secret) = args match { + case email :: secret :: Nil => + (email, secret) + case _ => + val inputErr = + s"""|Invalid input to `submit`. The required syntax for `submit` is: + |submit + | + |The submit token is NOT YOUR LOGIN PASSWORD. + |It can be obtained from the assignment page: + |https://www.coursera.org/learn/$courseName/programming/$itemId + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + """.stripMargin + s.log.error(inputErr) + failSubmit() + } + + val base64Jar = prepareJar(jar, s) + val json = + s"""|{ + | "assignmentKey":"$assignmentKey", + | "submitterEmail":"$email", + | "secret":"$secret", + | "parts":{ + | "$partId":{ + | "output":"$base64Jar" + | } + | } + |}""".stripMargin + + def postSubmission[T](data: String): Try[HttpResponse[String]] = { + val http = Http("https://www.coursera.org/api/onDemandProgrammingScriptSubmissions.v1") + val hs = List( + ("Cache-Control", "no-cache"), + ("Content-Type", "application/json") + ) + s.log.info("Connecting to Coursera...") + val response = Try(http.postData(data) + .headers(hs) + .option(HttpOptions.connTimeout(10000)) // scalaj default timeout is only 100ms, changing that to 10s + .asString) // kick off HTTP POST + response + } + + val connectMsg = + s"""|Attempting to submit "${assignment.value}" assignment in "$courseName" course + |Using: + |- email: $email + |- submit token: $secret""".stripMargin + s.log.info(connectMsg) + + def reportCourseraResponse(response: HttpResponse[String]): Unit = { + val code = response.code + val respBody = response.body + + /* Sample JSON response from Coursera + { + "message": "Invalid email or token.", + "details": { + "learnerMessage": "Invalid email or token." + } + } + */ + + // Success, Coursera responds with 2xx HTTP status code + if (response.is2xx) { + val successfulSubmitMsg = + s"""|Successfully connected to Coursera. (Status $code) + | + |Assignment submitted successfully! + | + |You can see how you scored by going to: + |https://www.coursera.org/learn/$courseName/programming/$itemId/ + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + |and clicking on "My Submission".""".stripMargin + s.log.info(successfulSubmitMsg) + } + + // Failure, Coursera responds with 4xx HTTP status code (client-side failure) + else if (response.is4xx) { + val result = Try(Json.parse(respBody)).toOption + val learnerMsg = result match { + case Some(resp: JsObject) => + (JsPath \ "details" \ "learnerMessage").read[String].reads(resp).get + case Some(x) => // shouldn't happen + "Could not parse Coursera's response:\n" + x + case None => + "Could not parse Coursera's response:\n" + respBody + } + val failedSubmitMsg = + s"""|Submission failed. + |There was something wrong while attempting to submit. + |Coursera says: + |$learnerMsg (Status $code)""".stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera responds with 5xx HTTP status code (server-side failure) + else if (response.is5xx) { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera seems to be unavailable at the moment (Status $code) + |Check https://status.coursera.org/ and try again in a few minutes. + """.stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera repsonds with an unexpected status code + else { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera replied with an unexpected code (Status $code) + """.stripMargin + s.log.error(failedSubmitMsg) + } + } + + // kick it all off, actually make request + postSubmission(json) match { + case Success(resp) => reportCourseraResponse(resp) + case Failure(e) => + val failedConnectMsg = + s"""|Connection to Coursera failed. + |There was something wrong while attempting to connect to Coursera. + |Check your internet connection. + |${e.toString}""".stripMargin + s.log.error(failedConnectMsg) + } + + } +*/ + + def failSubmit(): Nothing = { + sys.error("Submission failed") + } + + /** + * ***************** + * DEALING WITH JARS + */ + def encodeBase64(bytes: Array[Byte]): String = + new String(Base64.encodeBase64(bytes)) +} diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..a919a9b --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.3.8 diff --git a/project/buildSettings.sbt b/project/buildSettings.sbt new file mode 100644 index 0000000..8fac702 --- /dev/null +++ b/project/buildSettings.sbt @@ -0,0 +1,5 @@ +// Used for Coursera submission (StudentPlugin) +// libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2" +// libraryDependencies += "com.typesafe.play" %% "play-json" % "2.7.4" +// Used for Base64 (StudentPlugin) +libraryDependencies += "commons-codec" % "commons-codec" % "1.10" diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..017735d --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.28") +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.4.0") diff --git a/src/main/resources/stackoverflow/.keep b/src/main/resources/stackoverflow/.keep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/scala/stackoverflow/StackOverflow.scala b/src/main/scala/stackoverflow/StackOverflow.scala new file mode 100644 index 0000000..42a75b7 --- /dev/null +++ b/src/main/scala/stackoverflow/StackOverflow.scala @@ -0,0 +1,308 @@ +package stackoverflow + +import org.apache.spark.SparkConf +import org.apache.spark.SparkContext +import org.apache.spark.SparkContext._ +import org.apache.spark.rdd.RDD +import org.apache.log4j.{Logger, Level} + +import annotation.tailrec +import scala.reflect.ClassTag +import scala.util.Properties.isWin + +type Question = Posting +type Answer = Posting +type QID = Int +type HighScore = Int +type LangIndex = Int + +/** A raw stackoverflow posting, either a question or an answer */ +case class Posting(postingType: Int, id: Int, acceptedAnswer: Option[Int], parentId: Option[QID], score: Int, tags: Option[String]) extends Serializable + +/** The main class */ +object StackOverflow extends StackOverflow { + + // Reduce Spark logging verbosity + Logger.getLogger("org").setLevel(Level.ERROR) + + if (isWin) System.setProperty("hadoop.home.dir", System.getProperty("user.dir") + "\\winutils\\hadoop-2.7.4") + + @transient lazy val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("StackOverflow") + @transient lazy val sc: SparkContext = new SparkContext(conf) + + /** Main function */ + def main(args: Array[String]): Unit = { + + val lines = sc.textFile("src/main/resources/stackoverflow/stackoverflow-grading.csv") + val raw = rawPostings(lines) + val grouped = groupedPostings(raw) + val scored = scoredPostings(grouped) + val vectors = vectorPostings(scored) +// assert(vectors.count() == 2121822, "Incorrect number of vectors: " + vectors.count()) + + val means = kmeans(sampleVectors(vectors), vectors, debug = true) + val results = clusterResults(means, vectors) + printResults(results) + } +} + +/** The parsing and kmeans methods */ +class StackOverflow extends Serializable { + + /** Languages */ + val langs = + List( + "JavaScript", "Java", "PHP", "Python", "C#", "C++", "Ruby", "CSS", + "Objective-C", "Perl", "Scala", "Haskell", "MATLAB", "Clojure", "Groovy") + + /** K-means parameter: How "far apart" languages should be for the kmeans algorithm? */ + def langSpread = 50000 + assert(langSpread > 0, "If langSpread is zero we can't recover the language from the input data!") + + /** K-means parameter: Number of clusters */ + def kmeansKernels = 45 + + /** K-means parameter: Convergence criteria */ + def kmeansEta: Double = 20.0D + + /** K-means parameter: Maximum iterations */ + def kmeansMaxIterations = 120 + + + // + // + // Parsing utilities: + // + // + + /** Load postings from the given file */ + def rawPostings(lines: RDD[String]): RDD[Posting] = + lines.map(line => { + val arr = line.split(",") + Posting(postingType = arr(0).toInt, + id = arr(1).toInt, + acceptedAnswer = if (arr(2) == "") None else Some(arr(2).toInt), + parentId = if (arr(3) == "") None else Some(arr(3).toInt), + score = arr(4).toInt, + tags = if (arr.length >= 6) Some(arr(5).intern()) else None) + }) + + + /** Group the questions and answers together */ + def groupedPostings(postings: RDD[Posting]): RDD[(QID, Iterable[(Question, Answer)])] = { + ??? + } + + + /** Compute the maximum score for each posting */ + def scoredPostings(grouped: RDD[(QID, Iterable[(Question, Answer)])]): RDD[(Question, HighScore)] = { + + def answerHighScore(as: Array[Answer]): HighScore = { + var highScore = 0 + var i = 0 + while (i < as.length) { + val score = as(i).score + if (score > highScore) + highScore = score + i += 1 + } + highScore + } + + ??? + } + + + /** Compute the vectors for the kmeans */ + def vectorPostings(scored: RDD[(Question, HighScore)]): RDD[(LangIndex, HighScore)] = { + /** Return optional index of first language that occurs in `tags`. */ + def firstLangInTag(tag: Option[String], ls: List[String]): Option[Int] = { + if (tag.isEmpty) None + else if (ls.isEmpty) None + else if (tag.get == ls.head) Some(0) // index: 0 + else { + val tmp = firstLangInTag(tag, ls.tail) + tmp match { + case None => None + case Some(i) => Some(i + 1) // index i in ls.tail => index i+1 + } + } + } + + ??? + } + + + /** Sample the vectors */ + def sampleVectors(vectors: RDD[(LangIndex, HighScore)]): Array[(Int, Int)] = { + + assert(kmeansKernels % langs.length == 0, "kmeansKernels should be a multiple of the number of languages studied.") + val perLang = kmeansKernels / langs.length + + // http://en.wikipedia.org/wiki/Reservoir_sampling + def reservoirSampling(lang: Int, iter: Iterator[Int], size: Int): Array[Int] = { + val res = new Array[Int](size) + val rnd = new util.Random(lang) + + for (i <- 0 until size) { + assert(iter.hasNext, s"iterator must have at least $size elements") + res(i) = iter.next + } + + var i = size.toLong + while (iter.hasNext) { + val elt = iter.next + val j = math.abs(rnd.nextLong) % i + if (j < size) + res(j.toInt) = elt + i += 1 + } + + res + } + + val res = + if (langSpread < 500) + // sample the space regardless of the language + vectors.takeSample(false, kmeansKernels, 42) + else + // sample the space uniformly from each language partition + vectors.groupByKey.flatMap({ + case (lang, vectors) => reservoirSampling(lang, vectors.iterator, perLang).map((lang, _)) + }).collect() + + assert(res.length == kmeansKernels, res.length) + res + } + + + // + // + // Kmeans method: + // + // + + /** Main kmeans computation */ + @tailrec final def kmeans(means: Array[(Int, Int)], vectors: RDD[(Int, Int)], iter: Int = 1, debug: Boolean = false): Array[(Int, Int)] = { + val newMeans = means.clone() // you need to compute newMeans + + // TODO: Fill in the newMeans array + val distance = euclideanDistance(means, newMeans) + + if (debug) { + println(s"""Iteration: $iter + | * current distance: $distance + | * desired distance: $kmeansEta + | * means:""".stripMargin) + for (idx <- 0 until kmeansKernels) + println(f" ${means(idx).toString}%20s ==> ${newMeans(idx).toString}%20s " + + f" distance: ${euclideanDistance(means(idx), newMeans(idx))}%8.0f") + } + + if (converged(distance)) + newMeans + else if (iter < kmeansMaxIterations) + kmeans(newMeans, vectors, iter + 1, debug) + else { + if (debug) { + println("Reached max iterations!") + } + newMeans + } + } + + + + + // + // + // Kmeans utilities: + // + // + + /** Decide whether the kmeans clustering converged */ + def converged(distance: Double) = + distance < kmeansEta + + + /** Return the euclidean distance between two points */ + def euclideanDistance(v1: (Int, Int), v2: (Int, Int)): Double = { + val part1 = (v1._1 - v2._1).toDouble * (v1._1 - v2._1) + val part2 = (v1._2 - v2._2).toDouble * (v1._2 - v2._2) + part1 + part2 + } + + /** Return the euclidean distance between two points */ + def euclideanDistance(a1: Array[(Int, Int)], a2: Array[(Int, Int)]): Double = { + assert(a1.length == a2.length) + var sum = 0d + var idx = 0 + while(idx < a1.length) { + sum += euclideanDistance(a1(idx), a2(idx)) + idx += 1 + } + sum + } + + /** Return the closest point */ + def findClosest(p: (Int, Int), centers: Array[(Int, Int)]): Int = { + var bestIndex = 0 + var closest = Double.PositiveInfinity + for (i <- 0 until centers.length) { + val tempDist = euclideanDistance(p, centers(i)) + if (tempDist < closest) { + closest = tempDist + bestIndex = i + } + } + bestIndex + } + + + /** Average the vectors */ + def averageVectors(ps: Iterable[(Int, Int)]): (Int, Int) = { + val iter = ps.iterator + var count = 0 + var comp1: Long = 0 + var comp2: Long = 0 + while (iter.hasNext) { + val item = iter.next + comp1 += item._1 + comp2 += item._2 + count += 1 + } + ((comp1 / count).toInt, (comp2 / count).toInt) + } + + + + + // + // + // Displaying results: + // + // + def clusterResults(means: Array[(Int, Int)], vectors: RDD[(LangIndex, HighScore)]): Array[(String, Double, Int, Int)] = { + val closest = vectors.map(p => (findClosest(p, means), p)) + val closestGrouped = closest.groupByKey() + + val median = closestGrouped.mapValues { vs => + val langLabel: String = ??? // most common language in the cluster + val langPercent: Double = ??? // percent of the questions in the most common language + val clusterSize: Int = ??? + val medianScore: Int = ??? + + (langLabel, langPercent, clusterSize, medianScore) + } + + median.collect().map(_._2).sortBy(_._4) + } + + def printResults(results: Array[(String, Double, Int, Int)]): Unit = { + println("Resulting clusters:") + println(" Score Dominant language (%percent) Questions") + println("================================================") + for ((lang, percent, size, score) <- results) + println(f"${score}%7d ${lang}%-17s (${percent}%-5.1f%%) ${size}%7d") + } +} diff --git a/src/test/scala/stackoverflow/StackOverflowSuite.scala b/src/test/scala/stackoverflow/StackOverflowSuite.scala new file mode 100644 index 0000000..fb9a4f1 --- /dev/null +++ b/src/test/scala/stackoverflow/StackOverflowSuite.scala @@ -0,0 +1,47 @@ +package stackoverflow + +import org.apache.spark.SparkConf +import org.apache.spark.SparkContext +import org.apache.spark.SparkContext._ +import org.apache.spark.rdd.RDD +import org.junit._ +import org.junit.Assert.assertEquals +import java.io.File +import scala.io.{ Codec, Source } +import scala.util.Properties.isWin + +object StackOverflowSuite { + if (isWin) System.setProperty("hadoop.home.dir", System.getProperty("user.dir") + "\\winutils\\hadoop-2.7.4") + + val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("StackOverflow") + val sc: SparkContext = new SparkContext(conf) +} + +class StackOverflowSuite { + import StackOverflowSuite._ + + + lazy val testObject = new StackOverflow { + override val langs = + List( + "JavaScript", "Java", "PHP", "Python", "C#", "C++", "Ruby", "CSS", + "Objective-C", "Perl", "Scala", "Haskell", "MATLAB", "Clojure", "Groovy") + override def langSpread = 50000 + override def kmeansKernels = 45 + override def kmeansEta: Double = 20.0D + override def kmeansMaxIterations = 120 + } + + @Test def `testObject can be instantiated`: Unit = { + val instantiatable = try { + testObject + true + } catch { + case _: Throwable => false + } + assert(instantiatable, "Can't instantiate a StackOverflow object") + } + + + @Rule def individualTestTimeout = new org.junit.rules.Timeout(300 * 1000) +} diff --git a/winutils/hadoop-2.7.4/bin/winutils.exe b/winutils/hadoop-2.7.4/bin/winutils.exe new file mode 100644 index 0000000000000000000000000000000000000000..9f306391421e8ab8a4e8e6a2ed5fbc9947ed9159 GIT binary patch literal 109568 zcmeFaeSDln)yKU_69}at1-e)Y!U91eAOu@AK&x8_cp*Uo6p4ti*ir&k2(W>Gt)!;K z*6nIVkgBLv5i6orL7MgkOrh8yC!K7-`|=Lzm9G;E3ktiLD;> z^Y?sk*SCH4`gMH2;rcIJe<$IuZ~MG`|N6G?Tz{*5fBX7-2=Bh-GuMBG z@cU0RT;FHu-@N{REc}u$x5~oPFPhsc+>s>RCnOT{uG=Z`m8({r6QylS9GKX(ej=ZB ziNqS0+SEXNvc=QHT)aM!XiV@Ae^Hp&N-oO#cVj(zOfQZK`Le!XIZ#=LRul{^FKZ88dOiTGWA<>VRJu0E&1vlja$T*N@W@$- z1R3o^3tQV03p;{*XX}6<(<+-*EY$^I-wD%4DoU-t$(0H=10b%_mW(u2Gt{_%fGB@n5X9J)I7Q@}_oqyF`DO|X&6-HO)UxDW6^|-J9F8b~ zdgzGQpD+F=7*LA5#O(Eh{M*^GZb@qsthcPYp*0~r=bDGF0A=#y|4Ag4j`a7Ik{Qxr z`EI~#Dr2iMWGrQ62sTzm+Lf0uU8q3d$YSX7&3j6gTYm@!6eaB>vbzO{u4pOQO!uaY zt>p|wOUb{(oxnoAg6R*3t$!t4yh(N@GmQVnn&Qy2y(sPHmm^`ex2!O9(?L06aCsIX-nZKP%0oYp=R^+};gp8s!C)U;CaZE%Yox@lqELB6>UJ%$fG0Ggpq zy`HNv?Ukn55fh0c#b!0YR8Sf*wdx}Xjwa=bHz?k%)|(1)Q$`g@my#{UL3Zb1>Mv1j zC~l;^4sGfX$@E#YzAf|uw7zYJ#jS6Y6dKR%1o{)@KoQc`_%xChx;z?Yl(wXGWfbjf z?b}NwI$N{TT*({A>Le;`X+q@R{Ov?H+|CEw{MF5GK4(N)~iT;y4 zZ@ZC8?HilfgSzs~r=W2)2&K&5P?wm$9u+V9SE*F^HO;WNXOK@G2MEePgCKnOCKD;* zxj31k`Y4TH&x-i(U}$@N%g_|mw?P7Uu6p2#dKa9!zjUy?;YU#cDLxpQxp4=){{FHW zyguod+l0SRdjPI0E za>M^(d`>e}D*Ie5HqI0qU!UMr_9e&0h}hT%Hn6b)RK&l9hv+c7@=IC=QOgGT4Ve98 ze4gF3 zRm8Q&Qu1H_tj)(zEk4c_A8XO0S;|Vs$9nP63LjnhuGWPTR^}e$Z?LAICKi;EH_)@t z8=~ja|5cmdM{5!M%XSF9BO-W{BRDF8cWi_P6=h8;Hu!h#G{)d>uZ8s{VJ)Bs6XUZE z>sN&J{lbbmGeK_q^pW*)&R)1}ws`@whxHZY=O}Z9(JdvPjK%sehE&UZ-dBr(y~IFo z#6ZR|uviQ{G4g6H4(5(_jjv_sU{IJ7fQIxZ<*Rxa9$c%7_RQ3=QAF5+_rsW#j~t>rGNwq@z3fs2wfL=TgtZ-u#ZL#}xe-TOhKxs6LlvKI$) zX&us|K@Dwe>Y~x4gWQ&I8chpJkyz@bs%kVv7xuZn8r{MJFP2KhX~w1U=@afC2tV9QEG6%!2rLeiM%Av1 z5Bj0wb|pJbC5^@f4W~bUnsD)^EqtC=HJ&nHscm+kD{Gr^YfUL7ZnZ4S9~alLf*~5pU$~jUirV#!GFtRJy z-De!TCyB_u5xa*ucISvli?KWF>v=M3G5iu;fNG}9@V{RG*B0uR%E={vNj?lu6|Vd7 z_wYf^0avhbYdtN+kFtOJ2N}y7PgcgztmBQ=JER;N{7pHsf~PJSOY2k6Dqd(-@LMt= z^c+EWwg^$)>d|>p9o~~-+SB-(6@cPPbzE_{AI};`l!Us-o-|rYUM)+_HE*y~=1R*O zK2==r?aCTpdnwufcWX^SH0_D~O`C<`a>H-&IOJ z`Zr6KE@PFd{m))dL!}G0(MV3UMxuP_lOTM{`WR<@e|@pBFv=j~iPUHJ*IZM!1o*K6 z-wAMUyRtw;7j;?ON`(v7b)l>4-2z9e_UB3Hs>|mXhH8Yiu>kpNso}6Ul_<3&r#4!y?dp zEdD|%`S0hLyL_c2GeBd zZEjpFoczrm9^Tbv-qb@P{3J|-T8z{P;kfqWw~cn;J6M?{%dP)plQwMRo6i@4Ik(|? z+ze9uZkB({FjY@8T|F(GYoaV7Nc10FO3or#m@q@xmnx_|mLdF0sRRz8XSxPUFYN;I zcM!!8+L}m_LC|pk^&o3Dy<{_b93UF`<^u_s=eP5s{vUXbN1Y25@2{f~l#4G_mu7aAax5?j(2iwqEL z7>+fFm4IL%kE+(@VakXg7Wu0eyMsj3#rElA)PMBCP>+mIi4s^48THj^K1h{FB*n{S z%JLF4esT!}y#Si3IEBn<9gra3D_R#r2i^V`6KMo|h@>EqRR=wMu@gxtu}Ba7nFiit z@=SH|94&cnxWg?viAAYF{L?895+L#$1acW58URR2^zQ|XV28HWF+FL5>29UVZDDr6 z(ry>7fASM*J50U=c((w8d9)N2jsZsR`0{uxr zJyE1DiVQ@N?kLh5MFK@Al(Ep6g`%1<4JyK4Tp@DAyEQg4*-C!w{pdf}ymD`(-xHn? zMO+|?F=KY~kXN!&VkOTnU^nN|Cz_r|0YQnsCkPM!UZf=RRaDB)*dmmycR*I!sY1D@ zQ10qb=8}Io$54JzD1Y@`P{MJqnTiDHiQRHrx*G`J1Jzp0Us8+tZ-h|#a%A)$cFf-* zN|%ZfRn&t=J)!JTul_U1m}y$IpLQ!$;@z*NMS|s2AkXhZ^j2HX$2R z@zY?)I|nU}n?T$=^&M!V?}}M^cj%J3{lvYSandLC`XpA@4<*SP7cEVVtI~necx_BK z9(WBq8ILgtV7gDkSSh)Z9*1h40o57!_%QriKZBeRTmFKlX~bQvGs35+QL14E1={OI`hGNO@KSKp693D~Rh$nGM;U71Rl4FoKzuYux@i_N*E& z7qZ4@i8C2&{39W*`AHrQ5%jRF%sa>Im1j(8uWsnC+rM@Wp~qZM(6(l~u(MVt=Sr!&Sx z1&8CrC9SmS#kknEXV*a9(c*`?$!{{Tw#CWf>YuQ1VnL@9X* zRzbR|4Rct=Y*gdFKAlBS-FyC?acO}5tCSoCTHRNhk4AQic=9^MM-*Mpi35BSzj#2}>AJN*fcOUXz0*Ru!LW?b0=VDxphuw5){k2HcOHoi30 zf{R;`fJv!mEJI*-j0+VB7^gK3_FYUXjgQS^JW8m}@+(@mB5v6CJKJ3ECLgvGBo>LS z?LbO3N{RLNQ-BK{u%)dn6_-OT)Mc8J8R`B`+3;g)Y>3>K+?EYt6QV21pw+7yCLEBx z$F7OZBIR+QxDjPQ!J0wxw_B~X?@>yw_64o?S27ew1*71SORKQ-R z0T@|?Po~pOSV>P!N%Tey$0b`M9Q#(_IBpC*2YXyhZyTk2^%nN>^b9YF9b@RJiHhDz zqxYZRMf7g3!136GYWW=OV?a3h1}k811y=GIUg)JIXVm(zfFN$Ee5A{DWeuQu+6U)0 zv9BXUMQB(R0xh%TuM&YgjjfKD7Z+nWXl{+hcI%TE%dW8Iy9}XZHiTKgT=Qg#gmZ5t z?%QxFd$?Ax)H`U6Y8hcEd0(uXmBb?b-c_$=Rx0gpQoFvnd$LNYbol<`V$isy?GDL* zu)q**`x*{tKM1hEb^?WM1SYjYnf9>4oP6|{BMzeAXRv>Y{QDt)nH?~{Ak3c;=6zlT zvpU}x^J>ThIm7%s1Chi0b+<>W4g$=33-e52-iZEQnP*M7B8ErGXg?RD?XE(5xzPUU z7SQ4b?*Q$J)=VYCGJ4N&I>!7WF<&z<90+qxn02=1l^I@P!%WPx%-dsrn$0Aqikqu2 z?PYnOjonF$nnA(XSt|awui}kd6N53L zV>GJmV0qJ+RMk|lJg81a&7@&!o{mEx9g&QuSe4x4l2hLPp%PF1Tb9g0JJI-5ZWRZL z$x~o5?$>0|rQ~hsL=8o(ma<1lt(2E~t8CY|Sn!!lm{w0I`5AVE$(XYYKE_!(TgYrM%Zv*0k)(-fm?V(o)@~D=qd@KB>ni zp^uMv3O2g9b4zZZxxi{zYPJ6^c-FEI zpViyL?)={v|ClwSL9kKj0|Yisr=8apdyU?|EF~98C^yb%U9QrNgOAlJo@)v}^G91c z9l)z%bt}cNvE)4GvJb=##^t|c%s3m@yzd3#9$(4F!JpxG#_cP;IC7U_%pCCz#g$ueBe}t^Dzq)&ou| zB|qnp6i-^;1e$P<+r^PUJK1r`C`A zqC<+^4kDSi+wyujR@#GJnZJ$Vbq8+#7HAB{3zEY5vjYo$+nCvpfj5O~K4+trfan_y z)@*VVvg((W5}W03e#W}Sv1XHxlMi=wvdLS%0BLNhJ^b9~$6{;j-?f5=2%}E&v{U^0 z_fh-;ihD|`Q?adfy4wSUi#JIwKjR;N{!q0!L&C|h6b5ea51PUwnd49get}8N>p2fd zf>~QvZgvyvhxz6^h4ET27C&53@Wr2vDfsd>iY6OG@*2^T$7srV+ z^QVY22$0p#txlZHB3=&;w<7X5?LpjStl6lWQk&74g&DTQYUp)l;Y;JtKlvE7-)gvT zbGRQ8?pvvIiIrhtpye*CU|ADiTB}QSLWo8{i|bSiG)IyyCg1!O75oqd1<4AF9dB{_ zk?s(J+i*=GiR`VYelLaQtni_FqM)& zLihMqY^kPQEmR=gk8;&W|M)0K-InSB4(Ty6`)|>73{aARSFQCZW!yjpNvVpZBU`bm zXs_-hQl-f@g^PY`^7$Q2$@f0bN_ji-y<+83lkba;mFFbiflxI4c9r!!*(;p3FhnPf z<|;5>jb(}VGUn2jSjfBGhQLzEGWuDQrCo~m6zHI+$u0&HPr<76LP3AdL%S-il2@Bj z)%BTYVpLt9IWMnYy8_`w;_3pE_a{!?cZklLp(Emfjaa6A^tafgtfkF)L@0Yj2b05|ak$y?m3$RoSggwr;Iu$@Mh0!afuO zS^Lmvy)4EEx7?MU%EsKTF1=3aW;;>!M|Kd-dd;|ol zCYiixt}CY*lxs?^O<5OmtAJ1Qz;WNG4n)h`?1Q4=U}%KlGx?Cv=SI2jU}+UgDDp%6KKTS|iPvW*hHqyeE_@I3(BR!C=Vh3j zAV>dl$AwaGwz2SOWrMO^2ngE}iBvsdiscWjqs$)OnTUE~HrPtZhkgP$`fy8(rT)gk z>CXJ-<+f;j86|GN@v zBBvKr>|izE$IK`tXG39V`!(6-Qu3`tXR(2j=zpC`u;biC31NP8E9K^K(CAKb3-8DM zSG5f+?wG{gFQmqQfe0xr>(aY%w5+u=`kJJ#wC-JWavx$9+ldnEZt#jpPT*hnp|*RdqE+yzD|ySZXI zUzCqG2y4`i9k3$s`+?`38*kTm`~8V3)!FaoXL?S5WNEKxzZiaxYCR2JtA6at?90r6 zj8^|0*ArESsYetf_6&stGiCg){x(3JZ(Rc35+1uzNO@Zhp29OxSyk4hEKv;#IuK< z2(&R!N|qjU-5))gUABw+vwO|XtUuc|l4FCVjkO264>YI>$u(|$iNMWsTx~i79J2>A zhzPy>zd`>5`4z2$gxtFTJrb~fIU5E|g3%+9U>_uK7BYke)y1t;xt8y^J8xPu->w8Q zmQ`giZLy5QMuVK4U!Q7R`bmGeJe{oyuc;~w)@@3Fy)?zx*Bf;^3OX#LWoJ$Dm8+G+ zQu2%BI_8&1rBeS>tb&E-4n@rW5ICsls3^Ew-IogUrCKE~>%M4z`Vs;1I^5Uc?u&_} z^27vr2?DXM@|vXyHgMujaJlqOlMf-$UD(V3n-R(xJ{n2%cM5<|Na!~?Z%&WyHf?t`RMRj-n52+KECq?|+t|OA`O(HS z?NlO7Wh$W!nMZolq(7XuIijSGX$XgNC%iUkcSN0t)tZ!Je5|^|~oG z)~^+#8Eu2m7kJQnk`5GoxpN7%@ULgIyjDvO9Tg?-s3LN#G)}e_Xq-h6MbEnGo1b)= zv0zwY7Pf@5$I_O=GoC(IC)i-u0LF=pKuE5fD2dKW>^xYUs$SRV1R|ae52yBJUX)2C z7EMuEj=$J-jP8K?=8qC7dN8}5ScBeFkT(fVBDDp}8|?N%TO#{LC8S!`$xV8_#i_D- z8C1%FmPYl_R(wQM+YtI(s&=OGMdOk~Ln1CNo8WS4 zdFVBNaJ+t|Zf5v|=2^`B><=bD-|cB{@O+kICX-enJgm?rEo=81IsgtQMke5ekNW-kksdKF{m0^(W& zP0KEg@d4_;FPyZK^{foehyKvoRE7@YszZE-@dl+K>44TEp5MKy%V3~2+1tZ4=a>z7 zKVlDO#LXAe35WMAVo=rq+mq-&z?4F6S}F^|(_j&5-Y0IVJZpkm$O%W!N6 zO35$XXZ1dl%Id9LFN=?Q;6zz-z@)j~;#XPI;e`{SHlz6Fa`nCC@``4VokB*6F=pKx z5aH#8>XnL(!=~1#9k|-^?JuUMImcETMjNLyz6EZu-a-n_;xYpL5nb)0Iuo&`BEG63 zK3EZ7OB@3$0lvZ~6I~fWz1|I~-2mcq9v4-FHje1}8(OzlqCSU)K&4MIZ5A#C zqPR0?<`e14b3g>x4gYJe9x#0H5$?lf;iV&s1T-5UjmH9_E14kQL|Rgsg^zA6AF4cg zBN+flz1jjZHTUn$`8YCcyqzsE_C<`sPk|A`BB%k|A!VtZ?{EW$?v{=$r1dm@dVjTx zG5IfB{-VZ0UZwMPmCF^yAO3-3LiHym6qdxqJtLlppQ)AHbPY_rTQJn>D53uB08mAI zIEv>xThE~u-}5(M4jXM*H+lZCQ=0ml)w#}J`dI&8Wvu&*3_UJAAEDMS=v4>7`l~=Q z*IbN|GvICBC~K22=LETz=l|_H!Ap+@B_?1SWh18ld}kefdG)Q!pt1sf>J6j3fK-$;peYLc1?jRD?qY}lFEcfb9t2tcsmvFx#yM67H4Jt)h>TO^=z;^Cii`o zyNKrR(3O%G-3!I7(lB;EQiue%-4ar zhV=^~{D~QJ|72lzMV`2a7Vw_(_rrY0Mr7uaTuzeij{WZ6Tm};4=(GnMC$=Io+zxH{W{zb-4Fc){}i)#Pgas4qyhf z9d>~u{-w=g0}ZRU*jB~SD{ziTaPAMhFlzONb9OPDyI8+hJfMg}#FZ_RSJ@{tShjx= zqFz2HqqFMhU$?>5;!Uan4RP^Sfw_nzme&U4&~SR(<2XU%cG|{$Ww3j2uu@WicH3YZ zIq=0x$@l$_Dns8Sriwom-n8)69`ELNViGC-jb4l~3mJM=ULZ11+0MJt;!OurwmlR{ z6WK+?3%@~=KC5NPR{^&VEwcr~^;=JL06=H3mU}uF6zut;M%09f7npv=8}W?g@aGF8?p ztD!Yl_L?K_sMh?%R<$}%rJUKc58D+6G+Xmo{Ys`=GtGoe@3Xd>Hv6`=o2Km^vmb5u z{a0AqU8>Ts(+4~4G(8}DA$NfQBvmt%s4 zD3<2_Gii*qA`rfWhXP=G@w-a)M5Fc+t@Kp*^Kqj^;*=y+2L+jU_V5em6}^=dk~J^8 zPRj3M}1RqG{QBvnry^Uv6o0Ys=|oHM|nIqm$cr)vf5ngeN#56U{-covDIoy2#+hCW^SB|pD+v>WAL~lcQU^*j8<%Q z)D1gK#O`Hl%2sZ^puAYKP2oRqIvHjERIlU;$;BaQ92w+aaaiB7^p*+^7m&7~C;LGK zmm8xCuF4*)uYv3_#Xbq&IW$@){%k?NtkQFQUVV)`Qz+#cYUG(h$!n^SX9|@L3VV2V zC-k$S;Of5*uKF&_zEQ!Y)}QwEb8+G1;iy(mtdhKV%&<&VRK{c+U>h(sGK{{l|8h83qf;agnPM)AD2}5uj&S3 ziOPqRzQ*>7dZr$ASr57y z`%uxDnC^zQAD*44Si6AJ#kaEW^?d}Xw(b-Hnsxs`c`*x3;p5xaaBE$^>O7#z zos}B>Yf$d6oI2%+vX)WRZD|eRhtI}+mMzi(%`jl&Mn}VyD#b)Y9cPMwJ1kOyJ+PEA zbsS5VcEt%!GkS)iyn=_!v;Q-+y?x<+JIH<8M?^ult%|pnlGjt;n?(loz(zy=LDD(L z&qnHJ4>u_}Z(9*;d7cNTlwX`ym4igQ*W&Y7-zTP8eLcQM`&Bz964&ktVpcY7y^KCL zfpaTomTIS(HKHwVibgMYDy2MrM?)7IIlF)tuGn*SGqoeKEe{s8&VW@ml z$>A6?pNP-};}&7hph_g0W3^L@m%Unqy>bfG(5_WE=M;C(XXVdaSsT)AHF?Y;?NpQ| zRL0_vKWO7cdIf&UiUaP1nRqNTCE3VUg4? zf@*bic@y}@EP|RpARqlK*24PKKHALGBHLCvf8T_Dh^<;QJB3q zs3nU1NU}aS37g^eUWSMpJ-K!&q$|gNM!{BlFobXllXJYF#saUO7+V?K$$HHizJOZF zODgousp|CYi+MmG9<|uIZKS(cOj@h z5O)?PB-)p^~K{=nRHcqDC~CUSbJb8QGBBmOoQ=jwjJReZiUuQ0_uwzybaV) za&AIv8DOU?Tt<}AjH#RYqIHd^M~gZj`m>`|@BR#dy0YIldy{Op4A+%)Vi;57^5mRF(yN#5TFCkXd-dL)7;#5Cp>oZa}`d`9&CewWUO=yNpwDC`xOFzIaV> zs>`RsKzkCRxDlwBDV`&yCiI_KN%Gq}Q#G$f(#B2jI+Sh+W(-}XN-tDz5XJptblxKF z`U1FBkJqM9-DFA;!n6;qx8&P4F9zc3S09;f2>?Gltb}1nO5?HvJ*iSM|4mbkc8J`(yt4h1SK0nI%E;u( zv|EO2|KXU$U@UW4Vnxzg5O=90#%*}5&U zE|*oPm2FNiR2uDuemka3lM-#a{qbt8ri%8huZ4E(Izl*Z(owT2uRoNRjoW?>qvd7e z>IUV_!QF@q-=8*%XhV_gAlDapNq~XbEd-^)=Vn#s+6wI|WLGnjA7-Ay@>{LW89e+F z>hO)w_e-c1{Ba&x%{p%IDG-$#JXT5*)@0$qcT`iI6v>}Jkn3PhW!158u(qWJJ9{q<4 z{KB2oHJocL!TOn^T?N9b&n7MW3nCUC;~zDX`u=k$ayVbsyBscK3Ia}OhHs_E60@!v z^w1RB$ju?B!s2;gSV@sma{JdQ$N;L=c{p8~yLAMq=g25tC=zx38IQHwdVGiesw(u_ zFc$i62>lAs>yYBj9=9aKd0{WWMP@S=CchrZbRAHLVB;!GRK4`&Mtr*UHqP}$*f>8L zm3^0`jh(Pm*@xYaO__cWhG~G!5a~$O1l~D&c%s*w)poj+`~h`rd^+!(e#kn{l$$r~ zZkpI^8@ZIu41Lv!c<1x-VY=AbcX7X4q)G{$J2pdV5VHMFc);-gJ1pS)T_ zsMD75*zp>Ww=6xLQrJ{Ved?H*e|jjKpl7{X@-LC87hF?T-GN?k%{MQ6`ZcI>wnnH- zBHIRItSRV#tiAAynFqN^8^aH~#=HwF1dPzUIq%?Tjj6(=EWC1sbu8V!*G#u(4BZ~j zK6EFP>3%|zSJHin_FB}dgL-d7{HWVGjO_&W2y7+6rpMSP40{jn6jjGg3vCe*!A$>K z{`4C3vxhe|soyz^W7fCbjwGF#ov9$upE~k^D3XpMy)Lq(bx;xh1Qt(eGf_%k6bYh; zn^@?Gg;MyFTW8N&_0j#bNse6e`k&CD&Rj~j?&iMChL;I8JC&_H#W0CHpyBY@beY|> z9aVi_qW>TeD_;*+l|er)(7Zqo7AReOMow2sq=V`QsrtePF5Ds6`NB0@xPBEku&O7g z-n1GLU{8xBHVqHv{0~W9k}^|g3eooWs>>H5)Mu(;nt~bh_-QIuq%Mh;(R0PAil=2( zJwZNxOe*6N^q5o*cXX!f$yCL6To*^r-yz-uh4=fYt#FEPnLJf> z@nvz3N(LoRTpgb$bzF7qCR`T@7o*r~sgBdNv|@-~xkmnZOiZbk32#7!;Xl|CFXUmc zcw<~6y+Pg|7N&YQhUcA?n4%dkWP3H0Ppg|7u(SN(nv2F0OqOl#Y7txl!Q%eHwN`3j zvq?=++v*jqQpZZHg}S@ZigGXHW~WB_Sepf4GgtYfv`=EFo@YsJ{mS2<9<<^olL1w0 zzr2r1TGfQn)&t5;f)0GyDcfA*aX*5tyxb*rei$|=9=fR2Ul1ie?h}FC z66d>9(hj6zozB{z6rNTVB}NqTCrT+#i71M+zqs2YZ7V6A{4dZ^%QXz!iSEV!r2a3# zH2yCoKZ!07M~AB8HefzcJJ}@pL)=t!n@FUTYBydQh-{#Dvx-Yoyp;T(&l)c2gjzGz zSVyTC9p$D!Q0bpM6DwO!IUqzdkv&aBMuf<$iLS7x@>d01myiZEPBwkm{LjZkuqTgt zxlqp#>T^LYgc_&ZsrgBu9{b>a^t&qV{4(c-V)Q%LLVvx`|9lSUOO0-z2)dx5O6Cc~ zC6hF%Y|f+VaPqY=cePY-nGmfIqPsvO+ZGNtTu2l@TzIMG3i@0@FB3ErWkFs|?zDA0 zXfOb~UtS6G=kJh(BZPDpA)O9VngO#{t3&Gshc-^gJs5oO!LDG!M$AIi1&9UF zS84$=LP=o8Xakhn5VEwtp~olk*22HjWN!DA<8(p_Y%$ryTSPa z7z#%sT~JXc0g2(qr^f|r^#T)w;yR&N3-oKPj}P4t)yLdgd_Da;kRBzZTt}^_k8=^H zvOd&{x1&6xE8~?$UcGOBL39dwlfok!xV zemgA2ouSKc9mDS|LmU6jRpE}!!#=6PRax(oGG&M6R+6xOlM!=mldPN-eB`5qi#N6N z8SQ)6dT0E!v^x(e{#5HDittBtP$7Rr?{+ao#lHSx|3Q%+OJTfOrW||lrHPn(a(ZQ) z*5=bRjWM8Z7vJ3vAahlVBRPHRMd3Z(5hz%1M-X^sP=bF z6&eKaeq0z1WX zPe~Mi$rl3-R(xhHiYsY`;XzO=UZc)#6jZt`CPl*bAORKdQvuyeWm7v!V#BJOxtDnU z{mEv8%Jf1N4+V|T4Q*xXvwz|fW}g8TwmW8+Qt5}*D+uzYDBshgM1 zV*t?K=B4e#WEPv3+A%6(!R8YtySzRR737z?-`3F6mn+EA+m^2UZSsDUSHu^I!|a^4BcbAWM7}VQ4w^vkLuydcOkI>6n=&B zy7DKa{Hr#3(F=cZ26gZX2&M_aTgnJlfgk`u#ANP9@hY=Pe3G2yCroDl8x1>r!O)_~ zEMqP63+66WPH@qP zUCZ~u@}*lP@GInn*5-@aSL{ z0qYgH5|1XTj%?TL;r$bOQ}9N4JAw`OPzP0|(?epcCkpHDg9z&(Wvs`5m3fFX$u^tu z_s^KW&BVs=*C|Xl#h9LCj_vu|W{liu_LOg)0~~nCMpO<9E*y_)wor9}%Cl+2H)7DtukbZSu%~_l zH}}4?nwz(lx!D~w?iU5lrwuK)_BM1+2KtFB&&b)Q?PZPzLmOLyV@I!=7_8rhKgC~5 zF!cLvL2hIC+)`NK312$p4CbuH27?N6d0{Fg8^_|7?$YDAy+GVf9>eXja%Ic~Te+Wz z%%=b9NM-cIL8M#aX^h^uAzq#^NBuXU#(zzfZM*2kwbBI*@Exvt*+u<=@vSZ(wz)t5 z8WulvBICq!6?FxH?h3-p^kTv=ae|!@&^}Uo1BMwfpg|@D5^>O#TWaJV^PLg>?#Io@ zQl;eU#&fYmTs(6EW5lUiYx!=gpnV}Rw7sN@ z(BH_7GotHM;%Z7n{UQ%1Eo!o#W|aZ1!;c9XAwPk#s_x|z{lwH3Vvk0YADL5TH*g~6}55mY1!5-K(pZ;L@ zN3KEHfsp7Rj1Oy#myDKcP#c5wG8qYf*vkxAtPH153M3p*hTO&j|hR1=)ACG{NqJt{>tW)6t7#RB+n;3)TT#oP=F!3EEwt5~)@RM>w! zo7O#(DsS^vl2X_O?qN@IR3zcaNJrW@Q9rHYO~amuoTmCOYwN|p>}Y_pw7wdPzF)ge z8SJid(XkscqU0O|r1Au)HMte3|xS(*149>A}XeoKB1L$>B71a0Z2(s2# zkx2;!4&5j&SwA?EVq0=(WODg7?Z1uclGYxwRRIbU{k+KXcQT;8Kxd4;)U~n ze1T0+#doNtl*}>cD*=zH4H2ypSCbW$%bP%(H(Cj2lUR;B_i*M{CI&W%r(jD80V8ccX9<@pu zw5=dvcA^+|vfs~vIsfgk#DzCfNOsa4Q~BmlH5Kec@92P3lNVH?pzyqli|HD*zO!-a z3`DgqL$O-_56Hn}I-=G)O37AIK&q6&&9#t@sdb@>Yn}4*KB`)8S*mV=_g)-+{e&Jh zufS@aW8X$>9l)w1At@z4%NL{tRXBiuRNuFHc@Mu3zd>T2e0h~ z!oM^=NXTUIPzuc+?k29~#FfgVxH8$drIqNM@pjAgm)JFIXAX2m6FWh zn@XJf??XUxbVy<-SIvq+cZEG9pbyUVvxW_sNKYCwRF>tHN1ND$Klw4`aoFf=p^CL} zGa?lhaZDj9$2m!oe6@XqsB7LS5v6(Gd@14LO=`~OWX0#N&Kh0|aJWKlnEgt~2fnQm z*3?&%gFUN06k0PU?Db<_{8)0209w{Hp33&gd}JTwQ>#-SRcN!U|6J>q4^U1bZOCSl zXPpNiI`jB~si=dlI8ABS^PNs2*%y^ml6c1V3>m z<<4wO0EW^?wQe)xS)@75Y{_>B2?t_k%C?pPGyTC}IfE^+=oJI6oFIqapyb?UMFkt= zW>!CL00)7?hVT-sODB7f1Vgpc2M?&!DskEupbT~l7+aJE-wIj7jG}vSwA-g1nBBd& zZ}AYB%vrz`o^qmfU!#v-_>cYd@u0*3aYpo*Q8w7w2foA@N$ni>qIrpd_TdyIj4#~= zu|odViNWilg2Bn)VICl0W?Hv2v#?H0BwyAlZ=34KhObW^I!#4!-W#1-j&nHhL4L;< z`eW+(_(!PcpS%qj8j+!(RW#KzC(f7%Ss%1y5O(Q<1{KDJ1HxYS!-``NP7}gKLWpN( ztB{ndlg}|oqjBdB`Pp0&G3rKu&8V~F6{ktlFp%&zSo#U5-VCbZg5t&Es-d3>jZ7B~ zeleTAE*41#5@osxaqzFVQtU1E%T^tY{hX1kDE1up)XK#s`(nQrwM`WB-*rNBr@o#N*B0x2NySW^DN}!sz$u6f(ZxB>t+rt*U;>xL7U)H4%I}^V6*-;axF}((P z-3T$P6s%B!$}1dH$R94_o9T9gd?z~@@lR@hiH<-`bwDor0Kc%t`B#$coPH5h;gvHq zZWZ>V^8~}T#RR1hW`j?YY3q!<-5KtFhriVGYbr;b)&QN@7KnzwbZ7e-3nR+Ob1y+= zxQ#6QW(YA>G!||lkN!&Y06Y$fqzjzAbZ|>7-qm^u9qTfS&uP85^rK+zW5IH~5mI2~ zg+T)KhJ2QiPktCe(fBT+XsR2ULPb>Mg^6fdaTU_IB3z6mj|$vof7_M72hG);V9-aK zKDPQCX6J6RTl+3-2qpi#PB<9fE`o=`{eVSsthTt5;}(lo%0cBxj>86>9BcV3C6lip z$7*unM2rX%QIkW3B!_VQw{lz_$?;`s$eWPrMlEWfl>92Ki@s>kQv45@(5E~+ABe@9 zMoBh*X3Y8yjBqUUJDUB6)!|vb;@HB zL?-c)F6Xz8i7C*M9R(#HRM;Cg(21wRy=J?1fw$p49<8$ug@w52ox0fHWYJBPM)UsX zR|%@Sku*8hpY%qNz9`bI2tCa_A7*@bj`U4>kG^##(%-(pIv*zm!8zk1xyO4tAzR(-_eE@7zsMI9WKz%I2 z_X{#y`B_%pdLl}P7wkqO!tFu58Rt7@7=RCW+#)HUB)Ydt`qG=Wvc_&LN z4=Peh-sPjcoPQ+>3Ld&DH~L?G0LY3)U$Mq3Y1QJ}@b|9-iauE!$g}t|ewEd7+A^GH z5y5+D^t8V9ZM*P5d_zt@r8Vt#RPwcBRMs{+oNN)8%0uhPexsF9GIF0ZS&_ERTpe_e z27%EX1!%pl`LYh5^@cHajk?@EbPECvrA$Nl<})pU<23okqy8&&cRUBW-(swVU#Usw8~^r= z*Q&dgaHm@-{ACrkp)B9{Mp+j}FZ z8HR1YGPaY+0owur5#QmO2ZZg<49OA~sH-BVr%6LzpTjRFTtE)kJ|Td7 z@=Foiy+DMgJt#C^Eu*<1%Knm}SzbnSokQ~-hh}XAcaxVnPqIZMo_02 zwqwiKR*?f!8wHS0{wRWbBM@QNSA}M`GMXPm*%J-T-!FAiBinZ!nwK4#U9IOq?|%Ry z{Ksmc+3bO#l8CbJC#{tHRvFFT9GX1^kWaQoaGw{N|9nko2Fqynj)&7#pB=G3l&OASJCZlvJptG~wC#PG zSCIde-fx+w8Ff05#a)6b?{ngyb}OEeMmk#V+GSaj`*aUokSy-phxtIffMv^Q%uM9& zxQ}#@Ylt6jxj{5|;lawLJOm$tplJE+79;dSwU^iOa!aIdIZ#yERP%)5`o+&00>m`X_0jvpwJ~qqZxL5^Ly1(>cH9*zJ}L_1um! z+pNY!Rj<&n!xk6rw|Bl@L*e2?R|pnTQUQ*>T_ekgZi-1JWXeB`71uYSSH8c|D~WA| zop6p+K-lHOV34g%zPt4e3D#0FTJM-65`E!!8chLyp;0eVcDvu(0i?@;++72N4oS%7 z@S-3=7suf!8E~L2i70{iX%{^ulF-Jtb{BNG#vPj4Fvypo=X@_SI6N%3Y#)x?VyjA(6v~hc-e4FKA6}-fB zP=$cAI%{a8r?`uZrAH_#U^%r=rMn4g$e>S4*7TUA!mQSJ$c#3R`L)T*NFpUDtOBoN z`E-srQfcDOC^V}_^zV;P655% zi0e`C(JBi+$JF%|3tQVBFePeQ;GX1(Yu4=DkO0)Wys6k! zO73+5)jsC_G0VP=Nt|4Du^slFpD?$F=VCI*RPhK}+1@k>cktTkb?`@wgC8RwC$Tj1 z$~FG+Qv_~NNZY@x2QDMfpB=#>4l28BqI4||tR)N=kw^`h3KCiO2JcD=vCwPf-P3;~ zfNnNq#jLu^_NYAl&s?yvtgF4|XhXWB_bmQJMlmSr;JD_mtoF_6*Y6|gb!^kAejiC6 z`qLVch`^ST+=&aJ7*u!{zPvR%2oc?~IU94BtH=VJE z|KbkdqyE5_V4`M9m^J~3SVuuRa@e6;KTt;iBc(vA=Jkn*(dDxi)^_6DZ5I zz%`^`-Gc=B3-Yu^1JbGL6e%y$*4)8CJAZLJZ; z%)*upmXZq(UfKnR-M1Li9Dy`nP>^ZKR&HbNlr;xZq#@p?dcUzx6FD083!gQec+0N5 z)Z^P~bdWY}8)`PaDkX1nI5^w;50nA5ImJV@C_E@+INW_^@Znn4*2(nlW>b!+V9oRM zV_~|Kd^5;g`*Sy6Xgi0H1T>IihsTQhEpf*f-R@$PxHl(_+ZmNnctI-}f{NP5s792+ zpR{aUnT&4OrK!X@oj#K$XDIqCbkZp*6I!CsDntf^5q9!WE`Nl953T0-o~LgO9orP? z-(S(~7M-G~4Hot~{JcVzo;t3<^0|2>^}0$W*6sTs&bolb{kq}?vwp^qtt3%D@3UIT z(5q>Zfhsd7eqQ0F`U+06ACOgxDGfd&gyAQ5cjb6{G@}l@|FE$j+zSLZJv@HbKN%?u z?P|~#r;H``aZk)-)dZB&S4K=T@q@=wVvbgk&+nm~9g(>{B6D<2GWLQ_tj9*%BkrPx z2B8JY&`3JPgx}GX7L+lT)!(7MAa8c-A(wJoycXp5fuwX_BFj1>2fO&pC7Khfof7rQ zM%(d0-kl{5kKkkn4)wG$EVCxN$>l+DwIeL#FqPprm?6=*rmS=_VglgLg^%)TWG$W! zpn$-c4#dCk^R-ui{Rh}Ehw=d0N_DD}92ObWs4$`0RXtnn-baFscxvIwj=Pek_eOQ} zS8B_EZ!}=_9k6aV8Oze)URCVq^nw{=@gj1w;dYe0aqCWz@VQ+y%tc?E+F|eve`YWf zqSP8NJ>goN@%I`W3!rrI!@H7jt^SFw=RBhDGEbT6FVtD*XuovUTliFGJ!ZW{!*#Cl zf9N@R|Av1Zr@Xjmhl`pHR^_?FA&d*0(a1Fq2{y4S@|2+lK|V|W^hZ$VkB9PyMIwmf zt09Elzr;tZf^hZ-eGhHbc@mqb6SvbKhG=T8`EPXD5kpfw+c85?X-zz$(sp7bDm^^D zv|}_XsWT~2$vT}ViAsL7x}*e1F@Mzn(o|---QkGHjG=ss z8d^-X=bA4iHBv&E6*5k&0B?_2DU%1HtrkR?BoGs;A)<#0{dzzR`}WsgG(Pr8U4%2E zF)`y4<|u)Tc6|%7$7gi-3wXUpwA0{>`+#ReXnz(#E(Ef&Q|u`v zn;h}|E4z7;pLd0u(2fqAm?so7lLen-%!rwcF>_f&VsFpP9Lbo0nZ1me^J8Y-89_cL zN28*L&hWmpgQ{YN@%!rwJ1_5kxOoj%JK|*8cw`7>wuY-^Vj~N;~lnqxT zp`wSFl%TZ;_tL?wa=rj*RUnp5E=5$Ik($)y+s!2HD7>xoVFZ+QrX^x}#i z9Tz_`D*j8V*hx22T~p@OP`pUm8D}TP#fWql zaly+OHMnSxx%e%rHEmp{QpiwMdZ1?M+gxcTRjYkzR)eVomM^{RY%dbKhI=4j75o`o@L z=;&nSWJ>Y1!0J!&l`d*=A=RJKx45Wg{7ZaPL&|{5#|&M>oj-~ad!s~g6U9edRJ}-_ zqF`JVC6$LF4J2k3G=<6V96l48XWfU;c=kFD+ed>Oq-3Fp#)G_nUWkgQn3X}KO~{ok zajc%Y@>Ny_c587W$M0dEVrEf`&Jc7oZIJ@R->5Gr-IS6iQjm7G2fyP^ZAZMFN|}~* zuBH!849fH5pO&Q`qMal8R*LG6=;JP?=o95=SdRYDMd#y-)MUfvA;IeQ#FkB`GLX#|1y@3yzSef(otR z%8G*aeo<8=fku$h2p1+QD~cRrf*kgrUDdnF%9R%sDr&2X3R(@SsLC{-WCJ6rwX5G1 z8DWX~izNAx6>u(@2Ki;;1=m82ouX3_G`z{(jHzY!EZ88d-Fap9t4am&HWKygEK#%b z@9god9ErfTIXCxR_u>~n?0!n4*1n-uD6}@NF&>VXyz5=fgf9W)uc_wQ-bbEAtB!E3X>L zX-nJ6ysJvBe69>6Og*9jYClhQjcuvSEBry`3_s&u;D11}0v)ZeQ!)3EL`tTxz!h3B zuFy(fsKVjSM}^)-p_+_C-uW8FxjbOjL(07W?46N^a9o7yS#%(#84VZYJDYTdwU_VS zs-F;O5HHxVIwjj9#PsJYlNu^hD*tJ z8i>cb8Ocmh8BBB;(JB)ivwqDc-vD+;gEBj0>CQs%K~#nrMbe6p(Bs35LNOr<{--$f zxsp%EL4diQBU`ewG+k!6Ah3(}aY3@RV(krgkis(DYh9JApW*RxPRcsG=%Y((^#yhL z#XVW|48GNraNC4IK~#ght*EVN9poe2@<*%49?*IlUYEuTn>w>op$Y^61wU_-y90jro$uzv^DMyB6I;Cd#45o;5W2MbR8ADv(wtw zL7aDkCHTQ#F2G=dV2<9m1=yOjJqI_5ZK#;VdzFz5=Z^)DF?;hm$Zlj4VfD<0!-raCOW!ZN|IoawPtmF&#gY?e0tIVnbMKJ0hF0`7&d1geCo`pG@p z`zqt3tDuKA%aaC?#%>*Q1S|X)W+?ljcjHC3fT5_Zl>8jbO0l3zm6AW3ZG4XXxi-d2 zf8JkYso#ekTzn#74E=mEcLj)`kP3C)MdJnp{V%k1my$Ca)pm#N+;LRxJ(@9{p#Acw z93OV14!?}R?MP{D7(2<{jLmA=GbpL~)9|7LHl^g-uu|^KJh~MhNDf9Y*M~zRZHV5d z^J9J9-J$BIVotKP$PsQ9_9n^|&NAZUY)+`{I>!nmQVJdxKOzxfwuIc()Y6^C1UAJF zqM`+UhtfH9^X=CXs}m*$c{e2DXQgS$xh1U~klY~R%2KZ#KL2wq$KTMJ7BeKSR^sjK z2_pM;;`$5n?!9G+(W{SPCGwF`-`1m&d{850*N^66oThW~ zZHOQ|CZsEK>Vn++mqGpp<4yITH7@Ew?bN&V?%EyeK<_Z~%X(+k+RFg<6jfz7>*As6 z9qp%RNDyQX$8G7TZ#l9Z;KiF{8qqH~R@kHS@2JB*;fhB~5*9=Wfg&WNql9SqoF~nb z(JSL3%oqSo$gLi@Au--1l#bZHd%H=LVLmq7V!e!5z1L(LBhSp4nmUdQ5!DBGKy0Gqa_Lxb*pRk7wpqd{z%WJa} zg>8VT_SzVB+Q*%b7{f1Yri@Jy=VpGp$NjY!_gsfNRbImrsAb?Iy34P(BUzl&$iB4G|ZFE0l zfSH-;d(YK5EkU^Z@2X*cZm637MsEp!(C&@ zlgf~vi6Bp~G@E|!ip1Sq7p2+I%%dL7r)qL!>p|@%5RzH7$)X3v7TKJzWN?uq5;cCC zZhVlNa^!l6>x;Tsm{zR?;YiU5XkWY&cdTi>6RnpI@Q&9vTP>IqQ>OJOLJoqdwmba5 z7Q>c8wW-7+@tv}1)|m5)Zh~@nBt}Y+Se$(06ln&2nG-km_&9EC4bS?!iE=oi z9L~8~3CZBtybYY;VRZ0(^LtaxsZ05OA8RZ8HqBR%_i!{9`T6Db1LqIvn5b2mdaN`a zsZ76=TuSX&<*5rw+0FX{sRmfFW?yf=F=yq9`)ycG5iyu5B|iiZZ5e<11y{dqK4V`# zp`gaxp?+sf{i)MQtHGqeWu2U}BdF5>zPs#sg>!#tI%p?{&6XL6OqDc0KsAKDsE2b{ zGeLlZEP>E|^3Bq?tp74HY)U7Y=pGHL@z)bJ*Ti~=6uXCE)vp;wSgFV4j0o#K!g?J{ zbGEDe&P1l_W6wEIV{zy^JZw9CXBg`AgC>|*>VA6Y@nefx@ z$b^?)Z)-^YH;L0;9w71R{sP^vqHUZ%Bnl4Q7D<=8QRfe97GpJG+r7g*X|R0nD2m$4 zKFj74b3SUC9L^6NZM9opH212Pw77xO%9pfeNZF83a-OCb_C9P?R@^J4qLfS&(fFhW z-Ww(KD55i&8pz#67f!4AwPB9&_OuM`!O*3oH9$(c{O)%{V87%|p+IK+GsQPnFtiCh(V#gz zQ6YB3Lr~B`W2Ee@>p$?-5o;Y;s*_1)WYT?D?(rJ7F2SWxtL)>2jZ0CAaAj=0O~>@} z_IpX|(-bWwb8jUZsXGM2KM?>)x_9t@sZKp2SS8ok>h$kMsyY|eWiL~p>U<03#_D%s zq~GER723P*ZKl0LtEfcU+hGkBwjk{oA}vd=C$W~Eow=rBl~{+t$T4M9NvUAMh!lmk z?9$Ureo#4!?L<>L(;`J0MyGR^o1SL=@=3;AyGo_745sYTuaI%?YeXMyvA~t)awD~~ z19Q0~GMAR6%l=~hZr@2lNRivCEw z#l@`Cs zuHkSR{XA90qo@IyOluzp1JiN^w99|NVfYI!vJ~F56A3tenx3+YI76)lB4SR4;Jwqz z-IjIjhWbe8zBxt>2BE&j-nR8bekdZBiO6Up#2wOj<7+jAMIoA9!s-{w_{yvf3(H}` z@=>r<@unHQ7&T|0lEo>GMNq!07RoOO<>Qo42&J%K)QWAH&{O0nS`(k`qFCK{92-HmwM4fwLgK6@du-N`hnu&d)SYU=hYOZvd8 zU(%`wG!+E=IFV?!49R$+L?exR59VzoFP~C3HfM8Zoz0qSq&HDw946rReTDQ?MLHTV zu0Hr6vfGn0?$~*dYuJqV6Whkp!_%mnT!Fx84c1AYs`SDHI<8k1AA(Nx{z&%(2BIGY z$8KyH+DhTE)OM^7R6ec*Z2UL+2o7`AQ%f&85At8=<&L=T3Gbq-r9=9ZaQo{W{kho`^hzvwhBVu6QN>HBI z_z7;sdOtHH?>nAqoY*zQihQ@qkBe&) z*YC#d@^IaO4weCtpTv%)@)mY3>l}L(UdB|xlwK9CWNP2emb<(k?pX`|3)c1>!&tsT>kNjF?c?a3~12``f1RQ#;xwU&ylt^U7M@tgx{t2mMe zUslEqK%@ZztN*dNoG(Oss5E)b7C?m2}k{ zmB&q&BQE;UTXg?Vd*1>dS5@u3Cy$brhNPt}EzhAeP$(^-Aj z`TqaC_MUTQGSjAre!u(unEX!GIs2@=_S$Q&z4qE`?{iL3@;u$Qnf^4(O)05ML(=4J zh@|42$oaI7QUlAFCcqxBH{cXg2>q4=U10B2whjt5+IX>7s)(CTzp+J*)yLe0QV)GF zKi3%gnEh%l6ubF|xws%JONN#E&$Cl^jAWeXa9^XAljY3HFTiy4pW)K4)5$(svcG8b z89Y1*d82ZObm=F%^}HISNCLM~N|y2<=O#B5qV~Aw?#IAqtFdcJc}OUdern5i=?}v5 zR3-XC3t&x;uK+ebq^Rvx!Nn-vCHM6E$>K~cu=ic%W1E02{{y6^uXI>kYZf@OF9$=B zUaY@kWHQ=p>xW0T4L|o83RW4iw7+d;tlsJ>H0vz-a|ZhQDqJTHB=5wN_eaY7x|`E& zU;G$ts>w4QJYY7)HZ}{(roeRl-dAL^Xe_NwzaslA8S9)@JFo_jsnzx8h$w7Dj6&-u zS-X4HUn_+VwmGFHkQfDuSW$WJ6wcitY?OyZZ@huQTged5V)IZlP<5Tlz!d8I0Zd-p ztvOb{1>`=hy`r&^U#qOWWz!f|feRn{{IH+h}73Dxrk<2U}aFPxz^cfx>>wHy`t+SLi9!_Xv6_ zgjWga0_)B!Sof!-lJL!h@Kf7Eo-_W_Ol+R=s=R{!+s@+I=x9Oz-|+znZ=w3rAO5c7 zYr{JW<{YsvRe_LF&zz_CGlRaE^ssUJbVz6fGg^9j+&Sn?o;eF$8<`;% zH~?w(DW!Tm5#0l3ouQ5{#N}Q+2Dzw54fQ%r{a;`djV;mm8lys&m{ z{lv8LxnaqtD+L2E7vDuRGJvY;`e}+yqjkRY`W?b7f%R+B94s*$Kpq;uGg$;H0S>pgIq_0BSFw7?A}+@S49197)!KbtGY0ftwFln-h8LVtfK7DEi zE3m<)$(-_2iyn)H*@NcJii7pYx+EEj&iw2M?tc z$_~}23$^llW_o&4ZcM&!Qcn4$4b_XavY@UdUrTK}7(6znxTEmGoc?R^9`xjo2Q^493K6x#GEaLGE8kqLq1;8KUWabnb zo*)e>K@$g)Rq%ljx#|N4c>l3nFc*S|Ix{}Qd7nPRIDgjpnD9ROGep5S3psoy>7coN zEP*GB?MX%#$1N{+zSRuet6!oc&oBiB?$clOp~g1h`(5X1M(#n`qtoxeMFP();bJFz z<|r;X4Z0ZLx8$8tF!R5>VCH-bq$isKyAHyJg*@n3E1E@YKs7HM=5y1s? z?z12fK=t+!5ycGyCU2UBl}r&AWT2&X`x&6dGug8(Vm)&1k+ZoFs!IV8W(Hf0pqYIC ztFVrOM!IYau6iz-1c+y+a49nd-F-5jFPGuyj$gA$(5U|zU}kL4y-P3Zp|(x9!5{0W zUl?S4Km>G{M=ohWoi8e}`pGA`n@94aYMM;E;K)2a8-cBSz+ineMbzgWk{X}`c`wQ` z*-}NaHR@=G?DWZ$Ei+>nAWKMuZm(++MJ$7~Q3N@F$Ucb~U2?-KT4Xv2+;IJpB2yCq z?|6!`NQo@td1WoH^&9pb^MlNxsldmIHpFzyrSchT<4MeGALfdCTokS}ER-7x``Eys!0Y5; zP+*yhc@v`35Z69^jeQVA`dvP5A~9eR36o)XCrGQqIYkbDD=HCLXBo+J+ydoyOG#rR zX~3Wk8!mXAY&Bdo@g}Hg2;@$E4e=1E$54nc5fly>3cOBUPG1Q*`g9Hhaow-4Va~5T1Cid@_H1muXIcO7Vo5W)aP$_@9?@j zvSrlg30|Q1dssYp?KiygTJMMB%|U$=>qNYX>w|;?#^>Qha^A0`R=bD^t~CVakj{x( z5ox_CYc~>Uv*ad>7RjNVnVy_t(po^04fCb*5y2p}0>YJ(nfkMW+)2k#M99e(A4pmM4D#zU2N6yPlAGK7qL~lp^S#?^FHBCni}`jH3qI znV;&pzE{VC$GoJp)Qi!uIx(^X>u?(3Tb-z8cHZ-?NB@ifU*AD8WevOpmOIq&+>|$c69o^Qz4*2 zrr$lheUjcCn&cb+Y;suxdb4>>5qEETgMmar8C|77Vq?*w(M7{Y10`QP=Ps0ncVaDl zvUlR+&^eX(!;z_9Pl7{yccT;`rh}4N*Z@2rP5wGGMI~pWgd^`t6kd%~>`}RB=Q&qs zuBX>>(lb!J03bO9`%(}HD&04VEQgc$xAUA?U^G#9iV!;o75gKicgXM`0LdRg4o?^U z0$Py=U9+0Uw$t^$z^%D3(eteUgUfl>06UEh22ns*^ z8GxN0eE;|!o+YO51V|Le@IQIeCoy2_^c~BCNKm~++;VdUa_(3j*9q9}UW&^^VGAyg z%->mf1K^J3@6tCX68pa(pXYWgud?W`;5t#b2$zAxX04mv#wQ{9x%+Bwz-xxDD%|(S zKaJu|zt14B+gkWbT%axw66Ei{V{u&-xcJ%EPes++uL{iUJ-FzZ9s8(Dr)`~h)jh{> zQu2}gJB#-XA)?^H)#&t#QC?sNUcNv5AF%NO756e>XW`c$(%>FshYcU zc$t@6hz3q>Mn#Q=UAP{dex!t{l}LsBHT74Jf`%}9&wfw=OkN+pACr^O zvbnlk=viQAd`_uWw#4g!#1G$iA}@YPWx{_9FWIM#>xF{k1f2Qa_@*a5F)(o5vE;up zUOiB8?8uB{r@e!L<=G^@l7TVjNs%S?!!1UM5UAE^+G6uq^+5405XtED?-f%9g_|jU zbd8PLN5*`a=4NBEeu5;|aS00x-F|243p=PcY+?Y#w3vD4qWhfc}=D8nosizUXI}Vkot7t3?xo0P{U6b zmg0;}iVR;_0ss~|asEYf9>IDaDz~$+A9%X$lU_6cHU2%UG`>RtMID{K6M?fG%WnV@ z$0ia?9@8Cp5Ll0~D80>kD)XcsFrj`15}rF2x5l~;_0B^azM!`ZUr}7{E&Tgg@#)LY zCpc~SY=TpkE8oXFy%Un3qXkQ;g#Pv)xcDL(4Eh5u%&pO~H&E7d&-v)>72WG#Yj05 z;505yPDTDe;-DF_`ZRXo!^p}M-{|yqN%31!> zP?NvkU-C8Iw5pP?ZJcoJK#iJNb@xm0a}(E3u37p*bW*}!l>bD{(vfJ1e{_Ff$Nsrh zOP`It$`TU$U85WIq$aT+J^A%F|1?^icqVx>1cW-zuAR5PIx)hOdnhv6&DPc0=l;Wgo}?=b$}|xo@5`Q=1ED`{VCIC3m#J)d+lJ;R7$v|JXcS zFMT>bIq>EPI@E)>ePnB30L~HO;S4a)#*R)efcI6II9R>trP1j}k!;U@OcY=*wh50T zp$2&t*DQJ|un(N#hm;Er2KFs7#nmi*xu&G@V9m~R7NE51H@}CoOUV|p`Hb};&!v@6 zy_wpBSiixKx1IjlSDkpxpEy>NcokYc`kkHFDcx66J@3cFZlaEU|H$DZnocL^`23r- zYZ^#ARkJhTt=x&VnVof|^@iBu+Qci>t;Yh1*LO4>K&F=iCI9rJK>iN{J2w^?+NFVc zKee>|qc0vwGFqoi2!?%9-$Z#U$Y!+cbyR$`?C1Lb_x1k+`u~^o{}**@pgmp)jFxS< zl1-z3q^|9`G|(b6`N5jRw-T=fP`Sl{yI+Xk6iDQ;%i&pr>GwlJp#h_%cMtdZ*~bEb z#P?uUM)?U=eKjR9@+RL6YiMdFz}TdL z!X5NjV4Cojz7<7!<#|PE;AuY=Pmsl z%5wRG_>w`*&S`jY0pwb{=%pG+VP?&|k=l|K-wW0x&Mw0ks%n>hFE*pyf5)rpXzjo! z6lT``Z{{<#_h?N5d7+1NM@!K!h5xDq0N*1Cc> z57uA@U+aOI3%*<3`sB5VX{by^DOL*u`PUV_;mPVAIp)aPuGc2~XbRl;ZgMAj%s!)Jx<8Mg*3e< zUYj@+NF1&is3-u5K;rr8#D67yAXJ&yR8W~%SKv=P3EuPkny~+ZVSoRt?Um?+v{BP) z65pu>{lxbJiEk(G)wza^KMx)WzttQ{UQ1^4uT661#6ZH4Y5adXrbbe4P6O8-4Vz8G&<1IOPuvbn^Zm zewy?CKHi^~JO426pPKvr0p8#A!<_W{dH-4Tj;#D81`hK6S-JB!@qQv#{(*r*I)Co^ z>0r&{2h+;e>uGEQwEeFF0|UEA+m+LS^Fz6Nha{D>qL{{~$sS^6y<=#V`1 z$b+ay;S@xMj+}y%B8G@|JXH?0b^YA!q02fe#jpBH{C>oTYWTZGVDf5#2MruH@Jj~n zGVr|y-fCdGfolz1V&H58Cm48?I}-7G(ZDYoc)x+~xA02A>o>5+z?%%LG4Kil&o*$f zfp3_4%{2A;mbre!z+DFR8@ScLn+#lQ;ME3t4JtTFHsLwBLMzS>-`Ht;3`;|6}; z@bgUr_Zj$c1K)4pZ3ea*xYj_QfpZL;VPJuQ$1XSZGw{bo{?8luRRbR~aL~Zp41CVy z+h^b&19uttegpdq)QyUN?>G86<0>hCvVjE#zOh)+e{JAF1D`N(uYnI4_;CZ@Yhc8{ z76aEC7%iH*gJ=OHTpPTD%W#Fwea@%NNr-2JIH^{NoAR;n%ySF2{#jq4@KtIAX#CsX(}s%ypUR}rq&4jsHPm%l>2ax`ADBM*5jWS zu+&qm?!nvT`MV5%%x!Dei~MYhE}eE6T85lPH03xXwh5Thp3Bumn)*fRM$mBTc^PsC z(av$m`(j-N^{gJ(QBduH1S?bvT2HRxkSc$f^{c@Bpyni~jrkQl zAoq`bR^az1{&~bHx4L?bpmIs};oZ$1tn}iF=i!#%&rlZH?8;rR^ z2}P%uo#G)ynV6u=-zI&khRzjv)cr!*BVwT3J@Nn7jXc?wc?z><^TTUU%+P34(qXo? zPsfle-%{bL^lE{_K7mKg^`UFzdT_D8@?`=e^96!RKq$>r3-w8nTA4Q~Rs5-*BD73I zmEun@Jt{Zm%rwlr<6vHrhM9jH%#~@F1;@c`Ny99J{bG%Z@VBbKsZl_uoQelTx2n^< zGhs<1%A2a)1e}W;{Ay*slZ$e=UrqIvsz-r8GlhT32}>E)rMzlO5n<#1B^c@6qjswo zRFTIxjYvIumgw8y*#ciMFktjBl|9Ev6VlJV{KEw^&!}JX(2@6kA#&BO0XAmAjrY9& z$l1?K`E(NFerce|_+9FtOFv1nKcoLQIpxyq!cJAfmU86hC?n&SGirI&1sDn1fVU3z zc8j*s+wk8DOKnFsuPVprQilH_*lzn9F@8(k(Ue~5Tf>5|@{VsTy7O69^oc#`;^gyBCP{RhjOQpwLFxL$l5etw*R|MTPEW0h3> z`a+NPDV_p6AJ>~N@~C4zDS#!ft*@;Ak0rk>`kRi`tG{u2hx*sXd9N|OV#((Au23+x zxwE+~9FELixZqt2u9zQ-w%UZ&aCdjOXMQ9azB$+$Pfy((Z;o~ZgkPl zg<=aLZ7rbd)D{;Q`TFryDEP9t$o}J>~Rz$s?~-l^17@H}#!NBMqMxt?-{N1bNyl-Hwi`oYWIUkLv90r@+{ z;Op{QP!67*Em;fsp6XGjMwFU$alSgk^HEib^WPzj`=ULNRncsN`z7E`%2Si3PKbC= z<{3=Ka54$_o~e)%X|hgCC%h97mS`5QStkpb4*rT)0J8267>IOtUCL3HqlV|Jl2V78 z0V#u7i}KX@o>Pm+r_X2Tx1bJ}6{vGO7pNJL=}o*x1@oLH@|iUYynkszgz2=>o-^Tj zdEiySSDN;v(#R}0Fta?fblvyi{;YGUcOClL3xhRlZl0Rqxn5s$1GJU_%JiIGpiYN= zPL7<~H0!Kq>v1&s5Bl?=#({#6%U_@_@LW^G z_MK_&{md_Q!g43R`-ie{9K;yFA8i+7=-v{KcJ(MLeFOgvK`t5#`hg;q*FYa29fKxw z-4Do^(2#+{fJMv?sAEE)&1(f{=`&_Dh-=?5k^A=lB7#!y17w*mo9o{IGSBNK{cnFS zF_2L}j*(aX!R(jFb>nUl?XMZ?T_Xpx0KsmE!Q}-Ra z;3>{m#lV@sK0~{Pd^-P~fV68Z$g`+0IJ@;6&+W8CbyDsK8dTewXlX?AM%zO>34G6K zdFr$mCPik=&4-QItV#^8BYB4A6Raog^Ndp3Tkkju*#>Gi6Y_FLm&LkGOXq`g{32C+!Kw zT=6=Cy$hVQA0htQ4>=8&J81=oc@9oTJG$uYLz)ltcDwM7BCQX2yIg705TEQs+FV!K zYNQ=wyzp|TjQ@qS5#qVhE<~*J7}DxoX;H*6y)#g@i{5QWD+OM*-#hDNBA0j8%R~t$wrEZ2J3;evHN(O^ecEFO$*Xzq$D#FHw*k?kuHjHv3{ zfH+!3G}s&u)_69aVkm&iplzV2`Cc zL-}K|t`2+M5WY6tvZjUM5T)8YD>cztFxlJ@M2XKAGz6pFT|Ex-N}ZVpZfnDBy&+wJ z)y>_+{Gj2Nb=shch*Q%Xi&sUX;V7bB`DF3Tp-{LL4-6Pe zx_Hf-wCHYF<@eSG*$JX4O-v&*fj= z8;owx%E!OCbk*G=dHL0`%3w=xM@KMP7Y)X!jNf*&C&ipnGti!~-tJ&SCptx&Qu82{ zP>}pIgdKq@b)JWeS-t&d)_+}3r`GgvyVs=ydA3sw%n8A(Z!DFDtP%I z?nc5<>D1snK+2YAWiZwn?TWxCR5rJU)DF}B8-k(Gbv@y2J(}AfbF7O6qo#jOQU10z ze=F?|``NZ2E?}J0Z6>#ovp>c>C~>vBM6J(TJ=Hy3@vi1j*R4T_Nj)yLaw@7|TUZ7p zaWkw?&($v06J}ak^7^8zJpCJ&nO=v#ohCyqFNF2dwrgi$S8Q8rd^>H> zYlSgW0BMyJHg1c-B*md`ok$3(Ypz-3d$)gMU6p^M?*_G}ushh@8re?VR@_^Nd%V?n z9d4@H`t=oSFE1|_{l4GR*xViKXbhr%AUXkgZ0iWdyPILBqaDb9E>bEQW07EMS9@1$ zqx2cbFD5!2j?pP2?jtrs7(cpy*(IT5Bf5RKM>GFMUZYO^RDPpL z`A>C?rjA;)ha20_ZEyy9Jp=7b0Tg zSsQ7U6l-3p+tGvWV>&vUzZbNkUAO8%nS%%Id^lPW>Y^?Ddtz~f$%dw0a%iLfp)bZ` z!CR`^(IcL4?$pr1=uvx^QrpoTuaAc#!jIg+kczdDx{9?8SBRdVKY^!PrPo(<1|cj} zB56)nhB4Omv9`GfV?NrdNHECjn0phPd!~j8(IH)mL($ef8t!fgZvn@r9tVTQ%D=85 zaD!%fs)8Wvg3)&RAw3v{IgqO9I#ryfTKEDoG$~bA4`Z>QvZe-ZDjR8aybgvf>Neo2 zZq0_Zb!*qG6o~}YYBW@&YejE+doZd8d}tTaX!THZ4OMtmG~64Bsnt~tE?YET)x?@> zppCr|Ru@f5?*b!GU32Re2o8}t!RQG0kQmockwc^y?~V3Ik!k@`t^U-6fo(@PZ4j;L zGgi6E8SqzVwMOg^rWAh3_A$54S2f}0HkJ*F?b;0kPwUW0(o)sB`l_{6tJiCRJS-!< zOX3ybUUm+3Z~mIzc%(P3$8@pS(8hkp$KinOkJ+JHy~go^SN=H6drNOTNI$o^Fa%~{ zwQ+uh_I|V%rhaR&U2U~uyK&Xja6x@27>ua@R8>8#bWFm!!>U)|XXkJL)P)6W|N7_rY|nzY$U3>IvyD)@BU5l!ZuislF!CTJD;SxDAh+Q(VfhOX8vS|mz6 zOP-CFF1|}O%6Qt9YFZM>Z+culw{!w^12OfvtI}!}Q^$^lWQxuw_A{;M?FywF3TGUJ zL_g>;3fbDqRx>00SKZ^XgRgysFY;a%59J>WrMhU>R(n~;R-Jtf6 z23y*44NgAgfmTb?szaXI<}E>gODr6M7SvZNb-tp@L1$d|uh8~&Jzcl-1|g^Sda7e6 zs;f==ERU1QYS=ZpNDbi)T|F{=xRNre3HD%|(RkAwU#2qH(`8*5bso}h?v06%Q&ov! zEg$04-bhzl%Kczf)HK|Yxmk5jEKZA~5RtA3gTa)q!+n7jVaL4T$xY9iP;hN8Jb-X0 zXnO3Lnktwv+kH>-G*QU zHMA-F$lkYa^uEykA|u1{`x5{o9)pu4fA22gU;(VvW$;wEm*O()2IL;NF#aNHv)*CJ z{c`iM--7!JgTU>>u0yD-s`Ck*Ymoy&!fwqV=B4mra1Fz#{GdR{Yjj3+t=Bf9uNL3T!8a6pvrxe+$R_Y zr66!(dT%FpTasgi`YBbTYr6&9M9iMTt!Cd}2k@M|n#3;)Nm=Q;Nw_Pt4LcQOcctu> z6kgsM9(Pl6XQ9w*gUq>ik9A9@mmTlAV6-I|4Qqjfj9hL*e#*H4ZNb{FLCt#rqqyIR z+GeXc>&ss?T4W`1kedcz2LXGLkGZ{CS14I8=*gZ(Y7lqPdQZAUsVDj5j$H2745N%- zmR2EOtDuEpXdL&_ZcerQpUdxwN){fyD4%>*qc6zL));udM52(Git`2=3Zzp`El!)+mcneaI^8tJ?FA_ zo1D09gWc!trpA8DvF5~lXVUG^b)=Q1t#LmQAQtyBj1sr0tJN}Cs!P;tR$c!{hnAAN z|0sRgmn{-;D9K*!DEc$|u$$uwJtlJ3x${JVV?S4*6{Rk=Jp0r_17yml7TDA3fkk^3 zM2by!+j4mhKz3+zU*bYYl4+S|3v}9*xJ$juy&paoZSqK2a8Ek9VSC9F2%EH?w`0dT z?Yi)DBkodP#7@x0a}PUn*fEauPBhMnEZfY}5Vrjt`@{C(K6xv9d2S#xmyFYVW+4Qt z6v7|J^d-;+htfLmCEQD`M4qB=4%T?fs|Gb0{i)Z|#%`|ODqZ>;$FIjK$bxMYF?uMn zd#mN0s2?r(F+UPSWM+6=@gx2~kLv&+l-j zuKiIrd5=#`&^}Q|>s1x@1CfT~^YBR^_F6u<(*Zu*eTcNUGq@XaWM7$-(mvT2oJpTg zo6tjKEkysumPEVYMOkh5qb1?U$}+LNL;qM_So>9S?bs{vO=GYD^qg&NNrTj!6v@ZY zG3Js7^I6-$yzDFdiLTRXlXHdA`4p4ohI2}(CG8gFWG$Y^glR2Ei-QhFYlWK3SC^~5 z!L{^nX(h@lrX_6sQQCn`x*fVvA899A3eF?w9dV4H2hUz3rLaEaQe^7n@|wC6t5-{2 zp5^L7K9(X@!Hbr6<|JOwrVZKviqsHFoHbwzTkn+qr;K^d7)QNvN+xyToEDCf>jKv@ zc2S$8eCj7_E!yMOC(0}HI+wgfCuo;M&dlf5lU#Cl2E0-q+#{`9*Q6Az7wUGP z6|r-p?CqP0XUn40**|S7ZNZs+j*ERk`ia#Z>R#malm+#OsbOgH zcuJ-;xT>7pE}V#udY7G$k$UW?YU`KHo@A4!yPljo(k>le3zV+A+kR!E%X)G&iE2AR z`<0t&rk%@1&&_AH@r>oo&+^CgGFUyf?rk`0)BojM{?pTt)I|n|z8P_^NMVbmoMWD| zv-D&fX`N)1oOzfHGxwg*$nqL;nUf6f8K`it}` zpr`cY*(!1ERZ)`kd+l?wnhnf(|2%(M{21#cliFB4rC05q!?0xLpvAX2*Q8d?{=P&#CwFb3nKwZ|LoO(Qe$}w#vu4~|F@X1}rDrL7tlq4+($1SJdTMAx0 zlO^K{$2`vW*sH}xw1GEXaVA2qcRTX8=>Hrq`P3<&evTua#8HjhlYK4d=Gg5l{R&JCPZ8v9C^kt~Y^bzPUQkK?lqm}aF$zqNLUi2RJ z--~d@dU!GWTn(9CrrWm%dHam-kggB(G}-?c-#YnPO+voRKlWnl(a0Rr(HhaB2)Lmx zi7wFU@d-EkEVXC=HO85ZZ9|=@WiqxzaK%`Z_1gma@hC6X7_ep>jC!m3uuPAIQQa#L zr(Q4dTfxH;Cw}uGN&Bla_cQ%b>J(>S@~pf9KWq&~bmbQT&rZD6_9OaBGO}^r$=`gm zBY*C6KWJ0qDRcYVuBFg}cCt@gi`OZB8oesY!SMwdtF}>=pstZC5i<$p+k^ExitPkvXF19r-^Qy41z-@w)`0P7I|+ zS%m(bOgZd*)F#@lbnSNZ)$)=3{u2GHyY+F&z+XqdrCgS`2J)uAQwa%IV9b|D6}^W^ z@D+lv{Mqj@FMm#d&%PRzDVc0NlytZr%F+DRteU(QoN$eaz9jXh79#?q;2k)ZwsZ2k zb&pXAO5TaDIe6K6j7XnjG~d5s*A-|%#P6_t-kPT2PAQaL6*!^~VB^U2+M2Z;kr*BQ z6|PfA@5qeXIs9d>#}cd#R>6C(QSWw*hL$hNZ#?;|wZ=PbzzxTZHbB-pdn;zA$#%=s z?;uvXxh}Z|$IV*y>FY7>{yrAd_9xqa(jy?*@TI@5MwuKt)Ad4TM%JUEepjI_*qUzc zSPROJw#r#6bbL7CXDeCqT?+};;tfJ|kQ}yw7<*8q$0*KpI9l1+CS}bThmFgTf?FQh zbFD!8kQ>oqk#~je3G|LQFX0;@c<#)p(&@?^!)l!H?JhU{e!3U>cF9+Iu*|$0&@0Jr zGuq7lSa&Oz>^ev;)7RD_Oa>d8ci(vL$n0V_`d~uKnqBpQqrAk)<@Q#vg3Qv z1|0u6E^&6nk(4nEj-N8myH2-sC{>m&9REa{*?N`>(G1&5YmkpRZGRSvo)i7NCFTb8 zN_3QCPu$dqGIiv`=#I?r*Pz}`#HRr@uSc&~4Ok1?C~=?);MU^LkMv4tR*lXhaiPq4 zy*EwXj5sjDOi9phaJab{E!=_>{C$yB*JZL~_M{Hfnms^vmANG(vSf6G zzKd8N%9Oo_{`**+ATDLiy3le)5vh%tSZgM}?UU)Y#BEEdUD<8OI@pyejLqqmMSLV1 zCH_lWo0G9MVp+rv*cqYh406_TIS%BO5!=EUH?m76GdJ7EnLCgNX@5uZ*=&;2hp1W9 zisSKR76(}bPYJ=Xf}Q>obgfk5UU@@2y0gzIMPr8Lf2Xg zcd49V*`8{xujMdXThIp4o8xMO6J3^^vO>-=)jbb&ubI;htkFHzYK|Sttrc@To9v~_ zx`pEniCwULmi?9f1$#I>M2Bj+th3Qh_buqnUW$1O>qOs$^{xeVJ5F*I%-sSUC#BT6 zu!Y<8Ijsd(LB`sa4agORH%0zB%shzk8@`1#Gk=910~>IcmS`?Y7rV&T7}vviZ__)Gg_POw)eM< z(UP&$ZEKP8SQ?X#>s^ke#=h&UD=~&6)e<6uFwlT}DpcY~#Oe zNsg@Sf23fo3TL3S#4O*IA}dkVNP9cxJ2D@$v&hwE47c;TT2Mj%%{EnJb+(-&t2-JcF3D zpgVXE#br6RCYg1dl)7Urig+{p-|CVd#{j82DRJy` z_QqYL%O~%&P~_h!WdW`>=v2Fp(5v&4zSNHOruCAYq0(Eq|6%;}>$Q})dMwY%IP)HN z4RXoAKBeRK;b^g0fBL0-9*b6%J1_0;?XtIS4P6N-F&6RIF{dhk5rv1sdVrf%)GPZG-fsMPc zzRrjvIoALG9 zJvrT)Tis*ZZi1y{biEnl$0e8}Y=SK3!Lz+lpGPr6>%*H-dGEFq?v0q8Tnwy>jAgL? zk$ZQjops2bk6e1=_Q#Lcf2+`b^sG2avBz>o&-qjcJtmIPPxj745kt+^vuJ~bLgqSP zJ)}1t9%P>EetL;>8NHoJchqe0`1Clvy^<)sMDU%6y`AT!>pge1(I#=u$Xc`AtVVlL zYr8T*t)V4h+Y^uDfW;r{3sQQdYUlV4MOro4zn*da{BB)_dK5ZJDz#`THx6g0oy`i1-3zNxC5U%+Biew zv%1+^pAi!JK8)H>&&1MfgU)a-DXqT5ZDePu?WeS6^knGqaSg>j4@lj1o>ZYlv@<_1 ztdCe~@!QBBJy`0Uy(6#abY zDQod3#`aUEE+;d8&p!9g%^$5iWA}bINnyNA zdI2pb&&K-b619-hIl72X-GhF@@iwUKn~fE6%BTYna%;n zYW8$mI~lb&OJiSUjFK^pNw`M|F#=3U%j`q?dKpsaQKa`j_5(`Ou^`zZXzsWP+uicZ zNTy?9#8cwzE4M@(eUm7;-BoQpx3T@w(OAk-d}HzTh|98M#nkRNcV?QryAEDXHN3D% z;4`zPq$XoF=P_*k08%&?WM5@(k=~oR!pl07HoXQr_hjT-eATyNV@{?wJMyqSSlYM$ zF4-^s*V!NJ^lsK%|L*>4mM`m0^U*@GR*1JE{Z-2kK~p4JJsgo!b;cIP=Iws^vHNIq;bqpB5~J+o83^Zjp9-Wz z!8PZrT-Rew>78>_;_t2I%Pjw`%7K<|EVlvh$(~Q|Uii$E-+1$7@>vDCHpXx=`eZ53g~**}Ep)BbSXs*(CBl6uPTi!|?4R%nHT-k%(%4F*G3{O zmnV|&Ty-J(zFWfSE#UaUCmJ75bU#xbnb#*few{3SW_gajzcqhb>dXFoyysZ0UvXl* zO#U2yIo-02$1`Lf_;d4T^22B|N6@J5$Fy_o&z3h@Wky)!ErX1_@%$FVAn^Kh{Tqv) zX~BsrV>>g9#jk;^W$wl|D2(rZEymbxaKllJBe$DhH$Ab+ESDIWW2TIWjGNlouEM-a zo&*v5w+1%QM$1XXxt|`*xBB3o=U985zLGmzuRP9%6}CGg-Elp62ck%v5gUne<$~mg zb+8^(=6A}MK=Pb(;a#>ST$zMA;tpOs0JKS@I)_+cWoZjyC-81ExnV`+A_pj%)YX#3!Y1NqRDiq31#?CO9&(b4NADKN-PgkB7q-SF|nvoOh6FI_F$3E5nkMaWU!4bmwUchsv;fri@ zkPg^(CiX8}i0^j;&OZx#==zWn1{k2(anc%I<+06&3aGid^rTnhT&xfF0Qj^6>_2e<`C z9DGIq_uyy%{$9Yye3VQ2fIAi|bqM%_fJ>Jub&z=hFIlG4GT@X0PQDsEflevlf8ZDa z&QZXZuR&g<9|HWkPtu10=T=C1Ibb~w^40|S%POUsAj6}8r{mSDO!or*ew9-DKogsI z)CD-0UJkhQI`9d6A7CpErbhrraF8EDe!Pt5b%6hZgY7a5*is7_;yeQQ1#`X^aK>t- zZiCFdfH$mBY6#~|fKTD5Bu&7NHz;*4(uV+-uUBfAG6DQDj-%jV81QEsA%EZ>1^oHD zAuHe?1>A(M4N^W$fc{3MeBi$h@PQ_g$zH%$aohtsM*(}A(Jr9V57^Oy`jBV9C_-0v zfo4D87jO`NFJPoy_#DIses1d$x%C5n0S9po-3)!)qSS8iKHxp`50EG1Fbo*_IKHR{ z{0QKrPvR?RNG}C!z8`f%-U#5QaIo$}fMuVSHYx}F_XniDhXDWmAB}tfeGiGw5I%;3 z@*f7g>T`1L13Z7Xv_&~!*~5}v4%o2=?Ls*K?!$2@^8()Tc{%R~oc0BL#icHw*k zFo}bCj{<%)iN1ich5&zxgZvx@`zLf&5!VyP$9pL-S`5@rSIJ6u9H@#+b z1Mnv}o!y0bKn%Nv{L^1P zz`{2nL*y+5Tzm{RANW4Nul)(~f({G={x=TFT#e#u$vCte0B^+6kMt(MK9fEK_zC6F zKEM#*cW@AY1aQ=xE00H=h9g3G04Xys1iuLIOmpr9 z+=*ilIQIg6zYulB`9Z*069oTkz$?u8GQeNs*bN-Q{8I#{0B{+O14t)~n{!p>tl5AXvx zI*9}LTa*4fz$uf3pP7K4!|^Z3yBF|z91mV7@US^Q3OKb`(h2L$IpN=#^M1gOne!pQ zedc@^@Sr(A1o#JYjz&>u;b2)_z@_Hg2e`?cHv#^wIqwJjggGAqeB7K51HNR=4*~wc zoU0Ph$D!#1-i_lvlr;$Wd6Nzc`OfcuxC9C`|9SZCu2No@h?#(k0Ri++?t5Sj_^!3= z>77~9Cn96NkVvD$^g*Ny4Wqw-=+AM@-_Q4Z@YPf2n#n9P562Za`pt10puT~dyLnCo z92f#HFGt_PJlly3Fp|LNC}+qqRczMvyuh>TY1uF?#eIH%VS#e*8ulV>p^36D!abo@ zscA{fp#~DFKpq==ASX5gz`bmYKadx@W@c$@JRuFn7&)&e1;#TO2Wi9g@uvf( z-7TT*-oEZoPi*;Yd|G%3K6xJOZjR0G?rO#79mDPM`Iy}=X^wR-*n0VFFTSMQ)sBw_ zuMb9J_yq3q*_SUUpM7=lB(HZ_Jc`ey@^iG~q_#-%A!p2d*j>}{;h3Yov z%d4rI?%VRRc`#BF+!_pdL;Sydc5|${XDhxI9i8p%?UFB6FQ47s9Et^JU%l+I6z%lP zDf%vqF{LlNEREM?m!&F=?8`2*^1QmvU+1r^tX{op<3@AxKkZtTV%$fcse7pNp~yo+ z5AA+v??d|@I`Htw!v`Ne^l7T?djapzvrGkgL`)E z8QQaZ&)z-z_6+Yiu;Cw(dBaiky zI`ZhDM~^