From cd2b4cf394506cdaa8fba4cc1e165228f03839b0 Mon Sep 17 00:00:00 2001
From: Alexis Nasr <alexis.nasr@lif.univ-mrs.fr>
Date: Wed, 29 Jun 2016 14:31:22 -0400
Subject: [PATCH] added maca_crf_tagger

---
 CMakeLists.txt                                |   1 +
 maca_crf_tagger/CMakeLists.txt                |  24 ++
 .../src/apply_template_crfsuite.cc            |  72 ++++
 maca_crf_tagger/src/crf_barebones_decoder.cc  |  90 ++++
 maca_crf_tagger/src/crf_binlexicon.hh         | 365 ++++++++++++++++
 maca_crf_tagger/src/crf_binmodel.hh           | 406 ++++++++++++++++++
 maca_crf_tagger/src/crf_decoder.hh            | 147 +++++++
 maca_crf_tagger/src/crf_features.hh           | 100 +++++
 maca_crf_tagger/src/crf_lexicon.hh            | 128 ++++++
 maca_crf_tagger/src/crf_model.hh              | 170 ++++++++
 maca_crf_tagger/src/crf_tagger                | Bin 0 -> 100190 bytes
 maca_crf_tagger/src/crf_tagger.cc             |  60 +++
 maca_crf_tagger/src/crf_template.hh           | 146 +++++++
 maca_crf_tagger/src/crf_utils.hh              |  75 ++++
 maca_crf_tagger/src/lemmatizer.cc             |  19 +
 maca_crf_tagger/src/lemmatizer.h              |  51 +++
 .../src/maca_crf_convert_binlexicon.cc        |  46 ++
 .../src/maca_crf_convert_binmodel.cc          |  23 +
 maca_crf_tagger/src/maca_crf_tagger_main.cc   | 259 +++++++++++
 maca_crf_tagger/src/maca_crf_tagger_utils.cc  |  31 ++
 maca_crf_tagger/src/simple_tagger.cc          |  21 +
 maca_crf_tagger/src/simple_tagger.hh          |  40 ++
 maca_crf_tagger/src/test_simple_tagger.cc     |  27 ++
 maca_crf_tagger/src/utf8                      | Bin 0 -> 8718 bytes
 maca_crf_tagger/src/utf8.c                    |  33 ++
 25 files changed, 2334 insertions(+)
 create mode 100644 maca_crf_tagger/CMakeLists.txt
 create mode 100644 maca_crf_tagger/src/apply_template_crfsuite.cc
 create mode 100644 maca_crf_tagger/src/crf_barebones_decoder.cc
 create mode 100644 maca_crf_tagger/src/crf_binlexicon.hh
 create mode 100644 maca_crf_tagger/src/crf_binmodel.hh
 create mode 100644 maca_crf_tagger/src/crf_decoder.hh
 create mode 100644 maca_crf_tagger/src/crf_features.hh
 create mode 100644 maca_crf_tagger/src/crf_lexicon.hh
 create mode 100644 maca_crf_tagger/src/crf_model.hh
 create mode 100755 maca_crf_tagger/src/crf_tagger
 create mode 100644 maca_crf_tagger/src/crf_tagger.cc
 create mode 100644 maca_crf_tagger/src/crf_template.hh
 create mode 100644 maca_crf_tagger/src/crf_utils.hh
 create mode 100644 maca_crf_tagger/src/lemmatizer.cc
 create mode 100644 maca_crf_tagger/src/lemmatizer.h
 create mode 100644 maca_crf_tagger/src/maca_crf_convert_binlexicon.cc
 create mode 100644 maca_crf_tagger/src/maca_crf_convert_binmodel.cc
 create mode 100644 maca_crf_tagger/src/maca_crf_tagger_main.cc
 create mode 100644 maca_crf_tagger/src/maca_crf_tagger_utils.cc
 create mode 100644 maca_crf_tagger/src/simple_tagger.cc
 create mode 100644 maca_crf_tagger/src/simple_tagger.hh
 create mode 100644 maca_crf_tagger/src/test_simple_tagger.cc
 create mode 100755 maca_crf_tagger/src/utf8
 create mode 100644 maca_crf_tagger/src/utf8.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 85d4e4b..a94a43c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,5 +8,6 @@ include_directories(maca_common/include)
 add_subdirectory(maca_common)
 add_subdirectory(maca_lemmatizer)
 add_subdirectory(maca_trans_parser)
+add_subdirectory(maca_crf_tagger)
 
 #set(CMAKE_INSTALL_PREFIX ../)
diff --git a/maca_crf_tagger/CMakeLists.txt b/maca_crf_tagger/CMakeLists.txt
new file mode 100644
index 0000000..6df7df6
--- /dev/null
+++ b/maca_crf_tagger/CMakeLists.txt
@@ -0,0 +1,24 @@
+include_directories(src)
+
+#compiling, linking and installing executables
+
+add_executable(crf_barebones_decoder ./src/crf_barebones_decoder.cc)
+target_compile_options(crf_barebones_decoder PRIVATE -std=c++11)
+install (TARGETS crf_barebones_decoder DESTINATION bin)
+
+#add_executable(test_simple_tagger ./src/test_simple_tagger.cc)
+#target_compile_options(test_simple_tagger PRIVATE -std=c++11)
+#install (TARGETS test_simple_tagger DESTINATION bin)
+
+add_executable(apply_template_crfsuite ./src/apply_template_crfsuite.cc)
+target_compile_options(apply_template_crfsuite PRIVATE -std=c++11)
+install (TARGETS apply_template_crfsuite DESTINATION bin)
+
+add_executable(maca_crf_convert_binmodel ./src/maca_crf_convert_binmodel.cc)
+target_compile_options(maca_crf_convert_binmodel PRIVATE -std=c++11)
+install (TARGETS maca_crf_convert_binmodel DESTINATION bin)
+
+add_executable(maca_crf_convert_binlexicon ./src/maca_crf_convert_binlexicon.cc)
+target_compile_options(maca_crf_convert_binlexicon PRIVATE -std=c++11)
+install (TARGETS maca_crf_convert_binlexicon DESTINATION bin)
+
diff --git a/maca_crf_tagger/src/apply_template_crfsuite.cc b/maca_crf_tagger/src/apply_template_crfsuite.cc
new file mode 100644
index 0000000..eba51ba
--- /dev/null
+++ b/maca_crf_tagger/src/apply_template_crfsuite.cc
@@ -0,0 +1,72 @@
+#include <string>
+#include <vector>
+#include <iostream>
+#include <fstream>
+#include "crf_template.hh"
+
+// http://www.oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html
+static void tokenize(const std::string& str, std::vector<std::string>& tokens, const std::string& delimiters = " ")
+{
+    std::string::size_type lastPos = str.find_first_not_of(delimiters, 0);
+    std::string::size_type pos     = str.find_first_of(delimiters, lastPos);
+    while (std::string::npos != pos || std::string::npos != lastPos)
+    {
+        tokens.push_back(str.substr(lastPos, pos - lastPos));
+        lastPos = str.find_first_not_of(delimiters, pos);
+        pos = str.find_first_of(delimiters, lastPos);
+    }
+}
+
+static void replace(std::string& str, const std::string &search, const std::string &replacement) {
+    std::string::size_type pos = 0;
+    while ((pos = str.find(search, pos)) != std::string::npos) {
+        str.replace(pos, search.size(), replacement);
+        pos += replacement.size();
+    }
+}
+
+int main(int argc, char** argv) {
+    if(argc != 2) {
+        std::cerr << "usage: cat <input> | " << argv[0] << " <template>\n";
+        return 1;
+    }
+    std::vector<macaon::CRFPPTemplate> templates;
+    std::ifstream templateFile(argv[1]);
+    while(!templateFile.eof()) {
+        std::string line;
+        std::getline(templateFile, line);
+        if(templateFile.eof()) break;
+        macaon::CRFPPTemplate current(line.c_str());
+        if(current.type != macaon::CRFPPTemplate::BIGRAM) templates.push_back(current);
+        //std::cerr << templates.back() << std::endl;
+    }
+    std::vector<std::vector<std::string> > lines;
+    while(!std::cin.eof()) {
+        std::string line;
+        std::getline(std::cin, line);
+        if(std::cin.eof()) break;
+        std::vector<std::string> tokens;
+        tokenize(line, tokens, " \t");
+        if(tokens.size() == 0) {
+            for(int position = 0; position < (int) lines.size(); position++) {
+                std::string label = lines[position][lines[position].size() - 1];
+                replace(label, "\\", "\\\\");
+                replace(label, ":", "\\:");
+                std::cout << label;
+                for(std::vector<macaon::CRFPPTemplate>::const_iterator i = templates.begin(); i != templates.end(); i++) {
+                    std::string feature = i->apply(lines, position);
+                    replace(feature, "\\", "\\\\");
+                    replace(feature, ":", "\\:");
+                    std::cout << "\t" << feature;
+                }
+                /*if(position == 0) std::cout << "\t__BOS__";
+                if(position == (int) lines.size() - 1) std::cout << "\t__EOS__";*/
+                std::cout << std::endl;
+            }
+            std::cout << std::endl;
+            lines.clear();
+        } else {
+            lines.push_back(tokens);
+        }
+    }
+}
diff --git a/maca_crf_tagger/src/crf_barebones_decoder.cc b/maca_crf_tagger/src/crf_barebones_decoder.cc
new file mode 100644
index 0000000..229868f
--- /dev/null
+++ b/maca_crf_tagger/src/crf_barebones_decoder.cc
@@ -0,0 +1,90 @@
+#include <vector>
+#include "crf_decoder.hh"
+#include "crf_binlexicon.hh"
+#include "crf_features.hh"
+
+/* This is a sample decoder for the crf tagger.
+   compile with: 
+   g++ -O3 -Wall -o barebones_decoder barebones_decoder.cc 
+   
+   example usage:
+   echo -e "I\nam\nyour\nfather\n\njhon\neats\npotatoes\n" | ./barebones_decoder en/bin/crf_tagger.model.bin en/bin/crf_tagger.wordtag.lexicon
+   */
+
+void tag_sentence(macaon::Decoder& decoder, macaon::BinaryLexicon* lexicon, const std::vector<std::vector<std::string> >& lines, int wordField, bool isConll07) {
+
+    std::vector<std::vector<std::string> > features;
+    for(size_t i = 0; i < lines.size(); i++) {
+        std::vector<std::string> word_features;
+        macaon::FeatureGenerator::get_pos_features(lines[i][wordField], word_features);
+        features.push_back(word_features);
+        //for(size_t j = 0; j < word_features.size(); j++) std::cout << word_features[j] << " ";
+        //std::cout << "\n";
+    }
+    std::vector<std::string> tagged;
+    decoder.decodeString(features, tagged, lexicon);
+    for(size_t i = 0; i < tagged.size(); i++) {
+        if(isConll07) {
+            for(size_t j = 0; j < lines[i].size(); j++) {
+                if(j != 0) std::cout << "\t";
+                if(j == 3 || j == 4) std::cout << tagged[i];
+                else std::cout << lines[i][j];
+            }
+            std::cout << "\n";
+        } else {
+            std::cout << lines[i][wordField] << "\t" << tagged[i] << "\n";
+        }
+    }
+    std::cout << "\n";
+}
+
+void usage(const char* argv0) {
+    std::cerr << "usage: " << argv0 << " [--conll07] <model> [lexicon]\n";
+    exit(1);
+}
+
+int main(int argc, char** argv) {
+    bool isConll07 = false; // warning: no verification of conll07 format
+    int word_offset = 0;
+    std::string modelName = "";
+    std::string lexiconName = "";
+
+    for(int i = 1; i < argc; i++) {
+        std::string arg = argv[i];
+        if(arg == "-h" || arg == "--help") {
+            usage(argv[0]);
+        } else if(arg == "--conll07") {
+            isConll07 = true;
+            word_offset = 1;
+        } else if(modelName == "") {
+            modelName = arg;
+        } else if(lexiconName =="") {
+            lexiconName = arg;
+        } else {
+            usage(argv[0]);
+        }
+    }
+    if(modelName == "") usage(argv[0]);
+
+    macaon::Decoder decoder(modelName);
+    macaon::BinaryLexicon *lexicon = NULL;
+    if(lexiconName != "") lexicon = new macaon::BinaryLexicon(lexiconName, decoder.getTagset());
+
+    std::string line;
+    std::vector<std::vector<std::string> > lines;
+    while(std::getline(std::cin, line)) {
+        if(line == "") {
+            tag_sentence(decoder, lexicon, lines, word_offset, isConll07);
+            lines.clear();
+        } else {
+            std::vector<std::string> tokens;
+            macaon::Tokenize(line, tokens, "\t");
+            lines.push_back(tokens);
+        }
+    }
+    if(!lines.empty()) {
+        tag_sentence(decoder, lexicon, lines, word_offset, isConll07);
+    }
+    if(lexicon) delete lexicon;
+    return 0;
+}
diff --git a/maca_crf_tagger/src/crf_binlexicon.hh b/maca_crf_tagger/src/crf_binlexicon.hh
new file mode 100644
index 0000000..8294860
--- /dev/null
+++ b/maca_crf_tagger/src/crf_binlexicon.hh
@@ -0,0 +1,365 @@
+#pragma once
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include "crf_model.hh"
+#include "crf_template.hh"
+#include "crf_lexicon.hh"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <limits.h>
+#ifdef CHAR_BIT
+#if CHAR_BIT != 8
+#error CHAR_BIT != 8 not supported
+#endif
+#endif
+
+namespace macaon {
+    const uint32_t lexiconMagic = 0xbffe1253;
+
+    class BinaryLexicon : public Lexicon {
+        // disable alignment in MSVC++
+#pragma pack(push, 1)
+        struct ModelInfo {
+            uint32_t magic;
+            uint32_t dataLocation;
+            uint32_t tableSize;
+            uint32_t numLabels;
+        } __attribute__((packed));
+
+        struct TableElement {
+            uint32_t hashValue;
+            uint8_t keySize;
+            uint8_t dataSize;
+            uint32_t location;
+        } __attribute__((packed)); // disable alignment in g++
+#define lexicon_tag_t uint8_t
+#define sizeof_LexiconTableElement (sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint32_t))
+
+#pragma pack(pop)
+
+        private:
+        bool isBinary;
+        int fd;
+        const char* data;
+        size_t dataLength;
+        const ModelInfo* info;
+        const TableElement* table;
+
+        // copied from https://smhasher.googlecode.com/svn-history/r136/trunk/MurmurHash3.cpp (MIT license)
+
+        static inline uint32_t rotl32 ( uint32_t x, int8_t r ) 
+        {
+            return (x << r) | (x >> (32 - r));
+        }
+
+        static inline uint64_t rotl64 ( uint64_t x, int8_t r )
+        {
+            return (x << r) | (x >> (64 - r));
+        }
+
+#define	ROTL32(x,y)	rotl32(x,y)
+#define ROTL64(x,y)	rotl64(x,y)
+
+#define BIG_CONSTANT(x) (x##LLU)
+#define	FORCE_INLINE inline
+
+        static FORCE_INLINE uint32_t getblock ( const uint32_t * p, int i )
+        {
+            return p[i];
+        }
+
+        static FORCE_INLINE uint64_t getblock ( const uint64_t * p, int i ) 
+        {
+            return p[i];
+        }
+
+        static FORCE_INLINE uint32_t fmix ( uint32_t h )
+        {
+            h ^= h >> 16;
+            h *= 0x85ebca6b;
+            h ^= h >> 13;
+            h *= 0xc2b2ae35;
+            h ^= h >> 16;
+
+            return h;
+        }
+
+        //----------
+
+        static FORCE_INLINE uint64_t fmix ( uint64_t k ) 
+        {
+            k ^= k >> 33;
+            k *= BIG_CONSTANT(0xff51afd7ed558ccd);
+            k ^= k >> 33;
+            k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53);
+            k ^= k >> 33;
+
+            return k;
+        }
+
+        static void MurmurHash3_x86_32 ( const void * key, int len, uint32_t seed, void * out ) 
+        {
+            const uint8_t * data = (const uint8_t*)key;
+            const int nblocks = len / 4;
+
+            uint32_t h1 = seed;
+
+            uint32_t c1 = 0xcc9e2d51;
+            uint32_t c2 = 0x1b873593;
+
+            //----------
+            // body
+
+            const uint32_t * blocks = (const uint32_t *)(data + nblocks*4);
+
+            for(int i = -nblocks; i; i++)
+            {
+                uint32_t k1 = getblock(blocks,i);
+
+                k1 *= c1;
+                k1 = ROTL32(k1,15);
+                k1 *= c2;
+
+                h1 ^= k1;
+                h1 = ROTL32(h1,13); 
+                h1 = h1*5+0xe6546b64;
+            }
+
+            //----------
+            // tail
+
+            const uint8_t * tail = (const uint8_t*)(data + nblocks*4);
+
+            uint32_t k1 = 0;
+
+            switch(len & 3)
+            {
+                case 3: k1 ^= tail[2] << 16;
+                case 2: k1 ^= tail[1] << 8;
+                case 1: k1 ^= tail[0];
+                        k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
+                        //std::cerr << k1 << " " << h1 << " " << seed << "\n";
+            };
+
+            //----------
+            // finalization
+
+            h1 ^= len;
+
+            h1 = fmix(h1);
+
+            *(uint32_t*)out = h1;
+        } 
+
+        static uint32_t Hash(const char *k, size_t length) {
+            uint32_t output = 0;
+            MurmurHash3_x86_32(k, length, BinaryModelConstants::magic, &output);
+
+            // java hash function
+            /*uint32_t output = 0;
+            for(size_t i = 0; i < length; i++) {
+                output = 31 * output + k[i];
+            }*/
+            return output;
+        }
+
+        public:
+        BinaryLexicon() : Lexicon(), isBinary(false) {}
+        BinaryLexicon(const std::string &filename, Symbols* _tagSymbols = NULL) : Lexicon(), isBinary(false), fd(-1), data((const char*) MAP_FAILED) { 
+            tagSymbols = _tagSymbols;
+            Load(filename);
+        }
+
+        ~BinaryLexicon() {
+            if(data != MAP_FAILED) munmap((void*) data, dataLength);
+            if(fd != -1) close(fd);
+        }
+
+        bool Convert(const std::string& from, const std::string& to) {
+            std::cerr << "loading\n";
+            Lexicon::Load(from);
+            std::cerr << "writing\n";
+            return Write(to);
+        }
+
+        bool Write(const std::string & filename) {
+            FILE* output = fopen(filename.c_str(), "w");
+            // magic
+            fwrite(&lexiconMagic, sizeof(lexiconMagic), 1, output); // magic
+
+            // features
+            uint32_t dataLocation = 0;
+            uint32_t dataLocationOffset = (uint32_t) ftell(output);
+            fwrite(&dataLocation, sizeof(dataLocation), 1, output);
+            uint32_t tableSize = (uint32_t) wordSymbols.NumSymbols() * 2;
+            fwrite(&tableSize, sizeof(tableSize), 1, output);
+            uint32_t numLabels = tagsForWord[kUnknownWordTags].size();
+            fwrite(&numLabels, sizeof(numLabels), 1, output);
+
+            // create table
+            TableElement* table = (TableElement*) malloc(sizeof(TableElement) * tableSize);
+            memset(table, 0, sizeof(TableElement) * tableSize);
+
+            // write entries
+            int num = 0;
+            int totalNumCollisions = 0;
+            int numTags = 0;
+            int sizeOfKeys = 0;
+            for(SymbolsIterator siter(wordSymbols); !siter.Done(); siter.Next()) {
+                std::string word = siter.Symbol();
+                if(tagsForWordEntry.find(siter.Value()) == tagsForWordEntry.end()) {
+                    continue;
+                } 
+                int64 id = tagsForWordEntry[siter.Value()];
+
+                num++;
+                TableElement element;
+                element.hashValue = Hash(word.c_str(), word.length()) % tableSize;
+                element.keySize = (uint8_t) word.length();
+                element.dataSize = tagsForWord[id].size();
+                numTags += element.dataSize;
+                element.location = (uint32_t) ftell(output);
+                fwrite(word.c_str(), element.keySize, 1, output);
+                sizeOfKeys += element.keySize;
+                for(size_t tag = 0; tag < tagsForWord[id].size(); tag++) {
+                    lexicon_tag_t packed = (lexicon_tag_t) tagsForWord[id][tag];
+                    fwrite(&packed, sizeof(packed), 1, output);
+                }
+                if(element.dataSize > 0) {
+                    uint32_t hash = element.hashValue % tableSize;
+                    int numCollisions = 0;
+                    while(table[hash].location != 0) {
+                        numCollisions++;
+                        hash = (hash + 1) % tableSize;
+                    }
+                    totalNumCollisions += numCollisions;
+                    table[hash] = element;
+                }
+            }
+            std::cerr << "avg collisions: " << 1.0 * totalNumCollisions / (double) wordSymbols.NumSymbols() << "\n";
+            std::cerr << "sizeof (keys) = " << sizeOfKeys << "\n";
+            std::cerr << "sizeof (tags) = " << sizeof(lexicon_tag_t) << " * " << numTags << "\n";
+            std::cerr << "sizeof (entry in table) = " << sizeof_LexiconTableElement << " * " << tableSize << "\n";
+
+            // write table
+            dataLocation = (uint32_t) ftell(output);
+            for(uint32_t i = 0; i < tableSize; i++) {
+                fwrite(&table[i], sizeof(table[i]), 1, output);
+            }
+            free(table);
+
+            // set feature locations
+            fseek(output, dataLocationOffset, SEEK_SET);
+            fwrite(&dataLocation, sizeof(dataLocation), 1, output);
+
+            fclose(output);
+            return true;
+        }
+
+        bool Load(const std::string& filename) {
+            isBinary = false;
+
+            struct stat sb;
+            fd = open(filename.c_str(), O_RDONLY);
+            if(fd == -1) {
+                std::cerr << "ERROR: could not open crf lexicon \"" << filename << "\"\n";
+                return false;
+            }
+            if (fstat(fd, &sb) == -1) {
+                std::cerr << "ERROR: could not fstat crf lexicon \"" << filename << "\"\n";
+                return false;
+            }
+            dataLength = sb.st_size;
+            data = (const char*) mmap(NULL, dataLength, PROT_READ, MAP_PRIVATE, fd, 0);
+            if(data == MAP_FAILED) {
+                perror("mmap");
+                std::cerr << "ERROR: could mmap() crf lexicon \"" << filename << "\"\n";
+                return false;
+            }
+
+            info = (const ModelInfo*) data;
+
+            // read magic
+            if(info->magic != lexiconMagic) {
+                bool result = Lexicon::Load(filename);
+                if(result == false) {
+                    std::cerr << "ERROR: invalid magic or unsupported version in binary crf lexicon. Please reconvert it from text model.\n";
+                }
+                return result;
+            }
+
+            // read table
+            table = (const TableElement*) &data[info->dataLocation];
+
+            loaded = true;
+            isBinary = true;
+            return true;
+        }
+
+        bool GetTagsForWord(int64 word, std::vector<int64>& output) const {
+            if(!isBinary) {
+                return Lexicon::GetTagsForWord(word, output);
+            }
+            std::cerr << "ERROR: GetTagsForWord() not supported on binary models\n";
+            abort();
+            return false;
+        }
+
+        int NumLabels() const {
+            if(!isBinary) return Lexicon::NumLabels();
+            return info->numLabels;
+        }
+
+        bool GetTagsForWord(const std::string& word, std::vector<int64>& output) const {
+            if(!isBinary) {
+                return Lexicon::GetTagsForWord(word, output);
+                //std::cerr << "ERROR: called GetTagsForWord() on a non binary model\n";
+                //return false;
+            }
+            if(word == "<eps>") {
+                output.clear();
+                output.push_back(0);
+            }
+            size_t keySize = word.length();
+            uint32_t hashValue = Hash(word.c_str(), keySize); // % info->tableSize;
+            uint32_t offset = 0;
+            while(offset < info->tableSize) {
+                uint32_t location = (hashValue + offset) % info->tableSize;
+                const TableElement& element = table[location];
+                /*std::cerr << word << " " << location << " " << hashValue << " " << offset << " " << info->tableSize << 
+                    "|" << element.hashValue << " " << (int) element.keySize << " " << (int) element.dataSize << " " << element.location << 
+                    "\n";*/
+                if(element.location == 0) break;
+                if(element.keySize == keySize) {
+                    char key[keySize + 1];
+                    strncpy(key, &data[element.location], keySize);
+                    key[keySize] = '\0';
+                    if(std::string(key) == word) {
+                        //std::cerr << "h:" << element.hashValue << " k:" << element.keySize << " d:" << element.dataSize << "\n";
+                        output.clear();
+                        const lexicon_tag_t* tags = (const lexicon_tag_t*) &data[element.location + keySize];
+                        for(int i = 0; i < element.dataSize; i++) {
+                            output.push_back(tags[i]);
+                        }
+                        return true;
+                    }
+                }
+                offset++;
+            }
+            // unknown word
+            output.clear();
+            for(int i = 1; i < (int) info->numLabels + 1; i++) output.push_back(i);
+            return false;
+        }
+
+    };
+}
diff --git a/maca_crf_tagger/src/crf_binmodel.hh b/maca_crf_tagger/src/crf_binmodel.hh
new file mode 100644
index 0000000..33deb53
--- /dev/null
+++ b/maca_crf_tagger/src/crf_binmodel.hh
@@ -0,0 +1,406 @@
+#pragma once
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include "crf_model.hh"
+#include "crf_template.hh"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <limits.h>
+#ifdef CHAR_BIT
+#if CHAR_BIT != 8
+#error CHAR_BIT != 8 not supported
+#endif
+#endif
+
+namespace macaon {
+// disable alignment in MSVC++
+#pragma pack(push, 1)
+    struct ModelInfo {
+        uint32_t magic;
+        uint32_t templateLocation;
+        uint32_t numTemplates;
+        uint32_t labelLocation;
+        uint32_t numLabels;
+        uint32_t featureLocation;
+        uint32_t tableSize;
+    } __attribute__((packed));
+
+    struct TableElement {
+        uint32_t hashValue;
+        uint16_t keySize;
+        uint16_t dataSize;
+        uint32_t location;
+    } __attribute__((packed)); // disable alignment in g++
+#define sizeof_TableElement (sizeof(uint32_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t))
+
+    struct LabelWeight {
+        uint16_t label;
+        float weight;
+    } __attribute__((packed));
+#define sizeof_LabelWeight (sizeof(uint16_t) + sizeof(float))
+
+    struct LabelPairWeight {
+        uint16_t previous;
+        uint16_t label;
+        float weight;
+    } __attribute__((packed));
+#define sizeof_LabelPairWeight (sizeof(uint16_t) + sizeof(uint16_t) + sizeof(float))
+#pragma pack(pop)
+
+    namespace BinaryModelConstants {
+        const uint32_t magic = 0x132a0ab5;
+    }
+
+    class BinaryModel : public CRFModel {
+        private:
+            bool isBinary;
+            int fd;
+            const char* data;
+            size_t dataLength;
+            const ModelInfo* info;
+            const TableElement* table;
+            std::vector<double> B_weights; // cache for B template
+
+            // java hash function
+            uint32_t Hash(const char *k, size_t length) {
+                uint32_t output = 0;
+                for(size_t i = 0; i < length; i++) {
+                    output = 31 * output + k[i];
+                }
+                return output;
+            }
+
+        public:
+            BinaryModel() : CRFModel(), isBinary(false) {}
+            BinaryModel(const std::string &filename) : CRFModel(), isBinary(false), fd(-1), data((const char*) MAP_FAILED) { 
+                Load(filename);
+            }
+
+            ~BinaryModel() {
+                if(data != MAP_FAILED) munmap((void*) data, dataLength);
+                if(fd != -1) close(fd);
+            }
+
+            bool Convert(const std::string& from, const std::string& to) {
+                std::cerr << "loading\n";
+                CRFModel::Load(from);
+                std::cerr << "writing\n";
+                return Write(to);
+            }
+
+            // trimming is already performed when writing the bin model
+            void TrimModel() {
+                std::unordered_map<std::string, int> newFeatures;
+                for(std::unordered_map<std::string, int>::const_iterator feature = features.begin(); feature != features.end(); feature++) {
+                    if(feature->first[0] != 'B') {
+                        int numNonNull = 0;
+                        for(size_t i = 0; i < labels.size(); i++) {
+                            float weight = weights[feature->second + i];
+                            if(weight != 0) numNonNull++;
+                        }
+                        if(numNonNull > 0) {
+                            newFeatures[feature->first] = feature->second;
+                        }
+                    } else {
+                        newFeatures[feature->first] = feature->second;
+                    }
+                }
+                std::cerr << "trim: " << features.size() << " -> " << newFeatures.size() << "\n";
+                features = newFeatures;
+            }
+
+            bool Write(const std::string & filename) {
+                FILE* output = fopen(filename.c_str(), "w");
+                // magic
+                fwrite(&BinaryModelConstants::magic, sizeof(BinaryModelConstants::magic), 1, output); // magic
+
+                // templates
+                uint32_t templateLocation = 0;
+                uint32_t templateLocationOffset = (uint32_t) ftell(output);
+                fwrite(&templateLocation, sizeof(templateLocation), 1, output);
+                uint32_t numTemplates = (uint32_t) templates.size();
+                fwrite(&numTemplates, sizeof(numTemplates), 1, output);
+
+                // labels
+                uint32_t labelLocation = 0;
+                uint32_t labelLocationOffset = (uint32_t) ftell(output);
+                fwrite(&labelLocation, sizeof(labelLocation), 1, output);
+                uint32_t numLabels = (uint32_t) labels.size();
+                fwrite(&numLabels, sizeof(numLabels), 1, output);
+
+                // features
+                uint32_t featureLocation = 0;
+                uint32_t featureLocationOffset = (uint32_t) ftell(output);
+                fwrite(&featureLocation, sizeof(featureLocation), 1, output);
+                uint32_t tableSize = (uint32_t) features.size() * 3;
+                fwrite(&tableSize, sizeof(tableSize), 1, output);
+
+                // create table
+                TableElement* table = (TableElement*) malloc(sizeof(TableElement) * tableSize);
+                memset(table, 0, sizeof(TableElement) * tableSize);
+
+                // write templates
+                templateLocation = (uint32_t) ftell(output);
+                for(std::vector<CRFPPTemplate>::const_iterator i = templates.begin(); i != templates.end(); i++) {
+                    fprintf(output, "%s\n", i->text.c_str());
+                }
+
+                // write labels
+                std::vector<std::string> labelVector(labels.size());
+                for(std::unordered_map<std::string, int>::const_iterator label = labels.begin(); label != labels.end(); label++) {
+                    labelVector[label->second] = label->first;
+                }
+                labelLocation = (uint32_t) ftell(output);
+                for(size_t i = 0; i < labelVector.size(); i++) {
+                    fprintf(output, "%s\n", labelVector[i].c_str());
+                }
+
+                // write weights
+                int num = 0;
+                int totalNumCollisions = 0;
+                int numUnigram = 0;
+                int numBigram = 0;
+                for(std::unordered_map<std::string, int>::const_iterator feature = features.begin(); feature != features.end(); feature++) {
+                    num++;
+                    TableElement element;
+                    element.hashValue = Hash(feature->first.c_str(), feature->first.length()) % tableSize;
+                    element.keySize = (uint16_t) feature->first.length();
+                    element.dataSize = 0;
+                    element.location = (uint32_t) ftell(output);
+                    fwrite(feature->first.c_str(), element.keySize, 1, output);
+                    if(feature->first[0] == 'B') {
+                        for(uint16_t label = 0; label < numLabels; label++) {
+                            for(uint16_t previous = 0; previous < numLabels; previous++) {
+                                float weight = weights[feature->second + label + numLabels * previous];
+                                if(weight != 0) {
+                                    LabelPairWeight item;
+                                    item.previous = previous;
+                                    item.label = label;
+                                    item.weight = weight;
+                                    fwrite(&item, sizeof(item), 1, output);
+                                    element.dataSize ++;
+                                }
+                            }
+                        }
+                        numBigram++;
+                    } else {
+                        for(uint16_t label = 0; label < numLabels; label++) {
+                            float weight = weights[feature->second + label];
+                            if(weight != 0) {
+                                LabelWeight item;
+                                item.label = label;
+                                item.weight = weight;
+                                fwrite(&item, sizeof(item), 1, output);
+                                element.dataSize ++;
+                            }
+                        }
+                        numUnigram++;
+                    }
+                    if(element.dataSize > 0) {
+                        uint32_t hash = element.hashValue % tableSize;
+                        int numCollisions = 0;
+                        while(table[hash].location != 0) {
+                            numCollisions++;
+                            hash = (hash + 1) % tableSize;
+                        }
+                        totalNumCollisions += numCollisions;
+                        //std::cout << element.hashValue << " " << feature->first << "\n";
+                        table[hash] = element;
+                    }
+                }
+                std::cerr << "avg collisions: " << 1.0 * totalNumCollisions / (double) features.size() << "\n";
+                std::cerr << "sizeof (label+weight) = " << sizeof_LabelWeight << " * " << numUnigram << "\n";
+                std::cerr << "sizeof (label+label+weight) = " << sizeof_LabelPairWeight << " * " << numBigram << "\n";
+                std::cerr << "sizeof (entry in table) = " << sizeof_TableElement << " * " << tableSize << "\n";
+
+                // write table
+                featureLocation = (uint32_t) ftell(output);
+                for(uint32_t i = 0; i < tableSize; i++) {
+                    fwrite(&table[i], sizeof(table[i]), 1, output);
+                }
+                free(table);
+
+                // set section locations
+                fseek(output, templateLocationOffset, SEEK_SET);
+                fwrite(&templateLocation, sizeof(templateLocation), 1, output);
+
+                // set label locations
+                fseek(output, labelLocationOffset, SEEK_SET);
+                fwrite(&labelLocation, sizeof(labelLocation), 1, output);
+
+                // set feature locations
+                fseek(output, featureLocationOffset, SEEK_SET);
+                fwrite(&featureLocation, sizeof(featureLocation), 1, output);
+
+                fclose(output);
+                return true;
+            }
+
+            bool Load(const std::string& filename) {
+                isBinary = false;
+
+                struct stat sb;
+                fd = open(filename.c_str(), O_RDONLY);
+                if(fd == -1) {
+                    std::cerr << "ERROR: could not open crf model \"" << filename << "\"\n";
+                    return false;
+                }
+                if (fstat(fd, &sb) == -1) {
+                    std::cerr << "ERROR: could not fstat crf model \"" << filename << "\"\n";
+                    return false;
+                }
+                dataLength = sb.st_size;
+                data = (const char*) mmap(NULL, dataLength, PROT_READ, MAP_PRIVATE, fd, 0);
+                if(data == MAP_FAILED) {
+                    perror("mmap");
+                    std::cerr << "ERROR: could mmap() crf model \"" << filename << "\"\n";
+                    return false;
+                }
+                name = filename;
+
+                info = (const ModelInfo*) data;
+
+                // read magic
+                if(info->magic != BinaryModelConstants::magic) {
+                    //std::cerr << "WARNING: binary crf model format not recognized, trying text model\n";
+                    return CRFModel::Load(filename);
+                }
+
+                size_t lineSize = 0;
+
+                // read templates
+                templates.clear();
+                const char* line = (const char*) &data[info->templateLocation];
+                for(size_t i = 0; i < info->numTemplates; i++) {
+                    lineSize = strchr(line, '\n') - line;
+                    char content[lineSize + 1];
+                    strncpy(content, line, lineSize);
+                    content[lineSize] = '\0';
+                    //std::cerr << "TEMPLATE[" << content << "]\n";
+                    templates.push_back(CRFPPTemplate(content));
+                    line += lineSize + 1;
+                }
+
+                // read labels
+                labels.clear();
+                reverseLabels.clear();
+                line = (const char*) &data[info->labelLocation];
+                for(uint32_t i = 0; i < info->numLabels; i++) {
+                    lineSize = strchr(line, '\n') - line;
+                    char content[lineSize + 1];
+                    strncpy(content, line, lineSize);
+                    content[lineSize] = '\0';
+                    //std::cerr << "LABEL[" << content << "]\n";
+                    labels[std::string(content)] = (int) i;
+                    reverseLabels.push_back(std::string(content));
+                    line += lineSize + 1;
+                }
+
+                // read table
+                table = (const TableElement*) &data[info->featureLocation];
+
+                ComputeWindowOffset();
+                loaded = true;
+                isBinary = true;
+                GetWeights("B", B_weights);
+                return true;
+            }
+
+            bool GetWeights(const std::string& feature, std::vector<double>& output) {
+                if(!isBinary) {
+                    std::cerr << "ERROR: called GetWeights() on a non binary model\n";
+                    return false;
+                }
+                size_t keySize = feature.length();
+                uint32_t hashValue = Hash(feature.c_str(), keySize) % info->tableSize;
+                uint32_t offset = 0;
+                size_t numLabels = labels.size();
+                while(offset < info->tableSize) {
+                    uint32_t location = (hashValue + offset) % info->tableSize;
+                    const TableElement& element = table[location];
+                    if(element.location == 0) return false;
+                    if(element.keySize == keySize) {
+                        char key[keySize + 1];
+                        strncpy(key, &data[element.location], keySize);
+                        key[keySize] = '\0';
+                        if(std::string(key) == feature) {
+                            //std::cerr << "h:" << element.hashValue << " k:" << element.keySize << " d:" << element.dataSize << "\n";
+                            if(feature[0] == 'B') {
+                                output.assign(numLabels * numLabels, 0);
+                                const LabelPairWeight* items = (const LabelPairWeight*) &data[element.location + keySize];
+                                for(int i = 0; i < element.dataSize; i++) {
+                                    output[items[i].label + numLabels * items[i].previous] = items[i].weight;
+                                }
+                            } else {
+                                output.assign(numLabels, 0);
+                                const LabelWeight* items = (const LabelWeight*) &data[element.location + keySize];
+                                for(int i = 0; i < element.dataSize; i++) {
+                                    output[items[i].label] = items[i].weight;
+                                }
+                            }
+                            return true;
+                        }
+                    }
+                    offset++;
+                }
+                return false;
+            }
+
+            /* note: this function can use bigram templates conditionned on observations */
+            double rescore(const std::vector<std::vector<std::string> > &input, const std::vector<int> &context, const std::vector<int> &context_tags) {
+                if(!isBinary) {
+                    return CRFModel::rescore(input, context, context_tags);
+                }
+                double output = 0;
+                if((int) context.size() != window_length) return 0;
+                if(context[window_offset] < 0) return 0;
+                const int label = context_tags[window_offset]; //ilabels[input[context[window_offset]][input[context[window_offset]].size() - 1]];
+                int previous = -1;
+                if(window_length > 1 && context[window_offset - 1] >=0) previous = context_tags[window_offset - 1]; 
+                for(std::vector<CRFPPTemplate>::const_iterator i = templates.begin(); i != templates.end(); i++) {
+                    std::string feature = i->applyToClique(input, context, window_offset);
+                    std::vector<double> feature_weights;
+                    if(GetWeights(feature, feature_weights)) {
+                        if(i->type == CRFPPTemplate::UNIGRAM) output += feature_weights[label];
+                        else if(previous != -1) output += feature_weights[label + labels.size() * previous];
+                    }
+                }
+                return output;
+            }
+
+            /* note: this function CANNOT use bigram templates conditionned on observations */
+            double transition(int previous, int label) {
+                if(!isBinary) return CRFModel::transition(previous, label);
+                return B_weights[label + info->numLabels * previous];
+            }
+
+            void emissions(const std::vector<std::vector<std::string> > &input, const std::vector<int> &context, std::vector<double>& output) {
+                if(!isBinary) {
+                    CRFModel::emissions(input, context, output);
+                    return;
+                }
+                output.assign(labels.size(), 0);
+                if((int) context.size() != window_length) return;
+                if(context[window_offset] == -1) return;
+                for(std::vector<CRFPPTemplate>::const_iterator i = templates.begin(); i != templates.end(); i++) {
+                    if(i->type == CRFPPTemplate::UNIGRAM) {
+                        std::string feature = i->applyToClique(input, context, window_offset);
+                        std::vector<double> feature_weights;
+                        if(GetWeights(feature, feature_weights)) {
+                            for(size_t label = 0; label < labels.size(); label++) 
+                                output[label] += feature_weights[label];
+                        }
+                    }
+                }
+            }
+    };
+}
diff --git a/maca_crf_tagger/src/crf_decoder.hh b/maca_crf_tagger/src/crf_decoder.hh
new file mode 100644
index 0000000..0c89aeb
--- /dev/null
+++ b/maca_crf_tagger/src/crf_decoder.hh
@@ -0,0 +1,147 @@
+#pragma once
+#include <list>
+#ifdef __APPLE__
+#include "../../../third_party/unordered_map/unordered_map.hpp"
+#else
+#include <unordered_map>
+#endif
+#include "crf_binmodel.hh"
+#include "crf_utils.hh"
+#include "crf_binlexicon.hh"
+
+namespace macaon {
+
+    struct Decoder {
+
+        //CRFModel model;
+        BinaryModel model;
+        Symbols tagSet;
+
+        Decoder() : tagSet("tagset") { }
+        Decoder(const std::string &filename) : tagSet("tagset") { 
+            model.Load(filename); 
+            tagSet.AddSymbol("<eps>", 0);
+            for(std::unordered_map<std::string, int>::const_iterator label = model.labels.begin(); label != model.labels.end(); label++) {
+                tagSet.AddSymbol(label->first, label->second + 1);
+            }
+        }
+
+        Symbols* getTagset() {
+            return &tagSet;
+        }
+
+        bool IsLoaded() const {
+            return model.IsLoaded();
+        }
+
+        /* Faster decoder for simple sequences.
+         * This function supports an optional lexicon to specify allowed word/tags. And attional option sets the location of the word in the feature vector.
+         * */
+        void decodeString(const std::vector<std::vector<std::string> > &features, std::vector<std::string> &predictions, const BinaryLexicon* lexicon=NULL, int wordFeatureLocation=0) {
+            int length = features.size();
+            int numLabels = model.labels.size();
+
+            /*if(lexicon->NumLabels() != numLabels) {
+                std::cerr << "ERROR: num label mismatch between model and lexicon\n";
+                return;
+            }*/
+
+            // store score and backtrack matrices (TODO: size matrices according to possible word/tag assoc)
+            std::vector<std::vector<double> > scores(length, std::vector<double>(numLabels, 0.0));
+            std::vector<std::vector<int> > backtrack(length, std::vector<int>(numLabels, 0));
+            /*double** scores = new double*[length];
+            int** backtrack = new int*[length];
+
+            for(int i = 0; i < length; i++) {
+				scores[i] = new double[numLabels];
+				backtrack[i] = new int[numLabels];
+                for(int j = 0; j < numLabels; j++) {
+                    backtrack[i][j] = -1;
+                    scores[i][j] = 0.0;
+                }
+            }*/
+
+            // possible tags for each word: use lexicon if provided
+            std::vector<std::vector<int64> > wordTags(length);
+            std::vector<int64> allTags(numLabels);
+            for(int label = 0; label < numLabels; label++) allTags[label] = label + 1; // warning: there is an offset of one for epsilon transitions
+
+            // perform viterbi search for the maximum scoring labeling
+            for(int current = 0; current < length; current++) {
+                // honor lexicon or allow all tags
+                if(lexicon != NULL) lexicon->GetTagsForWord(features[current][wordFeatureLocation], wordTags[current]);
+                else wordTags[current] = allTags;
+
+                // create context vector (offset of features for current word)
+                std::vector<int> context(model.window_length);
+                for(int i = 0; i < model.window_length; i++) 
+                    if(current + i - model.window_offset >= 0 && current + i - model.window_offset < length) context[i] = (current + i - model.window_offset);
+                    else context[i] = -1;
+
+                // compute emissions and find highest scoring transition pair
+                if(current == 0) {
+                    // TODO: compute emissions only for valid word/tags pairs
+                    std::vector<double> emissions;
+                    model.emissions(features, context, emissions);
+                    for(int e = 0; e < numLabels; e++) scores[current][e] = emissions[e];
+                } else {
+                    std::vector<double> emissions;
+                    model.emissions(features, context, emissions);
+                    for(int e = 0; e < numLabels; e++) scores[current][e] = emissions[e];
+                    for(size_t i = 0; i < wordTags[current].size(); i++) {
+                        int label = wordTags[current][i] - 1;
+                        if(label < 0 || label >= numLabels) {
+                            std::cerr << "ERROR: unexpected label (" << label << ") from lexicon, please check that it is compatible with model.\n";
+                            return;
+                        }
+                        double max = 0;
+                        int argmax = -1;
+                        for(size_t j = 0; j < wordTags[current - 1].size(); j++) {
+                            int previous = wordTags[current - 1][j] - 1;
+                            if(previous < 0 || previous >= numLabels) {
+                                std::cerr << "ERROR: unexpected label (" << previous << ") from lexicon, please check that it is compatible with model.\n";
+                                return;
+                            }
+                            double score = scores[current][label] + scores[current - 1][previous] + model.transition(previous, label);
+                            if(argmax == -1 || max < score) {
+                                max = score;
+                                argmax = previous;
+                            }
+                        }
+                        scores[current][label] = max;
+                        backtrack[current][label] = argmax;
+                    }
+                }
+            }
+            // find last label
+            double max = 0;
+            int argmax = -1;
+            if(length > 0) {
+                for(size_t i = 0; i < wordTags[length - 1].size(); i++) {
+                    int label = wordTags[length - 1][i] - 1;
+                    if(argmax == -1 || scores[length - 1][label] > max) {
+                        max = scores[length - 1][label];
+                        argmax = label;
+                    }
+                }
+            }
+
+            // backtrack solution
+            int current = length - 1;
+            predictions.clear();
+            predictions.resize(length);
+            while(current >= 0) {
+                predictions[current] = model.reverseLabels[argmax];
+                argmax = backtrack[current][argmax];
+                current --;
+            }
+
+			/*for(int i = 0; i < length; i++) {
+				delete scores[i];
+				delete backtrack[i];
+			}
+			delete scores;
+			delete backtrack;*/
+        }
+    };
+}
diff --git a/maca_crf_tagger/src/crf_features.hh b/maca_crf_tagger/src/crf_features.hh
new file mode 100644
index 0000000..917a395
--- /dev/null
+++ b/maca_crf_tagger/src/crf_features.hh
@@ -0,0 +1,100 @@
+#pragma once
+#include <vector>
+#include <string>
+
+namespace macaon {
+    class FeatureGenerator {
+        static void prefixesUtf8(const std::string &word, int n, std::vector<std::string> &output) {
+            size_t offset = 0;
+            while(offset < word.length() && n > 0) {
+                if((unsigned char)word[offset] >> 7 == 1) { // 1xxxxxxx (length of utf8 character)
+                    offset++;
+                    while(offset < word.length() && (unsigned char)word[offset] >> 6 == 2) { // 10xxxxxx (continuation of character)
+                        offset++;
+                    }
+                } else {
+                    offset++;
+                }
+
+                output.push_back(word.substr(0, offset));
+                n--;
+            }
+            while(n > 0) {
+                output.push_back("__nil__");
+                n--;
+            }
+        }
+
+        static void suffixesUtf8(const std::string &word, int n, std::vector<std::string> &output) {
+            std::vector<int> char_starts;
+            size_t offset = 0;
+            while(offset < word.length()) {
+                char_starts.push_back(offset);
+                if((unsigned char)word[offset] >> 7 == 1) { // 1xxxxxxx (length of utf8 character)
+                    offset++;
+                    while(offset < word.length() && (unsigned char)word[offset] >> 6 == 2) { // 10xxxxxx (continuation of character)
+                        offset++;
+                    }
+                } else {
+                    offset++;
+                }
+            }
+            for(int i = char_starts.size() - 1; i > 0 && n > 0; i--) {
+                //std::cerr << "s=[" << word.substr(offsets[i]) << "]\n";
+                output.push_back(word.substr(char_starts[i]));
+                n--;
+            }
+            while(n > 0) {
+                output.push_back("__nil__");
+                n--;
+            }
+        }
+
+
+        static void prefixes(const std::string &word, int n, std::vector<std::string> &output) {
+            int length = word.length();
+            for(int i = 1; i <= n; i++) {
+                if(length >= i) output.push_back(word.substr(0, i));
+                else output.push_back("__nil__");
+            }
+        }
+        static void suffixes(const std::string &word, int n, std::vector<std::string> &output) {
+            int length = word.length();
+            for(int i = 1; i <= n; i++) {
+                if(length >= i) output.push_back(word.substr(length - i, i));
+                else output.push_back("__nil__");
+            }
+        }
+        static void wordClasses(const std::string &word, std::vector<std::string> &output) {
+            bool containsNumber = false;
+            bool containsSymbol = false;
+            for(int i = 0; i < (int) word.length(); i++) {
+                if(!containsNumber && word.at(i) >= '0' && word.at(i) <= '9') containsNumber = true;
+                if(!containsSymbol && !((word.at(i) >= '0' && word.at(i) <= '9') || (word.at(i) >= 'a' && word.at(i) <= 'z') || (word.at(i) >= 'A' && word.at(i) <= 'Z'))) containsSymbol = true;
+            }
+            if(containsNumber) output.push_back("Y");
+            else output.push_back("N");
+            if(word.length() >= 2 && word.at(0) >= 'A' && word.at(0) <= 'Z' && word.at(1) >= 'a' && word.at(1) <= 'z') output.push_back("Y");
+            else output.push_back("N");
+            if(containsSymbol) output.push_back("Y");
+            else output.push_back("N");
+        }
+    public:
+        static void get_pos_features(const std::string &word, std::vector<std::string> &output, bool utf8=true) {
+            output.push_back(word);
+            wordClasses(word, output);
+            if(utf8) {
+                prefixesUtf8(word, 4, output);
+                suffixesUtf8(word, 4, output);
+            } else {
+                prefixes(word, 4, output);
+                suffixes(word, 4, output);
+            }
+        }
+        static std::vector<std::string> get_pos_features(const std::string &word, bool utf8=true) {
+            std::vector<std::string> output;
+            get_pos_features(word, output, utf8);
+            return output;
+        }
+    };
+}
diff --git a/maca_crf_tagger/src/crf_lexicon.hh b/maca_crf_tagger/src/crf_lexicon.hh
new file mode 100644
index 0000000..5182c1f
--- /dev/null
+++ b/maca_crf_tagger/src/crf_lexicon.hh
@@ -0,0 +1,128 @@
+#pragma once
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <stdint.h>
+#ifdef __APPLE__
+#include "../../../third_party/unordered_map/unordered_map.hpp"
+#else
+#include <unordered_map>
+#endif
+#include "crf_utils.hh"
+
+namespace macaon {
+    const int kEpsilonTags = 0;
+    const int kUnknownWordTags = 1;
+
+    class Lexicon {
+    protected:
+        bool loaded;
+
+        Symbols wordSymbols;
+        Symbols* tagSymbols;
+
+        std::vector<std::vector<int64> > tagsForWord;
+        std::unordered_map<int64, int> tagsForWordEntry;
+
+
+    public:
+        Lexicon() : loaded(false), wordSymbols("words"), tagSymbols(NULL) {
+            wordSymbols.AddSymbol("<eps>", 0);
+        }
+
+        Lexicon(const std::string& filename, Symbols* _tagSymbols) : loaded(false), wordSymbols("words"), tagSymbols(_tagSymbols) {
+            wordSymbols.AddSymbol("<eps>", 0);
+            Load(filename);
+        }
+
+        virtual ~Lexicon() {
+        }
+
+        bool NumLabels() const {
+            return tagSymbols->NumSymbols() - 1; // account for epsilon
+        }
+
+        bool Load(const std::string &filename) {
+            tagsForWord.push_back(std::vector<int64>()); // keep space for epsilon tags
+            tagsForWord.push_back(std::vector<int64>()); // keep space for unk word tags
+            loaded = false;
+            std::unordered_map<std::string, int> known;
+            std::ifstream input(filename.c_str());
+            if(!input.is_open()) {
+                std::cerr << "ERROR: could not open " << filename << " in Lexicon::Load()" << std::endl;
+                return false;
+            }
+            while(!input.eof()) {
+                std::string line;
+                std::getline(input, line);
+                if(input.eof()) break;
+                std::string word;
+                std::string::size_type end_of_word = line.find('\t');
+                if(end_of_word == std::string::npos) {
+                    return false;
+                }
+                word = line.substr(0, end_of_word);
+                int64 wordId = wordSymbols.AddSymbol(word);
+                std::string signature = line.substr(end_of_word + 1);
+                std::unordered_map<std::string, int>::const_iterator found = known.find(signature);
+                if(found == known.end()) {
+                    int id = tagsForWord.size();
+                    known[signature] = id;
+                    tagsForWordEntry[wordId] = id;
+                    std::vector<std::string> tokens;
+                    Tokenize(signature, tokens, "\t");
+                    std::vector<int64> tagset;
+                    for(std::vector<std::string>::const_iterator i = tokens.begin(); i != tokens.end(); i++) {
+                        int64 tagId = tagSymbols->Find(*i);
+                        if(tagId != -1) tagset.push_back(tagId);
+                    }
+                    tagsForWord.push_back(tagset);
+                } else {
+                    tagsForWordEntry[wordId] = found->second;
+                }
+            }
+            tagsForWord[kEpsilonTags].push_back(0); // epsilon
+            for(SymbolsIterator siter(*tagSymbols); !siter.Done(); siter.Next()) { // unknown word
+                if(siter.Value() != 0) tagsForWord[kUnknownWordTags].push_back(siter.Value());
+            }
+            loaded = true;
+            return loaded;
+        }
+
+        virtual bool GetTagsForWord(const std::string& word, std::vector<int64>& output) const {
+            return GetTagsForWord(wordSymbols.Find(word), output);
+        }
+
+        virtual bool GetTagsForWord(int64 word, std::vector<int64>& output) const {
+            if(!IsLoaded()) {
+                std::cerr << "ERROR: Lexicon::GetTagsForWord(" << wordSymbols.Find(word) << ") called on empty lexicon" << std::endl;
+                return false;
+            }
+            if(word == -1) {
+                output = tagsForWord[kUnknownWordTags];
+                return true;
+            }
+            if(word == 0) {
+                output = tagsForWord[kEpsilonTags];
+                return true;
+            }
+            std::unordered_map<int64, int>::const_iterator found = tagsForWordEntry.find(word);
+            if(found == tagsForWordEntry.end()) {
+                output = tagsForWord[kUnknownWordTags];
+            } else {
+                if(tagsForWord[found->second].size() == 0) {
+                    std::cerr << "WARNING: inconsistancy between word/tag lexicon and model, word no " << word << " has no tags => treat as unknown word\n";
+                    output = tagsForWord[kUnknownWordTags];
+                }
+                output = tagsForWord[found->second];
+            }
+            return true;
+        }
+
+        bool IsLoaded() const {
+            return loaded;
+        }
+
+    };
+}
diff --git a/maca_crf_tagger/src/crf_model.hh b/maca_crf_tagger/src/crf_model.hh
new file mode 100644
index 0000000..6547460
--- /dev/null
+++ b/maca_crf_tagger/src/crf_model.hh
@@ -0,0 +1,170 @@
+#pragma once
+#include <string>
+#include <vector>
+#ifdef __APPLE__
+#include "../../../third_party/unordered_map/unordered_map.hpp"
+#else
+#include <unordered_map>
+#endif
+#include <stdio.h>
+#include <errno.h>
+#include "crf_template.hh"
+
+namespace macaon {
+    class CRFModel {
+    protected:
+        std::string name;
+        std::vector<CRFPPTemplate> templates;
+        int version;
+        double cost_factor;
+        int maxid;
+        int xsize;
+        std::unordered_map<std::string, int> features;
+        std::vector<float> weights;
+        bool loaded;
+        int bigramWeightLocation;
+    public:
+        std::unordered_map<std::string, int> labels;
+        std::vector<std::string> reverseLabels;
+        int window_offset;
+        int window_length;
+        CRFModel() : loaded(false) {}
+        CRFModel(const std::string &filename) : loaded(false) { Load(filename); }
+
+        bool Load(const std::string &filename) {
+            name = filename;
+            FILE* fp = fopen(filename.c_str(), "r");
+            if(!fp) {
+                fprintf(stderr, "ERROR: %s, %s\n", filename.c_str(), strerror(errno));
+                return false;
+            }
+            char line[1024];
+            int section = 0;
+            int header_num = 0;
+            int line_num = 0;
+            int num_non_null = 0;
+            while(NULL != fgets(line, 1024, fp)) {
+                line_num ++;
+                if(line[0] == '\n') {
+                    section ++;
+                } else {
+                    line[1023] = '\0';
+                    line[strlen(line) - 1] = '\0'; // chomp
+                    if(section == 0) { // header
+                        char* space = line;
+                        while(*space != ' ' && *space != '\0') space ++;
+                        if(header_num == 0) version = strtol(space + 1, NULL, 10);
+                        else if(header_num == 1) cost_factor = strtod(space + 1, NULL);
+                        else if(header_num == 2) maxid = strtol(space + 1, NULL, 10);
+                        else if(header_num == 3) xsize = strtol(space + 1, NULL, 10);
+                        else {
+                            fprintf(stderr, "ERROR: unexpected header line %d in %s\n", line_num, filename.c_str());
+                            fclose(fp);
+                            return false;
+                        }
+                        header_num ++;
+                    } else if (section == 1) { // labels
+                        int next_id = labels.size();
+                        labels[std::string(line)] = next_id;
+                        reverseLabels.push_back(std::string(line));
+                    } else if (section == 2) { // templates
+                        templates.push_back(CRFPPTemplate(line));
+                    } else if (section == 3) { // feature indexes
+                        char* space = line;
+                        while(*space != ' ' && *space != '\0') space ++;
+                        *space = '\0';
+                        int index = strtol(line, NULL, 10);
+                        features[std::string(space + 1)] = index;
+                    } else if (section == 4) { // weights
+                        float weight = (float) strtod(line, NULL);
+                        if(weight != 0) num_non_null++;
+                        weights.push_back(weight);
+                    } else {
+                        fprintf(stderr, "ERROR: too many sections in %s\n", filename.c_str());
+                        fclose(fp);
+                        return false;
+                    }
+                }
+            }
+            //std::cerr << "weights: " << num_non_null << "/" << weights.size() << "\n";
+            fclose(fp);
+
+            ComputeWindowOffset();
+
+            std::unordered_map<std::string, int>::const_iterator found = features.find("B");
+            if(found != features.end()) {
+                bigramWeightLocation = found->second;
+            }
+            loaded = true;
+            return true;
+        }
+
+        void ComputeWindowOffset() {
+            int max_template_offset = 0;
+            int min_template_offset = 9;
+            for(std::vector<CRFPPTemplate>::const_iterator i = templates.begin(); i != templates.end(); i++) {
+                if(i->type == CRFPPTemplate::BIGRAM && min_template_offset > -1) min_template_offset = -1; // account for label bigram 
+                for(std::vector<TemplateItem>::const_iterator j = i->items.begin(); j != i->items.end(); j++) {
+                    if(j->line < min_template_offset) min_template_offset = j->line;
+                    if(j->line > max_template_offset) max_template_offset = j->line;
+                }
+            }
+            window_offset = - min_template_offset;
+            window_length = max_template_offset - min_template_offset + 1;
+        }
+
+        bool IsLoaded() const {
+            return loaded;
+        }
+
+        /* note: this function can use bigram templates conditionned on observations */
+        virtual double rescore(const std::vector<std::vector<std::string> > &input, const std::vector<int> &context, const std::vector<int> &context_tags) {
+            double output = 0;
+            if((int) context.size() != window_length) return 0;
+            //std::cerr << context[window_offset] << std::endl;
+            if(context[window_offset] < 0) return 0;
+            const int label = context_tags[window_offset]; //ilabels[input[context[window_offset]][input[context[window_offset]].size() - 1]];
+            int previous = -1;
+            if(window_length > 1 && context[window_offset - 1] >=0) previous = context_tags[window_offset - 1]; //labels[input[context[window_offset - 1]][input[context[window_offset - 1]].size() - 1]];
+            for(std::vector<CRFPPTemplate>::const_iterator i = templates.begin(); i != templates.end(); i++) {
+                std::string feature = i->applyToClique(input, context, window_offset);
+                //std::cerr << "feature: " << feature << std::endl;
+                std::unordered_map<std::string, int>::const_iterator found = features.find(feature);
+                if(found != features.end()) {
+                    if(found->second >= 0 && found->second < (int) weights.size()) {
+                        if(i->type == CRFPPTemplate::UNIGRAM) output += weights[found->second + label];
+                        else if(previous != -1) output += weights[found->second + label + labels.size() * previous];
+                    }
+                }
+            }
+            return output;
+        }
+
+        /* note: this function CANNOT use bigram templates conditionned on observations */
+        virtual double transition(int previous, int label) {
+            if(bigramWeightLocation < 0) return 0;
+            return weights[bigramWeightLocation + label + labels.size() * previous];
+        }
+
+        virtual void emissions(const std::vector<std::vector<std::string> > &input, const std::vector<int> &context, std::vector<double>& output) {
+            output.clear();
+            output.resize(labels.size());
+            if((int) context.size() != window_length) return;
+            if(context[window_offset] == -1) return;
+            for(std::vector<CRFPPTemplate>::const_iterator i = templates.begin(); i != templates.end(); i++) {
+                std::string feature = i->applyToClique(input, context, window_offset);
+                //std::cerr << " " << feature;
+                std::unordered_map<std::string, int>::const_iterator found = features.find(feature);
+                if(found != features.end()) {
+                    if(found->second >= 0 && found->second < (int) weights.size()) {
+                        if(i->type == CRFPPTemplate::UNIGRAM) 
+                            for(size_t label = 0; label < labels.size(); label++) 
+                                output[label] += weights[found->second + label];
+                    }
+                }
+                //else std::cerr << "*";
+            }
+            //std::cerr << "\n";
+        }
+    };
+}
diff --git a/maca_crf_tagger/src/crf_tagger b/maca_crf_tagger/src/crf_tagger
new file mode 100755
index 0000000000000000000000000000000000000000..48867b39b0ef4bffed7927ba5b51a0cbe81cb1e2
GIT binary patch
literal 100190
zcmb<-^>JfjWMqH=CI&kO5KltW0W1U|85k}&gG9kX3=Rwy44e!O4Dt*z3~USx46F<c
z3@~*LP!^2-0o4YhIUq(bFf%YPurM$%STI2ZESMlBz-SqWFawN+8UuD4$Udkvip?M~
zeG`aE7|p<-01^i41G)8vEkyo?Z6a74My~-mhJk@$2NzhF0j3Y+JdnO0P<=n3`d~Bz
zNC8L-l!o~aWE=>GK*d9#{)5pjAUzBW3@{p`79<q#v?K+@o*)Tv=LAVdkT3&`W`UXq
zr@^ue41rHeQb6v6atTrbP!&N?`*4Me2Gl+n4RI9%gMLnCl9`EqPKs_$W?pH9ZiR)J
zu9=BmalW1rI9@^KgVehFg@SDY83S@3C^kU;6JcNgrvZ?Bf=`Nt`TO3yr;1Pa3+(ap
z3sC0x4oV*Z3=H7lWoKYuXjmeUW5CGE#F5Ctz#zcG2$BPt9Ld1IP&AF5NhO!ZdBfY3
zkQ0qum*yt#JzBJf&C2KanF~FU4j(N)b<1eDUiIPo`R$^}3?AosG6g#-J+rrg9fpIb
zU}azs!Xn4Zj$ND&hx!XR#2q=Xn^Vb%T|5zodOjTDO3c{JdBcER928%mxIy=_JUl0$
z%FM<Q4u9FOo9~0eUp6@0e;S87b(yf6uZ_b!KXHi9!(s0S9PT&4q5d8Y_dLT9{^~f)
zF~$+j4{)R(VHWJ+po_zNe;nr5;Rt5~9P!11!`{<4!eIvv@u@i6DS;#WrE#d=jKiJ1
zIMmnS5HH0co`l2w9yruz<8V(64u3V^NT0?y!Z{O1Joe)-A5`&SE5{;nxIYhvxG9eG
zoPa|;8xHZ)IQ->~!~JV<h>PP0=eao2b2bip|KkY%RvhlXg(E*&<47kDaG1Xwhx&;)
z{Iw8AI3(e4PZtjHJRI)%g+p8kM>wp;ksd^Fs4v8!{w^~EgCK(x!vZHrdrSh<XkcJq
z*vkeHf8Y!e7l4X8vokO#GDtBnxI)Ampw*Hq3q<?@R6GJIew+;=p5P8q4-;PmHRk}-
zd|2IA3pM8eR6R_5B@4to2_6u0VB+Z<5b*}6IIR8W1&!YZ1xULV>Kq0}CWw0)pzZ{<
zV?c5<Ss~^)xIxT^`FAlZMEwJ(dtm<k2~E!jlu*rQ$jMA9E=ft&)&{l3%oyV1({uCl
z;)_cXi%R0-8REUl;)7DtGmA@7i=1;3i;Gi>8R9)deB)D6i=e_GzRo%Md8r|ZNja$y
zIqWj=1tl3psfj7^*{PNB$wjG&C8-SYQGUV2Ch<Y31%?LkAVZ5wit;O6gS>+iJ(FF*
z(j|t*Nr}ao$?<8KIjKpdX`ac!C5DE{8Hq*lB}IvuCB>e}uC6YIu4N$A-oYiN$t9Hq
zsh-KMhQ{%}@#UE*sd@34d6^{;1;NG6hOPnL$+-wCK*q&;Bo=4HCsmfD76*8j<>oTP
zM+KJ{n#aeNWEAC>$0sGG#3$zD<R?QdDK<1t%gjrOPs=PSE{V@i1DgoagzB5j;)49*
zR21Ljo2KQI7H5DR6I^0whHOJ}VsQ!7f)Ycc_;`f3J(FF{GmGQ%3sUotbmkYA6s0ES
z#%Jagrxul94VR!`c<@10dxpe^7{t2<o5crs2OG!dFvLfNgawxvnm}yJOoJGT)ij3q
z_~eSj_@Y#BsDQ#79GpSk!NuSp^9wGq$jmPWIXu<GGcU6Q7A?UgmQdsKi?Nz)0*;}8
z;1WYqh(d@fAkl3Y4^0VXIr+(nIjL|{p>c_bN<Z)5V)Nwu+=9fSR8X9Nod^#Bd<n<|
z9JT@8pu}Yy@0nj5?_ZFbmz$pgNuvmVIU9jvz>lDpOp|j`6N_AfK~V`Z&ojTc1e)Z%
zgG(&p<5N;g5;Jp*4B`WdGILYoi&8T{X|NzaCo{Ry&?MeBJ})&j1uE>C3kzUka)W7d
z4k+D$(o%4VadKuJI5`^`z!F=2DJap#7bWJUrv`W@gF?wWxYz`gq+OH2$=Ex%*w7rr
zhbHm7e2@-MkV4&2Y?fGDoSB{n76PXsKSKjh@~B8m$}BSkWu0VDaS;#7jPaRyY5A_u
z)RUB&o|zY)oLG{a0n!&jblQU#6-EZ}!SSiN1tpd7MX3ex#U=SgiRr1(G(t@HID<=z
z`1Fd3_=41;;{3eCoXnES_%Z`<IGLxXmgHpSrD98{;9|)YDOY7e5-KcLna2kOCwT^&
z#0Q(khZx4Y2Ajl#LN~bB&?w$FJ|{IXJ3b?^C<U5?ip>%W3R3e@V1WoKJ&MZ{3tWSO
zi@{DYNzN}V0fiDMhhoVYm<1u&w)~u8*Gz`^sJwDeWCoYy6nlaSIkYkp$rrG)Avq-8
zHP|#BnkI;h8CZ5CB2^oLQ*}j2d{TCaE68ogC58(uXTj4Js9=G3$he@i#5Eb3f>QHR
z;OQs0#3VVjs0bWJC@Bz>+KV&eF%uyu895t*3T03j`vn)97o`@b7L}!f9TZ#w(g-f!
z;&W2-(n~VpQ;Ul7i{L?mTEIb45~KtrqM8DG1|I8h?}3U4P@xH}<UEsM^;t?z04NiK
z3TOfaD5x6Gh1B3VnMvu%$??S?o*_egXkIy}stQUiF3n8^DN6=Pm@%Y*N<4-Fu<sa(
zON#Q63o02Blk$s7K)jOt6bO~Wkei!Wz>t=nT2c%W$w|$FMnhsrYDH!VLvCtracT)e
zJSd#<^5a28bV+7@9!NYnw}2rHToy4Dm!yEADYrBaWLkWDS_P;y1R0o|QN)l|P?VWh
zk_OU~n_rd+5{9@dK0YTiDH+-t$xY16V{mtNjyKjbU~u>GbaIY2(lgRCg|H(c;*Ir8
z^b9Q+oFgI}ojl_W^^DQvERm#8H5npv!J|k_3?RtNz{J4J03t!-QJ~Q!h$ssKD_jO5
z3mSid$iY-Wcp&|3U^77KKo}$rGL4CW9V`w~0TSl`o5{q$3KeBy-~_t?q8_G-nSlc;
z$_iBvl7XpVWng4rWVpo6zyKSc2~ex#W?<lC*Z^&4g5=*L$(KrHa)L(IcEIJ?KwUtv
zeo#9z<HvL+1_lm>BODMjOE0eHW?*1vxD4fY)@w2`Ft9N^fbuV|&t?UU@V<lc6T$jf
z7=A+eC7P2sKq17z2{N65;fH-9m;oB=;|8-J1gw1z8oPzHc|kI;_BTizG@b_%17TRZ
zA0!?K5`bZ7yBQ=N1QP>|4<d<2A&JBK10eOmNaC<|Hb^`gNgUQs0EveniNpE>An`CH
z@dwa$IY>MPNgUQs0EveqiNpE{AaPh<1SBot00}pC1_p3^fy6*q0ZAN`%0OZutbrsB
zN~<6-5H>&(2aU6W#6Z{rNgQN1NDPD>ki<c81`-2d4<vDrn?Pb99DpPaO=2Knh6p5a
zXi@-+Cm@N#ht?Sw7&4H=k<&>5k~lBe1PD=qB+dsB0+S6$;{0F{2+@Hg4$3<aQ3i$y
zNaBJpK?Vkf8A#&D<0T7_#D$U6uRsz9jb+26HXw<M!URF{3P|E&Na6>O#Kn=sPaug)
zAc<c<5|>00zkwt!g(Us}NgOm*4wHI;Bo3Qn0m*$p5|;%DK=BVGaXF|Mm~up<TUehM
zB+h{(t_Tu<VgV#^C8!vPl0Xty1_?m10+P52R18FEAc?Dj1fbXeNn8yo2BIvG#MMCp
zQ0#ytt^pMTQ65O*njirv4nPvuf{KBt2qbZBkN^}XAc^Zh#XwXBlDIBN0E!Ec#Py(J
zAgTgMTpuI=#SKW}22e2&)qx~#2oiwe2}t6gF=wbS!we*G6PO?a1H%F&aZ@Dm6-eS{
zNa7oi#Lbb!cOZ#dAc-G961PMWKY=7}g(Q9fN!%Jq{05S^4U+f+Byn3L@fS$qc1YqM
zki_kg#D5@(ql~yf$E`qRWGs?;4kU5N3?4*M07={lA_OKSki?zAA`n6WNgOs;2$Io2
z5_bg&K(PUmxEoXqL|Gt-yMqLv*a1o011bihJdngaK>|=5fF$k(6$4QbNaEfg0VqyD
z68C|Mfv5~5abJ)C6c-?g`$5G(R0WbaXf6XJ2ErcAZ#X=<S#=c^89Z7Kl(4?o&A`Cm
z(R_sCFi4hx;lHVpq9Vh8RTV`=27Y-5hX1M{eg;VL<%9qK|NmE&QdDHf0F5rbya474
zf%u@R_T>REp9{nXRk1HOfcZ=yKB#Jaxd6=nrJ%@=0jg48P5|>if%u@R^koB>{|dwh
zC6AW{VE!WzA5?X|OaSw5f%u>*^JM^-e+k3~Rh2It!2DAnKB$U(X#nOQ0`Ykm7#LnE
zfcd*Xd{9;SQUJ`~1mc6L$d?RY{wfe3R5iZ*@DJqQMIb(?N__bM%%26~gQ~)p7r^{U
zAU>!He0c!O?*j2bRo}}EV15&b532HBE&%hZKzvYD_i_T5Uj*WVs<@X8V15>e531T;
z7J&IlAU>!{dzk>{M}hdDs_bO|m>&e<gQ~EX4q(0)h%XKDuK}3v1mc6Ltd|O4z7>cM
zs;XWJfcZusKB$U%$pGeSf%u?l%$Fbjg8Z)p;)ANHmk+>vDG(o2MZLTL<_m%NpsMNR
z0WhBn#0OPLFE@bsOdvj}Dtfs9%>N~?$dCc5f?iGl^FM+3psMF(1DO8`#0OP5FAKo@
zM<70^s(G0J=HCMGK~>Dl05Ja&h!3h-UOIsJr$BsAmGaU6%s&L;gQ}933Sj;&5Fb>9
zyc7WQH-Y$|s^cXCn7<0d2UQs_Kl}mte-Vfesw!SS0P|;o_@FA{<pnT*5{PdE3hx78
zeiw)jsw!S?0P~wbd{7nfasilM1>%FMhL;n-{2~w^R3*G@0Q0jzd{9;JvH;9a0`Wmr
zz{><MKMKSL75y&*!2BQ(A5`SObO7_cKzvY9|Iz@=cLMQ2Mf^(zFy9Kq2Nmrv1;BhG
z5Fb>ezhnUOwLpAOQU3D7Z;=0$KzvXU{_+8sF9qU*is+XYz<ePPA5=8IJOJi%f%u>z
z`Q-*Mp9#bV6~!+Xfcd}V6d5u=MexfBVE!i%A5`?dYyk6Lf%u>z_hkW?{|Lkf6}2xD
z!2DYvKB$O&835*A0`Wmb>q`eP{}hN1DpFq>fcb|&d{9yPQsMXi{{bG|>>kV&FS^SW
zn$I!5@csAyf7IbLJ%0HXP!Y#4!7<D+)U)&JzB&;{2FH-l&cDGPjc-7!Dm;2aR0=$L
zYg7bY{P^?#Kb)%oR{G+}pa1`x|1$FTsj)CHG{0r??2YB{=}l1y@a)ZbE8w^fWICuk
z_2@PR8B*ZW`RD~GeSkUp>O~wGUQGP+|9|U&QeMyQT80-(SQ)_OFGRixBwq-YFL?kG
z5`fqm2NvRQ?Pg?P@aQdpIney?|NkD{E+B5j3y;SCpw1P4>uqLGNq?Bb!}2wMYsr6*
zA8J&f28lupYPkba(_5k<0J5auh1@Zaxiu<KPrm>C|G%r@+tvg8t(QPH9(PeOsPIT+
z;Fo9c=w@w{Rb=q!c6i~@J!67%B7;ZkZT=R}vWkXk3kLocVMYc9{%y|AAZHrz@4K)c
z)WdzT_b14u0v_E#FTS}5D6sOkvVjcl4N>6(xxiuHWMM}JkK-;X;65;j0qz8X7zv;t
zeUbAU<c|;)2as|DpU!75R<STJfIRQu(Tn7HAts1VKmqU5`R>ILHjt_ul?adS7?l7I
z%e(w7-$C6Dkf9!}2l!ifnLrW1;L*+P(OZcKL}QTinn8hhxAd_`ca2JbN9%w7mcRf0
z|9{c&^Z);spZ|dz1P`)3zd(`7-x>kZ2a6$v7gK-#|Nk-)q_p$?iw<xgFz*9ZVji9M
z{|6Yny!P+^f8W-(C0w4}t_(h%@BasIfcymVAjn%UOhH<D_oz%@WMFt<VF~ryi+{!-
zU%99#_;fx4tK$W!>vmBQfC>Eh@&CU^;}KAbHarj=>lou0>lk;~qx1bslmGw!@BhHS
z!0_S+C`>z#dv+du(R&JHbPL!XP|PWK9(QF3kaT2lJl1*w6jqMMThA~sfNM^l&i9_j
zTQ5L)o}I20FZ5n9Fuc6__y7Oq6aPQ(A9VcDdVs%wFUZ@yB`OXt@<9Q%4#e#|4Dwz9
z%#~pvS9T-$!xbt3_lF5cpz}UB{=pfhn27-tP#&F!U#yS-Ghcl84vGqBAPHN5d{U!Q
z@ZzQs1H<cu9-W6@-`WZCf=6$-fJf`e5*vsknLuv52+BddB`W+cPXGA-|K%YNx0@Lh
zq|oeCD(wMH1<zl8{rmracfgNcum7DMKRPeG-Us2dUh4GtQGWvzYz{A4e*FLc`iw{C
zVQ`@UZkj+?<$wPFfAQ?w|NkD4Fn~vK(D(oUU)qAQY3E^|&gU;a*)TAqO?Z79s?Gsq
zE<eacFEqaY|Nr`qZ|gUZPfApgyuthD|9?=PU;}vqRJ?<O%7c-C0pdi6d#`$Q9)5Wl
z5)Cg~|NZ};hA0q0?Zy2)9*GPun7;r2zsp0|kzpT*yB{=B3u-sM)cXGaf7%4Yx1OCB
z<2*Vac{D%y5D?;d{D1*C=N>#D1j@k2A1FI8ytx1E|Nk#(6A-4SP1w=zl*kb3q1)k<
z$l%fZgU_S$X*pBu;T_<JX+2Q#C3ZhZ>i+|NdGJ*34iFEf{@@R0kIttaorgRQzOwgV
zyx?*1M=4Y6;aw2TmrB0GdUPIx=zj#&-(Y)CPLYA3R4~+|^XZGa|NsBT9%Ef6r^vwg
z`7pSAZnotRQDk5!k%K!yBy|4=+e8MB&Zqw`#J;Tl|NlR~Jj2Uk2pzl&)KvB8{JpPN
z*pXpBXc+E=1Ss8g-h*(Wzy1HeZz?D(I*-5b`}Y6;eo))_Max%^F%Arl;f`URo!1;g
z9YZ`jzl3^#DxBsw79QP3hL=3L{a(D#{`UXBXSW&1lb!Fuaqsuyg&SCqN5!){M1{k%
z^QedA#S$a1gFQRXgNv_zP;o2oz~I@<4oV}?RLkEAS|;h)e4N9_@>l6&kLKV1Jv)6=
zI7)M0Zf1a#cdaKuIZ>fR(4&(PRLm-X3iur$!lU^h2Us<VfwLGG7(Bbpd^%rwbiN0>
zxbiF1#fPDe26<!3*Z=<^)*UVJ_5c@ZFFiDGdRRxiC=URY-3lI^w>&!Ez3Bc9((R+d
z;c4xoBEa984axz%C7>eh`HQ~E(DLnN1vvN}7(AQ*{x4nc(R{=LR+u@)AKw38*pcDI
z#xMW>qh#>l9iRsO2mXWLEbb09SpLO}FaQ6)wD|x3|30XE!H?E&{QXx!IRa#}M`wr%
zhes!m%8PwpK-Cq9*Uh8ya?3x|e7_PLJuWH=psXnm3Z)mlpFtV>{)?mF2t$sFQa)HT
zJbzgNGN|B(ORwAi)^9GI4nOK2gN#r>SnP^y@#~|A9OM}8(Rl&XXmJb)1vOPbdE2Ag
zfdiyfz@uBpqq9au!K1T8MZ@EGi^>B~&H#1zj<=}1fUz|%fTTbrKS;y_l&L@!U>rZF
zz=Y`W>D{9O8fFD02vApy!K2efMFN~(x?NN_d^-Pwi~y+s8*#ivg#(nzp*F$RW_a}K
z90El#M;j;>YZ*Mc%U`s9bL{-U-`CB>z+ia5@V4Q}*M9u+E({DFmdE(p!g)b$1`+mN
zuL})7Ir*m@Y`I;k=F;nR!SJ?Y!%xmKDVJWa`Hl@gCCdbwUpY4XlH{L$7@E&Lnh!8K
zS{|sAZ#h}Ax#f20dys+h4h;Nlr+6TSzTO6Mf6Gb!76~qpPeW8}JU}%n0|Uc8A5eQs
z5EOUa5U+Sxx2S-;!Qb+On*r2w_UN9XGJ%1Cp+v-^Qvl?75C@b?c7X_w<^voaolvd4
zRfy(;KOe~Jt_&W{6)zb1+y1eD++xrhwjbp9_V-|}17q`xL>o!|_6MMrOmB#a6)1lc
zKpa)`36uh%Cc_=&19Mb}N&zV63V<T20OS(<&T;2uU}(9`-?EPb5&{+=n*ty<31PMA
zG$^cE4|sHks06@m3LtC~0}sUGyFnuX9^D=<JX%lkx6ET;U~n~j!Y|L@^o>QKfxk7B
zk%7Up`3NH@CN?mDs_}=0mq2Nz1Z!mIftsrz*Gy3XsfI@eh=XtwC<2fp1lAyy1nKT}
zWoUlQ2y&5+<wO4VKMV{EpoS}m2Bi;hq3Y9{qhjyVyF_IHC`Gp(DD`{E$qp(m9bicT
zBmuV_#35vRsc-XZ#@5^Xea7q{U%uM+UD%NURL#%+fEmH1GkrRr`E>q&(boqG#VHVX
zfV>6qO37we$>#K<^+5enkKQdR3ZRxD--rMIUrz)10~Dy9mS^~z7ybMH--&;lh{C~N
zY|X!z_**Cb`~Uy-16T*B^)`PiXxssmq(Mf&^0NjsKO24n$$(b7f@BPDdsv<>b^~Pw
zkOB|uDJmeT&N(WeW*~pdJ5Z~)^->8_r<6zYAr_C$DJo!fo!7pxNHp-b`hnz|4>NjL
zo-W$u(cJ=ewTI?0kAwf196P@?zhi9u#^2`(N}b8yJd#B=`1Go51zGH2d5XUslxjVj
z->`twluz#(u%)j{A>{`oRe+oUj~5V!V7$~dzh-pob&>nsdVs%g9V0k3^eqFq6Y4y#
zWDx~W1qQO&!#dyvf4dpTea8=iDi4p&cRro(UmU{{iTte@|3R&>cc6$o+5?G5kdr)+
zBC-iuVY!1^r2H*&IT#prfz18De^8+F0;pvg3$C`ds2l*b)b-x||Nqka|NsAKu=w+U
zgeHG;^1uK8`L~Hkz@j-A6wMDOfRY9%cYqw>0Cfb2kK_pcmgj%qH7C>|pyjD(4mkww
z1w#|s(zpNrgU35S=0iNS2OOC%ejf$-)Rn=bdkZA)yFFgK{P!2^WKcQZ8=_(fDs>X}
z`GE@EId3tOKYxoRE40uJQAyx$(FONUz-3$lJU(BF{|2=~L0Jlv1i|^%qjwEBoIF|&
zlzP8h%D})d!2^;mK%oau7a$JY9B@EjmKN~*dKp|QLGr6d=ld70-(WhpbS5|*{C{z;
z3*`AJ5Jw<IGnV`cii8(&Z$SCgrxRg(57_uA;QADly&#oPIXfskF7dY{f*9Q`5DPk)
zKzS498)%#D5C<sNfvRIrg9P3VWMyXnm3ytX>nB3;OE)CGJX-&ku)f&#>i>VA?ls^x
zf{*nY6;R`VzonCzfx)-+Eq_ZUxZs3VLfS3hrh(yYSHo|h-XSP^dL9SmSq28r<F25Q
zQwCV0qLqc2fx(CIvu|f9gKuXkhi7N3fKT@lu<v|2KYMhq0h{G%`KClW4dSlW+a=r{
zo$ow4@4wja3e<)?$^q(md0M_HeGJkHYIJ}~4p4K(von^%qxpaYsOkmrJ-X*WjJABt
z-*T9VfdP^#K&_iV==h3fH;W2Nz<>%9&*mc>K9-M5A9-{`+dbe=S@04R)h;Rm9?dQ)
z9E={_0^pXG<^{0#EL~JM_?zB<dUD+*DiR*uJ}MfZ0RjbA!vj8@zd%s~aw{l!Kx|Nq
zz}TQj1+ib;%Y>vCkV25BK#Dw|=7U@f+WlpC8&sZy8tDQ)y(+i%gJ$MEx*0sWO>TH}
zhrM_)>(&4NyFjJ&z5o$N29M6;9-T*Dw7vqhs!deDZS=4gFG@i{**ygu+Hi|NMF-R}
z%ftN5r$L2Y>+O;$5Ys>-F<{GHSRiQz_k&;w7F2)T1_u&)f(-(t)7DG;EiE8McMGHj
zgCtm3b%{U0Y9J?A2L@0H463wW2LA+gq!>Wsaps_M!~s%{gn*p^O``C2QYmN@2vh=u
zs5tPq_=Edt;Bv$Pp8sE3e*ga;6vmJNL6EuDpw<Ye_X#oj=L=9WKsWj?IJ6FcszQ)b
z{+1u09uK5#2ogY8a33TAE=>Rb|1a-hd4a!O4>TIpD{~Z-2tX;l3>00R$6j6p_20Xf
zfTPLq0C)h~r}O#C+`k|f)Tl`KbpCsBs)m69mJwcZfn<71R1!etdjhzf9io!(lKmSj
zO(cMNsh}kDq6qAbHQ)>a$|&G82O0;nXGP8^!l0rTW$1&y^){$s3LXA<jG9Mko*|VS
z-Alkd4qwZs{4LEapnhVG3WsO6jEaZl-I82HX7mKLdm;UM4>YT|kgf9Uwo!o;P@>QN
z|MzS@BH?TKv~(rN1)!b{MC#X5@CX;EBZSl?^yqy5`Uog*Gr$@OFK_+?4G)#51c1WI
zq!pC&LR11?x`LtyG*Sl=mW2uzygUNxf%R?yH~K*`%wQQvC*Y+7R1y@?AjxMfAk(I(
zfcP(egPN|O@h*_e8L&)<io?shU;h7xbW}N>fjWtYLEZp0xgg#!ehO+V{D1LV3uIJ{
zio}b9puxS)`!6bAfGbZ(`rK~~sx^;+{M>o?^|=Y)boGJ%V8D;oOZ@%Dpqdia7-W3*
z|Nl$H-=OT`zyQ(L2iAA|#j)rA|9e{AE0Oi=W&_pWAZK_sAL9U(j4w_;0ZW!Xe)0av
z|Nq@iKYBg>cY6JJ`TaXGF9F<dM=4B89bpCOvzMEGp*AT>9APenwrlQ#LkZfP2z&bf
z|I4$VVcl!UNZKZt%U+m2{r~^<Vz^6Q?+1<gT>>SE?pg-Wcx)?ZjPWIi0C&lu^<(cA
za4!MusV|TJ|9{y9>UV<&?!Y$i_rC&-DngaLxcKD%|CcvFBG3x<<vI`t)WZA!f~6T0
zEf9lWF8YDyIVUvF%>ebKVDV51GPeyZg4VkRb*eo&kG(wb6Dee0?}PdQ)ItCa3)q1@
zU<~rW?Z0RiyP;V;;Ug%GLqnATWN{rx1T@sx0&XsY!W7hE0GoOL(f|K1OF^TVut<Kr
z3v9UyXnegy#SpA&CrDKysGxxu14`r|cbS4^XM<#&5wf6uF-X=7EL#hbRfWldXDCWk
zOu$k}ASrP91v1t}B><$$2rT6el6wE=|9_b8AgTKaa_UwFg~uh32ucZ3Dh6sTcv#*o
zeg1OqZ%~$c|MJRj)P!{x6bPUs|Ki@G|Nmc~L$yU1WF{=`_JUl(2ogcH#Smf3n_r;Z
z{Qf0`29IkefV^T1_Dape|NmdA{6TfVA-Dr#K@Qjqp1A`DJhY<hEm5%rYqteyfBO6X
z{}-PhgFFN76;?Dr(jurLeR=!`irM`AAOX1fe?j`Uf|4<`4u0AF<NtreaMFbC_8w3V
z_&|l4NAfqu3m)D61s?qCx32(od}nwt9{bEMz_O7)>Hut<C5=D)0P_4<!+uan%uuQi
zH^`NMLC|(ycl!^}2;}+#0gvu>P`iim(<gobmJ5d=@?d-T<-y~n;2Ap5c<G<eV2|cE
z5}+x)ZadJ37soF4O#b5@-3}6<u_cdA7L^y)k3cP-92Et_OP-y_JidSR>~;aQ&R8yZ
zc3uZ9fzbBomhkBImVnsfX?d>1+M`?AqxE))AV?TA{BQ!K&GLH5Bk%wrXzYW-1JrJ8
zJ|Ynv>(lw%5j>0H82|sl1keDxM|UxYM|b!O59_cOrJG-zd<dEXDqYoGERZ(g^+7~n
zgW7QjZwPoCXBUukWbo+T4-W0t|D~+21w1<MdvwdcP`&^Ezenrs(hr`{c=PPuqcVem
zfx+;bN3RID-SA@KJ&;c!!k))lz-1k%K>=ceOa!rC+yN_EqtXE$ww(j6p*$?_dUU=o
zQ3AQgquUx%|7^YoYHS~t0A-z5Alty%9NZ3`0X8E>B?3IfcMWOUtd#rZ8}Pt0gGc9U
zk8WoHk8W*Hx`lT;)IhDI&igMyL8iC9ExiGXjR4Q?8Wj&DpB%pn@=1+~hv#t@74SSC
zhykAYdm#<ATmxizE2vBa_Y*+oD1gk7Kr*KuYK{a*J7@}@0mJ~UC17}Q87v2C%p*?|
zf~V=4j|hNT4u?TzVz^BktPi`}Aoj(mfZf)T`TzfaSR1F5>*Y!Y(EJS~TsTm}rPLVI
z=ySXaisQGXcV6oMM=$D5K*yr_Us&J!|NrIAf2a!T_k&6p{ugrh{{Mfy8LPXC@8Ay8
z<6t?MyElUD>UKtVV;;zjbMJuN$luZfQq=kS^)<{`F3kn01li{S>MZ7g1}s1-p+g(c
z$u`)C6?iPZ6J#2Ax-I7J|Nk$mQ0wLTR*3H$KyqdOVdYyVc(UyPWF`w-f!DV|6pDis
zmV<^xCJ>pwj@}0O3xEDHybTIL0{N>3tcZyG6%4Ym8EgKs0Gk2JU-wY+*B`w3%M6;o
zzTN`)2Uq?|zXkFM_B6a3EC)>st)Qj>?))VWR$HTjEq@h*<<RpNXyF*Bmlz6iAKrX+
z_$F>Qii6FDy0Ik@IiCr=+z85N@C6-s^4rCmkTAOQQXV6}?SLo9)3^Trf4LGzep`17
zXMSVG<Nk249L)U-L3ZKDZ@LgS^0(B06oGOPuKWhFkLdhX2{H|w-`?E(|Nmtc_WX7M
zBu7ksTMkl)Ex&<Q3V3wy2M-7~|NmdY>)E|U<pyZv^2n|K{~f#csDKs^fMi%-e7X*b
z@I5M^s><^?iwd|dZ2e!#@md(Pz64Sa%H9CQ8UB3Q2{i~*J%Z{!P#poP`#@}v;UM;l
zD_})L<kQvHan^$qz-GYmDOx@F1#dppgXU9Tkm)$<!Tr}jL4rM>3PUXi*EOx6dIons
zEr*(eEuWqS%c1Af1d#ji=1*m?KJ0FT*au7BDD@x-`P1+kD6T>Ep*lwX1TD3Ml=a%z
z|Nnmps<Mz<?Xdcg?>f%>nS2#@*lq{Q!Q2nZ{2n;+XYW<88~IyWKsgFu{sh@abpC7v
znFh|EKG**Le_4b*e;R<~h{>PqAcdsmPnGNF`7`JWD8LBh&)O@X@W7uxUtWe7gg1Yh
zfNdipe{zFt#9ohn09yphpJ??cartxYWsrYx)uZxY3$W*bVz3-E4H3wn*DrxW1baQI
z4K@c`{sh&dydd}C&8O8+v$3_M&x7TlZbYp|iD<7*yaWlGJ1_C&&+f~3@@L*<ocZ(c
zMM#)nbGJB(yYc1Ew->=~BqD!;>?0$8uDOIee@+0&5tBdDK?<Sy6SSTK)Y(N^(*f$M
zfoB>zLsU@KbVxwv2AHhd?|_yf2zfLf0jULbUO>zG9ODmrByR^*w=Ufxn>tHWcszP-
zz|*I$t(Ph;d319&|6l~I;RMYvcAISMEKxD36LRVHvHIiMda{C}+r{!vr;CaKXs$uQ
zqq{)B!@5Aiqtir%zhw?9149~r_(_js7Zn4?J?Uv_X`lE7K{FnmE-Dt_&M~O#1{(DC
z=`N7)u`W<3&3p+uq=z3m&YOJ6BbjAl>jD0bcF=&sZT=3>Q6C<?A`T$yBtSD7y(||{
z=U6Nc^Y`8c)l9u6w?Je42`|oq(tkILif8jN0gxs9EvK0o7(9E+I3V(%{Oj6n*Bzq5
z<Cz@C;==f=LK<YgfJZk6I7l^5cpUt}WO<0cCkxb1>8(*o*ah0murCR;gy`Qn(C}G}
zO2IzRf_C_#iWkeyg9hMzR0>==e?!I%I&XB|cl`dQ^Txp+@&|u0X&&ljnZ$TN^LXcv
znT8iMFL=yw;or8R2DA*U+wp}*Zw8}>b>@e1PM2=SAFizj>cl|KEdVWa@P--f(e3!c
zr9+1iG?7%`(fAV-mi(>h%nS^jHxB-mcl`baWY4Y6%cae(t^fHuikKJ}x(h)ompZ~h
z(;U5^wT``@JlgG}QqUdv!=v-G2Xo|$)=L%qVB204z$85^PnT$T^g93dusl&F4{k^B
zZxiI-X5i6$kkO<01*1bp;s1^RMi0w_MbALhE_fky!V7<}0pL-*3iukAmYje8|AW^%
zgO($Dw0`4n5nyIuK<V#!LhHAH7dhwu|9@!(nyZGSi(R0d5c^U=vlI`U6B%BdIsgAZ
zY$@G>v;Y6^;sdSE2>=BoXlCp1i`C%pVo?DPlo)_#z-FBP{~r>G@ep+{+CYk63%COy
zYp0DsV%;t(2`B>;TA-i<E$fAjB$dB-@e{1DL`A`~^Qhq^pUxK^-FY4!-@kfvyE%At
zgT|4rftTDp1*Obx2_KMd(8v)u9DOWbmPBJ8Pr6p}$g{T|9&W9HV5>pP$C_C{na)H7
zl;8MUGC|8V8$j8((?x~n<@`U$19qUXCQsN{({#`bL)3Zja8q};w?OO3in*ZFuHexf
zP~g$+PysD3EDzSUg7Rp$0|#g=_i+XWhHe)X6UGxBGeF~eP5e;@KJiDmsAzoR7j#k4
zco_;_YzG<uVt^*1mnZ*1<`F=t)T1|o(Zf3OLpghQ<&W0?@Wg!YMco-_J^?8^#=@wJ
zl9*e3|Nj4f+z~YH$KcT!`QjLhBm-E%OB2NU0MIyFZvl8cdcuo);HUt_8LV^xEkp|d
z&m6uq`S<^SbB&6@fBu#wp!5Y=KkU(6qGI8}zkUO#;Sckp2jd5<-pu>=|Nkd`0g#VD
z(-m-+WxhDZq6u{wXpaUshQYgSP=+)VP?Gp|&>%Nx8tFynng9P^t^kdygQprG!xncS
z!xmu9%a@>~6p&Hx0#Nn}042#6fu})%2l8xhiHaXs$POgbdH+Qjs9ZwQ;18BnIQ{?s
z>$4u6hhLun)gUk5{`~(RvKF=%w2b3Yi4v@h44rf7gS+wR>Hq&<*8c^y`P_f>2K;yI
z4FB=^4t%^7ytuIWO@>c*nS_sZnSw_zXhxv3Mnwa(v=F@5P{E_qM@7P=(?vxDR8E2?
zR6zr<{4FKS3=EJe;&t2v4@9}&1}*o0g7OWt+*iO^?q3F}0?q&Rn%sJE?JOi{IKbt;
zWq|@@df%hlMg>&%zu0#6|9?oaFM?9+OJElJ;Ps{5HY$(;16=BJoP?D60q{~Eyuj_n
z29S8Sk4k_`=Wp108}iD1<ctykD)mEQMuRd6tke&Hm--S+n5BLzxYW-96%K)*$-a(2
zPzd$bs06&24$XD}-Hz~5{{W=a4|q`plLVFeTCh@I5n3<fDfhvFU!oH5A_8mzc!maV
zvHzJ7N3n1JqVy!R*heq#@0>)G_ZyC5DerfKL&^oTAospUH>AK{eiFUFp9)q5N}s(U
zD$oMo5iABuxX2^EpnWNzGE&2%`A7y*%Ly7Du=4IaxQYU;I{;PR@bV5+-$Kf}0FTal
zFZzyy(xs0|04N$^<sCd4TR>~D;H3(pybAzVfRJ_sC<%iKEe?<7V*)SUodTH$a&k9#
z1+R+=G+(hniaewLNChaOm<o7#9+a~?kH6jmo<#x~&|9JcnK=A)3^YG=|Ai6AY)Bab
zo@%oPmmznKfy$8A&+wFN=Rk#PFDN0bKmPy!%l+`qDY!6ujZ(6)fp!zX2jf8HNtkEn
zKhSD-@YIcfPj|Y4M|ZviXvI$@Xb=N5ap2RN%K@%YuYhKRAngK=&Kn+@FFd<lSQ$X>
zgia`wdU-S-7VxyZQ+nUCyG{Z!bMf;isOR7&fUGVVJh2T=t))61&`uMmoebJu@%r#i
zP%HQY|3Qx*ttabOfQn)M7w15sF$X0qGLcI8`kfGE%R$Q4qA2r6R@MYj)^P0qfAD%4
z9G=mDd*-Dc+9>yAr0U}JS_lU+<b4`qwhG7z`_m?X_7i}E7rK+c!{a#n1qnyctP*%d
z6lkoC$D?~IXhqix`{V!rckg||z`y_!W_^)=1eDzNf@a?!LAmY-Xf?-D&`uwZ?zsm*
zlYqxv874?LGI(0v;cp3LLS8At$O2klhcYk9-x>v7zX6J)#~$5#!DfJGk%GXM%>_+b
zf+pMSAhF+ipp^gRZ)VWqT5x;Ar}Hsr?8T>B87VsXTepMSN}cy#@E!r}lX_Bm!=t+u
zG@jtmy%#j$j5G&SeHiQ~kf_J;R!}p50ql)dki{V8i<4j#wFM9_?*{W?13+%Fg1d?P
zr8Hze7!LQW1(m&>_g~Z=hP%hr!=t;_0m&sF4}n}#>)>(R6+D&YaoiQWtL=pc*h*Ik
zkIrMD<_w2Nx2u3h=XZ}@4*?HLu#kXf=P{q&3<+=@%ippJkuQ8ZzxZgUs0es>hv;zl
zciZUrSpMQ~*$$gC@ag>R+3OF<O`v$5;L&-*NArhAHwz<5cuod|N%LU=56fGncf7l8
zbRdDT4(wzX6$#I778Vo@1&pBOJZvappgn<}&BqLUEK5{4_*+5a@*dr_0uZB;!A7})
zmy|$rFLYO2^AQiEa@V8t`s*gpBByJA(Klp7gL`NtD)yjY&;ZT%D!kAEhaITn*&U*y
z0N%xc)r+P2pv(pG-h-Di7{iH<&?3$L#hpX{|GzW>6=|UT7;)hJ7!g0}4}o$Svb|ft
z-htWsdXY!BGY4uSEj0&CgXJ6qCkXzQ*MC9t^^ad)gSEjRYZ$;=HG4}`>|Z2-Y}SA`
z6Ct^K57^!pZioK=f4u>vfbl~rVEFqB;U16%8I$x6zAgkZ-KLA88aCZ#|KjaIl<77|
z3BC?ezCbwVQEatCF^3;+>r#+8p#9^h>$f1g<Uo1<`OE+CRTmJ0F?Y$?zlb{s-X(`4
zEENtw!;&AgEUptHET@3hWq`v{4CI_vxP>TT`Rf2EEMe_8<oUtwcn=TI+W*_I+yh!S
zEco&hBWSDxx<ME+pM-N>yLLZ#ya_U|?EspOwm>QY-tPm|Of@PNp0FLQAchA6XhOt!
zKS(Y{MFQjm)Ol?IkU1Pk<}8Pr!vWF`+L{lZ*9M&t!0_TXSPo?!7Jq9bsCa_bc!xc@
z-5or->yaH62G)e#VXMG$P=~eTAWydOzFY$?i9qYP!Sns-bH?C#?aY0UFu^~soxC5<
zytdnZ*f=3Zn9kk{3ODRde-D;}IUO`f;DKWt|L9(@8;O|L2H8jSIAI4ggYds-+4ukd
zOVG3%ByT}dBy?Uo10+YxIH5gAA$Xh+-lPKU9z<<Vad>p=gPKzTkjks`HMBWZ5(i%2
zfxR7tXnJ^d+j)TN=S_P+6JL;}5b##g6O>lcDNyOe|6;}7|Nmd_!_lbW29>*@65#&p
zCCGk%0nwWS(t8K7CI;Sq0j=!u@aQfFc?YzKO~S+S9k{c}-vSy6@#u9G@aZj;@a%j8
zsxt*(`PsMgsi)>!@9q#&4*%{NQwDH#xCoY^k*h<{MwB${^<ilp$kE`+!W(1=BtRDK
z2G!p#Dgx-OQ2y3+;MFuJnXWY7v-yaDk7Wv|Dog{_T;1SxM-~vnLO_OL)_hYvI={cZ
z3tndQf&U<6A0p@&6i{FxS3jnp$bqad2UUB%|Iy0(Vw7^a2xL9DVEnuL|NocC=&FNI
zR2#!pp9iT{0dcXl8cM;51{_K+L3<@3u?s2OJCQl7x>4%Vljxqw0EGqIGp|=7QbXDV
zl=3;B!=qasw077LlD1n9l!U?hX(;W^QdRhRJj9yehdU7o8nom6^$V25d=}&^{uj4*
z{r~^^AX+sBO2izXl~B)LuZQ>yl7XR#csWSV9q@P`%)Q-kw;F-P;p?iR!LB$ATUP~H
z(98*H0KIhB`TswN0dX9pKj;|dVF}LPo}FJpgFQNbm$-Oz`*HYm>v?qBgVzFdp7qgu
z>!JC><KSN=&{(#I<<as&A5azAc>pZ@g9#)g2P(8$L8Jd3oz)y3oi{)>f%7%QeXR#P
zdrMjw_*+3EMjo0UJ$h}{dmQ}91lk#Y)}vRXmjS%-<@gJ)U7&2h?9pqp0%X$bEl~Y$
z`CHzDT9M$i^#A{Vdk@Q_{O!*{x_V94LUezBF$Waat#3U$kMKhq?9laQ&O849_wKbZ
z@#$3d=&Tk18T~R0Y25MPQzjqKHjq~@PJ%+P^SuYNHCT}$Xq^wdJoM-lX@%JJ?!|(g
zpxz5;A(ORdw-0EW>M;+^e_)R_Kl|g!Y@!14C&Z8ZEl>ab|Nl}LY$LcnJMPo@$*1$(
ziztwF;8k(_tsn=1T?OB7<!boN)$qwnQ<%ON(Yug`XTj%*z|utX8wLJt`5X={Z~0q5
z*X}g``|sI#mA}P<iGcw$Ob%Ls@6+85>X;Z_@<=}B`Tc`OcRQq80~%(&2<{Y1fx153
z5-#1v9FCUXO7tM<1Tw_T-vU}5)a}gy(tptN`v*_Ui~OxG7#SEGJI^^<ek;A<k?hO^
znq&d3BLsD@JbFzocyzmQcxbbM6Hn`b(&$~F(_8j!5P|Jy_t^3Ozf0#g$bNR|ZU6u8
z0v*@0?+0Xi$qUULpc=RZyp+(V^SwuJfy4_}(D(xut3aFMG(mFBM-)0=z>^>%$9r^d
z2Rp0t`U^%Z>L&Pfy9a<g2MHW^56A#KC}c`>Ji0l$D>)$HVfmfE1#~vPM>i)Z1q*an
zg2UuEBuqeusD!A1r$GcDvjHmG|Nq|wI`wGZ1h~Hgwu4f7iHZQ&=isRYTaYr);Y<5g
zLZ%i#E6+8+>X<=ipZIhyVFq=reOsUKx4dFtU~p`xQ2`x)vINBD-{zv?%)jpgsAKSW
z2WS}P_zQ2aX0-X;78bN3n!g29hasKp@EG2*(AxI@|H~`j;VBnTpKJ>}BASm$yx0S3
z6Qc$;B)|3+g2&VaUPNvE|NkW;EdBa)K7a9J3n)TaRKN$4_;fx89Z2E^t_8rWnFYWF
zC8+23V%-*yC+@#^w*CMA7m8a@R_1|c4Z!K&v-2->09m17KWHq0fxmSD69a<_|F(3F
z&abX5PxxCv;RDVO*Gha^zm*htbZ>{{gQFhbzoF!V3!r7YY98GZ-QFCQXZc$?K&@NI
zl3f8<+V}YW&C~Ki$pg>st)TSe)A`*46dY?oi?f@LD7bV!fG0jkcp%bUCl)pE{EnRd
zJi9@Ce3lCykcGtH^j8Q=e_vtlf~LPhaQXu$Tg&U1>2Jvv%=C8w6uR9g>2EJc8G8C#
z0agc2e;(ay!08WE*tdW-Q#aItQlA)TG14|ySlUb928y=hFV2HCqNhF3VJ^s>KK>Sc
zMg|6?w09q#_SS9w{~vq|8a#Xu<x49l+_4ld5$MIs%gz7)cfJGXu@`$cqL(hGH-i!*
z*f3Bef(n-xp!MtU!UZe^vd5?M`-`(6DXj4ePw!cq{{Mdo+Vl*G_s;h(I5vUuxhn%W
zy?cNT+c9`~3AEx5bPCr#1yNWz=&<?!{}<64|Nnnswh<J7=(%A5SoHpj8)z8;w0IZR
zJ_SwdBk}`i{FB3{+uYakOGydXJ)Yfp9G=~Bphiv{ypdCZTt1?lLgRwe&Vjbue{BG5
zjXEabYx$*gBX~JFxH<oL1ETc<uJ%DYqR#qsK7V~5r5UsioV#CK+4%oIXjB8mHg33W
zuP0z>27&zl8mTQ0>Te+GJ^_#JcmdF;ngPsTrL3<DA^mCeexn1Zgfs-Xxb<!69ngAa
z4p2J|sSCVh{r~@<-Y>Y*3t~X_nZo+GC|=qG@e(7*OUpbkdw@b9+YYY>_W(;Kd33&h
zeGA!l4<XtQZTSEH^;OW=s^cEeAqL>q7-aiFIERO}xW~a?OdhS5N^5;OpMyIpFE)VF
z6}Ly{QHZ4_ptD-udO(&OeC8KmgEdw>8LyVGeRkv*bOoO$(0RUu542IU^W2NOpfKpR
z_G~`N0_iu%fSlQ>?Zf!gr!$+wqcdB;1H20Xv<2ZXXq{v)i;#!qp)wxN<3~WpxxEB0
zzXk^fxJ`EO5i?ZA!}5d=zuTjipuPhleS!Lu-@tR<AfH0~*lh>eP4VI**e%%{pp6tq
z!1LeCklObus65tzZli#<$2~32lxRa5CFuJou9iH4l)Z)r9OGd7NI>0A<ZTm2UQF5m
zp2yye+*Cyx-wyWZ{0usr$fNaJiM~&FI)_KMyMRaMUmwi}knOm~%C$WXK4v!j29A$g
zK8%lF<gW!~ThJciBODMXIL0}~gU|i(>9+R(kIaGhWOiQg)%@dm@DDS{G|OZB?cffc
z<rk0651z-5{rLa?zbC)*5g*0}#r2?4=lBLtL$j8_qdOFI8jp|WFOP%2m_0kMcpiMh
z?9qCuM98P}pHJr_Q0s?#Jw&Z9;~R+cJUVarbUp_;@4_0;_}2dt(0M8^jMsw33v(HK
z8NYgT#&Y;{=7M4x)!CqSDroyC=rA16Se2+x=Ob`~^zG}`{f9vdSbW#~|L@al0^T6<
zGVuTZ|49Ao#y6nFLLS|Wpwub>>SKcsoDqPIt~zpfbVK(2w0`4neGJ|P3aTo)OH>q4
zmgO*6w&7lW)CTITH`_G*2d!Nwx$DvS!K1qjzL2Qo3}_hwXucS{ksr2Hh`+^?6+UO2
z1TGhm=8Y*?FEkgFeNfg5-3IMTg|8Q?T@RZ$CTqRWxmA#vWAybxWvf9o3{BPxNyCf=
zO*z75juGpHELbpSjv?!XnwS}2>xDpziXcmZV%I?(ifz4+156S$b4<>9A$hO?gx3q%
zvEZ0E4tQa+8ai<dEoac@kLRpL%pVu5#4>+e4Gu3B<n=-stI?;A!$699p$9%fr;nLH
zVi@a%e8EQ{)Tji2%S>=Nc@nG;X}yq7cbbRi_b=${g>Ha5avG2W2)lhH!08;iUMQEr
zqxCJQ;PjOMZ$R_>{>9VsTFFC?-fF~xp@m=rFc%D!{Q3XC8?j($<{zZ}!XDkF93I`l
zkiBWsK^33YYH&H*?UU>)09y7p2Q(WFTJ8s3G4uh{FLbm#SlbNBtI#Dw;Nv493v3!d
zb86t8X*{Tp&<&b&?-uv!wdwQd<vHYGdETR!r`?f%@&VA9Et3yAa-86A*$ry3H~-+^
zZ`%lBbsqKT^%8J3yyS84Et5|#3wX4EKm3w!=Vj04LyVpWAG3Vo7hv>gKB(ZtFC-$L
z#xLgh!iirjM4p3RJVeESKk5>Hl#hx*8h^yWPy7O&7t;8}Ja44&N1RII7j}G*#xL&p
zB8^`dBrfiG!_)Fr(IOwq1N<$ZqX)fvZ5X|Jc@BYA1nq+?d}92>FUaWG>%r(@d4j(U
zGFf7C%7^i!XY&Cjkh(sQZtxl@_D}qR?EG!1|Nj5?>Sa0P%Xs3m6TbjU8^j=nPyB)m
zFFpPthd*Qy-pieTh*>{nw;Y-sL75x2o&cV?TWtRzP5&d-6BK|qm_qu&pg8k@ub-L+
zDxbh<7|Z%86;OY>^ZbjMKmPxJDew!)XvE5bf|u)m|Nn1z5)=yk5Kpq4`ou5Datgdu
z(DGpE)0Yt-b3mtRzV!Qxwg4#rT*V>|v#|py1uv3HS^oe3OMS$4X2`Ua_73QzJb0Jb
z6wop$uxc}q>Q+!PcLuFi>J0wzdIO}%1{r?5h{Soh8`K4cEObCxUG;e>C}>euS3Lj;
zb>4p=u@clw0JrYJ8X&8y4lM<(jDW1Jf)6)MfV7Zcn^64)JTwn_9Q?`bVR??f9n{D5
z==SIM#4o_)(RtoO^SH;s|I8ki|M=U$^Tn1Y_*+2xUwnE!I6ON~d3GM~Jov!ISM!wT
z!58+P2VXJ!b{+<$e_zWBzMU6*`Q2}MHveMdZ=V5L;@bQJluQ}QTs@nAFn!_|bl~vm
z^$;jC^KAaX4B`s-^m<5?se3m6U;%L@e0n_;$|O9Sf3SkM3O>Ca8fBayriM?ihk>u<
zp%MpQ%|oCZck#7P=UorRw<TI>{2C|xdRYz_9!TRCISD#a&9nJ{K$;`JrpE^#%Ns>+
zJuP4P_VT<eVg2ODFUSb?r{+KY7Vw$kohLjFKCtmP_`=@9@`DGz`!SE^e{A3r0YC7!
z9|aX%5HB!#^j30|xq3jn;mG09TPaXx<^l1Bqku<mr9_#!2gDnW5+1#k3S|-=5N|jt
zc=T36yy2+f(OYTYVR@{?(X;cTr{+Q5&J&&&U-)(&@@2eKq6zYeN3Y01gik!04+*64
zYkGa~u{==p&e!sySFg?W5>QyNd-Sq=^yqZvfV8X+gUe;`I@bN5@lDWalaFk`Cr(;^
z^60$i(fP@P-~AlOcl_<3Q)4}ve}SXMqqhR=uV0Yp@#w7p`|B4ZdOUh7!2bFLveH4q
zqqhR=uV0Yp@#w99_{%}VqqoAq!}1!`Ul+0Yi|2yjB}fb%0u_=Ve|diJu{?z7uMb%K
zRjTdLdEC|Tq^scpkJkS_oy<O+-W)!i-U9qBzd${GaR1`v;zghWi@^h&Bf*ViP`lU$
zRDAd9d}RQK4Cr9QPVgZ!FSda4Qn!hUXXgQ*ULFl_QUD!RiIyHd@e6QybbjJ*2Q5wj
z2b?3nAcrR?<h(qZe=&j5l?R9~P-f-P{EHdP7w`myot8)QFBULg!V?sDQXb8}!0FIK
z!4njETwpm3PiXMDdT3q)wLUMt@PwoZgEW4PgPy(6usZ;4Pk1!LgV6DVhvkLxcb=A?
z;7NnuqucuBb(HeF^-`(3;Q^1%^DkTe{{KJ0xAk_ZzN_H@&(4D{*MQ2a){~_Mpep?M
zOPhcH|AWq-D%Ak3B0l~SwDRzU&XWKCeLHWw)B&mOyfESAGce=S%Wa^dvGdT&=OEjV
z${UZ)3m%<MVa<5Zk&mG6G<4g&N9WWCP$k^C7j#y>N9R`15&57~AiEtn_*>$@XYXtU
zOM%b%VFE1^wT86b`8|45Z$yK-k)S?N>|u}OZJ?%mx6G!_t)PR;U|R=TZ&zG}?i^?Z
z9T^NNpu0^r)`_@u$AI<?+^*mP?;8M1dUUsfZR@TO@UX6s@aWtNn#|&FY2^Y<sfR!D
zNIvG#*{T6@^1%np9-XbABl<t_3oZp6<?qqm3R3NH+;stH?YmERg@liFg#v#I=zto>
zy`Vh@uM?5_1)w!7ttk5lS|Q$N@aV<1li(&e?Sgg^)Gm0@vKUkyfGzZFJ}v;U=sdV@
zP|o4eTf5*z(qho+Sa#3kc$UsX%u_*O(%A~?c~!`Ibhm<o9b->H9LQ(AwF{6Im#&@%
z>LS!`fUYfd-2n32i_rNX>wPzL&jp#tFV6s3SNaz3eu53K{RBR}35-70iQxSNfj?YZ
zFV%^7^ww?wo&5U;WL>xK22caSrNe~L1H^`HCa^_x4&K5x6STQ>_k!Hu+Io_|!w=L)
zsQdxiUSJGr==Robcww~&>eNE`+R|?oyde8t$U~(_>>reF00%H5Shwp2(8w}ug-}Zd
z#%6*S9B7>bS5P6<TRP!|!u<dLUz&qQ!Wq!}1ugRt{ep<OSo#IY;1~cW0Hm!1zVp$$
z2G$^Ty`dl{Pk>AvyaWfa>jGqVw|)ZWHAw0|?h4+5>T%o^lvNo#j=O?Z!7()K03VgX
z-!ct!bal7u1mEtA7vS^N<}ooac=wuU_;eluRal_&P&*HTN~VJkSv3#x?{oUkc*4W-
zQ|T*@Zb)wKo(5V(-|Zz~c<K8`kM1^5lf3n$M`x=CsLS7ZjlV^Qk%6J}0!X^^0%-on
zqq_|}-PEfx!LhqUg#$GI1X}m!+3PA`c-sS#vR_+HXg<tnd78f&v?Ru{mqiX_06bGS
zfK2GT<k2e<<b!MuXt2bQf4hqci>Kw$(!Czt;B2xVG(ra&J9_T%{ku=+E02Q@SUfb3
zz)b6W?!mkPv_$d6O;C9Dy7q$-h0`$>9R`oiXW(`N_yENhRdd1VZ-Ga5=>)?A9^HZ9
zz6^g06F56pBDzO?tPBh-2l!h-OVyyZG#_U4>HOo--2e_Q4++cD{7tH$Jlb8m0W!X}
zXEtbf#&v>cFMP2me``H>@B<Q=&EO<r;L*JmTsT=5Nbt8<g0cyu!v%KZOGnUIgphUv
zw7m~n;tv`R1?5BdehK*YA8>XBEg<%t4fY&3yF%Lgz8hexz*~y2HTQityo>_(B_QWP
zf!dj!y`aO4Uj)ttb?iL4!94yc2VUL-WfW+k3holrE_l%c3hZvz1+Y@qqtkT)WNoo7
zsPPWE=VD(3d<5KX&j0@}K}(?x4}fp$fG%eC=}dg#bc_Xb{J-ypm#aYS?QYiz4Hixy
zQ$bAw{+1)424DA9P<r&}4P5{(E)IMFm8dQ%9G;9vp%LW6yaJR4z^R8Vt@S{Oe)9`R
znbjNOz-)NHgYnX5M}C14RwhUO?Io-X9?b{XKqsh0efz{OXz~$sVjnnud^-Ph2XM4r
zDpmCWMQt~<ViD*CRV<(QBX~d^H!$ZV3uxQOUT{?gQp?}+8C3H^GgdOV8vDdAFcqZH
zgMU3ZYk4q(>#R@w5hBMw@e6|GUhV;D)4TwxQb9dt$euj@)}=rG|99zb2i2vX$sb(0
zO*X-5QrFgR{2gte2@CK>JjXrIs<acFvg-I<x^qAk>e~udm+lnHKd=z$t^n0e68tR=
z%-~A&wrBDem(Hc&dKOYu`*!~E>;yRsT$y-u_d+VCso>fWS}&-8c93+}Za@rM2+jWg
z-?uy81C&LNd4kT#affbix&-ds-vY;=gip8Y2GH&%&_Ov#7z4Z@eU_I%8=H`_&n(bd
z(dHutzSijy{4J%73=FQ200H0V^NC-e6&$#z?GIRcth<=QvpWEs$4e*pbRPHV1P8Yd
zq-=YUGYgc+eY(LVoliGdj=weK|NsBp!2+$9_&Y#rUp%}03p~5sE1*N~mWTLzqd^5D
z=<I6H2B<lp4N%~8!*~I*1*#F!w*?nmpZEpAG<ch<2gD{=8U`)d<zN5d#n+F}qzo;Q
zeHdSOFt<UIa)}BXsCWe*nblpp!SE9Qb`BT*?GB)^gbWrB{`I|}Y{4J(7Toqcz~RGq
z(DKtKenFk{HCJD1gKkd%H|PHIw=4zcU~qd3l8-&1XETA^4c*p+)#;!;d7yv>)$C{u
zJ?K_F&<>$*{4JnspwK$$jvz~WOBcL2F#Z4km&PDo=V1>}0o@5M?LfK413br6KMhoR
zxlZut20IZ{A-?DaiFMw8ad;-`uE3HU@T9~C{)1edr$A@b1%jqsdrSK|TfwDQ@bv%x
zyTQzt=D*N}5p2+0m;lkb;DsE>g+aeSap3?O7;x+i_yJxM+#UF%x9ERo;g8o-9J}FC
zuMdH8>r1Au;4**1OE*v!28|Se8X=|qVE3P%3ii_ikKWQgu)r>mK<E7zpJsqZ30^+}
zUFy*J=jB)QHMgeVuF?nSar5s%hfVgDZg^2O?f?Im4?#S*O%uQdCr<tU|Me84k&D;I
zkvJ~}!S^hLdUXCkYyX2PdwBbQFF2ETZUtxg&Z*!!suPmFT0VetFnBAR2dD+p`5RPQ
zKwBp-jNXGh3eE*+!~4zP;eCt_LN}y$kqjT=M-1<GJ4kpy$|4WTW2G{$k09oUo55+u
zz{3(um#l)O6_@TX4%gO8rT0C$A+2_JtG`ad$FfcVI-c1ID&|VFUmoTH?GOOXe<1dg
z!P@_zqq!kX9B8W?NBjRF_|Q#Q`+qvv_n=liqWvGs1=>ptYyVFNB`fyssURnSdI5}w
zAgv^D`yU+8M794XVQv3U23Zel|3f-A;Pyl3jm~?H-!b|D$2vbC_7!982YCK)Z9R$T
z2Ydlp3u^m=cNL@c1NLyjCkc@I0pL>46|_Zr83%l>KpS+}HK-q81j^Xmz8g?x3LyOe
zai|n|{eYjK3<j!YkeXmEpu5jd7bS4=qV)r8K_wHYA0ROqG?obI2Y{!yP^Yq<gM0uf
zK0Tmm82Kb$=yVo;D-WnNK&0+YaN6T<`3ml!BTZw0%R2s+yCCuA0}hbL^XP<BYTe*u
z4r&L1`gyQEY-#38(7hp``C82Mi`WN`KmDTgUyn=yB_Ob6i1h2vj+A~EO#!E0P|5}M
zUqPvuob>y;A6xpJIss%oEd7$*e`W6mZDmBHT05w*pjI<R@70VQGu3vubZ-UKap1Pq
z1Xc!y?#LgYF@aQ23m%keYoHm;r8^SVeRXZU4eh_iLnX;ew~k=lpsEAW_QBGB{lJNq
zZe3yNHemwp{_Bwmi2m!0J}mv$#o(X<Cjg}WYwrZ~{%bWz9jNs0t%deqH9;bvE<i8B
z-K_$gpfe@+LK<eJ6J9(4A3%73za@o{fx#!)12k2{-w_V#b3qI2?t%iy7IWBy5r3}*
zL>g46Lg$UtAR^#Osv9wL#19tU3u*>=z~_$IJuFXwrjQ_WN9XyWb4QJ!VjDbnR1RWw
z9_A0f?Q!rm6Lbp6r<Y|esLSEod6{41GHmJyJZ%ITO9#y%p-dl5H2^Jr15F_Hf>^{&
zA^BKdE}!RPd7vcHyVr&T&-785g-0*y)RCv<0Z7{t)B|BJmcTV_<k9U4ZWh2sLR!rs
zBMRW4gH@u?b{4o38Vfr36WR#{Wp40wdK1vcL2CLC=_sHFOFD|~1Lb7&bmR(3a^Q0@
zU`Hw#gVdp<qldj9ooM4AA)q*drVwaL1Lru14Crup<Z%!$X5z;|z~O>(90Zh(9YNzD
zpmn!|#z8>Gv7?TIIKta6*vCU4mQ!my1Z)~&Jme%OTtUt4-r5DI;~}7Eev#D!3U*(N
z@sI{ko<hF5t{plaA`Wf_f?HXH$3xb1f!oHY;~^ZNO`Xt4?RJoOss0T#Hvn4i#{k<e
zz4I4H$N|p(_#MgzAAbVfKh5dVo%qAG^$DcF2akVrfjxtL{KEiwP7*fV>AT^j1lTU<
z_y@B2&~5hc(Utd|cubf1jx^(mWI79|-3=dr_kdRDu<;WgP~?CLPiT7;F@6GGYT9|;
zqZ3@Nyg1wq>Zp2jgL$CQ7*LA_Zd&aE&?N1Pf8e;pHX5@Wbow#cXv~JL|NmbG{)0Ok
zbr=S8ahONv`4^`^2N?2z4z)i3y0;8;q&(JPn~mU+2()1v@OhHmTS3_e*5!S{`54rY
zfDhY%QY(?eHaQr>Hg7@QT*$Bu*qqKk9^D<F4h{Hvpi)o^4BFvt{wWOZXm$(mw~2z2
zwv&K|<#CT*cL4|f$;Uh_5Ajbv;J|SVa*v~D=Ly3DhTl9dzVPqmVfe%^z{Bp*d<-;0
z$-<PzFXqF}k;X6P!o&e8?$h{%L4x8gOrXmn1UxN2l%MmlJjvhk>koKV|I{ab0ftXb
z{1GhY!Snf#{}7r1cL4QAyCDrg=wKIYpy13yXy8Gcp4bKo-h&zh=mP}_;DG{AMDRyF
zK^iEy_K9Cm<$V2B@R_iN2S8^gANs@}!2%lpVL1evv-Pk%QTpPgE~t45?r|czvhUkK
zUD*3C)Iqrp-lqqZD&U|7_vu%I)PU+D=!jTv#ful)L1LZvUx4xxXuAjlC>B8L+=9_M
z=GQ?(fuNy-*7pDZU!DZ<z)slU(OU|x-CpFh{r~?m9W(?Bmw@!UgFzC0;87Yxe|`1q
z{jaZqH(Mb01xkESdfL6<bNx#<ybu6cTK^MmYm^R(;#Htj59*db2H7_UREHv+(t@(6
z-~x)B@+ih!1YP?9GG-3Qn8V*tR>Hr&i`<Xr1YHOSYN0@nM=G&E8wiO2r5^A=2&h5w
z!mAB55CUSpv;)b2ZiD>uG8(i+89u0S0@;EOpODs@fL3u5yIz%{15`ZXSg+b`hP+yp
z#j&##eXXh?C{-Y@RV`=%B`=(7RU!2SrR!D0J$9{L1p$s>o}J%79u9>begIk%%Hg5S
z4qgz-zy1(-91}De>C>yj;A8oMzYTnA$MNGIKr1@;LQGl3_2PRQxYFbB>^uUxXT+!T
zu!rVhkLJfed>9||w=4n;XLg5icxsEe8vX~(njZ&Uch2B(+!=B=-f?HpR0jieK`?)-
z10w^2FXM6W%(^p&PiM7&XSbPWw;zYA;Wxw%q!KZoUK7|ZE1%9!FCMo1|L@s(z?boZ
zPj@MUPbX-DxhsdS<uU#iPyy)E`O&BI5h&NWcpP_v+Ux|e`8cSl#sKd7GI<;aT?WJ8
z(Ot;l(e1?H(OJad(Fy7i7YcZEI|+Dn772KCItq9+AC&NDJ|y8|d9ZYqXY(-$kM39o
zAI*<G-K88J2VXM#_ST#+@U*<g-vZjw1#b^|fOkffu)b(+0<AAUEa7Q+4>T_YzK5ZN
z^+iq-Xj_*{E`v{JDuYjFEr(|>%Mp)GSIot%kowzkFUYmty*0TE9tWQ@gYQ5G&zpd=
z8+Zw`<)LydpI)1(pgaUR@XE6l98NE1g6_=itpVMN^8IzAXXi2S&V(1Iz^U-~i$^V>
z)wNGboIzV$t~UPv|3U%e<Q=eB_q04(*6smK3!O(_egkjV{|>$v7qrW#2yD*%7r#Js
zw=0K-wkzabqi!4U@><Xk=$aQbjsO3@UW!;x4O*TY37f6=?1Y?Hui*jS9_TLMq4@%w
zHZ6~pn}ODmbiM#BrDpdy&Hx%M0N*0m{ED&kl}E43R|d~c(5W}gM>G&imp>l{ZHVlA
z;Q=l}z^gJkPkCtG02``#$iwm)e>=DnX8FOR^ONWCqoAeB9{kQ1JQy$VH-n}hK^wL@
zOH?>sFoRNCH}rlM&(33zLuf2t^S4|FukE<y)A<oJUeS88gx9B=$p>^hC=<k$hd4l2
zgL+uLDt+kD>&yXKx~_Q~G){T&1+!<b%P|g*&W|3Qhrr`mp!CY_aU4nKOOXF8U;Fgh
zd??9+Opvx7;BV;$?<{BV=oSXw$OXCRt~3m?1L1Y)bI^VOP>{2};B5H+-<R>LZ>Nch
zZ)c4Phi_+&iU9aNTabG#Pw=;kgX)*o1EmqJhHqUBzr6%c`E}lZz2B$v!%NVrMabqB
z@a1{)K&$=bUtDSU|Nr$Q$RcWvdQiEDvJ(P&`|s=0=imcfe0p`jY2f8rP_YcIKp-1L
z!MFd)zi0!Qb_SG=LHBij0Br(sHGJzB5^DGsw5QXf@eO3J8Utu4wu47^8Td%84A4sK
z0ML<IF`!cyJv3QVJa)heJCB1lD$E}I>p?47SyVb*KsQ;pgIi9WE-DI+oyR<8c(fky
z=yXvL;BQ?A8uChJQGtp`@VAP9M2@?tfDa!4Ex-odFb}?I8(fn3w!Q@|Lx2BbO&us(
z*Qf|E@V7c}f^TaCsRkd)?`rtpqq#;!0<5h7lpR4*6%YnwKOl(B-&zi`tQ&MNWG6(}
zxAi~BWfdSh{=e{m+5s}C1$@B{NE_&qEYRLZ7#p-548-PdjR22L_^4QTboM|ll4=Gy
z5X1v%0BHnIxP#K|@fH<unu4;y6Yrpu)q0o%bZt_KN`m2ApUx#JAPLY3A|N(ML+d5}
zzC=(n6Lj2PiHbt=1AdQ#FPJ>O-)MfoZ25uT{aEuqc7C^GprmTS$-n?QNz)ZHAI`wv
z)(hq{9d~5`6~p{(?O;ChaaRsdJ;C4R59YHRcjW=?BjIoJ1oK&sy9%&^oYtla=Cd7l
z6=4JOjX4<@j=M^LE*b`n8ZubAsDKVUV_;x#0WHDpJm{!-z@_u}!S_tPCVN3a)cUQY
zuGfh%jXz&>PZ<M)N3TyW11KT%Ix%&+sA%*$F{kn8`KV}ghNx(O4){#t&vQ}H04+7h
zs{yT|O*)^(pI4(Ikj9^MJdHo+U>biuZ*v)FwlV)k^8v;*{(lEOnhy%3@z*<kNaO!^
z%koDtdm4ZK;n(*jFn)5h{7|9=Dm@f>y%;@`4|V#qGFYCg6Yq6m@<={ud92f=m!Xc+
zV@C5K=FX5-hHe%W&`pc!pnFA=T~uPgRq9(0@NytfiUFUd2)bhZ5qSKgM8(0Qn;mqY
z8Z+eXC68`CkYyHL%qEuq0|dIeL6a4rBmg>e926KJHfZ)0#s-yoV0Q9L&@Q=F@O|+}
zcjLMm9&j~$VtA?bq)%sy3P`6<=Y#GNm56$g*X9#CIUsFs7R$*Xi|SOJdU>?8yFtn-
zSS-)g-#-r8KKCC*QT=UK&_xK@;JaTz9_Z}>->B!&I|XtFPv?D*BSHH{!45m#q5@i<
z0Aqtw73hc%hG+l(yS9F-(*v#0<^bI^2GZiy&0}GB0Jgsj+Wr@0Ip_ht0ryz_TaZG~
z@kd_0ELI-f20q;p9G;q=K_!iX=fU4hzMvyd1lbuFy4hTM6aItJiL2!o{@!Rt1_qbT
z2M1rsgBDTvGBPlD^}1NSNaGKG0?Ov#hK5ULj!FXf6gCeIpUxj1%q}VgY5W>*d^%%P
z3}8(gP?7_8=Q`OK7`pYE-!k%d<bn*h{L0@88cqaXsM4?-95KGhcf7h;th~BwtX}x_
z>fHF`$S)Aez~9;e+ArGO47!}g@Bp}%0XlxxmGOvc>jB^7CzY}<?}N_$pU|z-2DX3)
zV!;IdUeLM_pX8%H-QESCKx?y)`|z)C1{IW__#+PaGCpYj_5ZUozkp05f7I<y{1Jys
z*wXm*4wkWjmW+130)--|1GO9!<{F@j{{$F83k!W2Jh~a2Iz?uH#0@+wLB(v#G0-S8
zC`*ETB+$(=0UDq&u9jbF`QU-s@(#4Q5WI`?F8rX=$5o(B*3Tf@Ah%Wg|NlA%R9`^+
zQ{muZ`L)*CC;4a^fA}|uH=2L_2l?O=f5b8DPCoJyG@c5bN8g2JT}jn{_@+ybivRz2
z*@MniogfOnG4MrL6{yDB14*UeZs3v%hzKZGL8{$JP!Uj33lV7mi>v|PO9Q%54wUFT
zKsU<yAm8r>-R~a^QrrwZ2LW{T8CW&gE(?%UcaBO0Xlq0P*aRl92`->9X{df-kRpWs
z8kGWwBYu~I913bFb(g4kcz_x{hdnyKmdL=?!$b972C0MVZ>~|{U?|}N-Pg$RVh2d7
z@d&6&Ml`jc_Yiq>H-qw~PxoZdYAI+!)HvhP?IB=z;I+~Omu{9e$KH(pE}+ES{_p>P
z$Icgy2Vcm$f-+J0zyJSzl8-{-&$07`EC2d~pg42|r@~Lp{1GgNu*c<!mo?x6R}t%P
z?ZL+@FoBBAgHM@2H<3D4f%bZ_Xn1tKGW_Pzc^uSx>1+X)FrbnQl)ZdFx%)*%+5i8L
za?+>UMMdDH0QgQgNc-CZ-W~>}X$k)JrJSI8vPMP1zgMLmw4V*MS*<fg#p1=q%K!g;
zG~aqM9s$+m93BV%GW&F<sCam^zV+!<c?&AO_*+3$oKNR3M1P_6KncGG=)_sjwZ{np
zKAj1mQ5OjhP&@b0%NFo3At;`AfL6nRTeRT3)vg4t^j%aymtKO~TA*gUAIJ%vkD%T?
z>eZ{G@S>~q|Noc#2y<ZVd#HaNfb0Dn6${X2Jif~R|9v(8fOcqhx~LR*9{j@u@=FEi
zkYC0ZzO7IEdUZh8#xj6z6b1EH`CC_mM^9Z;p!Y?Aw&@6X9CrozmI1U$&!;n&!=w4A
z1nAi0(ubhCH9`7ba)BM>fD(V8GNb(icz_(VvF_jlCdh4_ASWceD607X-xGAk@hi|4
z#{a&(D*7+hmHhwzvf~?4{~o>`+aBaU0seLeR#1GUfNp?%{z4CwNxDN+1Uwm!fCCBa
zQ^?g&pq37()#GvS5z~uMkimaIgBa-X9}G6H1{7ZZU)(DPZA$zJD&D}Ctb+3_<3Ui%
z3)DOVwYOUjl!!y)*W<VYxH0K*+yPu$fUjl+#q+I~Uf>a97X}8yOE1kq1FW$63v`t{
z^B!;`5Oir7sBn4lW7q%xFO@+9HJ$HYih;+m-oL!~`~Ux4p!U+sx1h@{K`pD7k3lR@
zo9g9F5DV0LdU+ni0`)0h9tE*LEwY!pK^_Gy{_#z|1Ixl^y}DT}KsC-wp<n<1yL5vq
zHslg;Q8B1s*#pi`kfLTn@&Esh%|{tMn~zJlSmvli@b~+E2kBK&@i=}IlzttX-!OV6
zAMxmAae%aF4tc<vEmj`gUK}2pKcR=L@V69$nwy}tn4p@?r<;Y%@W5*YQ2od90bC=o
zgK8v?WLTBN-{J&1`_-hq`S1VUu>T&(pGq%*RP%gzeG94o3Cl<Jo|Z@W+XJ9A6ztq2
z1K-vsC4r#pRzL|Dw2vAbV?LelL2PiWy?9v)8V2Rj@a$w!0rx?DI$!y8{sWgVJ}Qvy
zqAyHJK%wKKqT$);qrw5Yn$x2>N5z1#bRIkluzvxqe}}n89#VdRXA-~%BjtdObaheD
zfR@vsGh*JCfL0KKTn1V+3w7CZ59p=BF9J&c|M%;)QSj(=W$@^9<pA%nedhsMBUUT$
z0u*1M0S8EC%u&%`EM4_d<ugY7fGY3|pKc4E?hFoJ%lG^(M?lBcf-Lpz{0AN}iUJ?~
z2q_;?&k^Nsy$#-rbBx2+@@wg1P(w5W)DG$v^XaVPfSk6?49b`i{O$FO3=GBYAloE-
zdQA@bT7EAv0QEE&d^^8_tpT0?J;Afv75RWu{#MXzjc4;wkkde`8(mZ~d^!s_Tw9-1
zc=;xS%BQCwr~3D*q<{+N2_D_epiB+w*7usc0NIw|!EEtD6`XiMMO^b+4zF$&1<&R;
z5}wU}UU($ytSCJNs((RQ4U(>R6oBFqOFa&1<wDyN@Nu7RkYac}+6~RR;Cj3OB!ya!
zPXu*nK<k;|`n^Gl5c=WuxG_i^lt)2<im1o=TLMrnEQFiG3{np>=cP8Nv;g-_eL5>R
z_*>5W`~M%bShxgq^<RxjhKJ^{7m1+D(>H_0+B@IB?(*n-4{C3{bo~GS|BGowppna?
zpnHEoMcfND(197?1NCJ<EHr18NWfiDn-A(PgIYB)DjuaTJW$#d2_VVN_b+x7{r~^M
z7-Gfi6{snyln-?8DkwEQf4LUzFvzE%5)stoe_>Ye|3CUklGj0#CB0j~ZMGMp1^@rQ
zJo*=ObOu^_Dg}*R!qU^r)gVos$6p*R1VuS$PRH`NN9W-ZF}ROb=RthrX?ef&)r+?v
zUN^K@>^urdFC9=pMBA_oDgsSY{4JaQ{r~?WArEvAI*4Tkvaa(eXg+E^s7=&eqrw47
zs}jDg|4UpwkAu3p44%i`!CBt(xI3gEVgTzp{z9Vg|9{X4cj6w9>t0J(U$BBKY(5G)
zN4WIm3wJQ@1*7AB(D4wZt6u7Y*0yyXeX$OdNV?%^?0hb0Nbo;@ix$Wco&R6#%l-fV
zr7So}9DQkxaZIQ%sP2IU@g|UO7(pVfZ%bUk=g_^tQSrTSD}YT1fQN<n`$0F{!DBN6
zWD;nj11PyjFgor5xq1hPC|&n*7kH)M(U&2hF|5wR@PQfOTu>6d|028K|Nj@y^8f#T
z@i71Y{|PT{gP7MqDXp7=uIAM1;I1Zr-w#kmf~0q7tal4QW|<HZOpcbv>fgS+22$C1
z_=Ok9Zg9HiZvoBUK-$zPIsgB^wEzGA|I59gv;E=osj#6@*pR)3N4GnC$R0Gf!VFq(
z0voOm0acO5K{uNI_uLDrV8IPU@c5;3K4{Dyw7EHy!L##-Pv-|v@!S01hY#ZekIv)#
zEoVVPK%naNMMe%xX)S{f=%6Uj9F|AtFQ3kb6F`SW@p^PKd0>oh`dEGfjc>+tfQ`Q4
zaqtCbO#FxdxC{rKz98@tv;q-UJ~kib@aSd-E$oD@;3$prvHa}OdB5}tBH8+7gGR?$
z-oOUOp#$ScBfPNlF(BjOUcENpcJ)gRaB8^z()R!V|GvF7DjcuZfg3W=o1g2aKt?h7
za{m8+-GSx~FNiz%Tfm#Rn-6omz6Wnb%7aG1XL>+`xbx`C`QXfO{KXP*R(Vol25NRs
z&-(xW#iJ~gG+n<EV&&}Y|Nmdl1r3-xLR08T5DgnJ?=4Z02ais}M$MbwOT0+W`v3p+
zI`H_gBg%XZ=%&}d;IpH^hkc+V4%mDS=)zHtUJ(Wl%M1K%{-D-5q6rHb%?GuoIbJB`
zf=a_$4qs3W3vSB%^VK}&+5GH>C*v{xmKt#KaOUvPW(Exs|Mxr&YCbY}9(RKlLT=zf
z2<&#yn(-b`x@7$8+iB<9S;yhonJ3`WoeVn126V=x<(m>^SUcu+3AYF6;JW)SK4gN5
z(xV)pF>FuEH>Hm~yCKf;?2O~^u{_S-k_1ZM-7G3TpwV(rzX3GIR^o!ukcRZ1Ji56&
zIypRGlRZTo9^D=s9-Rdo9-Uqs9^FL(9^D=S9-RdO9-UqSpt&7z1Kjdg=_=3eTn^vv
zS_aSN0}>t}`!o-E9DK#>WBJ#oH|2r>e@h|gz$j2X?b}_>;n^(@p7^PS9is_uuCRcP
z#hrk9P$GXTXzM=2fA>KCvxn3WX&In1iH=M7SpF?ti#U_WKLc9pcyxot?o&BH4P{Tz
z%wjLgF;B=O5prJe>HPkB-7ZjL-lNwh2y_562Y8Z50hCC==^tDkfR02F0)-@WR36kh
zR>=nUre1<?o&ou|H$mbh=*(5HHfXlt?+4ZXkaT=G19T8HD4QXYljWhZHs}lyI8D7=
z4yry;Po7MG`TqIKzaVFI9)FRM1*)&^mq>Yn3WO3BdC%tK;B&BF<fVgUN}s)$k`5})
z-%oh?2&&0A3zS%FR2WLkL3aYYP)`5<AFLiS)eEji>L)|os-E%x|Lb;8o;wRlVxaqV
zK~{Fxa(HUjGI%sU|M6nWn*aY_?|-ouEPwdL(o9fhfSwq2A`N8sjn^HZ^ZCOa!5g(e
zbE)8SC&B0RBV{}mP~^ADf>M9yf6%nVi<6n4jU#y+zTIj*y$%AP-ht*LkLE`oJQxp^
z$bq7^o3E3r+mEHwiv@IcB{+2+cjEv}9{F0n;co%8U_E<tKnFBFM4l&7=r-&$>MmsI
zEMoEKJm;hN%%j^z&!e-7!{a!M9!NK65E`_P+sE={={e8d5ETKR&etBAm%&HsJO(+z
zr`JV50aUy*c(h*PZ#e}TBLtT-ASIfQJi0?f8Ib3fl0XN)`dGd$y$?E&sP%t|I5?tx
zI$2a+EKG%_Rv*v-GK|MPJK20Y-8g(Z>p+v4$mtZ+4~CRTWe%Q@GUz4f)&$ULTcG2V
z|NsBMf3WCB>$my|;0Q$65ete6P<0Ek<7GZ5`N5|4_&qF7m2dRyJnnh$IqS=Ma5A~&
zgT)Vga6h~>2knFL`q5kXzx8Bi(U1D4Xx6<$_R{O);Q06K{0^F*^6b0{I=k!4PEd9O
z)y^Kh^$MUvc_lo$!+(GdI`Qc)=XfzE<NyDb1N<$2K*cAbZ`XO*!}1V+JLsBApI)1*
zpe_n%s&-!b|Nr1|2%pYho}I^D+)D-pX{mtWe^{r^r#Do<qxD;<^y^S?Jb8kSCt~yL
z7Vzja0@cm-9-Zery4^shqIC0leE;m*%W}=Po5Q#DWa$+T@VWVH9-XBEzMTRd&2|is
za|JzGPkMHm{PXC%UV0Ez_rAEE3>wCG0os-w%i;0;vrlI!gJ<V;&x6mIL6e6P-n}e#
zpqpY^PnPI=bPIZRil}&WGx&5Wcr?38FoKQ+@Ijm(2tGiH0W?MbdSBWEkM2+bkb0la
zQ~}R!5f#vUr>IA_6Uf2eOF@I=zMU%9JeXY>JU~9}6nMQF>`#x*>)><gUr2*eK<Dun
z#%UlomTJ7H0)<iM;TNAkan*VMg;W|S>w*S25$knJc|p}OL-POsFT9h{*1M=fCO4T{
z57gfRML++GAIYFw?m@-HJgAv!X|TE+a(>2T_;{UTxM$~g@Fp?T5`n`5dMeoM6p(ZB
z;HQFtx-;NQvB0N-=|N8ggPR9B7c3iN+ZgzM^{XY1_Ja;He^CK)RqKHg70+(?P*P_A
zhv#v2P^O0sCLMPGwU8M+IuAnn{h))mz~_lH9}sx01shND?6wD8kp;hwz2${f3TPo7
ze=F!5ZI51OP~PQ09_EENO~LU(*#F7Y_kRlLD7pinewY&I5D93n!l(1!i?xaW|HGD-
z9CwBWz)eH|fKG(*00n?1v>l4Y|H*j$@6mbu#ddI#yZ>T+!vFs-ekFivhr=(5pn|`W
zkq@{9x8L`Hnn<99H%JQ{P+IY=pjIpB_(pJ3eFiwla#T2cK?P~&QJ>xl4v)?kKAHzS
z!0q)H9-W6ugh2g%4v%g&k4`3!<7}Xq_q2S$-vVlG`*a@i>3j)}A5fXV>(OoG(P@aX
zs^B08cs+t=Zv@C_@RA5{d;KVKdwm&rGy>AjYCg;XDrI`BU>k@_!#pfsdv@L^eS%nO
zFfSf-T7!-Xc)0;=seuQ|k_^~90kr(rhMpJq@*lX1dHnS@a3T4D|De;4)&uo(Ksi$W
zMOwoD|F0(?t>UmeResE~^O)zsXDly|`~#&h<WZqgA&ArX!FP6o8XT|hAvX)aCl<=T
z5CEBa6)_eF8s7lL7xV@mN6?AVpp%#wJeuEVcyzP3esk&0c){NX3Sy7WtB#=KP8eQC
zLBhGWMnwQrVBYlU{PyBmBB<PU<?yg}6(}hItttQwGM5N?bP9my20^j43q*jIuylfM
z{Xm3kwny_}Mn}s#HNlRScWak<^oFSLfleP2*!LZDWZkTIgxRG_K;a_*YQRkfH?=}k
z1YU0j`2#Lq{rCU>*U#YP3tD7=78>-r3zRs)nt9Nh&niJREBJ!l*Kwc=ut8kV0z}X_
zJV+SSZ+mee7L@Yuzu-vx|Nr%aZl@o;9{)SNe!Sj?2vW#>JD^o(&_g}Z!!rR9p1#R{
zUZ;b`_dv7PnzuYG177eqfhLi`p7-dy2|8`M2b9}Doh4An7LgdTiI9-JSL5kud8c*?
zddND&VGUVJ&=>|N{$6iR1Gh)O;jR4V|Nqx7CZL5UbO|JAv*^hZUz9+thu(5DD;9do
z5jfrK{||~&HE>9RPgFuWJ-8qS622?qQNwpLO86qL?*o-F;09>~Y?uJF;0K)K3_uN#
zgFis4WWnqFT33L&g55Qs0csPK7b@|f_Wx1PAQOjYZ>fP#w*!YyHwS1ITmn>_Xr2N$
z7a3ob)PP#7pj8E+HY*3T=;`$0==S63^y2a8_Jee%j=Kqf6Zml_P@To#aokA*)XMQV
z?gS~7yZt0QI=v)3j=MqmbqXHcUJ4$aRSF)RehMDlbs8SsUK$>qRT>_hei~5s_*x$1
zZ(#+U2hn-Tr}JeRcs!yKJRWh*NAsOWCnIPHA7uGdrz1zV6Hlij4`dmiqkzY8C#aho
z!FGBbchLho4#a@CuG>k%qtj6W;mRTfk8Vc=kIq5`k4`5AkM1H3k8Vc|kIq63k4`5I
zkLE)L9?b_0d@K*~w-$mHNj4ue@Yo4mgT&>ddCKG9D^QSMgqL0X9*U<tdP9PkJq|u%
z@$7XGH2}44CBOmi(Otj+y5J9Vsga2ae~S#rqrDXpptbKlojITzHjnsr{`Ktq=Gl21
z(tq{=o#^@0tJ_DH!>c<)m%+pGB7X~L{tr}0F?e)d_UUzkTyF$gNdUgj!9(+cXEzIS
zdCA`jx)2G}EcURxS$fB-yGEA*(lI?7`TxIHw~HyNYEa)E+*hlBRR;X6pjB|5&BqOV
zENxWyTT?*=2Be*y13C^9daFZSBq)c03K7si6X-5W2_ME6KAn0Voqil1oo=AUMg&r0
z!=v;3%VLmG(ApvhoRmSUQC~WOmmi*gy$tm-2vCb2RQCCUrgAMnU0?&yDlv^0%2A*}
zE%1t&5YX+RJ}Me7xj;tm421wzQ2$}RM=whgC_)504yLFGu)MtW2d%m23ag*MN3MZQ
z=q*w4e{neS|Noc2!M%<fm?2yWS`a4y3f%`U=fc;(K^l|W!Ph_dfv-~X_)&iitMgML
zF`WN;jYsGAmy6N2Rij?B847X{*nvtQ2fCrzeFxNj`|r_P&jISwz($f9->d-5J9U<*
zXuRN#0!5kiJ|PiDh8KDf(3TgA3V%x<Xw_P;i%Nz^?;7wjGsx;eSHmZu05tIEg{)-*
zaTE~sPq&E5J_fMq?_bzMjN@;42|CWS`6y$<9<V`N{4Fw!3=GFwROW+bD?K`2b#r+%
zA7bfk{QvLYzwZ|nPpCFDG&GdF>^ykzrNGLD1_lOxmxCU?UQ8bRE*BXNcr@Q)_RxIg
z+WO>Ji^zOXu~VA<tp#lKeUHxfCBldT)T8-0<2UyErQbn2B3rhBPN8r9$H?EJ0d~<p
zCjJ%^2#cA&l@+wYs`)tME-nTJ2E%VJ93nu=7u;<en~yQRR0eI6lV@;j{=>-M0@}vs
z*!+iyzvT<)Zh_`M%q4Y>&3{<<TUppaT0s?dQ50yJTI2xe-~~_Xc2FxTjX&zZXMRB!
zl>+{jbD){X)&nIhpZNtr$9S-KcDh%*+zZ~d>%zd`*nEWXC1`E{+KvVJ1Lm$3;b3=Z
zfG;wE$|Jo0vV;fJ|873W_>J*?>Gxxd3{ZWbb*Z4q$rl;npeXlI0pAf_0IDSeJV3i2
z3K?I>hX4QX*<0@bN}~lY{(t)azY|<^6@VH9(1Be37ICyR$=?#gg|w;jCFl?ZsJlRQ
zJIsoAp56073y)rG4+Gh<A2hWDv1l50i&_tW7CnG&u!I=X`R+vxNNaaH=oEcV>vqtL
z3xCUQNKk@i<qP0#umVu+1aE_Rc7nAa4R!Fh1R_pb1lQ;MEzX<_3@CLvf4>f>+0|R3
zlJMeCC}?g3yb1-hKI6EH3fKzJ$~92jf~@oCd<Eiyjui$aN)Q|5M;IHlI1|Linb={z
z#Fyw_c!mA{&%X_l+K;=afW~bZY(n{4tXLQrY@GO8JD3?5j=QK-fW|F6I$wiJRFJJ8
z2Dn=E?0f-YgZeBChL>JTf!irIs{AcCKx2l@hZzko@o!^pIZ&e1da2}j^DjpJ)_IH!
z3^o;@c4z55P#cSX8}lZnmII~tzz34JsBpZP5&|i|I6%`K3Lc;i%CQ&oLqRQfcL`5x
zcLn~IEYJyBpeYT|73JW_GCb+gDbO9FQUP)gI5ay0IJ!erYB~dWK&$pZ;p)*DAOH$T
z5E~rEuzv4x2XNi)aohn~wNFt21-wURfCNY{DBwZ*Kw@C;cLpec#6gh&5(mjc#Wg&;
z-9h6M$6LTlx4@#!2MjzcPkDB_OYpbcV1ixqh*aI~WCNYRzyQAW3A4UE14@$M`vgyy
z-T}=ngPZ}Wa-)Jkxer=)B8D*dTR_KqLfe<%(?9uJj<O=<s@4Pi{e_^WDmXQo1poj4
zG6{U}5h!FBntw5sYTHCl;BN(8CJu_DP0TM%!KDdU-lH={g<}&VNaCd&=)|92%>1pt
znHU&)11>cD;w<B^(dBRb4@&HvF)9MPz{PU&VaAuAKtj#O84bU^SQz^MKWOQnyTQv>
zpp7_4C8by}xJ0sec@ZShZKLvnB?L4m>Mr2h?Jogt)EP2?3dWtBpb{9I#~|$+$L0zS
zM*bGiy*!@X0TQ0w6$&1f*Gpt!;}_jbmIwL$Pgow~_xS)C>h!R@Ui#Xz`GA41<-yWJ
zn&&!C^|~>Bzvb9D9W*%)$v~h2!?Cj+)V0``3~GM62Z72PSg9rnK4%SlxG}s_#^1UJ
zk~&Mcx*Y@@JKI4EEgg@wgQAUb2RJJ=9syOFNR1b8zG*$_$nSaTC1_uoXKy|DxYU9d
z*BAW%|MJt1|NnO>f>ss&fS(`rFA&t?$6ecbK<|OTTLA0~MBdX5SunXY5Gkj%PWuD)
zFSx5w@M7m@P=f1$tSRb-<`T<O{H>tN7D3}~DWGe}T~xpe*CCtKx?NN>Ji2ZBD-;-9
zI$!O(09qE=&0%=p#aVFTwNZJY5(HXM@bVd``O%GP_RIaCR0=i1r$T{YpMn_Z0#*UT
z121}E`kw@X^)CbU?R)owmTW+3f;I0zjtx;Mcp32P|NkAJ{TiSd;QueBK=bB&f&c$^
zTfArw00rYgP@5X$%opVf4EsbtxAB1HW?yJRoaPO38l+DPju!Ae_b>kVfwbO#VG{KJ
zfAcSvQl(yx3k^Rx`P-C1MVt<Q>u*pBZa&NiSzGn_7f2Z^e~Sp{3XorH{H?ct{r?Zs
z1G?|YCYHbTI7kopbkANN6@O474DjhZ=A(J&g)hju?iv*dXdV3GjX%hLps|E+U?EWJ
z$>PN|uqbFne|L?F0VpH_I$cx}I(<}9UhD!J0ofPeVGSvw_*+1&9%%14qeP`UL?r=S
zlBRS9uz-rz43EwL4p6C@0V*?L)t~@q86T+NL#_u~mxGG^ZWon+W*3zjMxS11)T>bg
zz*8NE4KKZX2HIE%DirSi1Z`#pmn+RL3~V_0+mC{zI%8BMHnD+=nB5?LFZiVM5*1L$
zOT1Y04CM0=m5kSopw<S+LXb!OKrM?GslK37HTHl@@E0230~uf?WQmF|ScwxzNjKC;
zh>$)=sPq1d^Z@wTO8%hH^Acxp3G;#fpdjdEB~?&*1eKbg``BOn0%_`IQF+M+5{4v@
z43JA8#~k1G`TzeV=-kR~@Vr51(T~nkuXmz$LX<%94Kfha2?1Y9D*)<=aJ-o33#kPq
zKwTdJP(*ON{Q47RZ26_hFSH&<6iSZ+bTbRcH3=ZIEnfIQO|*DffoODrn=2*yFi%3q
zbSyv#9^4fW_x=C>r6yWeU=d;f=OWfxs}wwO;$eBZ^ubF{kfTAj*7?D@Esx=jLiX@l
z9}Ew_-U+wp<*sjNepN;Bt0HI}A}AbRq=G_S=-dDQFFir0T7!2GL3&TuUz($eyi5h@
z>OB1N45(@XElL87<Szm3O6$D;BE}DSbT(~5DClfj@Nu!wQ9sbV8HV3H8sC8Sg&k`L
ztqNxF=(c__+3)}V<{$qZJKI1<@j?o22Qfzm{+8#UZb2{jWVOy?`$2WVi!>jw;$2Fh
z;ynYb>L5rJ<WNM{Zm=1yh9?dG?*uKl+y&C)*|`n8cF78qpPPUD_v{3pkH+6p3_hi%
zjsvv+zt#s-j)Nr{Ew*xL343<7f!15|x7xCSF1PX%@a!&=@UZ;o(b;<g6yKd&FMulf
zmOESw3_iV?5+1#d0zREvL09g0cAoR?eB%jPX5_%&+xgu`doM_fcejf=hj+J(x`*Wr
z{uUc91_to_pl9bTkJd{by}=y(Eem-;Z5wd@_UXLlp?S}v+r-GDw+=BHAi>SR;Msgg
zz{m1JDd@grbx7N$&h!6&?`{@-P=<x|7FuTsf?SSzRk9jLtQmAwGQ|C@G5<jc3F7W<
zu>U<Fhgo=ZgFV_^#Nlzg^}xUX|3S?&&rUyyZZ8%O#uKgIJUUxJzV+yB6##YjJ6l1X
z<ZoFI8vg2S1?@(G48pdq0G&VV)4dm@8hX{y(jDMY>HjZUy+GB%VbC=tn!i0jMPTPG
zkIvQ&pdbd%kv#YSI;*quxo78-7Z%<i#m|qi$S`;u2ipoNWIc|94_SmVL1+4cT<USW
z6|{93$^@P424cQQ^#m1$;P?O^?fL9Qu@|UW&!Td?6?8o_ND-(}Z^{a3)Nf^BU^w0?
z@&EsSa8l}xQDFq_K0DqDihYn`SHri4m-yuwKvC%ZpTAWH)EiLcZ~4OvT4T(=jTuxd
zw;U+F51J@@?g6qL9IoJ&`ZLg(<1apXg3_Lwgr~Ke0)NXyP#+%DT0aP4bWa5ZE_eXo
z38>9J7335o&x6|SQ$Y>~x7??KjxmNb+>ycrI$wp+Zl4MYD@d~)d>D<#aq#JqAYXZa
z8|_m;!G;`=odFu2-EI;d$HC|0ffY6%F!1bjlkl~CQ{oOD8uRIV?b}-m85*0+2T#x7
z%dua0cC#QaP2z6_<w4N+n2+V-(mOugOF>QoEe+-X1=k8UP;h~Lf|{`TLHjYl2>?a>
z7%x~HoOl?)iPN(ioQpl0!6}&0qZ^#?x*a$?tidAu?VvW8XZJkNDss=>HgFvcO@5&5
zNsE|aJrMBy@E<wg{=sn{JLp6+(B&3yT|q1So`CLSZUgmvJiGUS%tpFMDjKAsdmX3+
z=G(mt+^_C!1MxhMgU|W|g|6rEHju+WOpi`*KJvAE%HLW9TKfy}nP>Mj&|#0ho$Ej*
zbVu-jR^BjPZavB0asgB@_O^cb_y0e5=m?x-J)k+WvlWyBU#BC+k6I6SKoVPtFnBfv
z9K4`R1#84`cy_jdEJ10*90CP7^ITB*01a!<0E$QVQcz}hu}l!)Z+pYU0K4JrzDFlm
z;zgeucvO5RXlaw-B~Q(}9-a3*dtDhlI>FHbIt0h#`$we1dO-!i^B3ms|NnzeqVefo
z3vxf`fZpz@psWbmRB_W4lw9_Lq8~J)vFjmdW0QhMH>4~=>hZO{2km`8DYrZz#&~pt
ziw4k)XSXXTSAu=)Y25}M@Mr~{!0yq#6;#l9bngX~YcEP3gDeH-Qt%QQSpUhh^Xw+Z
zmw&(~Puv4d5x;&7YH6Vybq?y+H~(TR6}G9^#Khlv_V@q)FHeE@#5jQamBKdBo0$1q
zxBmYBf5OX+U<rn1Q2$Yh1GWD+`8Oyy&x3o9FFQbI08fBaq=+8S9~W@h`62~OgU&UE
zbYPyiV0LOQxPZJ4Es$FCL4JUE+OxM#;Kie7pc-Q-sC4z|UJJ^wFaLqv2Clatg(WD(
zzC8B}Lm|kE`;|c>o_Ac~_YR7|?zxKqxvkq6>@e`Hm+mNz1I-Ydf|T^8-Y`6XbY#3|
zH;z2^-4WCoCMS=z%=z>GKe#*tEjwxf9bx3t`5H97hqCI5zttU7N`vNbE$@^*ewha9
z079~4w}Xgpx0`@3Xg-GVkgw%I&(3L}9hLko8_|2@pmYH3kAp5*MfAsi5!)Z{{Lpzy
z^ThXC%?B7gy7z*zhGXY6NXLBYJMd;O@L~Io$J)RhYjE$}@mL$E5ytppq0|5Wjt%=j
z90vZD)1Yn#B+tOl1?K~w3tqy?06rKT-2cXYE;zL906h@g6X`&3PzmAKd7ZySm=X0r
za8Kj|!Ch21Kvf^8f9Kg-3O*1V+%$P@2tEJm9B3G$mqi|Q9ynAd@<HHG{h+~4NB-?0
zpo74#mLBowo(d|CI*)lWZv&N<9-T|USs1ibO!Eq;l<9o_;<ghge}aokkKVnYS{Gc>
zfQp#TLmr*r((^@`BPh4`f@D3qw}RR@pvC==lfX+#JdjVl`ppJ81^g!Xif(W<)O;9J
z1A(mWh8zKI1a5qR%O{j`zeT_^0I4^?10YBV9XjZ78`J>rJpPga+#m;?^aP6W7dIV2
z3oJakdqF(VNl!VTh2&^wH+^yV|NnJ2tOpbD1JqBe2Cbm(1$*a3hy!R^`Ukw9>Ct)U
z<;SlmBLcJjf`<?U{(-^*>iB&ej0_CmgSWx;1}OhSuVd-n3W|D<UT_il!gc2V|Bj&f
zD^JFwki)ipnb(2h1a#)MQS%E%&+a~uQ64DAZu?-I-*oa5zaS6z{3dWzzT5^%<hwyD
zvp=}-AB_9q+IpM6|MC}5lx_vbR_9bmy)YFNIM8b5MY=u63lN!Zh)g%Q0(v<Eq!q%3
zI^Co5m<Q;@<>nbsAAk-5S7GyEeBlW>a2a&eaxQ49vKt&2D5otuuz*fm1_cCv)B(sI
zZSZN!phJ{cK!+%SjsZuFl;xmew-@aA7yIr0|9?3REYJ%|bRM1HIDR<;B-DBQ#eD}*
z;eHri)q!<^nno{LZ9y_OUf)C>;4BG)_Xa(B!HF4kU<0^W%HIzfKJ(~2_re=w=TlJ1
z0A1<btqd{_-n9TVX+X^!Hd}Br2XxQi<JZ?9OF<yr$7MJR<s=)j3uX52ptWhQQP=*K
z8h{p}cv{{meg5(p`~W2II#vGua8TC-8j6*+|Np-P-6srjCs;-OVGpQmwC(@@ueYMc
zfe|Q!LFKJMx<MUhtUa?*Ramd=!OPT7pi~8JNPv3CO{JhHxc|b+9<&84?uTQq_kY*c
z+ns(t>Q6&0_kg5M4@j%br*kSOrMx(412PAkIz76<?KPinu*~bppn)7n(+-p@A*D4a
zf?iCuhFEsi4m5toQYsA_GAsK0|G$mSCRYB|w9o(lzfArNl4mW^0A1_^T5rwY>h~Ej
zz~%x{9b2MhaOtJxXHZ$d03C1A1dVBS_JW#Ro7i5;g5|*VKByG!?FFA8*1H$f=Je5g
z1iA<0h!w~adqEn&<8FrEUL@Lp7GwH!{s#+qb{=`*5B4dzLFLiA6{NcpT&}ouE(Kjk
z{X!q)t8Q?I&chnq_uy|?4O({rZnT2-B$ViMPX*<SPJ!;JAT8kGxTzo+$beich>6^q
z74YbUn1@(+#or3L;nAZTTn99RyMv6Lop(Ut3QPDcnxD`HR=nU@2{cc@1&VrTNy=sY
z|NqOMA3>o9nz!l(_m4dwlAueNUT|Ip1r6AdFH1oSW|2l;HbL_%c*KdnWi|uG1Q~c=
zPVZJwh`*R`_5c4%XHZfH4Xc6u>H(1gIsQe1CCDPMw>)~cg4zRM;Y_eFB%L-Nmw2IB
z16Bs^L-uZc3o710hpst+m4S<O4<uo2kZ|Yy7s)pN|04#Tk|2(FJpp>UJ7_}XTS*ys
zEivNA^(UZW1>!(Rl7Dg65_H%YsBC@-+GPXvJxKS-5@(bkmPQKV<(B{dzXXRl$lu_a
zz@r;Hu<`OaXz&VtPFWWu%R`E$oml&#rFO7>=!2KVAK~MgASZk)u|;vhZqQhHFSw9>
zp#pNks&}xVMo6Ku6&lSi*ew75f4v-JdC~vQf*+k1UhntlJp8f^<8qlbpe+vI>I-^o
zyEBp<2P{Ba0^Xq>MvwK#{NtcaTTqvEfpl#H34j6z5`W!WK|u_T{cLkkNpa)l9>h6y
z;KWv90xJWd!<(|80EcLNVFogj=M%)YP%nWm@`a~>*Jn`T&Il#o+&_RTWKc$Z@!I_V
z|CeSTK$R){oIS{q`AFH|6uf^5T2DdB`tTYn$olXvpke~-i2wip%X@TQ2CokX9WRNz
zKD^EnR3w3S@PgNe?=uBu1;XpY!6%&=__iJ>vG?gb=8^2iVR*@>o6Y0<XU|@qYd+na
z=<CCMIt3x?!$A@5+j;<XefW0M|NlXEbiDvA4EOl{8FT?JbbUC|(r^o6mWG4Yg@e|W
zBlMM?z`jO2*$H$*JoYu>i{Rl8ny(T8ZBhLUPB+J2a9e=t;!=$lL7=eeJpAGmSmOSR
zPv)TBBWNuWqRS0gCw{}^|Nj@trf4OoI>tKj3#PDj;$=`X|CvLQoY#Ly7QPHw{}>9|
zDe%Xmw>|-M9pY!m;9~Qe2#@3L3noZ7GI$(!UjZugJ&wDBc7%fXkUrgUchE=+h!1Le
zf@shXF^C2Y`Z9p_fi~ECK=*;ZcwqMbKjM;R&{lL0(5A2UyR4uyjvyy+fmWVam_t*M
zN9QZg&Z93Xj6u_ZdsIMngVy&uc!Cx$fZE0&26)=f^EhO>1tbV|zT>eL@MH`FNCwo*
z0?B~buz?^D8`Qr6i8~%|0UOT1&|nV|2YU{5Vvio0iy^aDU`O8oby$%$C^dtW!cXov
z`r@z=D36Ay=z*rw417D+fafnkrh|hF)SEfpq5>KS0=ZzHhKM8Bsh|N8kN{{){{;z0
z29Q6%JxY*HP>UWS*`gu<8c2njA^~N;kTU^=REvrNXiBWNM8yDnAcc#H#0wK}7`CWr
zfVL0!?osgo(Z^d<44_&;#)D=QV8(;aJOr^JfqA?ItN|82peZ$wBxv%F;{_)u;erc0
zP|@Jo2{9BLNOM4w(nx_62g;bBvrV8eJI@g0-aX*O9-trsyY6_4N&=FrGLYDytDry@
z!(9blB?65#(1FPy$rlwSsOb!19@s&kF#``I2c0!RO=`S`paRQ91sq`!;7~+bL+A<`
zlkdC-8dFUH%?9>{sOW>D*I}PNXe?#{D9XWGp?gbI99}e@|NkGd(a*EDMFnga==c`T
zZYZZ4yg5+cqno3<kOMZ})%vX@(xaObv@Bk<yAV{OBp>tqz6Bg2puH&f!CRtVh=RP>
zdZ0uMy0-x7qz-UKg{+%BXn4u++Y3(cY+r@IYZlNjIcOTp;l)+3Q(aUXKy?_bRo42I
zfq}ud8?smqe398{_<k$LZdU={-dc{{2*&2$|4TU>yXUBY(g<j|3<qeOZ}K@%aRqUx
zPx5EOOPvxvogX~De?$0}<$^~icvp#<N4G_{KL=>M#_}d;!7g~4p+L7k*uMuozJK$u
zyjt=E;#DnU@S467A*fe7BRD_@k|2B2r}L9X=erjQ;7QvG0g$;MCw(vk^^kHIKwfDD
z?FRQ~K7zb@6Qs_g`MALAODJ`10Jxfi-TU$e+<q%jF?i8v@c;kIyC5#8Apj0LP!0pN
z?b7r?$)-j{;zbT<8*}IL7bc+Xf1USV^cbO*xNAH*55K;GVqY$beRDvA65#YB2(qvD
z@BjZVj)PMrXpep8;TKtTpn%>3&dD#%>VY_j3=f{UdU0J3<Qx~sOguP>UmVc`^;XbY
z$NevIK_+(|e!UyytTUiC6=;w5i!e~W>;~<#?>r2d8A}0K)LWzC@Zwc1$Vw0^04gT%
zf~^Q5#sN-U9-uwJ!5}wu-ha_;`2YV4HmEfhCpd=fGZk@UfGz~@{O!@3E#T4oM#1Aa
zyMUx4!!dUb2GD{&UQo6Yc;N>M*WMD)8u0fo3PC3JR$F)+cZRN<a)zv&I_?ZwJkD_3
z*#cBRfy)Mu<IWDCvcRV|S>i>80djfZ(dn!JE(=OP$JIm20-w(JFAf?&%K{%zwR7wR
zqb{ffug>u4ESB&%?i>Iz&*Qjr1e69Jp$N9@xN`=`Vvpm_1t7T>KR~XA7l<C6&H-Ti
z)`3?`L#jEDeSv89P1J$Jnt)Gdu?I@NbOV(;y}LoxlE-mpaE0e_ycyhI1U1W!H-kna
zK~{SlZw5_|fS6#Hc^q$s)ps7p!H1@Sq&$u{gQ`*x^LR7JP>Acn2akXRUznqL0Bi`@
z51T-vnn+>Rs}E|uz|N~W_TsEID9m6RLVP+mgQ{Lg>DUAg&Jxfb&*v{vPr=GYk8WoV
z(3<kr10@=;qR@qbq4iry2(%m&gq4FH--`o4W#D5-!5|27C7v>{x7x#_6H)}SeFZrS
zxd;?^aS7xe(D_+NL0!u0(R@S!wnpkON;VBb%BJ<;b3yrE<mvqX|N0E*pe~3(B`W+N
z(_Vyuf}wjisC@y-J}N%l`Y)8UL1R2`OT>LT-@kt6*?9!zSn1cRJh~yl+`StVW=Jz!
zm0F;50TxAyIi&b}aU7(gcQ+_Oys!ozvhn`K<1A3jJb%Hh_y7NkHeJxf#*x>zP<({C
zm|-@w;NgEUL;L^#mp!0@$4?iv^t$fT`P`%P=!<8SASYEzyzm9t++7V`sl^XktqIEd
z5nw@Z%e6#>4;&z_Afe9tFKTuF|9|lVl+ZhmzCH#jbgm)RI)ln%^z#E8_kz}ZI);FD
zum*d8@99SDOoJXF(CQCLm7VWFn?X2U{L%qUm$-6(Tkf4le0nP+d^!*KXnuffPJ2)i
z2-%s2RAqt^J+yZ5=oSM{+kp=c0PlA04&d<U1ntWY;{b0S=?)O^=mhP{4-@d|hFAkW
z(;0MrA>>SFkUv1_l&<GGmn4Db6X55*Y}NqnKgT%Fd5#7sS)?-fGQI&Ft_0fV3fd0~
zOY)BKX#3v;J&zv+?`N0;UTN_C^$w4nh!fdPl|S(5)oJoP_>}o&HRz(_355HX94Pyb
zWpqgCU&?92i)e8FGEN<o4(Z;%3|A+=f60Nm^%$H=4?bt6djE2=CTLvZ_>0w=pa?J3
zc=1UMByjje7Fgo`i%DRK=P$a{{{Me*6)NcqmAtA69d83w)Wn`Y-vH9wP4@Zo8^Muj
z?b&>k1ypFVzWAUDaxrbspAYqbT*eJvT?O7V4=QCM__smU+jo{OXgSc~I)lHZ7*q#$
zeuva$%hW(qMEosgp#BZ$_K<D?pYEy`j=k>8uC4za`8|&x{K3>~(+VzFlK*=!UjKfB
zzoizWI{CzR&;`Ez9*hS)dQ19Qc7j$(A<96PZYGcBLoAM&2Lg5HojG_yF!R7<({^dY
z69-QSdsu$z?P*BK4tZ93!=>{_^8@DQA58o$FPT8c7c=v>2rz>fj1Y#4;m^(!2VV#n
zp2)nAed={<=Y@kWcnvRP9xz?E=@96!VS&!t6^5=WJiEcum)#y5pv4Y<K{si-u7FMp
zf;#o!X~9W8y($5qdxpUmVS*+^J$v23(~7PgugyT^9duq1bbN6)NI&C^?;pW86*qW7
zmP4VKFby>Q$iK}+g~iA6N9kdYZr2V^=53(9FKBONXX*rxX4rxyaQgJ<4c+I_TYJFi
z7>f<)I^h?lprio~c;5ry^J@cN7#{Gj4tv4hvI^8BLY_ecUC#_Uy_16pw2lkxTks5G
z>V$3&4$uw4-v2>~9d<sPE5mWu1E4-9bk&<Hhi7*ms0-d*$kY0*p6j(f$iJT5t{k40
zt^)im;FC&P50tQW3Ur4a@Mu23;@Rm6I%T*s^ni6BPu<mHt}__;x4F(hySEdvejIdz
zW2bM!ixgE*it_08Z2-+U+WZIYy#dW9fX@EuwgYeAhn(4vAn`&&71Xb(QvlucdcgDh
zcko&w@SgpPpyTJYpnLYg^?|45i4tp%ZfTF!+a-dadtX6&fKPxlSzau81YQ;jI`E_O
zyW{^W9hh#M-~kTgVh)dP3-})V4L+T|GkiK-SG-uK0&=xax9bX@Zr>TD)4PiWUT*-~
z>(N_!0My8}nfw2Lcjy6V5Wal+4;0icponl~@U(UnfZnAI3Nro{&@I&<AFx5X&9Jb1
zITNu@0JOKmo$=rU4#Nxl+r$kIWgbG!CkJ0LqnuC0zb%~U;3I+8Ck+q0J_pWgFAx3!
z4VH$euz~jS8r}w3?a6rEmw6v3{W&(&sIW4W>b!P^#!u^k5+=}L6p(0j1SMM5=EI;f
zBN;7k)X9U=fg}GmaZp<53_VbK9_I1GzyAM!eZiwUbpj~vU;YQTt{`Eu;uq*}x8G=E
z<N9cW`<@UT`#W75Ua%?s|KIJ}@X{0{)_MGenacnF-SAQQi-_W5f@8QxFL<vOWWSsz
z_{M_H&mN6$K!=xu25dAuy20B5JgnP6eFe~767ZlMDBIvTxthNv7qmVJw4`7nhyjfr
z^cGb26!777p!QSu6z~z);7OzIDJq}^8{qYHP^+5{NPxzPK;0J5@Bo;$hAt65-U2oS
zTy)}ek08hyAorMp7-YK#W;y6!C76RihX#VOyW=qz6%Oc%E2NP+P<Vk73e0_=!VtuU
zpFj>8@N7{56`dgA7q1mjJD-p-KX7MiD=%oxE22Z_pag2&fMx(e$HpFg(I5xvwd_&(
z02+ttT%!USIP~b&ez5{1-p%^cO#!q8@d9XtuHiS2ZrcxT3Jf6Ufz)4cP6V@0fMglK
zdlfH&n4lH(py>AKc6b3&3)&k5awmxW0Hgq#3|=6yLHEvr)pYJ*f#_we1zFV#Jy8xh
zmY6GEypT`;xf>FVAag-_X1GG!19IB|5WBZR;KhDqh2R^xc7RgKi|g_T19pM-d4Ue#
z0%_<2`MmW2D2hOaf{rEy(flntLFXtlSG+L%_QDEm=pGf&xZw+3MNspy!$n1%zonUz
zfnmb878L;|1_mbn*2$oCecdoux~MonMwA?0cy!NUc>oG>(8|RC9tMVnY6}Mb79r3P
z*4vz&AwwKczw__A@M5RT|NjVM)j-CA21*Ps?E`HV0&QXl02%!v5#)IA!s+AuE!p6m
zfhC{;ILMw-a98z(FWB*0z*|8<Lm&Y!W`jh!A=~9ZT`Blr-DPgjU|ordfhXwB6p*U|
zTsptL;NA@xiV5&derI^erPBa(7BZ-ga8dE_=mrhJT?Q4g>X5$n1k11dEfS!131rMs
z0MXa}?qPYk<N>6sy$lo~tp`ekp}iy*&>>~V9l*QQLDqvh+Te71@5St=ptu7K!hyP@
z3}AP?NCSDj+eO8}vp1a4!#dyvfBRcdjDg0vK;BI7IDQZ`YxlwzWKnmGN&>vbdtoI9
z>d)-~50!&zyn~+Kzk?zkk{ehqcy@yBH!rd9=#~H-hn)bb<PZg-C+H$D(2Y0P>%dDT
z_dqU80Hr99fu7xSR6y?b?S}5<*t7Hhe^1T(h(ioKyCL!AdGJ585C8gKpfI+8g)ww~
z01?avkYGl*2Nd3qL5T}jc!QR5zAyuYSL*@zm?tQ_k2~;yGNEVh9`G(d574<euRv?=
zJ3$kRy&gO-`@!ZS?px<?Wd|*c0-dKD0=iYl2Q>Bq+U)pU22yRisBrjngG9SSR3!LY
zZ-O>6LA?i_KJx&j8-$-cJFkP1jt<mM;Cde9A5d8b9jif3Kc1G?OCEp^BYTl1i#mpj
z(?6hL+npal5$*!ocH_a*?JUsAh_a~bGbrakcHdOIfaiv}pyUKfVaR!;1++y5dB}sm
zWf}5DU(iy6*PyjOy^tLDVxH9h|1WQX7JkCU<3U5_FU`TbW<lwK!K3*g5BR<_*#6+o
z`!6~qLA_9B$cS6#{r>?5FToe5H6P>wjb++{Qh0$&r{%s{&>k<OnJQ4>2&(%)@d{#t
z>SGZ5MVJ&Q6G5i5Kt>dRj7R{jQ32f{^rBrEWFdGf5mFFvf`b5(emyMbfTzDerXofF
zTU0<JQXm^Z;;=Emk`nNGOlx1zQNM_McSs7<$}ANC<+<aYpmBUx4oCq49>X{E>DKV<
z_LYE!gQw+r{+4*qMOoe80rwI?kfh-ykIoaGkm3Bhpyfp#))04<_JeX7$nT!kbHFoW
z{4Li&GisnR7qY*03fQq8+Ec)VA$aNj0S^K<b=83!z<3mXQ&+YCXzwa001V%HboW3^
zgdf>mqUh191HQ4#qw}8+sLp-$LI)g|&@A400M<b81f2rm3_j@|-cA6Yk?bQ5ii$Om
zz=jqc;2RG>X9$4DR>2EjrC32L-ofivknbVlZ#4!jQEWcU;bD0fbpG@naOnf-{(cmL
z=7X1i5ba-&ZfnnO&?%|mFTmZM)*jHlLN!THhURYtohs+p9V*cL>wjkmXqSEmXu6^s
z67QhRcc3^g(E{aSXrmT6Qb273Py*p^efRhO|Cdo9y}czW1)#za6oxPICxXY6IAF2J
z-wHaS_@yDZQ4i@q!A~N!2QLsf$^vfUK=1l`{~}frG!S{<WjM%eNOpx}Jy5oNAuk51
zyC7k398%?h6D#OQS!;Iz{+5}bPI&8q5~L1-N2fc7XSa(=g-3UUgh%V`Qr_2Y;Pedc
z@>q8}3$z|6`RLi*0}k0vh^ZdkfeIehfg1cRl3)`rl`wTOfoh26Lo6PhfeIjVj)EJ2
zj3Cd08YQ3{18QqvG-QsqfSb1hu89mU^gyYn7uxm+1SO5$Ju0ASRgi9wDIeSu8D4Zq
zfYJ=BrL6*kN4KpNi0Xv4LqM%ESSt(E!~m7($jvKI<Cu|w0o1yC@lXt8%pB-}_P6<4
zKvzJ)l3Xe0OGg$4h6$iC*=}dhxu&2Hh8LGVK~;X|{TIC=pn~~r=?zeg3Yv@t<rh#z
zh&2BEQy65=9u<%<$RDs4FNh6m3BPa=0d4<rQBn73JOb)zAPp#jj07bg!vin(fNnt*
z18q7B5`nE&UnL4M-Wai3eWvLD|GR`iiZVgvB`9AWebE9|<;nnVw|38A1x1AisIa)t
z3~8X1WbkiucIMyr0knd^4HWd9$6u@y{r~?Zctbfxh2aYd0Z0n0KvW|qnHd<4xu~c!
zykr5T(B4W3&?(O#*T4Am=KuefOTitm`!A+~!vwj=f87FCc><&|1FZ7>O9Rj@&F&f%
z4Y)KX=mvIZHT!ZKsQO{9c+p+1(0q>NMJ>p|cA(h`m==&nUp#vO3L|K98+4h{u}}a1
zzqlau|NqN9pTMh#SArS=D0()b>rwvv|No0QAU%?wL3+UT_e)05N<{dH;LzE=G9l2&
z+x-{kK%ovgS_4`YfodO6Ed&eA7r7vXkh5~Y6QUI_UPOU}Kxr4WfMP8uoO&T?=fwu_
zsbf%%Fi2VF{TJCFD^NBAloo*tCCj^|&mkN2d-s5I7-$jbSx|cIyy4Mp``c210knLx
zJN$)5Cp2WbZQp`L_lST(4U|5?%TPdF8jypanF-Vd0cW5E!qBa<uh)Z?mU;b$tU7B1
zwFnDdOag^e5p*5iepq|pg|*24|1ZqI&Nz&Ggbeun^8{EVgEHHT06|c!m8fvMJpJ$g
zf5=H%cLe|cf3aNv6ufBKJCD8Gj@Tj#S}%8!zonH4?F0ob(1PmTE#SiJ#e2d3|6l%q
zMj$AWf&!!$7FWkWy?qYwWb84Jg`jA9>G<#efAG{K{K(v|pcDve0L=xN_!uP8dHAIn
zC<|Z|9;NOco%dgafJ_A4igg&Q`1R{+$nC5Wd!)%S{(jKO5U`MWVFj|H3AC&byg&^S
z=4;^6(t@D#ry=nHS){c0MLR$0n!S=}m=~aH>)wEJ49pKV1^)kkdFw0M@+eaj7fFC}
zB}~H#kOsCt|Np=I3?3SSucLdt7}*`5gY`jM_6uNc1xIo=KPZxGR5)G;hW`H#zI75M
z7C;Amb{>EI5G8nwQ5@rf?0RXCV~oC{%)7k|M&-Xg3G+W><H2sw>O@dY{o*eF|Nk%7
zd`I&;^3)}N{|#j8)_^n|1BpOOB#`65Et?`fsK;I^p)MPES@IXfb+1<;x;GO-JvzS{
z9x(hC?9ut#qwx)BD)Sh4D4hX(a_d_`@W$iL<};v`xXPeHegk;>F=#`IN9QBh2IZst
zV0HX0p!I#A`8oraZm=G-P01den?cpli>08f*8JnYM`tsrO5$(X!@$7MXb~**Z@)(;
z_@c}bThJgv=lvJA-u(aHd1wb{gtz5VsmrbmkRvq2Vbzf|C_WpHfE;dkAUgIiw0+wh
zz;eu)gYj51sFGy#V7%bb*$nFUfX(rMsE>|4JYkm#$haHuy;x_!p;*d*sNQz*A@39e
zZN^#wQUfspYNZ1MWOvs;P{IeF!NDNz$nbhi8fd*<=YP;quHQgst6y~t@#$4%&{kjw
z_2|{*&{kjw_R)N5cpG$=lSlI#ju&US!S#EI9IX5T5B7d@>HP2bU*ryGPpyaL>(W;q
z%|}qqP(?)O=fi0eKqi4yfR06CWPlC~ff{BE3=9gHc?u<|xdl0iC8-KZ3`$%K@lFh`
zK|%gORtlMUWr;bNDGJH?Ii<OI3JeB@M#d(lX66=_iAl*Rsc8(Q#fj;uRtgLXHo5sJ
zsX2BE(K)FVnaTNiv0MzA3dxB%IjJcM`FRSdxdkPa3Q#$Qvee{~{30u>c;EPx)U?FX
zoRav&f`Zh%6qvoGd8rizsmUd&DGE7>NvSys8VvFAd6_x!@eE0c#hJ<R#U(|VdFfVG
z#idEbB}Fi8s>M1As>NIkxw(l23?+%_#i=C>HmL>0b`0hDMJdH-dJ{_+A{qP`^kKS_
z^GkD56!P**6!HsF^Ax}V;{)-Hm6cC^Vv2?)11E!S27|6{Mrsatp$I4lp+N;#>7H5=
zl9*oXmR}T}UzDQ35bhY{=jrDT3e4pEyyDE_lEl2^N`<7<lJeBlJOz-;^h*-cVS%HN
zn3tlE3kpmfu$)3(K6tGRgF;4PF^B{5mx8UGLP=3-Vu?axu|jEHc3yrt#3U{Tm~YUW
zp$Ya_acMz8ei0~G^YavvGV>CPDj{|ibHVgM<IFv^Bs?`UJ)@*pLlYE%i3)jWT0lpX
zf&8zjkXDqR3v<4XLP1VyVsWZMaz<)$wn9lpVu?a#i9%+vLUMj?L1IZ}QckKud1grl
z#9lqPy+~1>k(!v2TBMMZnU|`dngU9nph$&=Gm<)Zsx7H303}_JYvBGb$<J5FP0XuQ
zC{9f-$;{6yMl%O3wv&s}ATCl+f*1o24|u9bD=tYaQNU0Lbr-}uP|DNLL{Wqy4^Len
zW1!gqY9od^gncLqQPd&XhoXo99uBZ9oST@QnXHgsq)?iNNCjo7Ma7x<d7z+yrUoQ;
z=_v$2qOvG8IX|x~wFn%O;K(jXttf#6mmVw`Ktqjz;e%@;n7u*V5ls7;<t8R4=I2>B
z2f6uz(lz9!Hn1FsC~$|U{{U6L07`@8Kp3Xq(9j8-VA0Kh*{2|ZYM(hYN1|EB0JARv
zs$Kv}gX{ufxP8VD`*0Y;0CQ)8A9$S_!vi}AeV_<JgUrH*FF^g-PzO=3U=N`ix*#+@
zy&yT5y8|2`>Nh~v%P}-S%`wP;h=bH(!wyh?E4V|{9q@wC0>2=%K{kX29mIu<LDen_
zmS(mFU9$_izA}LI|Nj6c1_lGJ|Nkd2F)##h{r`VLh=C!2|NnmpVFrc{{{R0Km>3uu
z1pfb@z{<dIK;ZxX20jJ`2I2qzCkQey1gQT1U%|w{FhTYIf6(c%p!EZwIgJBq|Nk2>
zGcYJ<{r{iA%)l@~_y7L|%nS?$hX4QHU}j*rVDkUJ01E>{g7yFZ2`mf@8|?r8-@(Gb
zAmI4_KLaZRgM-`u{}G_8>OBAdU%|@2aKZEc{|~GT3=h2j|94<xV3^?Z|9=G=1H%KK
z|Nl3zF)%Ff`~M$wE#QK{|Nk@C85lN%{Qv)eoq=IN=>Pv391IKvVgLUpa4;|gg#Z7)
zfP;bIL-_yyA2=8o3?l#k2i=X_knsQi3Qh)w4=Mlue*n=1|NmQXF)%1J{{NrB#lXPO
z^#A_~E(V4LE&u=j;9_9d(Dwg-1UCc2ftmmRZ{TKNxG?+we+C`~hJgA1|AXc)0v7)N
zzkr8<Az|_V{~ti~lK=l5co`TPmj3@gftP{7VEO<54?z4C|Nk5CF)%z>`Tzd~J_ZJb
zb^re>@G~$d?Ee41f}eq*;Qasp2lyEn8ZQ3-FCf6c@ZtLZ{}}=d3<7ul|KA|Mz;NO2
z|Njhv3=9kI|NkE#$iUF>;Q#*@pm=%u|G$P11H*;y|Nl1#F)$eX{QuuTn1Nxz&;S1`
zKplMs1_lvmB4exyVqmNgV3g)z=a|6AE&vjjVPIfL;Qjw!3#tZ0fy6;Hs(J<nQ1c6<
z{s+_l{|`V3k59mjPr{3zyPTte!CuN*%UA_mM1%CZFfcG&Q2PJh6l4$-gT!MP7#L=-
z|Nk!v5_9Ae2<GDeyRU?Sf#HGD|NpTdaVQ4a1!6K*1^9qn&dz*5$$^0x;(i7O9|nk<
zIF$eYKg7Vmz@nn-$l$~$(9h(=C(+03#HY~9;>xGd!|K9k(8lJ-XVJ{=!WY0)!^LOe
z$Y<clr{T({;KV24#3$gy$KlM)V8Xz_0J^K8L;3&zIMAhuPJ9AAOfGy9ZOo2*3e7Ao
zd<M)tj9h#Qj(if1U`>wPAoqdvnJ_XiY*73EKM#k#DLC|j4itK!`TzesW(EcpU5NY9
z?L2~}7i1skjMM;Ks6IpN`p%%~1BDmpmZ%*D|Nr;0F)*;0LiF`BdBVfX2^3zA@bGfy
zJHVI&4=*RA@N(p42w`AgSi;D_@Wbr?e`gj329_k4d5G{wGtY%@0VDGRCaflUfCCeB
zb2kG61L#iU4_5#G$AJnoCq98bCRaX*UQnF&usHH*w6Qw!88oxG@HsHma)IO05!G#=
zcmcW3g^7WI!{+~g(3!3*{rKGH0!beYjLb{1yAKlC3=H6OqQcC;@WA2!|81ZqJ7zjz
zdW@M&!09}OnStR2*Z==EAO||~2{eQ5Zf0O$04<mfaQ^@Q7)Ttie}i%Q7ZkssqY7_0
z|Njpv_tEnLMqD&7GCjf?6%en2(i!NE+8M6@|AV%&g3Lopn@k>9{0Y+M0xIL({{Q!a
z`UhEGAr~zFxN<wf*^b<xFsWf-V9;>;|9>l%cz1!vJCiRLp9XS_yMfXUs0=Y-VPII{
z{{R1fCI$wU$&mQ&1C<BRw1Y6ig)e}ahmniVz!}vjZ*EWsqX{~5Gh1L$3$~U4WbYal
z1_lGK|NlYbvn)GN>~-Z+Kuxm&OdVW&1}>=9x`C}l6ZGO{UJkMrO&DS=1E`E$!^*&L
z!sq}0N6>JF`W3@Y2c{yN=>`;EUsxF!3Vi?n2leo=#dA8Acm~Cn4jTglhu{DI*P!<I
zg2K9o*_}_J4U|utS>5>@n3^H6<p?WlT|lt~DZihvGBB+0`~QC)sQi8c3pXz;d7UX7
zVhW_hcH!gj;EqEL!W0GuhCOTy3_tw-{|D`#0r>;DE<j8F?tBgvSW~|%H`5BN63}o5
z<+C|#3=9|i|NnPoXJBA?0dY58yACii1z|PRksB0`CF~3g7sCGkPX*Phj(h@bOpbgK
z&CKq62F%wPA$i1+kHd|d=>ifb4bBFaogi~e*cljpME(E&kDGykMNH3;0c@5VFIzxA
zb014DYY$s9J5wMR4?Bk=H*-A)h~dG_oX7}bKyp0;gAM}&Lk*~HmG=L?x&Q+MOCk<)
ziXgf`>A-}8fnh@#fpkz0i9vWe0J|EJ4&JacFa(tU{|{PF$Z|msT95mH>Lph`g<cj!
z+(FA}2j=;VSi{qk8`Q=)!ok3>pz{BJ9#Hx0j@l*wn}ysaNT}npa6HUs;CPHr!|4p4
zg40<(38!;>0#4`oIGp$rn%UdfdRTi|`k4Dcrh)6dIiNOX_5c5D!VC;7A7Jiv1-Tc|
z?f}=L&U_Zl>|T5qn3yNCV|B1QBsW09;R^=?14rNg|J<PRYk@vA9I(}=&~Qj#X2l*3
z9^9aG>cYvuaH9YJf6)3^mIE;Jz~PGKPH_9koiBm;3wHAy!RE<uF);j?^#A`~X!+dF
z<N@-tD<4W6fz5K^OJG*UZWg2f0J-l8Cj-NZDgXbogZ%pi=03D=^aQm>JV4<HDzsTZ
zX1ahYedGp-3)p=oTnr2u)BgYe2r5rx44~oXh1-n|OlM)uL1fcF#Tm$bEnEx?64U?x
z7h?pqFNre`Yb-l*gWIby+zbparvLxH2p)dmxb=XCA4<Ap`he9eXo>;3PlcO-!DHtC
z|E?hS`M}%<Hq(_47AFWZJ^2!tPGdC_>b???d9(ii4+WW*ifJCWJq=FR;5a5|-VtsF
z1`DSD|II-0+kj~vA}>PIa{;q4D1IRgbmSO;q`x^l3=ABz|NnOYm6PDMJ|ZoH%|f`%
zg>M2Q^KLwCIZ*pBg@=LR$Grdlzwj_HuuO%y4`+MWi|+v^vnlp`46Pj)!1eMT9tMU5
ztN;IZ7hzyv`Dy?yhh2HuTtMZp2c#fo7GwvNpsw7^&d>sw`2`b51X2=%;+KV&fnmk|
z|Nr?x;g@0vP0#r92h)EpJ`2q30d22`@G>xD9Q^-3AJqQuftd$RFW~Y39EKjC{05F+
zA5a)xVZoYWTw&!FsNPuv>f;{&{~xrHkL4xA+&(6EEcLiIUjTCt6Sl?($iFpw3=9#c
z|Nq|y&mV~R^?>(HplKz5IShMRfn*Snc`kem3@6U~{|`DcjK#o+aQHdk4nI&mCj&ay
z@f>;e94NhA;bUOPVEq5zRfvIsB?8ku;P6MJVRya;W@b)atl<wyBn%7;Ab08TGcZgz
z_y0d=Q3wl<F*Ln7L(`jUFRK@*=4Va<Cogwyrn#VG<_w8Jkh^j~V-c7C|1Sl(OUoD<
zPH6c6QI2@=9bjY5;=%7OP`dCDU|{fg3hl>%+aeyIc8wR`21cfxTznGFd;*T3rd$fV
zDdWUl4;P0dF;G162rw`-eE;{Km6d^kWk1Ay(6+7@ynXEq>Jz&2ZD3)(1WI6NZFqQi
zfyN=82rw`>{Q37^4OGy(@Cn57ae(vf9{~o2A5Z`PPX&pC%NKB7bLE4LC4ln=xGwVL
zbI8G3b%4xw5oBOccn0o<fJ}np|Nj|T=Q1)eFn~^Lg<{QhOeu$W7?v_8tm0u<!^--T
zo1vdA{tyqtSx(kfJPdQWSV6KptYA%=QyG&FurqWsCER0Yn9RcZjh*2=OTqzmhP7<0
z_t+W!vauduXSm4@Q4F$0=`iC<Mh1r6%vonx8UC}VGcass6*<Go@Dvo3uUJ_@qW>Ay
z`xrqNIqYUiyT!_|h^2&qVKJ-7EmnrpAXQgcS#Pm2d<HS0PS*Ozm~w!R;VV<Z3O<HE
zEUe#o8Sb;jAK+tH%gwrikKr#j>j6H7MLcZ~kGfxH%IgznxW*j!P>kUgE9+@-hV5(#
zec}v_T&$DD8SZhjJ``g(&%@d$&hVFqwN;eiG%rZ}xBx2y!!%(KbBE|PMzHrYBUu?3
zt~2HBm0`HXoOV-|;SnqA0a=FW9C^)h4AVGc8s!*fakKuHW%$mW&@9KWUm#(x48ueb
z)<y+}d!nq3atwRKSZ~TQ^oz4L%Q4&)XWc8q&@I7wQ-)!q1S<nWljI?$Mg@i|GPa-y
z`_C-PC>jD1W>Hzj!nm7-^%)DpM;2BF&}t(F28RF4YZw_9Fy3WjUariro8vvpVMT^p
z0<6oG8BPhZ?p0wpE5ypc&@0TkRfS=t2y2%T!&?#7Rq_mbMOk|l7&eQ66mF7aJ)p#R
zT8eeDBI7rinM})-8J5a}6g4ZbGB9)~fK={NWWA@#(5eJ7VYd=U<r5{=UrG#Ll|gzh
zsIW3HTvY)Xc1s0h>jRL$Qx%Y{%T!q#)u4_AyH}P`#DkTAp_p|EGviKX*6Yj+kC@@!
zVffE%$fynq-`k9=7g!muv#_?ZF@9%deayx@myLBd8^bX+RtAQ9Y#?Dq1_scT#|;0O
zRT-uASs55&S1`3QGfre?oyN?3fSL6bGs9P?(GYe28Ci<$L1CuKC{@hLz|hCY`jL@w
z9V06PLn9Mw8xzAsCWK4CQ7sCZuGqoIx`T<~0we1zCWg%*761P;iuxxqFdSlJJ;B6q
zgOPO=6T?y_&}=s-u?tQBsaea!dX|~t7SlvXnu$@E%)-F1hlTZ!B*PR?B3{bMx<QiR
zJcxObeFNilNrvw{O`!WO_~WiiGBgRW-j`%JC*a4xuwIb$pCH30K`jP`e?qL+B^eG1
zvrZLd=oDq0FUIghbOH<5q1hVUj0_AbSlbzAsxf@xVSOpZ(8$aBQHtRJFNpn|m$h4+
zVFDj314BQ*;Y>A#oBW^zI$vM{GXuj_QPy`V4AaF~@5wPtlQ_=!PKDu()N~dGhIR$k
znQ9DY6j*;NGb~bK{iek5LK&2);o-f3sh62?CNt|iX66&jte=<}{-T9<WVY@r9)`J0
z2D5k=&N8$1@-RGSX6@i%IK*Pm%)@Y*)nFwz!*({wk6aA5*cLKw;bJ(!F@y0V7sGun
zRtAP8+^j3P8UAszHuEq{<zb!0!|(u906pbleZ>O`XlGqk1_mqEUJiz0oemC$$Ba7r
z*%_8F>;Gb7_{q%L!N$<W(!qFzmEi*@$$VvF{l&(xmYsD!JHtOv(bUYr+QEU8c98tC
zjhS^fGs7_yzc6oLWL(0ynQ;+Ymkh&OcGg?U3{$yTPe?Ot<7VBez_6Q#^@%*g0$z~F
zd0y5o8HNUa5PL2^>jF83`(XBKLDoYujAw;cGhS6@m>~L%sY`}owgl@uWrm#+tXGv8
zj!Us#R%Ey*#kxb0;fECKEJcQ?(yV_J7}iU(K2%^hEDbX6lQb&>L#vE!mkh%j8P+yA
zhVOD93w|hrtZY^RISbTk0TmsfAd&(d(7lbBbrUB;CtEZF!wn7)^D~D#IMGaCoWN|r
zz|hRfdRY!s=x>!{IKt|5S(f1w8|zAWhJEa;mt+_&u(N)XXK3I!%y>zL;XKz5rdM(d
zpZSDW$}=40XKhhtc*4(mM~UH%i2F_zhK-Wf8BZuMERts3slu>Nn)R|Q!&Pb4tuhRq
zGOT-*7|zOMFfcrj14%xWV`X5NFAuU{i9E=%D~hZq6hKbS0VOeW)_WWb*{pv#8LIrh
zu`zsOl=;iaFo%itD<{J{Ce~jZ4C|R$Cv!3!WM*B&2})9vIT_Bg$b4gC_{<{wi-Vz=
z4WzP<jrAov!&x@gz3dF@K~cVgo%I_V!&eTF0gpLBrh~(2lo$<x(GVC7fzc2c4S~@R
z7!85Z5Ey(R@IVu?-spn{ggyY}!(0k$^rF)z*dZ!l;+N3)@1cC~-b)4sh68dC^LZiK
z7)*pA>&7IZ{2R8A^=Q&iK4?29h$#gkKszF!^00Lypxw<NsjvV3=Y!}OOc3)y%Z)(%
z3)~Ps_&g$z05gOSS_TB-ih%YQGcYi4gE$Nf44~7JKuqx36%Yf8K}$ZsOb3Yjz>`D_
z3=9@f|AEGXK=M#7gE)x8z`)QU4zUj=9v}wc!`uzp+yqhzbq3u2fBzx=Ss)5g{~yYS
zx#I(r|3m~L4>E%u_y;un(e3&MjaN2#h<n7Kv>KE)gVJtLIt)suLFqCm-3FznLFr{s
zdK;8J2Boh->1R;-8<b{KfZ7kG)u6N)ly-yCVNf~^N|!<DHYhy}N-u-b+o1F@D18k|
zKZDZWpfsBz)P5+f2BpoQv>TKTgVJeGx(rIULFs8wdKr}72BnWd>1$B>8I=A8rP-9A
z_CskkC~XF%-Jo<Blum=vWl*{eN>78*%b@f&D18h{UxU)mp!7E=&87^sA4;o1X)`G8
z2BpKGbQ+W{gVJqKdK#2o2Bo(_X;8XzcXqZ?&~Wz))l@Liv(PhEFfuSSF|af=RL}@Y
zO;PYjEP;sYT52*d=oMGymLw)I=oOa~LFfz^D>JVowWxqWFE76&RnO7MQ@12B9m-43
zE7eOX&CE&B%}im?1MxBvi!&JXQY!O`D{~=qNfAT_x{Mcv6JNxjSCpEQ2+{y$737pK
z=w;?*mN4jn7Vt9YrDf)2GUyfMrzDmnGU%md#HSS{=BCDHq!b}|40`Fw$?>Tb$*Bb;
z@g<2#IjImc;uDLC5-TBQz}Q*IMPP>|=4K`{=%weEfC)XYy%0N+ii;WalJj$OQ}aOU
zSwLY)DF&6(pt2uc9zx4s3#b6BoQBy0QVU~)XwaY@sBDBPX9$7n4}l6mR^NkELNI7b
z5W<3xpld~t^_M{Pmq7J{)-ys>LP&J|p!th$|NrMhl{3KVj~P$_(0UV)PFQ^cE4M*n
zAnXUNJD|!L7@+kF1GJ#N0M!7he?WeRa^dBC7y|=XDNH}CzPbRdkYM!_$Sx2DnE|3<
zI1V)Ci{yS-eeeWYA%XVsfYid=53>hGXEQJ`fa)3;A6DNifL4H@4bLF`Aoqj31am(~
z41}vdbMOod@Lesi`YiyoOBGZ<LLCgVAEqBx&w_S6gW?2cKdip&fL5Te`W2)f)P_LU
z-^TzMsRy|a<Q`c2;(#28$G`w<H$Zp{pk3uKCYbI-)BgZkk3E3u2d{wztAr4+_6Urg
zf~Nlfw46EsO4y(^f1o>rAX>pBOdpuTz`!sIO+TzXWCFUMh=G9t)-Hgl2Za@k4WdE2
z7C}J_3qJ>Fd&&W-KL8dmAjP2lMo=0?f%dB->z^PAX^%~i1c@>*u)qw4YCyMt8`S+U
zWizB9`e#T(^!ErtH9{#^{6V=4piR*rl`#FF<-H&|6<v@h0|PfGbAtq+7~TF$3=9mQ
zCG4QI2~q-UKi;r~=tmE4bo=iyFfi~#^B_q73uyc11ynz}dUXCPsQ+R1e}L-$K%{<9
z@&H8<Oh2rB^#iIOma#x;K<<Ot5AzpjgD%LYaQ*g>_UR9MkSGHKOg%_12*da=8gyVC
zNF_`^1GIh10A5_jz|a7T7?5TLgn2OiY>bfn2h-o+1hK!t31li}enXEx9!7|nF#QXl
z`dgs-LHh)tR)Z<@`alqBKllV=unGtP(+^tb1`&af5LZFm3=#n)Png{xJ`8Vw+Ajsu
z0F^-Ff+yb4f(Wc~f;U8^0JIzf6(>eW;Rh@KK=~V+{R@&H`uCw#U=UHT6$A-p21W)8
zXnlyRn3;hIUeBV6Gc&;IPgHRhc)f@!&Wfn-kR+HH*cf2-7?LmpGXpyVtbRfj=U{-<
zJE-EE46ynHRh$dnuR;~)hW9T~#d#QD<vOZ3F9WQ+Miu9S_e)X5`59p4E2_8v1FYOc
z6&GZHm4~R}LJY8S4pm&30akvYii<G7$|Y2BQ3hCfgDNh@04qmO#l;zZK+6YIaR~-^
zzDJT^W{_ln<!2;e24)5+23S5t6$iBjkwqZ+jD_JN_|{%z8Bm$a#DEBYXmf+XAKaGU
zWeCuM^dmrP%0T)+hX;Yw!^S7TYdRPh7(hoJGBQXp^mstZ9a#PVmCM-d1s&1P$bcDc
zOpKs53@<|f+PH@}RJ;LATnj3WK28EEHbL&`Kywf1h-F3w%y=w=ngbhu0r?e#o51B7
zW_rNouZ3Xsf(#0}kop&71_-YQi(~p1B!?}0c7xSpriY8*fEC6_hmWA*=;`(=*c{As
z_>Yl+K><s;;$&iA5JXB>uq!#_nHU&^K#oPjc1#QmiVXY=uzh^6@-z~MdeB}IZ00;=
zVqj2W;AMdA!vpQn0a?Y&3=vNdgoGz}j|u|=gE}(~_uvo@0*mu97(m-)u(~1%YJS0C
zNID0vb!1>**vt&^R|9l^AozrS1_p*xQ1ugLL)3%TP=nmb&jNA}FM|YH`V<3;gH)hm
zYZi#V1P(#W0j&WAsi=mEGZa9?!E60N?qLDBM~dNqGbDe(>fdKj^$-3*)PvTPfy@ET
z>0=9LF;<W}K`KzOCM(1}u>F#tJsltwi8$0ZvVz>h&yWBOAK19zJRIuxL)F9fi^AGX
z*H}U65M&c7eu_i<Cl2#@*|7Ud3x{|Z4)F{e;xobGnDx<ousEvQLA;%8knn`<YX$8Q
z0EvUz(b(d}fE^N^u>Hc&?Su^O?2!EU02=QC;C#-&5XX+)zlAu&C$K}^^AVCxKx;=q
z?pY2MPe_J{!}=LFq2jRpu(1629V!mnp9|Wv05T_p17f~IB*Yw8J}u(F?#><@;>$Qd
z;ULAp09ug5z`y{iZ$ReX!=YZE6JjrV{%zpIZtrTS`U~+8cY^nnF)%P3!lC{uR6T5e
zF?g>c0|UbwPLMnK8DRT|VdWm^@H$ZZHbBQAL2d_O&<=4}o(1t?*ozC|eu38z_kh+4
zgXB`6;u~f`#9`&Y94_qsdI(jIUM{I|L(F$r05J!&hXQ1NGgKV5-x{_TbQL!!y&>9H
zuztc$Zjima3<A*h0B9X7$eg24bI{}cF;rY34Pq~NZyEyw13M4IJ+OV%pgmk5bKH19
z=7U(MxEP0cJJcN5K5fuiT#(L_Q1K6YA@+jy5`e@P@M14#cJVSW2w<rPkMn}!SBjwl
znhwFYrhp9M!|qNUK8U{-L_zEY?STX-_kfBgY=ww}_J)AO`=R0jRS<FTUL*zvhMhRf
ze*{$z+ush}`^vz;@DnQTkOwgbyf=-3fkB=h;{FFv_k(YHVPIeg1dH=C!1m3<%Hec=
zi2KpgVIx$$p%7v|s9gne|7xhXz<!80c#j1G1H%)jxWP||I4s>t2|&yb*bET|?YRM&
z6ABe)cnJ{)t-S|{M+#yO=Tbq4Iq2zXCJyyGq3SnWftU~4BLFi0wjcw8Ad@6R2eh9H
z5(DAqF!jeF>c4;nyBHW4ScR~=M-_*-wGb#DNijIMLF#K*xg8_~iWhzc2k7_?tiPO(
z!<^M1bC~!UJfQ6skQ+gG2h^Mc(E1nFKDms;oabP1UWNlgkaiS!%{2o9!*{4TuyZG%
z-DCzKVMsU^q(Z_0yeE}`fk784j-H-NpyCgp?H14;Ly$WIL?Hf!ol5~K8$se}A`tT*
z?1GpN+LHtl&lSZU&Zk5n<}jdz+XEcxzv2+*6vJ-5yconi=;=8Ehx%1e_2~KT0#v*J
zTJC}O)-o_Kyv1P-w>YT$kz!~;ONZj(5ci<xmmrup^t=mLeUu3mZ$PWx8^p2ucQ#Z#
zdOf&F92Cw{3<=QlNMPm0bFewQ3=?!9@desb0g5kU35Yw<+pF0U*zN7cA-)-E&Vd6^
z^P%mW&rtCXdm!SVy=EYLPfB7p|Ai#PJ?Q0+oD@VHc0LL0tOQ3X?DayJ6eJuDK*Ir6
zclS$Sw|5=XeDw1F6b|+8q3R#(fcOi%$B%)5!Co4>`N25E({YGzkcRm8LkPrt(3t_C
zaO0AJq_+vselbW4g!yH#+pCO2+(HHtJ`bSb18b)h;ZVO;2I3xrc@X#TKs#VJaj2J*
z#qJ&}9O4PGkn%PG>MwYF;ZT1Hs=fiL9yX48PZr`%*!eM_vo1j4BPj<FpO6G`Kltnb
z1_lN%IqdGtfT}mhho}ed^=4pTaFWMvPBBy*y}a#{$8P>2sQQHO5c5HMu0ZZwssM^#
zNsRJjjRM3R(4ih6bufGuDh}hr=tl|=cf!u~0i9t1k~2_*m;+)XV`m)V@rn?07JP-c
z6TH`qfq`KW4)wcmh~K~=&aMP8AHAJyuY}#55m5E${hclx>eu2BKZ!&91rG6FIK+9B
zvAfe-8R9S4IZB|t@1XErf<yfdusAQn1L(XLXukzW{a&a!=;aKr3dml}aav&&h`s3J
zFot0DAQMq>HB>!%{4yjLrNx&drl+SC=_MyK#76~}<P>`bmlzr*XCxNImlP#tmK1v?
zySfGimlzsDj<d@z2A$@X>zN!9?;30xAK;y=SCCj#qGxEt5FZ6Qkjlp*BtJVfFS9Du
zHOM=-I4HQp404d3XK-<FqGxcit81`fJVU&@kH3?nPkelFX;Qpre0-FlQAuKYd~s@C
zNornlY7opgbC=ZQ{FKxp*8soZctf<qu7bR=m=5-fk41cZW<GcoL2^+^aY<=fnqD$P
ze7u))P`r<4a7cVSLrQ8<YI<gINorAiNp5^{PJUi$F#{H%`1q9k`1G9oq{N)~l#=|S
z;`qeU3I@<QaXG0asVRD9#>VChSQW*CRxUtR9mJ>Rl@wJnKvpiKl;-AEGQ>mIF@SmT
zAg7`zjgNN=a`bhLclC3LkB7P(%7zA=znfdIYe;;Eqmz#-NCk=!DGc$*W)_zu7L_o>
zyF~gq`g%Guq$HLk#zV!Tf=i5(GxHoA+<iQqoFgLQjrB}G@flof208A~HNZPL7hO8U
z6Ba{gC!T>)l954td`U)8etCROYF>ItMto{fQGQW?cQU#u!6oMDsU@Hz^gNR>(_U~Q
zS^~_3Ccu#R5QBKvVDtE(;3UsrllWlM_z=T**I<))bPM2)L`^v^My_QbKSB?ELrYgC
zpkoxlsTZUsI6tS@H51j~+|=CS)Doyzypf)fo+(3od`@Oka(r@eDLCH2TH$FQ;ST2@
zw}5~U=t-8Ypd4Ws@9Ju38Sfhp7EDf!PfASA1|>32&tQ<8Yne-de@Hw-d{jsn%p9a+
z>f)pPka9u1XJ&AsXQnFwlf8nxQO!mq217Gg<RvAh#3v^fmy|(0ibxx<a5HmDO)M!b
zN_9`oOD#$)$uF`fE=^0ztVk^e2VW+(+>aVCX~Bt}X}J9F737`f>Wb|D;1WwnS(KSy
zj4jEy;&LkNoIMjn+$QJb7pFqiL*p<pDZdCkTBH2Dq27g=VQ7MSY^5u*KcT@7@ufv(
zesO$KVsWaokt;?hh6KZ66D|F@f;AUA8-jCQkT*CVKphH?14ITyP7Cqrx%qjJoDC{9
zyvpK(poN@sPGWI!YB586R7y?&hFkoCi_MEti&KlrQe83fx?gayNqkUhfuRBDDAdg2
zg8X7|;SS24sLo2Q$ixzS!6l~2pu;aclU)ss<9*}HGgDIYz@-yfpy(MIGC(s=d~!u%
zd{HX66mf8Hj)-t{@{BjsGX@0>G&)cU9+2yk^NYalg6Fzm!+4}xC($$5INlYMFbxs8
z#0iwI{DMp1F=gVJms#R$h~_d-=7UEUa)3t#mzX4{78Rj}LmKF8R;Um(X+etza6XIA
zO@tS?Fll%N3QJ`mH-LPL$lQqJg1r!NF~nD@xfr0zA|F&~nVOqf3_4>IoNq}8zaUT$
zrvxW@rnrJrEi7t`QCtWsZi7K-8q_L)1`RARgCje!1Y7C}2}86$(lT>WlS<Qw%lE`-
zGXX^uD4T(l$9v`%$NLwg=7A2jMROu@AxkJ8uovcDs0D6Ra0#NC%r7m8&rgdlO3X`7
z#ZpbeEHn?U%uUMADF%mUT6$_pG1Qef6A@Ttd=#Rvz*^LxrG8L4f*K5qA<q!dA;_@S
zQHZZIsJRpZUT2AD`oY?2&W4~&1}bWhY9?s8h$C!*yfcvtda@d$Xii3sv0y}oPlQye
z*xliYUM(VWCA1xCXp{nOkOqT~b_V&AKyC>(jt}t0(ss=R<!xAYLC%YY1|>y_dBvHa
z<A7Z=Ga2GN{o{*EQZn=6ON&!e7~-Q$!N*w#c!OH)u#{nlmf@f!0ix`{3;|FqgWLeC
z-Oy47v6Y;6aIv9zT4r8Kd|GBvaY=k$J}4=JvQ#cwMN(W+l#>c;XChK8vG#+?4W#x2
zG(CZQ5L{vrAD@z1l9-ue8Sk4|0IqRC1%HW2L1Jc+r+091Cb&Ih9PbK>34{3H)ST4h
z62syW*WeP1)WXulocNM_P~RlD#00c}8N>vsw21f3Pl061c#wEzUOLDGBZGJs=!xLq
zESmzBgvtaIW#*>F7o~#K#~0-1WF}XFWDL#YJravEz-yCX-7B9YLsy?916L633L!Hq
zT!Vs*;u+#WISzY%i$`g_g9}I#Xrm|%+CawAn8s`wq15WB#U(}gm7w&DS*s&*I7*cZ
zOF$@z4AIFWHKQSnfz~I6Ch@-UX_+}W@rlL7ndy11xk28b^zR2sa>htWE<X)haznTp
zR-?jdK!i=fXzc?~a~oD<hWQy9#K$LBR3s*4mKhqF$Hymwno#kemP~wRURu5@TDt^M
z$igf{t`gyeqY)^O(F#YHkIg|1ENIGtcD`~UDIB8{jlBT#0`;#E4Q8UtUzE}pNf$gi
zaaae+l*k3QUvP<`0V1J$W(LFZU}kVJI1d_u@*pC^8sf@~nXa(BnCa>lY!YvXJujNX
z<IId;DeSq?Bp!Qq1Z71KHgt7$bu}`K_l?iYD^4vci7(B|EG$ip&&y9qb<NEU06Pfc
zPq3J0uu(iHBSRWN-oeEtpycU_*=<G+T8uggn({#D+Z;5$kXV!oO4u0n8n#*ksb3Qx
zAL8o_8#jPgVDLsOvdSnwaFZ;+I~i*)15}!TdhSpcz{_k<n>W7{*3yTy5s<p4XmtrV
z9|V^e5>rfpN)@PmFoWY$iu2<$67y1WQbFx-cqbU8G;(DCZCA<9f$D|1711dN4Ud4d
z!<+GF+EM$_u(2vwoZv4raOVzCiGq?JQL;yHF+6|3>L_CK2PiusS5|b&A;=Y$D<qYJ
z`T!^ai&Ffd6$nGz$0U~tpppbUA_*E+fexTT3l*&W7%aoB(9tfl_yF%<<9JvChhz>t
zOHjzea|9yfb5nD3^UKhNH;PM&k~3gkRd^0bs{maYftGJjTF<b|fIXRm8cRrnG=2of
zXiPv^(KR5r#LyJdZUL1uiMf!jonbtdmI|Vcjjh7QXzW7UhKa$TsuncZ3>tvU%a6~=
zPfi3itI_-#pHy5788acNt_5`#icu;*Xu%AN1yE52G7(J}G!_IB4$e1C%PB3+z#M9T
zwd&#hJ%|rOP@5#6?qhK#p;ies$JLQ)FTCS+o@nEC$m1)Z0u>tmkR%KaiV{Pkcu=!F
zGa1z4OZ7~41qB|4I%r55Vvai&6r~b1?l`P^3jV>x;Ls~JgSL>djnpGW8Z=+v$i}E`
zKxk0In++J<()fasjH1-Ul=$q_%J}4>)Wi~S?Hod6J&zH$p!o(!J0H}j#E^!%2c^;m
zr6ow=Y6xy~#wS&lq!tHwmw~#Th%rOZ;5>492FqTCMzC>s&yv(!aO)IQUE>HIl;NA)
z)ZFCU0;nUQC24MHUTz|q7`RbEV944uIVUx-$Tb+02S6iGp825i7*=#6G6|xij54@p
zXq;G3kW(3w@0<f_qk@V&L~%)Q05p>!9(1Es8kWKE;*z4g<bq06Pl6^Puov8zQ-@HU
z2p@n3*&ro8zM(wOz$X^1m^~m|y(Mp?0Rba}cr0B2Lr~K`FSVj1J}J8dZNOX4(1HQf
z?@I&)Onh=;Npc2SM=!Vp)Efj3O~-@gND^~$@{_R*se?z&jSS+0K~p;=mGMQX1@Xlt
z`9+E8spJL@X0cdYl7bk2h9<>$A|@?BxeV8M3M{M%<P=E$1CQP&rKV@*VGS+xF5wVw
z0YY;tc={00DIgS3kO~~BD28{eK@rMO0G=R)CVE61qP3loI@6HxEMof#L$)uS3L5HS
zh>uUNsE98}Eh^5>OU%hEsf;f(K*^G61x1;8C21Jp2dy*Ubr?mHM0w?C<C&nc9X6o>
zs&LTf&k?0CMo%Am6@xZI5SmvG8deQTEiTOk)dtSa@y2=v;0nLQ4Ahj(Nd+YysIL*F
ztT(tOL2XT$XBNkUr#i3{Q*d|UZyulx(fbAG8yA$8xMIxdBPDWpu0S1~hS#)^@&hyo
zVQ3WZ8=sS!m>r*ySd@aLMI2mgQe2)`;2IPRoAW?;7}RnAU(5)q`_QC8o`!}HG+%)(
zqeB$|MI?NY3%;7k6kJPUaUFU>qt1L9ct9fs)vchJLue9!E~|kDGAXSYw4?^=kbz8v
w`U7qF29(M`vP9IYpds75)YKH{__u2=TE+lZuE=dM%y|jyc>^+Q0~<*J0Pg6*$N&HU

literal 0
HcmV?d00001

diff --git a/maca_crf_tagger/src/crf_tagger.cc b/maca_crf_tagger/src/crf_tagger.cc
new file mode 100644
index 0000000..a9fba5a
--- /dev/null
+++ b/maca_crf_tagger/src/crf_tagger.cc
@@ -0,0 +1,60 @@
+#include <vector>
+#include "crf_decoder.hh"
+#include "crf_binlexicon.hh"
+#include "crf_features.hh"
+
+void tag_sentence(macaon::Decoder& decoder, macaon::BinaryLexicon* lexicon, const std::vector<std::string>& words) {
+
+    std::vector<std::vector<std::string> > features;
+    for(size_t i = 0; i < words.size(); i++) {
+        std::vector<std::string> word_features;
+        macaon::FeatureGenerator::get_pos_features(words[i], word_features);
+        features.push_back(word_features);
+        /*for(size_t j = 0; j < word_features.size(); j++) std::cout << word_features[j] << " ";
+        std::cout << "\n";*/
+    }
+    std::vector<std::string> tagged;
+    decoder.decodeString(features, tagged, lexicon);
+    for(size_t i = 0; i < tagged.size(); i++) {
+        if(i > 0) std::cout << " ";
+        std::cout << words[i] << "/" << tagged[i];
+    }
+    std::cout << "\n";
+}
+
+void usage(const char* argv0) {
+    std::cerr << "usage: " << argv0 << " <model> [lexicon]\n";
+    exit(1);
+}
+
+int main(int argc, char** argv) {
+    std::string modelName = "";
+    std::string lexiconName = "";
+
+    for(int i = 1; i < argc; i++) {
+        std::string arg = argv[i];
+        if(arg == "-h" || arg == "--help") {
+            usage(argv[0]);
+        } else if(modelName == "") {
+            modelName = arg;
+        } else if(lexiconName =="") {
+            lexiconName = arg;
+        } else {
+            usage(argv[0]);
+        }
+    }
+    if(modelName == "") usage(argv[0]);
+
+    macaon::Decoder decoder(modelName);
+    macaon::BinaryLexicon *lexicon = NULL;
+    if(lexiconName != "") lexicon = new macaon::BinaryLexicon(lexiconName, decoder.getTagset());
+
+    std::string line;
+    while(std::getline(std::cin, line)) {
+        std::vector<std::string> words;
+        macaon::Tokenize(line, words, " ");
+        tag_sentence(decoder, lexicon, words);
+    }
+    if(lexicon) delete lexicon;
+    return 0;
+}
diff --git a/maca_crf_tagger/src/crf_template.hh b/maca_crf_tagger/src/crf_template.hh
new file mode 100644
index 0000000..7307f14
--- /dev/null
+++ b/maca_crf_tagger/src/crf_template.hh
@@ -0,0 +1,146 @@
+#pragma once
+#include <vector>
+#include <string>
+#include <iostream>
+#include <sstream>
+#include <string.h>
+#include <stdlib.h>
+#include <algorithm>
+
+namespace macaon {
+    // from http://www.jb.man.ac.uk/~slowe/cpp/itoa.html
+    static std::string number_to_string(const int value) {
+        const int base = 10;
+        std::string buf;
+        buf.reserve(35);
+        int quotient = value;
+        do {
+            buf += "0123456789abcdef"[ abs( quotient % base ) ];
+            quotient /= base;
+        } while (quotient);
+        if (value < 0) buf += '-';
+        reverse( buf.begin(), buf.end() );
+        return buf;
+    }
+
+    struct TemplateItem {
+        int line;
+        int column;
+        std::string prefix;
+        TemplateItem(const int _line, const int _column, const std::string &_prefix) : line(_line), column(_column), prefix(_prefix) { }
+        //friend std::ostream &operator<<(std::ostream &, const TemplateItem & );
+    };
+
+    struct CRFPPTemplate {
+        enum TemplateType {
+            UNIGRAM,
+            BIGRAM,
+        };
+        std::string text;
+        TemplateType type;
+        int size;
+        std::string suffix;
+        std::vector<TemplateItem> items;
+        CRFPPTemplate() {}
+        CRFPPTemplate(const char* input) { read(input); }
+        friend std::ostream &operator<<(std::ostream &, const CRFPPTemplate & );
+
+        std::string apply(const std::vector<std::vector<std::string> > &clique, int offset) const {
+            std::ostringstream output;
+            for(std::vector<TemplateItem>::const_iterator i = items.begin(); i != items.end(); i++) {
+                output << i->prefix;
+                int column = i->column;
+                int line = i->line + offset;
+                if(line >= 0 && line < (int) clique.size()) {
+                    if(column >= 0 && column < (int) clique[line].size()) {
+                        output << clique[line][column];
+                    } else {
+                        std::cerr << "ERROR: invalid column " << column << " in template \"" << text << "\"\n";
+                        return "";
+                    }
+                } else {
+                    output << "_B";
+                    output << number_to_string(line);
+                }
+            }
+            output << suffix;
+            return output.str();
+        }
+
+        std::string applyToClique(const std::vector<std::vector<std::string> > &features, const std::vector<int> &clique, int offset) const {
+            std::string output;
+            for(std::vector<TemplateItem>::const_iterator i = items.begin(); i != items.end(); i++) {
+                output += i->prefix;
+                int column = i->column;
+                int line = i->line;
+                if(line + offset >= 0 && line + offset < (int) clique.size() && clique[line + offset] >=0) {
+                    if(column >= 0 && column < (int) features[clique[line + offset]].size()) {
+                        output += features[clique[line + offset]][column];
+                    } else {
+                        std::cerr << "ERROR: invalid column " << column << " in template \"" << text << "\"\n";
+                        return "";
+                    }
+                } else {
+                    output += "_B";
+                    output += number_to_string(line);
+                }
+            }
+            output += suffix;
+            return output;
+        }
+
+        void read(const char* input) {
+            text = input;
+            size = 0;
+            const char* current = input;
+            const char* gap_start = NULL, *gap_end = NULL, *line_start = NULL, *column_start = NULL;
+            int state = 0;
+            gap_start = current;
+            /* template is a succession of %x[-?\d+,\d+] which must be replaced by corresponding 
+             * features at the given line, column relative to the current example.
+             * They are parsed with a rudimentary state machine, and stored in the template.
+             */
+            if(*current == 'U') type = UNIGRAM;
+            else if(*current == 'B') type = BIGRAM;
+            else {
+                std::cerr << "ERROR: unexpected template type \"" << input << "\"\n";
+                return;
+            }
+            while(*current != '\0') {
+                if(state == 0 && *current == '%') { state ++; gap_end = current; }
+                else if(state == 1 && *current == 'x') { state ++; }
+                else if(state == 2 && *current == '[') state ++;
+                else if(state == 3 && (*current == '-' || (*current >= '0' && *current <= '9'))) { state ++; line_start = current; }
+                else if(state == 4 && (*current >= '0' && *current <= '9'));
+                else if(state == 4 && *current == ',') { state ++; }
+                else if(state == 5 && (*current >= '0' && *current <= '9')) { state ++; column_start = current; }
+                else if(state == 6 && (*current >= '0' && *current <= '9'));
+                else if(state == 6 && *current == ']') {
+                    state = 0;
+					std::string gap = std::string(gap_start, gap_end - gap_start);
+                    int column = strtol(column_start, NULL, 10);
+                    int line = strtol(line_start, NULL, 10);
+                    items.push_back(TemplateItem(line, column, gap));
+                    size++;
+                    gap_start = current + 1;
+                } else state = 0;
+                current ++;
+            }
+            suffix = gap_start; // add trailing text
+        }
+    };
+
+    /*std::ostream &operator<<(std::ostream &output, const macaon::TemplateItem &item) {
+        output << item.prefix << "%x[" << item.line << "," << item.column << "]";
+        return output;
+    }
+
+    std::ostream &operator<<(std::ostream &output, const macaon::CRFPPTemplate &featureTemplate) {
+        for(std::vector<macaon::TemplateItem>::const_iterator i = featureTemplate.items.begin(); i != featureTemplate.items.end(); i++) {
+            output << (*i);
+        }
+        output << featureTemplate.suffix;
+        return output;
+    }*/
+
+}
diff --git a/maca_crf_tagger/src/crf_utils.hh b/maca_crf_tagger/src/crf_utils.hh
new file mode 100644
index 0000000..8a33ab7
--- /dev/null
+++ b/maca_crf_tagger/src/crf_utils.hh
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <string>
+#include <vector>
+
+#define int64 int
+
+namespace macaon {
+    class Symbols {
+        protected:
+            std::string name;
+            std::unordered_map<std::string, int> word2int;
+            std::unordered_map<int, std::string> int2word;
+        public:
+            Symbols(std::string _name) : name(_name) {}
+            int AddSymbol(const std::string& symbol, int value = -1) {
+                if(value == -1) value = word2int.size();
+                word2int[symbol] = value;
+                int2word[value] = symbol;
+                return value;
+            }
+            int Find(const std::string& word) const {
+                std::unordered_map<std::string, int>::const_iterator found = word2int.find(word);
+                if(found != word2int.end()) return found->second;
+                return -1;
+            }
+            const std::string Find(const int64 id) const {
+                std::unordered_map<int, std::string>::const_iterator found = int2word.find(id);
+                if(found != int2word.end()) return found->second;
+                return "";
+            }
+            int NumSymbols() const {
+                return word2int.size();
+            }
+            friend class SymbolsIterator;
+    };
+    class SymbolsIterator {
+            const Symbols& symbols;
+            std::unordered_map<std::string, int>::const_iterator iter;
+        public:
+            SymbolsIterator(const Symbols& _symbols) : symbols(_symbols) { 
+                iter = symbols.word2int.begin();
+            }
+            bool Done() { 
+                return iter == symbols.word2int.end();
+            }
+            void Next() {
+                iter++;
+            }
+            const std::string Symbol() {
+                return iter->first;
+            }
+            int Value() {
+                return iter->second;
+            }
+    };
+
+    // http://www.oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html
+    static void Tokenize(const std::string& str, std::vector<std::string>& tokens, const std::string& delimiters = " ", bool strict = false)
+    {
+        std::string::size_type lastPos = str.find_first_not_of(delimiters, 0);
+        std::string::size_type pos     = str.find_first_of(delimiters, lastPos);
+        tokens.clear();
+        while (std::string::npos != pos || std::string::npos != lastPos)
+        {
+            tokens.push_back(str.substr(lastPos, pos - lastPos));
+            if(strict) {
+                if(pos == std::string::npos) break;
+                lastPos = pos + 1;
+            } else lastPos = str.find_first_not_of(delimiters, pos);
+            pos = str.find_first_of(delimiters, lastPos);
+        }
+    }
+
+}
diff --git a/maca_crf_tagger/src/lemmatizer.cc b/maca_crf_tagger/src/lemmatizer.cc
new file mode 100644
index 0000000..70c019f
--- /dev/null
+++ b/maca_crf_tagger/src/lemmatizer.cc
@@ -0,0 +1,19 @@
+#include "lemmatizer.h"
+
+int main(int argc, char** argv) {
+    if(argc != 2) {
+        std::cerr << "usage: " << argv[0] << " <fplm-dictionary>\n";
+        return 1;
+    }
+    macaon::Lemmatizer lemmatizer(argv[1]);
+    std::string line;
+    while(std::getline(std::cin, line)) {
+        std::vector<std::string> tokens;
+        macaon::Tokenize(line, tokens, " ");
+        for(size_t i = 0; i < tokens.size(); i++) {
+            if(i > 0) std::cout << " ";
+            std::cout << lemmatizer.lemmatize(tokens[i]);
+        }
+        std::cout << "\n";
+    }
+}
diff --git a/maca_crf_tagger/src/lemmatizer.h b/maca_crf_tagger/src/lemmatizer.h
new file mode 100644
index 0000000..5966c7e
--- /dev/null
+++ b/maca_crf_tagger/src/lemmatizer.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include <fstream>
+#include <iostream>
+
+#include "crf_utils.hh"
+
+namespace macaon {
+    class Lemmatizer {
+        std::unordered_map<std::string, std::string> dictionary;
+        public:
+            Lemmatizer(const std::string& filename) {
+                std::ifstream input(filename);
+                if(input) {
+                    std::string line;
+                    int line_num = 1;
+                    while(std::getline(input, line)) {
+                        std::vector<std::string> tokens;
+                        macaon::Tokenize(line, tokens, "\t", true);
+                        if(tokens.size() != 4) {
+                            std::cerr << "ERROR: unexpected input in " << filename << ", line " << line_num << ": \"" << line << "\"\n";
+                            break;
+                        }
+                        std::string word = tokens[0];
+                        std::string tag = tokens[1];
+                        std::string lemma = tokens[2];
+                        std::string morpho = tokens[3];
+                        dictionary[word + "/" + tag] = lemma;
+                        line_num ++;
+                    }
+                } else {
+                    std::cerr << "ERROR: loading " << filename << "\n";
+                }
+            }
+            std::string lemmatize(const std::string& word, const std::string& tag) const {
+                std::string key = word + "/" + tag;
+                return lemmatize(key);
+            }
+            std::string lemmatize(const std::string& word_tag) const {
+                std::unordered_map<std::string, std::string>::const_iterator found = dictionary.find(word_tag);
+                if(found != dictionary.end()) {
+                    return found->second;
+                }
+                return word_tag.substr(0, word_tag.rfind('/'));
+            }
+    };
+}
+
diff --git a/maca_crf_tagger/src/maca_crf_convert_binlexicon.cc b/maca_crf_tagger/src/maca_crf_convert_binlexicon.cc
new file mode 100644
index 0000000..4704208
--- /dev/null
+++ b/maca_crf_tagger/src/maca_crf_convert_binlexicon.cc
@@ -0,0 +1,46 @@
+#include "crf_decoder.hh"
+#include "crf_binlexicon.hh"
+
+int main(int argc, char** argv) {
+    if(argc != 4 && argc != 3) {
+        std::cerr << "convert: " << argv[0] << " <crf-model> <lexicon.in> <lexicon.out>\n";
+        std::cerr << "test: cat <text-lexicon> | " << argv[0] << " <crf-model> <bin-lexicon>\n";
+        return 1;
+    }
+    if(argc == 4) {
+        macaon::Decoder decoder(argv[1]);
+        macaon::BinaryLexicon lexicon(argv[2], decoder.getTagset());
+        lexicon.Write(argv[3]);
+    } else if(argc == 3) {
+        macaon::Decoder decoder(argv[1]);
+        macaon::BinaryLexicon lexicon(argv[2], decoder.getTagset());
+        std::string line;
+        int line_num = 0;
+        while(std::getline(std::cin, line)) {
+            line_num ++;
+            std::vector<int64> tags;
+            std::vector<std::string> tokens;
+            macaon::Tokenize(line, tokens, "\t ");
+            if(lexicon.GetTagsForWord(tokens[0], tags) == false) {
+                std::cerr << "WARNING: word not found \"" << tokens[0] << "\", using all tags\n";
+            }
+            if(tags.size() != tokens.size() - 1) {
+                std::cerr << "ERROR: wrong number of tags for entry " << line_num << "\n";
+                std::cerr << "    TXT: " << line << "\n";
+                std::cerr << "    BIN: " << tokens[0];
+                for(size_t i = 0; i < tags.size(); i++) {
+                    std::cerr << " " << decoder.getTagset()->Find(tags[i]);
+                }
+                std::cerr << "\n";
+            } else {
+                for(size_t i = 0; i < tags.size(); i++) {
+                    if(decoder.getTagset()->Find(tags[i]) != tokens[i + 1]) {
+                        std::cerr << "ERROR: wrong tag \"" << tokens[i + 1] << "\" => \"" << decoder.getTagset()->Find(tags[i]) << "\", entry " << line_num << "\n";
+                    }
+                }
+            }
+        }
+    }
+    return 0;
+}
+
diff --git a/maca_crf_tagger/src/maca_crf_convert_binmodel.cc b/maca_crf_tagger/src/maca_crf_convert_binmodel.cc
new file mode 100644
index 0000000..4c6cc55
--- /dev/null
+++ b/maca_crf_tagger/src/maca_crf_convert_binmodel.cc
@@ -0,0 +1,23 @@
+#include "crf_binmodel.hh"
+
+int main(int argc, char** argv) {
+    if(argc != 3 && argc != 2) {
+        std::cerr << "usage: " << argv[0] << " <from> <to> or <binmodel>\n";
+        return 1;
+    }
+    macaon::BinaryModel model;
+    if(argc == 3) {
+        model.Convert(argv[1], argv[2]);
+    } else {
+        model.Load(argv[1]);
+        std::vector<double> weights;
+        model.GetWeights("U18=a/jamais", weights);
+        for(size_t i = 0; i < weights.size(); i++) {
+            std::cout << weights[i] << " ";
+        }
+        std::cout << "\n";
+        //model.Dump();
+    }
+    return 0;
+}
+
diff --git a/maca_crf_tagger/src/maca_crf_tagger_main.cc b/maca_crf_tagger/src/maca_crf_tagger_main.cc
new file mode 100644
index 0000000..0371581
--- /dev/null
+++ b/maca_crf_tagger/src/maca_crf_tagger_main.cc
@@ -0,0 +1,259 @@
+/***************************************************************************
+    Copyright (C) 2011 by xxx <xxx@lif.univ-mrs.fr>
+    This file is part of maca_crf_tagger.
+
+    Maca_crf_tagger is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Maca_crf_tagger is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with maca_crf_tagger. If not, see <http://www.gnu.org/licenses/>.
+**************************************************************************/
+
+#include "maca_crf_tagger.hh"
+#include "crf_decoder.hh"
+#include "crf_features.hh"
+#include "crf_lexicon.hh"
+#include "crf_tclexdet.hh"
+
+void crf_tagger(fst::StdVectorFst &input, maca_crf_tagger_ctx *ctx, bool debug=false)
+{
+    if(debug) input.Write("debug.crf_tagger.input");
+    gfsmStateId start;
+    maca_ht_structure * ht = ctx->ms->xml_nodes_ht;
+    xmlNodePtr seg;
+    char *tokens = NULL;
+    std::vector<std::vector<std::string> >features;
+    std::vector<int>ilabels;
+    fst::StdVectorFst output;
+    if(ctx->model_filename == NULL) {
+        std::cerr << "ERROR: crf_tagger model file not specified, exiting\n";
+        exit(1); // ERROR
+    }
+    if(ctx->lexicon_filename == NULL) {
+        std::cerr << "ERROR: crf_tagger lexicon file not specified, exiting\n";
+        exit(1); // ERROR
+    }
+    fst::SymbolTable inputSymbols("words");
+    inputSymbols.AddSymbol("<eps>", 0);
+
+    // extract features
+    for(start=0; start < input.NumStates(); start++){
+        for(fst::MutableArcIterator<fst::StdVectorFst> aiter(&input, start); !aiter.Done(); aiter.Next()) {
+            const fst::StdArc &arc = aiter.Value();
+            seg = (xmlNodePtr)maca_ht_index2adr(ht, arc.ilabel);
+            tokens = maca_sentence_get_segment_tokens_value(ctx->ms, seg);
+            ilabels.push_back(arc.ilabel);
+            inputSymbols.AddSymbol(tokens, ilabels.size());
+            aiter.SetValue(fst::StdArc(ilabels.size(), arc.olabel, arc.weight, arc.nextstate));
+            std::vector<std::string>word_features;
+            macaon::FeatureGenerator::get_pos_features(tokens, word_features);
+            features.push_back(word_features);
+            free(tokens);
+        }
+    }
+    if(debug) input.Write("debug.crf_tagger.features");
+
+    int64 isString = input.Properties(fst::kString, true);
+    if(isString & fst::kString && ctx->n == 1) {
+        if(ctx->verbose_flag > 0) std::cerr << "INFO: using linear tagger\n";
+        // faster pipeline for linear automata
+        std::vector<std::string> tags;
+        ctx->decoder->decodeString(features, tags, ctx->lexicon);
+        output.AddState();
+        output.SetStart(0);
+        for(int64 state = 0; state < input.NumStates() - 1; state++){
+            const fst::StdArc &arc = fst::ArcIterator<fst::StdVectorFst>(input, state).Value();
+            output.AddState();
+            output.AddArc(state, fst::StdArc(ilabels[arc.ilabel-1], ctx->tag_mapping[ctx->decoder->getTagset()->Find(tags[state])], arc.weight, state + 1));
+        }
+        output.SetFinal(output.NumStates() - 1, 0);
+        input = output;
+
+    } else {
+        // add possible tag labels
+        input.SetInputSymbols(&inputSymbols);
+        ctx->lexicon->AddTags(input);
+        if(debug) input.Write("debug.crf_tagger.tags");
+
+        // rescore with CRF
+        ctx->decoder->decode(features, input, output, true);
+        if(debug) output.Write("debug.crf_tagger.decoded");
+
+        // convert to macaon
+        fst::RmEpsilon(&output);
+
+        input = output;
+        for(start=0; start < input.NumStates(); start++){
+            for(fst::MutableArcIterator<fst::StdVectorFst> aiter(&input, start); !aiter.Done(); aiter.Next()) {
+                const fst::StdArc &arc = aiter.Value();
+                aiter.SetValue(fst::StdArc(ilabels[arc.ilabel-1], ctx->tag_mapping[arc.olabel], arc.weight, arc.nextstate));
+            }
+        }
+    }
+    if(debug) input.Write("debug.crf_tagger.output");
+}
+
+void traverse_segments(maca_section *section, maca_crf_tagger_ctx *ctx)
+{
+    xmlNodePtr segs = section->xml_node_segs;	
+    xmlNodePtr seg;
+    xmlChar *ulex_id;
+    maca_ht_structure * ht = ctx->ms->xml_nodes_ht;
+    int n;
+    int index;
+    char *tokens = NULL;
+
+
+    for(seg=segs->children, n=0; seg ; seg=seg->next, n++)
+    {
+        ulex_id = xmlGetProp(seg, BAD_CAST "id");
+        index = maca_ht_adr2index(ht, seg);
+        tokens = maca_sentence_get_segment_tokens_value(ctx->ms, seg);
+        fprintf(stderr, "index = %d n = %d id = %s tokens = %s\n", index, n, ulex_id, tokens);
+    }
+}
+
+void traverse_automaton(gfsmAutomaton *a, maca_crf_tagger_ctx *ctx)
+{
+    gfsmStateId i;
+    gfsmArcIter ai;
+    gfsmArc *t;
+    xmlNodePtr n;
+    maca_ht_xmlnode *ht = ctx->ms->xml_nodes_ht;
+    xmlChar *ulex_id;
+    xmlChar *ulex_lex_id;
+
+    for(i=0; i<gfsm_automaton_n_states(a); i++){
+        for (gfsm_arciter_open_ptr(&ai,a,gfsm_automaton_find_state(a, i)); gfsm_arciter_ok(&ai); gfsm_arciter_next(&ai)){
+            t = gfsm_arciter_arc(&ai);
+            n = (xmlNodePtr)maca_ht_index2adr(ht, gfsm_arc_lower(t));
+            ulex_id = xmlGetProp(n, BAD_CAST "id");
+            ulex_lex_id = xmlGetProp(n, BAD_CAST "lex_id");
+            fprintf(stderr,"index = %d ulex id = %s lex_id = %s\n",gfsm_arc_lower(t), ulex_id, ulex_lex_id);
+        }
+    }
+    /* creation de segment et d'une section */
+}
+
+
+maca_section *create_morpho_section(gfsmAutomaton *a, maca_crf_tagger_ctx *ctx)
+{
+    gfsmStateId i;
+    gfsmArc *t;
+    gfsmArcIter ai;  
+    maca_ht_structure * ht = ctx->ms->xml_nodes_ht;
+    char id_pos[500];
+    xmlNodePtr posNode = NULL;
+    xmlNodePtr lexNode = NULL;
+    maca_section * section = NULL;
+    GHashTable* segments_created = g_hash_table_new_full(g_str_hash, g_str_equal,free, NULL);
+    char * prefix_id;
+    char * temp;
+
+    //section = maca_section_create_section(MACA_POSS_SECTION);
+    //maca_sentence_add_section(ctx->ms, section);
+    section = maca_sentence_new_section(ctx->ms,MACA_MORPHO_SECTION);
+    prefix_id = (char*)malloc(sizeof(char)*(strlen(ctx->ms->id_sentence) +3));
+    sprintf(prefix_id,"%s_M",ctx->ms->id_sentence);
+
+    for(i=0; i<gfsm_automaton_n_states(a); i++){
+        for (gfsm_arciter_open(&ai,a,i); gfsm_arciter_ok(&ai); gfsm_arciter_next(&ai)){
+            t = gfsm_arciter_arc(&ai);
+            if(gfsm_arc_lower(t) != gfsmEpsilon){
+                lexNode = (xmlNodePtr)maca_ht_index2adr(ht,gfsm_arc_lower(t));
+                if(lexNode){
+                    temp = (char*)xmlGetProp(lexNode, BAD_CAST "id");
+                    sprintf(id_pos, "%s_%s",temp, maca_tags_get_str(ctx->cfg, "morpho", "stype", gfsm_arc_upper(t)));
+                    free(temp);
+                    // printf("key = %s\n", id_pos);
+                    if(posNode = (xmlNodePtr)g_hash_table_lookup(segments_created, id_pos)){
+                        t->lower = maca_ht_adr2index(ht,posNode);
+                        // printf("segment %s already created\n", id_pos);
+                    }
+                    else{
+                        // printf("add segment %s %s (%s)\n", xmlGetProp(lexNode, BAD_CAST "id"), maca_tags_get_str(ctx->cfg, "morpho", "stype", gfsm_arc_upper(t)), id_pos);
+                        posNode = maca_sentence_add_segment(ctx->ms, MACA_MORPHO_SECTION, MACA_CAT_TYPE, prefix_id);
+                        t->lower = maca_ht_adr2index(ht,posNode);
+                        xmlNewProp(posNode, BAD_CAST "stype", BAD_CAST maca_tags_get_str(ctx->cfg, "morpho", "stype", gfsm_arc_upper(t)));
+                        maca_segment_add_elt_from_node(posNode, lexNode, 0);
+                        g_hash_table_insert(segments_created, strdup(id_pos), posNode);
+                    }
+                }
+            }
+        }
+    }
+    free(prefix_id);
+    //  maca_section_add_automaton(section, a);
+    maca_sentence_update_xml_automaton(ctx->ms, MACA_MORPHO_SECTION,a);
+    // section->xml_node_fsm = xmlAddChild(section->xml_node, fsm2xml(a, ht));
+
+    g_hash_table_destroy(segments_created);
+    //  fsm_affiche(a, ht);
+    //maca_section_update_xml_automaton(section, ht, a);
+    return section;
+}
+
+
+
+int maca_crf_tagger_ProcessSentence(maca_sentence * ms, maca_crf_tagger_ctx * ctx)
+{
+    maca_section * prelex_section;
+    maca_section * lex_section;
+    gfsmAutomaton *lex_automaton;
+    fst::StdVectorFst automaton; 
+
+    ctx->ms = ms;
+
+    if(!maca_sentence_is_section_loaded(ctx->ms,MACA_PRELEX_SECTION))
+    {
+        prelex_section = maca_sentence_load_section_by_type(ctx->ms,MACA_PRELEX_SECTION);
+    }
+    else prelex_section = maca_sentence_get_section(ctx->ms, MACA_PRELEX_SECTION);
+    if(prelex_section == NULL){
+        maca_msg(ctx->module, MACA_ERROR);
+        fprintf(stderr,"sentence : %s no prelex section\n", ctx->ms->id_sentence);
+        return -1;
+    }
+
+    if(!maca_sentence_is_section_loaded(ctx->ms,MACA_LEX_SECTION))
+    {
+        lex_section = maca_sentence_load_section_by_type(ctx->ms,MACA_LEX_SECTION);
+    }
+    else lex_section = maca_sentence_get_section(ctx->ms, MACA_LEX_SECTION);
+    if(lex_section == NULL){
+        maca_msg(ctx->module, MACA_ERROR);
+        fprintf(stderr,"sentence : %s no lex section\n", ctx->ms->id_sentence);
+        return -1;
+    }
+    lex_automaton = maca_sentence_get_section_automaton(ctx->ms, MACA_LEX_SECTION);
+    if(lex_automaton == NULL){
+        maca_msg(ctx->module, MACA_ERROR);
+        fprintf(stderr,"sentence : %s no lex automaton\n", ctx->ms->id_sentence);
+        return -1;
+    }
+    gfsm2fst(lex_automaton, automaton);
+    crf_tagger(automaton, ctx, ctx->verbose_flag > 4);
+    if(ctx->n > 0){
+        fst::StdVectorFst nbest;
+        fst::ShortestPath(automaton, &nbest, ctx->n);
+        create_morpho_section(fst2gfsm(nbest), ctx);
+    } else if(ctx->n == -1) {
+        create_morpho_section(fst2gfsm(automaton), ctx);
+    } else if(ctx->n == -2) {
+        macaon::DeterminizeTCLex(&automaton);
+        create_morpho_section(fst2gfsm(automaton), ctx);
+    } else {
+        fprintf(stderr, "error: unknown -n value (%d)\n", ctx->n);
+        return -1;
+    }
+    return 1;
+}
+
+
diff --git a/maca_crf_tagger/src/maca_crf_tagger_utils.cc b/maca_crf_tagger/src/maca_crf_tagger_utils.cc
new file mode 100644
index 0000000..93ed95d
--- /dev/null
+++ b/maca_crf_tagger/src/maca_crf_tagger_utils.cc
@@ -0,0 +1,31 @@
+/***************************************************************************
+    Copyright (C) 2011 by xxx <xxx@lif.univ-mrs.fr>
+    This file is part of maca_crf_tagger.
+
+    Maca_crf_tagger is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Maca_crf_tagger is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with maca_crf_tagger. If not, see <http://www.gnu.org/licenses/>.
+**************************************************************************/
+
+#include "maca_crf_tagger.hh"
+
+char * maca_crf_tagger_GetVersion()
+{
+  return MACA_CRF_TAGGER_VERSION;
+}
+
+void maca_crf_tagger_add_stamp(xmlNodePtr node)
+{
+  add_maca_stamp(node,MACA_CRF_TAGGER_NAME,MACA_CRF_TAGGER_VERSION);
+}
+
+
diff --git a/maca_crf_tagger/src/simple_tagger.cc b/maca_crf_tagger/src/simple_tagger.cc
new file mode 100644
index 0000000..1446829
--- /dev/null
+++ b/maca_crf_tagger/src/simple_tagger.cc
@@ -0,0 +1,21 @@
+#include "simple_tagger.hh"
+
+macaon::Tagger* Tagger_new(const char* modelName, const char* lexiconName) {
+    return new macaon::Tagger(modelName, lexiconName ? lexiconName : "");
+}
+
+void Tagger_free(macaon::Tagger* tagger) {
+    delete tagger;
+}
+
+bool Tagger_ProcessSentence(macaon::Tagger* tagger, int num_words, const char** words, const char** tags) {
+    std::vector<std::string> word_vector, tag_vector;
+    for(int i = 0; i < num_words; i++) {
+        word_vector.push_back(words[i]);
+    }
+    bool result = tagger->ProcessSentence(word_vector, tag_vector);
+    for(int i = 0; i < num_words; i++) {
+        tags[i] = strdup(tag_vector[i].c_str());
+    }
+    return result;
+}
diff --git a/maca_crf_tagger/src/simple_tagger.hh b/maca_crf_tagger/src/simple_tagger.hh
new file mode 100644
index 0000000..2185338
--- /dev/null
+++ b/maca_crf_tagger/src/simple_tagger.hh
@@ -0,0 +1,40 @@
+#include <vector>
+#include "crf_decoder.hh"
+#include "crf_binlexicon.hh"
+#include "crf_features.hh"
+
+namespace macaon {
+    class Tagger {
+        private:
+            macaon::Decoder decoder;
+            macaon::BinaryLexicon *lexicon;
+        public:
+            Tagger(const std::string modelName, const std::string lexiconName = "") : decoder(modelName), lexicon(NULL) {
+                if(lexiconName != "") lexicon = new macaon::BinaryLexicon(lexiconName, decoder.getTagset());
+            }
+
+            ~Tagger() {
+                if(lexicon != NULL) delete lexicon;
+            }
+
+            bool ProcessSentence(const std::vector<std::string>& words, std::vector<std::string>& tags) {
+                std::vector<std::vector<std::string> > features;
+                for(size_t i = 0; i < words.size(); i++) {
+                    std::vector<std::string> word_features;
+                    macaon::FeatureGenerator::get_pos_features(words[i], word_features);
+                    features.push_back(word_features);
+                }
+                tags.clear();
+                decoder.decodeString(features, tags, lexicon);
+                return true;
+            }
+    };
+}
+
+extern "C" {
+    macaon::Tagger* Tagger_new(const char* modelName, const char* lexiconName);
+
+    void Tagger_free(macaon::Tagger* tagger);
+
+    bool Tagger_ProcessSentence(macaon::Tagger* tagger, int num_words, const char** words, const char** tags);
+}
diff --git a/maca_crf_tagger/src/test_simple_tagger.cc b/maca_crf_tagger/src/test_simple_tagger.cc
new file mode 100644
index 0000000..ac3f021
--- /dev/null
+++ b/maca_crf_tagger/src/test_simple_tagger.cc
@@ -0,0 +1,27 @@
+#include <stdio.h>
+
+#include "simple_tagger.hh"
+
+int main(int argc, char** argv) {
+    int num_words = 6;
+    const char* words[] = {"le", "petit", "chat", "boit", "du", "lait"};
+    const char* tags[6];
+    int i;
+
+    if(argc != 3) {
+        fprintf(stderr, "usage: %s <tagger-model> <tagger-lexicon>\n", argv[0]);
+        return 1;
+    }
+
+    macaon::Tagger* tagger = Tagger_new(argv[1], argv[2]);
+
+    Tagger_ProcessSentence(tagger, num_words, words, tags);
+
+    for(i = 0; i < num_words; i++) {
+        printf("%s %s\n", words[i], tags[i]);
+    }
+
+    Tagger_free(tagger);
+
+    return 0;
+}
diff --git a/maca_crf_tagger/src/utf8 b/maca_crf_tagger/src/utf8
new file mode 100755
index 0000000000000000000000000000000000000000..4dee4508ec27a0923a86e9384cf41e7758a38d30
GIT binary patch
literal 8718
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
Hk-z`|v62B9

literal 0
HcmV?d00001

diff --git a/maca_crf_tagger/src/utf8.c b/maca_crf_tagger/src/utf8.c
new file mode 100644
index 0000000..4e1ea27
--- /dev/null
+++ b/maca_crf_tagger/src/utf8.c
@@ -0,0 +1,33 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+const char *byte_to_binary(int x)
+{
+    static char b[9];
+    b[0] = '\0';
+
+    int z;
+    for (z = 128; z > 0; z >>= 1)
+    {
+        strcat(b, ((x & z) == z) ? "1" : "0");
+    }
+
+    return b;
+}
+
+int main() {
+    char word[1024];
+    fgets(word, 1024, stdin);
+    printf("%s\n", word);
+    int offset = 0;
+    while(word[offset] != 0) {
+        printf("%3d %s [%s]\n", offset, byte_to_binary(word[offset]), &word[offset]);
+        if((unsigned char)word[offset] >> 7 == 1) {
+            offset++;
+            while((unsigned char)word[offset] >> 6 == 2) offset++;
+        } else {
+            offset++;
+        }
+    }
+    return 0;
+}
-- 
GitLab