From 97c01676606179ed6c112d9f2d11b763ea3644d2 Mon Sep 17 00:00:00 2001 From: nobody Date: Wed, 16 Jul 2003 10:53:07 +0000 Subject: [PATCH] This commit was manufactured by cvs2svn to create branch 'RC_1_30_0'. [SVN r19146] --- doc/PyConDC_2003/bpl.html | 1127 +++++++++++++++++++++++++++++++++++++ doc/PyConDC_2003/bpl.pdf | Bin 0 -> 96642 bytes doc/PyConDC_2003/bpl.txt | 947 +++++++++++++++++++++++++++++++ 3 files changed, 2074 insertions(+) create mode 100755 doc/PyConDC_2003/bpl.html create mode 100755 doc/PyConDC_2003/bpl.pdf create mode 100644 doc/PyConDC_2003/bpl.txt diff --git a/doc/PyConDC_2003/bpl.html b/doc/PyConDC_2003/bpl.html new file mode 100755 index 00000000..a9ecebd5 --- /dev/null +++ b/doc/PyConDC_2003/bpl.html @@ -0,0 +1,1127 @@ + + + + + + +Building Hybrid Systems with Boost.Python + + + + + + + + +
+

Building Hybrid Systems with Boost.Python

+ +++ + + + + + + + + + + + + + +
Author:David Abrahams
Contact:dave@boost-consulting.com
Organization:Boost Consulting
Date:2003-03-19
Author:Ralf W. Grosse-Kunstleve
Copyright:Copyright David Abrahams and Ralf W. Grosse-Kunstleve 2003. All rights reserved
+ +
+

Abstract

+

Boost.Python is an open source C++ library which provides a concise +IDL-like interface for binding C++ classes and functions to +Python. Leveraging the full power of C++ compile-time introspection +and of recently developed metaprogramming techniques, this is achieved +entirely in pure C++, without introducing a new syntax. +Boost.Python's rich set of features and high-level interface make it +possible to engineer packages from the ground up as hybrid systems, +giving programmers easy and coherent access to both the efficient +compile-time polymorphism of C++ and the extremely convenient run-time +polymorphism of Python.

+
+
+

Introduction

+

Python and C++ are in many ways as different as two languages could +be: while C++ is usually compiled to machine-code, Python is +interpreted. Python's dynamic type system is often cited as the +foundation of its flexibility, while in C++ static typing is the +cornerstone of its efficiency. C++ has an intricate and difficult +compile-time meta-language, while in Python, practically everything +happens at runtime.

+

Yet for many programmers, these very differences mean that Python and +C++ complement one another perfectly. Performance bottlenecks in +Python programs can be rewritten in C++ for maximal speed, and +authors of powerful C++ libraries choose Python as a middleware +language for its flexible system integration capabilities. +Furthermore, the surface differences mask some strong similarities:

+
    +
  • 'C'-family control structures (if, while, for...)
  • +
  • Support for object-orientation, functional programming, and generic +programming (these are both multi-paradigm programming languages.)
  • +
  • Comprehensive operator overloading facilities, recognizing the +importance of syntactic variability for readability and +expressivity.
  • +
  • High-level concepts such as collections and iterators.
  • +
  • High-level encapsulation facilities (C++: namespaces, Python: modules) +to support the design of re-usable libraries.
  • +
  • Exception-handling for effective management of error conditions.
  • +
  • C++ idioms in common use, such as handle/body classes and +reference-counted smart pointers mirror Python reference semantics.
  • +
+

Given Python's rich 'C' interoperability API, it should in principle +be possible to expose C++ type and function interfaces to Python with +an analogous interface to their C++ counterparts. However, the +facilities provided by Python alone for integration with C++ are +relatively meager. Compared to C++ and Python, 'C' has only very +rudimentary abstraction facilities, and support for exception-handling +is completely missing. 'C' extension module writers are required to +manually manage Python reference counts, which is both annoyingly +tedious and extremely error-prone. Traditional extension modules also +tend to contain a great deal of boilerplate code repetition which +makes them difficult to maintain, especially when wrapping an evolving +API.

+

These limitations have lead to the development of a variety of wrapping +systems. SWIG is probably the most popular package for the +integration of C/C++ and Python. A more recent development is SIP, +which was specifically designed for interfacing Python with the Qt +graphical user interface library. Both SWIG and SIP introduce their +own specialized languages for customizing inter-language bindings. +This has certain advantages, but having to deal with three different +languages (Python, C/C++ and the interface language) also introduces +practical and mental difficulties. The CXX package demonstrates an +interesting alternative. It shows that at least some parts of +Python's 'C' API can be wrapped and presented through a much more +user-friendly C++ interface. However, unlike SWIG and SIP, CXX does +not include support for wrapping C++ classes as new Python types.

+

The features and goals of Boost.Python overlap significantly with +many of these other systems. That said, Boost.Python attempts to +maximize convenience and flexibility without introducing a separate +wrapping language. Instead, it presents the user with a high-level +C++ interface for wrapping C++ classes and functions, managing much of +the complexity behind-the-scenes with static metaprogramming. +Boost.Python also goes beyond the scope of earlier systems by +providing:

+
    +
  • Support for C++ virtual functions that can be overridden in Python.
  • +
  • Comprehensive lifetime management facilities for low-level C++ +pointers and references.
  • +
  • Support for organizing extensions as Python packages, +with a central registry for inter-language type conversions.
  • +
  • A safe and convenient mechanism for tying into Python's powerful +serialization engine (pickle).
  • +
  • Coherence with the rules for handling C++ lvalues and rvalues that +can only come from a deep understanding of both the Python and C++ +type systems.
  • +
+

The key insight that sparked the development of Boost.Python is that +much of the boilerplate code in traditional extension modules could be +eliminated using C++ compile-time introspection. Each argument of a +wrapped C++ function must be extracted from a Python object using a +procedure that depends on the argument type. Similarly the function's +return type determines how the return value will be converted from C++ +to Python. Of course argument and return types are part of each +function's type, and this is exactly the source from which +Boost.Python deduces most of the information required.

+

This approach leads to user guided wrapping: as much information is +extracted directly from the source code to be wrapped as is possible +within the framework of pure C++, and some additional information is +supplied explicitly by the user. Mostly the guidance is mechanical +and little real intervention is required. Because the interface +specification is written in the same full-featured language as the +code being exposed, the user has unprecedented power available when +she does need to take control.

+
+
+

Boost.Python Design Goals

+

The primary goal of Boost.Python is to allow users to expose C++ +classes and functions to Python using nothing more than a C++ +compiler. In broad strokes, the user experience should be one of +directly manipulating C++ objects from Python.

+

However, it's also important not to translate all interfaces too +literally: the idioms of each language must be respected. For +example, though C++ and Python both have an iterator concept, they are +expressed very differently. Boost.Python has to be able to bridge the +interface gap.

+

It must be possible to insulate Python users from crashes resulting +from trivial misuses of C++ interfaces, such as accessing +already-deleted objects. By the same token the library should +insulate C++ users from low-level Python 'C' API, replacing +error-prone 'C' interfaces like manual reference-count management and +raw PyObject pointers with more-robust alternatives.

+

Support for component-based development is crucial, so that C++ types +exposed in one extension module can be passed to functions exposed in +another without loss of crucial information like C++ inheritance +relationships.

+

Finally, all wrapping must be non-intrusive, without modifying or +even seeing the original C++ source code. Existing C++ libraries have +to be wrappable by third parties who only have access to header files +and binaries.

+
+
+

Hello Boost.Python World

+

And now for a preview of Boost.Python, and how it improves on the raw +facilities offered by Python. Here's a function we might want to +expose:

+
+char const* greet(unsigned x)
+{
+   static char const* const msgs[] = { "hello", "Boost.Python", "world!" };
+
+   if (x > 2) 
+       throw std::range_error("greet: index out of range");
+
+   return msgs[x];
+}
+
+

To wrap this function in standard C++ using the Python 'C' API, we'd +need something like this:

+
+extern "C" // all Python interactions use 'C' linkage and calling convention
+{
+    // Wrapper to handle argument/result conversion and checking
+    PyObject* greet_wrap(PyObject* args, PyObject * keywords)
+    {
+         int x;
+         if (PyArg_ParseTuple(args, "i", &x))    // extract/check arguments
+         {
+             char const* result = greet(x);      // invoke wrapped function
+             return PyString_FromString(result); // convert result to Python
+         }
+         return 0;                               // error occurred
+    }
+
+    // Table of wrapped functions to be exposed by the module
+    static PyMethodDef methods[] = {
+        { "greet", greet_wrap, METH_VARARGS, "return one of 3 parts of a greeting" }
+        , { NULL, NULL, 0, NULL } // sentinel
+    };
+
+    // module initialization function
+    DL_EXPORT init_hello()
+    {
+        (void) Py_InitModule("hello", methods); // add the methods to the module
+    }
+}
+
+

Now here's the wrapping code we'd use to expose it with Boost.Python:

+
+#include <boost/python.hpp>
+using namespace boost::python;
+BOOST_PYTHON_MODULE(hello)
+{
+    def("greet", greet, "return one of 3 parts of a greeting");
+}
+
+

and here it is in action:

+
+>>> import hello
+>>> for x in range(3):
+...     print hello.greet(x)
+...
+hello
+Boost.Python
+world!
+
+

Aside from the fact that the 'C' API version is much more verbose, +it's worth noting a few things that it doesn't handle correctly:

+
    +
  • The original function accepts an unsigned integer, and the Python +'C' API only gives us a way of extracting signed integers. The +Boost.Python version will raise a Python exception if we try to pass +a negative number to hello.greet, but the other one will proceed +to do whatever the C++ implementation does when converting an +negative integer to unsigned (usually wrapping to some very large +number), and pass the incorrect translation on to the wrapped +function.
  • +
  • That brings us to the second problem: if the C++ greet() +function is called with a number greater than 2, it will throw an +exception. Typically, if a C++ exception propagates across the +boundary with code generated by a 'C' compiler, it will cause a +crash. As you can see in the first version, there's no C++ +scaffolding there to prevent this from happening. Functions wrapped +by Boost.Python automatically include an exception-handling layer +which protects Python users by translating unhandled C++ exceptions +into a corresponding Python exception.
  • +
  • A slightly more-subtle limitation is that the argument conversion +used in the Python 'C' API case can only get that integer x in +one way. PyArg_ParseTuple can't convert Python long objects +(arbitrary-precision integers) which happen to fit in an unsigned +int but not in a signed long, nor will it ever handle a +wrapped C++ class with a user-defined implicit operator unsigned +int() conversion. Boost.Python's dynamic type conversion +registry allows users to add arbitrary conversion methods.
  • +
+
+
+

Library Overview

+

This section outlines some of the library's major features. Except as +neccessary to avoid confusion, details of library implementation are +omitted.

+
+

Exposing Classes

+

C++ classes and structs are exposed with a similarly-terse interface. +Given:

+
+struct World
+{
+    void set(std::string msg) { this->msg = msg; }
+    std::string greet() { return msg; }
+    std::string msg;
+};
+
+

The following code will expose it in our extension module:

+
+#include <boost/python.hpp>
+BOOST_PYTHON_MODULE(hello)
+{
+    class_<World>("World")
+        .def("greet", &World::greet)
+        .def("set", &World::set)
+    ;
+}
+
+

Although this code has a certain pythonic familiarity, people +sometimes find the syntax bit confusing because it doesn't look like +most of the C++ code they're used to. All the same, this is just +standard C++. Because of their flexible syntax and operator +overloading, C++ and Python are great for defining domain-specific +(sub)languages +(DSLs), and that's what we've done in Boost.Python. To break it down:

+
+class_<World>("World")
+
+

constructs an unnamed object of type class_<World> and passes +"World" to its constructor. This creates a new-style Python class +called World in the extension module, and associates it with the +C++ type World in the Boost.Python type conversion registry. We +might have also written:

+
+class_<World> w("World");
+
+

but that would've been more verbose, since we'd have to name w +again to invoke its def() member function:

+
+w.def("greet", &World::greet)
+
+

There's nothing special about the location of the dot for member +access in the original example: C++ allows any amount of whitespace on +either side of a token, and placing the dot at the beginning of each +line allows us to chain as many successive calls to member functions +as we like with a uniform syntax. The other key fact that allows +chaining is that class_<> member functions all return a reference +to *this.

+

So the example is equivalent to:

+
+class_<World> w("World");
+w.def("greet", &World::greet);
+w.def("set", &World::set);
+
+

It's occasionally useful to be able to break down the components of a +Boost.Python class wrapper in this way, but the rest of this article +will stick to the terse syntax.

+

For completeness, here's the wrapped class in use:

+
+>>> import hello
+>>> planet = hello.World()
+>>> planet.set('howdy')
+>>> planet.greet()
+'howdy'
+
+
+

Constructors

+

Since our World class is just a plain struct, it has an +implicit no-argument (nullary) constructor. Boost.Python exposes the +nullary constructor by default, which is why we were able to write:

+
+>>> planet = hello.World()
+
+

However, well-designed classes in any language may require constructor +arguments in order to establish their invariants. Unlike Python, +where __init__ is just a specially-named method, In C++ +constructors cannot be handled like ordinary member functions. In +particular, we can't take their address: &World::World is an +error. The library provides a different interface for specifying +constructors. Given:

+
+struct World
+{
+    World(std::string msg); // added constructor
+    ...
+
+

we can modify our wrapping code as follows:

+
+class_<World>("World", init<std::string>())
+    ...
+
+

of course, a C++ class may have additional constructors, and we can +expose those as well by passing more instances of init<...> to +def():

+
+class_<World>("World", init<std::string>())
+    .def(init<double, double>())
+    ...
+
+

Boost.Python allows wrapped functions, member functions, and +constructors to be overloaded to mirror C++ overloading.

+
+
+

Data Members and Properties

+

Any publicly-accessible data members in a C++ class can be easily +exposed as either readonly or readwrite attributes:

+
+class_<World>("World", init<std::string>())
+    .def_readonly("msg", &World::msg)
+    ...
+
+

and can be used directly in Python:

+
+>>> planet = hello.World('howdy')
+>>> planet.msg
+'howdy'
+
+

This does not result in adding attributes to the World instance +__dict__, which can result in substantial memory savings when +wrapping large data structures. In fact, no instance __dict__ +will be created at all unless attributes are explicitly added from +Python. Boost.Python owes this capability to the new Python 2.2 type +system, in particular the descriptor interface and property type.

+

In C++, publicly-accessible data members are considered a sign of poor +design because they break encapsulation, and style guides usually +dictate the use of "getter" and "setter" functions instead. In +Python, however, __getattr__, __setattr__, and since 2.2, +property mean that attribute access is just one more +well-encapsulated syntactic tool at the programmer's disposal. +Boost.Python bridges this idiomatic gap by making Python property +creation directly available to users. If msg were private, we +could still expose it as attribute in Python as follows:

+
+class_<World>("World", init<std::string>())
+    .add_property("msg", &World::greet, &World::set)
+    ...
+
+

The example above mirrors the familiar usage of properties in Python +2.2+:

+
+>>> class World(object):
+...     __init__(self, msg):
+...         self.__msg = msg
+...     def greet(self):
+...         return self.__msg
+...     def set(self, msg):
+...         self.__msg = msg
+...     msg = property(greet, set)
+
+
+
+

Operator Overloading

+

The ability to write arithmetic operators for user-defined types has +been a major factor in the success of both languages for numerical +computation, and the success of packages like NumPy attests to the +power of exposing operators in extension modules. Boost.Python +provides a concise mechanism for wrapping operator overloads. The +example below shows a fragment from a wrapper for the Boost rational +number library:

+
+class_<rational<int> >("rational_int")
+  .def(init<int, int>()) // constructor, e.g. rational_int(3,4)
+  .def("numerator", &rational<int>::numerator)
+  .def("denominator", &rational<int>::denominator)
+  .def(-self)        // __neg__ (unary minus)
+  .def(self + self)  // __add__ (homogeneous)
+  .def(self * self)  // __mul__
+  .def(self + int()) // __add__ (heterogenous)
+  .def(int() + self) // __radd__
+  ...
+
+

The magic is performed using a simplified application of "expression +templates" [VELD1995], a technique originally developed for +optimization of high-performance matrix algebra expressions. The +essence is that instead of performing the computation immediately, +operators are overloaded to construct a type representing the +computation. In matrix algebra, dramatic optimizations are often +available when the structure of an entire expression can be taken into +account, rather than evaluating each operation "greedily". +Boost.Python uses the same technique to build an appropriate Python +method object based on expressions involving self.

+
+
+

Inheritance

+

C++ inheritance relationships can be represented to Boost.Python by adding +an optional bases<...> argument to the class_<...> template +parameter list as follows:

+
+class_<Derived, bases<Base1,Base2> >("Derived")
+     ...
+
+

This has two effects:

+
    +
  1. When the class_<...> is created, Python type objects +corresponding to Base1 and Base2 are looked up in +Boost.Python's registry, and are used as bases for the new Python +Derived type object, so methods exposed for the Python Base1 +and Base2 types are automatically members of the Derived +type. Because the registry is global, this works correctly even if +Derived is exposed in a different module from either of its +bases.
  2. +
  3. C++ conversions from Derived to its bases are added to the +Boost.Python registry. Thus wrapped C++ methods expecting (a +pointer or reference to) an object of either base type can be +called with an object wrapping a Derived instance. Wrapped +member functions of class T are treated as though they have an +implicit first argument of T&, so these conversions are +neccessary to allow the base class methods to be called for derived +objects.
  4. +
+

Of course it's possible to derive new Python classes from wrapped C++ +class instances. Because Boost.Python uses the new-style class +system, that works very much as for the Python built-in types. There +is one significant detail in which it differs: the built-in types +generally establish their invariants in their __new__ function, so +that derived classes do not need to call __init__ on the base +class before invoking its methods :

+
+>>> class L(list):
+...      def __init__(self):
+...          pass
+...
+>>> L().reverse()
+>>> 
+
+

Because C++ object construction is a one-step operation, C++ instance +data cannot be constructed until the arguments are available, in the +__init__ function:

+
+>>> class D(SomeBoostPythonClass):
+...      def __init__(self):
+...          pass
+...
+>>> D().some_boost_python_method()
+Traceback (most recent call last):
+  File "<stdin>", line 1, in ?
+TypeError: bad argument type for built-in operation
+
+

This happened because Boost.Python couldn't find instance data of type +SomeBoostPythonClass within the D instance; D's __init__ +function masked construction of the base class. It could be corrected +by either removing D's __init__ function or having it call +SomeBoostPythonClass.__init__(...) explicitly.

+
+
+

Virtual Functions

+

Deriving new types in Python from extension classes is not very +interesting unless they can be used polymorphically from C++. In +other words, Python method implementations should appear to override +the implementation of C++ virtual functions when called through base +class pointers/references from C++. Since the only way to alter the +behavior of a virtual function is to override it in a derived class, +the user must build a special derived class to dispatch a polymorphic +class' virtual functions:

+
+//
+// interface to wrap:
+//
+class Base
+{
+ public:
+    virtual int f(std::string x) { return 42; }
+    virtual ~Base();
+};
+
+int calls_f(Base const& b, std::string x) { return b.f(x); }
+
+//
+// Wrapping Code
+//
+
+// Dispatcher class
+struct BaseWrap : Base
+{
+    // Store a pointer to the Python object
+    BaseWrap(PyObject* self_) : self(self_) {}
+    PyObject* self;
+
+    // Default implementation, for when f is not overridden
+    int f_default(std::string x) { return this->Base::f(x); }
+    // Dispatch implementation
+    int f(std::string x) { return call_method<int>(self, "f", x); }
+};
+
+...
+    def("calls_f", calls_f);
+    class_<Base, BaseWrap>("Base")
+        .def("f", &Base::f, &BaseWrap::f_default)
+        ;
+
+

Now here's some Python code which demonstrates:

+
+>>> class Derived(Base):
+...     def f(self, s):
+...          return len(s)
+...
+>>> calls_f(Base(), 'foo')
+42
+>>> calls_f(Derived(), 'forty-two')
+9
+
+

Things to notice about the dispatcher class:

+
    +
  • The key element which allows overriding in Python is the +call_method invocation, which uses the same global type +conversion registry as the C++ function wrapping does to convert its +arguments from C++ to Python and its return type from Python to C++.
  • +
  • Any constructor signatures you wish to wrap must be replicated with +an initial PyObject* argument
  • +
  • The dispatcher must store this argument so that it can be used to +invoke call_method
  • +
  • The f_default member function is needed when the function being +exposed is not pure virtual; there's no other way Base::f can be +called on an object of type BaseWrap, since it overrides f.
  • +
+
+
+

Deeper Reflection on the Horizon?

+

Admittedly, this formula is tedious to repeat, especially on a project +with many polymorphic classes. That it is neccessary reflects some +limitations in C++'s compile-time introspection capabilities: there's +no way to enumerate the members of a class and find out which are +virtual functions. At least one very promising project has been +started to write a front-end which can generate these dispatchers (and +other wrapping code) automatically from C++ headers.

+

Pyste is being developed by Bruno da Silva de Oliveira. It builds on +GCC_XML, which generates an XML version of GCC's internal program +representation. Since GCC is a highly-conformant C++ compiler, this +ensures correct handling of the most-sophisticated template code and +full access to the underlying type system. In keeping with the +Boost.Python philosophy, a Pyste interface description is neither +intrusive on the code being wrapped, nor expressed in some unfamiliar +language: instead it is a 100% pure Python script. If Pyste is +successful it will mark a move away from wrapping everything directly +in C++ for many of our users. It will also allow us the choice to +shift some of the metaprogram code from C++ to Python. We expect that +soon, not only our users but the Boost.Python developers themselves +will be "thinking hybrid" about their own code.

+
+
+
+

Serialization

+

Serialization is the process of converting objects in memory to a +form that can be stored on disk or sent over a network connection. The +serialized object (most often a plain string) can be retrieved and +converted back to the original object. A good serialization system will +automatically convert entire object hierarchies. Python's standard +pickle module is just such a system. It leverages the language's strong +runtime introspection facilities for serializing practically arbitrary +user-defined objects. With a few simple and unintrusive provisions this +powerful machinery can be extended to also work for wrapped C++ objects. +Here is an example:

+
+#include <string>
+
+struct World
+{
+    World(std::string a_msg) : msg(a_msg) {}
+    std::string greet() const { return msg; }
+    std::string msg;
+};
+
+#include <boost/python.hpp>
+using namespace boost::python;
+
+struct World_picklers : pickle_suite
+{
+  static tuple
+  getinitargs(World const& w) { return make_tuple(w.greet()); }
+};
+
+BOOST_PYTHON_MODULE(hello)
+{
+    class_<World>("World", init<std::string>())
+        .def("greet", &World::greet)
+        .def_pickle(World_picklers())
+    ;
+}
+
+

Now let's create a World object and put it to rest on disk:

+
+>>> import hello
+>>> import pickle
+>>> a_world = hello.World("howdy")
+>>> pickle.dump(a_world, open("my_world", "w"))
+
+

In a potentially different script on a potentially different +computer with a potentially different operating system:

+
+>>> import pickle
+>>> resurrected_world = pickle.load(open("my_world", "r"))
+>>> resurrected_world.greet()
+'howdy'
+
+

Of course the cPickle module can also be used for faster +processing.

+

Boost.Python's pickle_suite fully supports the pickle protocol +defined in the standard Python documentation. Like a __getinitargs__ +function in Python, the pickle_suite's getinitargs() is responsible for +creating the argument tuple that will be use to reconstruct the pickled +object. The other elements of the Python pickling protocol, +__getstate__ and __setstate__ can be optionally provided via C++ +getstate and setstate functions. C++'s static type system allows the +library to ensure at compile-time that nonsensical combinations of +functions (e.g. getstate without setstate) are not used.

+

Enabling serialization of more complex C++ objects requires a little +more work than is shown in the example above. Fortunately the +object interface (see next section) greatly helps in keeping the +code manageable.

+
+
+

Object interface

+

Experienced 'C' language extension module authors will be familiar +with the ubiquitous PyObject*, manual reference-counting, and the +need to remember which API calls return "new" (owned) references or +"borrowed" (raw) references. These constraints are not just +cumbersome but also a major source of errors, especially in the +presence of exceptions.

+

Boost.Python provides a class object which automates reference +counting and provides conversion to Python from C++ objects of +arbitrary type. This significantly reduces the learning effort for +prospective extension module writers.

+

Creating an object from any other type is extremely simple:

+
+object s("hello, world");  // s manages a Python string
+
+

object has templated interactions with all other types, with +automatic to-python conversions. It happens so naturally that it's +easily overlooked:

+
+object ten_Os = 10 * s[4]; // -> "oooooooooo"
+
+

In the example above, 4 and 10 are converted to Python objects +before the indexing and multiplication operations are invoked.

+

The extract<T> class template can be used to convert Python objects +to C++ types:

+
+double x = extract<double>(o);
+
+

If a conversion in either direction cannot be performed, an +appropriate exception is thrown at runtime.

+

The object type is accompanied by a set of derived types +that mirror the Python built-in types such as list, dict, +tuple, etc. as much as possible. This enables convenient +manipulation of these high-level types from C++:

+
+dict d;
+d["some"] = "thing";
+d["lucky_number"] = 13;
+list l = d.keys();
+
+

This almost looks and works like regular Python code, but it is pure +C++. Of course we can wrap C++ functions which accept or return +object instances.

+
+
+
+

Thinking hybrid

+

Because of the practical and mental difficulties of combining +programming languages, it is common to settle a single language at the +outset of any development effort. For many applications, performance +considerations dictate the use of a compiled language for the core +algorithms. Unfortunately, due to the complexity of the static type +system, the price we pay for runtime performance is often a +significant increase in development time. Experience shows that +writing maintainable C++ code usually takes longer and requires far +more hard-earned working experience than developing comparable Python +code. Even when developers are comfortable working exclusively in +compiled languages, they often augment their systems by some type of +ad hoc scripting layer for the benefit of their users without ever +availing themselves of the same advantages.

+

Boost.Python enables us to think hybrid. Python can be used for +rapidly prototyping a new application; its ease of use and the large +pool of standard libraries give us a head start on the way to a +working system. If necessary, the working code can be used to +discover rate-limiting hotspots. To maximize performance these can +be reimplemented in C++, together with the Boost.Python bindings +needed to tie them back into the existing higher-level procedure.

+

Of course, this top-down approach is less attractive if it is clear +from the start that many algorithms will eventually have to be +implemented in C++. Fortunately Boost.Python also enables us to +pursue a bottom-up approach. We have used this approach very +successfully in the development of a toolbox for scientific +applications. The toolbox started out mainly as a library of C++ +classes with Boost.Python bindings, and for a while the growth was +mainly concentrated on the C++ parts. However, as the toolbox is +becoming more complete, more and more newly added functionality can be +implemented in Python.

+

python_cpp_mix.jpg

+

This figure shows the estimated ratio of newly added C++ and Python +code over time as new algorithms are implemented. We expect this +ratio to level out near 70% Python. Being able to solve new problems +mostly in Python rather than a more difficult statically typed +language is the return on our investment in Boost.Python. The ability +to access all of our code from Python allows a broader group of +developers to use it in the rapid development of new applications.

+
+
+

Development history

+

The first version of Boost.Python was developed in 2000 by Dave +Abrahams at Dragon Systems, where he was privileged to have Tim Peters +as a guide to "The Zen of Python". One of Dave's jobs was to develop +a Python-based natural language processing system. Since it was +eventually going to be targeting embedded hardware, it was always +assumed that the compute-intensive core would be rewritten in C++ to +optimize speed and memory footprint 1. The project also wanted to +test all of its C++ code using Python test scripts 2. The only +tool we knew of for binding C++ and Python was SWIG, and at the time +its handling of C++ was weak. It would be false to claim any deep +insight into the possible advantages of Boost.Python's approach at +this point. Dave's interest and expertise in fancy C++ template +tricks had just reached the point where he could do some real damage, +and Boost.Python emerged as it did because it filled a need and +because it seemed like a cool thing to try.

+

This early version was aimed at many of the same basic goals we've +described in this paper, differing most-noticeably by having a +slightly more cumbersome syntax and by lack of special support for +operator overloading, pickling, and component-based development. +These last three features were quickly added by Ullrich Koethe and +Ralf Grosse-Kunstleve 3, and other enthusiastic contributors arrived +on the scene to contribute enhancements like support for nested +modules and static member functions.

+

By early 2001 development had stabilized and few new features were +being added, however a disturbing new fact came to light: Ralf had +begun testing Boost.Python on pre-release versions of a compiler using +the EDG front-end, and the mechanism at the core of Boost.Python +responsible for handling conversions between Python and C++ types was +failing to compile. As it turned out, we had been exploiting a very +common bug in the implementation of all the C++ compilers we had +tested. We knew that as C++ compilers rapidly became more +standards-compliant, the library would begin failing on more +platforms. Unfortunately, because the mechanism was so central to the +functioning of the library, fixing the problem looked very difficult.

+

Fortunately, later that year Lawrence Berkeley and later Lawrence +Livermore National labs contracted with Boost Consulting for support +and development of Boost.Python, and there was a new opportunity to +address fundamental issues and ensure a future for the library. A +redesign effort began with the low level type conversion architecture, +building in standards-compliance and support for component-based +development (in contrast to version 1 where conversions had to be +explicitly imported and exported across module boundaries). A new +analysis of the relationship between the Python and C++ objects was +done, resulting in more intuitive handling for C++ lvalues and +rvalues.

+

The emergence of a powerful new type system in Python 2.2 made the +choice of whether to maintain compatibility with Python 1.5.2 easy: +the opportunity to throw away a great deal of elaborate code for +emulating classic Python classes alone was too good to pass up. In +addition, Python iterators and descriptors provided crucial and +elegant tools for representing similar C++ constructs. The +development of the generalized object interface allowed us to +further shield C++ programmers from the dangers and syntactic burdens +of the Python 'C' API. A great number of other features including C++ +exception translation, improved support for overloaded functions, and +most significantly, CallPolicies for handling pointers and +references, were added during this period.

+

In October 2002, version 2 of Boost.Python was released. Development +since then has concentrated on improved support for C++ runtime +polymorphism and smart pointers. Peter Dimov's ingenious +boost::shared_ptr design in particular has allowed us to give the +hybrid developer a consistent interface for moving objects back and +forth across the language barrier without loss of information. At +first, we were concerned that the sophistication and complexity of the +Boost.Python v2 implementation might discourage contributors, but the +emergence of Pyste and several other significant feature +contributions have laid those fears to rest. Daily questions on the +Python C++-sig and a backlog of desired improvements show that the +library is getting used. To us, the future looks bright.

+
+
+

Conclusions

+

Boost.Python achieves seamless interoperability between two rich and +complimentary language environments. Because it leverages template +metaprogramming to introspect about types and functions, the user +never has to learn a third syntax: the interface definitions are +written in concise and maintainable C++. Also, the wrapping system +doesn't have to parse C++ headers or represent the type system: the +compiler does that work for us.

+

Computationally intensive tasks play to the strengths of C++ and are +often impossible to implement efficiently in pure Python, while jobs +like serialization that are trivial in Python can be very difficult in +pure C++. Given the luxury of building a hybrid software system from +the ground up, we can approach design with new confidence and power.

+
+
+

Citations

+ + ++ + + +
[VELD1995]T. Veldhuizen, "Expression Templates," C++ Report, +Vol. 7 No. 5 June 1995, pp. 26-31. +http://osl.iu.edu/~tveldhui/papers/Expression-Templates/exprtmpl.html
+
+
+

Footnotes

+ + + + + +
[1]In retrospect, it seems that "thinking hybrid" from the +ground up might have been better for the NLP system: the +natural component boundaries defined by the pure python +prototype turned out to be inappropriate for getting the +desired performance and memory footprint out of the C++ core, +which eventually caused some redesign overhead on the Python +side when the core was moved to C++.
+ + + + + +
[2]We also have some reservations about driving all C++ +testing through a Python interface, unless that's the only way +it will be ultimately used. Any transition across language +boundaries with such different object models can inevitably +mask bugs.
+ + + + + +
[3]These features were expressed very differently in v1 of +Boost.Python
+
+
+ + + + diff --git a/doc/PyConDC_2003/bpl.pdf b/doc/PyConDC_2003/bpl.pdf new file mode 100755 index 0000000000000000000000000000000000000000..ffd8903e2debb90a96890b5fad941bc5e2c4457d GIT binary patch literal 96642 zcmY!laBIOiwLVFj6p32;$QB zOUz9zR7Cwo(dp?oDz#u6*M%t?CiMo!!lFL zQ;PzMQqxk4QuC5ii$N+t68;6Ld5*~?nfZANh86};b3e zFG#@>qB53C-#NcDuSCJn802xNdq75Gx`#{OF)uH_1mqy76Tk*S900KqVg@x%z9i(6gF~AVwGDCNFXN?<9)IX|x? zHLs*t!3bMgXnV9IA8<{8=nH%Yunph~9nHlJrnVEo82A3w4fRnOMW?nXzz9T5bK@&G5 z!ExyaD}WL!B*kjvCT8Z8wcd=qBgq6_@6eWag#oCFkdY)0dqc7c@#i z!9qQ67#rxBo0};Z8CmEV8<{AWnV9LB8=8^hjf|3#0xNy}^73*$B76Z#C?xv=lyt!H z1C0y|13fbXV+B(~BRxw)V+BJ)Gd*JyO9fLSLp=*a+;I`Cpzod^lFy~@qM(tOR9sS& zm|TJu$e;vFE$5k78tGYDSST188|qn@8Y-9?80uL-k}lSyg6zD^ypp2)lu~dxh2}(1 z=AxPt%?8c)4D~GWBxYn6LgKohvLquvPd6pCI5Ry@H$6Wwrx?w- zpxj6`=b9So8CaSs7+P598Jk!rn3x&q85mpOOtr}F%}CA3$=5}3b$NbKP70cH!DSxR z+-q#8XJ}@rU}$QrX904riIJY6p)pbJ&B;tEN-V0>%`ZzWD$7hQM|USEPf+YmLjxl{ zQ&7Yk8tNIDm?#*Uo9LMuE0`D==^0uO7p|!l1^LC9dFi^zIf=!^sp!!Ut~Drkr;(wa zp{b#Qv8B15v5_Izod#w`3MK|-dPXM1ITTd5mK2pHm*f{=I1*GFQO}VUhI+=PW(r22 zn#$N*!PwGBkGMEbNi0d!%}vcsN-ZkZP0UNtEhx$_NG&SK#0*xlDm74=K`uuPVZmx< zu4iIksbFMipl4=js$gtxp=WGrL{vHjIWVy#zX+UAbMg~YGV{{W(<)h27f#2Tn(CPv z7%Lc>nd@1ankpEZndq4qm=fjK%)E@$qRf)SyyR4LH&Ufyf(NLHp`NL!se+-Uk)ElU zk%FP0Z0vE6oEJ{}_ptDs>dxrADTDre>xJM&P2@3t0W^cFB?<=WK<>0|P?^LqiKaQws$nBNIJKBjUm`IkNUi^bA4n zGcwdOH?dSOGBDAzFe5Harse0C?3k4%k`5(lk?`CYN zU<4|tfSB?UjM`!Ayko{6Xd-MKegykW1e+ zFF8LYGcR4i2vmLtap{9bD?myW^g}XpQ;T(j@^cgOkWB`uLp2#@2A94|W*U581Jpm( z*UQToAf{_uz9Ag6o{q&+lkR^qspxDdI zOV3GF&@V1XOioqMPpwGKNz7Hy2g{`-p~8v9C8OEYu8RzL#8 z-&MiT#1P3HCguqHAWFclN=eFrdMv3lCnvQ;K|eJw1)LdEb0IXuulglLi7BbMiAC88 z`o-v0LQTj|RWLL~4iZyK1^vjB%+#V(khcuYkaMz`AuKDUggK%pB`q~K6Ozb5fs&J&3(EbV zoR*WHo|&AOlb2tD$WleA>6su~Q&SZ5a}$%nrAJC?IyfVN@*t#~0p+ja(t^~Y%={u~ zK0|O5lS@lLB~mFkTPLO$B^G3WV<|H~1>~*NB9P(9sVSK`If>x>iqM;1l#-iRoLmYT zLsrm-yBFq}JWzHl0%yehywr@uoHVHIg{6rg;bKsvLNX{M7#*P{mm}0sj>rke5gM0{ zh>&vxm$CYet_u3ja2L8l4RM7U;)-mDD?+m;RGlYOohPz7PlP(xk_-iXKd9yYP)+_& zP5#K5{9%d^nsY%zkp(%G3i|%g;vp1jLMYUPP-GKA5jrEG@*x@dMR^MP>8VAziFqkW zImHV4phyB0kBLwviO5DILLHHaFe(ueOo^!q`pNKMN`<;36>3N-vLUGm&6!YjnNW4m z!VHp`GZE@iLBW&}T6`b(kuOOf@LBJ@>4 z25iRC8;i{$@wX%T>3t# zdFdq?3Py&8CLn#qB}J);xm*=<_C`idzr9VgZhm~@_AEcIDGWOpYCZQJDHD30GJCS! zmf8gx9!b6vxjb)%)z#{SIVvr&Set(C%pAr}?mNF9zI(8~oMq_;=YY5eP6i>LWP}43 zRh3U)#QSIOHxWto8QcvxSx>+TXu?E}ZiuucTjoR_5zt z=YQAD`M`fX^LF|77xph6&fdLVZjVPQpHp#2K%?ez^VaQ4JRP!I#61!M4OFf53nE2U zXhz|^)J|SU)P{xTmQSU-Et_Mjk{_`!`EEJ~`7xum5(53I{OU_So-nv#K zWP9KNyN%Wd9g`;(?!Mo>Z}zIHuFYvXSR9$JO;>mMB=p&5fs*hH%fGWume+mBU6AZO zpM`tQB-`7keR;o6ZLa2zx#;vSd1cA;E4Aiww>YQtzpgW%K4BWmb4%OlADk-t()2=I zs&$U*h=;9s@0iZgaw{bEbF``Hy{>bDjp8d!=NC;V`|WRk;&Rh#_DZ$Q+bYuy_5XZy z?dzL1Bd|{I^3#ivvv$f%T)dbi{m9QZr?VzqJgj7(^qg(}$2p5xeS@bR(D!}(EmwTr z?eG8J#oLFzy(N3u{{6%S8I5;8zg{TsKC>n}=Oe@a3yY+eiwRu(ld^dJgZ8E05C1L9 zFuT1&-RX+<%bgQUw0Fd%>0Hn?`xM*D8+9pIXN{HpfAt4$6Q#VCDsS!4oH=jn+UoU7 zKj*Mpo_e@N#3jo0jJ|fEX9$$onb%$kv$7At2bd>SE!-kCr?m1^XI483yWy8xoRwoQJJ6fmN zYndEfm14divPnVw$FJkZ_$7%*gb3mfEjc zWWx46T>krCi7y=bx44R;&TR3HIAIDdrao z6%IcMUl66_{y4jeD#7Mr8L+auB=+xQp%v)yIxj% z`SJHp@-*8cTt6Sa@cyZRsiEp+o<(JD0SBxf9bUEVY|X-NCzdmqO%?z0O*2C3b`8U> ztf17lOSQMxZta@>W#y~mmRvK=CN5`Hn`1Ne(^X#P&gl|z+m;r-tk7;xTeU07t!HBfILsy8;hcX_gZVfTb>_cn>{l=qE}U$*~c`?Kx$ zOyc9+dSp-inetR)LVDhtr3WOnb5#1pFKcB_ov)g0sPW^pQQR^MCHG{%XXyrCSK025 zwm5zIl~vE;gvXWZ7R{LUC~V!Y8ith(^IGn*y~xNBi2fITZC`%wg4s9F?*)%!S4Ubl&l0tGZpl?~=5JJj z-iyf!tqoc~dbw^3TJ+oPm(Te9m+w~w{~k%jLfPbBXBC6ymUXLZ@O?F}?(&=H#5p0( z^Ay`w)eo)Vcjb<07e<+OxwbXgF78eGsUj<^s@lf%oXyBPVWQ)qz0>L&44zAJmCacm z)1WtxMO^Sz^g-TUOQz=6Q#YR9H-A@MR~fU(QPnM`2lo{>Z!sx;#CMd;rq!p_ z7rnE*WK@@0q?f;n$>+khxRO;opRO*F{OS?D@15+Fy>&L<+8tSgK7V=Ct$%03*V+Yf ze-m$Ci8kLZUsbI4e)d$EySGpAW*K}=`!c)k@29or4tn0+W4!RT|Jg@7EB9V=T`i|K z`&`{lOJRW*+1D3DzMI=UaV6KsXMX^+H2NNc=++HYlzgU z-lx7bsSDd*H}BhZrTR_vZL!x}uKuroJKApgv??)IYvSE~nW;vm*83wDzw1A?S3l#6 z_vPgoY|HPmMLJK&>3>&g+P`R4dyHk*+&Nko!iow_)=j&^b2TqraoL8$Dz9^||EZer zIR2`3sjJOP+3m;rE**-TvS{6_++xLprve-2oK@&u(Y5X6oiqOoJid$dxf?gV@~$s` z%|AIkIsYotOD*&3bMutWoT+%T?A79H$&quc55+6AYjJHa+fcS<`pK7FXJ;6*r$?_z zHEleyxO!Vx;NjfOH`S(_-(5Sq_r|uWy{nznFSJaSe*3#H=Zx*OW8`Dz|%Tb8$?Cnr~> zxvt@w?*{+7@jGU2+P%;A-LFLcWqXQCu5Im_yqN7%><6P?e=T^6mcO;C650CcfzA(} zo2HJD#xhpwlj_QpEX8l1wKw>|Q983CXV-#&=gThk%#7%I;$n2VBIL=gyPH(lm^%%U zWr`|#Zugh(XJ(%D+9x$H1=^N?4xxZoMWD8E0us|xkrpc$gXRK)6pSHj2aG|pH9-o- zpuwgf1!K_ULJ(+V9kGPK7&@3k?h*nMunnMA56p=m2cSBUj5P$Hi68@GGX-NaBRwNC z3k4HHOFa_{V+CV#V?857+|w3*i_-PchBd*VM!DN9%=OGo z%)m1lpn+@?0~0+%W6@hA!xO!bT{De*vZMTH(@ zQBGQZQF^LgUTO)37eJYhVlNn)8R!`pnkg8Wfu?j!6^sl`^(-ySAZu*!ESMlJ4!~3X z;Ea)znN+L~S+Ss>lAny8oIr&J%rBr$7QV4zeK$iB1!K?%EojWx#8klqTsDD<48mi^ zV0|!~Af5o(ho@6#4C)4gY|1PuE^*FCEK)Eu<_gmDavx%9&`Q%W+56^ub0JCxyN z&!CVHrx0yt-{43?11^1#RW7N;$wiq3CHX}PCZNSt;K6QKSqHKU)pdBrouQ*N-~k$> zF&D(x0c>D3HxbmyhIN)xG81$2^HLy!NTWSq<3M}`eNeYKCl#3umV`+lY=9_)nGEHD zMw%eZWY7>*iGqH9L26zSbf^tRrxuoGmL(Qf<`$IXmt-b`2VpbI5_3Q+1{L&+Kogr4 z#idEbpy4?f4>YO_;)6zQQ&Gljp}c~k)a2BZ)M6+%Ek7qGzZ}LZ&dkjOk5eZW6cpuG zK-ECz`C&G}c*r*8mL_K?=%*Kf2ZtdXm^}*mP%9Ml6N`%S%OQh|U?y}l3(PJpfXYM0 ztH5GO`JhMvGxPF`N-|Oti%X!Yit|fhe5jkDZUw7ONyb3yMqf74%E9y6DpjTmk#2>rxXx;r~>f#aZ+Ltct{3AOupu3mCA@02Cw^fkrJsv6`GyngkmD1{Yom`WdN-MI{QL`6{qvd955i#XvaX}(zhRG6XIt6(^7F@l8YR6~=Qv*=dIdm|{ zy&xP5TTu+I`aymHty*_12F;i#n421K={qMDctGYX%`A+$^g*o!&~&n{p`{6={Qz2) zub_|Cu<$HN%*jl41ee3Qh9+G4!6m7=VG0(eT>24E<4lYVAqF@>7Iy0zSt{sSni(h< z8XB4@n45!Fak=FrrWY%iaOr~@I-tpS4Sh&`tq&?=z(pB^1uk1OAqKi-=AGWV_jrpArBm8-zD0il5HkVJbuOIsw*Evd4 zts0zJ4+#gqiJegTDSyT*{dbE4wT{kM<-X5+>G@wXx&mI=8EAy|v^AQ1SYF^U`R_{a z#QB!>9l}zf|JQXKoYE5G;ijQj;F4bW^IX$DwuRpmw+QenNK~o>BzT#=IjY$Cefyzf z*RxyHxp=rf@f7_qxPC8+W@PRVfMp7wEPjom4so8KQ#Uvoe8 zv!`XNa0*L-&<=*4Wrnhc@&uwe9c;KbgxaGRwysH*o4!Qi8%M%oPZq8&x2j{O)(Pv+ z=@m1wt&l%bo@6LB*;zH@k7(IHhVN&$vH$a2J${s{ETP z;UlDN8t{adrN-|5*_HYnJKL5ip8TRGGyCQ7D8~)O4FTLwJz{xP=g$?In)=61Kl5MX zsXzJ^&T%vQ>{f2JkPn))rTc*J(k9#P{apz~^WJ4^_~}1(36K1JDm`D;dA`JSHM5%Q z8|%+ZOsXx*JF)C)Q_#CyP5147`*VL-x3BbfP1T;BbARu?gU1$6soV0IefP{#*|L+% z601Lmo!`LxeAoM{JhHaR1*dyXK8}6O7k!0a=Jds@=J)4(S)^jQx~6Z>+gGyofeh|% z7tfDcI>Xvz5A*A)o)aP)LqqwxXSay2ik1qCe4Ov*#k2JEmbKfY)t2Ad{$J_r)v{NU za~@@DC+S$2o7s5ZzCKeyvT4e#s<<`lb-Xq6AEd5{D>!#8zv<<~;wP^^s|yY&y`NTK>T3qSe@MEj@Iv)vVdp-A$>CxERUP_nef3)b`e|A?;@J@y27qhN5BtEY5 ziqNs{T;gdkzr6Xfp2@x26^3lRzkg21KXqn%{C=*jVI^trh2(uBo`jfX9@5km>Zh|J+dM?-ymp+FF3YrKPv(4@yZfvvzv%9x%F9h9<2B0eh@0Ew zEDefgublZU#wyG*_4kv|)Z;>yld?kZg&co+HBk8aJ&xed<)YDHO2+e8d3U@^wfh#f z_lC`~%8&~s@A@~*$`^9?+2nXn!Z-CG=eeV*r~Ol}nfaZ`ED4AY+PyRS_NjZSXJ1Q| zm8{X-W;>-xp+lG>wH?wOzVT)zstgWKR);~!D8?Bf4N}{H{;S>ryuuL z%~lpY@N|J|VMr2A$ZKhqz&cJ-W_0z&)p9400<9RBd@ z7VE>dV{0YrpZt7bF1|J>a^=)RU9!*4P7Bt&IrGNy9ozI?9h&xLN#(wuFSftjDSUqh zTj!hE>~`OO|E+XCU6Wat5FWA7e{yNrL<{e;l9ih8o$9Xp=6HF!CT{taQe3-F~ni_d?-uce_xV&_$)7Br4ysuooDu9JguXcj{Xv}=bhbW98}i+jq29<>vnlgwcGb^yK|E!Rqowi z_3h>B>S*)$v%*cM!@?(vy^;~$bT2kO@!CG;N(t+&wZ5zJ6O}qPtkuZvl#Q4-MzShLR+ybNzRz=O}mA<{{_O_3A;}@FC z`zyDZPYk*zZQ-(cnbxK96_Dv#{X)Ne`>I(FbU!hkXUx`kRByxX8`2Bn@4L(v z_~$x1RxG7GW%Bdi3cQbfD{F=G&VBg4x%CFuiEGV2KVF{wzTWHGZ0CX#lg=)`JUez0 zOQb}k&HTRnf14}2?&tp7{qxIq^ZK7pUa0;~*#4i*u`x2pCTEd}$E6;RSEtGy=PYsh z=xki_-t1!C=PPo@i&ornn`RK3x8vrqN`d9oGrxUI-J$TpjC1qZ;EOU_fBy5Y-?Vq` z!e@D(Ln1F^GoIzx6}RQNc1=6O-%GbUx0SB(dv9jwFyAMARwAdti8o=^uNGPMvzFw| zJ9n&U>f4Chs&=#G7tB0ncwt}2*2u`J)bzf;5m&x;=Pk)uZ$2Zbkaz9vh{}~xn?=7d z9$utj@uuw1hE2A!-ZPk;OX{!w_)g)-BOMnr-etQ>(+rL?d~Eu`^6l+z-UU^!O3!^< zs<|$7rEvT7*%uf7TPlC%>Y^>dw=O40z13UYXy&Ng8hfC?@RrTiWak_ku{EN1?#|PB zc5u-@nYPZvjsN0o!%IyW4jcX0WF6*q+N@M*W6jj~zYHmv^*8vb1qEokz- z82-v=&xHFGlb1DfigtfpqcHtZnX&Ks`9~M5&C4lR*ywJ<&F86ie-=Tkqzx3fyxjtb@7R?NF@$iFKQnuA8@e(Q1zg zg-kC_d*7bKcEt11Y*(4b!R>!r&DM!VFG*e-Eq?!Eq)gQ$5$@M2yq$sv?yWqy>TOQC z_2e064VfDau5hTS^hG)$7t8K%`ncfRg^dZ))pLHoin@9rCFVB2_CpqS#>h!*nO`hwJ|<|)=m-+$e-b#M zQ8hpy?%#8RQ`1F?9oXBS3A!>y27FPTU>h*!V)gHTZ^J6NW^Z#_pL5V+`_|(s9XTDo z$_qVk&-{x8J|HuNF9~Dy4U7{zO1UAenFSsn`6{EyA z|KN{Oq4hosx4z!|mFFE!t_Ym4}|QLe(^`xghtxP|Qf8mr{aXzG1Q@Op3i8M$^ z6LK@`r-@5jOtPqEkGkI0)55JQ+pqXnA@!B~24l`<+msXhH(w^a+Z5~&claE;h^}?I z$n-aJF6=(<$56>6AAjf9#%%{(CH(pMHkM3i;AFQ?F8Ruw$|(@%z?U)OwK<2@i;km# zHk}%}Z$v-+zV#*Jn1txkbz6Gw?PT~fWdUnq-?H`Rdie_MTK~?y*;lLBD9m^B#Jfft zU?)0R25f%j; zVq&3-cCp2DFHyR3SVm1I_PM~Jjn-i$Q{HVhw{1x{Cs%a6@lNtOhdoW(`A=wOr#}&! zGgn}83~TWFR}ow1{JIdnSog%8b!-1hRVV4O=4$>D-RNo(apR?m+ULK;)ioO{UM+gx z+d3hKefHdm2dbmDP52{aeIcIPO8z8c^Ym%2OvDau6^gt#UCnSc<37~{-W80h4C!gC zyL&G`VDOvyKYZ)&%4LovDhJc-YgN>Ayj(0;9$s_3oqu2WPW0bpyc3+a>lwTKssH!m zp*6R0Mt=RoPczS4y|#AsiW;U`b2HXLHBPPm?GNndTUu_Jn^U$S_3xS`4IW;n^jI`Z zb&KBhs;*zSobO^-Oxo4^AK%BU=>Dr?+^LxTTg&X*BH1~a$EUD~msaN;kF!o&vL!lT zd5Yy~zum5jH=ew8#WBSyU>}DaQ%v*O1?i@<%4eHAxfD6+n3-7Ss-!61j2xsvwtM=k5ZQ(Azk|V7+_bj*Xjmy2q zxy}|m+|`xtAagB*UH=Pw634x-ilrBrGzd17C0_05m8i|y*Lufbf1jv#j zg-K~v2d|74$%(rha4L?YuL?J69TD<-GxR_IkN zet1xS+t+gOS(<;Y&ECLq?wQ9@zc1(c?e)bcxK*!G^%XPdW*2>?82F-H!F0CZ+BF>y zQtX@$ecYtfl6^b6@BS_6CBd&QbtFlseYWxstV{eo#bx4ZVT(04cE9?kq#eRi`G4;{ z?IZ8DDF!vSr>WQ`1T6g}KcfnNgp1$Mj&ciDfRy>eDVt5=iB>3zCYjrXgB zr!AVVD0;E}#}1{R2AoPWtQXo(-*dobiKY7yll_a{5b0~TE3*7o0&16t&mB`YjJ{j?e73B zkZ&JclF?lMZ+c?uA4T2=`CfHu60Ma!pC+&iMtsopwBX&&9cQG)f9&3!#D#&=3QG^{ zcJ&VKxKfuRt9)};L3E5+>EdURS7hx16Xf4>F4%O%bV1%0XB+>o?U$Q0cpbUAdT%-WM>- z&&IA-#J!aNfz$aQ(u$yK!~wo=p*@ zm+kr=X&j82QvSc^bwIVpVkOrcFGtSVd-|46bGdg_cKQmA_7S`^7Gj&I34$NKfgZV8P`FY-S=P|7^V5whQ{BFH1;1JQ-;Zg!<2N%W|7>6Wm&^AY z&$uhxvux{o^;OH(9AE7EwkYnqM#|LG&k^xG?4mZgoqd}bZXY`2dZz!_j>F!1Ph~^w z&odQGXH}4~Z8tdj#mS<48uRSuVm|z}9tXA;Y>E_VNeF$U(<;38|F(YdA8XAMUlyc$ zRNl1TCUBo^@}@=GCs=qjA5PrWH*=-Hg!U!L8w4#a+B9Dt6^$wS zl-1fUCndGFz0l=3!tpCsY1I{P_Ss&qMJwN3QQ80bU2ppY_O})UTIv?+T1wT~(=ajoVuP z`?~cRA@{{wS1-MIC$3B2-1<$fv+pG>Z$G&D)NUmy<%wPS=lQ36d340t*l<&2%6-k& z$#;tseUEP4!5Q|?W;VA=e)NnTZx!8>e@}QjH!AtIt^X1In#0d+LJedN1V#fDG6`P&vb z%e4G_d^>{w**(_DO?w`-y%coan=~sp=8mE7%gX%NwD7Wd?|-g+)wXd%_`OfN*3OJe z&A#At?v&`td2f%NyHiqj_2Y|EF;jDIMjf=>_uNn=u|MC}cE;`+@d?*I{AF}K{ax~? z>V5Sua(i_jrkhu3n_}86h-kQCu_x$8;?GyH`?fmZG zwA-TN{gi!cT4c_|a&n%?R{7GfXzGQYM?p@)Rp0&HwC(vBlD#`uK07dZxe>Fi!m)+} z_3tkFPrYL_w|K%m+kL`^cU@j&K38i(_4B^OugzK7X3CQpQ9Nx{W9+x*%cDc;dXK!DyqRhCr07$p)xYiAd&7H^N{M3WAj#f%nR)@ZN<}&xza=z7RJi!^yFV2{~ z?fA0}`x^pBt*TCMmAtNGmOXdrga;X!@itq(`Pd$-mZ;mcT-@yVj>XP}Vs8##4W7|- zVUk!!q8B`1hUw|&W?-l0t>JiFc*+H z4&$6(T9lbu1R6?#`3vL%l1w$R0G+P`nni#u+b}agTDD-Q+FNK(> zpkDxTMly&f0J$X};uffd3i@D63_*q%f=w}m8Uqr_$TWn`ID@8XOY#dK`+DFLx1c#` z@J=7FNKs~bMhU7Sun?-MjQsp;kZDkZL0qsZ1%2331T-#0aaC$jetu3$aYTnCBm>@Ah>!#AFht^ImZc(i#hDdIoYb<^JOmd!n}^`$W#%Digv?5VX4b)}0<^FN zzS9x9;zU8exCFEzuOP7qv{oV|6SRrbk*a$V({e!j9P<)OO2Je4#Tki31t4dX7A2+T zKXms=oNq$y`J z(5y0%Q_g6mB4n5xW6Igw2+x$Wv5|tVk*R@#p@E5og1IU9JQOTb&Y)ZXTW^It$83r| z#|##LdC3R7Y06N++|n2}lWe45XlQB(s+Xj6xj(-EDzvojc8+OnVZy=Z-@Vn;^T^%2S&9mMeu>z&FN;U~UYdo&5;;`7ueeG)Kug`nq^Y<42oAdYW`!|2*zR#Hz-7-h9X#?*D z7H3Yj_LG853Nw99B{F|HlNOw@ML{M^fTN8~na!1TW$0e`~4R- zD(IYTP}a<1s$jXO?pdR7>5#>ukOdCQ6s~NTwq{{SMuSYSl0jku(?mDsYkv1U*s~v& zNStp_RBJuVD6mN70GkD?qvje<-=L6I#(&WgmzM7JPCZazrO8#{b8-oDkV?x!DdvU* z(bla}%DWFPnfAYJ&C8>zR=sIkovb74POaSkR5Z#Z`A2hO=H(KW9%f0lgBDhErI`1N zsy8dTET21Xst_0NB9VofQW6Fh+B4pW<`=5OHoZvHU{aitwIC?t(nC!dw}mggJpL~$ z*>Fg8;X=cW^XhBfh8Q_DE^t|*$jkP|{${Y@^Yb%IINiS}%sZR1*-~ZVo%ep7muGbb zsxihOUe=ENSTHO0&Cbe%nLb_v`E4+j&X^xr9#5qZFL!_4+;{nQcG1z$xyN%}zU20G z-?nZ3{jyU}PRqReTDq&x{GSYG{g>U3&5oa+@R(0kIr07m1=02WRZ&xN&rRCA?W@JC zx3Mooe?Q$Ur`0YzDJ@1XJNvIgb-{7l(~k~raXa6>e~10<*@gGE>HV>5kN1?-oD*66 z`q^srohxTs{n9CmcYf!rI_1bFD`U)0~z-DUMrfo1g}ovYKItG}s#_UD#U?%vCD zt8U*@F_?BTCvV1|xO0h@zpnbn$MxLdRg!nk%7m#g7S#*Ce|=FTyy4e{^Jhg~-@hZG z_B;2RgLtgLgS4vs(X-|U3rQ*WO2rneIpi6++2OHxknh&E*U{IvX8gU_&D#7m`_tT@ z*Va4hj?UR~Z10w>Cm*lR*sp)n@^j{+Zt=b6?@O%I<)0dv7q(74?MTHh8IBVlKFBQF zzIo-g>-!9LAJx1Pe^=RsapB%F?cUs5^O=5ao_#z;A*?|8{L^O__3!A$oT`1eqb&aC z@lB=k?wig0uF~-}tZ9e$3-!NK`~Q`$JX+B(TZOahy|0~g<>}8wxrbXHbc>MJ)$C7TinSd1I}9)dIWeZ;#BDh%bqGQM=*y()Sn6 zs|G$hHc|OrcTsNpHXV<Ap3;`r?j8WQ(rs{C%`;^OgOrQRNTileMoK?C4u{ zLT8)Wxy=`L)i<%3hG%8RW=uH!;HiUaSNqYcI>rCueq9kYHD4~d-J9dq!*KQ0UW+1k zHrs0ZSMC;km^{@fWM4>C)aE-`$L7vV_V`opiR zXMWzREu1*3VN#JFpJ?Z5{$+QBzqh2>^Z$6r{KEG3gNv&EN#|y9amVqjsy_c?inw?C z`jW1^t?w3IEz6v>%A5P!iD%a=*4FDEd06mU^r0#H{=09FzTMNC`}j)gk<-^dE}yo$ zCfh^r5lgwWeCw8$bf*^+#dL1emF6!Fj%F~{_YaY}QJQ?c!)oQi$5oN{)@MxF(`a=u zY->HAx>o)34|ZxNm`>)Xon-3vF<|>7z@Oli<9TPH zhu>tG=>K)EUvvv?mCQ+yc(-`h?p=NBS9Sj@5nfaCgEP$Y)l%0jr#9W`{#&rs_TG=x zwtxPmXk6<`d3)i{hl0fv{4ol5#&nKz$zF8zG`mENxS+6db}ub&-f zua28i?fFlgU2E3=+C^zL9}j7SvD?WU`I{x{cJS4^$A7gxgqL67pLF%EC;#=g0q^-2 zzw2wiw55IDl&a8F&I)&lH48)!#m%quzF6`%a7$X{_RDMKcjZa!Enoie!q#VZ5?`Kp zyu~(Vo%~x}v)H>FH_e}Bv~4S}2?#$FcT#6cYG{Ay^=Z9wLEq~oSz}~wDz^SuT`ExF zI!R-v?Cz{AmW?x*zHXa6%d6bXX}e3X{A;lY!{W;U+pF(Ze*SvBtT~y#XMN|@jdrti zUzJU{d!bvm=;gr-7MnYNBQ{A)STtpWM*Q8k>bfy9sShvg$+J1V>AkM@xtF_FF8y2j z--35>^=wx`3CEK~tMfY6yiF;;@;1nPP1eTix&N5IFN}NBk+N;k^t)jplSQf=q8Ybo zU+mr{b*65us?nF}Ns3Lu`W{^w)89K<^Ily0Zr0^Pa#4EQziYpl#k^EP{pPOfxQT(P zZi)w4ZUk-0C@p_+Lw(-Y1}nbVWzlbH(++K#$#byyuF0;whKr;3)W3hb2i49=C)eC zJdtqtUo-QYj`ZhO->#c{Jm|L!pXbNSRZCacxNEGO-agSWYnR`YgGUearyh4&RHy9q ziS3fYs%d2vU(Nh<{)o?f2ggo4df`|4$wOK%)oT6ktu!ln>={?F z^X~hvw#RIroGnv%~IBv}s#-&%}w}?s`K0+IO0)&r_V921@uJ=eU`;;)$mj z=dBP%nIi6co@~5MSJ}>9x0af;sc3O%U8YCzwDy(nXGbMECO+k9z8z)BW>`Kn7!W@qi zX-h?>2`Md{AoVCJ*K^0}TlO<81wa32zA^1`g2iQ@S8vPK%)a!gYvH!KFMlU&W=r1q zORoD+<+KC46nI7Uozv&a7Q7*IYH{ZKWy>Ppv#^<1Fy}0|WbAwM-SVP*jhjN}w*{H` zz3mOSxVb=P<$Jckg@&&GYLy0Z|~)h>F|$f zdR@=uk?#9``MR7o*OJc{)AuNJrF9lPSw3UdmE%E^9{yHntUYel75wDp6)Wr6$8Pu) z@+u4WP4#`8!eY|mB~@}fDC?<_PI>dg_QO`D+kGv04(WuQduqM%%%Nk&YNb|gt8ejr z%w7MyZ|?TdduuCTRQ8Q}8bD$m@!>&NE_3s_E@vh1S9ia>LBXReFe zWUp9zzP&sn_t^sroivfeDHSt48IG-sE?J_`=q;snphEHC0^w)eg((+0{nx(g`>bAU zevS8L0h)kO!X|r?;d}`#p{IuHb>znUi(oezz5$(QvWb zomqduL~H7?WnEVj-`N!xBr%^`e4D*{){@Y@i%hPl%(z&@Sg~F7?~6I__nmo}a85R6 z8so8p|Fvy%a$YPszxtKz?CX9W3{@syLw+VdX`epfpp72Zr7f1DZY4SyvNHJF$tg9yb4JZuH2ORdh=UoVDhD9{k7bUH;fs2 zWapL!sX9*Uabr(>=J+UhG2^3}gBsaS&ziDxCT){jb};XNTAwbf?wJR77*GBc{=zdg zL^&|?o5uFn+FDIpr7i?-+0GWU>eZHYF3KURdJq3lTHeh#X`${C&1ZhX=yj9gV>1)T{+veNDmwfuW zJKkf_1oH-_-^=b~wH$ZQR^6qz>$TLqNUeY+S@wBxOe+26TPJV7z;R@Pr|QO%8-6pb z*;bnF*(kYGR5f8%RovwczrMo6ljXJB0_H?c=-qUtJE(cnd%yjwgibRlFtfD@c$nti zzZiRl_k-u+k4AjO`n=tnE!rXvM3qU4T}+w3E0tM#tAOtFTaxR3zOSyBU$RP$;l}3V z52u;Vi8*|DyW`#zuJaQf?OAYQs&sUyi&Wjeglz5Qx;tbQGp0TC_R3$M?dh?!Td6U6 z3&;7Q%*~anGjHmoO<9(7LL^4XUejfVFp1Las8HyxO)lK%?`fto|wyT`cHXW~iELlJu~PMw_;pKQ17?C-jH3t19hq*f;7?0tL7R4hdC_+OoAbCsLs z+`k}gw&u%^7krOioP5}-Gd(I@?AEJ~jvVcA2UYafemC9!LUVt$^QB~qBl~po6-@2^ zYE&MZ^5FRNDv8|6G})BS@Bf_+m!4mopLQ?RaMIAixw|hWab%vT-Ro@uPn^ka_K`C(Wyz&Q+__&*VoLj zRJS$y(p=9&b$t=*wn z1Kp}sRaOfg-@RA2*u1`~sCSNfNBi>S=_UL2-@T_NFE1MR;CR~ezmGm{41dzN@aMG@ zE6;bW28{W-4FRUz`Kr%e3ZB;cb>_gy-46<9-}>>X|EaWloY<}QqKpu(Z7-Mp{8?RM zbK+g3yiL#5*VjJ;-0${Y&D(Y3G{dArSK9Zp&U&PN;{mfzSc1X>i@1;{tJ5=n$1w=m z+-oWL?eull?C<9LBM)EOy>{+fJsoQeBmL&>jmrY>XB#BevHYE&y8qgq>$$3NCGCsV zUv4x`TH1a0p4~lZTNXvO2Qe=lwGY)qFA7cR)|cNGyQS&tqONrsWaTppZhudgUzK6*3GkCEj;u_An8{RpZM0` zKYk0kOJ&}4_IKCt>KDl}HS2sm^y9R`g@t?B8fUrOJjuO}f6e^;j{eWitq$LHR8rp5 zQ)`Y-)a*k>ohREedtU#(l5#R|^UB-(F-nheqnlSoTQoggQuxkzuY5q1+KWH-1*dNq zZnvr4c>ksJWX5PNzbjS@`Ms$jkJQ8Vef`n3n%g*RX;n&0%xi^h^0{Uea;9Hr7s!=~ z1*m@Jyxb)5;PLv!UAJ=I=9G78MLv1znbWuB{u4R&9J)Pw&3$y`%48)0_C2=Z?uYp@a|m zUNLXCm2KNSrD9fP<+NGKQ#+>=3GfHs?>N45#{79%>JI* zM#pysZhyRCO@~Uei_Om>dF$&pzpA9Yv%epC`?(a)vsitRx@#9D`L4cw{%-L{uiuR` zcGXW>c<%Al-Isp{MjbCPk$3vx`7kc?-O|E;;-c%#qi^5U`yzLUTh-U8c0pC;>31h& z?6fL3x}0O#6dJ(8VWQW4-Dy@|dN-q#QptpQs|y^hwAxx$PXB%K(*M0jD<3R0xwG?r z-aqc%r!(GT_I)t-tOtP4I>0p_X9}9YBW*qoH2sD=H3T*S-rvGLA4hQBP2UasoE=f~ zai$kDJcR{OCX5G@-TAynE4% zpMBP+%f!#+F#rF*Li1TyO5R1A_!yhIw?{X(s4g^*UZc-+K6&9`)A>hVJ=%VAYQ61m zljuLMA6-3k;&YPNk$RHl$9qIxBu*%AYpL*^VB0|@4x4k+Rkq8Gu~Y^?@-5a z$4O__$=NTvxhMDaG`m+ix3}l+3z}tDceYkJ`JI|hU-E;4kwPq9$J{R8`em_s=WD|N z4|V#>&Vi@w0asOHXI_-E-As zchy#Lr$GPoCH2>r*G?3C`TOwgJ@>Dzp1w0Iw_W&d{)#i|v;IC`(s^LlnPR3L7AI@g zu6SnifPRbv6 zAgr_Q!Ht5F0L6KC58p_X*^^NIaFdGPmGH`sA0LT7E<2K&x6!)DdirmjOH51mJ<{5` zDnDMgZq@~nJy$yaykENVQCVCtha7vGbL{`x&kvs+?w8-XYrZ|d^Rjhq`?@whz8Erj zTW$H9(=Q*)>&hyww0x>`F3&3Z_h~^zx(b8U(|W;@2_XQ zcK;t7mh_n~Fk^$lkIl+GDpCe7K5@O7JpFRP;l&-5_kKLNtW;CLAMRb@m8ibl-$C$T z(;ux>i}WvT`XPV7F;-GxlEjpgCmEXpME2Jdzj@es+(Mwm@qp8TclpHyM|1KDtv|h2 zNs-yp;;|%c{lCxuz8yYf_i*Dfn-az18vW4D+mW%A9EKA8Q|~@Yjl3UwH`c$}`Hl0Z zLV1=26?bc%n3spgze-ZwA$-aIhEhlC;ublF_$PI%HB~n6Jeqq*XwK0ClQNd9vOny) z*Y)`xW*4X35ij&V3+_rSFaJAbS#SLE)mKA=rewacm_E;IO7idGXKKrv0$p#c^w^rM z94PriXTj!u%(izJHnF{Uz}8pLY{tHaMK4_Ttfy#UM=Mi;R&DxMuIn`n&#LcmOxBLN zTVACsC*f{zw9#6O&qQ6xnN#DJZtwi-rPnuqXWtO>HDyuNWJ#lj?!LQ@AIoBntW3$e zC}-KcN~hE1*7NtSQ>Xr(TB>nc_y|!IiYoK;u2P`r}zGsf4uy5Hzz~kLq6MobFV1prGI@KFty!A zQuD{RZ%^0f$KBNouw`5+c%tlZyYs6TZDBuuy$dO|TC?KArt?!mEq-yQiXOV3viwu{ zLyfgzW&*N@ofkT;T#+#)skA^!;m?7@K4Ci!1)ZopuN-%HHz){+&N+W!*JVSleGhl- z<9m{H{YtKXnalkdJ^y#!GBG7v2@^0SdMG`IWRk?fJOAyPu9@W8!}87rhN zJ-oPa2gi)YN9jj2yEaXUgYKd=iArM9Gv&`!k zEtWp*I>)+^^YUJUqKx+aO1IxndlV=7yGm&DaWk0(uUJ+&9v5SsckG9|#14g0POA

1MMo)2Plw-p66}AysjaBFBBB(=FrX-HE-NvDE#0gT&7Z^KWf5X_z3M@j$+A!TY3k zVc(hWKdoB6EbgOQf5@F;+xeVA_nodx6YyKJYzoh_QxeYHes}#0cF11mcYoJ8nNc=c zCz`SQez}0r?=43z9O|s@{IrZyXmVP8`1>sywOo659yyz--B`9*bLr_jv5&Vl%?^-M zxi%|g+pM`_+qUv8PKa3VSoPj3H>c3dluI{UM*VSN_4L+xSt>LBL~v+6kvSD@ zvOHm)hg`$w=PE)M`c>r={|4N1Qgc1eygzoER&3G_rG!U&7}+!R=DaET5XoCB;NxBJ zU7g9^HB9$f)#tFsXSoHHf$=RXLPVOKbQC|P{&{zosnfXc|t(kLjDPjpS=pi zKCjwownI&=y@9nz@m}=e%2f-m9%WK3NqxGY>-d3GB^&pzGnsPPEw@~gZf-xhY|&A{ zC}Shpl6i&#atG9{q~mINhjy+SoCV?-1e$`voMo4MRU1qJrm?Q8A|SKyZp#E$?bDPU@6n9 zAW3gbgoj#a?v!@39h$T@*C&LG~>{&q?ooSE<)+Uvx!5?W_01MHjCq zx6esVT+5?y?erIgVnMCVx-P2?Dgz20%3X-te!xxh>y2x#R4X}ZDza0rP3%0l=gwR0 z$OqXQ!plWv!~?_5=g#{WB$dGTN{YeBtMg>U*2f0+6a6aQ7^z#{H;FJ=eu;CUjl$eH zlRaLh=zP?e%k%W^gN?2CryNbY?aj}aar|8Vy|>eAX9{~pzTpa%?Uej_=;5q5-3My! zyTkr&xS#0b(e(IHr&Zv_^!qETpZ$8s!nzU{O>mPhKtu1nH>dlAMf*1JJeG6`uqAtMm&w}S}8*t7%kvj2pU$s_C{lm_A-kq*JE=8|j zF22C>@{6K!KkL4Wce4bpoW50ZSnTJSkG_Y^UR-Y3vT9yX;;-$Y4hwJmS!FY&{?fBx z*~RKryftzzg(0C8dRL{Artsg6^7TFM7!`W-h>HKmm6JLo(w4qiG1aY}Wds!pK*pj%!4BFVoqyXue5^5L5Ky)ZP^*&LqD$a(1rH`km)zKA#yq zpDnzRYhr=h@1vJiwFW%Cy3niq2fN;noAOdTM=Y#(Z+@8^cVtgO&eCZHn_oOR-FoKe zk6B$$o6lWsE;`_KTYK&Y*BdAAH{QDP!D_O{ne#Kw%+Q@#IKT2j!lT1m&kL!GJm389 z)MMX`TUhVZP4xGBH0|jA_^vM^(c1Ca%=fD)V_Dp%89;5*GEzR`ZH( za>Ks2Ufo^wuaaE36W(n7Q@SiTbgOvR_ZxfsyEj?eIeIRevxeWbglB&A6Vun<_O&HB zZ?pLM%Ew=R(+$pLT1xg}`==ki5cT`%qyLM9b+=>ogFvhNU_AoJ3Oxe-AkgAF()vN5 z6>%v2AdnF-J3*%);XmMsN^22KLC%2Nil^^mWNK=}g|zHr&RMDK{>R%s?yhfbWV(Cz z*_n>2?8`C}y!A4hw@iPqjiW2C;e=7HwtBZ+XV>Qc`<#QEgEnn>efX{D(T5vqc+%Sr zSDt<^Uh?vw{jQ!Qq4a}Bo&#|KmOcSh)q-JZhm^V zOpuka(>CkFom{d1k#X+kr{kxU-`9`3A6ez-wdf0HlkFMHqdA_MCl`54bUCN|h&|E& z<5zk2d(*zYOWXNLIluC6>D2AI+0}M!Hq{KwF~_&q|Nfdj|KUca{+F3?`2{z+mT9-# z&CF|foZVI|aUr8pcW1)0Gb=@^iw`Jh@uyBTzk1kf!!lEjQ&P*K4=5#ayCv98c>Mm< z?4|rO>aVjz)LJV>dkMYL7nWBMKE5vFyz3graK1H@3T9a3-{RiEe$naDclLEj{5yWl zG{1U`V|p9&!$-j@i=&yJPGb0(Uc&OzTHm~5f9fMSkG#D6#&vaJTkA5)znf1k$rPD5 zL9O8Q@fSh-9BFJGZ%&t`UJPtM*s~-viy_`G$=gFHD&>Imrk_g>zZakU=9r-8q62g8 z`aX*PS?DHI(X?=Z+LKM$Gp5{_lat}L=)`}6npVLT%Rj2HAH*O%wk)?+@V0 zeqiw^*4R!x;_kFN4l+NMt-i5m>2VXqxJfZ7FZtfgS*qMI?a=&FyH;3Lp6A@%!89r5 zn%=*v#@IEM`A450XW#hT^rn3O3J>E6?;+jQy8#I-Ux>6gwt zNSPziG;62)|Jv`{&Hvjx@AG=k)%Mxujp8=awrvfH>osfRdEM+k~Pjf2>|D{zBvbn!fv5`Nga6+ADo>ixUdm z63mfT-JaT??QdOohWa?4a0Sn>#wm1v`Sc>eTv;>efgoW&LdI{B(flX!ZHe zrFicS??K67axAzcDN*D-MYw$Gn+sJ-=kT(6tkyiYXr|89o`kFiBhex;UcJrC?@P zTTt@8OCSB#Mt#YxOVB?j7szH~{A=xHol}$7HY~BqH{&bUy3Sg?ukNWwX3gwPjyxaK z7>}JTij8#pr7n3wpmfvuCx-YSJN!!grnPQ+ICuqi?d4#avi?f&}Lck1=s zdiOK;PN{geet+d}=j!_886_6Nzhh3Qw=Qh7fBSi3*}@dRHTCr(3~Gwfan@Hk`{GV! z_ehGiGjj z9u!>U|nrw-|~j(4RfO1%h@~g z8B!%f{Mk5oWuH{aY_nif4dBqZk#u9vXBAchQTt6hw?FV$u*E+|F7}tv%cv=Dt7q!W z^5SMK)1PsbTgJnE5s!TL+fV_P7PrSK%SHKb&Hvr`I`Qv~jHY{9pH?N!J~=yG^5fju z{H^crS$*IvW!T#OSy=eC%Eiu4K29$_`TV@sFiU-+^#NsluPKfBPJ3P2?kY@oOFGou zGQ*VHzG0#L^zOPBYh%Rw-V0VSaz6f8ozb@83+samox;7*8_o8vC{tbGIX`9QhYy?i z_}Q*Hw(c(4Zay)Qf7K)ZBMbIq?OeHic2LLhU-6DY8p``sN-Q=MFJyMqObJq=F|QAm_*X|uHYP{zET%*$nxKaX(dO20SJY!}fv z-DTc>#a}JQZ0V*Juk55r%I}{2vYI-p?7QcZt|*m-ulCNIz{jWLF=v^o^_rQP?zy{n z2_#(YzaggFR;j^0gzfLby&h%WW5s_`7 z^_tUUk7jvluH@l_De`??+E*1=6pw%7Zga``J&kSene!n_)5ZAqtbWeLRJxpDlEDfo zua1=CF_HaIf#&=vT*nn=t$e-W>$W=)62*4M8x3Z~oHpkbl3%>+mR(roLU|YG=!L6y zPFsE~!|l+!^}cUbX0!XAZ+~K{|7}Oq)UB2llcq+!jyi2JPygGksIQ951qDy6G*4~| zUT^cOY4M_@Rg)!JyVE>;`W*bP{N1`~g{p}+^IUBXV_QQR-doIFUh_*I81B7sRW;)o zM_uQ&Zun6|g5DC^VXz-n$w zU0JWCxnCPwQUYI`Kvdk{jTqYmdfMK znO;T$PgVPlH`;D~d+(!yM}D9F)D_Ft3i(|BzV^PJ<4mc~(L7%t=3ROe{&(lX&b>92 zf`T5WveG(gEEl*XBu8xw0m}~RcGTHwcUjc3~_H*Do^Na zm@;dU#P(fJ?QAaIUmlSoS9`N)2}7MhPh&-h`09wGyFBW5d=8iU^Eo`w=TXnPNh+!f z#n)^*Zk@liegAx(BfHomtqtzm-`o3Z;x)xdUZ#t^NnEC#cCKsIhYl8#H{yR5YR%sEZd-1s`QKa4R(r2nF?c2K z|NlTdFz+X~#FxESov&Vfw%A~!{6(IM#SXK7J)0jI`(*QtmHwNqm^a*RGVOKK^Lc$g z_VO#U%e}z|T`YI+N-@~rAzJ_Ww7W`gGV@QN!sQ#bM7}$~Bf7?9imZR@ji?2iqKt2F z{Cv94`qL#J8`~+dfg67P`QpT%`?hU=gzLpujVEU6#M#!whx&Ton|s?iHLPjwH^yi7 z%U3;&vdgyT^_bIeqxOca!@`byevN4!mllg%y?by{Rz|W~@xEr$nuPV*x29Kp`Khpt z{cd1Wz+s2auMXDyIg`8EEO(ox&+fH6F1k_ws@(S1PWo|B>!b6Jud!d}t=h>HYrSE` z(+hJlD)|?Edv;d(fnZ(QRn-#?u}+Iw7KE!#7wWNH@T-4U;RDw|!7ERevumD{ERFiU z;+ms}k^Vcuf1=wII#^Izu(`<|AKjCGoQd zePnm6xO-l8zP#x7HDWvyCf70VjqCoJ_II()k!l6!m;0QRhpYjbR9$-1VaMPM7+bxEt$`Vrb&WbZUsVZAFsX)l3Q*E31NfQM}kv^YK6Xne- z5?0u7>|S?g8&k(5G5fy5^K#}`Z`>t%dtE|b;Dq1nb#$6?A}iOQOJCg{_3VP{-O25; z)1I5=&$|9OOyA36N`(GTYm?md@%q1CT~RmLcut6Q`8C6w54+0lN*VpEFt{R`6s)~} z$&7W{sr&4@=0xXI`9G9dypQL*iMVyDM_B^Hl;y7Sq1*P{TX{Dj`f**^wWkZ!7RHpP z2fj<6qs-Oj#be*OaHZb#zhOLP_ZNRr+W+)`pN_-(?ItG-N&@vVG^FfhSTdQlcg+^J zWR*6)b?)9mQ{4`^uwd!t)v~kyUhcmC`rhjB3tgLdwsM_UfyG80u6#e00Z%q{%0@!zip z$C|RQ?7ijrmnE|4Raxm_Ly;rK%Z^un_Cj)HxYhEmv;daICrH)}u*@AI~%iaZPb`cD%NAhHDSst2!KvaO_MjW(O>(W1P7tsM(5-CZ1Tar>_=-(|cb9PZA! zdx|YhOhnD-YPbF7X)iTiy+5&(MQ#KM94mh`X^SLQqojhvJIohiZgcxdQn4abvmT8XhHn@=FP&COW-mK=J7a?Po!;hmZ2!`H?(`g%6#9Pl zam@3=q~njzh-}`|@neJ4*W^iuG}yP6zr{bJcxGkw*vgAWa-R`YkR-~4%(UXvMb zanj*5r$ae0^LCuze5qT=vtsi5JfDBJG+!@%^<~ks+uIB(HyC_)e@<%wyZ^SDx@HZg z!vz;VZe8Gg^sC&X|H0Aw?KRH-m^1(VT#lQnhp*gZkN*GctoQ{7hbe9oif=UVcphBD zU)_08GUd!;38g%k79S2ij?($~)E)eC4iG|SrO#23!FDXQn$`!>!L z=UY{ML;uWIk?YP@a#M0eL&_bNu6fk<$?5IdgQ;AHD-L_APTO>8^66PJDFQ~0^B$%v za$eG&dpY-r41=a5OGS43vQ9O2Ntss~h9&hulbab#EO*KFn_H}9%*Yh+sgzk^bVFI6 z)6K??dyU<~7;`uOZ4d56i=H^GcbHqm|CVp*!>AW^;bJkWTwHlcp;s$%4Q9JA-e;a+ zAfMEnp?2fi(qbR=veMEkxl`M{j`kiBm>j(1^w|Kxb9Yo`rz{Iyx7f43 zWa?aYG5URS2d__Zg;7 zThgh_ad48;ilh_oo}P@(yp^`2C-qUawc7gf@k4x>r=A5Y?GAi*B;w;%V5{*&k9T%Z*KqH^(o}GyM{;D>9?PKe)}zY z#TBfa6(#yF$-RBqidnxNiD|n(yVz4JZ^%{pyvrx*+yBdlxdh@K#fRB$Ti|{4vD~9c zw>G+#HDCJJv1#V1j}4phTOUiuhC1Ea5@Rt<(YxfSvPZP}wi{xnj@cf#z*f5~YtMSi zeD&Z52gI{ZE=$VGI{B{gmG=fiPsQZ9KhHBHa?PHhIe+c*x_!U(r`z&j_KeIxClA2- z2*^Dn@ZBuM3egH?pygIU3TEI%RH%2vn1R+L5i_BHx-!cdcI<%}Xa)m$dI4+~s%yaR z;L>->FG@)*QixUnb#UP>RFnZDwr7<>X{i> zDi|AD=$ROpD3}=<>6w}uf&4(heIx~GDWysInZ@AiNb*W^3o7-|ZovQt70JE;^&i0= zgZct=(~X6prGkl(nVzMQnSzOlp`MYYxq^wYxt@hNPEQ0Y=)32KAkE1uq{iGdMnZ}hBm?Bv_qKK`w1 zo%g6X_n^uFfB)I+=AP=A%RQs~KTkN_qsci*B6x{Y(7u@Z+Hyu6hM*HCpXyMs&2`p0i^?u{l2-u%z*{$6c2-b^6>cm<-a)@PQ1H$;Xus2i30tS;uCLv z_#3w`MyFCnXWc@=tUH5zT^p<~@&P28;o_>7eOiIt($>MB=+>8fJxEMDI6#nCFsBb*T8}R*Z=mD2` zx_Jv_HyE53Z`VHeNyj|?>Q(#obz8bQ1ty6^^+%;hvPmwO#3(Q8*VS(q&X7LsKpM+p zPo3AB<}V0T+2H@D#6+!sdEgVBvxllPdMvz;tK5qz-*juA8mC2x_4_-UTUZT}AKjT= z*z&-l+>7f`&p|%T6TVuL(hn|Z*{tE;;K;YUm`&BNd((|aYhSWX6Da@pWMgMV%cH5! zY9@IqIXvjSb7@bkcIx`bcQ<5W5(>0`2S;E2B3brXAz=Zdm}bnr9((S(&k9LSPY&d_ zUzXfoIl<_largG)`G;>?By@K0)=xUU=WWO4_3X=8x;`YX*{5^blkM|?MK;X$q9VT) z%$l0oRH+m>FY3UJBfF&q@{i_E_;~%_$~{}!I8+WLNj#X<6TSMYtxnLF-mM4j*I#Oj z+!h*~bE@ij*h#k41rI{!@yWIrIL9;SRMjuH3pV)4%X?>A>iY!`lIthzJ+vn#xIB5o z%N5D14G%x_JXj`VWudU+&WYIy54PSBaHubzQub)y3y#*QQ?}lnF4g`hd~?aGITK6Y zZ_|D|=bqqoO&Pz}Ti?9#yXAZE+Nzh|udEYE34HJO;3Ch-nR|r4zErqf6dS@ZtBHSW z9^dic_%Er!k2ikmd0fdhgwqxNEVU zb*nzU6*~BUk1zDi!kcrnZfZWA(bWHEUFNQNk?i3vzivr~@4mBQa_C--Yb$;@X1yxC z>G{h3#MgZ*PQP=I{bi?l;r2EQIa9%`6@{}L>iJ`Y$p|~uK69ex7>1#eDEN;z)*~B!QZ3Y*~g|Q_VxMLlnEaY-~G{fS>O__ z16xnnO@Df=e@62E7b?ql_MJ|XyPO}VWLWjkJbwkN)auu_g=fX>UYla=mTRH$R>f!j zv=a(!&kJ-JRRvS}ZFHk@U+R{ZMKP_3V!ERsaeCYA?2lGO>}|)mmix%wXy-rmVDY}% zkB2V(ZJp%h`tFV_SMWiXKlZzvzw3Sdw$}Cb#gok}haN~8nQzMDOSN@;adTzghSNz8 z-U$b3*?Gs@-}lwvdj{{paPiC23sy4wEHFIfaqM~1F^OmH7nP@;7H032^V{sGd{AO} z=sPu!Cj#Neer{Hp_`JcZhQCuOSupN3W2gD`|j z?WZqrQF-#c(_;$rrJKs8w}iVp+Apu^5a=n~Z9cnFu+mRuQb&x+#f}pbj?8{tTbxn0 zY1-L6IVzhseQf{lW_F;no3&o7XY;0q1+%8}GABQpdwI%l*G(ULJhp5qY&@cp%z1IY_%PG+Kta;7H8*wZ;+l|deB(u;=J#wLZ0G09xSm6)i)QJ z?O4Eb?A-22EtS!Q`zus#<<|1;+5G5X!7OopX6KEMm(+AjNL8w|l9Ko*_GNUZ?|RgYq76t^OH?2 z5t=M|$Sqv1FSPSz5PPJ|!tAw+5}1NB^{(!E81rI@(bL^gH%sL*^b?ORnl9_$cID@1 zmkKt=Y&LBdr3A-Wuh($$ZLz-mqkaB8;|t#We{A-eM+JR;6(9fqt+vIgA2O{PO^108 zDRM1t;XN30r((~9dq2c^vRu48EmCszum9fl=WUAr`pna^E<&ND>B-JKCe4KxwoJ5g zbhF&P%JS70Gyj~r?$tHP@p7l?FF#zFICt@pYy0lJo$hAjBUYuG`61v-?oaDl(W`#z zZZ+Ax+;UaN>XYAo=YrW!!zaY5?+u;mqIKn0?Zw-19dA-MS|0zfBGo%F7XyUuIOO}C!fMM)eZ$C(?-6js^^btNOv!A)m~{i^~x2UV}XAoCLWCoJG3q_@m=4g zqx~NZZptpx6aH4D7~Fz95zYd9e3634b2JR(z}mW-JMgCZVYfElr84QePPcA;F=N?%iM>Q?H7~DKlEd@uXEz>=c5ZfN;S<_t zW}I`8Rcneyyy)D07OO&zi#_P!KO6ey#g=QQT~nU0FAtsnN7}H$vzL`wHFJH-w}tBK z_{4SiW3?`AUcWOx{_>QMHd3~AbNZ1~uAu=f2^-&|rGbi${>@E=S&O32~p;x*)*_ zOHrFm2M*Y+tXO^4cBQ}x;f-ZlD=pL37GzJn?Kb^WM1$dhhNHIiJKHxMx_5VNozB`f zljF?)+Z{W&x%2QB$M?x{Z|?k-Xta4^u=nY_2L{Ja_{uyJ*x4@gEPR29-N|0xmhV4r3R1^E)a(|S z-?*v!w&aBF+o>DwW^A6nMVa-H>H2pY=3SV%cV>5En8`_D`Qyi@ILy3y?u6tD)fB^u z#m-&-VgkuR#p%89bUX+^`6SR*Z%d+lUVrt?zSNDKQgmt@#ys} z(4AEjS;WE6R8S$SK2cQW%Y5dLRNG+grt?BP>&@#gRIOdQGE3@HcP4+wCc`?}yEiTe z8fq-MHMQnR%btpz4v%jOhGfg^m6hnZ)_vf^$-Y-pio~m*-r8Myw@h&{K2iZubj1fUtr-9zw8(1 znvXRzvT zdlPHS?p2YZQ+4;e{+`#dCg}IYGR@G9@3ePc(C81o&AZKb7W0`TDZ44$g>tf-ef8o)rr@P*q%Lu;e?>Q&NwMXr%^bccAZ{xt6j@n?ad#f32Zq6;y zyw_$Y8sAf|Q8Z2E@$8JPFN9)Q)OQth`uvmHonPW*8tghVb?sr1uPaxJYz|p^(?D=h zN@VM!RonBg9PVtodQ-`?TS($pcJ_94#_;$PPFd2E7hH?k-5Mswuyy`2>HT@$p$s;g z-=+i|U4Q6Foa>VlH%~iw$*oXL7US@@zcSY=Xhrg2-nt_9qm9K@&N{xFOJ3cY_iF3f zb1zqfZ{`mFPE%kqG}<6mTbKlO7)qQ(Jnx&Iv}wJ+Xn;Nb6F;;oitKgZJga8XQ_fN|aW81A`> z_FLzkoj!f<`;|%jHx9465}9Rxa=y<)2LtAQW9cQHSErp&$ShY#?#x}zd3E_oiCdmx zzQwxMbFcW@@Vhbh2scE&xpesEfrVDTRT&@dd-KaW`C@Q>^`95-cE!oX-EKH$bL+u{ z8+$!%Z(jb#`>)FB`TWvP($-wuIp-v1KgnNe_y5Cb)s??fGpnaAUum-|#@=jeSykrr z{4CGvvZw2=MW*iAzF=Op)Xfw7G_e|k{ZU_)s?4udTD$ko-8?TB0#m{4&>vtRTmT$sG6nE{JnaV|{e;MRkpAL&F1BZ&|k|yiF@v-tcWG za+qhd;mdA0o4~bywmQ6+-Lbb=%Sc8;fi?5__1?L0-uh4YKOEou_|xQjY1c0F&h%K_ zlrQz<$vS)H##2Glo^R~sTlR$0W`c~%Ifn`SCO?}W8Kenj%v!GWp0mza_Svcp2|o7^ z)H7sD9KSii;rk!AD6Il9TRxko>)BLZWLQ7Rmc8;@{;c!eb4)uL&V6S6@Y#vWtZ}uJ ze`}OrOQ6So`)`l0)-xQ7SZ#_i3vY(Jcocc833Ye~age4NXx%3<<4cGUCD25F5NIwt zH7^A;*Z?*H#IUh}TymuVW*A#27(oV~^xa4s>(F;Y9I8p&xCh8da2Mbi_b@UwLEX4_ z7P@ioUHn6T@%Kxgc`b4AobAl#aY-axB%@2`o!uO6muW8}Y$J3Yd=IjLi^m&p`&v)8?ePCZNRdh($tas527P+};w>!=Ma{d3GqWUZ<<=u%r zyXW7jOZjM`$eDim_g9sao`Un+tKNlAuJ=#>c=pTpmw(SsmVdwd`^Fh9A76^I8LAhu zFuT>r?N&Oz?WfA8m0NUGrm$W!yI-DTtfwWIdh7P~{H=xC<(pRsFXLQj!tm5fNBh#U zoioqveD0gRUnkA=-j?p!)_Y6VEq%Vvf0DtKY!1Z@O%EqcjgWuec%EsJ%1M()Jr^`4 zy8Y+mu5XtQe{k=-aQ_CCd3J|xDBE%5sZUWUTr|JyndlE_}PTJXj{7|B@ z_3Q%WyL&$}&uXx)T1V1b znLY2@Hz>*2R9aM6{FG&4U%FS(u-8_6MXe=gp!~l%%pnJ!J!-r1$ni?=gvSTXWtgV* z?>)ZvW`YRojw-hwb5C5VRX*T<>P5-7e_ykW*G#MD2$`}qO>6b3Woy4j&JJHX^R%?F z*V|J;XJ&`JdGp!tisq_G-1P@C>prZL`LZlE?QF!tl3P(zBcrBG-I}b~rz50oyjB19 z9s~ZjY^ISvx3oR4u=>60e%GeAKI@O3c-X`$()XY7U(1In;x9J|yl{Hqx%=#jyB&?u z&WyF57Ec9a535uOZf`4cN|vxNlf5t1x%$McS|fv&9)}P6=XHm3)Fryi*mI^<{BHGw zVy{OEFMjU0H=X0%?na;GLBj0yl|^AfuU(E8&6xJ%--m(;$L(~|=C58`5gzLM`{l~J zAy2EXsHCo&cE>`cyQ5YAQH^g=agLO4p4+oIPs&W?*Uz})_iM%E+Y9^u-+y8@tHf`? zcV@NiYzEdn=4|^4^i>S^PsljayKU#h3rQB_I6ZhgwUpt4)L zPAChmu-TH={{7Dln^&$aMOCW5d$vtx3%ajt^5*7t(S-Y_6!nT{Y8*0nb@25*KjV!) zoBz*9*ft}#@cz|cT_can&oqNo7HOPeaj|IMmwmKd|HlmRLml-$jW|+w*cKkpvHEYu z@vy_iF)4Lc+0^OHrx;!p%0wT0_`6`mo2#qk*Em;ZuKoH}XA_Ui-@9+pCJETTWB>en z`P?5H*RB3_?u7p3sgldKKC*eX^XT3d(X^!4t7SLs^lw#uk5Vb<`Z9OH6t&KK`4fcM zCZ*?Iy6d;`gpj0^pPugXBkn#iPcBbR>fp%!>KyjAJM^cQ;kIjM^lbX4d4-o8kokCh zs#5Qd=&fd(b$?kF2%TZJIl@x_fN@IW&I%A)6)lxomRuX=}5JAG;jflJ;}A zG_QKjIAvRBiU9laY@1r+Hy(>VpVK_M!0c5`YcK25&1niJeg>>M{YdT~%NN%t&hbWS zCw}(%9$$E=eTiiIwRe*(?KvLUzNxZ{YrM`PApR&>oIgR_$*I}u+XYwW=!vHsEjRm0 zbF8qt6SJqp;`-{l5-)wMZ{n20<%`Ei<&7-mvI{m z)B>i@FPr#VWr64Uqpdn&bNpswX6DBr3J_* zIG$Hfik$ZO{A|t1e-`I`&rDSK5$Ie#!`NdH3#+{VgX|@jxvTe0Vl$CxSMkbvC#de^ z8YlHxyNE}o_L6FYP}KFS*Zw5!$#%W@B13<<;SB3pQk%m{uN8c9)D`_2{QXeal|`Ao ztqb355M8vf%AoXwYnkV?sLdgH6DB+S2us_ux@&oE=zFuBdp7$kFI`^N>1F#qe*ZBy z)~6HNyo{q?&T(4EGxhh=lK-CU0-qPh|a0)8oRYyo*Y|gXZ>|{(B_LP z`If89_RU_rYV8_DtC=(JuKqlALy+{vy}a>DeiY2UrEt7d%WU1Ohs}=HKm036Xzpd4 z+AkeAYvY}Woe$My+WamoJ;J>!YtP*V=f;wx|E0;(q=gz*m2BpeFB4^QdRtl?y*he9 zOceKL4wI|vBBZ}Y8#l}G6=to-O1dTL_UVW-*OQ0E>m=;fT?=vLTfM1b(>d+QLDr4S zn-_1%5d9i(I@dj-Qft?mph(T5Jq3l=?VGI(llJaA?kvurut@gFx+BJS3x9Sqb$ELu?+QK37e93EC|5i+%rs^<9s48swwTZT; zjAxlj38vn8n|nU%-Jb-mGKM==XLE9UcQ-9QZgux=?~d9fKNIYG+R7_TeP1g|{J*zF zPQ`hW;rHDJyZ;IaEquG!MI~{K`X$+>j~Bl2ifxiDDrdjidP>mZH?QH_y6s$d=7}-O zCBOfnn`XSsLmO>fX1E+qXU}SzaPG_wV)>@2#ySaNVANy6a=xUae2D54@7ke7-w% zQAhWW8%e}J6=ARFqSDyGM=%(s{V!Qqm+I8jST2sDH>1!>zX*6|XjY>xCiyyBT z=AAom@cUaMUe6a-to|R?v;M1R9&n_Gg>m+=DJ&0LW#{e;G<&~Awtf4I$qd`#yLa6G z&!%|bN%c6c=ggG{rGF=o^KCq`5D*p-)jl~#!80ky_>yv z+%KFwt4OH&;RXApJV_dk!dH`w_y0Bfsc+Qm<%-c{HaAc(P=GZak(44R1`FgQt9v8q;9AP+bFd2Wiu@rlxvkmgWk^ z#uj>}#tP<^dPe3J3dSaudM4(^AP-P5H=C57UtFS>UzDz&lbKYkUrnXI@Hbgp(y;~RgfUAVxVx8;lT_+~_Pul$a_ZL|{OO>x1giH7xK^yc=JS=!- zuKd47SdZz4xZX8R@1lJkmwS#nZn+_o*L0NcUwxuxSX;`|i+gtN{(1R+#kLcX^FAEC z`z`IPrFYYXPdl~q{_T!)P!oE5WwM8K{N|41ZVJg~ zI(jTlH-<`Vs@ZF{x%JLH{rW$J%Bxn*ey7tTyr5S+^HAiJXI?MPZ+OK%Lnom={yt03 z^P7(ZZ!vOzt|{En88PqavhcfmcKo3hEqEruq^d9JT=^XA+Bep9y5t0`gQ zo(I3HKfTEts~iV&hk~JV#~k&7L!6zM98e zF4z37@?6EIkk^WN+WmKrR9VQhG#^%w`mJzu<*U0p7b_%6#+?0dM}MtY?exuE!3#p4 zNjKk2ZTl8~_N`Ks{Wta&M1>^cE`%CHt)ZkoG)R z>r^C57kArwzu8BgZ_>3t@kq}1`@e6kSGUC(8yP)ydKkoP^JQ8^!^6@|MQPV8?GEa+ zvsiA~WWUu^cU4O5`aDLz#P>C2U(cMDy1cjQYi8Gu2X79uD0?3|xNQErH@w@Y9?yIA z#@V2@!_&o?FXTR##*tUwZf?u8NuS`7z;pA(kHV5r@00(NjpYMH_ng%@S!Wz-SvEa1 zHDk@HHLCnuuYP+T`u|7Z+^3uCR1D-)Ohd|lZ@L#5e$o8Os@ePQL~K`-n-!ej`K@e^ zM&QG{fnTHxY_F|f@!jIN`4{K-XMoE+ep3DPtsr1qVsTNYBH0zz_Q>@%?;d} zR}1l;bXl~1E00Rwle?L^OA7vcU%+Bv`o;Of?sS>H1@R}F*Hkas#1~e2;Kzy5a+7N> zzRG`*zqu`yvE2F2saY@P@Q3f+q|CKvy5-^LhAxW>5`G#d8u3M(DUJ{I=bYSj+hujL zfn>3m`{QmK4~sa%>^)qX);kuO2(ho&!12#S`r>rozmltzul{m-eP?o$-`p*y%-q(r zSrn_YJcs&IUU=US6!}NBggnyW~=Xmnu{m;Zv7~kn6+!}ypB`%x z(`mQO*t0JCDV@H%-R|sCJ+?jR&2xCWZ`Ed;O}y(Hlgoch))rc8FX=hw*W|`M*S9{mGqDKfc(diD^y~25Md92RmYOBE-a2=- zQ%(MKp8eH(%1PyS?dDj$J|h1{cFU)TsUONx9$I{zHhF`2(6!X~Tj_>udknjUznkp0 zt^fRC@|`dBb83$js81}CX{*TS6wkM0Tk2X__H~-=Ebr|;D)S3YUst)L+pp@ESE=#- zT>sq18(!Y`XTP4ev2wqDZiifUx52*dbBFq-ZQEM-{Y9SKpG3p0Gd4z-Ud+|omNMZ7 zyL_*%WxC^p-(0m`Ifi%CcF9Mi>}yL=l3rOa5S`|u!x_0}U!3@-482)16n}~c?^e1v z>xRdpD@WKqz0*6;V34f1UEFlCMtg&O<%VzHzlglZ-r>8={zfpv0-Jt!b%V}NYu+=g zU(q<_Ri4*bnLJjwl3k{Eh&A2)_JY~l_pU8fm)K%>>3r^Fbse7e-!dXI*`%+w z@-O;(hV}3*RgH6(&o0QgdG;Ms_R5z}Tzis4PX})goW6#6Tj^HGeb0JU3+b^Nvn^TM zX+Qnbi#fmdKdlug$mGpg5vafXmr&^SEl=0`{+-@!DU`hhMz4#6T!-{h*3%0M1Y6_FT&9>b? zY~e8uKaE4p9}@edEX}U|ys*XH<#iRuW%rvePx-8|nYH1}7gv7&fYWE)%I0RA7iU~? z(N^!Ia=38wmg>l<3+&FwzS-ipEu{Ox^8abYEH~SeBTt6LOS$b>l`p+iHO5)I_sSy; zxl>#WBG)d5aLoS5+7FTGrUH8)Sm#6R^j2C^LabQOOqDyiYKV@%Io*S^1Wj&9= zY~6W@?vE>TnocQQZoX~)vwl)lr;eV%KE18``gT3>{c5XLFy;C*S5?<^Yu~pK7gpO| z=8(9u@XeLl12@a=X++L=^{&_uPW-3l>a&dDCm{s_zp2t6$A#j(F1+ z_t;{(((0xE;<}ig#Q80(;Bing=IotUogZC4f006e-LKM|6O)XZ)=f7y&}a@(km`~@ ztl(P|s&Zg~g=Du2`@zF^Sc~KmXNouEKFV2})t8}c@v~r=Y5pzYeXegrnmFz)OgsIm zdEGsZ`Ky=xU8eO_hEpSY;k*M;3%{+e+O5vJ-aj?;My*!6;lXn@>5)H9il>}dpOt+i zDEoEy_Wbk~b;i@58ow-OC_nu6*E6AWn@r^2zO(=T=lGd-3O_j*ro?WRTeMTJ_{a%G z-aTSfpDQl@xbW3|d0cqBl9P)^kM-5Fo|YW%E3&!01J6~M{A=R){@{3v&aoXzx1*}B z<@Damzuv{{>pUm#;LR&mZJNQ|j4$->3)%&Fe<QRSF=}Kf5juXu(2;?>D@JJthEAOh~{S7n0hg_hd)f7!|NsGpmvgBn*6MF@uj>9 zE$hl#xlAvgKD^P$FI;!g`^YT69XGbxhEA1rTamCd;)(C4gbyLj>(`u<`?h@2)LCz* zC|f7^H0SE?UzOSPDW&pDn9w&#*_Xb|=}93UXYra8+*uX4oGsa5hlxX(Sb^mJtOFB` zrmQ{4D{algP;q&hJCllB#Un18sUj6T-QTYC+DQ1$`g&)ctEuP9Rk_A$M`}A>gx;9k zdaq*DhhEXtSBv&*yf(EI-G0McP{1SjZLsfiD~3Lyx2$VJ7u}nC#7}F&)^i(YG5!*K zasTs$1MYpovRhe94=Mbx6`ZBQcRcoF@YgMRQyG^qe4QSjD?4*vm*DSrEo$s;7I%*% zM}}Up>-hG!z?$BLW+3!^){8wI^d7kMIUrF!39S57QOq0IyXxTLhf!ixmRNPfB zTQu?>ug!1eubn8T?N+wXVA5l@ZvMGzGBznMd-iXOOXb0%_uU1<-M-y(-hS-3t;0IL z%Z9xD`>m!`Sfm=K@HNL9@k}b-WpHNN&R^$T693N4QS8>O7riAPl$cO!xqn`~;U7nd z(@f!N1{V#sSB3d*(B!g>^jqk+HErK@iMtB2VG(bP-pYn@3+;7#?y0e2;j{&9T~G7m zZ%qi8x4}m4&AScvVj>n!V+xx7y==?YE6cvjd3w3&bc+1eH@)3iQ`#>-IG&M{A-$k8 zD%|74$`wsF=l(Ph(_Z*X$|NdWdE2j+z11#UWqG$x&$4QgVi5PTzV%gCL+Y~Do9v?r zpAUNFss{AVYre}+`>!iK|Kdg4jH+gS#SLQfPT_@Sgte()9HvHhZ%33Z*HHiou*aw zV#Y=-m9VBQ#T{?A&i*u~FU@dT!gm+N6&t7Cdw)4e=N41a^l5&{Uo1j?@J(F)&+;VC zpB`r|y;Yr?7H7rE?D*omwO>SvfkXMxp^ME7yVv+eYL-o%ePi10#}`@?3uLqyR`I_v zS$bFdT_m$`j{luo5xuw5A}$sl{jz|6+5?d%wM!Q0=b!j%|^1xxQPm+rN6Ob2ipv&!5A=-xVW%KaR|+Icwz6u}yzb3X3&-942qfk%F*2%U+!y>9BJ{qa`35AzMvT-#rp z-neroe96OdKY<3rmGq zE`7vc321T?dFIg^WCXlVJP=2QnuDAKcLBa$xskCYO0WEERCWHHV}k$V=Ray-nmDB; zTW`AmQqzFlezTQ&Wn5eXUD(d-j((Z(`2@Fo=oSC_Gi&8{mQO3z5|D12UVZxJ&B{N| zHvW0qqv!O)Tiny@Qj_i`9rZ#9-LTu63kv!F|H}$v$?|-#rn0p7+oR}d`dmRvate#f zr@2^qDmy<*tNr%*QB&LW?(**0`{(c7x$*bLIVwNZ%|f!%tdGsSYsl{@r8Kw2MJLSq z+u>iYR>|*LwR3+AOQwXL-nPGg&AWQV?_ath#G&NIb)z>ZNTpM$Ab;Af>Z40eOtjwV zHktd$mPN|-zn!nHc~yA!ZQm6^A?3)4(pL3f-c6mTqjzRj>g{ibyYD{UkSV?C=G#3b z-7QNtK7GG*dY;4No(nefWh&mtMw&jI_I-EJ(XTBx{=LfjUcp(l(?RXaL+8@GES3Ac zs~z`$U%zej_2Q?OS12U1qzM^KYWU=^e*b~f?;jjD4PaT-(%2xEAR`nd()~7)#Ypc; zaP7CtH~X*om~1_uXl^WgN=Yba^Wv1U@U?c=_PjRT9bTnSKf{<$%x#fIwc0V$t&4ly zb(95t)#rRqHZ9n18T_F`B}w@2y~-J;P}n64t|x3`5qG?+KF{Qmu{ z*wfp?axe48zgW(mXtsTNTwCTW!D+6?a`+3R4(+=pZ)MjLn_RQ|&x=5@#XSaY;+i=V zm^|h#)A7|UIB|B$(F`vM-80iBMJgV;-OXNfztHYZlR}>U-w2n(Ju0qgeJc|+5*!R> zX?`_dJmuT9@axy@@19Ye&g_&@z~$yS%~hStPmo(C%Xp{ghqPHUc~y(^|JQ!r{#w4~ z1!E(}*IznsGPB%&I(K+x8YX^_zh3|I`S<)heX~95ZZlt#P1#UB#qOkZZDU60^xF3m z8FjcGdT2`c%$ujf?5bOEf>UzG#nwwRS|and{R#Q7iSb-7({B^UHI;edzm%`lsqEQX z@jp($#{PfQZ~LYT!Ag&UmEOj^dZ0K{O~w0>`i42yI*X5I+xHx3`&6+iebck@3KK@b za~Jmsr0*4Cs8ap-_!EPaa_8ZVVnUh`)w{N@h+4VBDRMCWYU;jAl-JZ{^AOEk-_uHq_$-7nNZ}+yGUM4tE!bkC(qH&|S z|7UGioq`o-lPjH)JWRd_oxIUJTjpP;|23Bg^`!gDrglF1_3_Ug-sS5`7O0#_J9_$g zMs(M_HOJq+yQZ!`>zLBzF5`v?k5cx8Tw!XzBcpG)liB!sw#>wt78l-CG#lNrtJ+|k zs^a&;c;lLtvZu&MJwFP-J1+jD2Gb#UH`+sc|gSzF)3i5$3ZdGc~#?9F00*TN2}jd2u*&_{2}R0-sLGG%liLt{hs$^ZN~0fHg}4> zI26@HF<%cRY_elQ)J z^(fl9#>;}FL;F}~do zf9;fC5Tj|mIY{fQKvwu;F($c|?b{SBxYPIi*!N3gt+O$^8gti@;F1qZIt5;7W^la` z?JG(;b<|_has#WgQVN}CiazADFTMG-CxFK|#(&oiBl{iSx6PJt;HlpCI89xCdeh>% zS369aK27sGUu|xtZNl6uFjdlX_RlK*OHcIK^;wcUq$WR~=)Isr);5SmL2^@4k;=n! zd5ak;=e-O)##owh*}tJcEhh82S?$&>4X+bcWLU1)BQRg~pv0>~d(SXcW-d4yF-u-< z)h@FMv(p0<{h}8OUDzDKuuJZgA9J6=yGK`>cBM?R&pTtaa@p3pDQ|g7%BL~A+_}-J zyXVSF_PFAdedhM}Dm|iY45c+YJJMNW;?Mi4q$wuEaJ>{#POMORX3rmTB*Df|!vDbs zlg(QsHb}m9*n6yj^&Wp^jG|w$#-GRcuUal-WAf{hH)g)0qqbaR?)saI+$W?j-mG_L z*WnTp`|^WxvcQrF{(d*)uOv6!jgI)9bKFMuwJ_Tx{YU;X!PnY(-W&Otho|;FsFR8i zT2m(Y&wJ{P2l)^7ZTj=qe{0x>iI1FD##jEm8eVm+s-Y$XN`!g*F|-+5R;&T8`e#Wy=j}ZOr&gC#0TexMKAQW z-+Ap5V#?o^Wqr6n#=Jm5??*$!My>4UdsDA=@SJ9uel3LCO|0tCx2DO}iV|lI#H@U@ z{>G;acVY7_DX9#?t`5g_o!iWHm^zA`%xBhLp5onlQ>SxcTKLq`ulIvgnR$d{PHy;7 zdAm#Yh=gksBd_n1a9hn>C!W*=FTW-qub0#o3|GAv^gkf{a6s{WK7J#?+}-_EY%cd+ z6sCH7mtI@-dvRi%RO5^*XQoXmw2ai-I`i~@3(Ewa`={}hR$X_KjLv%Lxc5C<{Rrv1VZUU> zg}C!4eSG?+r!Bp9WZSCMn>J^s{x{sDDady`m9@-n%i{fNX`HG$+g(52cGsMyzc*<{ zjfm=`4=V(;B)ku29)DlazKQcpI&Y#=hTYm5VQ+6F9P3Jv*wyWl$!L1Yd&LHwyyek` zi*}q0i@IEe_EvW_Wpfa=POIHh#YJcU8eNKGq!%pW4@Y z?BUve>vwyYWsZHW?$Ej(`Ef4arF%Obnl*lio#iXk7I(hOInne<-B!cDlT=hr{yB7& ze~RMfwSrsr_ukBqxuwgn`R2_PHwzU0##S}X3t4&UA@iCKErl7(F3TNR>!;3S5xOOQ zEcSZd*9aEZ(EF-CbYFZ?y3=;5)9O@SB(GJWpjV5|i6`vlv%RF|7*2h)Z{-GS7R`Cg zzhvY4FO@HA3;4V#Tjs>^e$@-z@187ei2l4vPBH3cz{yaJ2_ZMm?E7f3Lwil-`t+X{ z93$_^nyk6*7peRH_TT%(jS_S756zQ{-Wq3;n{tEWRLFs-E62Kj-IU~2h_d`HswUQ+ zU#roYHNn+?*Vn>p=Wb2cT7BJHZN6qlS-t+dzK>cr?=Y;8X8d~dq51S554q0muSksB zWpbY{sl&W~shouH^wW3wo0jQ*d@Oacc=beI%>dgYF}=+UryY;U&k=}p$}CDTT+Xy! zV!BdX_rhzk+{r7g8GCtnOWyN#3N2bUp?cdvKemcm3&XeHs{*$ik@TrIe=&7-(e}sF zY#E;?u|z()VHrNL(dx&(_q#(~On+}^onD*Dn=@%;S?TRpXL4(wEEdYt^Z&K%h>mSa z$t(N$XLDHUG1$``%l( zAx}_3``-UA>lHkcq<{U(HsX?uNy+{D_xH6wmiFH>e2iq5s_+Zn|Ng(QFI;d_<%0U( zAK%oQT@_cv?B#)$i^BRlMD_A4A$N-t-^;TA&Bvqk@<2ww>?CdOm4f+JuruJc;_2BL zSr{0j^z5ce-JB%7^KI=ur+m5V>#Ba+JyPNKyLs}N>djM2K5ZzQx-!ozX&L{{{<(qs z>=Yaurd%nSbE}AV7K@{R`GUW{1ah5z=-*1IoDhC;nZ_vv-p9UoZChqD|M_j4oTGc{ z+TqIoUp`zuzT{4b<5cz8%l|ryO?Q+tp1J(l!|P|0MfTpUxo7kD>%+s7ck635ulXY{ z9yKTbzDG~GFPS6U30PPL#?$M+>4Y7kGMVCA@`s#^M!?6 z`ppl;>`njb=JhjoC*Jrgy@7G(JoTvCC;vV;QPmVw>%1xGZ=>qf&$@RnoqKgay6vW0U_k23+Sx_#J0G6^z$5ZteGXH#UJG+&fa*@$ z4<%E+t3*%r^ZZmQ=W1)5C1QESgzMCm&hOt>f9YD_p?$0}N_7frXrR$K`Rk>UO_^3s zrP^w`b1j;~&k3ywJTqxu_C?_xkB_DrNgZR3I&Ao1YZ^z(kyGaJ3u;!E_cyiu`*N6n zPlesdm*v`ZQmm{Jb%O4WTwEXSe7P1YSo->*RQ^|2uX|a60Sad~yywrAR zk7-JaH*gr8f4$Fra>;!zOM?qW=fl5L`ksCH=kntZFW>GCJ(|CG4U6;oEr*)4bTq8q zdcJD9TDSPLrzNXa+Y%;w*_W#W7q98ve^$&b&ZsX06Tak<+aPGc@<5WC^O zvy#!G#cK7f5`M!azfaX|<^Nyt?|ge!B5ZT|^Ou(&<2G@u-*ja1*5Dn_0=L{v$To87v>(8+I9&~m`%Ydq`lOq1S?ieqX(e%~4)R{I}-wGlAz$ z-&oJKQfAd4Hv!TMO-Hm>pBN(Xqtb z?PqD@vSZqL#_QEu*4g&NuH1Q@GpDvJ=-9flH)}R#{c<-ni`MLEIUe_EuU_dbj%DH} z8EP$WS?yP!Bid^r=@fCk*=X;&s;#Bn7rBhimD)FmDyk;#UBa5h6<%@qF}t2e=I^H& z4@*}!{F`V{Sdx-iW);*qcjvj-`oC41PyCJF?GygC|Ki%6QI&s+6b$d{a@{=5xRI~# z*k27XDVNu>uM@K@BYyWOui1FjL;l6}XEPPei_?+~>N?ePoFxj^=1H};EqEO8d1F<` z@|#=^`dc3Ltlg&8zs^5KV8`UWSM+S|-zz+*-I5a0@vovW!BVH@iC1OK;sdi@o8J*z z^)-U|(Os2jE91L@_g%}K9;jHgxjH{NxSfAp-9opySr@-EPnqAiT1fcaTno*aTU!-n zuZ720aIIG8zuV*Yo%?*rvx@znJWnh1aoeTID1TW#r>$YFvanpW%DoRdWl>j72c)lJ zt{5V_wXqBVRMm{qSuLtIpdT zj*FQlPbgJmwYPjQNpD-*!lk*^OARCiKD*s_=y`lO%e*V_reu@e@AWY=GxT~CIV`-s z2=C>6DZ0G-*|~YsZ0&M+1kg{k+rvv154vn*Yv<~??xWm=h1PEplf+ zTpHgWS(WtAX|Ci(t~tRkwKo>(an0@c_?=;|R+{naxQ{`_ZIZ{>_GrxLt8>xbxb(Wb zNstzo*s8cRcfBI>4OcAYE>P4S=}JvY((%;yWa(=YF;RBHJjBJyt6-*!wOh1YEdK z%AQ&_H>SpU&kCD!Ta0wK_U-z8R4drLNu%g`spN+`d!gdxX$lA170chYh5y>Zc{3*J zb>ixx!|ZmiO;6AGk^KAeK`qbhhl0H?d6l_8FZn%VT71{xM>m3Bo((vd60k-%=-l%> ztEJ*+^{X=MtJ>3zn?zbZ~9lBBQ-Aty4Vo9 zBg_J{U>4SPM(!nmm$HJE)LKB+(prF)RR$?ofLFmPSb(OGf)p%3vpPWv7NDt>AO#Ch zj{|Mhtp%t@K+L8!)VU}4M3N=QeaKx5uyary40bC1Rkt8hkdG5GF}2XMG&WK&HZjsO zFgH{%F*DV(G%!;zHZ|5WFf>t!<@cYrp5|JMwWV(rp5|}1{Qk8M&=4eCI)&Imc$(*m6n=V zQd)$*ej1z)NOm83$eNfK=$Tt87+P5BnVNy_Pc+suGBr~$GBD6HH3T2&g*DcR3)<9_ zbkG@H=m87Lxs*HH(h!_p4GoR;j1A2cOpPt|OhG;|GSM@_lU|8)c@k(DyKZuRUU6wo zNoF2aZ-7c3ioIb9S&9!%CZJhuJqtr~1w%t4J!6U!HZgtxm6R|)U>`d{jFEu$+D7;% zWr2=(18w2;%r#W7gpbDP2NdNe2d9Egf71_eaZ}I_Nv$YR(D%$uOiy)Ihy@LSK#PB{ z9+;(&=ztoFQ~-kYap{L=rj%qTm>C&y>3gJRre~BW7+V-~={seX6bGahIp^mVHDPSrI%zV7#Ud@8*=G8=jY@X1s5bHrz+^Xq?TnSrv|w@ap}83M`~Q0LtIjm z^HWkm?t<1%|8M+%z#!n|=IO@3$jHc$z>vVe@c%Z0GXpad69_RgGqW%=v#@fova+zS za@^NzuhzSS^i-?Mf^6-mGh>1uDiHM4T3}IwuVPR!q zEDnVDFaL9S+CV60_iVrF0wWEE00bYv3_Ok`Io6ftU?xR68HY2!iB zpoX!XqN1l2cOC(lau% zic3n%$}1|Xnp;}i+B-VCCQY6)b=ve9GiNPYykzOJeA&aSFc^aar4&0M~|O8efIpt%U2&ieg5+G+xH(oe}VkP$iNKt6^MX%49#DH z3`~qnEG*0{?5r#-42(?W42(?7f-J0xhHOHPf$WKe!b(Ps93oB=7j8VrscandK{To8 zBA1wo$wSqTAg_UaMx4i*$nqK7V+eoUV&GwBWMC3x7G$tz_#3>aOM_vs;`Q_YGvvp2 z#i{?${jFZ{{8$|CmH!MI_joS*+x}+y{`9VT{WJTwcmDGXo$yqp@YI}ija6W9!pv=lflH8~5x{?yla=+fG~%%hB2_!^brFLIC^o zwe#P8{q6rp;lueu{$2YFKlb%Mu4nmiD%W+__*tTK`yG|5N`l`;gc#{Vw64AL+}oey!W6 zvu)cU$w!x$3i@q(5+iixOi|+@CabA0W7Z#f|3hp3q5K{@qYukl{+K?zFB4bMe#C0# z<)n>LAv)K(Skm7Hi_C3k&^~c=!fvJ8r_QWmn*Njd!Tdw=-RAXD_Sx$NEBYV$ci9*g zKAO*5u{Sq2_42cMlKOdh$rE*T--#Ja_wjqsrt`D0TtngduKI)K^=WsD|4HnB^q--* zCj8-E=ZF6pI&BiGSG?q{Ui0F7e&x4FlNU*CPLnw9MJ%pd$F=Uk?w$q#Zz+Z^{6DJA z5Apxx_|I_2^ds{>f%9+Izp?+}TfZUvxBR)^H$U6FH~t&`e$RgWb2raVpQrsc>W|`w z@Ha;vNAokB{_y$ee+G^^o9uatcDib|!Y;df%@3`zS=taMJySPN$@o3ryfPLyA?dRW zGXHM=QU6f>M|*$kJl+q>&g=i?cHMO8TkYz%)-^MPY%1@C#HHW;{V$|?*BhU=4DwU! z)c>ye&yW=_QB(VOfsOft`JKD!CNBD8{hy(wIqJ9B!X*)NGPjAl*{iAjR_XclHidgn z&x3abRe!aATh4$F}ecpEIe(mv-jD24hPV)#g zH$4-$$u0W1_0>?$h<^zm)Zg&d{&xO%XTP-WKh=uz<8q=Oe0I&-zUt+!uS>fl4_9hl zx#F_Y=7UX<_EDWarcX{ye2nK`EZzR5{DD4y-RW815Bz7~&HVN&uXWYS>0;|^YqO?L zwtX}4VJYXqTdH$YBA&2oZ#gRAuN%nJP&7~DKSNV{jo}5|eS-fP(r>+%)4sA#-C1Op z+2n<`E!z!DCb@IDm#FM=Y3(`p;dx=8itx(Br?Y-GKal?;x;*5^+~2{se&ipC3cqQ? zyf*ISq8~-KJuhF)m#d!ry5h~U{h4|a{Ce^R_LB;Ch5eN0-hTW)!-LuT`SbVb)m^^F zvi&1_Tb{}8`lfsGd$#TC4%(18E!J_X_RWO8fLDvIRXo{W?wWgcw||mBQS#ULo7WG; z+5TYs&%mnlWA!6`!5ZCJ`)Z?Yns2tzG`2if{UoDCuUKswIUEok~km z-fR~ZN}a+a@abHL{8l@;e|PqC*(ubW-?jW$;L0p1{$`Wz`)~gGZuPZuE=;wYq!Lo` z;B)!A-!42nsRx95CWJQq5&!U?;X(V6X#HE>-!5NyXT$uc!{l!Lp*YiR7vHO1To)$m z75QFtLEqg|NnsN!U7fu7+_JSF_P91Ek8u5wI==oxvyQbt&b5DJ@R7BxaqZ3e z^s_6Dev__FI~=pcQAFp`zRweUSQ$>9VEBFc!Th)1KYV^1-}j}TZC&SMe%6Y~v73Co zoi=^U>&o4~O*cw&>2KG~$1W*$sPL8V*4oakwDo_!IT(#3mLnV*g4{l{G6{CAOk#`~N5e}*=<59+OYCAaI}@(X3px+3R0?LUM5nGM^fS?2F6Yjcsluynsg z>hJIwGYtjfy(;{VTE+L(XfLjq?6T{3UiX?v=M@ome&)*0Wm%!p`)vo$&M2ytQ8V)jj5q?Cmy^i+O?>_Gu_kRY}WfSM7B_&UAN6yqH5Ty%~$T7&HbLYoGsT`bY6UL+j;-(Xqn+gySVDgx`E$S^anM zgSFwmtv>m#RI2n>J9ftX%$;zVO^WA5Ux);-_y0TdpJCJbhW)AhY_WYkZjS<{~3~!uV7BKLe}7-_3S>|1R1y zY~knqWBalFkBZdQ^_^?Ka<1LKSzmYCfo;D-%@>jqAHQ-4HSJM5#{Es{QKZDxFhxxx{-qwF<`?c>s!w~Spr}ej- zkDANze^8rWuX=4^=EIv?SIgSRO?K`}zuG(Z%%Yn@C!BcZWyhS&T9^FyMeA|{L5q%_ z`8Q7=)qKS3e%wq-_Iih&jp)LAw==&5{y1j+^qRun?9!dP+{_QeY~4P+IPK!>o|Aju zIoLUDXTD&kSRwvTINDY(+xz{S^#U2w_urajw&Y^v%k0u8rx}f$ce-q}F+MB2>}k@Z zjqY6zlhncwpKqV_to6~_>Z5hq*SGvFKQe3U)M?YsFV4(gcJp|4)^{T(7NeLL%PD)` z9((?|Z0e^2u3Bdv$k{*Kn;o|OhwX>!k3FxOcK(TcbaRia^U?ojc=i=Rf*-|B$`F{Z~>`=2>jM?B4$NKf^=WkL>LW zuP9%vv0T0N=#jL~5fx@f3z@Ame>7fu@rcuXg3SZF=*nXRvyYNTU^CM;7 zo6bGf`|!!-DC-x!b0(fn<@tWEQ0QR3O5rBfHp}P}4c#7^eQxz)>J?nj!wT|VpDcCo(A2R0q~q}lc^hjl7Py>4g1QVHhp19CPO_o@Fd z_xu>%p1xz(g{={;XQv)LJLOxiiObwg9M4<|*04QIT3|Fa_0$IS9bx9)#ay!AiBCFxhc{|zB8thfK@ z|Cam1<^#9hUh911-&x21;osKm?Qf;ZR<4h_9pk*ydEJkD4yUK4CQdw&P$l(+>+^pG zk$>moxowKq-Oa93`H}hHKZ8Jacy!&x6|ZLTu04C?c8JpZiH~(|goaAmure=Lz~A(r z;laB5(*I;@y#CIvF<(~UeEgpH$FQa1-MQ;hHeWQIoqckWVtU0gr-{1-7S3gRldQvD z7{6@l&*}deSj&DWet7+G{hQkl_xc}_WBjncwN$evIyYDKS?A*D$TJ_Mt&bT_?-iM< z(^2+ZGN794ugL$IrLfab4Fd$rt$J_F-D{n&{lmU;g^XY~8k_yQn?( z;-1FoTBi;xPjGyZ9aXX_tY*uj+n>UPGxN%)girs^a5(6X_sa}qqxI)k)}H^N{rL za#q%74_D(-D)5{=y zvfbWA`?VMT-BO=pf6M+&=ZDP?&;L>0Ww*G}=w+QZ9k$>lu z1Gm&0i9@yazqaz|?VP4JS6}7l)Bg+&>pvTOyK|o<+V0lJ?QgGt;65Oq^zZVIr3>!X zuCJf|w^RAm?y|S%{xfXS*%yCL;(qU)$JTy!byw>jY^vu}ysWQsxkmZ1_g>e;vTfUU zZQs%)naWkBePTObpm%~8#}Ouvuw#7R56}OhQh(5&vr_oSq}b}?=JCl9d5Zh@{#IQ$ z|EaD?W}cAC(+Dk-bxb?i3mMxlNQJ+d_`%!#vd$ZYAD$0(Dr{|4=zX_EJG*mcs8iFY z6Fr~I*!A8-_IavI%6Rs0jrp#{VVmEVihg?ix_(04uh@wn?*Fmd|8K?hf3fy|kGub| z{e5hn{eOl|{=@g1;ni_Z-v8NiS?IXY5Zr1`gs4O|9=Js$2ZD%|2}`l|Hmr(QTb8(AG2%1e?0znU(oqq zcfR`6(sfB!YEIew?o2HGc`yEMtCsQHBO4k1Gqh{eUrqhba60-w!*1;_zq;(N^E}Pu zI;Fj-M`!=1fM0vvKkQxBo-5u_ll$mya766A+Iur{bJjlj*%`ZG zPq5L$vgVo6D#8b>-Cu}`PW970_iDAbz&|hTPaavVrWby=|C4$6A^u-myf|IgrT-*5lH?(~0#iQ4a8Y0v+gcFz8Vn)5f4-}2g-@gIEjZ=UwQtn)Vg zgU+uxADkw7R+p~5uXCMe730Js#Y${RMowDmJP$QChV6fN^Zw@azry{0*1d}VGdbS> zVobjES?T@PR@FZ!`p;mrcaHs3d4u}HoA;-k|D`VfGxo~Dsh4 z%X4?mF5CBQx2BunO;(Y>7*|%2KnFrtuKwfnN9ljGYvny7mS29O|8@6Gli#`ik+rkR z=03f7=bmqAiL<7mYks=*Tb|uF1G$f^yL9~gAAxIXAI(;;ynp_q{82fjeO;oz)7!q@ z=RLmQaN4D9Pv&uZu6eXKg0nB%%%>s_81#kN>)A!paK=*)HMvmQlPa3)Xlo_$$s6lB=j|0(_0^2hpv z`Z4YGf-!NK`wV6~McN2`yLLJ`F@JT;?dU@abCbl`D(tRu@0oPs4c7QRBGEWV^?;Ta!Kl1V)$3=JZe%mcxdRO)Ap4$^v+p{~;cFoPrnK|pWe^>U*w60@!Hmx*>c2|l% zR_4Z2pr+iDD7L(-K7D=bJj?5OTlWjqn0%=3FkQWNf5w$kU$$${9_6kQEI;vaT8`GL zo_S1-7XsK1)_(}k@BPofw@^|wsHj0nmbN^T!)O~c@b)(1^v(9dPa#CRPo?u0f zyBz`Nn#&BQG4EJ^+3U~g{|rsLYXp9jez^WE_rtyA5BV8>#DfQqwr!jA+2!KeHP0$~ z=RTG=eXPrCnu~RD&kEbtx>*5?t|CNY(Z}<*guhMy@cdiig`>}VOzX`v^ z`{DlW8TWbL);~=6(PjJLKSM^I!ArAG{HDgwYJbmNH*uRXlcnIN&CKyu^@kjPJN;)k z7*yl`&06$7LyP)D*`?2KtUo*F+^!mp`15QJy;sNIS+qn!tD);&aBhQF%-_4e_394Y z=c`YB?Xu_c+WLp8ADeS3YrIzHH~ggJL?(cJBy1e#>UN-ihmG z+4|Yh|7u-d>_4vhQ~#*(?0KbjyyC@=e*9-hUw`ZOx4YH7-jC!vmj9@JaQf%sK41Qj z%AAE}ho0W)_;TV{?7~!5-<$3QigwuyvUQt4tvHt3KTJO)`-END!eOVF{ppbM&MSKY z^JUMdOzp2|yV}@gdS5bC#imPk%Bcg_ubPC!f8fi-(^a~&Vu>8TC{kP@|NPV)7{u_4b zYJS~GyAP~W_qax;?Mypl7uuAvfX%69ez@*wL&>$J?T2;CbKgua`=$R<&~k% z?ayDWfAh4@{DXDe^LU*f)jm+~g-2dbofT@yYpls;(J!IoE?uUw&+@mye};o5 zTjM*oi_I6V&o~<#c5SO{kl2JD@j*N1zulxit&?T?_PB(pkpc2|^d!rUNFI|DyjOR$ z{)1oj{q{Kh{4RURbqOEU`{NSplb3Hj9J_Yv=|<%{cTOD^j_s6eP}robIl-nUaPjX) z`xcAN3OKpqud~|RC;zNgSK3{yRocCNdU;F2E4!@uDxsIQtk&iB*?hU=wuQ-Gt8*gT zw{6+lnbgKH(P>FW7ei<$0|Ofa1J_np(GR)X-kn>ps7sR}&{c${IDGz(<=ErIv$a-dl${Cfwtg^mR>Y?j ztJ_`c&5H!Ze#d|C=f4r{`$P0$ukj=G?%cYYAC<#4M=or8#@7FvjWbwH)SYqKlhwIX zPp}qU)Dy4vezO0AU;WMF@juQ#UOoR-{G+31_tk!DoD*uZnCDXr&({A8(>KLu#U}0Fwe635^k*l%#os3VWEBaF zXDcPtz@etRJVX7#tp5y8uG#4RXGosA{@;ZA;rG9OVE-ew{-1C1q5Iz~Z~bR@$@{hT zKLf#r7i(Qm-R0}y`?pVj`*y(}rHl9Ke>mGnn{10}y~A-Tk>k{@CBM(G9J{^PJLLX8 zW0k-O4l}Oby}zyexMY3$`kSYZ?$h0{qlvf1X`7VAtc;7jPu?iI_r^weUDT}nvv+yX zfddax`sdm|m{`yFW{={BwC|5HP^O@ecIw?;Kk-Psbu0Dw$#+V zJ0DKD)*kbi7TL6ZuJ@z(-OK-3>c*e4uUqkK<@=MtKR3U7tL?Sv!j{vv=DwSo{V%V1 z=v|%d9WhNKp;JW5tz!XeXaECf(ah?Qz<7b%d2e2FX)KyR&5*}tmdx^_`m*(hwtrAt zzV3MP-_`Z~h<{hb?Dc|5ISeJ!WH`|3XR zmvWjP`TwX)uM+QEylMNcXpOZ(7Edmjyx!JjaUsQJbFA+Mk4qlH8I>x^g|GH0))-!} z(};Xo_UlVk`ya3O^;>k-ne2^v@4I9B{FBp|UYf4ic*0oZ(?aPD&U2dMvVR-b$bU3H zbT<2u?e~tgx^IP#%$mDyVWy0o@s&R=7uS>p+&1!5{bD%x_QLzmcsA>FN+;$%I>8n` z+F~7Tv9=P?Vr75E|3~!p5vl3@b?P-rKO7%E-M{0S-uJj}e#@jSd*;1M-MU#ao%@-# zYr*b04Bh6uZ8ddME20lu2 zK2Ou1(?1%Wi|@Ao5XsN`qdsomoR|0HW}S+)%$;U&OLXhy{aYUQ%xy2)da~%~rrRm0 zJUe3duN3{Z^YyQN%=!n{KY3pN?f3qN`4i83M|#DT{ch^gSl?wun;}XKoI`5V9G2rh zBi|VRv1;Qd4{eJsI66fnQ`u88aKjPhkDun$ zEeXH*F{q}Nh|4u(LYx>l0yYIiP{$X=oPo>k&V|j>#$AyB$7AK!fc96q5p9`4{G5%rw+q#D7 zf{o{g?+5=geCU529;deb^0S@)F7MfSmpSV0iIdwh=ek8{oO3vT`Z~+=ud5okG#25; zpZ+u4s(+OJNAv#8*N^zSA79zONq@^*x%xN1&nut*^nOXa*5xO+4m_(=k~h#Yp7ztx z^p)t({r?$QtAA`S5wyoF0|9JY?Rt1-bfnXBbzYNXod^HbY$ zKR6Zah`smDz4710GZ_!>AFkg2VQu__wd;emCOv6R`~FAp@Wb=p_z&hwRGh2OfBSBa z>l)oT@fp6`K1bbh|K!aVBKG&~(No53+B3E_aBZu1Sl#?X_(Q+%5B?ABhwd?5O8NY@ zB-)Vs&co?%jW_Ds+>X4ne5Uq^-{u0lzx6S!`?UF7;c3G&+DVJI%$wKk`a{L4@JUlg zpsVO(DQtBgEybwS%8}WB;iK*ZpTWq@AetQ~&F1i$}8q`}6K=X=gm|@?iTEv1gO^+YpV7)24W{+kfbe zxt6yzUL<3B)Y0QTTYRN5-oCYoE$0z9TUxp+bob`cCW}W?*UlC6xv)qr)xhZIwauGD zf4qFSzC%v?-;oU;=2X?5Z$Cfp*V#4m)b@V(`sAI>{>?h!)23ban#lZY^P0Ng%__=g z41aDsx%$Jp+sCEqZ{^syKb+70!~1ZVS=h8$YSSOax>euTPM@`@T|YZ#;t_#pox&yQ zc{h!2Xo)bNt+D?oesuZ6`;B&nu@zm9KKp)bxg~V+!Srq0qNTZOqn_QmC&=cRuh{2a6VU!$=+PqI@t=pT)k-7ynCO* zuD)M%IlZk~yE3;&Nh88`4^!l|NS4(ivlSc8?N5KT&-#zWz8{kxzLmOl+5h4n=ltba zNxwcHmQ$X(__FDaedleI?mcY@W>~3m@y@!$DV5VU@@O9EV7&Ds`sghAz7p-&{KHk} z=0D;UdtH9>tE}Ejz1(NFj!LI=cx{u6^y6psc%>b+V|{ZS*S;T>AKv?4_{y1k>yhNT z<edSN;V}0iO zoF9{eKJB|+{davm`^=|DCaRwldDYA*#V3+3X)`H<`2eC&CVLHIPEHVLh@(TgE{}U;vP_opI5*A z`rEJ73$lv;Nc}Li`PJT+uO?Ntd`+g~(pjg%bLS^*xjE^2&hc!c&w|$B-**~Z{kwDj zgN46q_P5SwGPVEVVH3Efvg~@C@`vAlm!JGxAav{C*dC&e?LpCiAg}!WBF9drnRY+B#M;emgRf5+__XnxrzNuqoMjd-dt>GkEs3`8%dn zuF1Rq;pN^Z{~2b*F4`}@(=O8YO3j3yo2&nEe*P_8f7qbLc7E_j+qo_Cg-q|y`}TCy|Xp@3d~D>b;fVI_GevTO_Y;^P;YwGG%7lk~`*U3%u8?4~z3&cu``7UWiDb zYrz!y*3^(?RTM3!inpq}Qor5*@cf%q7vFr9UjM_hCgJuaecnvtW7h@tPf~H)rShL) zvD@uO(lf-Bd{6X=ZoJ-lDE!#f5Q7UvlV9v#e`KboYwW!DkMc!c?Blxdu5`Y@t8Y?# z{khxEUoo|wvTb{JF-z-y@#Y<%UXZX-o zqx-XU&jz>u3^zM%xB{f3#b(!h5})R9d3QqHPnP++BUXP|{y_fC%?JE9mme0+xW{^N z-{EijHP?UT?&P^%{>iP8`LyDHhDHMyZmBJOq2h(-Zy)Q6KO7W%#57uDTh|YT{|tM6 zZM|9ca43s|CVyuWN&#;6Tjde|A-WEtB-n#Ta8xm^DfD4Shb4l z=Tes*IiIQFpRT`}rLn&2SaP9cYkJ3#v#0#`KiRFX3Q8v03X8h5I~EMgvj3m|86Hi4 zBlAeKg%S-}L;qxlhl%kh=EH&3CiUE2E2U zMQn9(JJjvhhCm1XQh&@oYNg+_#d>b~5v#pz&5Lj6e*Vhq_f&W7qdN!1OZuGFMNW0| zS82R*)lJXWP3vvrO4pi$AC3QUd4J%3xcu7``42PxGaL$C6?9|<8@uFyx^yT@roxu z_p z{Qi%#^uzoI@29T#F;D(SAZi5ouR3Jrfl86X3M7co8P$GJpZ6wHQ)4Z-R1jR)!#l{xv&32x8wYW@0)F&wmlEM zyiQ7ylQ%E^{8h=2cW#O5Q*%}-_PMdDGMm2Lzj^u}q4ERre0GP|{PEcJpP{w+k;XUQ zzw@^oUUK_{&GO^ZzrQQn<*{=@#}oI%PQ0~uU5k?}`Zg{3aI@63efceW;ji=Ff6xpJ z-*fjypsVO7wueN|x)3`%gGF8Z)cOy>_MLSW7x(A#x9#J-Xs7d|`C)(C*R|rlUBABj zUrXP-dGqJ3(kqtinJN@<;f({gSnAJYh7HWhUxW5r?a%AKasGJy7XP;kKkPm-Z5}8Y zH$R-ZcG~piKiZ!E_T$doptrbC^-`{}%5ecNzGTUt`+|2AFgE^YU^)Kd_v5?a-zNQ7 z`k_7cM`z(j??Y7~=GT9fca&;R-#sgB`<|w&sZD9qoD^nRGXJce>&O+*doF(KkMjQv ztkM4&TE0KR(?q2gz|``=zKYyO*m(|zjZy!X3qZ|o%G9nY#;u9YkstTL+$c_$>H0L?)`21Vq>4ER&sOynN0p$ z$}z_!Fl&|bl$ez#kwU^s>|CI-uKBO*NzuG;w_>pzF+pOsGnv3p>2p;}c zl+v5tyJTmt==n2`pU3yy-w=L0U-aL}`)4wlg!TE^=tJ%uIAs0Kjwd1_@nS)_J0QE==#hb`wxBJ^wTP4`ow6} zt7}j6rr%0otht(zkhsewwkdwkQC5c1dWQcDdD7=Q?YD?`)N$81{;2-Xz_tDOt<*_z zw{Lnr;;!}8`z+;kQG3w>qmII4x znF5ElZJVBVl5<*i$J&ec^L18SX4m4H#uZ}pyx?c}H@m+ROy6_XU9A5w=aoIf9{msD zhi|Fq-AdZtxqj>0xXQHc6|!QX`?Cuc*YuoKVQ5Wp4YQnTxBu^wI*y2c7ymP`>m0iKquthOnOlAfSDWAXa>M4PLiS4CG`5?IgmjO;OX}>&QplLtsV#p0Kf_k` zx0ws-&d76%{bx8j{a0Q0D=p}Cf0oAbA2jmiY6QNe%j1Eb@Qj? z?VoQme@oQe{I$HfM)5<;;l(fHI6u^P=#|cuKl1U_wvRgI3@1LE+jJ=EjmE@h75j@+ zcuQ)|usDjp^gqgfbMm46qWie}nO=Xm{i8c*%ZER|)TTe$FB0jxDl07LWRBwR{232D z=WKFUHkzVyg3q0Wp>q8j)89oe56PAzUj;F9?s8Yik-2z zWK!&jqDj${Sk@FhZ){@k|1tX?*Ubmn@weg+Rq1s<;+6aSVC}gXpI*;Xp6zzU_3PRf z(Q_vrn-LVUDT=2f{%`W1!}9u<_HU|xGx=fs&FqKw`WHt-kAokP>!*!RH`R^(|X5hE~X#PjI|KYWrzrBC>KZ;#8`C+a8p(?W(-^}MR zwja87Kk>@a)SXO^oFvp9G`^gKQEZdGI8H^#_3uI9yTc3G-OAu%Cz$BYg`(cIV*MQzPZb8I!(K_t!$!Bz(FC6 zkX}Itu2l;d7BDa{gzjR%KC#)f|H0;8zTX!A*2tJHHZ5E{?3V^*6i!b>;sHF!lfEw)mf1|8K+ki@yIEPO+^7EwOv^^yb!@>@ z)1P(ySyy#@_S>Cv9xUJanQ!%PhJQDr-p#CB_9|XZ;fl%Y6LI&RU3z^?>d1!k!Yjjj zFD_7=I%R^Rgu;aEV6XoSOQBKf_bzkpB!%vg{we z_|LFe{IAIUKMS(v|LKjl>^fsu@w_H9(*NqVecq{`Ona2&TjfIofXsOYEle{AKyCH-euH2Xip6yD(f3=>!UXJ}vi?@auc;QCLE ztLi_QGSr5;)~qJ%9tUEr4q(3;5`JnwfmCpXanyaFT!a~Qqv3}&KMb$*_c(U_A2IGn z@{Ri}e%uPPsz0*7EjT-T{*7zpVuyWqv>os?*!C&SqggL>q3-kJ%7SusZaP?S}shZGJx#|1-Gc zKm5<|Pw1QdpRB!MT@0Vr|44b?B>%yx1Pk#NXx>apE7(cl&iNsMvJwvDc1h*3XkTww#qZsd)5uZgj`dyOY`` zcAwrki|G{Oub00KYs$C(UHp$XW1mIedH+l6-j{dpRrbG@c-8K}&pQ*nO&nOK9J+Ag z;-=NhXNgV`$X@mF`q6&oKl;D<|ER?oefZDNvhVKxH}h}rS|S#oadUh9^k=)?-QH{5 z^SJ2wiuI;{d;Tb{_`&^P@Ameu_AS@`GjQmcyqtEm?3kz+R6fX8x z3|i;)PEBVX2lIpVw>}@A#olfIV9|Cvj@=*rGaUZQ_3qv&UaNfh8joGOujrP1skJ$G z**)F#)QxpV=5#EWB<8;3@MdOBEynNv8IJG2)$IJk^S4>{eu+J1yXueFiu-@EJ7=;t zly&d(nU%7ub9TB;t#d0o$M}@%LnZqGe~|L+zs)k@CH8pisy}>IJmB-c3#PuIynE+Q zuT)+2erIWCU771Kho^@=RoeI6-~4QCt@$6}{|w(YTvpk;qt@(JKG&UtX_q#(X{KE1 zI;pg*V9jeD#~C~mrI_a~7W-%apCPaQ@UH!-^_%}QWYixr|8`&K@AlL8zu9lC&#=F_ z=T6PMyz^V%-2I&UdE3Wb4feSv^FeE;xBh1kj;tv5`S`u{tGvLyL(|Q6`7Ya>t9$38 z4bwvvkEy3OZBg-(VB!)u+?RE2|HtG5|3p59&3@RMe=JV^;vUbHmGy}izw!og=vMkR#RdZJZ)s27(W%XtAzbOL;JbR`5)HVKa~H^&|>_3f5Lx;mgp1z&i!YY zSo-I__KCmuf894OyZ`9!yy7^<)W7rpGjMf(+w-5Hb>)8sj{gjQ^y=TPRey8;mVNTS zi@)o?ZMlD|KI!(4pYzjymOcJa=petV&g#d;2lcK084~|9w46Q`C;wsk?d^Xj|1J+{ zI{jAK?ER-d*JE#&FDsmr7xPv0Vf>cxw|ke>C|}JnInR3ZL48Nmq_d^DYi{2C<8i4( zbIZ)6l+fNwPdn4njG7iOwJGrj#?P$ZHvNyve})go?r*&RNA>A_J^4mUs_V51;cCTLjXLzvEezW-D^Ec}c?Pn|TXLzwc;dlJE%~?{tXO?B3 zb@I6E@2GM~{)aG&!^YK~iY~Fw73AxG^#9|s{IL1({w=fq$nMzjTlMbRH`8XV?=qEs z9i1z6I(5$E4Z<@$GhI$idn>)BqO5SYhUluS``h34HU7@N_lM=mAMO0*ncD*Pp1t0) z?(5br2_;{RSNh6_E`TN!#EcpkbZWI9?C;voQzue?`1T(6hx6r+FMm0ocm4X5E2i;p zeWSd!>+K$U{EoK1BN;fk_Rj1EKDG!2>4L3SYl416KkV=BxBoMU->Ue1=zE;-h8fXu z*%w}$olnly)L<#{lTR`gom z{ffEI1^o$&d`{0?TygBo(~HwrDD4-hPw)R}Xn*VZzo+tltvq9vAIopuC-GtVo4LQO zDo%S{wmbdEU#$L+``@kA-#X*p#;5N0So_Z{sdKAD|HN?1lP8}w*w=62@0Mf#7~i#3 zpC@CU(T9JVANiV}`CZn({M7{i%Iv2*`uxpAie7HLp`6Fe<6tvu^~yi_KS~$57Ga)Etgf8 zAKaDx&Ajc)ee*Zd;xnez>!11X`qY00n>FW(K3P8PGj2Sqad;}5)CcIT|_7V+Lk z)`mQrj&0mBarMs`X|A)BPVqTR1Lr^3%Le{~50RxxMY(+c|}CtII!ZKP=DvpCPOM#_@lGy~e+d{|V`eAFNZn zYJW*}`djhu;`jH>{@!!DJSTo?!D1Ok2l-9Yw5W@yY4N|PtMXc z4c*@6v25F{k{zDYES*dH{HJ}pL_pl?eyP9 zx%boW9-sQHf6Mdq)6XjRAKleppT5WPdfr2Y>l8oT>HbirH^WbkHyJft1(~pbAH}6lfOZnZ{F5y z5%PR?rZ3QJF?;TjL=W}m_zCt~=YtBrkInb9LGyF}1kV3(Hqn1`{)e~C`Zx94|1&JE z-TwB*-{&{?tlv<%dtdDAZJ+n;;=j4yVfTfS^|$46wU5iOU*DqZec;yHV`~@vVE4bm zHaoRStT;zuUbe==2vLZ$q1`?o>-+cUKaBN? za6MC2b#HTSy77_7DIP-ZK_Qzg?)Id5yn$7%AJ)x&R12wE%|TV`HB;Uo4xM*_su!>mnA^EOcS`+jXhV9CQGgZ|cLd#>el8U#W4f=8C!X(rnh* zE!&HaeBf@Gs1&Yu>cT{g1P%>nKFjr7KjQy0u*&?1{Fwjv?SF>DvPzHN$fx`c;avaf z`n3%=UzG>mbWG(mP=>H5Kre)NB==#ra$p7!= z<)BTl-}K+=zwO_ue&gTi&1ZhQ&z(Cj?EL(?vbWDI)*m_FYZj+;r9SulEw9VRuK#D? z{j&Szm$P1~`+iJWxNzI$84fxrmyd3;mUYZIyR$>fWQI_x&rJ3OKU$YnYzCe7=9gKO z+ul|GQ2)c$^+$MDuBfOw_V3ool4V`thNtp$b8khoEjaQ%wd8Y@3I9$e1@^o4qV^9K zRmnH*&zRr(pMiIe`NGV9))#9^zwvMWvDy10lj$D&j4NuJa=RvaZ2FnYE!-)tuKj7^ ztlv!gx!(O}c*y>rp>6r!p8AKJ;Oyi*^$`}{>O%2%((S z%Ab~%=*^DpUU$jh$i$QD)^#y_{%ifM>PO__7g75<6F*!&_$^9r^{gSNxDa@K0{*tk5feG}rzp zx|FN*O}%ucjN7%%i%g0YYZoaGLfu31HWJyuRfh`;Ud+&{X?S<`R#wA zzsde*Ncng7(u2Lf?f&lmv)@!Y?``LtWb1HSR*>PMkK>!<`Sz)1ZOp3E|G?k+Cv>GN z$F{!D(HGaQSMz8#<~V$!$EmH{y0~Yf?b)dR46XhT>VNqCXLxA#pP|M4@PCF6GygMO zIR1A!`?vmI>(&3+nN`kzwZHuPzW1jucfESCfZzV|{`B>&v+P<|znCV^Ya^e!=(U{x zw`p_p7u-6YU;EUsqUObs#FQIHHoardw3IN(vfq6D(Ai|COD5j`8QSLY@4Z-3Z~fcc zZ^wn)l$strK^4?NsbnH@$VyL9YHk1JBnFoBs*M|8TQ^7*%2a zPjLUX{|uS+$^RJ=ZvE|l8*Q_HOQl`hdGqv>`=z&ioD;fxev>@k`qEhT;2j_MTc2Oq z!jb!SOZCZ;?6ldwU2Jz*%H9PV>g1l%5)k=h7`s-4@x%H9`+umv+xg%>!>#j=#gFLA zAAj(lL9~MX*!^4c{5rMvKAyiNYeJm!^MfW#`cs@Ix8+#Gq_6n(p8=Y$!hJs+gXAk| zP`-Nct8F^Vx3Yav5|2MEEYX-9*rj*DU`gXaP`-L<`k$fYAN$An){^@?*>$`h{%wA^ z*ZlNvzWx=N1^(&TPZ##tYl%B`Zk1T{mYK^#M#_Z0n0>ot`(&5t zX5VE;ne&>g%Qa_AGkYq^!Br^%ip58JvOhRKynbYDn6CfPe~cgYcWu>JD0cB)c9kh; zJ8@c~M5SOyz`5snh11k`u8x9c)I+=S+f2b3^}{_-Ms){e)XiHzT{td3m3yJft)9TL z##R+RUT|J2{^0!Z>bunoUX{x+e%SMCS|jh?ZT*#cy*ZaNH#*z~9Tk|S+Pl-$iD{mj z#+82@HCEU5q|DR5V#EBf`agr1%7r`Y_7*RW_N>`-M8s8L?t~pF9F5VIv8#I$!n0i& z3gb*cXTLlOTP?Qy(SHVpAMr<{rUZ0nyf@t|b?Z|~hp|DAO~(<9^y2Lnr-koa`_J%4 zsY?Ej(*6%U^~st489wy>?fz~4TmH?;Z}ywseEN6($>a2?o1fh5-Cc1^JF1J}^Y8eZ zuOFsPeb!l?4=OyrSIOOfv+~h}+|Vz7H=DeEV$g4xlqu)9S)x4cmFIc~0w>e`zlm|I zKls>u*tyQIgO=TL5=&B{M=k3^k7zbFF)`$_v*Ri*DN0Su<*Jy|D|K_yW3zXA_aD-a zz8%{sdiVIgR3lHV*=z60O!m5(_DSLNmbE>6?TaoI?yKW$Xy992y=_@q8YDezA6C|E`XaYA=|REE`DKZls%yk-iWlC@`M!cJc>B^o_j#9>uB@K-)}QCy z6y-~IDmK~3El-WF`P6y9y0Na@c>2D_ovZA-LZ-PUoc!zfUsHF{)wL(W_{#63uCn~T z%WQgBfS2y;SKn@}2rDfMpJ$y?zn!0Hy@$~9t@G!X?acpqXp+St#uFc(hlaK#9?KLv zwn%i&guus79_5XT1Hq=*yDEptMt+QW;eXuU^Ug8ZqBPWJkx< z8xnUSIM%HFw`!B4WdIk;?AxOG0(B;-<#MMtKgtiOd%|85E4X5+Wa;tvza~__EYc^-k_EkD)(xcnYrMi>7A>B4lBg_Dm%@Qn0b`*=cW9( zLzCY{U&wlNzMpZQ&c)V?N7s>( z+%-2hrOskegDX$B+U;|?x3W@|{oc7Ncm3`h|9b1j?u551rEl1*ov&Ty_{O!6tM8na z)~P)@MO9U+)zTl{|GX(JtEzUk$Jv-=OGTE&O-p~ITWB5VRUX3MW`9(e*L1G)ln-VJ zRdvx4-+Vqzn9X!x+PQw4$j48oA6@wVw~gJMiZ9pZ6C1+pYtDW-z#(e+N{=&+tr&Ce}-Q7`?9O>mR<9U+25!6D6)5bf8-W+z5o5Z zvIEQVBtKfGzWDaA#L{5TlY^CO`Zv`ieX!$EPM>V>G5+dD9!cp-$DB9LFq~1y_UX^^ zVC_9Q-rK*~{QI|M^Y1E=i*>L0mF~oNYwtAktJ=A==jko^Q;lZ~k{zjzeA^Ejta`fYR&&u!hDEVnwJHCi4(9e8dJy{1i;I`#+0EM~wmiwdKeg_e zV7p|Y#TpZa>$l_F7k}G&mSxw%xV-rvSI3>c@$v2PXD3Yfj8f>1Au9VMI()@EItc=7tm_q|)ai%J@Po~}J6rGD+|%`fU* z!sn$JQr8$PJfHXBuuXaCjo*^LVyEp?dwJ<8@4d`7qIDae>^(P4_S|ck@Rp9p>t~1^ z6`B&Leod*aSTm~YVEK!=3BPZg-)rCz9^Sk@D1DjF;n*CHf-4F$8QDL1Fj>x7Cn@0} z`A%nxmI<4?Jv6C*=iCWV9tW#_z^otlzHl zoD^raYW@j-8|ixSHQ~o!2G||ScT6jrQ8?>x;-Y;n^|j`w*?-5Kdk`tKk^eJGd8ys< zIO7ii0uCu#*SL-R(_ud?;WnCPQZJYXS+byFrRVMmeN|%Js)=s#% zeeyY(Uv+Ku%QuU93OzhNfB(;4^Qsw+OQw5&Tb-OVC%keUr(p0InXZ64LVUdabBcAF zRt9Jv@hZsBZcBc=YDQCWlFUvn!E1V#_-9myJ!RjcIH9{n`OOje-~xe0zC%BsXqYxP zoOpX^MV8kYhdXsAKHiV$SR537vw@fYdG|~^b)`M21$KWQ^vHS}KQB|#_kJ|>x@Ld< zg{fQXjvp(&lRj;;S%S%V^>j;)b2oWzF8guSbzh)C_MxCjOkc8+w>rv4I!JXitu>m% z`jF{+>Sf1Ml@kr-8uBb>ikLlX3THxBoSb~vq!CF z>(hK?k4=&nwr;w;TwtcZD~F2pj_fw?GH!WczoU#Y2aeA>$UODn+6R$(y$hzUwbj`3 z_vaGH169tar@gfISrO)Gr1Iv4-}4|}#zz~cy=-3P6<+wX@xhOfUX!yue2Yu6*xtmQ zYDg=YKK0Imkkw1FvS0D9YfI_~nL71$+F^$3>nD1qe_hnb!s_+3GFx@4pjxci5~V$j zOV!@*d+~J#7k6W~ivYv56wRQ=?GFR3x5u{x2sBK5&3~XkBUvx2=0B+9$V*XU1)}gKNGQ2Hk2^oO1i#?Aq(IcLhCNxN~nw z%?|Agt3pEh*bZ!1R|6||E>t9q`QmzChK3{)L*o3JgZuf-= zyT8xX4O&vy_4w^B?kL6wY~1&chX}4^m^fP~R+h!Gx+Y5VpGDwX_N@}X(pc;!>mU61 z-on0Vhk0A!Zee>q^R!RFmm}EAA9m)i{PcF}%ug~=_f9^NKlSX$>HXKtR;>8T9ra_{ zHp{Z+$n8=$<;*9YIKaPGL$7S1??I+$+r9H#jK0;i?VG5h<2k9~qS*U0-HTLT?|#D) z2+B6cR42~wx~}w{=k5A=($g5Whd(sp+iF|BrRMn!E9oY_l(5B$=H{h`yw-+KU089t z?k+2Ho%2$wz<*D~I=L^2g=GnyUgj#gZAt6uszB8$R~Fqv9}Wb3`X}fv&CtlbPWlR8 zUG(9iJ;~{U<+6Mm;BC5eUv9B1V1u7@5jj33zmHp>yL#v0@%^EA)!#9;GUpaYB zobc?sZ~B2P*Z+Chow=s-u9M@zx!5H?H?cLpUlniLdE(;ZFODJyiiZob~L%QkHBe14l?f8V`<Fzvt_exLotG<`7r(IjWP)%sPz}Guzo3{Fi8z0xx&Yn4|RQBmt!TA;vulP)-_@ygX z&z`oaO?~Z)hpc}uPJ5hr+Q39Y{e`7rv~3aFYBw(3g5Jv(Pd<068$Mbp!E(S|;d`G% zz}<^n%+vcH-T3wMUBs)$8`!0MbF~v~^sB-}wk)%I5yZVWl--Okt?t6!NVfm?S(W8G z6wjxg-jwY?9$d1+x?k;e+^FGfA+@BO2exIuk$L&p3QVl|jh2Qlf&-*iQQA)Pm}DiZAzf zdz(!=61k7t!SPObt*(~UpT9W=Yv)7>mfn;)!aeO-_rBZyzN?P7T}&xuWvgzO%y`!2 zoU%T@;`IK#DmU`pSc4kUejYz=cK1%~d9+A#YWI(5HjfI;>Bc<83q{+ax5~xN^1E=- zF5$_yyydY6j_jVcjC;~C)1E~ZF_GH;k1(c-@usIV&-{5~=Y8fQpE?>&_qo5V6JQX& z*43~2>wlZ!t3(OW>Y{9hI3f1cPcCr26yLBnQALya`U1xfnI^^t=KH!={(X3U&&uoz zZ;~_8YR_GclD!%*-}>PZUGDIE%szMCUe;Teh(uE-OsF9e>&VD)3K}#I5GQ7g4V!mw_dPF(%9Q!}rg)#E;-QPbe=uDMQ} zQ@_o%+IETgLd`(Y=ez#7@*Wp&nzhkPTL1Eo-P0na_GfXNNy+}PbfKjA>H6MFXD05c zZhv1FDX}arZ}I20{pI_szC16!Z~uqCrRL>g%$^(Qx;0qO4RRw-Kw^3-mp=HGGth-> zmc|MO3PB2%CSWR-OFt;JIKQ+gIki~9(i9}drBCn*XI!_ff$wtz^#@b)Qb3LX+XZ5P zE*XNj2J8;}x2>UHYz4Yy&C=XR!OYlH&(zXb!Pv}9&(zdd!OX-=&(s+A#amDd30xWm zzFSEzzbIWlCo`#7zo4=tBR@~Spt86G{hBUtkWuU#Q|L8OrlxvErp5}!#uj>p#wH4e zW@dVZmS#Awjw9@u{NfzF%u>D7lv4e=lCspCl#J3${er}T)S_a2*NTFo)Z*gI{5;)| z)Z7A4Prg_`wW6S?B)1?(FQX(k2kmAra8iKz75jBkCZIdUAPL3|^=c`s_eY`J8wE-z zaMLq$N>Ymy^s$aF7#W&_(i41qVQ!@NUS?A*b_d^d ze#ESrq;f>%V8fE*pVr^6TQGZyj&D$Oa4=IN+l7ph`o9O_%|9k_)_+veUz9XiFKl(` z6y3>3*X@kd`}X1g-k>|IlXgprWV+BW;z#lUB;+}w8O34e+T#gAW9 zJKx+e<)BIb!=^=ZB9`z6e>;Bt!^GpKl)vs?S6n9%eXn6xhkTywp4m@aVim5>*#B6y zMmGKLog3A`e4hemZdz6*bng6~D^IWeEDBI7TX*T(;?G{!drW2a-_?$d{kHe+$>%Q% zGcSAzcL~0>?&<0d^>@`TCFV?ic5i3at7&2E*}>AwdShFvAI1K;z~vYjTDDCEOOz!+`OVlMlwx( zb!p@@UDLfU_w3xQwC3Ix$2TXr^44wdUF)qsKRdL3bg`ZzdUz>-#Uqp57HUW5tr0HNsA8|x zWbK&O_onkEgzI1X^J1=2ZwKS7(3|hlLyE3SHVHc%$y3l?(5GwA7xhy6@}w)jjE`8x zN`9HN!o_mo>($%iJwKn_Ew6O*OK(Y+r*To=*|J0HGP#!*HTJvDFsNGm=XP0mzv#*< zorgpn4IG!68JKE(T0ZyXg?US=KcwDPmfFy{)O*3kvnlG1N3|BdcV{yD$uq0}>}JV* zWvQlTt7UG>=3RQN=lIRy#*Vn~H*fcCt=E};l5fpgHW{6UF!jBxy;aY%c5l{cF_z=o zknwk^MTo!5OU<)qrk`Rm+goWO7b|Dz5&Pigi?YqU2PXvH%KCD!>tnV>MC=CRd%Lt| z<;J_r4#{f{)Mf7dmukMF;I`y08;f_^KLZ%QJ)G6yl(s1;;dM59P`8?e%O+t4Hzj_K zuy4zhBqG;|ZNDFLyst+=v*K^h-I+x-Gp?}|+&U;yVls7tmFk{lHd}USoIiS-^ZO2+ zko=7=-4C2;t8n^f9WF0zWb)j4LX9%>L%l+;`%~5|{GnLx_iQ_3*}a*Ks{W4>9CBKc z4I&=2`^p}FIL|lx?v3~(r%PtKR_+TDx$|i1p1Qm8Uh6rmyB}>TVSaLSQA8@kW|0Md zSufu^oWOdCq0{AM+TMz$Z$@3_vt5fPyUhNoR&&+-|IVMKUEfo-?!H+h^8T#lnN2FE zHpgt|DKq%dy+F=6d+)W!4a}^`B92kHr3!JorB9X`>ZG5xiAqm@ARNLrH~a(h(I@O& zUzuBfF@D+nEyQ-J5`*p+*%PAj+wZ;d5|>Fja3)6STGQNBtsEv5r~YiZuBBE!@4Sff zoifF^jpCDXcm7`RJZJSyrGttu9-P~~Z-2oesX3P--klZQePi0a+Y2^X9$%R!^JFt$ zcGB#FjS72O|IVHy;cz4LZT(_l!Q@(dfpGv-jfz z_T0bOn{pQJpY8iYX|i>Eh|$zK191zDBBoWseCIy7FRZM)o+O%k;PSk8QtKqo-~bfhBfnlR!MRi_MMoy{&^l3!()xuW6#x;#9IZAJ=wIQyifH;{PZx} zNk?+__8s~bH$5RC$(?K03coE|z6QGelS<6k_x=gDYwV=TTa%7Hx&MwKIyKX^-nxst}1Sl{*PTgRK`ccR{j z|JNRt*zufu$Ain$I5q`NTFd;m^2Qd&h3^&aTa+s9?fJSahhx)%m2;n1TsS!;n3wVQ z_OuEkx9hS_HqVwEzJ1rxocCk?p|2gcgbFHSx%V>VTC;69w=$g;R!s>IIcsxR?4)MC|iSyksrz!GvX}Z@pyS@7+ znYJ38`;^q;aCfTXm8`WlY+0HN-j)72c{@?TmSeRC_uZ9mr}niv`mPL|dONE9{=OyM z6`tii6A#VaRX&CL=xkxL`87977cOwV7onAVB;u*h{ko&)vIHh^zwr1Qv-FTyOJ(>~ zO_moKYuTAA?@b9Vd~-xIlzoNdA>D2NJlDUlzQVTgtjvBfo|%lSQtfMd^}3hO44bpT zJLhbDK*!7q$=}(YH=`r!o-{Bny0VyK`e&vcwH=cCIXol(r20>Al}|9SnyTt?$WEiw z-l1%#JfBtb>9QM1k(XK)@ZXT$(&^c(Cz$NZE+~IY+wk_MlgoS#I8G>;;cmxZ8I^6Z z`q|$#dbb57?1TTRY>ifrNZql%yKtwA<;ds1 zH?L|d2fa8N+&}+VrZMmTbB3-;BGKW-&m&g}&Q&u_5iEE+>HP1Va|;5_x0VEb4tQl% zyrKI2v6cG_%Qo+7nBAKW;k5yH;xd8dVztq%N7cA z6uqm!OA=iDqRahNdOJa{$+FAdtEnEC(-i!5J))QK)4jGiJfB!x<-o32H z$MoL+iAzp9POcOAXnT51%Y`-j)qhUPcptJ%=UnwkM{9eB@Dhnxznrd3X>wwSR#&$U zo|m$>$H*i4ozR83S5<`qZtC1x^Z0qGe*a>YO*@~rOiT@A+b&k3@3z$YV8xC#GY;{5 z*df#|7$siV@jGLK`LdU(eGl}v1&S(KJyQDKF5vp$0MC@BM}g%Bq@zq26w9>lT{v*V zbN`b2dsN(HKdv-B`cK#W>_Xdu);=+=_hBzZJ|w(9Z@+i)JG(H|BTQR1R`I`jC$YhF_lchuGYh0%Ke%)8 z{577&qIECTa+e$t5ZvT<*hqHAWk+|FJ)Qo=n}VO2MDK|aXn!E~+J$jTx2EpFv}CE{ zKRq08?_`uns7!QE*mL2{mKodhmVIB-Bp36N=iUTiEv_!58#fi6MpoSCOXfJ5G3Am} z>Z6`QZC_jOxP_%{)+hv{}@&(+S?1XUmJdzK_wv-tX+ zDG86I7U(gSNeKV5_laC4IO~Y ztVlop%>L8WX~C^2w&(dCE}oUNZ$k8?wyh@3Qi;U}yiU8coPY87--6;LAK#r&oFo!+ zE&fU2`X^e)wwcYjmagq~^e4B&G7fj&>usyf|N63UUb#j6HG%D4Qw~Q?OS%@Ztq zM@03kxUnmM?SD|n!{?h;O!D0l7k^}8j;#BSeJ1k1zdoxssQkYcv;S?bV4wi&*|?+@ zmlP{lT7WxAc6MB_UNJ}jrp7xn1>7qKUEd43{2WOcNC>9PFEKY2q)x*nKUp`pB(bPO zQ^C*xd}XhKhG$7?uAU)C0CZ<{5LCbjBmf!?2vX2UOe!uZN=yc8g7p94LV5-ubtWJ; zDrkT^!@7_@ux?6fab|j+ZhC%VPBBOgcys`d8i+||5R;NKOA~XntODPARBw3RVm9HL56BwIS56po^SKib|79@{2&q4WT|p7S;p#+z{&Xl*E!m z-Q3jNq|~Bf-Nd{U-GZY0g4Cjt%v7*;Xkg$`1u+d8J}IfG1*t{4MX6~yso-%1-TXY= zl8jW{jQpa^s{A}n1w%t1w$ifVBt~%F$Wq4^j>d0Ayi^=}_nphRyB^)^JrSX05!1nOI`2uO(u)Q4aJ zunMTZzyc;9El|IJ1x!H#P}hS6%s>Ljt~CbjrNxkJ zqy_f0Db%qDL6FByp)N*eH6xIR&CtCD@`D+=&p=)eG_cVo-f$4)tS6MrK|%sF2I3Oe)F*l{4m0 zkD|#y41xL=G!ANLX$cA!bEt32GK)$|6LWOaO7p!nlcRvD85ESr znxP2{bgWZjmaKt#3M z#N0s7&=9N3L5YcwZX;to0}HGn4oYkUbekC(>6w{h(+zGW5zuXFW~ygqip}NV))caC zXlaNX-DZY*X4t|E+~7jd3-SnRn3q}$k7&(IQEDmOw;X_!gH%u>(92y0S7 zPiN2;ICAzfH#5;QH^Nq|fE6RV8`{J})@^8HqGw`?Er)>H^8|ET80nc>V2dtriyv7t zw84RFwUL>Ho`I<$mZSpi5)jgDWUgmwiY>B@(MvQek!@yXtY>J5)o%2JhE2Dzfu1GS zlH3?QpEqFjp46b-ALLAeM>l#> z!;(-zx=l^6r4|$P+7sHLA<}M8(n1b3EHyXCZtN-51U;=`NiBw^hI&S3*oqc#PZ!y4 zPz<6b7E@CbJwr>Zn$eRQmOwMZnY+OqXJo6f=r+b#5r9(`if&LGpt{_|!bs2D99y{r z?uaAn#uDTvI2%jg4m$zeMuz5kW=7abRB) zBT+TuZdrpzZ%}lD$|-c+xO!n`AjJf9tAYHq1#X@;$V3LbDGpxMMg&l2nK3V3)5Su>V2ZfIbvXJUb^Py)9ok#%Ef z@0gmI>ses!5txHg1|i)z3nlP)7_x3`E(Z+^nPTg(gB^;j8=P)YBiq!_QqKTecLF@1 zMnE&}TEqf0=m^sttf23nACk|dk2I?0mRW*2f(9;bVLBX3OEU6{6f|5C%Q90G9FvL? zGZJ%)t%DMC(iFn=6x@sQi;Giry-V|oOL9`nQZ>2sLo!QpQWZ3uN;7jn1NsUc;1NiL zV9*-T++v0D%#sWRr~Lfl61@QM>QPND{oqpY41$7&CYQc*Q7ULoKtaPNF(fqtqCBIr zAhoC{HBF%)F*!Ri9b`)YcxEHDNI|0@B`qX1Lf25w&>%@6wW1_7FC{faA*oU!ARxay zwa7#v%s|gl&qR|;-#fLkJijQV7-3Ilex6HWNveW|i7<5Mq`JU|EGcxR?=W!~!IUBxVe%B+$gnj4<_D zfO?Ur>I@Bx%+T#HG_)|q5HmKhzz_qCcY=&FK)A)w3^dyc5=0WS0F}q6Vnzm_4i=i2 zp(Tc!jf_FL1WlbOsP%>>W)2#HLKCyFFhO^-u>q)vMO9~P1e*Op6EiWu)N2YVPtnww zgXVM4#4HVr(d{)c0M(|b>P(D4qZw#o#s(PiVPb5FA!Y^|kOFB%3VSnC(1a975E1qk z=0+HCX<=c35to(*pt1~AucZ-a`8b-G31-@`Gy@d~XzH+py``n40a~~i8W>`vD?>v| zjBqwIG6a?LD0UbcnSkcsQN>IQKz$N4F++@SF|;tUKu>Fi7A6MhVP$9m%8?-B5aDcS zVFnsL0|~;z+0eoqR34*>Sz3ao1JT7W(w~umAx8Ka85v;MVPtA-g6=*eQxlB5Z)9o; z+CGkAuaSkBg%MiV8(ElRyzGY=Y@8e`FG3L2Bxdo<}g`t@tdKqS6Xb$RXq1bC-2x@VHgb?9k0m@ux z{;)7I!YE@cOieA&!^*J$qL1B~+C!omO}oGmO2LE|T=_F5QWOiAKAR(kUw=}{`la|JqX~@zTqpf0LX#(mp zgA7EZ4NFsu_JM_^DX7zlrVg|HXJKiEkq0d-%|TsbG`$v}B~<8Q80|0%OG}J;)e@`= zL?FVz(!c;T4gnH``vVlr=@14E4bW@%uA(LS&=Fa}MsqM2t5>Vcw(nP9XrEe%XC z^0}pfDQF!DnqJI0z|sJ-4z)Be$A}|K0}G6Hgr$K6sC$NHhb2ZE-O|7kBaSQ$4KU)! z(h$7N4b?nDLyS1GG&I6!gIF3GW7J2W 2) + throw std::range_error("greet: index out of range"); + + return msgs[x]; + } + +To wrap this function in standard C++ using the Python 'C' API, we'd +need something like this:: + + extern "C" // all Python interactions use 'C' linkage and calling convention + { + // Wrapper to handle argument/result conversion and checking + PyObject* greet_wrap(PyObject* args, PyObject * keywords) + { + int x; + if (PyArg_ParseTuple(args, "i", &x)) // extract/check arguments + { + char const* result = greet(x); // invoke wrapped function + return PyString_FromString(result); // convert result to Python + } + return 0; // error occurred + } + + // Table of wrapped functions to be exposed by the module + static PyMethodDef methods[] = { + { "greet", greet_wrap, METH_VARARGS, "return one of 3 parts of a greeting" } + , { NULL, NULL, 0, NULL } // sentinel + }; + + // module initialization function + DL_EXPORT init_hello() + { + (void) Py_InitModule("hello", methods); // add the methods to the module + } + } + +Now here's the wrapping code we'd use to expose it with Boost.Python:: + + #include + using namespace boost::python; + BOOST_PYTHON_MODULE(hello) + { + def("greet", greet, "return one of 3 parts of a greeting"); + } + +and here it is in action:: + + >>> import hello + >>> for x in range(3): + ... print hello.greet(x) + ... + hello + Boost.Python + world! + +Aside from the fact that the 'C' API version is much more verbose, +it's worth noting a few things that it doesn't handle correctly: + +* The original function accepts an unsigned integer, and the Python + 'C' API only gives us a way of extracting signed integers. The + Boost.Python version will raise a Python exception if we try to pass + a negative number to ``hello.greet``, but the other one will proceed + to do whatever the C++ implementation does when converting an + negative integer to unsigned (usually wrapping to some very large + number), and pass the incorrect translation on to the wrapped + function. + +* That brings us to the second problem: if the C++ ``greet()`` + function is called with a number greater than 2, it will throw an + exception. Typically, if a C++ exception propagates across the + boundary with code generated by a 'C' compiler, it will cause a + crash. As you can see in the first version, there's no C++ + scaffolding there to prevent this from happening. Functions wrapped + by Boost.Python automatically include an exception-handling layer + which protects Python users by translating unhandled C++ exceptions + into a corresponding Python exception. + +* A slightly more-subtle limitation is that the argument conversion + used in the Python 'C' API case can only get that integer ``x`` in + *one way*. PyArg_ParseTuple can't convert Python ``long`` objects + (arbitrary-precision integers) which happen to fit in an ``unsigned + int`` but not in a ``signed long``, nor will it ever handle a + wrapped C++ class with a user-defined implicit ``operator unsigned + int()`` conversion. Boost.Python's dynamic type conversion + registry allows users to add arbitrary conversion methods. + +================== + Library Overview +================== + +This section outlines some of the library's major features. Except as +neccessary to avoid confusion, details of library implementation are +omitted. + +------------------ + Exposing Classes +------------------ + +C++ classes and structs are exposed with a similarly-terse interface. +Given:: + + struct World + { + void set(std::string msg) { this->msg = msg; } + std::string greet() { return msg; } + std::string msg; + }; + +The following code will expose it in our extension module:: + + #include + BOOST_PYTHON_MODULE(hello) + { + class_("World") + .def("greet", &World::greet) + .def("set", &World::set) + ; + } + +Although this code has a certain pythonic familiarity, people +sometimes find the syntax bit confusing because it doesn't look like +most of the C++ code they're used to. All the same, this is just +standard C++. Because of their flexible syntax and operator +overloading, C++ and Python are great for defining domain-specific +(sub)languages +(DSLs), and that's what we've done in Boost.Python. To break it down:: + + class_("World") + +constructs an unnamed object of type ``class_`` and passes +``"World"`` to its constructor. This creates a new-style Python class +called ``World`` in the extension module, and associates it with the +C++ type ``World`` in the Boost.Python type conversion registry. We +might have also written:: + + class_ w("World"); + +but that would've been more verbose, since we'd have to name ``w`` +again to invoke its ``def()`` member function:: + + w.def("greet", &World::greet) + +There's nothing special about the location of the dot for member +access in the original example: C++ allows any amount of whitespace on +either side of a token, and placing the dot at the beginning of each +line allows us to chain as many successive calls to member functions +as we like with a uniform syntax. The other key fact that allows +chaining is that ``class_<>`` member functions all return a reference +to ``*this``. + +So the example is equivalent to:: + + class_ w("World"); + w.def("greet", &World::greet); + w.def("set", &World::set); + +It's occasionally useful to be able to break down the components of a +Boost.Python class wrapper in this way, but the rest of this article +will stick to the terse syntax. + +For completeness, here's the wrapped class in use: :: + + >>> import hello + >>> planet = hello.World() + >>> planet.set('howdy') + >>> planet.greet() + 'howdy' + +Constructors +============ + +Since our ``World`` class is just a plain ``struct``, it has an +implicit no-argument (nullary) constructor. Boost.Python exposes the +nullary constructor by default, which is why we were able to write: :: + + >>> planet = hello.World() + +However, well-designed classes in any language may require constructor +arguments in order to establish their invariants. Unlike Python, +where ``__init__`` is just a specially-named method, In C++ +constructors cannot be handled like ordinary member functions. In +particular, we can't take their address: ``&World::World`` is an +error. The library provides a different interface for specifying +constructors. Given:: + + struct World + { + World(std::string msg); // added constructor + ... + +we can modify our wrapping code as follows:: + + class_("World", init()) + ... + +of course, a C++ class may have additional constructors, and we can +expose those as well by passing more instances of ``init<...>`` to +``def()``:: + + class_("World", init()) + .def(init()) + ... + +Boost.Python allows wrapped functions, member functions, and +constructors to be overloaded to mirror C++ overloading. + +Data Members and Properties +=========================== + +Any publicly-accessible data members in a C++ class can be easily +exposed as either ``readonly`` or ``readwrite`` attributes:: + + class_("World", init()) + .def_readonly("msg", &World::msg) + ... + +and can be used directly in Python: :: + + >>> planet = hello.World('howdy') + >>> planet.msg + 'howdy' + +This does *not* result in adding attributes to the ``World`` instance +``__dict__``, which can result in substantial memory savings when +wrapping large data structures. In fact, no instance ``__dict__`` +will be created at all unless attributes are explicitly added from +Python. Boost.Python owes this capability to the new Python 2.2 type +system, in particular the descriptor interface and ``property`` type. + +In C++, publicly-accessible data members are considered a sign of poor +design because they break encapsulation, and style guides usually +dictate the use of "getter" and "setter" functions instead. In +Python, however, ``__getattr__``, ``__setattr__``, and since 2.2, +``property`` mean that attribute access is just one more +well-encapsulated syntactic tool at the programmer's disposal. +Boost.Python bridges this idiomatic gap by making Python ``property`` +creation directly available to users. If ``msg`` were private, we +could still expose it as attribute in Python as follows:: + + class_("World", init()) + .add_property("msg", &World::greet, &World::set) + ... + +The example above mirrors the familiar usage of properties in Python +2.2+: :: + + >>> class World(object): + ... __init__(self, msg): + ... self.__msg = msg + ... def greet(self): + ... return self.__msg + ... def set(self, msg): + ... self.__msg = msg + ... msg = property(greet, set) + +Operator Overloading +==================== + +The ability to write arithmetic operators for user-defined types has +been a major factor in the success of both languages for numerical +computation, and the success of packages like NumPy_ attests to the +power of exposing operators in extension modules. Boost.Python +provides a concise mechanism for wrapping operator overloads. The +example below shows a fragment from a wrapper for the Boost rational +number library:: + + class_ >("rational_int") + .def(init()) // constructor, e.g. rational_int(3,4) + .def("numerator", &rational::numerator) + .def("denominator", &rational::denominator) + .def(-self) // __neg__ (unary minus) + .def(self + self) // __add__ (homogeneous) + .def(self * self) // __mul__ + .def(self + int()) // __add__ (heterogenous) + .def(int() + self) // __radd__ + ... + +The magic is performed using a simplified application of "expression +templates" [VELD1995]_, a technique originally developed for +optimization of high-performance matrix algebra expressions. The +essence is that instead of performing the computation immediately, +operators are overloaded to construct a type *representing* the +computation. In matrix algebra, dramatic optimizations are often +available when the structure of an entire expression can be taken into +account, rather than evaluating each operation "greedily". +Boost.Python uses the same technique to build an appropriate Python +method object based on expressions involving ``self``. + +.. _NumPy: http://www.pfdubois.com/numpy/ + +Inheritance +=========== + +C++ inheritance relationships can be represented to Boost.Python by adding +an optional ``bases<...>`` argument to the ``class_<...>`` template +parameter list as follows:: + + class_ >("Derived") + ... + +This has two effects: + +1. When the ``class_<...>`` is created, Python type objects + corresponding to ``Base1`` and ``Base2`` are looked up in + Boost.Python's registry, and are used as bases for the new Python + ``Derived`` type object, so methods exposed for the Python ``Base1`` + and ``Base2`` types are automatically members of the ``Derived`` + type. Because the registry is global, this works correctly even if + ``Derived`` is exposed in a different module from either of its + bases. + +2. C++ conversions from ``Derived`` to its bases are added to the + Boost.Python registry. Thus wrapped C++ methods expecting (a + pointer or reference to) an object of either base type can be + called with an object wrapping a ``Derived`` instance. Wrapped + member functions of class ``T`` are treated as though they have an + implicit first argument of ``T&``, so these conversions are + neccessary to allow the base class methods to be called for derived + objects. + +Of course it's possible to derive new Python classes from wrapped C++ +class instances. Because Boost.Python uses the new-style class +system, that works very much as for the Python built-in types. There +is one significant detail in which it differs: the built-in types +generally establish their invariants in their ``__new__`` function, so +that derived classes do not need to call ``__init__`` on the base +class before invoking its methods : :: + + >>> class L(list): + ... def __init__(self): + ... pass + ... + >>> L().reverse() + >>> + +Because C++ object construction is a one-step operation, C++ instance +data cannot be constructed until the arguments are available, in the +``__init__`` function: :: + + >>> class D(SomeBoostPythonClass): + ... def __init__(self): + ... pass + ... + >>> D().some_boost_python_method() + Traceback (most recent call last): + File "", line 1, in ? + TypeError: bad argument type for built-in operation + +This happened because Boost.Python couldn't find instance data of type +``SomeBoostPythonClass`` within the ``D`` instance; ``D``'s ``__init__`` +function masked construction of the base class. It could be corrected +by either removing ``D``'s ``__init__`` function or having it call +``SomeBoostPythonClass.__init__(...)`` explicitly. + +Virtual Functions +================= + +Deriving new types in Python from extension classes is not very +interesting unless they can be used polymorphically from C++. In +other words, Python method implementations should appear to override +the implementation of C++ virtual functions when called *through base +class pointers/references from C++*. Since the only way to alter the +behavior of a virtual function is to override it in a derived class, +the user must build a special derived class to dispatch a polymorphic +class' virtual functions:: + + // + // interface to wrap: + // + class Base + { + public: + virtual int f(std::string x) { return 42; } + virtual ~Base(); + }; + + int calls_f(Base const& b, std::string x) { return b.f(x); } + + // + // Wrapping Code + // + + // Dispatcher class + struct BaseWrap : Base + { + // Store a pointer to the Python object + BaseWrap(PyObject* self_) : self(self_) {} + PyObject* self; + + // Default implementation, for when f is not overridden + int f_default(std::string x) { return this->Base::f(x); } + // Dispatch implementation + int f(std::string x) { return call_method(self, "f", x); } + }; + + ... + def("calls_f", calls_f); + class_("Base") + .def("f", &Base::f, &BaseWrap::f_default) + ; + +Now here's some Python code which demonstrates: :: + + >>> class Derived(Base): + ... def f(self, s): + ... return len(s) + ... + >>> calls_f(Base(), 'foo') + 42 + >>> calls_f(Derived(), 'forty-two') + 9 + +Things to notice about the dispatcher class: + +* The key element which allows overriding in Python is the + ``call_method`` invocation, which uses the same global type + conversion registry as the C++ function wrapping does to convert its + arguments from C++ to Python and its return type from Python to C++. + +* Any constructor signatures you wish to wrap must be replicated with + an initial ``PyObject*`` argument + +* The dispatcher must store this argument so that it can be used to + invoke ``call_method`` + +* The ``f_default`` member function is needed when the function being + exposed is not pure virtual; there's no other way ``Base::f`` can be + called on an object of type ``BaseWrap``, since it overrides ``f``. + +Deeper Reflection on the Horizon? +================================= + +Admittedly, this formula is tedious to repeat, especially on a project +with many polymorphic classes. That it is neccessary reflects some +limitations in C++'s compile-time introspection capabilities: there's +no way to enumerate the members of a class and find out which are +virtual functions. At least one very promising project has been +started to write a front-end which can generate these dispatchers (and +other wrapping code) automatically from C++ headers. + +Pyste_ is being developed by Bruno da Silva de Oliveira. It builds on +GCC_XML_, which generates an XML version of GCC's internal program +representation. Since GCC is a highly-conformant C++ compiler, this +ensures correct handling of the most-sophisticated template code and +full access to the underlying type system. In keeping with the +Boost.Python philosophy, a Pyste interface description is neither +intrusive on the code being wrapped, nor expressed in some unfamiliar +language: instead it is a 100% pure Python script. If Pyste is +successful it will mark a move away from wrapping everything directly +in C++ for many of our users. It will also allow us the choice to +shift some of the metaprogram code from C++ to Python. We expect that +soon, not only our users but the Boost.Python developers themselves +will be "thinking hybrid" about their own code. + +.. _`GCC_XML`: http://www.gccxml.org/HTML/Index.html +.. _`Pyste`: http://www.boost.org/libs/python/pyste + +--------------- + Serialization +--------------- + +*Serialization* is the process of converting objects in memory to a +form that can be stored on disk or sent over a network connection. The +serialized object (most often a plain string) can be retrieved and +converted back to the original object. A good serialization system will +automatically convert entire object hierarchies. Python's standard +``pickle`` module is just such a system. It leverages the language's strong +runtime introspection facilities for serializing practically arbitrary +user-defined objects. With a few simple and unintrusive provisions this +powerful machinery can be extended to also work for wrapped C++ objects. +Here is an example:: + + #include + + struct World + { + World(std::string a_msg) : msg(a_msg) {} + std::string greet() const { return msg; } + std::string msg; + }; + + #include + using namespace boost::python; + + struct World_picklers : pickle_suite + { + static tuple + getinitargs(World const& w) { return make_tuple(w.greet()); } + }; + + BOOST_PYTHON_MODULE(hello) + { + class_("World", init()) + .def("greet", &World::greet) + .def_pickle(World_picklers()) + ; + } + +Now let's create a ``World`` object and put it to rest on disk:: + + >>> import hello + >>> import pickle + >>> a_world = hello.World("howdy") + >>> pickle.dump(a_world, open("my_world", "w")) + +In a potentially *different script* on a potentially *different +computer* with a potentially *different operating system*:: + + >>> import pickle + >>> resurrected_world = pickle.load(open("my_world", "r")) + >>> resurrected_world.greet() + 'howdy' + +Of course the ``cPickle`` module can also be used for faster +processing. + +Boost.Python's ``pickle_suite`` fully supports the ``pickle`` protocol +defined in the standard Python documentation. Like a __getinitargs__ +function in Python, the pickle_suite's getinitargs() is responsible for +creating the argument tuple that will be use to reconstruct the pickled +object. The other elements of the Python pickling protocol, +__getstate__ and __setstate__ can be optionally provided via C++ +getstate and setstate functions. C++'s static type system allows the +library to ensure at compile-time that nonsensical combinations of +functions (e.g. getstate without setstate) are not used. + +Enabling serialization of more complex C++ objects requires a little +more work than is shown in the example above. Fortunately the +``object`` interface (see next section) greatly helps in keeping the +code manageable. + +------------------ + Object interface +------------------ + +Experienced 'C' language extension module authors will be familiar +with the ubiquitous ``PyObject*``, manual reference-counting, and the +need to remember which API calls return "new" (owned) references or +"borrowed" (raw) references. These constraints are not just +cumbersome but also a major source of errors, especially in the +presence of exceptions. + +Boost.Python provides a class ``object`` which automates reference +counting and provides conversion to Python from C++ objects of +arbitrary type. This significantly reduces the learning effort for +prospective extension module writers. + +Creating an ``object`` from any other type is extremely simple:: + + object s("hello, world"); // s manages a Python string + +``object`` has templated interactions with all other types, with +automatic to-python conversions. It happens so naturally that it's +easily overlooked:: + + object ten_Os = 10 * s[4]; // -> "oooooooooo" + +In the example above, ``4`` and ``10`` are converted to Python objects +before the indexing and multiplication operations are invoked. + +The ``extract`` class template can be used to convert Python objects +to C++ types:: + + double x = extract(o); + +If a conversion in either direction cannot be performed, an +appropriate exception is thrown at runtime. + +The ``object`` type is accompanied by a set of derived types +that mirror the Python built-in types such as ``list``, ``dict``, +``tuple``, etc. as much as possible. This enables convenient +manipulation of these high-level types from C++:: + + dict d; + d["some"] = "thing"; + d["lucky_number"] = 13; + list l = d.keys(); + +This almost looks and works like regular Python code, but it is pure +C++. Of course we can wrap C++ functions which accept or return +``object`` instances. + +================= + Thinking hybrid +================= + +Because of the practical and mental difficulties of combining +programming languages, it is common to settle a single language at the +outset of any development effort. For many applications, performance +considerations dictate the use of a compiled language for the core +algorithms. Unfortunately, due to the complexity of the static type +system, the price we pay for runtime performance is often a +significant increase in development time. Experience shows that +writing maintainable C++ code usually takes longer and requires *far* +more hard-earned working experience than developing comparable Python +code. Even when developers are comfortable working exclusively in +compiled languages, they often augment their systems by some type of +ad hoc scripting layer for the benefit of their users without ever +availing themselves of the same advantages. + +Boost.Python enables us to *think hybrid*. Python can be used for +rapidly prototyping a new application; its ease of use and the large +pool of standard libraries give us a head start on the way to a +working system. If necessary, the working code can be used to +discover rate-limiting hotspots. To maximize performance these can +be reimplemented in C++, together with the Boost.Python bindings +needed to tie them back into the existing higher-level procedure. + +Of course, this *top-down* approach is less attractive if it is clear +from the start that many algorithms will eventually have to be +implemented in C++. Fortunately Boost.Python also enables us to +pursue a *bottom-up* approach. We have used this approach very +successfully in the development of a toolbox for scientific +applications. The toolbox started out mainly as a library of C++ +classes with Boost.Python bindings, and for a while the growth was +mainly concentrated on the C++ parts. However, as the toolbox is +becoming more complete, more and more newly added functionality can be +implemented in Python. + +.. image:: python_cpp_mix.jpg + +This figure shows the estimated ratio of newly added C++ and Python +code over time as new algorithms are implemented. We expect this +ratio to level out near 70% Python. Being able to solve new problems +mostly in Python rather than a more difficult statically typed +language is the return on our investment in Boost.Python. The ability +to access all of our code from Python allows a broader group of +developers to use it in the rapid development of new applications. + +===================== + Development history +===================== + +The first version of Boost.Python was developed in 2000 by Dave +Abrahams at Dragon Systems, where he was privileged to have Tim Peters +as a guide to "The Zen of Python". One of Dave's jobs was to develop +a Python-based natural language processing system. Since it was +eventually going to be targeting embedded hardware, it was always +assumed that the compute-intensive core would be rewritten in C++ to +optimize speed and memory footprint [#proto]_. The project also wanted to +test all of its C++ code using Python test scripts [#test]_. The only +tool we knew of for binding C++ and Python was SWIG_, and at the time +its handling of C++ was weak. It would be false to claim any deep +insight into the possible advantages of Boost.Python's approach at +this point. Dave's interest and expertise in fancy C++ template +tricks had just reached the point where he could do some real damage, +and Boost.Python emerged as it did because it filled a need and +because it seemed like a cool thing to try. + +This early version was aimed at many of the same basic goals we've +described in this paper, differing most-noticeably by having a +slightly more cumbersome syntax and by lack of special support for +operator overloading, pickling, and component-based development. +These last three features were quickly added by Ullrich Koethe and +Ralf Grosse-Kunstleve [#feature]_, and other enthusiastic contributors arrived +on the scene to contribute enhancements like support for nested +modules and static member functions. + +By early 2001 development had stabilized and few new features were +being added, however a disturbing new fact came to light: Ralf had +begun testing Boost.Python on pre-release versions of a compiler using +the EDG_ front-end, and the mechanism at the core of Boost.Python +responsible for handling conversions between Python and C++ types was +failing to compile. As it turned out, we had been exploiting a very +common bug in the implementation of all the C++ compilers we had +tested. We knew that as C++ compilers rapidly became more +standards-compliant, the library would begin failing on more +platforms. Unfortunately, because the mechanism was so central to the +functioning of the library, fixing the problem looked very difficult. + +Fortunately, later that year Lawrence Berkeley and later Lawrence +Livermore National labs contracted with `Boost Consulting`_ for support +and development of Boost.Python, and there was a new opportunity to +address fundamental issues and ensure a future for the library. A +redesign effort began with the low level type conversion architecture, +building in standards-compliance and support for component-based +development (in contrast to version 1 where conversions had to be +explicitly imported and exported across module boundaries). A new +analysis of the relationship between the Python and C++ objects was +done, resulting in more intuitive handling for C++ lvalues and +rvalues. + +The emergence of a powerful new type system in Python 2.2 made the +choice of whether to maintain compatibility with Python 1.5.2 easy: +the opportunity to throw away a great deal of elaborate code for +emulating classic Python classes alone was too good to pass up. In +addition, Python iterators and descriptors provided crucial and +elegant tools for representing similar C++ constructs. The +development of the generalized ``object`` interface allowed us to +further shield C++ programmers from the dangers and syntactic burdens +of the Python 'C' API. A great number of other features including C++ +exception translation, improved support for overloaded functions, and +most significantly, CallPolicies for handling pointers and +references, were added during this period. + +In October 2002, version 2 of Boost.Python was released. Development +since then has concentrated on improved support for C++ runtime +polymorphism and smart pointers. Peter Dimov's ingenious +``boost::shared_ptr`` design in particular has allowed us to give the +hybrid developer a consistent interface for moving objects back and +forth across the language barrier without loss of information. At +first, we were concerned that the sophistication and complexity of the +Boost.Python v2 implementation might discourage contributors, but the +emergence of Pyste_ and several other significant feature +contributions have laid those fears to rest. Daily questions on the +Python C++-sig and a backlog of desired improvements show that the +library is getting used. To us, the future looks bright. + +.. _`EDG`: http://www.edg.com + +============= + Conclusions +============= + +Boost.Python achieves seamless interoperability between two rich and +complimentary language environments. Because it leverages template +metaprogramming to introspect about types and functions, the user +never has to learn a third syntax: the interface definitions are +written in concise and maintainable C++. Also, the wrapping system +doesn't have to parse C++ headers or represent the type system: the +compiler does that work for us. + +Computationally intensive tasks play to the strengths of C++ and are +often impossible to implement efficiently in pure Python, while jobs +like serialization that are trivial in Python can be very difficult in +pure C++. Given the luxury of building a hybrid software system from +the ground up, we can approach design with new confidence and power. + +=========== + Citations +=========== + +.. [VELD1995] T. Veldhuizen, "Expression Templates," C++ Report, + Vol. 7 No. 5 June 1995, pp. 26-31. + http://osl.iu.edu/~tveldhui/papers/Expression-Templates/exprtmpl.html + +=========== + Footnotes +=========== + +.. [#proto] In retrospect, it seems that "thinking hybrid" from the + ground up might have been better for the NLP system: the + natural component boundaries defined by the pure python + prototype turned out to be inappropriate for getting the + desired performance and memory footprint out of the C++ core, + which eventually caused some redesign overhead on the Python + side when the core was moved to C++. + +.. [#test] We also have some reservations about driving all C++ + testing through a Python interface, unless that's the only way + it will be ultimately used. Any transition across language + boundaries with such different object models can inevitably + mask bugs. + +.. [#feature] These features were expressed very differently in v1 of + Boost.Python