diff --git a/.gitignore b/.gitignore index 49c7857..fb959cc 100644 --- a/.gitignore +++ b/.gitignore @@ -103,4 +103,8 @@ Generated_Code #added for RIA/Silverlight projects _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML -UpgradeLog*.htm \ No newline at end of file +UpgradeLog*.htm + + +# Containerized build volume +build/output \ No newline at end of file diff --git a/BuildWithDocker.ps1 b/BuildWithDocker.ps1 new file mode 100644 index 0000000..e8875eb --- /dev/null +++ b/BuildWithDocker.ps1 @@ -0,0 +1,6 @@ +$IMG=docker build . -q +if (-not (Test-Path -Path build/output)) { + New-Item -Type Directory build/output +} +$Output=resolve-path ./build/output +docker run --rm -v ${Output}:/output $IMG \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ce7084b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,57 @@ +# Original example (ContractOps) uses .NET base because it includes a .NET project +FROM mcr.microsoft.com/dotnet/runtime:7.0 AS base +# I don't think we need .NET. However, the base debian image doesn't have all the libraries +# pandoc wants, so we're using that for now +#FROM debian:12-slim + +# Or perhaps we should just use https://hub.docker.com/r/pandoc/latex + +# Install pandoc, MiKTeX, and rsvg-convert +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + dirmngr \ + gpg \ + gpg-agent \ + librsvg2-bin \ + wget \ + xz-utils \ + && apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys D6BC243565B2087BC3F897C9277A7293F59E4889 \ + && echo "deb http://miktex.org/download/debian bullseye universe" | tee /etc/apt/sources.list.d/miktex.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + miktex \ + && miktexsetup finish \ + && wget https://github.com/jgm/pandoc/releases/download/3.1.6.2/pandoc-3.1.6.2-1-amd64.deb \ + && dpkg -i ./pandoc-3.1.6.2-1-amd64.deb \ + && rm ./pandoc-3.1.6.2-1-amd64.deb \ + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /pandoc-lua-filters +RUN wget https://raw.githubusercontent.com/pandoc-ext/pagebreak/main/pagebreak.lua \ + && wget https://raw.githubusercontent.com/alpianon/pandoc-inline-headers/master/crossref-ordered-list \ + && mv crossref-ordered-list crossref-ordered-list.lua \ + && wget https://gitlab.com/howdyadoc/toolchain/-/raw/master/howdyadoc/lua-filters/inline-headers.lua + +WORKDIR /pandoc-filters +RUN wget https://github.com/lierdakil/pandoc-crossref/releases/download/v0.3.16.0f/pandoc-crossref-Linux.tar.xz \ + && tar -xJf pandoc-crossref-Linux.tar.xz \ + && rm pandoc-crossref-Linux.tar.xz + +# Allow MikTeX to install things it needs +RUN initexmf --set-config-value=[MPM]AutoInstall=yes + +WORKDIR /app + +RUN mkdir content +COPY ["build/pandoc.sh","pandoc.sh"] +COPY ["content","content"] +COPY ["templates","templates"] +COPY ["metadata.md","metadata.md"] +COPY ["intro-rx-dotnet-book-cover.png","intro-rx-dotnet-book-cover.png"] + +VOLUME ["/output"] +ENTRYPOINT ["/bin/sh", "/app/pandoc.sh"] \ No newline at end of file diff --git a/build/pandoc.sh b/build/pandoc.sh new file mode 100644 index 0000000..48481e1 --- /dev/null +++ b/build/pandoc.sh @@ -0,0 +1,81 @@ +#/bin/sh +# To be run inside the container +pandoc -o /output/intro-to-rx.epub metadata.md -s \ + ./content/01_WhyRx.md \ + ./content/02_KeyTypes.md \ + ./content/03_CreatingObservableSequences.md \ + ./content/04_Part2.md \ + ./content/05_Filtering.md \ + ./content/06_Transformation.md \ + ./content/07_Aggregation.md \ + ./content/08_Partitioning.md \ + ./content/09_CombiningSequences.md \ + ./content/10_Part3.md \ + ./content/11_SchedulingAndThreading.md \ + ./content/12_Timing.md \ + ./content/13_LeavingIObservable.md \ + ./content/14_ErrorHandlingOperators.md \ + ./content/15_PublishingOperators.md \ + ./content/16_TestingRx.md \ + ./content/A_IoStreams.md \ + ./content/B_Disposables.md \ + ./content/C_UsageGuidelines.md \ + ./content/D_AlgebraicUnderpinnings.md \ + --epub-cover-image intro-rx-dotnet-book-cover.png \ + --toc --toc-depth 4 \ + --resource-path "./content/" + +pandoc -o /output/intro-to-rx.docx -s \ + ./content/01_WhyRx.md \ + ./content/02_KeyTypes.md \ + ./content/03_CreatingObservableSequences.md \ + ./content/04_Part2.md \ + ./content/05_Filtering.md \ + ./content/06_Transformation.md \ + ./content/07_Aggregation.md \ + ./content/08_Partitioning.md \ + ./content/09_CombiningSequences.md \ + ./content/10_Part3.md \ + ./content/11_SchedulingAndThreading.md \ + ./content/12_Timing.md \ + ./content/13_LeavingIObservable.md \ + ./content/14_ErrorHandlingOperators.md \ + ./content/15_PublishingOperators.md \ + ./content/16_TestingRx.md \ + ./content/A_IoStreams.md \ + ./content/B_Disposables.md \ + ./content/C_UsageGuidelines.md \ + ./content/D_AlgebraicUnderpinnings.md \ + --resource-path "./content/" \ + --metadata-file=metadata.md \ + --metadata title="Introduction to Rx .NET" \ + --toc --toc-depth 4 + +pandoc -o /output/intro-to-rx.pdf metadata.md -s \ + ./content/01_WhyRx.md \ + ./content/02_KeyTypes.md \ + ./content/03_CreatingObservableSequences.md \ + ./content/04_Part2.md \ + ./content/05_Filtering.md \ + ./content/06_Transformation.md \ + ./content/07_Aggregation.md \ + ./content/08_Partitioning.md \ + ./content/09_CombiningSequences.md \ + ./content/10_Part3.md \ + ./content/11_SchedulingAndThreading.md \ + ./content/12_Timing.md \ + ./content/13_LeavingIObservable.md \ + ./content/14_ErrorHandlingOperators.md \ + ./content/15_PublishingOperators.md \ + ./content/16_TestingRx.md \ + ./content/A_IoStreams.md \ + ./content/B_Disposables.md \ + ./content/C_UsageGuidelines.md \ + ./content/D_AlgebraicUnderpinnings.md \ + --resource-path "./content/" \ + --template ./templates/eisvogel \ + --toc --toc-depth 4 + +if [ -f /root/.miktex/texmfs/data/miktex/log/pdflatex.log ]; then + cp /root/.miktex/texmfs/data/miktex/log/pdflatex.log /output +fi \ No newline at end of file diff --git a/content/00_Foreword.md b/content/00_Foreword.md index 3ba80fb..0164503 100644 --- a/content/00_Foreword.md +++ b/content/00_Foreword.md @@ -1,185 +1,102 @@ ---- -title : Foreword ---- - -#Introduction to Rx {#IntroductiontoRx .ignoreToc .kindleOnly} -##Lee Campbell {.ignoreToc .kindleOnly text-align=center} - ---- - -#Preface {#Preface .SectionHeader} - -Reactive programming is not a new concept. I remember studying my first Event Driven -module for Visual Basic 5 in 2000. Even then the technology (Visual Basic 5) was -already considered somewhat dated. Long before VB5 and the turn of the millennium, -we have seen languages supporting events. Over time languages like Smalltalk, Delphi -and the .NET languages have popularized reactive or event-driven programming paradigms. -This not to say that events are passé: current trends such as CEP (Complex -Event Processing), CQRS (Command Query Responsibility Segregation) and rich immersive -GUIs, all have events as a fundamental part of their makeup. - -The event driven paradigm allows for code to be invoked without the need for breaking -encapsulation or applying expensive polling techniques. This is commonly implemented -with the Observer pattern, events exposed directly in the language (e.g. C#) or -other forms of callback via delegate registration. The Reactive Extensions extend -the callback metaphor with LINQ to enable querying sequences of events and managing -concurrency. - -The Reactive Extensions are effectively a library of implementations of the `IObservable` -and `IObserver` interfaces for .NET, Silverlight and Windows Phone7. -The libraries are also available in JavaScript. As a dynamic language, JavaScript -had no need for the two interfaces so the JavaScript implementation could have been -written long before .NET 4 was released. This book will introduce Rx via C#. Users -of VB.NET, F# and other .NET languages hopefully will be able to extract the concepts -and translate them to their particular language. JavaScript users should be able -to gather the concepts from this book and apply them to their language. JavaScript -users may however find some features are not supported, and some concepts, such -as scheduling do not transcend platforms. - -As Rx is just a library, the team at Microsoft delivering Rx was able to isolate -themselves from the release schedule of the .NET Framework. This proved important -as the libraries saw fairly constant evolution since late 2009 through to their -official release in mid 2011. This evolution has been largely enabled by the openness -of the team and their ability to take onboard criticisms, suggestions and feature -requests from the brave community of pre-release users. - -While Rx is _just a library_, it is a significant and bold move forward for -the team at Microsoft and for any consumers of the library. Rx _will_ change -the way you design and build software for the following reasons: - - * The way that it tackles the Observer pattern is a divorce from .NET events toward a Java-style interface pattern but far more refined. - * The way it tackles concurrency is quite a shift how many .NET developers would have done it before. - * The abundance of (extension) methods in the library. - * The way in which it integrates with LINQ to leverage LINQ's composability & declarative style, makes Rx very usable and discoverable to those already familiar with LINQ and `IEnumerable`. - * The way it can help any .NET developer that works with event driven and/or asynchronous programs. Developers of Rich Clients, Web Clients and Services alike can all benefit from Rx. - * The future plans seem even grander, but that is a different book for some time in the future :-) - -This book aims to teach you: - - * about the new types that Rx will provide - * about the extension methods and how to use them - * how to manage subscriptions to "sequences" of data - * how to visualize "sequences" of data and sketch your solution before coding it - * how to deal with concurrency to your advantage and avoid common pitfalls - * how to compose, aggregate and transform streams - * how to test your Rx code - * some guidance on best practices when using Rx. - -The best way to learn Rx is to use it. Reading the theory from this book will only -help you be familiar with Rx, but will not really enable you to fully understand -Rx. You can download the latest version of Rx from the Microsoft Data Developer -site () -or if you use NuGet you can just download Rx via that. - - -My experience with Rx is straight from the trenches. I worked on a team of exceptional -developers on a project that was an early adopter of Rx (late 2009). The project -was a financial services application that started off life as a Silverlight project -then expanded into an integration project. We used Rx everywhere; client side in -Silverlight 3/4, and server side in .NET 3.5/4.0. We used Rx eagerly and sometimes -too eagerly. We were past leading edge, we were _bleeding_ edge. We were finding -bugs in the early releases and posting proposed fixes to the guys at Microsoft. -We were constantly updating to the latest version. It cost the project to be early -adopters, but in time the payment was worth it. Rx allowed us to massively simplify -an application that was inherently asynchronous, highly concurrent and targeted -low latencies. Similar workflows that I had written in previous projects were pages -of code long; now with Rx were several lines of LINQ. Trying to test asynchronous -code on clients (WPF/Win Forms/Silverlight) was a constant challenge, but Rx solved -that too. Today if you ask a question on the Rx Forums, you will most likely be -answered by someone from that team (or Dave Sexton). - - - -#Acknowledgements {#Acknowledgements} - -I would like to take this quick pause to recognize the people that made this book -possible. First is my poor wife for losing a husband to a dark room for several -months. Her understanding and tolerance is much appreciated. To my old team "Alpha -Alumni"; every developer on that team has helped me in some way to better myself -as a developer. Specific mention goes to -[James Miles](http://enumeratethis.com/), -[Matt Barrett](http://weareadaptive.com/blog/), -[John Marks](http://johnhmarks.wordpress.com/), -Duncan Mole, -Cathal Golden, -[Keith Woods](http://keith-woods.com), -[Ray Booysen](http://nondestructiveme.com) & -[Olivier DeHeurles](http://odeheurles.com/) -for all the deep dive sessions, emails, forum banter, BBM exchanges, lunch breaks -and pub sessions spent trying to get our heads around Rx. To -[Matt Davey](http://mdavey.wordpress.com) -for being brave enough to support us in using Rx back in 2009. -To the team at Microsoft that did the hard work and brought us Rx; -[Jeffrey Van Gogh](http://blogs.msdn.com/b/jeffva/), -[Wes Dyer](http://blogs.msdn.com/b/wesdyer/), -[Erik Meijer](http://www.applied-duality.com/) & -[Bart De Smet](http://blogs.bartdesmet.net/bart/). -Extra special mention to Bart, there is just something about the -[content](http://channel9.msdn.com/Tags/bart+de+smet) that Bart -[produces](http://www.infoq.com/author/Bart-De-Smet) that clicks with me. -Finally to the guys that helped edit the book; -[Joe Albahari](http://www.albahari.com/) -and Gregory Andrien. Joe is a veteran author of books such -as the C# in a nutshell, C# pocket reference and LINQ pocket reference, and managed -to find time to help out on this project while also releasing the latest versions -of these books. For Gregory and I, this was a first for both of us, as editor and -author respectively. Gregory committed many late nights to helping complete this -project. There is also some sweet irony in having a French person as the editor. -Even though English is not his native tongue, he clearly has a better grasp of it -than I. - - -It is my intention that from the experiences both good and bad, I can help speed -up your understanding of Rx and lower that barrier to entry to using Rx. This will -be a progressive step-by-step approach. It may seem slow in places, but the fundamentals -are so important to have a firm grasp on the powerful features. I hope you will -have the patience to join me all the way to the end. - -The content of this book was originally posted as a series of blog posts at -[http://LeeCampbell.blogspot.com](http://leecampbell.blogspot.co.uk/2010/08/reactive-extensions-for-net.html) -and has proved popular enough that I thought it warranted being reproduced as an e-book. -In the spirit of other books such as -Joe Albahari's [Threading in C#](http://www.albahari.com/threading/) -and Scott Chacon's [Pro Git](http://git-scm.com/book) books, and considering -the blog was free, I have made the first version of this book free. - -The version that this book has been written against is the .Net 4.0 targeted Rx -assemblies version 1.0.10621.0 (NuGet: Rx-Main v1.0.11226). - -So, fire up Visual Studio and let's get started. - ---- - -
-

Additional recommended reading

-
-
- - - -
-
- - -
- -
- - - -
-
- - - -
-
- +--- +title : Foreword +--- + +# Introduction to Rx +By Ian Griffiths and Lee Campbell + +--- + +Reactive programming is not a new concept. Any kind of user interface development +necessarily involves code that responds to events. Languages like [Smalltalk](https://en.wikipedia.org/wiki/Smalltalk), [Delphi](https://en.wikipedia.org/wiki/Delphi_(software)) +and the .NET languages have popularized reactive or event-driven programming paradigms. +Architectural patterns such as [CEP (Complex Event Processing)](https://en.wikipedia.org/wiki/Complex_event_processing), and +[CQRS (Command Query Responsibility Segregation)](https://en.wikipedia.org/wiki/Command_Query_Responsibility_Segregation) have events as a fundamental part +of their makeup. Reactive programming is a useful concept in any program that has +to deal with things happening. + +> Reactive programming is a useful concept in any program that has +to deal with things happening. + +The event driven paradigm allows for code to be invoked without the need for breaking +encapsulation or applying expensive polling techniques. There are many common ways to implement this, including +the [Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern), [events exposed directly in the language (e.g. C#)](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/events/) or +other forms of callback via delegate registration. The Reactive Extensions extend +the callback metaphor with LINQ to enable querying sequences of events and managing +concurrency. + +The .NET runtime libraries have included the `IObservable` and +`IObserver` interfaces that represent the core concept of reactive programming +for well over a decade now. The Reactive Extensions for .NET (Rx.NET) are effectively a library of implementations of these +interfaces. Rx.NET first appeared +back in 2010 but since then, Rx libraries have become available for other languages, and this way of programming has become +especially popular in JavaScript. + +This book will introduce Rx via C#. The concepts are universal, so users of other .NET languages +such as VB.NET and F#, will be able to extract the concepts and translate them to their particular +language. + +Rx.NET is just a library, originally created by Microsoft, but now an open source project +supported entirely through community effort. (Rx's current lead maintainer, [Ian Griffiths](https://endjin.com/who-we-are/our-people/ian-griffiths/), +is also the author of the latest revision of this book, and indeed the author of this very +sentence.) + +If you have never used Rx before, it _will_ change the way you design and build software. +It provides a well thought out abstraction for a fundamentally important idea in computing: sequences +of events. These are as important as lists or arrays, but before Rx there was little +direct support in libraries or languages, and what support there was tended to be rather +ad hoc, and built on weak theoretical underpinnings. Rx changes that. The extent to +which this Microsoft invention has been wholehearted adopted by some developer communities +traditionally not especially Microsoft-friendly is a testament to the quality of its +fundamental design. + +This book aims to teach you: + + * about the types that Rx defines + * about the extension methods Rx provides, and how to use them + * how to manage subscriptions to event sources + * how to visualize "sequences" of data and sketch your solution before coding it + * how to deal with concurrency to your advantage and avoid common pitfalls + * how to compose, aggregate and transform streams + * how to test your Rx code + * some common best practices when using Rx + +The best way to learn Rx is to use it. Reading the theory from this book will only +help you be familiar with Rx, but to fully understand it you should build things +with it. So we warmly encourage you to build based on the examples in this book. + + +# Acknowledgements {#Acknowledgements} + +Firstly, I (Ian Griffiths) should make it clear that this revised edition builds +on the excellent work of the original author Lee Campbell. I am grateful that he +generously allowed the Rx.NET project to make use of his content, enabling this +new edition to come into existence. + +I would also like to recognize the people that made this book +possible. +Thanks to everyone at [endjin](endjin.com) and especially [Howard van Rooijen](https://endjin.com/who-we-are/our-people/howard-van-rooijen/) and [Matthew Adams](https://endjin.com/who-we-are/our-people/matthew-adams/) +for funding not only the updates to this book, but also the ongoing development of Rx.NET itself. +(And thanks for employing me too!). Thanks also to [Felix Corke](https://www.linkedin.com/in/blackspike/) for his work on the design elements of the web edition of the book. Crucial to the first edition of the book, in addition to the author, [Lee Campbell](https://leecampbell.com/), were: +James Miles, Matt Barrett, +[John Marks](http://johnhmarks.wordpress.com/), +Duncan Mole, +Cathal Golden, +Keith Woods, +Ray Booysen, Olivier DeHeurles, +[Matt Davey](http://mdavey.wordpress.com), [Joe Albahari](http://www.albahari.com/) +and Gregory Andrien. +Extra special thanks to the team at Microsoft that did the hard work and brought us Rx; +[Jeffrey Van Gogh](https://www.linkedin.com/in/jeffrey-van-gogh-145673/), +[Wes Dyer](https://www.linkedin.com/in/wesdyer/), +[Erik Meijer](https://en.wikipedia.org/wiki/Erik_Meijer_%28computer_scientist%29) & +[Bart De Smet](https://www.linkedin.com/in/bartdesmet/). Thanks also to those who continued to work on Rx.NET after it ceased to be directly supported by Microsoft, and became a community-based open source project. Many people were involved, and it's not practical to list every contributor here, but I'd like to say a particular thank you to Bart De Smet (again, because he continued to work on the open source Rx long after moving onto other things internally at Microsoft) and also to Claire Novotny, Daniel Weber, David Karnok, Brendan Forster, Ani Betts and Chris Pulman. We are also grateful to Richard Lander and the .NET Foundation for helping us at [endjin](https://endjin.com) become the new stewards of the Rx.NET project, enabling it to continue to thrive. + + +If you are interested in more information about the origins of Rx, you might the "A Little History of Reaqtor" at the https://reaqtive.net/ site illuminating. + +The version that this book has been written against is `System.Reactive` version 6.0. The source for this book can be found at [https://github.com/dotnet/reactive/tree/main/Rx.NET/Documentation/IntroToRx](https://github.com/dotnet/reactive/tree/main/Rx.NET/Documentation/IntroToRx). If you find any bugs or other issues in this book, please post an issue at https://github.com/dotnet/reactive/. You might find the [Reactive X slack](reactivex.slack.com) to be a useful resource if you start using Rx.NET in earnest. + +So, fire up Visual Studio and let's get started. + +--- diff --git a/content/01_WhyRx.md b/content/01_WhyRx.md index a95b2d7..747fd30 100644 --- a/content/01_WhyRx.md +++ b/content/01_WhyRx.md @@ -4,146 +4,123 @@ title : Why Rx? # PART 1 - Getting started +Rx is a .NET library for processing event streams. Why might you want that? + ## Why Rx? -Users expect real time data. They want their tweets now. Their order confirmed now. They need prices accurate as of now. Their online games need to be responsive. As a developer, you demand fire-and-forget messaging. You don't want to be blocked waiting for a result. You want to have the result pushed to you when it is ready. Even better, when working with result sets, you want to receive individual results as they are ready. You do not want to wait for the entire set to be processed before you see the first row. The world has moved to push; users are waiting for us to catch up. Developers have tools to push data, this is easy. Developers need tools to react to push data. - -Welcome to [Reactive Extensions for .NET](https://github.com/dotnet/reactive) (Rx). This book is aimed at any .NET developer curious about the `IObservable` and `IObserver` interfaces in .NET. The Reactive Extensions libraries from Microsoft are the implementations of these interfaces that are quickly picking up traction with Server, Client and Web developers alike. Rx is a powerfully productive development tool. Rx enables developers to solve problems in an elegant, familiar and declarative style; often crucially with less code than was possible without Rx. By leveraging LINQ, Rx gets to boast the standard benefits of a LINQ implementation1. - -
-
Integrated
-
LINQ is integrated into the C# language.
-
Unitive
-
Using LINQ allows you to leverage your existing skills for querying data at rest (LINQ to SQL, LINQ to XML or LINQ to objects) to query data in motion. You could think of Rx as LINQ to events. LINQ allows you to transition from other paradigms into a common paradigm. For example you can transition a standard .NET event, an asynchronous method call, a `Task` or perhaps a 3rd party middleware API into a single common Rx paradigm. By leveraging our existing language of choice and using familiar operators like `Select`, `Where`, `GroupBy` etc, developers can rationalize and communicate designs or code in a common form.
-
Extensible
-
You can extend Rx with your own custom query operators (extension methods).
-
Declarative
-
LINQ allows your code to read as a declaration of _what_ your code does and leaves the _how_ to the implementation of the operators.
-
Composable
-
LINQ features, such as extension methods, lambda syntax and query comprehension syntax, provide a fluent API for developers to consume. Queries can be constructed with numerous operators. Queries can then be composed together to further produce composite queries.
-
Transformative
-
Queries can transform their data from one type to another. A query might translate a single value to another value, aggregated from a sequence of values to a single average value or expand a single data value into a sequence of values.
-
+Users want timely information. If you're waiting for a parcel to arrive, live reports of the delivery van's progress give you more freedom than a suspect 2 hour delivery window. Financial applications depend on continuous streams of up-to-date data. We expect our phones and computers to provide us with all sorts of important notifications. And some applications simply can't work without live information. Online collaboration tools and multiplayer games absolutely depend on the rapid distribution and delivery of data. + +In short, our systems need to react when interesting things happen. + +Live information streams are a basic, ubiquitous element of computer systems. Despite this, they are often a second class citizen in programming languages. Most languages support sequences of data through something like an array, which presumes that the data is sitting in memory ready for our code to read at its leisure. If your application deals with events, arrays might work for historical data, but they aren't a good way to represent events that occur while the application is running. And although streamed data is a pretty venerable concept in computing, it tends to be clunky, with the abstractions often surfaced through APIs that are poorly integrated with our programming language's type system. + +This is bad. Live data is critical to a wide range of applications. It should be as easy to work with as lists, dictionaries, and other collections. + +The [Reactive Extensions for .NET](https://github.com/dotnet/reactive) (Rx.NET or Rx for short, available as the [`System.Reactive` NuGet package](https://www.nuget.org/packages/System.Reactive/)) elevate live data sources to first class citizens. Rx does not require any special programming language support. It exploits .NET's type system to represent streams of data in a way that .NET languages such as C#, F#, and VB.NET can all work with as naturally as they use collection types. + +(A brief grammatical aside: although the phrase "Reactive Extensions" is plural, when we reduce it to just Rx.NET or Rx, we treat it as a singular noun. This is inconsistent, but saying "Rx are..." sounds plain weird.) + +For example, C# offers integrated query features that we might use to find all of the entries in a list that meet some criteria. If we have some `List trades` variable, we might write this: + +```cs +var bigTrades = + from trade in trades + where trade.Volume > 1_000_000; +``` + +With Rx, we could use this exact same code with live data. Instead of being a `List`, the `trades` variable could be an `IObservable`. `IObservable` is the fundamental abstraction in Rx. It is essentially a live version of `IEnumerable`. In this case, `bigTrades` would also be an `IObservable`, a live data source able to notify us of all trades whose `Volume` exceeds one million. Crucially, it can report each such trade immediately—this is what we mean by a 'live' data source. + +Rx is a powerfully productive development tool. It enables developers to work with live event streams using language features familiar to all .NET developers. It enables a declarative approach that often allows us to express complex behaviour more elegantly and with less code than would be possible without Rx. + +Rx builds on LINQ (Language Integrated Query). This enables us to use the query syntax shown above (or you can use the explicit function call approach that some .NET developers prefer). LINQ is widely used in .NET both for data access (e.g., in Entity Framework Core), but also for working with in-memory collections (with LINQ to Objects), meaning that experienced .NET developers will tend to feel at home with Rx. Crucially, LINQ is a highly composable design: you can connect operators together in any combination you like, expressing potentially complex processing in a straightforward way. This composability arises from the mathematical foundations of its design, but although you can learn about this aspect of LINQ if you want, it's not a prerequisite: developers who aren't interested in the mathematics behind it can just enjoy the fact that LINQ providers such as Rx provide a set of building blocks that can be plugged together in endless different ways, and it all just works. + +LINQ has proven track record of handling high very high volumes of data. Microsoft has used it extensively in the internal implementation of some of their systems, including services that support tens of millions of active users. ## When is Rx appropriate? -Rx offers a natural paradigm for dealing with sequences of events. A sequence can contain zero or more events. Rx proves to be most valuable when composing sequences of events. +Rx is designed for processing sequences of events, meaning that it suits some scenarios better than others. The next sections describe some of these scenarios, and also cases in which it is a less obvious match but still worth considering. Finally, we describe some cases in which it is possible to use Rx but where alternatives are likely to be better. + +### Good Fit with Rx -### Should use Rx +Rx is well suited to representing events that originate from outside of your code, and which your application needs to respond to, such as: -Managing events like these is what Rx was built for: +- Integration events like a broadcast from a message bus, or a push event from WebSockets API, or a message received via MQTT or other low latency middleware like [Azure Event Grid](https://azure.microsoft.com/en-gb/products/event-grid/), [Azure Event Hubs](https://azure.microsoft.com/en-gb/products/event-hubs/) and [Azure Service Bus](https://azure.microsoft.com/en-gb/products/service-bus/), or a non-vendor specific representation such as [cloudevents](https://cloudevents.io/) +- Telemetry from monitoring devices such as a flow sensor in a water utility's infrastructure, or the monitoring and diagnostic features in a broadband provider's networking equipment +- Location data from mobile systems such as [AIS](https://github.com/ais-dotnet/) messages from ships, or automotive telemetry +- Operating system events such as filesystem activity, or WMI events +- Road traffic information, such as notifications of accidents or changes in average speed +- Integration with a [Complex Event Processing (CEP)](https://en.wikipedia.org/wiki/Complex_event_processing) engine +- UI events such as mouse movement or button clicks -- UI events like mouse move, button click -- Domain events like property changed, collection updated, "Order Filled", "Registration accepted" etc. -- Infrastructure events like from file watcher, system and WMI events -- Integration events like a broadcast from a message bus or a push event from WebSockets API or other low latency middleware like [Azure Service Bus](https://azure.microsoft.com/en-gb/products/service-bus/) -- Integration with a CEP engine like [StreamInsight](http://www.microsoft.com/sqlserver/en/us/solutions-technologies/business-intelligence/complex-event-processing.aspx) or [StreamBase](http://www.streambase.com). +Rx is also good way to model domain events. These may occur as a result of some of the events just described, but after processing them to produce events that more directly represent application concepts. These might include: -Interestingly Microsoft's CEP product StreamInsight, which is part of the SQL Server family, also uses LINQ to build queries over streaming events of data. +- Property or state changes on domain objects such as "Order Status Updated", or "Registration Accepted" +- Changes to collections of domain objects, such as "New Registration Created" -Rx is also very well suited for introducing and managing concurrency for the purpose of _offloading_. -That is, performing a given set of work concurrently to free up the current thread. -A very popular use of this is maintaining a responsive UI. +Events might also represent insights derived from incoming events (or historical data being analyzed at a later date) such as: -You should consider using Rx if you have an existing `IEnumerable` that is attempting to model data in motion. -While `IEnumerable` _can_ model data in motion (by using lazy evaluation like `yield return`), it probably won't scale. Iterating over an `IEnumerable` will consume/block a thread. You should either favor the non-blocking nature of Rx via either `IObservable` or consider the `async` features in .NET 4.5. - -### Could use Rx +- A broadband customer might have become an unwitting participant in a DDoS attack +- Two ocean-going vessels have engaged in a pattern of movement often associated with illegal activity (e.g., travelling closely alongside one another for an extended period, long enough to transfer cargo or people, while far out at sea) +- [CNC](https://en.wikipedia.org/wiki/Numerical_control) [Milling Machine](https://en.wikipedia.org/wiki/Milling_(machining)) MFZH12's number 4 axis bearing is exhibiting signs of wear at a significantly higher rate than the nominal profile +- If the user wants to arrive on time at their meeting half way across town, the current traffic conditions suggest they should leave in the next 10 minutes -Rx can also be used for asynchronous calls. These are effectively sequences of one event. +These three sets of examples show how applications might progressively increase the value of the information as they process events. We start with raw events, which we then enhance to produce domain-specific events, and we then perform analysis to produce notifications that the application's users will really care about. Each stage of processing increases the value of the messages that emerge. Each stage will typically also reduce the volume of messages. If we presented the raw events in the first category directly to users, they might be overwhelmed by the volume of messages, making it impossible to spot the important events. But if we only present them with notifications when our processing has detected something important, this will enable them to work more efficiently and accurately, because we have dramatically improved the signal to noise ratio. -* Result of a `Task` or `Task` -* Result of an APM method call like `FileStream` BeginRead/EndRead +The [`System.Reactive` library](https://www.nuget.org/packages/System.Reactive) provides tools for building exactly this kind of value-adding process, in which we tame high-volume raw event sources to produce high-value, live, actionable insights. It provides a suite of operators that enable our code to express this kind of processing declaratively, as you'll see in subsequent chapters. -You may find the using TPL, Dataflow or `async` keyword proves to be a more natural way of composing asynchronous methods. While Rx can definitely help with these scenarios, if there are other more appropriate frameworks at your disposal you should consider them first. +Rx is also well suited for introducing and managing concurrency for the purpose of _offloading_. +That is, performing a given set of work concurrently, so that the thread that detected an event doesn't also have to be the thread that handles that event. +A very popular use of this is maintaining a responsive UI. (UI event handling has become _such_ a popular use of Rx—both in .NET. but also in [RxJS](https://rxjs.dev/), which originated as an offshoot of Rx.NET—that it would be easy to think that this is what it's for. But its success there should not blind us to its wider applicability.) -Rx can be used, but is less suited for, introducing and managing concurrency for the purposes of _scaling_ or performing _parallel_ computations. Other dedicated frameworks like TPL (Task Parallel Library) or [C++ AMP](https://learn.microsoft.com/en-us/cpp/parallel/amp/cpp-amp-cpp-accelerated-massive-parallelism) are more appropriate for performing parallel compute intensive work. +You should consider using Rx if you have an existing `IEnumerable` that is attempting to model live events. +While `IEnumerable` _can_ model data in motion (by using lazy evaluation like `yield return`), there's a problem. If the code consuming the collection has reached the point where it wants the next item (e.g., because a `foreach` loop has just completed an iteration) but no item is yet available, the `IEnumerable` implementation would have no choice but to block the calling thread in its `MoveNext` until such time as data is available, which can cause scalability problems in some applications. Even in cases where thread blocking is acceptable (or if you use the newer `IAsyncEnumerable`, which can take advantage of C#'s `await foreach` feature to avoid blocking a thread in these cases) `IEnumerable` and `IAsyncEnumerable` are misleading types for representing live information sources. These interfaces represent a 'pull' programming model: code asks for the next item in the sequence. Rx is a more natural choice for modelling information sources that naturally produce information on their own schedule. -See more on TPL, [TPL Dataflow](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library), [PLINQ](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/introduction-to-plinq) `async` and [C++ AMP](https://learn.microsoft.com/en-us/cpp/parallel/amp/cpp-amp-cpp-accelerated-massive-parallelism). +### Possible Fit with Rx -### Won't use Rx +Rx can be used to represent asynchronous operations. .NET's `Task` or `Task` effectively represent a single event, and `IObservable` can be thought if as a generalization of this to a sequence of events. (The relationship between, say, `Task` and `IObservable` is similar to the relationship between `int` and `IEnumerable`.) -Rx and specifically `IObservable` is not a replacement for `IEnumerable`. -I would not recommend trying to take something that is naturally pull based and force it to be push based. +This means that there are some scenarios that can be dealt with either using tasks and the `async` keyword or through Rx. If at any point in your processing you need to deal with multiple values as well as single ones, Rx can do both; tasks don't handle multiple items so well. You can have a `Task>`, which enables you to `await` for a collection, and that's fine if all the items in the collection can be collected in a single step. The limitation with this is that once the task has produced its `IEnumerable` result, your `await` has completed, and you're back to non-asynchronous iteration over that `IEnumerable`. If the data can't be fetched in a single step—perhaps the `IEnumerable` represents data from an API in which results are fetched in batches of 100 items at a time—its `MoveNext` will have to block your thread every time it needs to wait. -* Translating existing `IEnumerable` values to `IObservable` just so that the code base can be "more Rx" -* Message queues. - Queues like in MSMQ or a JMS implementation generally have transactionality and are by definition sequential. - I feel `IEnumerable` is a natural fit for here. +For the first 5 years of its existence, Rx was arguably the best way to represent collections that wouldn't necessarily have all the items available immediately. However, the introduction of [`IAsyncEnumerable`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerable-1) in .NET Core 3.0 and C# 8 provided a way to handle sequences while remaining in the world of `async`/`await` (and the [`Microsoft.Bcl.AsyncInterfaces` NuGet package](https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces/) makes this available on .NET Framework and .NET Standard 2.0). So the choice to use Rx to now tends to boil down to whether a 'pull' model (exemplified by `foreach` or `await foreach`) or a 'push' model (in which code supplies callbacks to be invoked by the event source when items become available) is a better fit for the concepts being modelled. -By choosing the best tool for the job your code should be easier to maintain, provide better performance and you will probably get better support. +Another related feature that was added .NET since Rx first appears is [channels](https://learn.microsoft.com/en-us/dotnet/core/extensions/channels). These allow a source to produce object and a consumer to process them, so there's an obvious superficial similarity to Rx. However, a distinguishing feature of Rx is its support for composition with an extensive set of operators, something with no direct equivalent in channels. Channels on the other hand provide more options for adapting to variations in production and consumption rates. + +Earlier, I mentioned _offloading_: using Rx to push work onto other threads. Although this technique can enable Rx to introduce and manage concurrency for the purposes of _scaling_ or performing _parallel_ computations, other dedicated frameworks like [TPL (Task Parallel Library) Dataflow](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library) or [PLINQ](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/introduction-to-plinq) are more appropriate for performing parallel compute intensive work. However, TPL Dataflow offers some integration with Rx through its [`AsObserver`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.dataflow.dataflowblock.asobserver) and [`AsObservable`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.dataflow.dataflowblock.asobservable) extension methods. So it is common to use Rx to integrate TPL Dataflow with the rest of your application. + +### Poor Fit with Rx + +Rx's `IObservable` is not a replacement for `IEnumerable` or `IAsyncEnumerable`. It would be a mistake to take something that is naturally pull based and force it to be push based. + +Also, there are some situations in which the simplicity of Rx's programming model can work against you. For example, some message queuing technologies such as MSMQ are by definition sequential, and thus might look like a good fit for Rx. However, they are often chosen for their transaction handling support. Rx does not have any direct way to surface transaction semantics, so in scenarios that require this you might be better off just working directly with the relevant technology's API. (That said, [Reaqtor](https://reaqtive.net/) adds durability and persistence to Rx, so you might be able to use that to integrate these kinds of queueing systems with Rx.) + +By choosing the best tool for the job your code should be easier to maintain, it will likely provide better performance and you will probably get better support. ## Rx in action -Adopting and learning Rx can be an iterative approach where you can slowly apply it to your infrastructure and domain. -In a short time you should be able to have the skills to produce code, or reduce existing code, to queries composed of simple operators. -For example this simple ViewModel is all I needed to code to integrate a search that is to be executed as a user types. - -```csharp -public class MemberSearchViewModel : INotifyPropertyChanged -{ - //Fields removed... - public MemberSearchViewModel(IMemberSearchModel memberSearchModel, - ISchedulerProvider schedulerProvider) - { - _memberSearchModel = memberSearchModel; - - //Run search when SearchText property changes - this.PropertyChanges(vm => vm.SearchText) - .Subscribe(Search); - } - - //Assume INotifyPropertyChanged implementations of properties... - public string SearchText { get; set; } - public bool IsSearching { get; set; } - public string Error { get; set; } - public ObservableCollection Results { get; } - - //Search on background thread and return result on dispatcher. - private void Search(string searchText) - { - using (_currentSearch) { } - IsSearching = true; - Results.Clear(); - Error = null; - - _currentSearch = _memberSearchModel.SearchMembers(searchText) - .Timeout(TimeSpan.FromSeconds(2)) - .SubscribeOn(_schedulerProvider.TaskPool) - .ObserveOn(_schedulerProvider.Dispatcher) - .Subscribe( - Results.Add, - ex => - { - IsSearching = false; - Error = ex.Message; - }, - () => { IsSearching = false; }); - } - - ... -} +You can get up and running with a simple Rx example very quickly. If you have the .NET SDK installed, you can run the following at a command line: + +```ps1 +mkdir TryRx +cd TryRx +dotnet new console +dotnet add package System.Reactive +``` + +Alternatively, if you have Visual Studio installed, create a new .NET Console project, and then use the NuGet package manager to add a reference to `System.Reactive`. + +This code creates an observable source (`ticks`) that produces an event once every second. The code also passes a handler to that source that writes a message to the console for each event: + +```cs +using System.Reactive.Linq; + +IObservable ticks = Observable.Timer( + dueTime: TimeSpan.Zero, + period: TimeSpan.FromSeconds(1)); + +ticks.Subscribe( + tick => Console.WriteLine($"Tick {tick}")); + +Console.ReadLine(); ``` -While this code snippet is fairly small it supports the following requirements: - -- Maintains a responsive UI -- Supports timeouts -- Knows when the search is complete -- Allows results to come back one at a time -- Handles errors -- Is unit testable, even with the concurrency concerns -- If a user changes the search, cancel current search and execute new search with new text. - -To produce this sample is almost a case of composing the operators that match the requirements into a single query. The query is small, maintainable, declarative and far less code than "rolling your own". There is the added benefit of reusing a well tested API. The less code _you_ have to write, the less code _you_ have to test, debug and maintain. - -Creating other queries like the following is simple: - -- calculating a moving average of a series of values e.g. *service level agreements* for average latencies or downtime -- combining event data from multiple sources e.g.: *search results* from Bing, Google and Yahoo, or *sensor data* from Accelerometer, Gyro, Magnetometer or temperatures -- grouping data e.g. *tweets* by topic or user, or *stock prices* by delta or liquidity* -- filtering data e.g. *online game servers* within a region, for a specific game or with a minimum number of participants. - -Push is here. Arming yourself with Rx is a powerful way to meet users' expectations of a push world. By understanding and composing the constituent parts of Rx you will be able to make short work of complexities of processing incoming events. - -Rx is set to become a day-to-day part of your coding experience. \ No newline at end of file +If this doesn't seem very exciting, it's because it's about as basic an example as it's possible to create, and at its heart, Rx has a very simple programming model. The power comes from composition—we can use the building blocks in the `System.Reactive` library to describe the processing that will takes us from raw, low-level events to high-value insights. But to do that, we must first understand [Rx's key types, `IObservable` and `IObserver`](02_KeyTypes.md). \ No newline at end of file diff --git a/content/02_KeyTypes.md b/content/02_KeyTypes.md index 6e48fe4..c59136f 100644 --- a/content/02_KeyTypes.md +++ b/content/02_KeyTypes.md @@ -4,83 +4,315 @@ title : Key Types # Key types -To use a framework you need to have a familiarty with the key features and their benefits. Without this you find yourself just pasting samples from forums and hacking code until it works, kind of. Then the next poor developer to maintain the code base ha to try to figure out what the intention of your code base was. Fate is only too kind when that maintenence developer is the same as the original developer. Rx is powerful, but also allows for a simplification of your code. To write good Reactive code you have to know the basics. +Rx is a powerful framework that can greatly simplify code that responds to events. But to write good Reactive code you have to understand the basic concepts. The fundamental building block of Rx is an interface called `IObservable`. Understanding this, and its counterpart `IObserver`, is the key to success with Rx. -There are two key types to understand when working with Rx, and a subset of auxiliary types that will help you to learn Rx more effectively. The `IObserver` and `IObservable` form the fundamental building blocks for Rx, while implementations of `ISubject` reduce the learning curve for developers new to Rx. +The preceding chapter showed this LINQ query expression as the first example: -Many are familiar with LINQ and its many popular forms like LINQ to Objects, LINQ to SQL & LINQ to XML. Each of these common implementations allows you query _data at rest_; Rx offers the ability to query _data in motion_. Essentially Rx is built upon the foundations of the [Observer](http://en.wikipedia.org/wiki/Observer_pattern) pattern. .NET already exposes some other ways to implement the Observer pattern such as multicast delegates or events (which are usually multicast delegates). Multicast delegates are not ideal however as they exhibit the following less desirable features; +```cs +var bigTrades = + from trade in trades + where trade.Volume > 1_000_000; +``` + +Most .NET developers will be familiar with [LINQ](https://learn.microsoft.com/en-us/dotnet/csharp/linq/) in at least one of its many popular forms such as [LINQ to Objects](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/linq-to-objects), or [Entity Framework Core queries](https://learn.microsoft.com/en-us/ef/core/querying/). Most LINQ implementations allow you to query _data at rest_. LINQ to Objects works on arrays or other collections, and LINQ queries in Entity Framework Core run against data in a database, but Rx is different: it offers the ability to define queries over live event streams—what you might call _data in motion_. + +If you don't like the query expression syntax, you can write exactly equivalent code by invoking LINQ operators directly: + +```cs +var bigTrades = trades.Where(trade => trade.Volume > 1_000_000); +``` + +Whichever style we use, this is the LINQ way of saying that we want `bigTrades` to have just those items in `trades` where the `Volume` property is greater than one million. + +We can't tell exactly what these examples do because we can't see the type of the `trades` or `bigTrades` variables. The meaning of this code is going to vary greatly depending on these types. If we were using LINQ to objects, these would both likely be `IEnumerable`. That would mean that these variables both referred to objects representing collections whose contents we could enumerate with a `foreach` loop. This would represent _data at rest_, data that our code could inspect directly. + +But let's make it clear what the code means by being explicit about the type: + +```cs +IObservable bigTrades = trades.Where(trade => trade.Volume > 1_000_000); +``` + +This removes the ambiguity. It is now clear that we're not dealing with data at rest. We're working with an `IObservable`. But what exactly is that? -- In C#, events have a curious interface. Some find the `+=` and ` -=` operators an unnatural way to register a callback -- Events are difficult to compose -- Events don't offer the ability to be easily queried over time -- Events are a common cause of accidental memory leaks -- Events do not have a standard pattern for signaling completion -- Events provide almost no help for concurrency or multithreaded applications. e.g. To raise an event on a separate thread requires you to do all of the plumbing - -Rx looks to solve these problems. Here I will introduce you to the building blocks and some basic types that make up Rx. -## IObservable +## `IObservable` -[`IObservable`](http://msdn.microsoft.com/en-us/library/dd990377.aspx "IObservable(Of T) interface - MSDN") is one of the two new core interfaces for working with Rx. It is a simple interface with just a [Subscribe](http://msdn.microsoft.com/en-us/library/dd782981(v=VS.100).aspx) method. Microsoft is so confident that this interface will be of use to you it has been included in the BCL as of version 4.0 of .NET. You should be able to think of anything that implements `IObservable` as a streaming sequence of `T` objects. -So if a method returned an `IObservable` I could think of it as a stream of Prices. +The [`IObservable` interface](https://learn.microsoft.com/en-us/dotnet/api/system.iobservable-1 "IObservable interface - Microsoft Learn") represents Rx's fundamental abstraction: a sequence of values of some type `T`. In a very abstract sense, this means it represents the same thing as [`IEnumerable`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.ienumerable-1). The difference is in how code consumes those values. Whereas `IEnumerable` enables code to retrieve values (typically with a `foreach` loop), an `IObservable` provides values when they become available. This distinction is sometimes characterised as _push_ vs _pull_. We can _pull_ values out of an `IEnumerable` by executing a `foreach` loop, but an `IObservable` will _push_ values into our code. -```csharp -//Defines a provider for push-based notification. +How can an `IObservable` push its values into our code? If we want these values, our code must _subscribe_ to the `IObservable`, which means providing it with some methods it can invoke. In fact, subscription is the only operation an `IObservable` directly supports. Here's the entire definition of the interface: + +```cs public interface IObservable { - //Notifies the provider that an observer is to receive notifications. IDisposable Subscribe(IObserver observer); } ``` -> .NET already has the concept of Streams with the type and sub types of `System.IO.Stream`. -The `System.IO.Stream` implementations are commonly used to stream data (generally bytes) to or from an I/O device like a file, network or block of memory. -> `System.IO.Stream` implementations can have both the ability to read and write, and sometimes the ability to seek (i.e. fast forward through a stream or move backwards). -> When I refer to an instance of `IObservable` as a stream, it does not exhibit the seek or write functionality that streams do. -This is a fundamental difference preventing Rx being built on top of the `System.IO.Stream` paradigm. -Rx does however have the concept of forward streaming (push), disposing (closing) and completing (eof). -Rx also extends the metaphor by introducing concurrency constructs, and query operations like transformation, merging, aggregating and expanding. -> These features are also not an appropriate fit for the existing `System.IO.Stream` types. Some others refer to instances of `IObservable` as Observable Collections, which I find hard to understand. While the observable part makes sense to me, I do not find them like collections at all. You generally cannot sort, insert or remove items from an `IObservable` instance like I would expect you can with a collection. Collections generally have some sort of backing store like an internal array. The values from an `IObservable` source are not usually pre-materialized as you would expect from a normal collection. There is also a type in WPF/Silverlight called an `ObservableCollection` that does exhibit collection-like behavior, and is very well suited to this description. -> In fact `IObservable` integrates very well with `ObservableCollection` instances. -So to save on any confusion we will refer to instances of `IObservable` as *sequences*. -While instances of `IEnumerable` are also sequences, we will adopt the convention that they are sequences of _data at rest_, and `IObservable` instances are sequences of _data in motion_. +You can see [the source for `IObservable` on GitHub](https://github.com/dotnet/runtime/blob/b4008aefaf8e3b262fbb764070ea1dd1abe7d97c/src/libraries/System.Private.CoreLib/src/System/IObservable.cs). Notice that it is part of the .NET runtime libraries, and not the `System.Reactive` NuGet package. `IObservable` represents such a fundamentally important abstraction that it is baked into .NET. (So you might be wondering what the `System.Reactive` NuGet package is for. The .NET runtime libraries define only the `IObservable` and `IObserver` interfaces, and not the LINQ implementation. The `System.Reactive` NuGet package gives us LINQ support, and also deals with threading.) -## IObserver +This interface's only method makes it clear what we can do with an `IObservable`: if we want to receive the events it has to offer, we can _subscribe_ to it. (We can also unsubscribe: the `Subscribe` method returns an `IDisposable`, and if we call `Dispose` on that it cancels our subscription.) The `Subscribe` method requires us to pass in an implementation of `IObserver`, which we will get to shortly. -[`IObserver`](http://msdn.microsoft.com/en-us/library/dd783449.aspx "IObserver(Of T) interface - MSDN") is the other one of the two core interfaces for working with Rx. It too has made it into the BCL as of .NET 4.0. Don't worry if you are not on .NET 4.0 yet as the Rx team have included these two interfaces in a separate assembly for .NET 3.5 and Silverlight users. `IObservable` is meant to be the "functional dual of `IEnumerable`". If you want to know what that last statement means, then enjoy the hours of videos on [Channel9](http://channel9.msdn.com/tags/Rx/) where they discuss the mathematical purity of the types. For everyone else it means that where an `IEnumerable` can effectively yield three things (the next value, an exception or the end of the sequence), so too can `IObservable` via `IObserver`'s three methods `OnNext(T)`, `OnError(Exception)` and `OnCompleted()`. +Observant readers will have noticed that an example in the preceding chapter looks like it shouldn't work. That code created an `IObservable` that produced events once per second, and then it subscribed to it with this code: -```csharp -//Provides a mechanism for receiving push-based notifications. +```cs +ticks.Subscribe( + tick => Console.WriteLine($"Tick {tick}")); +``` + +That's passing a delegate, and not the `IObserver` that `IObservable.Subscribe` requires. We'll get to `IObserver` shortly, but all that's happening here is that this example is using an extension method from the `System.Reactive` NuGet package: + +```cs +// From the System.Reactive library's ObservableExtensions class +public static IDisposable Subscribe(this IObservable source, Action onNext) +``` + +This is a helper method that wraps a delegate in an implementation of `IObserver` and then passes that to `IObservable.Subscribe`. The effect is that we can write just a simple method (instead of a complete implementation of `IObserver`) and the observable source will invoke our callback each time it wants to supply a value. It's more common to use this kind of helper than to implement Rx's interfaces ourselves. + +### Hot and Cold Sources + +Since an `IObservable` cannot supply us with values until we subscribe, the time at which we subscribe can be important. Imagine an `IObservable` describing trades occurring in some market. If the information it supplies is live, it's not going to tell you about any trades that occurred before you subscribed. In Rx, sources of this kind are described as being _hot_. + +Not all sources are _hot_. There's nothing stopping an `IObservable` always supplying the exact same sequence of events to any subscriber no matter when the call to `Subscribe` occurs. (Imagine an `IObservable` which, instead of reporting live information, generates notifications based on recorded historical trade data.) Sources where it doesn't matter at all when you subscribe are known as _cold_ sources. + +Here are some sources that might be represented as hot observables: + +* Measurements from a sensor +* Price ticks from a trading exchange +* An event source that distributes events immediately such as Azure Event Grid +* mouse movements +* timer events +* broadcasts like ESB channels or UDP network packets + +And some examples of some sources that might make good cold observables: + +* the contents of a collection (such as is returned by the [`ToObservable` extension method for `IEnumerable`](03_CreatingObservableSequences.md#from-ienumerablet)) +* a fixed range of values, such as [`Observable.Range`](03_CreatingObservableSequences.md#observablerange) produces +* events generated based on an algorithm, such as [`Observable.Generate`](03_CreatingObservableSequences.md#observablegenerate) produces +* a factory for an asynchronous operation, such as [`FromAsync`](03_CreatingObservableSequences.md#from-task) returns +* events produced by running conventional code such as a loop; you can create such sources with [`Observable.Create`](03_CreatingObservableSequences.md#observablecreate) +* a streaming event provides such as Azure Event Hub or Kafka (or any other streaming-style source which holds onto events from the past to be able to deliver events from a particular moment in the stream; so _not_ an event source in the Azure Event Grid style) + +Not all sources are strictly completely _hot_ or _cold_. For example, you could imagine a slight variation on a live `IObservable` where the source always reports the most recent trade to new subscribers. Subscribers can count on immediately receiving something, and will then be kept up to date as new information arrives. The fact that new subscribers will always receive (potentially quite old) information is a _cold_-like characteristic, but it's only that first event that is _cold_. It's still likely that a brand new subscriber will have missed lots of information that would have been available to earlier subscribers, making this source more _hot_ than _cold_. + +There's an interesting special case in which a source of events has been designed to enable applications to receive every single event in order, exactly once. Event streaming systems such as Kafka or Azure Event Hub have this characteristic—they retain events for a while, to ensure that consumers don't miss out even if they fall behind from time to time. The standard input (_stdin_) for a process also has this characteristic: if you run a command line tool and start typing input before it is ready to process it, the operating system will hold that input in a buffer, to ensure that nothing is lost. Windows does something similar for desktop applications: each application thread gets a message queue so that if you click or type when it's not able to respond, the input will eventually be processed. We might think of these sources as _cold_-then-_hot_. They're like _cold_ sources in that we won't miss anything just because it took us some time to start receiving events, but once we start retrieving the data, then we can't generally rewind back to the start. So once we're up and running they are more like _hot_ events. + +This kind of _cold_-then-_hot_ source can present a problem if we want to attach multiple subscribers. If the source starts providing events as soon as subscription occurs, then that's fine for the very first subscriber: it will receive any events that were backed up waiting for us to start. But if we wanted to attach multiple subscribers, we've got a problem: that first subscriber might receive all the notifications that were sitting waiting in some buffer before we manage to attach the second subscriber. The second subscriber will miss out. + +In these cases, we really want some way to rig up all our subscribers before kicking things off. We want subscription to be separate from the act of starting. By default, subscribing to a source implies that we want it to start, but Rx defines a specialised interface that can give us more control: [`IConnectableObservable`](https://github.com/dotnet/reactive/blob/f4f727cf413c5ea7a704cdd4cd9b4a3371105fa8/Rx.NET/Source/src/System.Reactive/Subjects/IConnectableObservable.cs). This derives from `IObservable`, and adds just a single method, `Connect`: + +```cs +public interface IConnectableObservable : IObservable +{ + IDisposable Connect(); +} +``` + +This is useful in these scenarios where there will be some process that fetches or generates events and we need to make sure we're prepared before that starts. Because an `IConnectableObservable` won't start until you call `Connect`, it provides you with a way to attach however many subscribers you need before events begin to flow. + +The 'temperature' of a source is not necessarily evident from its type. Even when the underlying source is an `IConnectableObservable`, that can often be hidden behind layers of code. So whether a source is hot, cold, or something in between, most of the time we just see an `IObservable`. +Since `IObservable` defines just one method, `Subscribe`, you might be wondering how we can do anything interesting with it. The power comes from the LINQ operators that the `System.Reactive` NuGet library supplies. + +### LINQ Operators and Composition + +So far I've shown only a very simple LINQ example, using the `Where` operator to filter events down to ones that meet certain criteria. To give you a flavour of how we can build more advanced functionality through composition, I'm going to introduce an example scenario. + +Suppose you want to write a program that watches some folder on a filesystem, and performs automatic processing any time something in that folder changes. For example, web developers often want to trigger automatic rebuilds of their client side code when they save changes in the editor so they can quickly see the effect of their changes. Filesystem changes often come in bursts. Text editors might perform a few distinct operations when saving a file. (Some save modifications to a new file, then perform a couple of renames once this is complete, because this avoids data loss if a power failure or system crash happens to occur at the moment you save the file.) So you typically won't want to take action as soon as you detect file activity. It would be better to give it a moment to see if any more activity occurs, and take action only after everything has settled down. + +So we should not react directly to filesystem activity. We want take action at those moments when everything goes quiet after a flurry of activity. Rx does not offer this functionality directly, but it's possible for us to create a custom operator by combing some of the built-in operators. The following code defines an Rx operator that detects and reports such things. If you're new to Rx (which seems likely if you're reading this) it probably won't be instantly obvious how this works. This is a significant step up in complexity from the examples I've shown so far because this came from a real application. But I'll walk through it step by step, so all will become clear. + +```cs +static class RxExt +{ + public static IObservable> Quiescent( + this IObservable src, + TimeSpan minimumInactivityPeriod, + IScheduler scheduler) + { + IObservable onoffs = + from _ in src + from delta in Observable.Return(1, scheduler).Concat(Observable.Return(-1, scheduler).Delay(minimumInactivityPeriod, scheduler)) + select delta; + IObservable outstanding = onoffs.Scan(0, (total, delta) => total + delta); + IObservable zeroCrossings = outstanding.Where(total => total == 0); + return src.Buffer(zeroCrossings); + } +} +``` + +The first thing to say about this is that we are effectively defining a custom LINQ-style operator: this is an extension method which, like all of the LINQ operators Rx supplies, takes an `IObservable` as its implicit argument, and produces another observable source as its result. The return type is slightly different: it's `IObservable>`. That's because once we return to a state of inactivity, we will want to process everything that just happened, so this operator will produce a list containing every value that the source reported in its most recent flurry of activity. + +When we want to show how an Rx operator behaves, we typically draw a 'marble' diagram. This is a diagram showing one or more `IObservable` event sources, with each one being illustrated by a horizontal line. Each event that a source produces is illustrated by a circle (or 'marble') on that line, with the horizontal position representing timing. Typically, the line has a vertical bar on its left indicating the instant at which the application subscribed to the source, unless it happens to produce events immediately, in which case it will start with a marble. If the line has an arrowhead on the right, that indicates that the observable's lifetime extends beyond the diagram. Here's a diagram showing how the `Quiescent` operator above response to a particular input: + +
+ An Rx marble diagram illustrating two observables. The first is labelled 'source', and it shows six events, labelled numerically. These fall into three groups: events 1 and 2 occur close together, and are followed by a gap. Then events 3, 4, and 5 are close together. And then after another gap event 6 occurs, not close to any. The second observable is labelled 'source.Quiescent(TimeSpan.FromSeconds(2), Scheduler.Default). It shows three events. The first is labelled '[1, 2], and its horizontal position shows that it occurs a little bit after the '2' event on the 'source' observable. The second event on the second observable is labelled '[3,4,5]' and occurs a bit after the '5' event on the 'source' observable. The third event from on the second observable is labelled '[6]', and occurs a bit after the '6' event on the 'source' observable. The image conveys the idea that each time the source produces some events and then stops, the second observable will produce an event shortly after the source stops, which will contain a list with all of the events from the source's most recent burst of activity. +
An Rx marble diagram illustrating two observables.
+
+ +This shows that the source (the top line) produced a couple of events (the values `1` and `2`, in this example), and then stopped for a bit. A little while after it stopped, the observable returned by the `Quiescent` operator (the lower line) produced a single event with a list containing both of those events (`[1,2]`). Then the source started up again, producing the values `3`, `4`, and `5` in fairly quick succession, and then going quiet for a bit. Again, once the quiet spell had gone on for long enough, the source returned by `Quiescent` produced a single event containing all of the source events from this second burst of activity (`[3,4,5]`). And then the final bit of source activity shown in this diagram consists of a single event, `6`, followed by more inactivity, and again, once the inactivity has gone on for long enough the `Quiescent` source produces a single event to report this. And since that last 'burst' of activity from the source contained only a single event, the list reported by this final output from the `Quiescent` observable is a list with a single value: `[6]`. + +So how does the code shown achieve this? The first thing to notice about the `Quiescent` method is that it's just using other Rx LINQ operators (the `Return`, `Scan`, `Where`, and `Buffer` operators are explicitly visible, and the query expression will be using the `SelectMany` operator, because that's what C# query expressions do when they contain two `from` clauses in a row) in a combination that produces the final `IObservable>` output. + +This is Rx's _compositional_ approach, and it is how we normally use Rx. We use a mixture of operators, combined (_composed_) in a way that produces the effect we want. + +But how does this particular combination produce the effect we want? There are a few ways we could get the behaviour that we're looking for from a `Quiescent` operator, but the basic idea of this particular implementation is that it keeps count of how many events have happened recently, and then produces a result every time that number drops back to zero. The `outstanding` variable refers to the `IObservable` that tracks the number of recent events, and this marble diagram shows what it produces in response to the same `source` events as were shown on the preceding diagram: + +
+ An Rx marble diagram illustrating two observables. The first is labelled 'source',
+         and it shows the same six events as the preceding figure, labelled numerically, but this
+         time also color-coded so that each event has a different color. As before, these events
+         fall into three groups: events 1 and 2 occur close together, and are followed by a gap.
+         Then events 3, 4, and 5 are close together. And then after another gap event 6 occurs,
+         not close to any. The second observable is labelled 'outstanding' and for each of the
+         events on the 'source' observable, it shows two events. Each such pair has the same
+         color as on the 'source' line; the coloring is just to make it easier to see
+         how events on this line are associated with events on the 'source' line. The first of
+         each pair appears directly below its corresponding event on the 'source' line, and
+         has a number that is always one higher than its immediate precedecessor; the very first
+         item shows a number of 1. The first item from the second pair is the next to appear on
+         this line, and therefore has a number of 2. But then the second item from the first
+         pair appears, and this lowers the number back to 1, and it's followed by the second
+         item from the second pair, which shows 0. Since the second batch of events on the
+         first line appear fairly close together, we see values of 1, 2, 1, 2, 1, and then 0
+         for these. The final event on the first line, labelled 6, has a corresponding pair
+         on the second line reporting values of 1 and then 0. The overall effect is that
+         each value on the second, 'outstanding' line tells us how many items have emerged
+         from the 'source' line in the last 2 seconds. +
How the Quiescent operator counts the number of outstanding events.
+
+ +I've colour coded the events this time so that I can show the relationship between `source` events and corresponding events produced by `outstanding`. Each time `source` produces an event, `outstanding` produces an event at the same time, in which the value is one higher than the preceding value produced by `outstanding`. But each such `source` event also causes `outstanding` to produce another event two seconds later. (It's two seconds because in these examples, I've presumed that the first argument to `Quiescent` is `TimeSpan.FromSeconds(2)`, as shown on the first marble diagram.) That second event always produces a value that is one lower than whatever the preceding value was. + +This means that each event to emerge from `outstanding` tells us how many events `source` produced within the last two seconds. This diagram shows that same information in a slightly different form: it shows the most recent value produced by `outstanding` as a graph. You can see the value goes up by one each time `source` produces a new value. And two seconds after each value produced by `source`, it drops back down by one. + +
+ An Rx marble diagram illustrating the 'source' observables, and the second
+         observable from the preceding diagram this time illustrated as a bar graph showing
+         the latest value. This makes it easier to see that the 'outstanding' value goes up
+         each time a new value emerges from 'source', and then goes down again two seconds
+         later, and that when values emerge close together this running total goes higher.
+         It also makes it clear that the value drops to zero between the 'bursts' of
+         activity. +
The number of outstanding events as a graph.
+
+ +In simple cases like the final event `6`, in which it's the only event that happens at around that time, the `outstanding` value goes up by one when the event happens, and drops down again two seconds later. Over on the left of the picture it's a little more complex: we get two events in fairly quick succession, so the `outstanding` value goes up to one and then up to two, before falling back down to one and then down to zero again. The middle section looks a little more messy—the count goes up by one when the `source` produces event `3`, and then up to two when event `4` comes in. It then drops down to one again once two seconds have passed since the `3` event, but then another event, `5`, comes in taking the total back up to two. Shortly after that it drops back to one again because it has now been two seconds since the `4` event happened. And then a bit later, two seconds after the `5` event it drops back to zero again. + +That middle section is the messiest, but it's also most representative of the kind of activity this operator is designed to deal with. Remember, the whole point here is that we're expecting to see flurries of activity, and if those represents filesystem activity, they will tend to be slightly chaotic in nature, because storage devices don't always have entirely predictable performance characteristics (especially if it's a magnetic storage device with moving parts, or remote storage in which variable networking delays might come into play). + +With this measure of recent activity in hand, we can spot the end of bursts of activity by watching for when `outstanding` drops back to zero, which is what the observable referred to by `zeroCrossing` in the code above does. (That's just using the `Where` operator to filter out everything except the events where `outstanding`'s current value returns to zero.) + +But how does `outstanding` itself work? The basic approach here is that every time `source` produces a value, we actually create a brand new `IObservable`, which produces exactly two values. It immediately produces the value 1, and then after the specified timespan (2 seconds in these examples) it produces the value -1. That's what's going in in this clause of the query expression: + +```cs +from delta in Observable + .Return(1, scheduler) + .Concat(Observable + .Return(-1, scheduler) + .Delay(minimumInactivityPeriod, scheduler)) +``` + +I said Rx is all about composition, and that's certainly the case here. We are using the very simple `Return` operator to create an `IObservable` that immediately produces just a single value and then terminates. This code calls that twice, once to produce the value `1` and again to produce the value `-1`. It uses the `Delay` operator so that instead of getting that `-1` value immediately, we get an observable that waits for the specified time period (2 seconds in these examples, but whatever `minimumInactivityPeriod` is in general) before producing the value. And then we use `Concat` to stitch those two together into a single `IObservable` that produces the value `1`, and then two seconds later produces the value `-1`. + +Although this produces a brand new `IObservable` for each `source` event, the `from` clause shown above is part of a query expression of the form `from ... from .. select`, which the C# compiler turns into a call to `SelectMany`, which has the effect of flattening those all back into a single observable, which is what the `onoffs` variable refers to. This marble diagram illustrates that: + +
+ Several Rx marble diagrams, starting with the 'source' observable from earlier figures,
+         followed by one labelled with the LINQ query expression in the preceding example,
+         which shows 6 separate marble diagrams, one for each of the elements produced by
+         'source'. Each consists of two events: one with value 1, positioned directly beneath
+         the corresponding event on 'source' to indicate that they happen simultaneously, and
+         then one with the value -1 two seconds later. Beneath this is a marble diagram labelled
+         'onoffs' which contains all the same events from the preceding 6 diagrams, but merged
+         into a single sequence. These are all colour coded ot make it easier to see how these
+         events correspond to the original events on 'source'. Finally, we have the 'outstanding'
+         marble diagram which is exactly the same as in the preceding figure. +
The number of outstanding events as a graph.
+
+ +This also shows the `outstanding` observable again, but we can now see where that comes from: it is just the running total of the values emitted by the `onoffs` observable. This running total observable is created with this code: + +```cs +IObservable outstanding = onoffs.Scan(0, (total, delta) => total + delta); +``` + +Rx's `Scan` operator works much like the standard LINQ [`Aggregate`](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/aggregation-operations) operator, in that it cumulatively applies an operation (addition, in this case) to every single item in a sequence. The different is that whereas `Aggregate` produces just the final result once it reaches the end of the sequence, `Scan` shows all of its working, producing the accumulated value so far after each input. So this means that `outstanding` will produce an event every time `onoffs` produces one, and that event's value will be the running total—the sum total of every value from `onoffs` so far. + +So that's how `outstanding` comes to tell us how many events `source` produced within the last two seconds (or whatever `minimumActivityPeriod` has been specified). + +The final piece of the puzzle is how we go from the `zeroCrossings` (which produces an event every time the source has gone quiescent) to the output `IObservable>`, which provides all of the events that happened in the most recent burst of activity. Here we're just using Rx's `Buffer` operator, which is designed for exactly this scenario: it slices its input into chunks, producing an event for each chunk, the value of which is an `IList` containing the items for the chunk. `Buffer` can slice things up a few ways, but in this case we're using the form that starts a new slice each time some `IObservable` produces an item. Specifically, we're telling `Buffer` to slice up the `source` by creating a new chunk every time `zeroCrossings` produces a new event. + +(One last detail, just in case you saw it and were wondering, is that this method requires an `IScheduler`. This is an Rx abstraction for dealing with timing and concurrency. We need it because we need to be able to generate events after a one second delay, and that sort of time-driven activity requires a scheduler.) + +We'll get into all of these operators and the workings of schedulers in more detail in later chapters. For now, the key point is that we typically use Rx by creating a combination of LINQ operators that process and combine `IObservable` sources to define the logic that we require. + +Notice that nothing in that example actually called the one and only method that `IObservable` defines (`Subscribe`). There will always be something somewhere that ultimately consumes the events, but most of the work of using Rx tends to entail declaratively defining the `IObservable`s we need. + +Now that you've seen an example of what Rx programming looks like, we can address some obvious questions about why Rx exists at all. + +### What was wrong with .NET Events? + +.NET has had built-in support for events from the very first version that shipped over two decades ago—events are part of .NET's type system. The C# language has intrinsic support for this in the form of the `event` keyword, along with specialized syntax for subscribing to events. So why, when Rx turned up some 10 years later, did it feel the need to invent its own representation for streams of events? What was wrong with the `event` keyword? + +The basic problem with .NET events is that they get special handling from the .NET type system. Ironically, this makes them less flexible than if there had been no built-in support for the idea of events. Without .NET events, we would have needed some sort of object-based representation of events, at which point you can do all the same things with events that you can do with any other objects: you could store them in fields, pass them as arguments to methods, define methods on them and so on. + +To be fair to .NET version 1, it wasn't really possible to define a good object-based representation of events without generics, and .NET didn't get those until version 2 (three and a half years after .NET 1.0 shipped). Different event sources need to be able to report different data, and .NET events provided a way to parameterize events by type. But once generics came along, it became possible to define types such as `IObservable`, and the main advantage that events offered went away. (The other benefit was some language support for implementing and subscribing to events, but in principle that's something that could have been done for Rx if Microsoft had chosen to. It's not a feature that required events to be fundamentally different from other features of the type system.) + +Consider the example we've just worked through. It was possible to define our own custom LINQ operator, `Quiescent`, because `IObservable` is just an interface like any other, meaning that we're free to write extension methods for it. You can't write an extension method for an event. + +Also, we are able to wrap or adapt `IObservable` sources. `Quiescent` took an `IObservable` as an input, and combined various Rx operators to produce another observable as an output. Its input was a source of events that could be subscribed to, and its output was also a source of events that could be subscribed to. You can't do this with .NET events—you can't write a method that accepts an event as an argument, or that returns an event. + +These limitations are sometimes described by saying that .NET events are not _first class citizens_. There are things you can do with values or references in .NET that you can't do with events. + +If we represent an event source as a plain old interface, then it _is_ a first class citizen: it can use all of the functionality we expect with other objects and values precisely because it's not something special. + +### What about Streams? + +I've described `IObservable` as representing a _stream_ of events. This raises an obvious question: .NET already has [`System.IO.Stream`](https://learn.microsoft.com/en-us/dotnet/api/system.io.stream), so why not just use that? + +The short answer is that streams are weird because they represent an ancient concept in computing dating back long before the first ever Windows operating system shipped, and as such they have quite a lot of historical baggage. This means that even a scenario as simple as "I have some data, and want to make that available immediately to all interested parties" is surprisingly complex to implement though the `Stream` type. + +Moreover, `Stream` doesn't provide any way to indicate what type of data will emerge—it only knows about bytes. Since .NET's type system supports generics, it is natural to want the types that represent event streams to indicate the event type through a type parameter. + +So even if you did use `Stream` as part of your implementation, you'd want to introduce some sort of wrapper abstraction. If `IObservable` didn't exist, you'd need to invent it. + +It's certainly possible to use IO streams in Rx, but they are not the right primary abstraction. + +(If you are unconvinced, see [Appendix A: What's Wrong with Classic IO Streams](A_IoStreams.md) for a far more detailed explanation of exactly why `Stream` is not well suited to this task.) + +Now that we've seen why `IObservable` needs to exist, we need to look at its counterpart, `IObserver`. + +## `IObserver` + +Earlier, I showed the definition of `IObservable`. As you saw, it has just one method, `Subscribe`. And this method takes just one argument, of type [`IObserver`](https://learn.microsoft.com/en-us/dotnet/api/system.iobserver-1). So if you want to observe the events that an `IObservable` has to offer, you must supply it with an `IObserver`. In the examples so far, we've just supplied a simple callback, and Rx has wrapped that in an implementation of `IObserver` for us, but even though this is very often the way we will receive notifications in practice, you still need to understand `IObserver` to use Rx effectively. It is not a complex interface: + +```cs public interface IObserver { - //Provides the observer with new data. void OnNext(T value); - //Notifies the observer that the provider has experienced an error condition. void OnError(Exception error); - //Notifies the observer that the provider has finished sending push-based notifications. void OnCompleted(); } ``` -Rx has an implicit contract that must be followed. An implementation of `IObserver` may have zero or more calls to `OnNext(T)` followed optionally by a call to either `OnError(Exception)` or `OnCompleted()`. This protocol ensures that if a sequence terminates, it is always terminated by an `OnError(Exception)`, *or* an `OnCompleted()`. This protocol does not however demand that an `OnNext(T)`, `OnError(Exception)` or `OnCompleted()` ever be called. This enables to concept of empty and infinite sequences. We will look into this more later. - -Interestingly, while you will be exposed to the `IObservable` interface frequently if you work with Rx, in general you will not need to be concerned with `IObserver`. This is due to Rx providing anonymous implementations via methods like `Subscribe`. - -### Implementing IObserver and IObservable +As with `IObservable`, you can find [the source for `IObserver`](https://github.com/dotnet/runtime/blob/7cf329b773fa5ed544a9377587018713751c73e3/src/libraries/System.Private.CoreLib/src/System/IObserver.cs) in the .NET runtime GitHub repository, because both of these interfaces are built into the runtime libraries. -It is quite easy to implement each interface. If we wanted to create an observer that printed values to the console it would be as easy as this. +If we wanted to create an observer that printed values to the console it would be as easy as this: ```csharp public class MyConsoleObserver : IObserver { public void OnNext(T value) { - Console.WriteLine("Received value {0}", value); + Console.WriteLine($"Received value {value}"); } public void OnError(Exception error) { - Console.WriteLine("Sequence faulted with {0}", error); + Console.WriteLine($"Sequence faulted with {error}"); } public void OnCompleted() @@ -90,313 +322,312 @@ public class MyConsoleObserver : IObserver } ``` -Implementing an observable sequence is a little bit harder. An overly simplified implementation that returned a sequence of numbers could look like this. +In the preceding chapter, I used a `Subscribe` extension method that accepted a delegate which it invoked each time the source produced an item. This method is defined by Rx's `ObservableExtensions` class, which also defines various other extension methods for `IObservable`. It includes overloads of `Subscribe` that enable me to write code that has the same effect as the preceding example, without needing to provide my own implementation of `IObserver`: ```csharp -public class MySequenceOfNumbers : IObservable -{ - public IDisposable Subscribe(IObserver observer) - { - observer.OnNext(1); - observer.OnNext(2); - observer.OnNext(3); - observer.OnCompleted(); - return Disposable.Empty; - } -} +source.Subscribe( + value => Console.WriteLine($"Received value {value}"), + error => Console.WriteLine($"Sequence faulted with {error}"), + () => Console.WriteLine("Sequence terminated") +); ``` -We can tie these two implementations together to get the following output +The overloads of `Subscribe` where we don't pass all three methods (e.g., my earlier example just supplied a single callback corresponding to `OnNext`) are equivalent to writing an `IObserver` implementation where one or more of the methods simply has an empty body. Whether we find it more convenient to write our own type that implements `IObserver`, or just supply callbacks for some or all of its `OnNext`, `OnError` and `OnCompleted` method, the basic behaviour is the same: an `IObservable` source reports each event with a call to `OnNext`, and tells us that the events have come to an end either by calling `OnError` or `OnCompleted`. -```csharp -var numbers = new MySequenceOfNumbers(); -var observer = new MyConsoleObserver(); -numbers.Subscribe(observer); -``` +If you're wondering whether the relationship between `IObservable` and `IObserver` is similar to the relationship between [`IEnumerable`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.ienumerable-1) and [`IEnumerator`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.ienumerator-1), then you're onto something. Both `IEnumerator` and `IObservable` represent _potential_ sequences. With both of these interfaces, they will only supply data if we ask them for it. To get values out of an `IEnumerable`, an `IEnumerator` needs to come into existence, and similarly, to get values out of an `IObservable` requires an `IObserver`. -Output: +The difference reflects the fundamental _pull vs push_ difference between `IEnumerable` and `IObservable`. Whereas with `IEnumerable` we ask the source to create an `IEnumerator` for us which we can then use to retrieve items (which is what a C# `foreach` loop does), with `IObservable`, the source does not _implement_ `IObserver`: it expects _us_ to supply an `IObserver` and it will then push its values into that observer. -``` -Received value 1 -Received value 2 -Received value 3 -Sequence terminated -``` +So why does `IObserver` have these three methods? Remember when I said that in an abstract sense, `IObserver` represents the same thing as `IEnumerable`? I meant it. It might be an abstract sense, but it is precise: `IObservable` and `IObserver` were designed to preserve the exact meaning of `IEnumerable` and `IEnumerator`, changing only the detailed mechanism of consumption. -The problem we have here is that this is not really reactive at all. This implementation is blocking, so we may as well use an `IEnumerable` implementation like a `List` or an array. - -This problem of implementing the interfaces should not concern us too much. You will find that when you use Rx, you do not have the need to actually implement these interfaces, Rx provides all of the implementations you need out of the box. Let's have a look at the simple ones. +To see what that means, think about what happens when you iterate over an `IEnumerable` (with, say, a `foreach` loop). With each iteration (and more precisely, on each call to the enumerator's [`MoveNext`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.ienumerator.movenext) method) there are three things that could happen: -## Subject - -I like to think of the `IObserver` and the `IObservable` as the 'reader' and 'writer' or, 'consumer' and 'publisher' interfaces. If you were to create your own implementation of `IObservable` you may find that while you want to publicly expose the IObservable characteristics you still need to be able to publish items to the subscribers, throw errors and notify when the sequence is complete. Why that sounds just like the methods defined in `IObserver`! While it may seem odd to have one type implementing both interfaces, it does make life easy. This is what [subjects](http://msdn.microsoft.com/en-us/library/hh242969(v=VS.103).aspx "Using Rx Subjects - MSDN") can do for you. [`Subject`](http://msdn.microsoft.com/en-us/library/hh229173(v=VS.103).aspx "Subject(Of T) - MSDN") is the most basic of the subjects. Effectively you can expose your `Subject` behind a method that returns `IObservable` but internally you can use the `OnNext`, `OnError` and `OnCompleted` methods to control the sequence. +* `MoveNext` could return `true` to indicate that a value is available in the enumerator's [`Current`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.ienumerator-1.current) property +* `MoveNext` could throw an exception +* `MoveNext` could return `false` to indicate that you've reached the end of the collection -In this very basic example, I create a subject, subscribe to that subject and then publish values to the sequence (by calling `subject.OnNext(T)`). +These three outcomes correspond precisely to the three methods defined by `IObserver`. We could describe these in slightly more abstract terms: -```csharp -static void Main(string[] args) +* Here's another item +* It has all gone wrong +* There are no more items + +That describes the three things that either can happen next when consuming either an `IEnumerable` or an `IObservable`. The only difference is the means by which consumers discover this. With an `IEnumerable` source, each call to `MoveNext` will tell us which of these three applies. And with an `IObservable` source, it will tell you one of these three things with a call to the corresponding member of your `IObserver` implementation. + +## The Fundamental Rules of Rx Sequences + +Notice that two of the three outcomes in the list above are terminal. If you're iterating through an `IEnumerable` with a `foreach` loop, and it throws an exception, the `foreach` loop will terminate. The C# compiler understands that if `MoveNext` throws, the `IEnumerator` is now done, so it disposes it and then allows the exception to propagate. Likewise, if you get to the end of a sequence, then you're done, and the compiler understands that too: the code it generates for a `foreach` loop detects when `MoveNext` returns false and when that happens it disposes the enumerator and then moves onto the code after the loop. + +These rules might seem so obvious that we might never even think about them when iterating over `IEnumerable` sequences. What might be less immediately obvious is that exactly the same rules apply for an `IObservable` sequence. If an observable source either tells an observer that the sequence has finished, or reports an error, then in either case, that is the last thing the source is allowed to do to the observer. + +That means these examples would be breaking the rules: + +```cs +public static void WrongOnError(IObserver obs) { - var subject = new Subject(); - WriteSequenceToConsole(subject); + obs.OnNext(1); + obs.OnError(new ArgumentException("This isn't an argument!")); + obs.OnNext(2); // Against the rules! We already reported failure, so iteration must stop +} - subject.OnNext("a"); - subject.OnNext("b"); - subject.OnNext("c"); - Console.ReadKey(); +public static void WrongOnCompleted(IObserver obs) +{ + obs.OnNext(1); + obs.OnCompleted(); + obs.OnNext(2); // Against the rules! We already said we were done, so iteration must stop } -// Takes an IObservable as its parameter. -// Subject implements this interface. -static void WriteSequenceToConsole(IObservable sequence) +public static void WrongOnErrorAndOnCompleted(IObserver obs) { - // The next two lines are equivalent. - // sequence.Subscribe(value=>Console.WriteLine(value)); - sequence.Subscribe(Console.WriteLine); + obs.OnNext(1); + obs.OnError(new ArgumentException("A connected series of statements was not supplied")); + + // This next call is against the rules because we reported an error, and you're not + // allowed to make any further calls after you did that. + obs.OnCompleted(); +} + +public static void WrongOnCompletedAndOnError(IObserver obs) +{ + obs.OnNext(1); + obs.OnCompleted(); + + // This next call is against the rule because already said we were done. + // When you terminate a sequence you have to pick between OnCompleted or OnError + obs.OnError(new ArgumentException("Definite proposition not established")); } ``` -Note that the `WriteSequenceToConsole` method takes an `IObservable` as it only wants access to the subscribe method. Hang on, doesn't the `Subscribe` method need an `IObserver` as an argument? Surely `Console.WriteLine` does not match that interface. Well it doesn't, but the Rx team supply me with an Extension Method to `IObservable` that just takes an [`Action`](http://msdn.microsoft.com/en-us/library/018hxwa8.aspx "Action(Of T) Delegate - MSDN"). The action will be executed every time an item is published. There are [other overloads to the Subscribe extension method](http://msdn.microsoft.com/en-us/library/system.observableextensions(v=VS.103).aspx "ObservableExtensions class - MSDN") that allows you to pass combinations of delegates to be invoked for `OnNext`, `OnCompleted` and `OnError`. This effectively means I don't need to implement `IObserver`. Cool. +These correspond in a pretty straightforward way to things we already know about `IEnumerable`: + +* `WrongOnError`: if an enumerator throws from `MoveNext`, it's done and you mustn't call `MoveNext` again, so you won't be getting any more items out of it +* `WrongOnCompleted`: if an enumerator returns `false` from `MoveNext`, it's done and you mustn't call `MoveNext` again, so you won't be getting any more items out of it +* `WrongOnErrorAndOnCompleted`: if an enumerator throws from `MoveNext`, that means its done, it's done and you mustn't call `MoveNext` again, meaning it won't have any opportunity to tell that it's done by returning `false` from `MoveNext` +* `WrongOnCompletedAndOnError`: if an enumerator returns `false` from `MoveNext`, it's done and you mustn't call `MoveNext` again, meaning it won't have any opportunity to also throw an exception -As you can see, `Subject` could be quite useful for getting started in Rx programming. `Subject` however, is a basic implementation. There are three siblings to `Subject` that offer subtly different implementations which can drastically change the way your program runs. +Because `IObservable` is push-based, the onus for obeying all of these rules fall on the observable source. With `IEnumerable`, which is pull-based, it's up to the code using the `IEnumerator` (e.g. a `foreach` loop) to obey these rules. But they are essentially the same rules. - +There's an additional rule for `IObserver`: if you call `OnNext` you must wait for it to return before making any more method calls into the same `IObserver`. That means this code breaks the rules: -## ReplaySubject +```cs +public static void EverythingEverywhereAllAtOnce(IEnumerable obs) +{ + Random r = new(); + for (int i = 0; i < 10000; ++i) + { + int v = r.Next(); + Task.Run(() => obs.OnNext(v)); // Against the rules! + }} +``` -[`ReplaySubject`](http://msdn.microsoft.com/en-us/library/hh211810(v=VS.103).aspx "ReplaySubject(Of T) - MSDN") provides the feature of caching values and then replaying them for any late subscriptions. Consider this example where we have moved our first publication to occur before our subscription +This calls `obs.OnNext` 10,000 times, but it executes these calls as individual tasks to be run on the thread pool. The thread pool is designed to be able to execute work in parallel, and that's a a problem here because nothing here ensures that one call to `OnNext` completes before the next begins. We've broken the rule that says we must wait for each call to `OnNext` to return before calling either `OnNext`, `OnError`, or `OnComplete` on the same observer. (Note: this assumes that the caller won't subscribe the same observer to multiple different sources. If you do that, you can't assume that all calls to its `OnNext` will obey the rules, because the different sources won't have any way of knowing they're talking to the same observer.) -```csharp -static void Main(string[] args) +This rule is the only form of back pressure built into Rx.NET: since the rules forbid calling `OnNext` if a previous call to `OnNext` is still in progress, this enables an `IObserver` to limit the rate at which items arrive. If you just don't return from `OnNext` until you're ready, the source is obliged to wait. However, there are some issues with this. Once [schedulers](11_SchedulingAndThreading.md) get involved, the underlying source might not be connected directly to the final observer. If you use something like [`ObserveOn`](11_SchedulingAndThreading.md#subscribeon-and-observeon) it's possible that the `IObserver` subscribed directly to the source just puts items on a queue and immediately returns, and those items will then be delivered to the real observer on a different thread. In these cases, the 'back pressure' caused by taking a long time to return from `OnNext` only propagates as far as the code pulling items off the queue. It may be possible to use certain Rx operators (such as [`Buffer`](08_Partitioning.md#buffer) or [`Sample`](12_Timing.md#sample)) to mitigate this, but there are no built-in mechanisms for cross-thread propagation of back pressure. Some Rx implementations on other platforms have attempted to provide integrated solutions for this; in the past when the Rx.NET development community has looked into this, some thought that these solutions were problematic, and there is no consensus on what a good solution looks like. So with Rx.NET, if you need to arrange for sources to slow down when you are struggling to keep up, you will need to introduce some mechanism of your own. (Even with Rx platforms that do offer built-in back pressure, they can't provide a general-purpose answer to the question: how do we make this source provide events more slowly? How (or even whether) you can do that will depend on the nature of the source. So some bespoke adaptation is likely to be necessary in any case.) + +This rule in which we must wait for `OnNext` to return is tricky and subtle. It's perhaps less obvious than the others, because there's no equivalent rule for `IEnumerable`—the opportunity to break this rule only arises when the source pushes data into the application. You might look at the example above and think "well who would do that?" However, multithreading is just an easy way to show that it is technically possible to break the rule. The harder cases are where single-threaded re-entrancy occurs. Take this code: + +```cs +public class GoUntilStopped { - var subject = new Subject(); + private readonly IObserver observer; + private bool running; - subject.OnNext("a"); - WriteSequenceToConsole(subject); + public GoUntilStopped(IObserver observer) + { + this.observer = observer; + } - subject.OnNext("b"); - subject.OnNext("c"); - Console.ReadKey(); + public void Go() + { + this.running = true; + for (int i = 0; this.running; ++i) + { + this.observer.OnNext(i); + } + } + + public void Stop() + { + this.running = false; + this.observer.OnCompleted(); + } } ``` -The result of this would be that 'b' and 'c' would be written to the console, but 'a' ignored. -If we were to make the minor change to make subject a `ReplaySubject` we would see all publications again. +This class takes an `IObserver` as a constructor argument. When you call its `Go` method, it repeatedly calls the observer's `OnNext` until something calls its `Stop` method. -```csharp -var subject = new ReplaySubject(); +Can you see the bug? -subject.OnNext("a"); -WriteSequenceToConsole(subject); +We can take a look at what happens by supplying an `IObserver` implementation: -subject.OnNext("b"); -subject.OnNext("c"); -``` +```cs +public class MyObserver : IObserver +{ + private GoUntilStopped? runner; -This can be very handy for eliminating race conditions. Be warned though, the default constructor of the `ReplaySubject` will create an instance that caches every value published to it. In many scenarios this could create unnecessary memory pressure on the application. `ReplaySubject` allows you to specify simple cache expiry settings that can alleviate this memory issue. One option is that you can specify the size of the buffer in the cache. In this example we create the `ReplaySubject` with a buffer size of 2, and so only get the last two values published prior to our subscription: + public void Run() + { + this.runner = new(this); + Console.WriteLine("Starting..."); + this.runner.Go(); + Console.WriteLine("Finished"); + } -```csharp -public void ReplaySubjectBufferExample() -{ - var bufferSize = 2; - var subject = new ReplaySubject(bufferSize); - - subject.OnNext("a"); - subject.OnNext("b"); - subject.OnNext("c"); - subject.Subscribe(Console.WriteLine); - subject.OnNext("d"); + public void OnCompleted() + { + Console.WriteLine("OnCompleted"); + } + + public void OnError(Exception error) { } + + public void OnNext(int value) + { + Console.WriteLine($"OnNext {value}"); + if (value > 3) + { + Console.WriteLine($"OnNext calling Stop"); + this.runner?.Stop(); + } + Console.WriteLine($"OnNext returning"); + } } ``` -Here the output would show that the value 'a' had been dropped from the cache, but values 'b' and 'c' were still valid. The value 'd' was published after we subscribed so it is also written to the console. +Notice that the `OnNext` method looks at its input, and if it's greater than 3, it tells the `GoUntilStopped` object to stop. + +Let's look at the output: ``` -Output: -b -c -d +Starting... +OnNext 0 +OnNext returning +OnNext 1 +OnNext returning +OnNext 2 +OnNext returning +OnNext 3 +OnNext returning +OnNext 4 +OnNext calling Stop +OnCompleted +OnNext returning +Finished ``` -Another option for preventing the endless caching of values by the `ReplaySubject`, is to provide a window for the cache. In this example, instead of creating a `ReplaySubject` with a buffer size, we specify a window of time that the cached values are valid for. +The problem is right near the end. Specifically, these two lines: -```csharp -public void ReplaySubjectWindowExample() -{ - var window = TimeSpan.FromMilliseconds(150); - var subject = new ReplaySubject(window); - - subject.OnNext("w"); - Thread.Sleep(TimeSpan.FromMilliseconds(100)); - subject.OnNext("x"); - Thread.Sleep(TimeSpan.FromMilliseconds(100)); - subject.OnNext("y"); - subject.Subscribe(Console.WriteLine); - subject.OnNext("z"); -} +``` +OnCompleted +OnNext returning ``` -In the above example the window was specified as 150 milliseconds. Values are published 100 milliseconds apart. Once we have subscribed to the subject, the first value is 200ms old and as such has expired and been removed from the cache. +This tells us that the call to our observer's `OnCompleted` happened before a call in progress to `OnNext` returned. It didn't take multiple threads to make this occur. It happened because the code in `OnNext` decides whether it wants to keep receiving events, and when it wants to stop, it immediately calls the `GoUntilStopped` object's `Stop` method. There's nothing wrong with that. Observers are allowed to make outbound calls to other objects inside `OnNext`, and it's actually quite common for an observer to inspect an incoming event and decide that it wants to stop. + +The problem is in the `GoUntilStopped.Stop` method. This calls `OnCompleted` but it makes no attempt to determine whether a call to `OnNext` is in progress. + +This can be a surprisingly tricky problem to solve. Suppose `GoUntilStopped` _did_ detect that there was a call in progress to `OnNext`. What then? In the multithreaded case, we could have solved this by using `lock` or some other synchronization primitive to ensure that calls into the observer happened one at at time, but that won't work here: the call to `Stop` has happened on _the same thread_ that called `OnNext`. The call stack will look something like this at the moment where `Stop` has been called and it wants to call `OnCompleted`: ``` -Output: -x -y -z +`GoUntilStopped.Go` + `MyObserver.OnNext` + `GoUntilStopped.Stop` ``` + + Our `GoUntilStopped.Stop` method needs to wait for `OnNext` to return before calling `OnCompleted`. But notice that the `OnNext` method can't return until our `Stop` method returns. We've managed to create a deadlock with single-threaded code! -## BehaviorSubject +In this case it's not all that hard to fix: we could modify `Stop` so it just sets the `running` field to `false`, and then move the call to `OnComplete` into the `Go` method, after the `for` loop. But more generally this can be a hard problem to fix, and it's one of the reasons for using the `System.Reactive` library instead of just attempting to implement `IObservable` and `IObserver` directly. Rx has general purpose mechanisms for solving exactly this kind of problem. (We'll see these when we look at [Scheduling](11_SchedulingAndThreading.md).) Moreover, all of the implementations Rx provides take advantage of these mechanisms for you. -[`BehaviorSubject`](http://msdn.microsoft.com/en-us/library/hh211949(v=VS.103).aspx "BehaviorSubject(Of T) - MSDN") is similar to `ReplaySubject` except it only remembers the last publication. `BehaviorSubject` also requires you to provide it a default value of `T`. This means that all subscribers will receive a value immediately (unless it is already completed). +If you're using Rx by composing its built-in operators in a declarative way, you never have to think about these rules. You get to depend on these rules in your callbacks that receive the events, and it's mostly Rx's problem to keep to the rules. So the main effect of these rules is that it makes life simpler for code that consumes events. -In this example the value 'a' is written to the console: +These rules are sometimes expressed as a _grammar_. For example, consider this regular expression: -```csharp -public void BehaviorSubjectExample() -{ - //Need to provide a default value. - var subject = new BehaviorSubject("a"); - subject.Subscribe(Console.WriteLine); -} +``` +(OnNext)*(OnError|OnComplete) ``` -In this example the value 'b' is written to the console, but not 'a'. +This formally captures the basic idea: there can be any number of calls to `OnNext` (maybe even zero calls), that occur in sequence, followed by either an `OnError` or an `OnComplete`, but not both, and there must be nothing after either of these. + +One last point: sequences may be infinite. This is true for `IEnumerable`. It's perfectly possible for an enumerator to return `true` every time `MoveNext` is returned, in which case a `foreach` loop iterating over it will never reach the end. It might choose to stop (with a `break` or `return`), or some exception that did not originate from the enumerator might cause the loop to terminate, but it's absolutely acceptable for an `IEnumerable` to produce items for as long as you keep asking for them. The same is true of a `IObservable`. If you subscribe to an observable source, and by the time your program exits you've not received a call to either `OnComplete` or `OnError`, that's not a bug. + +So you might argue that this is a slightly better way to describe the rules formally: -```csharp -public void BehaviorSubjectExample2() -{ - var subject = new BehaviorSubject("a"); - subject.OnNext("b"); - subject.Subscribe(Console.WriteLine); -} +``` +(OnNext)*(OnError|OnComplete)? ``` -In this example the values 'b', 'c' & 'd' are all written to the console, but again not 'a' +More subtly, observable sources are allowed to do nothing at all. In fact there's a built-in implementation to save developers from the effort of writing a source that does nothing: if you call `Observable.Never()` it will return an `IObservable`, and if you subscribe to that, it will never call any methods on your observer. This might not look immediately useful—it is logically equivalent to an `IEnumerable` in which the enumerator's `MoveNext` method never returns, which might not be usefully distinguishable from crashing. It's slightly different with Rx, because when we model this "no items emerge ever" behaviour, we don't need to block a thread forever to do it. We can just decide never to call any methods on the observer. This may seem daft, but as you've seen with the `Quiescent` example, sometimes we create observable sources not because we want the actual items that emerge from it, but because we're interested in the instants when interesting things happen. It can sometimes be useful to be able to model "nothing interesting ever happens" cases. For example, if you have written some code to detect unexpected inactivity (e.g., a sensor that stops producing values), and wanted to test that code, your test could use a `Never` source instead of a real one, to simulate a broken sensor. -```csharp -public void BehaviorSubjectExample3() -{ - var subject = new BehaviorSubject("a"); +We're not quite done with the Rx's rules, but the last one applies only when we choose to unsubscribe from a source before it comes to a natural end. - subject.OnNext("b"); - subject.Subscribe(Console.WriteLine); - subject.OnNext("c"); - subject.OnNext("d"); -} -``` +## Subscription Lifetime -Finally in this example, no values will be published as the sequence has completed. Nothing is written to the console. +There's one more aspect of the relationship between observers and observables to understand: the lifetime of a subscription. -```csharp -public void BehaviorSubjectCompletedExample() -{ - var subject = new BehaviorSubject("a"); - subject.OnNext("b"); - subject.OnNext("c"); - subject.OnCompleted(); - subject.Subscribe(Console.WriteLine); -} -``` +You already know from the rules of `IObserver` that a call to either `OnComplete` or `OnError` denotes the end of a sequence. We passed an `IObserver` to `IObservable.Subscribe`, and now the subscription is over. But what if we want to stop the subscription earlier? -That note that there is a difference between a `ReplaySubject` with a buffer size of one (commonly called a 'replay one subject') and a `BehaviorSubject`. A `BehaviorSubject` requires an initial value. With the assumption that neither subjects have completed, then you can be sure that the `BehaviorSubject` will have a value. You cannot be certain with the `ReplaySubject` however. With this in mind, it is unusual to ever complete a `BehaviorSubject`. Another difference is that a replay-one-subject will still cache its value once it has been completed. So subscribing to a completed `BehaviorSubject` we can be sure to not receive any values, but with a `ReplaySubject` it is possible. +I mentioned earlier that the `Subscribe` method returns an `IDisposable`, which enables us to cancel our subscription. Perhaps we only subscribed to a source because our application opened some window showing the status of some process, and we wanted to update the window to reflect that's process's progress. If the user closes that window, we no longer have any use for the notifications. And although we could just ignore all further notifications, that could be a problem if the thing we're monitoring never reaches a natural end. Our observer would continue to receive notifications for the lifetime of the application. This is a waste of CPU power (and thus power consumption, with corresponding implications for battery life and environmental impact) and it can also prevent the garbage collector from reclaiming memory that should have become free. -`BehaviorSubject`s are often associated with class [properties](http://msdn.microsoft.com/en-us/library/65zdfbdt(v=vs.71).aspx). -As they always have a value and can provide change notifications, they could be candidates for backing fields to properties. +So we are free to indicate that we no longer wish to receive notifications by calling `Dispose` on the object returned by `Subscribe`. There are, however, a few non-obvious details. -## AsyncSubject +### Disposal of Subscriptions is Optional -[`AsyncSubject`](http://msdn.microsoft.com/en-us/library/hh229363(v=VS.103).aspx "AsyncSubject(Of T) - MSDN") is similar to the Replay and Behavior subjects in the way that it caches values, however it will only store the last value, and only publish it when the sequence is completed. The general usage of the `AsyncSubject` is to only ever publish one value then immediately complete. This means that is becomes quite comparable to `Task`. +You are not required to call `Dispose` on the object returned by `Subscribe`. Obviously if you want to remain subscribed to events for the lifetime of your process, this makes sense: you never stop using the object, so of course you don't dispose it. But what might be less obvious is that if you subscribe to an `IObservable` that does come to an end, it automatically tidies up after itself. -In this example no values will be published as the sequence never completes. -No values will be written to the console. +`IObservable` implementations are not allowed to assume that you will definitely call `Dispose`, so they are required to perform any necessary cleanup if they stop by calling the observer's `OnCompleted` or `OnError`. This is unusual. In most cases where a .NET API returns a brand new object created on your behalf that implements `IDisposable`, it's an error not to dispose it. But `IDisposable` objects representing Rx subscriptions are an exception to this rule. You only need to dispose them if you want them to stop earlier than they otherwise would. -```csharp -static void Main(string[] args) -{ - var subject = new AsyncSubject(); - subject.OnNext("a"); - WriteSequenceToConsole(subject); - subject.OnNext("b"); - subject.OnNext("c"); - Console.ReadKey(); -} -``` +### Cancelling Subscriptions may be Slow or Even Ineffectual -In this example we invoke the `OnCompleted` method so the last value 'c' is written to the console: +`Dispose` won't necessarily take effect instantly. Obviously it will take some non-zero amount of time in between your code calling into `Dispose`, and the `Dispose` implementation reaching the point where it actually does something. Less obviously, some observable sources may need to do non-trivial work to shut things down. -```csharp -static void Main(string[] args) -{ - var subject = new AsyncSubject(); - - subject.OnNext("a"); - WriteSequenceToConsole(subject); - subject.OnNext("b"); - subject.OnNext("c"); - subject.OnCompleted(); - Console.ReadKey(); -} -``` +A source might create a thread to be able to monitor for and report whatever events it represents. (That would happen with the filesystem source shown above when running on Linux on .NET 8, because the `FileSystemWatcher` class itself creates its own thread on Linux.) It might take a while for the thread to detect that it is supposed to shut down. -## Implicit contracts +It is fairly common practice for an `IObservable` to represent some underlying work. For example, Rx can take any factory method that returns a `Task` and wrap it as an `IObservable`. It will invoke the factory once for each call to `Subscribe`, so if there are multiple subscribers to a single `IObservable` of this kind, each one effectively gets its own `Task`. This wrapper is able to supply the factory with a `CancellationToken`, and if an observer unsubscribes by calling `Dispose` before the task naturally runs to completion, it will put that `CancellationToken` into a cancelled state. This might have the effect of bringing the task to a halt, but that will work only if the task happens to be monitoring the `CancellationToken`. Even if it is, it might take some time to bring things to a complete halt. Crucially, the `Dispose` call doesn't wait for that to happen. It will attempt to initiate cancellation but it may return before cancellation is complete. -There are implicit contacts that need to be upheld when working with Rx as mentioned above. The key one is that once a sequence is completed, no more activity can happen on that sequence. A sequence can be completed in one of two ways, either by `OnCompleted()` or by `OnError(Exception)`. +### The Rules of Rx Sequences when Unsubscribing -The four subjects described in this chapter all cater for this implicit contract by ignoring any attempts to publish values, errors or completions once the sequence has already terminated. +The fundamental rules of Rx sequences described earlier only considered sources that decided when (or whether) to come to a halt. What if a subscriber unsubscribes early? There is only one rule: -Here we see an attempt to publish the value 'c' on a completed sequence. Only values 'a' and 'b' are written to the console. +Once the call to `Dispose` has returned, the source will make no further calls to the relevant observer. If you call `Dispose` on the object returned by `Subscribe`, then once that call returns you can be certain that the observer you passed in will receive no further calls to any of its three methods (`OnNext`, `OnError`, or `OnComplete`). -```csharp -public void SubjectInvalidUsageExample() -{ - var subject = new Subject(); +That might seem clear enough, but it leaves a grey area: what happens when you've called `Dispose` but it hasn't returned yet? The rules permit sources to continue to emit events in this case. In fact they couldn't very well require otherwise: it will invariably take some non-zero length of time for the `Dispose` implementation to make enough progress to have any effect, so in a multi-threaded world it it's always going to be possible that an event gets delivered in between the call to `Dispose` starting, and the call having any effect. The only situation in which you could depend on no further events emerging would be if your call to `Dispose` happened inside the `OnNext` handler. In this case the source will already have noted a call to `OnNext` is in progress so further calls were already blocked before the call to `Dispose` started. - subject.Subscribe(Console.WriteLine); +But assuming that your observer wasn't already in the middle of an `OnNext` call, any of the following would be legal: - subject.OnNext("a"); - subject.OnNext("b"); - subject.OnCompleted(); - subject.OnNext("c"); -} -``` +* stopping calls to `IObserver` almost immediately after `Dispose` begins, even when it takes a relatively long time to bring any relevant underlying processes to a halt, in which case your observer will never receive an `OnCompleted` or `OnError` +* producing notifications that reflect the process of shutting down (including calling `OnError` if an error occurs while trying to bring things to a neat halt, or `OnCompleted` if it halted without problems) +* producing a few more notifications for some time after the call to `Dispose` begins, but cutting them off at some arbitrary point, potentially losing track even of important things like errors that occurred while trying to bring things to a halt -## ISubject interfaces +As it happens, Rx has a preference for the first option. If you're using an `IObservable` implemented by the `System.Reactive` library (e.g., one returned by a LINQ operator) it is highly likely to have this characteristic. This is partly to avoid tricky situations in which observers try to do things to their sources inside their notification callbacks. Re-entrancy tends to be awkward to deal with, and Rx avoids ever having to deal with this particular form of re-entrancy by ensuring that it has already stopped delivering notifications to the observer before it begins the work of shutting down a subscription. -While each of the four subjects described in this chapter implement the `IObservable` and `IObserver` interfaces, they do so via another set of interfaces: +This sometimes catches people out. If you need to be able to cancel some process that you are observing but you need to be able to observe everything it does up until the point that it stops, then you can't use unsubscription as the shutdown mechanism. As soon as you've called `Dispose`, the `IObservable` that returned that `IDisposable` is no longer under any obligation to tell you anything. This can be frustrating, because the `IDisposable` returned by `Subscribe` can sometimes seem like such a natural and easy way to shut something down. But basic truth is this: once you've initiated unsubscription, you can't rely on getting any further notifications associated with that subscription. You _might_ receive some—the source is allowed to carry on supplying items until the call to `Dispose` returns. But you can't rely on it—the source is also allowed to silence itself immediately, and that's what most Rx-implemented sources will do. -```csharp -//Represents an object that is both an observable sequence as well as an observer. -public interface ISubject - : IObserver, IObservable -{ -} -``` +One subtle consequence of this is that if an observable source reports an error after a subscriber has unsubscribed, that error might be lost. A source might call `OnError` on its observer, but if that's a wrapper provided by Rx relating to a subscription that has already been disposed, it just ignores the exception. So it's best to think of early unsubscription as inherently messy, a bit like aborting a thread: it can be done but information can be lost, and there are race conditions that will disrupt normal exception handling. -As all the subjects mentioned here have the same type for both `TSource` and `TResult`, they implement this interface which is the superset of all the previous interfaces: +In short, if you unsubscribe, then a source is not obliged to tell you when things stop, and in most cases it definitely won't tell you. -```csharp -//Represents an object that is both an observable sequence as well as an observer. -public interface ISubject : ISubject, IObserver, IObservable -{ -} +### Subscription Lifetime and Composition + +We typically combine multiple LINQ operators to express our processing requirements in Rx. What does this mean for subscription lifetime? + +For example, consider this: + +```cs +IObservable source = GetSource(); +IObservable filtered = source.Where(i => i % 2 == 0); +IDisposable subscription = filtered.Subscribe( + i => Console.WriteLine(i), + error => Console.WriteLine($"OnError: {error}"), + () => Console.WriteLine("OnCompleted")); ``` -These interfaces are not widely used, but prove useful as the subjects do not share a common base class. We will see the subject interfaces used later when we discover [Hot and cold observables](14_HotAndColdObservables.html). +We're calling `Subscribe` on the observable returned by `Where`. When we do that, it will in turn call `Subscribe` on the `IObservable` returned by `GetSource` (stored in the `source` variable). So there is in effect a chain of subscriptions here. (We only have access to the `IDisposable` returned by `filtered.Subscribe` but the object that returns will be storing the `IDisposable` that it received when it called `source.Subscribe`.) -## Subject factory +If the source comes to an end all by itself (by calling either `OnCompleted` or `OnError`), this cascades through the chain. So `source` will call `OnCompleted` on the `IObserver` that was supplied by the `Where` operator. And that in turn will call `OnCompleted` on the `IObserver` that was passed to `filtered.Subscribe`, and that will have references to the three methods we passed, so it will call our completion handler. So you could look at this by saying that `source` completes, it tells `filtered` that it has completed, which invokes our completion handler. (In reality this is a very slight oversimplification, because `source` doesn't tell `filtered` anything; it's actually talking to the `IObserver` that `filtered` supplied. This distinction matters if you have multiple subscriptions active simultaneously for the same chain of observables. But in this case, the simpler way of describing it is good enough even if it's not absolutely precise.) -Finally it is worth making you aware that you can also create a subject via a factory method. Considering that a subject combines the `IObservable` and `IObserver` interfaces, it seems sensible that there should be a factory that allows you to combine them yourself. The `Subject.Create(IObserver, IObservable)` factory method provides just this. +In short, completion bubbles up from the source, through all the operators, and arrives at our handler. -```csharp -//Creates a subject from the specified observer used to publish messages to the subject -// and observable used to subscribe to messages sent from the subject -public static ISubject>TSource, TResult< Create>TSource, TResult<( - IObserver>TSource< observer, - IObservable>TResult< observable) -{...} -``` +What if we unsubscribe early by calling `subscription.Dispose()`? In that case it all happens the other way round. The `subscription` returned by `filtered.Subscribe` is the first to know that we're unsubscribing, but it will then call `Dispose` on the object that was returned when it called `source.Subscribe` for us. -Subjects provide a convenient way to poke around Rx, however they are not recommended for day to day use. An explanation is in the [Usage Guidelines](18_UsageGuidelines.md) in the appendix. Instead of using subjects, favor the factory methods we will look at in [Part 2](04_CreatingObservableSequences.md). +Either way, everything from the source to the observer, including any operators that were sitting in between, gets shut down. -The fundamental types `IObserver` and `IObservable` and the auxiliary subject types create a base from which to build your Rx knowledge. It is important to understand these simple types and their implicit contracts. In production code you may find that you rarely use the `IObserver` interface and subject types, but understanding them and how they fit into the Rx eco-system is still important. The `IObservable` interface is the dominant type that you will be exposed to for representing a sequence of data in motion, and therefore will comprise the core concern for most of your work with Rx and most of this book. \ No newline at end of file +Now that we understand the relationship between an `IObservable` source and the `IObserver` interface that received event notifications, we can look at how we might create an `IObservable` instance to represent events of interest in our application. \ No newline at end of file diff --git a/content/03_CreatingObservableSequences.md b/content/03_CreatingObservableSequences.md new file mode 100644 index 0000000..e048999 --- /dev/null +++ b/content/03_CreatingObservableSequences.md @@ -0,0 +1,1300 @@ +--- +title : Creating Sequences +--- + +# Creating Observable Sequences + +In the preceding chapter, we saw the two fundamental Rx interfaces, `IObservable` and `IObserver`. We also saw how to receive events by implementing `IObserver`, and also by using implementations supplied by the `System.Reactive` package. In this chapter we'll see how to create `IObservable` sources to represent source events of interest in your application. + +We will begin by implementing `IObservable` directly. In practice, it's relatively unusual to do that, so we'll then look at the various ways you can get `System.Reactive` to supply an implementation that does most of the work for you. + +## A Very Basic `IObservable` Implementation + +Here's an implementation of an `IObservable` that produces a sequence of numbers: + +```csharp +public class MySequenceOfNumbers : IObservable +{ + public IDisposable Subscribe(IObserver observer) + { + observer.OnNext(1); + observer.OnNext(2); + observer.OnNext(3); + observer.OnCompleted(); + return System.Reactive.Disposables.Disposable.Empty; // Handy do-nothing IDisposable + } +} +``` + +We can test this by constructing an instance of it, and then subscribing to it: + +```csharp +var numbers = new MySequenceOfNumbers(); +numbers.Subscribe( + number => Console.WriteLine($"Received value: {number}"), + () => Console.WriteLine("Sequence terminated")); +``` + +This produces the following output: + +``` +Received value 1 +Received value 2 +Received value 3 +Sequence terminated +``` + +Although `MySequenceOfNumbers` is technically a correct implementation of `IObservable`, it is a little too simple to be useful. For one thing, we typically use Rx when there are events of interest, but this is not really reactive at all—it just produces a fixed set of numbers immediately. Moreover, the implementation is blocking—it doesn't even return from `Subscribe` until after it has finished producing all of its values. This example illustrates the basics of how a source provides events to a subscriber, but if we just want to represent a predetermined sequence of numbers, we might as well use an `IEnumerable` implementation such as `List` or an array. + +## Representing Filesystem Events in Rx + +Let's look at something a little more realistic. This is a wrapper around .NET's `FileSystemWatcher`, presenting filesystem change notifications as an `IObservable`. (Note: this is not necessarily the best design for an Rx `FileSystemWatcher` wrapper. The watcher provides events for several different types of change, and one of them, `Renamed`, provides details as a `RenamedEventArgs`. This derives from `FileSystemEventArgs` so collapsing everything down to a single event stream does work, but this would be inconvenient for applications that wanted access to the details of rename events. A more serious design problem is that this is incapable of reporting more than one event from `FileSystemWatcher.Error`. Such errors might be transient and recoverable, in which case an application might want to continue operating, but since this class chooses to represent everything with a single `IObservable`, it reports errors by invoking the observer's `OnError`, at which point the rules of Rx oblige us to stop. It would be possible to work around this with Rx's `Retry` operator, which can automatically resubscribe after an error, but it might be better to offer a separate `IObservable` so that we can report errors in a non-terminating way. However, the additional complication of that won't always be warranted. The simplicity of this design means it will be a good fit for some applications. As is often the way with software design, there isn't a one-size-fits-all approach.) + +```cs +// Represents filesystem changes as an Rx observable sequence. +// NOTE: this is an oversimplified example for illustration purposes. +// It does not handle multiple subscribers efficiently, it does not +// use IScheduler, and it stops immediately after the first error. +public class RxFsEvents : IObservable +{ + private readonly string folder; + + public RxFsEvents(string folder) + { + this.folder = folder; + } + + public IDisposable Subscribe(IObserver observer) + { + // Inefficient if we get multiple subscribers. + FileSystemWatcher watcher = new(this.folder); + + // FileSystemWatcher's documentation says nothing about which thread + // it raises events on (unless you use its SynchronizationObject, + // which integrates well with Windows Forms, but is inconvenient for + // us to use here) nor does it promise to wait until we've + // finished handling one event before it delivers the next. The Mac, + // Windows, and Linux implementations are all significantly different, + // so it would be unwise to rely on anything not guaranteed by the + // documentation. (As it happens, the Win32 implementation on .NET 7 + // does appear to wait until each event handler returns before + // delivering the next event, so we probably would get way with + // ignoring this issue. For now. On Windows. And actually the Linux + // implementation dedicates a single thread to this job, but there's + // a comment in the source code saying that this should probably + // change - another reason to rely only on documented behaviour.) + // So it's our problem to ensure we obey the rules of IObserver. + // First, we need to make sure that we only make one call at a time + // into the observer. A more realistic example would use an Rx + // IScheduler, but since we've not explained what those are yet, + // we're just going to use lock with this object. + object sync = new(); + + // More subtly, the FileSystemWatcher documentation doesn't make it + // clear whether we might continue to get a few more change events + // after it has reported an error. Since there are no promises about + // threads, it's possible that race conditions exist that would lead to + // us trying to handle an event from a FileSystemWatcher after it has + // reported an error. So we need to remember if we've already called + // OnError to make sure we don't break the IObserver rules in that + // case. + bool onErrorAlreadyCalled = false; + + void SendToObserver(object _, FileSystemEventArgs e) + { + lock (sync) + { + if (!onErrorAlreadyCalled) + { + observer.OnNext(e); + } + } + } + + watcher.Created += SendToObserver; + watcher.Changed += SendToObserver; + watcher.Renamed += SendToObserver; + watcher.Deleted += SendToObserver; + + watcher.Error += (_, e) => + { + lock (sync) + { + // The FileSystemWatcher might report multiple errors, but + // we're only allowed to report one to IObservable. + if (onErrorAlreadyCalled) + { + observer.OnError(e.GetException()); + onErrorAlreadyCalled = true; + watcher.Dispose(); + } + } + }; + + watcher.EnableRaisingEvents = true; + + return watcher; + } +} +``` + +That got more complex fast. This illustrates that `IObservable` implementations are responsible for obeying the `IObserver` rules. This is generally a good thing: it keeps the messy concerns around concurrency contained in a single place. Any `IObserver` that I subscribe to this `RxFsEvents` doesn't have to worry about concurrency, because it can count on the `IObserver` rules, which guarantee that it will only have to handle one thing at a time. If I hadn't been required to enforce these rules in the source, it might have made my `RxFsEvents` class simpler, but all of that complexity of dealing with overlapping events would have spread out into the code that handles the events. Concurrency is hard enough to deal with when its effects are contained. Once it starts to spread across multiple types, it can become almost impossible to reason about. Rx's `IObserver` rules prevent this from happening. + +(Note: this is a significant feature of Rx. The rules keep things simple for observers. This becomes increasingly important as the complexity of your event sources or event process grows.) + +There are a couple of issues with this code (aside from the API design issues already mentioned). One is that when `IObservable` implementations produce events modelling real-life asynchronous activity (such as filesystem changes) applications will often want some way to take control over which threads notifications arrive on. For example, UI frameworks tend to have thread affinity requirements. You typically need to be on a particular thread to be allowed to update the user interface. Rx provides mechanisms for redirecting notifications onto different schedulers, so we can work around it, but we would normally expect to be able to provide this sort of observer with an `IScheduler`, and for it to deliver notifications through that. We'll discuss schedulers in later chapters. + +The other issue is that this does not deal with multiple subscribers efficiently. You're allowed to call `IObservable.Subscribe` multiple times, and if you do that with this code, it will create a new `FileSystemWatcher` each time. That could happen more easily than you might think. Suppose we had an instance of this watcher, and wanted to handle different events in different ways. We might use the `Where` operator to define observable sources that split events up in the way we want: + +```cs +IObservable configChanges = + fs.Where(e => Path.GetExtension(e.Name) == ".config"); +IObservable deletions = + fs.Where(e => e.ChangeType == WatcherChangeTypes.Deleted); +``` + +When you call `Subscribe` on the `IObservable` returned by the `Where` operator, it will call `Subscribe` on its input. So in this case, if we call `Subscribe` on both `configChanges` and `deletions`, that will result in _two_ calls to `Subscribe` on `rs`. So if `rs` is an instance of our `RxFsEvents` type above, each one will construct its own `FileSystemEventWatcher`, which is inefficient. + +Rx offers a few ways to deal with this. It provides operators designed specifically to take an `IObservable` that does not tolerate multiple subscribers and wrap it in an adapter that can: + +```cs +IObservable fs = + new RxFsEvents(@"c:\temp") + .Publish() + .RefCount(); +``` + +But this is leaping ahead. (These operators are described in [the Publishing Operators chapter](15_PublishingOperators.md).) If you want to build a type that is inherently multi-subscriber-friendly, all you really need to do is keep track of all your subscribers and notify each of them in a loop. Here's a modified version of the filesystem watcher: + +```cs +public class RxFsEventsMultiSubscriber : IObservable +{ + private readonly object sync = new(); + private readonly List subscribers = new(); + private readonly FileSystemWatcher watcher; + + public RxFsEventsMultiSubscriber(string folder) + { + this.watcher = new FileSystemWatcher(folder); + + watcher.Created += SendEventToObservers; + watcher.Changed += SendEventToObservers; + watcher.Renamed += SendEventToObservers; + watcher.Deleted += SendEventToObservers; + + watcher.Error += SendErrorToObservers; + } + + public IDisposable Subscribe(IObserver observer) + { + Subscription sub = new(this, observer); + lock (this.sync) + { + this.subscribers.Add(sub); + + if (this.subscribers.Count == 1) + { + // We had no subscribers before, but now we've got one so we need + // to start up the FileSystemWatcher. + watcher.EnableRaisingEvents = true; + } + } + + return sub; + } + + private void Unsubscribe(Subscription sub) + { + lock (this.sync) + { + this.subscribers.Remove(sub); + + if (this.subscribers.Count == 0) + { + watcher.EnableRaisingEvents = false; + } + } + } + + void SendEventToObservers(object _, FileSystemEventArgs e) + { + lock (this.sync) + { + foreach (var subscription in this.subscribers) + { + subscription.Observer.OnNext(e); + } + } + } + + void SendErrorToObservers(object _, ErrorEventArgs e) + { + Exception x = e.GetException(); + lock (this.sync) + { + foreach (var subscription in this.subscribers) + { + subscription.Observer.OnError(x); + } + + this.subscribers.Clear(); + } + } + + private class Subscription : IDisposable + { + private RxFsEventsMultiSubscriber? parent; + + public Subscription( + RxFsEventsMultiSubscriber rxFsEventsMultiSubscriber, + IObserver observer) + { + this.parent = rxFsEventsMultiSubscriber; + this.Observer = observer; + } + + public IObserver Observer { get; } + + public void Dispose() + { + this.parent?.Unsubscribe(this); + this.parent = null; + } + } +} +``` + +This creates only a single `FileSystemWatcher` instance no matter how many times `Subscribe` is called. Notice that I've had to introduce a nested class to provide the `IDisposable` that `Subscribe` returns. I didn't need that with the very first `IObservable` implementation in this chapter because it had already completed the sequence before returning, so it was able to return the `Disposable.Empty` property conveniently supplied by Rx. (This is handy in cases where you're obliged to supply an `IDisposable`, but you don't actually need to do anything when disposed.) And in my first `FileSystemWatcher` wrapper, `RxFsEvents`, I just returned the `FileSystemWatcher` itself from `Dispose`. (This works because `FileSystemWatcher.Dispose` shuts down the watcher, and each subscriber was given its own `FileSystemWatcher`.) But now that a single `FileSystemWatcher` supports multiple observers, we need to do a little more work when an observer unsubscribes. + +When a `Subscription` instance that we returned from `Subscribe` gets disposed, it removes itself from the list of subscribers, ensuring that it won't receive any more notifications. It also sets the `FileSystemWatcher`'s `EnableRaisingEvents` to false if there are no more subscribers, ensuring that this source does not do unnecessary work if nothing needs notifications right now. + +This is looking more realistic than the first example. This is truly a source of events that could occur at any moment (making this exactly the sort of thing well suited to Rx) and it now handles multiple subscribers intelligently. However, we wouldn't often write things this way. We're doing all the work ourselves here—this code doesn't even require a reference to the `System.Reactive` package because the only Rx types it refers to are `IObservable` and `IObserver`, both of which are built into the .NET runtime libraries. In practice we typically defer to helpers in `System.Reactive` because they can do a lot of work for us. + +For example, suppose we only cared about `Changed` events. We could write just this: + +```cs +FileSystemWatcher watcher = new (@"c:\temp"); +IObservable changes = Observable + .FromEventPattern(watcher, nameof(watcher.Changed)) + .Select(ep => ep.EventArgs); +watcher.EnableRaisingEvents = true; +``` + +Here we're using the `FromEventPattern` helper from the `System.Reactive` library's `Observable` class, which can be used to build an `IObservable` from any .NET event that conforms to the normal pattern (in which event handlers take two arguments: a sender of type `object`, and then some `EventArgs`-derived type containing information about the event). This is not as flexible as the earlier example. It reports only one of the events, and we have to manually start (and, if necessary stop) the `FileSystemWatcher`. But for some applications that will be good enough, and this is a lot less code to write. If we were aiming to write a fully-featured wrapper for `FileSystemWatcher` suitable for many different scenarios, it might be worth writing a specialized `IObservable` implementation as shown earlier. (We could easily extend this last example to watch all of the events. We'd just use the `FromEventPattern` once for each event, and then use `Observable.Merge` to combine the four resulting observables into one. The only real benefit we're getting from a full custom implementation is that we can automatically start and stop the `FileSystemWatcher` depending on whether there are currently any observers.) But if we just need to represent some events as an `IObservable` so that we can work with them in our application, we can just use this simpler approach. + +In practice, we almost always get `System.Reactive` to implement `IObservable` for us. Even if we want to take control of certain aspects (such as automatically starting up and shutting down the `FileSystemWatcher` in these examples) we can almost always find a combination of operators that enable this. The following code uses various methods from `System.Reactive` to return an `IObservable` that has all the same functionality as the fully-featured hand-written `RxFsEventsMultiSubscriber` above, but with considerably less code. + +```cs +IObservable ObserveFileSystem(string folder) +{ + return + // Observable.Defer enables us to avoid doing any work + // until we have a subscriber. + Observable.Defer(() => + { + FileSystemWatcher fsw = new(folder); + fsw.EnableRaisingEvents = true; + + return Observable.Return(fsw); + }) + // Once the preceding part emits the FileSystemWatcher + // (which will happen when someone first subscribes), we + // want to wrap all the events as IObservables, for which + // we'll use a projection. To avoid ending up with an + // IObservable>, we use + // SelectMany, which effectively flattens it by one level. + .SelectMany(fsw => + Observable.Merge(new[] + { + Observable.FromEventPattern( + h => fsw.Created += h, h => fsw.Created -= h), + Observable.FromEventPattern( + h => fsw.Changed += h, h => fsw.Changed -= h), + Observable.FromEventPattern( + h => fsw.Renamed += h, h => fsw.Renamed -= h), + Observable.FromEventPattern( + h => fsw.Deleted += h, h => fsw.Deleted -= h) + }) + // FromEventPattern supplies both the sender and the event + // args. Extract just the latter. + .Select(ep => ep.EventArgs) + // The Finally here ensures the watcher gets shut down once + // we have no subscribers. + .Finally(() => fsw.Dispose())) + // This combination of Publish and RefCount means that multiple + // subscribers will get to share a single FileSystemWatcher, + // but that it gets shut down if all subscribers unsubscribe. + .Publish() + .RefCount(); +} +``` + +I've used a lot of methods there, most of which I've not talked about before. For that example to make any sense, I clearly need to start describing the numerous ways in which the `System.Reactive` package can implement `IObservable` for you. + +## Simple factory methods + +Due to the large number of methods available for creating observable sequences, we will break them down into categories. Our first category of methods create `IObservable` sequences that produce at most a single result. + +### Observable.Return + +One of the simplest factory methods is `Observable.Return(T value)`, which you've already seen in the `Quiescent` example in the preceding chapter. This method takes a value of type `T` and returns an `IObservable` which will produce this single value and then complete. In a sense, this _wraps_ a value in an `IObservable`; it's conceptually similar to writing `new T[] { value }`, in that it's a sequence containing just one element. You could also think of it as being the Rx equivalent of `Task.FromResult`, which you can use when you have a value of some type `T`, and need to pass it to something that wants a `Task`. + +```csharp +IObservable singleValue = Observable.Return("Value"); +``` + +I specified the type parameter for clarity, but this is not necessary as the compiler can infer the type from argument provided: + +```csharp +IObservable singleValue = Observable.Return("Value"); +``` + +`Return` produces a cold observable: each subscriber will receive the value immediately upon subscription. ([Hot and cold observables](02_KeyTypes.md#hot-and-cold-sources) were described in the preceding chapter.) + +### Observable.Empty + +Sometimes it can be useful to have an empty sequence. .NET's `Enumerable.Empty()` does this for `IEnumerable`, and Rx has a direct equivalent in the form of `Observable.Empty()`, which returns an empty `IObservable`. We need to provide the type argument because there's no value from which the compiler can infer the type. + +```csharp +IObservable empty = Observable.Empty(); +``` + +In practice, an empty sequence is one that immediately calls `OnCompleted` on any subscriber. + +In comparison with `IEnumerable`, this is just the Rx equivalent of an empty list, but there's another way to look at it. Rx is a powerful way to model asynchronous processes, so you could think of this as being similar to a task that completes immediately without producing any result—so it has a conceptual resemblance to `Task.CompletedTask`. (This is not as close an analogy as that between `Observable.Return` and `Task.FromResult`, because in that case we're comparing an `IObservable` with a `Task`, whereas here we're comparing an `IObservable` with a `Task`—the only way for a task to complete without producing anything is if we use the non-generic version of `Task`.) + +### Observable.Never + +The `Observable.Never()` method returns a sequence which, like `Empty`, does not produce any values, but unlike `Empty`, it never ends. In practice, that means that it never invokes any method (neither `OnNext`, `OnCompleted`, nor `OnError`) on subscribers. Whereas `Observable.Empty()` completes immediately, `Observable.Never` has infinite duration. + +```csharp +IObservable never = Observable.Never(); +``` + +It might not seem obvious why this could be useful. I gave one possible use in the last chapter: you could use this in a test to simulate a source that wasn't producing any values, perhaps to enable your test to validate timeout logic. + +It can also be used in places where we use observables to represent time-based information. Sometimes we don't actually care what emerges from an observable; we might care only _when_ something (anything) happens. (We saw an example of this "observable sequence used purely for timing purposes" concept in the preceding chapter, although `Never` wouldn't make sense in that particular scenario. The `Quiescent` example used the `Buffer` operator, which works over two observable sequences: the first contains the items of interest, and the second is used purely to determine how to cut the first into chunks. `Buffer` doesn't do anything with the values produced by the second observable: it pays attention only to _when_ values emerge, completing the previous chunk each time the second observable produces a value. And if we're representing temporal information it can sometimes be useful to have a way to represent the idea that some event never occurs.) + +As an example of where you might want to use `Never` for timing purposes, suppose you were using some Rx-based library that offered a timeout mechanism, where an operation would be cancelled when some timeout occurs, and the timeout is itself modelled as an observable sequence. If for some reason you didn't want a timeout, and just want to wait indefinitely, you could specify a timeout of `Observable.Never`. + +### Observable.Throw + +`Observable.Throw(Exception)` returns a sequence that immediately reports an error to any subscriber. As with `Empty` and `Never`, we don't supply a value to this method (just an exception) so we need to provide a type parameter so that it knows what `T` to use in the `IObservable` that it returns. (It will never actually a produce a `T`, but you can't have an instance of `IObservable` without picking some particular type for `T`.) + +```csharp +IObservable throws = Observable.Throw(new Exception()); +``` + +### Observable.Create + +The `Create` factory method is more powerful than the other creation methods because it can be used to create any kind of sequence. You could implement any of the preceding four methods with `Observable.Create`. +The method signature itself may seem more complex than necessary at first, but becomes quite natural once you are used to it. + +```csharp +// Creates an observable sequence from a specified Subscribe method implementation. +public static IObservable Create( + Func, IDisposable> subscribe) +{...} +public static IObservable Create( + Func, Action> subscribe) +{...} +``` + +You provide this with a delegate that will be executed each time a subscription is made. Your delegate will be passed an `IObserver`. Logically speaking, this represents the observer passed to the `Subscribe` method, although in practice Rx puts a wrapper around that for various reasons. You can call the `OnNext`/`OnError`/`OnCompleted` methods as you need. This is one of the few scenarios where you will work directly with the `IObserver` interface. Here's a simple example that produces three items: + +```csharp +private IObservable SomeNumbers() +{ + return Observable.Create( + (IObserver observer) => + { + observer.OnNext(1); + observer.OnNext(2); + observer.OnNext(3); + observer.OnCompleted(); + + return Disposable.Empty; + }); +} +``` + + +Your delegate must return either an `IDisposable` or an `Action` to enable unsubscription. When the subscriber disposes their subscription in order to unsubscribe, Rx will invoke `Dispose()` on the `IDisposable` you returned, or in the case where you returned an `Action`, it will invoke that. + +This example is reminiscent of the `MySequenceOfNumbers` example from the start of this chapter, in that it immediately produces a few fixed values. The main difference in this case is that Rx adds some wrappers that can handle awkward situations such as re-entrancy. Rx will sometimes automatically defer work to prevent deadlocks, so it's possible that code consuming the `IObservable` returned by this method will see a call to `Subscribe` return before the callback in the code above runs, in which case it would be possible for them to unsubscribe inside their `OnNext` handler. + +The following sequence diagram shows how this could occur in practice. Suppose the `IObservable` returned by `SomeNumbers` has been wrapped by Rx in a way that ensures that subscription occurs in some different execution context. We'd typically determine the context by using a suitable [scheduler](11_SchedulingAndThreading.md#schedulers). (The [`SubscribeOn`](11_SchedulingAndThreading.md#subscribeon-and-observeon) operator creates such a wrapper.) We might use the [`TaskPoolScheduler`](11_SchedulingAndThreading.md#taskpoolscheduler) in order to ensure that the subscription occurs on some task pool thread. So when our application code calls `Subscribe`, the wrapper `IObservable` doesn't immediately subscribe to the underlying observable. Instead it queues up a work item with the scheduler to do that, and then immediately returns without waiting for that work to run. This is how our subscriber can be in possession of an `IDisposable` representing the subscription before `Observable.Create` invokes our callback. The diagram shows the subscriber then making this available to the observer. + + +![A sequence diagram with 6 participants: Subscriber, Rx IObservable Wrapper, Scheduler, Observable.Create, Rx IObserver Wrapper, and Observer. It shows the following messages. Subscriber sends "Subscribe()" to Rx IObservable Wrapper. Rx IObservable Wrapper sends "Schedule Subscribe()" to Scheduler. Rx IObservable Wrapper returns "IDisposable (subscription)" to Subscriber. Subscriber sends "Set subscription IDisposable" to Observer. Scheduler sends "Subscribe()" to Observable.Create. Observable.Create sends "OnNext(1)" to Rx IObserver Wrapper. Rx IObserver Wrapper sends "OnNext(1)" to Observer. Observable.Create sends "OnNext(2)" to Rx IObserver Wrapper. Rx IObserver Wrapper sends "OnNext(2)" to Observer. Observer sends "subscription.Dispose()" to Rx IObservable Wrapper. Observable.Create sends "OnNext(3)" to Rx IObserver Wrapper. Observable.Create sends "OnCompleted()" to Rx IObserver Wrapper.](GraphicsIntro/Ch03-Sequence-CreateWrappers.svg) + +The diagram shows the scheduler call `Subscribe` on the underlying observable after this, and that will mean the call back we passed to `Observable.Create` will now run. Our callback calls `OnNext`, but it is not passed the real observer: instead it is passed another Rx-generated wrapper. That wrapper initially forwards calls directly onto the real observer, but our diagram shows that when the real observer (all the way over on the right) receives the its second call (`OnNext(2)`) it unsubscribes by calling `Dispose` on the `IDisposable` that was returned when we subscribed to the Rx `IObservable` wrapper. The two wrappers here—the `IObservable` and `IObserver` wrappers—are connected, so when we unsubscribe from the `IObservable` wrapper, it tells the `IObserver` wrapper that the subscription is being shut down. This means that when our `Observable.Create` callback calls `OnNext(3)` on the `IObserver` wrapper, that wrapper does _not_ forward it to the real observer, because it knows that that observer has already unsubscribed. (It also doesn't forward the `OnCompleted`, for the same reason.) + +You might be wondering how the `IDisposable` we return to `Observable.Create` can ever do anything useful. It's the return value of the callback, so we can only return it to Rx as the last thing our callback does. Won't we always have finished our work by the time we return, meaning there's nothing to cancel? Not necessarily—we might kick off some work that continues to run after we return. This next example does that, meaning that the unsubscription action it returns is able to do something useful: it sets a cancellation token that is being observed by the loop that generates our observable's output. (This returns a callback instead of an `IDisposable`—`Observable.Create` offers overloads that let you do either. In this case, Rx will invoke our callback when the subscription is terminated early.) + +```cs +IObservable KeyPresses() => + Observable.Create(observer => + { + CancellationTokenSource cts = new(); + Task.Run(() => + { + while (!cts.IsCancellationRequested) + { + ConsoleKeyInfo ki = Console.ReadKey(); + observer.OnNext(ki.KeyChar); + } + }); + + return () => cts.Cancel(); + }); +``` + +This illustrates how cancellation won't necessarily take effect immediately. The `Console.ReadKey` API does not offer an overload accepting a `CancellationToken`, so this observable won't be able to detect that cancellation is requested until the user next presses a key, causing `ReadKey` to return. + +Bearing in mind that cancellation might have been requested while we were waiting for `ReadKey` to return, you might think we should check for that after `ReadKey` returns and before calling `OnNext`. In fact it doesn't matter if we don't. Rx has a rule that says an observable source _must not_ call into an observer _after_ a call to `Dispose` on that observer's subscription returns. To enforce that rule, if the callback you pass to `Observable.Create` continues to call methods on its `IObserver` after a request to unsubscribe, Rx just ignores the call. This is one reason why the `IObserver` it passes to you is a wrapper: it can intercept the calls before they are passed to the underlying observer. However, that convenience means there are two important things to be aware of +1. if you _do_ ignore attempts to unsubscribe and continue to do work to produce items, you are just wasting time because nothing will receive those items +2. if you call `OnError` it's possible that nothing is listening and that the error will be completely ignored. + +There are overloads of `Create` designed to support `async` methods. This next method exploits this to be able to use the asynchronous `ReadLineAsync` method to present lines of text from a file as an observable source. + +```cs +IObservable ReadFileLines(string path) => + Observable.Create(async (observer, cancellationToken) => + { + using (StreamReader reader = File.OpenText(path)) + { + while (cancellationToken.IsCancellationRequested) + { + string? line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false); + if (line is null) + { + break; + } + + observer.OnNext(line); + } + + observer.OnCompleted(); + } + }); +``` + +Reading data from a storage device typically doesn't happen instantaneously (unless it happens to be in the filesystem cache already), so this source will provide data as quickly as it can be read from storage. + +Notice that because this is an `async` method, it will typically return to its caller before it completes. (The first `await` that actually has to wait returns, and the remainder of the method runs via a callback when the work completes.) That means that subscribers will typically be in possession of the `IDisposable` representing their subscription before this method finishes, so we're using a different mechanism to handle unsubscription here. This particular overload of `Create` passes its callback not just an `IObserver` but also a `CancellationToken`, with which it will request cancellation when unsubscription occurs. + +File IO can encounter errors. The file we're looking for might not exist, or we might be unable to open it due to security restrictions, or because some other application is using it. The file might be on a remote storage server, and we could lose network connectivity. For this reason, we must expect exceptions from such code. This example has done nothing to detect exceptions, and yet the `IObservable` that this `ReadFileLines` method returns will in fact report any exceptions that occur. This is because the `Create` method will catch any exception that emerges from our callback and report it with `OnError`. (If our code already called `OnComplete` on the observer, Rx won't call `OnError` because that would violate the rules. Instead it will silently drop the exception, so it's best not to attempt to do any work after you call `OnCompleted`.) + +This automatic exception delivery is another example of why the `Create` factory method is the preferred way to implement custom observable sequences. It is almost always a better option than creating custom types that implement the `IObservable` interface. This is not just because it saves you some time. It's also that Rx tackles the intricacies that you may not think of such as thread safety of notifications and disposal of subscriptions. + +The `Create` method entails lazy evaluation, which is a very important part of Rx. It opens doors to other powerful features such as scheduling and combination of sequences that we will see later. The delegate will only be invoked when a subscription is made. So in the `ReadFileLines` example, it won't attempt to open the file until you subscribe to the `IObservable` that is returned. If you subscribe multiple times, it will execute the callback each time. (So if the file has changed, you can retrieve the latest contents by calling `Subscribe` again.) + +As an exercise, try to build the `Empty`, `Return`, `Never` & `Throw` extension methods yourself using the `Create` method. If you have Visual Studio or [LINQPad](http://www.linqpad.net/) available to you right now, code it up as quickly as you can, or if you have Visual Studio Code, you could create a new [Polyglot Notebook](https://code.visualstudio.com/docs/languages/polyglot). (Polyglot Notebooks make Rx available automatically, so you can just write a C# cell with a suitable `using` directive, and you're up and running.) If you don't (perhaps you are on the train on the way to work), try to conceptualize how you would solve this problem. + +You completed that last step before moving onto this paragraph, right? Because you can now compare your versions with these examples of `Empty`, `Return`, `Never` and `Throw` recreated with `Observable.Create`: + +```csharp +public static IObservable Empty() +{ + return Observable.Create(o => + { + o.OnCompleted(); + return Disposable.Empty; + }); +} + +public static IObservable Return(T value) +{ + return Observable.Create(o => + { + o.OnNext(value); + o.OnCompleted(); + return Disposable.Empty; + }); +} + +public static IObservable Never() +{ + return Observable.Create(o => + { + return Disposable.Empty; + }); +} + +public static IObservable Throws(Exception exception) +{ + return Observable.Create(o => + { + o.OnError(exception); + return Disposable.Empty; + }); +} +``` + +You can see that `Observable.Create` provides the power to build our own factory methods if we wish. + +### Observable.Defer + +One very useful aspect of `Observable.Create` is that it provides a place to put code that should run only when subscription occurs. Often, libraries will make `IObservable` properties available that won't necessarily be used by all applications, so it can be useful to defer the work involved until you know you will really need it. This deferred initialization is inherent to how `Observable.Create` works, but what if the nature of our source means that `Observable.Create` is not a good fit? How can we perform deferred initialization in that case? Rx providers `Observable.Defer` for this purpose. + +I've already used `Defer` once. The `ObserveFileSystem` method returned an `IObservable` reporting changes in a folder. It was not a good candidate for `Observable.Create` because it provided all the notifications we wanted as .NET events, so it made sense to use Rx's event adaptation features. But we still wanted to defer the creation of the `FileSystemWatcher` until the moment of subscription, which is why that example used `Observable.Defer`. + +`Observable.Defer` takes a callback that returns an `IObservable`, and `Defer` wraps this with an `IObservable` that invokes that callback upon subscription. To show the effect, I'm first going to show an example that does not use `Defer`: + +```cs +static IObservable WithoutDeferal() +{ + Console.WriteLine("Doing some startup work..."); + return Observable.Range(1, 3); +} + +Console.WriteLine("Calling factory method"); +IObservable s = WithoutDeferal(); + +Console.WriteLine("First subscription"); +s.Subscribe(Console.WriteLine); + +Console.WriteLine("Second subscription"); +s.Subscribe(Console.WriteLine); +``` + +This produces the following output: + +``` +Calling factory method +Doing some startup work... +First subscription +1 +2 +3 +Second subscription +1 +2 +3 +``` + +As you can see, the `"Doing some startup work...` message appears when we call the factory method, and before we've subscribed. So if nothing ever subscribed to the `IObservable` that method returns, the work would be done anyway, wasting time and energy. Here's the `Defer` version: + +```cs +static IObservable WithDeferal() +{ + return Observable.Defer(() => + { + Console.WriteLine("Doing some startup work..."); + return Observable.Range(1, 3); + }); +} +``` + +If we were to use this with similar code to the first example, we'd see this output: + +``` +Calling factory method +First subscription +Doing some startup work... +1 +2 +3 +Second subscription +Doing some startup work... +1 +2 +3 +``` + +There are two important differences. First, the `"Doing some startup work..."` message does not appear until we first subscribe, illustrating that `Defer` has done what we wanted. However, notice that the message now appears twice: it will do this work each time we subscribe. If you want this deferred initialization but you'd also like once-only execution, you should look at the operators in the [Publishing Operators chapter](15_PublishingOperators.md), which provide various ways to enable multiple subscribers to share a single subscription to an underlying source. + +## Sequence Generators + +The creation methods we've looked at so far are straightforward in that they either produce very simple sequences (such as single-element, or empty sequences), or they rely on our code to tell them exactly what to produce. Now we'll look at some methods that can produce longer sequences. + +### Observable.Range + +`Observable.Range(int, int)` returns an `IObservable` that produces a range of integers. The first integer is the initial value and the second is the number of values to yield. This example will write the values '10' through to '24' and then complete. + +```cs +IObservable range = Observable.Range(10, 15); +range.Subscribe(Console.WriteLine, () => Console.WriteLine("Completed")); +``` + +### Observable.Generate + +Suppose you wanted to emulate the `Range` factory method using `Observable.Create`. You might try this: + +```cs +// Not the best way to do it! +IObservable Range(int start, int count) => + Observable.Create(observer => + { + for (int i = 0; i < count; ++i) + { + observer.OnNext(start + i); + } + + return Disposable.Empty; + }); +``` + +This will work, but it does not respect request to unsubscribe. That won't cause direct harm, because Rx detects unsubscription, and will simply ignore any further values we produce. However, it's a waste of CPU time (and therefore energy, with consequent battery lifetime and/or environmental impact) to carry on generating numbers after nobody is listening. How bad that is depends on how long a range was requested. But imagine you wanted an infinite sequence? Perhaps it's useful to you to have an `IObservable` that produces value from the Fibonacci sequence, or prime numbers. How would you write that with `Create`? You'd certainly want some means of handling unsubscription in that case. We need our callback to return if we are to be notified of unsubscription (or we could supply an `async` method, but that doesn't really seem suitable here). + +There's a different approach that can work better here: `Observable.Generate`. The simple version of `Observable.Generate` takes the following parameters: + +- an initial state +- a predicate that defines when the sequence should terminate +- a function to apply to the current state to produce the next state +- a function to transform the state to the desired output + +```csharp +public static IObservable Generate( + TState initialState, + Func condition, + Func iterate, + Func resultSelector) +``` + +This shows how you could use `Observable.Generate` to construct a `Range` method: + +```csharp +// Example code only +public static IObservable Range(int start, int count) +{ + int max = start + count; + return Observable.Generate( + start, + value => value < max, + value => value + 1, + value => value); +} +``` + +The `Generate` method calls us back repeatedly until either our `condition` callback says we're done, or the observer unsubscribes. We can define an infinite sequence simply by never saying we are done: + +```cs +IObservable Fibonacci() +{ + return Observable.Generate( + (v1: new BigInteger(1), v2: new BigInteger(1)), + value => true, // It never ends! + value => (value.v2, value.v1 + value.v2), + value => value.v1); ; +} +``` + +## Timed Sequence Generators + +Most of the methods we've looked at so far have returned sequences that produce all of their values immediately. (The only exception is where we called `Observable.Create` and produced values when we were ready to.) However, Rx is able to generate sequences on a schedule. + +As we'll see, operators that schedule their work do so through an abstraction called a _scheduler_. If you don't specify one, they will pick a default scheduler, but sometimes the timer mechanism is significant. For example, there are timers that integrate with UI frameworks, delivering notifications on the same thread that mouse clicks and other input are delivered on, and we might want Rx's time-based operators to use these. For testing purposes it can be useful to virtualize timings, so we can verify what happens in timing-sensitive code without necessarily waiting for tests to execute in real time. + +Schedulers are a complex subject that is out of scope for this chapter, but they are covered in detail in the later chapter on [Scheduling and threading](11_SchedulingAndThreading.md). + +There are three ways of producing timed events. + + +### Observable.Interval + +The first is `Observable.Interval(TimeSpan)` which will publish incremental values starting from zero, based on a frequency of your choosing. + +This example publishes values every 250 milliseconds. + +```csharp +IObservable interval = Observable.Interval(TimeSpan.FromMilliseconds(250)); +interval.Subscribe( + Console.WriteLine, + () => Console.WriteLine("completed")); +``` + +Output: + +``` +0 +1 +2 +3 +4 +5 +``` + +Once subscribed, you must dispose of your subscription to stop the sequence, because `Interval` returns an infinite sequence. Rx presumes that you might have considerable patience, because the sequences returned by `Interval` are of type `IObservable` (`long`, not `int`) meaning you won't hit problems if you produce more than a paltry 2.1475 billion event (i.e. more than `int.MaxValue`). + +### Observable.Timer + +The second factory method for producing constant time based sequences is `Observable.Timer`. It has several overloads. The most basic one takes just a `TimeSpan` as `Observable.Interval` does. But unlike `Observable.Interval`, `Observable.Timer` will publish exactly one value (the number 0) after the period of time has elapsed, and then it will complete. + +```cs +var timer = Observable.Timer(TimeSpan.FromSeconds(1)); +timer.Subscribe( + Console.WriteLine, + () => Console.WriteLine("completed")); +``` + +Output: + +``` +0 +completed +``` + +Alternatively, you can provide a `DateTimeOffset` for the `dueTime` parameter. This will produce the value 0 and complete at the specified time. + +A further set of overloads adds a `TimeSpan` that indicates the period at which to produce subsequent values. This allows us to produce infinite sequences. It also shows how `Observable.Interval` is really just a special case of `Observable.Timer`. `Interval` could be implemented like this: + +```cs +public static IObservable Interval(TimeSpan period) +{ + return Observable.Timer(period, period); +} +``` + +While `Observable.Interval` will always wait the given period before producing the first value, this `Observable.Timer` overload gives the ability to start the sequence when you choose. With `Observable.Timer` you can write the following to have an interval sequence that starts immediately. + +```csharp +Observable.Timer(TimeSpan.Zero, period); +``` + +This takes us to our third way and most general way for producing timer related sequences, back to `Observable.Generate`. + +### Timed Observable.Generate + + There's a more complex overload of `Observable.Generate` that allows you to provide a function that specifies the due time for the next value. + +```csharp +public static IObservable Generate( + TState initialState, + Func condition, + Func iterate, + Func resultSelector, + Func timeSelector) +``` + +The extra `timeSelector` argument lets us tell `Generate` when to produce the next item. We can use this to write our own implementation of `Observable.Timer` (and as you've already seen, this in turn enables us to write our own `Observable.Interval`). + +```cs +public static IObservable Timer(TimeSpan dueTime) +{ + return Observable.Generate( + 0l, + i => i < 1, + i => i + 1, + i => i, + i => dueTime); +} + +public static IObservable Timer(TimeSpan dueTime, TimeSpan period) +{ + return Observable.Generate( + 0l, + i => true, + i => i + 1, + i => i, + i => i == 0 ? dueTime : period); +} + +public static IObservable Interval(TimeSpan period) +{ + return Observable.Generate( + 0l, + i => true, + i => i + 1, + i => i, + i => period); +} +``` + +This shows how you can use `Observable.Generate` to produce infinite sequences. I will leave it up to you the reader, as an exercise using `Observable.Generate`, to produce values at variable rates. + +## Observable sequences and state + +As `Observable.Generate` makes particularly clear, observable sequences may need to maintain state. With that operator it is explicit—we pass in initial state, and we supply a callback to update it on each iteration. Plenty of other operators maintain internal state. The `Timer` remembers its tick count, and more subtly, has to somehow keep track of when it last raised an event and when the next one is due. And as you'll see in forthcoming chapters, plenty of other operators need to remember information about what they've already seen. + +This raises an interesting question: what happens if a process shuts down? Is there a way to preserve that state, and reconstitute it in a new process. + +With ordinary Rx.NET, the answer is no: all such state is held entirely in memory and there is no way to get hold of that state, or to ask running subscriptions to serialize their current state. This means that if you are dealing with particularly long-running operations you need to work out how you would restart and you can't rely on `System.Reactive` to help you. However, there is a related Rx-based set of libraries known collectively as [the Reaqtive libraries](https://reaqtive.net/). These provide implementations of most of the same operators as `System.Reactive`, but in a form where you can collect the current state, and recreate new subscriptions from previously preserved state. These libraries also include a component called Reaqtor, which is a hosting technology that can manage automatic checkpointing, and post-crash recovery, making it possible to support very long-running Rx logic, by making subscriptions persistent and reliable. Be aware that this is not currently in any productised form, so you will need to do a fair amount of work to use it, but if you need a persistable version of Rx, be aware that it exists. + +## Adapting Common Types to `IObservable` + +Although we've now seen two very general ways to produce arbitrary sequences—`Create` and `Generate`—what if you already have an existing source of information in some other form that you'd like to make available as an `IObservable`? Rx provides a few adapters for common source types. + +### From delegates + +The `Observable.Start` method allows you to turn a long running `Func` or `Action` into a single value observable sequence. By default, the processing will be done asynchronously on a ThreadPool thread. If the overload you use is a `Func` then the return type will be `IObservable`. When the function returns its value, that value will be published and then the sequence completed. If you use the overload that takes an `Action`, then the returned sequence will be of type `IObservable`. The `Unit` type represents the absence of information, so it's somewhat analogous to `void`, except you can have an instance of the `Unit` type. It's particularly useful in Rx because we often care only about when something has happened, and there might not be any information besides timing. In these cases, we often use an `IObservable` so that it's possible to produce definite events even though there's no meaningful data in them. (The name comes from the world of functional programming, where this kind of construct is used a lot.) In this case, `Unit` is used to publish an acknowledgement that the `Action` is complete, because an `Action` does not return any information. The `Unit` type itself has no value; it just serves as an empty payload for the `OnNext` notification. Below is an example of using both overloads. + +```csharp +static void StartAction() +{ + var start = Observable.Start(() => + { + Console.Write("Working away"); + for (int i = 0; i < 10; i++) + { + Thread.Sleep(100); + Console.Write("."); + } + }); + + start.Subscribe( + unit => Console.WriteLine("Unit published"), + () => Console.WriteLine("Action completed")); +} + +static void StartFunc() +{ + var start = Observable.Start(() => + { + Console.Write("Working away"); + for (int i = 0; i < 10; i++) + { + Thread.Sleep(100); + Console.Write("."); + } + return "Published value"; + }); + + start.Subscribe( + Console.WriteLine, + () => Console.WriteLine("Action completed")); +} +``` + +Note the difference between `Observable.Start` and `Observable.Return`. The `Start` method invokes our callback only upon subscription, so it is an example of a 'lazy' operation. Conversely, `Return` requires us to supply the value up front. + +The observable returned by `Start` may seem to have a superficial resemblance to `Task` or `Task` (depending on whether you use the `Action` or `Func` overload). Each represents work that may take some time before eventually completing, perhaps producing a result. However, there's a significant difference: `Start` doesn't begin the work until you subscribe to it. Moreover, it will re-execute the callback every time you subscribe to it. So it is more like a factory for a task-like entity. + +### From events + +As we discussed early in the book, .NET has a model for events that is baked into its type system. This predates Rx (not least because Rx wasn't feasible until .NET got generics in .NET 2.0) so it's common for types to support events but not Rx. To be able to integrate with the existing event model, Rx provides methods to take an event and turn it into an observable sequence. I showed this briefly in the file system watcher example earlier, but let's examine this in a bit more detail. There are several different varieties you can use. This show the most succinct form: + +```cs +FileSystemWatcher watcher = new (@"c:\incoming"); +IObservable> changeEvents = Observable + .FromEventPattern(watcher, nameof(watcher.Changed)); +``` + +If you have an object that provides an event, you can use this overload of `FromEventPattern`, passing in the object and the name of the event that you'd like to use with Rx. Although this is the simplest way to adapt events into Rx's world, it has a few problems. + +Firstly, why do I need to pass the event name as a string? Identifying members with strings is an error-prone technique. The compiler won't notice if there's a mismatch between the first and second argument (e.g., if I passed the arguments `(somethingElse, nameof(watcher.Changed))` by mistake). Couldn't I just pass `watcher.Changed` itself? Unfortunately not—this is an example of the issue I mentioned in the first chapter: .NET events are not first class citizens. We can't use them in the way we can use other objects or values. For example, we can't pass an event as an argument to a method. In fact the only thing you can do with a .NET event is attach and remove event handlers. If I want to get some other method to attach handlers to the event of my choosing (e.g., here I want Rx to handle the events), then the only way to do that is to specify the event's name so that the method (`FromEventPattern`) can then use reflection to attach its own handlers. + +This is a problem for some deployment scenarios. It is increasingly common in .NET to do extra work at build time to optimize runtime behaviour, and reliance on reflection can compromise these techniques. For example, instead of relying on Just In Time (JIT) compilation of code, we might use Ahead of Time (AOT) mechanisms. .NET's Ready to Run (R2R) system enables you to include pre-compiled code targeting specific CPU types alongside the normal IL, avoiding having to wait for .NET to compile the IL into runnable code. This can have a significant effect on startup times. In client side applications, it can fix problems where applications are sluggish when they first start up. It can also be important in server-side applications, especially in environments where code may be moved from one compute node to another fairly frequently, making it important to minimize cold start costs. There are also scenarios where JIT compilation is not even an option, in which case AOT compilation isn't merely an optimization: it's the only means by which code can run at all. + +The problem with reflection is that it makes it difficult for the build tools to work out what code will execute at runtime. When they inspect this call to `FromEventPattern` they will just see arguments of type `object` and `string`. It's not self-evident that this is going to result in reflection-driven calls to the `add` and `remove` methods for `FileSystemWatcher.Changed` at runtime. There are attributes that can be used to provide hints, but there are limits to how well these can work. Sometimes the build tools will be unable to determine what code would need to be AOT compiled to enable this method to execute without relying on runtime JIT. + +There's another, related problem. The .NET build tools support a feature called 'trimming', in which they remove unused code. The `System.Reactive.dll` file is about 1.3MB in size, but it would be a very unusual application that used every member of every type in that component. Basic use of Rx might need only a few tens of kilobytes. The idea with trimming is to work out which bits are actually in use, and produce a copy of the DLL that contains only that code. This can dramatically reduce the volume of code that needs to be deployed for an executable to run. This can be especially important in client-side Blazor applications, where .NET components end up being downloaded by the browser. Having to download an entire 1.3MB component might make you think twice about using it. But if trimming means that basic usage requires only a few tens of KB, and that the size would increase only if you were making more extensive use of the component, that can make it reasonable to use a component that would, without trimming, have imposed too large a penalty to justify its inclusion. But as with AOT compilation, trimming can only work if the tools can determine which code is in use. If they can't do that, it's not just a case of falling back to a slower path, waiting while the relevant code gets JIT compiler. If code has been trimmed, it will be unavailable at runtime, and your application might crash with a `MissingMethodException`. + +So reflection-based APIs can be problematic if you're using any of these techniques. Fortunately, there's an alternative. We can use an overload that takes a couple of delegates, and Rx will invoke these when it wants to add or remove handlers for the event: + +```cs +IObservable> changeEvents = Observable + .FromEventPattern( + h => watcher.Changed += h, + h => watcher.Changed -= h); +``` + +This is code that AOT and trimming tools can understand easily. We've written methods that explicitly add and remove handlers for the `FileSystemWatcher.Changed` event, so AOT tools can pre-compile those two methods, and trimming tools know that they cannot remove the add and remove handlers for those events. + +The downside is that this is a pretty cumbersome bit of code to write. If you've not already bought into the idea of using Rx, this might well be enough to make you think "I'll just stick with ordinary .NET events, thanks. But the cumbersome nature is a symptom of what is wrong with .NET events. We wouldn't have had to write anything so ugly if events had been first class citizens in the first place. + +Not only has that second-class status meant we couldn't just pass the event itself as an argument, it has also meant that we've had to state type arguments explicitly. The relationship between an event's delegate type (`FileSystemEventHandler` in this example) and its event argument type (`FileSystemEventArgs` here) is, in general, not something that C#'s type inference can determine automatically, which is why we've had to specify both types explicitly. (Events that use the generic `EventHandler` type are more amenable to type inference, and can use a slightly less verbose version of `FromEventPattern`. Unfortunately, relatively few events actually use that. Some events provide no information besides the fact that something just happened, and use the base `EventHandler` type, and for those kinds of events, you can in fact omit the type arguments completely, making the code slightly less ugly. You still need to provide the add and remove callbacks though.) + +Notice that the return type of `FromEventPattern` in this example is `IObservable>`. The `EventPattern` type encapsulates the information that the event passes to handlers. Most .NET events follow a common pattern in which handler methods take two arguments: an `object sender`, which just tells you which object raised the event (useful if you attach one event handler to multiple objects) and then a second argument of some type derived from `EventArgs` that provides information about the event. `EventPattern` just packages these two arguments into a single object that offers `Sender` and `EventArgs` properties. In cases where you don't in fact want to attach one handler to multiple sources, you only really need that `EventArgs` property, which is why the earlier `FileSystemWatcher` examples went on to extract just that, to get a simpler result of type `IObservable`. It did this with the `Select` operator, which we'll get to in more detail later: + +```cs +IObservable changes = changeEvents.Select(ep => ep.EventArgs); +``` + +It is very common to want to expose property changed events as observable sequences. The .NET runtime libraries define a .NET-event-based interface for advertising property changes, `INotifyPropertyChanged`, and some user interface frameworks have more specialized systems for this, such as WPF's `DependencyProperty`. If you are contemplating writing your own wrappers to do this sort of thing, I would strongly suggest looking at the [Reactive UI libraries](https://github.com/reactiveui/ReactiveUI/) first. It has a set of [features for wrapping properties as `IObservable`](https://www.reactiveui.net/docs/handbook/when-any/). + +### From Task + +The `Task` and `Task` types are very widely used in .NET. Mainstream .NET languages have built-in support for working with them (e.g., C#'s `async` and `await` keywords). There's some conceptual overlap between tasks and `IObservable`: both represent some sort of work that might take a while to complete. There is a sense in which an `IObservable` is a generalization of a `Task`: both represent potentially long-running work, but an `IObservable` can produce multiple results whereas `Task` can produce just one. + +Since `IObservable` is the more general abstraction, we should be able to represent a `Task` as an `IObservable`. Rx defines various extension methods for `Task` and `Task` to do this. These methods are all called `ToObservable()`, and it offers various overloads offering control of the details where required, and simplicity for the most common scenarios. + +Although they are conceptually similar, `Task` does a few things differently in the details. For example, you can retrieve its [`Status` property](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.status), which might report that it is in a cancelled or faulted state. `IObservable` doesn't provide a way to ask a source for its state; it just tells you things. So `ToObservable` makes some decisions about how to present status in a way that makes makes sense in an Rx world: + +- if the task is [Cancelled](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskstatus#system-threading-tasks-taskstatus-canceled), `IObservable` invokes a subscriber's `OnError` passing a `TaskCanceledException` +- if the task is [Faulted](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskstatus#system-threading-tasks-taskstatus-faulted) `IObservable` invokes a subscriber's `OnError` passing the task's inner exception +- if the task is not yet in a final state (neither [Cancelled](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskstatus#system-threading-tasks-taskstatus-canceled), [Faulted](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskstatus#system-threading-tasks-taskstatus-faulted), or [RanToCompletion](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskstatus#system-threading-tasks-taskstatus-rantocompletion)), the `IObservable` will not produce any notifications until such time as the task does enter one of these final states + +It does not matter whether the task is already in a final state at the moment that you call `ToObservable`. If it has finished, `ToObservable` will just return a sequence representing that state. (In fact, it uses either the `Return` or `Throw` creation methods you saw earlier.) If the task has not yet finished, `ToObservable` will attach a continuation to the task to detect the outcome once it does complete. + +Tasks come in two forms: `Task`, which produces a result, and `Task`, which does not. But in Rx, there is only `IObservable`—there isn't a no-result form. We've already seen this problem once before, when the `Observable.Start` method needed to be able to [adapt a delegate as an `IObservable`](#from-delegates) even when the delegate was an `Action` that produced no result. The solution was to return an `IObservable`, and that's also exactly what you get when you call `ToObservable` on a plain `Task`. + +The extension method is simple to use: + +```csharp +Task t = Task.Run(() => +{ + Console.WriteLine("Task running..."); + return "Test"; +}); +IObservable source = t.ToObservable(); +source.Subscribe( + Console.WriteLine, + () => Console.WriteLine("completed")); +source.Subscribe( + Console.WriteLine, + () => Console.WriteLine("completed")); +``` + +Here's the output. + +``` +Task running... +Test +completed +Test +completed +``` + +Notice that even with two subscribers, the task runs only once. That shouldn't be surprising since we only created a single task. If the task has not yet finished, then all subscribers will receive the result when it does. If the task has finished, the `IObservable` effectively becomes a single-value cold observable. + +#### One Task per subscription + +There's a different way to get an `IObservable` for a source. I can replace the first statement in the preceding example with this: + +```cs +IObservable source = Observable.FromAsync(() => Task.Run(() => +{ + Console.WriteLine("Task running..."); + return "Test"; +})); +``` + +Subscribing twice to this produces slightly different output: + +``` +Task running... +Task running... +Test +Test +completed +completed +``` + +Notice that this executes the task twice, once for each call to `Subscribe`. `FromAsync` can do this because instead of passing a `Task` we pass a callback that returns a `Task`. It calls that when we call `Subscribe`, so each subscriber essentially gets their own task. + +If I want to use `async` and `await` to define my task, then I don't need to bother with the `Task.Run` because an `async` lambda creates a `Func>`, which is exactly the type `FromAsync` wants: + +```cs +IObservable source = Observable.FromAsync(async () => +{ + Console.WriteLine("Task running..."); + await Task.Delay(50); + return "Test"; +}); +``` + +This produces exactly the same output as before. There is a subtle difference with this though. When I used `Task.Run` the lambda ran on a task pool thread from the start. But when I write it this way, the lambda will begin to run on whatever thread calls `Subscribe`. It's only when it hits the first `await` that it returns (and the call to `Subscribe` will then return), with the remainder of the method running on the thread pool. + +### From `IEnumerable` + +Rx defines another extension method called `ToObservable`, this time for `IEnumerable`. In earlier chapters I described how `IObservable` was designed to represent the same basic abstraction as `IEnumerable`, with the only difference being the mechanism we use to obtain the elements in the sequence: with `IEnumerable`, we write code that _pulls_ values out of the collection (e.g., a `foreach` loop), whereas `IObservable` _pushes_ values to us by invoking `OnNext` on our `IObserver`. + +We could write code that bridges from _pull_ to _push_: + +```csharp +// Example code only - do not use! +public static IObservable ToObservableOversimplified(this IEnumerable source) +{ + return Observable.Create(o => + { + foreach (var item in source) + { + o.OnNext(item); + } + + o.OnComplete(); + + // Incorrectly ignoring unsubscription. + return Disposable.Empty; + }); +} +``` + +This crude implementation conveys the basic idea, but it is naive. It does not attempt to handle unsubscription, and it's not easy to fix that when using `Observable.Create` for this particular scenario. And as we will see later in the book, Rx sources that might try to deliver large numbers of events in quick succession should integrate with Rx's concurrency model. The implementation that Rx supplies does of course cater for all of these tricky details. That makes it rather more complex, but that's Rx's problem; you can think of it as being logically equivalent to the code shown above, but without the shortcomings. + +In fact this is a recurring theme throughout Rx.NET. Many of the built-in operators are useful not because they do something particularly complicated, but because they deal with many subtle and tricky issues for you. You should always try to find something built into Rx.NET that does what you need before considering rolling your own solution. + +When transitioning from `IEnumerable` to `IObservable`, you should carefully consider what you are really trying to achieve. Consider that the blocking synchronous (pull) nature of `IEnumerable` does always not mix well with the asynchronous (push) nature of `IObservable`. As soon as something subscribes to an `IObservable` created in this way, it is effectively asking to iterate over the `IEnumerable`, immediately producing all of the values. The call to `Subscribe` might not return until it has reached the end of the `IEnumerable`, making it similar to the very simple example shown [at the start of this chapter](#a-very-basic-iobservablet-implementation). (I say "might" because as we'll see when we get to schedulers, the exact behaviour depends on the context.) `ToObservable` can't work magic—something somewhere has to execute what amounts to a `foreach` loop. + +So although this can be a convenient way to bring sequences of data into an Rx world, you should carefully test and measure the performance impact. + +### From APM + +Rx provides support for the ancient [.NET Asynchronous Programming Model (APM)](https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/asynchronous-programming-model-apm). Back in .NET 1.0, this was the only pattern for representing asynchronous operations. It was superseded in 2010 when .NET 4.0 introduced the [Task-based Asynchronous Pattern (TAP)](https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap). The old APM offers no benefits over the TAP. Moreover, C#'s `async` and `await` keywords (and equivalents in other .NET languages) only support the TAP, meaning that the APM is best avoided. However, the TAP was fairly new back in 2011 when Rx 1.0 was released, so it offered adapters for presenting an APM implementation as an `IObservable`. + +Nobody should be using the APM today, but for completeness (and just in case you have to use an ancient library that only offers the APM) I will provide a very brief explanation of Rx's support for it. + +The result of the call to `Observable.FromAsyncPattern` does _not_ return an observable sequence. It returns a delegate that returns an observable sequence. (So it is essentially a factory factory) The signature for this delegate will match the generic arguments of the call to `FromAsyncPattern`, except that the return type will be wrapped in an observable sequence. The following example wraps the `Stream` class's `BeginRead`/`EndRead` methods (which are an implementation of the APM). + +**Note**: this is purely to illustrate how to wrap the APM. You would never do this in practice because `Stream` has supported the TAP for years. + +```csharp +Stream stream = GetStreamFromSomewhere(); +var fileLength = (int) stream.Length; + +Func> read = Observable.FromAsyncPattern( + stream.BeginRead, + stream.EndRead); +var buffer = new byte[fileLength]; +IObservable bytesReadStream = read(buffer, 0, fileLength); +bytesReadStream.Subscribe(byteCount => +{ + Console.WriteLine("Number of bytes read={0}, buffer should be populated with data now.", byteCount); +}); +``` + +## Subjects + +So far, this chapter has explored various factory methods that return `IObservable` implementations. There is another way though: `System.Reactive` defines various types that implement `IObservable` that we can instantiate directly. But how do we determine what values these types produce? We're able to do that because they also implement `IObserver`, enabling us to push values into them, and those very same values we push in will be the ones seen by observers. + +Types that implement both `IObservable` and `IObserver` are called _subjects_ in Rx. There's an an `ISubject` to represent this. (This is in the `System.Reactive` NuGet package, unlike `IObservable` and `IObserver`, which are both built into the .NET runtime libraries.) `ISubject` looks like this: + +```cs +public interface ISubject : ISubject +{ +} +``` + +So it turns out there's also a two-argument `ISubject` to accommodate the fact that something that is both an observer and an observable might transform the data that flows through it in some way, meaning that the input and output types are not necessarily the same. Here's the two-type-argument definition: + +```cs +public interface ISubject : IObserver, IObservable +{ +} +``` + +As you can see the `ISubject` interfaces don't define any members of their own. They just inherit from `IObserver` and `IObservable`—these interfaces are nothing more than a direct expression of the fact that a subject is both an observer and an observable. + +But what is this for? You can think of `IObserver` and the `IObservable` as the 'consumer' and 'publisher' interfaces respectively. A subject, then is both a consumer and a publisher. Data flows both into and out of a subject. + +Rx offers a few subject implementations that can occasionally be useful in code that wants to make an `IObservable` available. Although `Observable.Create` is usually the preferred way to do this, there's one important case where a subject might make more sense: if you have some code that discovers events of interest (e.g., by using the client API for some messaging technology) and wants to make them available through an `IObservable`, subjects can sometimes provide a more convenient way to to this than with `Observable.Create` or a custom implementation. + +Rx offers a few subject types. We'll start with the most straightforward one to understand. + +### `Subject` + +The `Subject` type immediately forwards any calls made to its `IObserver` methods on to all of the observers currently subscribed to it. This example shows its basic operation: + +```cs +Subject s = new(); +s.Subscribe(x => Console.WriteLine($"Sub1: {x}")); +s.Subscribe(x => Console.WriteLine($"Sub2: {x}")); + +s.OnNext(1); +s.OnNext(2); +s.OnNext(3); +``` + +I've created a `Subject`. I've subscribed to it twice, and then called its `OnNext` method repeatedly. This produces the following output, illustrating that the `Subject` forwards each `OnNext` call onto both subscribers: + +``` +Sub1: 1 +Sub2: 1 +Sub1: 2 +Sub2: 2 +Sub1: 3 +Sub2: 3 +``` + +We could use this as a way to bridge between some API from which we receive data into the world of Rx. You could imagine writing something of this kind: + +```cs +public class MessageQueueToRx : IDisposable +{ + private readonly Subject messages = new(); + + public IObservable Messages => messages; + + public void Run() + { + while (true) + { + // Receive a message from some hypothetical message queuing service + string message = MqLibrary.ReceiveMessage(); + messages.OnNext(message); + } + } + + public void Dispose() + { + message.Dispose(); + } +} +``` + +It wouldn't be too hard to modify this to use `Observable.Create` instead. But where this approach can become easier is if you need to provide multiple different `IObservable` sources. Imagine we distinguish between different message types based on their content, and publish them through different observables. That's hard to arrange with `Observable.Create` if we still want a single loop pulling messages off the queue. + +`Subject` also distributes calls to either `OnCompleted` or `OnError` to all subscribers. Of course, the rules of Rx require that once you have called either of these methods on an `IObserver` (and any `ISubject` is an `IObserver`, so this rule applies to `Subject`) you must not call `OnNext`, `OnError`, or `OnComplete` on that observer ever again. In fact, `Subject` will tolerate calls that break this rule—it just ignores them, so even if your code doesn't quite stick to these rules internally, the `IObservable` you present to the outside world will behave correctly, because Rx enforces this. + +`Subject` implements `IDisposable`. Disposing a `Subject` puts it into a state where it will throw an exception if you call any of its methods. The documentation also describes it as unsubscribing all observers, but since a disposed `Subject` isn't capable of producing any further notifications in any case, this doesn't really mean much. (Note that it does _not_ call `OnCompleted` on its observers when you `Dispose` it.) The one practical effect is that its internal field that keeps track of observers is reset to a special sentinel value indicating that it has been disposed, meaning that the one externally observable effect of "unsubscribing" the observers is that if, for some reason, your code held onto a reference to a `Subject` after disposing it, that would no longer keep all the subscribers reachable for GC purposes. If a `Subject` remains reachable indefinitely after it is no longer in use, that in itself is effectively a memory leak, but disposal would at least limit the effects: only the `Subject` itself would remain reachable, and not all of its subscribers. + +`Subject` is the most straightforward subject, but there are other, more specialized ones. + +## `ReplaySubject` + +`Subject` does not remember anything: it immediately distributes incoming values to subscribers. If new subscribers come along, they will only see events that occur after they subscribe. `ReplaySubject`, on the other hand, can remember every value it has ever seen. If a new subject comes along, it will receive the complete history of events so far. + +This is a variation on the first example in the preceding [Subject section](#subject). It creates a `ReplaySubject` instead of a `Subject`. And instead of immediately subscribing twice, it creates an initial subscription, and then a second one only after a couple of values have been emitted. + +```cs +ReplaySubject s = new(); +s.Subscribe(x => Console.WriteLine($"Sub1: {x}")); + +s.OnNext(1); +s.OnNext(2); + +s.Subscribe(x => Console.WriteLine($"Sub2: {x}")); + +s.OnNext(3); +``` + +This produces the following output: + +``` +Sub1: 1 +Sub1: 2 +Sub2: 1 +Sub2: 2 +Sub1: 3 +Sub2: 3 +``` + +As you'd expect, we initially see output only from `Sub1`. But when we make the second call to subscribe, we can see that `Sub2` also received the first two values. And then when we report the third value, both see it. If this example had used `Subject` instead, we would have seen just this output: + +``` +Sub1: 1 +Sub1: 2 +Sub1: 3 +Sub2: 3 +``` + +There's an obvious potential problem here: if `ReplaySubject` remembers every value published to it, we mustn't use it with endless event sources, because it will eventually cause us to run out of memory. + +`ReplaySubject` offers constructors that accept simple cache expiry settings that can limit memory consumption. One option is to specify the maximum number of item to remember. This next example creates a `ReplaySubject` with a buffer size of 2: + +```csharp +ReplaySubject s = new(2); +s.Subscribe(x => Console.WriteLine($"Sub1: {x}")); + +s.OnNext(1); +s.OnNext(2); +s.OnNext(3); + +s.Subscribe(x => Console.WriteLine($"Sub2: {x}")); + +s.OnNext(4); +``` + +Since the second subscription only comes along after we've already produced 3 values, it no longer sees all of them. It only receives the last two values published prior to subscription (but the first subscription continues to see everything of course): + +``` +Sub1: 1 +Sub1: 2 +Sub1: 3 +Sub2: 2 +Sub2: 3 +Sub1: 4 +Sub2: 4 +``` + +Alternatively, you can specify a time-based limit by passing a `TimeSpan` to the `ReplaySubject` constructor. + +## `BehaviorSubject` + +Like `ReplaySubject`, `BehaviorSubject` also has a memory, but it remembers exactly one value. However, it's not quite the same as a `ReplaySubject` with a buffer size of 1. Whereas a `ReplaySubject` starts off in a state where it has nothing in its memory, `BehaviorSubject` always remembers _exactly_ one item. How can that work before we've made our first call to `OnNext`? `BehaviorSubject` enforces this by requiring us to supply the initial value when we construct it. + +So you can think of `BehaviorSubject` as a subject that _always_ has a value available. If you subscribe to a `BehaviorSubject` it will instantly produce a single value. (It may then go on to produce more values, but it always produces one right away.) As it happens, it also makes that value available through a property called `Value`, so you don't need to subscribe an `IObserver` to it just to retrieve the value. + +A `BehaviorSubject` could be thought of an as observable property. Like a normal property, it can immediately supply a value whenever you ask it. The difference is that it can then go on to notify you every time its value changes. If you're using the [ReactiveUI framework](https://www.reactiveui.net/) (an Rx-based framework for building user interfaces), `BehaviourSubject` can make sense as the implementation type for a property in a view model (the type that mediates between your underlying domain model and your user interface). It has property-like behaviour, enabling you to retrieve a value at any time, but it also provides change notifications, which ReactiveUI can handle in order to keep the UI up to date. + +This analogy falls down slightly when it comes to completion. If you call `OnCompleted`, it immediately calls `OnCompleted` on all of its observers, and if any new observers subscribe, they will also immediately be completed—it does not first supply the last value. (So this is another way in which it is different from a `ReplaySubject` with a buffer size of 1.) + +Similarly, if you call `OnError`, all current observers will receive an `OnError` call, and any subsequent subscribers will also receive nothing but an `OnError` call. + +## `AsyncSubject` + +`AsyncSubject` provides all observers with the final value it receives. Since it can't know which is the final value until `OnCompleted` is called, it will not invoke any methods on any of its subscribers until either its `OnCompleted` or `OnError` method is called. (If `OnError` is called, it just forwards that to all current and future subscribers.) You will often use this subject indirectly, because it is the basis of [Rx's integration with the `await` keyword](13_LeavingIObservable.md#integration-with-async-and-await). (When you `await` an observable sequence, the `await` returns the final value emitted by the source.) + +If no calls were made to `OnNext` before `OnCompleted` then there was no final value, so it will just complete any observers without providing a value. + +In this example no values will be published as the sequence never completes. +No values will be written to the console. + +```csharp +AsyncSubject subject = new(); +subject.OnNext("a"); +subject.Subscribe(x => Console.WriteLine($"Sub1: {x}")); +subject.OnNext("b"); +subject.OnNext("c"); +``` + +In this example we invoke the `OnCompleted` method so there will be a final value ('c') for the subject to produce: + +```csharp +AsyncSubject subject = new(); + +subject.OnNext("a"); +subject.Subscribe(x => Console.WriteLine($"Sub1: {x}")); +subject.OnNext("b"); +subject.OnNext("c"); +subject.OnCompleted(); +subject.Subscribe(x => Console.WriteLine($"Sub2: {x}")); +``` + +This produces the following output: + +``` +Sub1: c +Sub2: c +``` + +If you have some potentially slow work that needs to be done when your application starts up, and which needs to be done just once, you might choose an `AsyncSubject` to make the results of that work available. Code requiring those results can subscribe to the subject. If the work is not yet complete, they will receive the results as soon as they are available. And if the work has already completed, they will receive it immediately. + +## Subject factory + +Finally it is worth making you aware that you can also create a subject via a factory method. Considering that a subject combines the `IObservable` and `IObserver` interfaces, it seems sensible that there should be a factory that allows you to combine them yourself. The `Subject.Create(IObserver, IObservable)` factory method provides just this. + +```csharp +//Creates a subject from the specified observer used to publish messages to the subject +// and observable used to subscribe to messages sent from the subject +public static ISubject Create( + IObserver observer, + IObservable observable) +{...} +``` + +Note that unlike all of the other subjects just discussed, this creates a subject where there is no inherent relationship between the input and the output. This just takes whatever `IObserver` and `IObserver` implementations you supply and wraps them up in a single object. All calls made to the subject's `IObserver` methods will be passed directly to the observer you supplied. If you want values to emerge to subscribers to the corresponding `IObservable`, it's up to you to make that happen. This really combines the two objects you supply with the absolute minimum of glue. + +Subjects provide a convenient way to poke around Rx, and are occasionally useful in production scenarios, but they are not recommended for most cases. An explanation is in the [Usage Guidelines appendix](C_UsageGuidelines.md). Instead of using subjects, favour the factory methods shown earlier in this chapter.. + +## Summary + +We have looked at the various eager and lazy ways to create a sequence. We have seen how to produce timer based sequences using the various factory methods. And we've also explored ways to transition from other synchronous and asynchronous representations. + +As a quick recap: + +- Factory Methods + - Observable.Return + - Observable.Empty + - Observable.Never + - Observable.Throw + - Observable.Create + - Observable.Defer + +- Generative methods + - Observable.Range + - Observable.Generate + - Observable.Interval + - Observable.Timer + +- Adaptation + - Observable.Start + - Observable.FromEventPattern + - Task.ToObservable + - Task<T>.ToObservable + - IEnumerable<T>.ToObservable + - Observable.FromAsyncPattern + +Creating an observable sequence is our first step to practical application of Rx: create the sequence and then expose it for consumption. Now that we have a firm grasp on how to create an observable sequence, we can look in more detail at the operators that allow us to describe processing to be applied, to build up more complex observable sequences. \ No newline at end of file diff --git a/content/03_LifetimeManagement.md b/content/03_LifetimeManagement.md deleted file mode 100644 index bb7f368..0000000 --- a/content/03_LifetimeManagement.md +++ /dev/null @@ -1,312 +0,0 @@ ---- -title: Lifetime management ---- - -# Lifetime management - -The very nature of Rx code is that you as a consumer do not know when a sequence will provide values or terminate. This uncertainty does not prevent your code from providing a level of certainty. You can control when you will start accepting values and when you choose to stop accepting values. You still need to be the master of your domain. Understanding the basics of managing Rx resources allow your applications to be as efficient, bug free and predictable as possible. - -Rx provides fine grained control to the lifetime of subscriptions to queries. While using familiar interfaces, you can deterministically release resources associated to queries. This allows you to make the decisions on how to most effectively manage your resources, ideally keeping the scope as tight as possible. - -In the previous chapter we introduced you to the key types and got off the ground with some examples. For the sake of keeping the initial samples simple we ignored a very important part of the `IObservable` interface. The `Subscribe` method takes an `IObserver` parameter, but we did not need to provide that as we used the extension method that took an `Action` instead. The important part we overlooked is that both `Subscribe` methods have a return value. The return type is `IDisposable`. In this chapter we will further explore how this return value can be used to management lifetime of our subscriptions. - -## Subscribing - -Just before we move on, it is worth briefly looking at all of the overloads of the `Subscribe` extension method. The overload we used in the previous chapter was the simple [Overload to Subscribe](http://msdn.microsoft.com/en-us/library/ff626574(v=VS.92).aspx "Subscribe Extension method overloads on MSDN") which allowed us to pass just an `Action` to be performed when `OnNext` was invoked. Each of these further overloads allows you to avoid having to create and then pass in an instance of `IObserver`. - -```csharp -// Just subscribes to the Observable for its side effects. -// All OnNext and OnCompleted notifications are ignored. -// OnError notifications are re-thrown as Exceptions. -IDisposable Subscribe(this IObservable source); - -// The onNext Action provided is invoked for each value. -// OnError notifications are re-thrown as Exceptions. -IDisposable Subscribe(this IObservable source, Action onNext); - -// The onNext Action is invoked for each value. -// The onError Action is invoked for errors -IDisposable Subscribe(this IObservable source, Action onNext, Action onError); - -// The onNext Action is invoked for each value. -// The onCompleted Action is invoked when the source completes. -// OnError notifications are re-thrown as Exceptions. -IDisposable Subscribe(this IObservable source, Action onNext, Action onCompleted); - -// The complete implementation -IDisposable Subscribe(this IObservable source, Action onNext, Action onError, Action onCompleted); -``` - -Each of these overloads allows you to pass various combinations of delegates that you want executed for each of the notifications an `IObservable` instance could produce. A key point to note is that if you use an overload that does not specify a delegate for the `OnError` notification, any `OnError` notifications will be re-thrown as an exception. Considering that the error could be raised at any time, this can make debugging quite difficult. It is normally best to use an overload that specifies a delegate to cater for `OnError` notifications. - -In this example we attempt to catch error using standard .NET Structured Exception Handling: - -```csharp -var values = new Subject(); -try -{ - values.Subscribe(value => Console.WriteLine("1st subscription received {0}", value)); -} -catch (Exception ex) -{ - Console.WriteLine("Won't catch anything here!"); -} - -values.OnNext(0); - -//Exception will be thrown here causing the app to fail. -values.OnError(new Exception("Dummy exception")); -``` - -The correct way to way to handle exceptions is to provide a delegate for `OnError` notifications as in this example. - -```csharp -var values = new Subject(); - -values.Subscribe( - value => Console.WriteLine("1st subscription received {0}", value), - ex => Console.WriteLine("Caught an exception : {0}", ex)); - -values.OnNext(0); -values.OnError(new Exception("Dummy exception")); -``` - -We will look at other interesting ways to deal with errors on a sequence in later chapters in the book. - -## Unsubscribing - -We have yet to look at how we could unsubscribe from a subscription. If you were to look for an _Unsubscribe_ method in the Rx public API you would not find any. Instead of supplying an Unsubscribe method, Rx will return an `IDisposable` whenever a subscription is made. This disposable can be thought of as the subscription itself, or perhaps a token representing the subscription. Disposing it will dispose the subscription and effectively `unsubscribe`. Note that calling `Dispose` on the result of a Subscribe call will not cause any side effects for other subscribers; it just removes the subscription from the observable's internal list of subscriptions. This then allows us to call `Subscribe` many times on a single `IObservable`, allowing subscriptions to come and go without affecting each other. In this example we initially have two subscriptions, we then dispose of one subscription early which still allows the other to continue to receive publications from the underlying sequence: - -```csharp -var values = new Subject(); -var firstSubscription = values.Subscribe(value => Console.WriteLine("1st subscription received {0}", value)); -var secondSubscription = values.Subscribe(value => Console.WriteLine("2nd subscription received {0}", value)); -values.OnNext(0); -values.OnNext(1); -values.OnNext(2); -values.OnNext(3); -firstSubscription.Dispose(); -Console.WriteLine("Disposed of 1st subscription"); -values.OnNext(4); -values.OnNext(5); -``` - -Output: - -``` -1st subscription received 0 -2nd subscription received 0 -1st subscription received 1 -2nd subscription received 1 -1st subscription received 2 -2nd subscription received 2 -1st subscription received 3 -2nd subscription received 3 -Disposed of 1st subscription -2nd subscription received 4 -2nd subscription received 5 -``` - -The team building Rx could have created a new interface like _ISubscription_ or _IUnsubscribe_ to facilitate unsubscribing. They could have added an _Unsubscribe_ method to the existing `IObservable` interface. By using the `IDisposable` type instead we get the following benefits for free: - -- The type already exists -- People understand the type -- `IDisposable` has standard usages and patterns -- Language support via the `using` keyword -- Static analysis tools like FxCop can help you with its usage -- The `IObservable` interface remains very simple. - -As per the `IDisposable` guidelines, you can call `Dispose` as many times as you like. The first call will unsubscribe and any further calls will do nothing as the subscription will have already been disposed. - -## OnError and OnComplete - -Both the `OnError` and `OnCompleted` signify the completion of a sequence. If your sequence publishes an `OnError` or `OnCompleted` it will be the last publication and no further calls to `OnNext` can be performed. In this example we try to publish an `OnNext` call after an `OnCompleted` and the `OnNext` is ignored: - -```csharp -var subject = new Subject(); -subject.Subscribe( - Console.WriteLine, - () => Console.WriteLine("Completed")); -subject.OnCompleted(); -subject.OnNext(2); -``` - -Of course, you could implement your own `IObservable` that allows publishing after an `OnCompleted` or an `OnError`, however it would not follow the precedence of the current Subject types and would be a non-standard implementation. I think it would be safe to say that the inconsistent behavior would cause unpredictable behavior in the applications that consumed your code. - -An interesting thing to consider is that when a sequence completes or errors, you should still dispose of your subscription. - -## IDisposable - -The `IDisposable` interface is a handy type to have around and it is also integral to Rx. I like to think of types that implement `IDisposable` as having explicit lifetime management. I should be able to say "I am done with that" by calling the `Dispose()` method. - -By applying this kind of thinking, and then leveraging the C# `using` statement, you can create handy ways to create scope. As a reminder, the `using` statement is effectively a `try`/`finally` block that will always call `Dispose` on your instance when leaving the scope. - -If we consider that we can use the `IDisposable` interface to effectively create a scope, you can create some fun little classes to leverage this. For example here is a simple class to log timing events: - -```csharp -public class TimeIt : IDisposable -{ - private readonly string _name; - private readonly Stopwatch _watch; - - public TimeIt(string name) - { - _name = name; - _watch = Stopwatch.StartNew(); - } - - public void Dispose() - { - _watch.Stop(); - Console.WriteLine("{0} took {1}", _name, _watch.Elapsed); - } -} -``` - -This handy little class allows you to create scope and measure the time certain sections of your code base take to run. -You could use it like this: - -```csharp -using (new TimeIt("Outer scope")) -{ - using (new TimeIt("Inner scope A")) - { - DoSomeWork("A"); - } - using (new TimeIt("Inner scope B")) - { - DoSomeWork("B"); - } - Cleanup(); -} -``` - -Output: - -``` -Inner scope A took 00:00:01.0000000 -Inner scope B took 00:00:01.5000000 -Outer scope took 00:00:02.8000000 -``` - -You could also use the concept to set the color of text in a console application: - -```csharp -// Creates a scope for a console foreground color. When disposed, will return to -// the previous Console.ForegroundColor -public class ConsoleColor : IDisposable -{ - private readonly System.ConsoleColor _previousColor; - - public ConsoleColor(System.ConsoleColor color) - { - _previousColor = Console.ForegroundColor; - Console.ForegroundColor = color; - } - - public void Dispose() - { - Console.ForegroundColor = _previousColor; - } -} -``` - -I find this handy for easily switching between colors in little _spike_ console applications: - -```csharp -Console.WriteLine("Normal color"); -using (new ConsoleColor(System.ConsoleColor.Red)) -{ - Console.WriteLine("Now I am Red"); - using (new ConsoleColor(System.ConsoleColor.Green)) - { - Console.WriteLine("Now I am Green"); - } - Console.WriteLine("and back to Red"); -} -``` - -Output: - - -
Normal color
-
Now I am Red
-
Now I am Green
-
and back to Red
-
- -So we can see that you can use the `IDisposable` interface for more than just common use of deterministically releasing unmanaged resources. It is a useful tool for managing lifetime or scope of anything; from a stopwatch timer, to the current color of the console text, to the subscription to a sequence of notifications. - -The Rx library itself adopts this liberal usage of the `IDisposable` interface and introduces several of its own custom implementations: - -- Disposable -- BooleanDisposable -- CancellationDisposable -- CompositeDisposable -- ContextDisposable -- MultipleAssignmentDisposable -- RefCountDisposable -- ScheduledDisposable -- SerialDisposable -- SingleAssignmentDisposable - -For a full rundown of each of the implementations see the [Disposables](20_Disposables.html) reference in the Appendix. For now we will look at the extremely simple and useful `Disposable` static class: - -```csharp -namespace System.Reactive.Disposables -{ - public static class Disposable - { - // Gets the disposable that does nothing when disposed. - public static IDisposable Empty { get {...} } - - // Creates the disposable that invokes the specified action when disposed. - public static IDisposable Create(Action dispose) - {...} - } -} -``` - -As you can see it exposes two members: `Empty` and `Create`. The `Empty` method allows you get a stub instance of an `IDisposable` that does nothing when `Dispose()` is called. This is useful for when you need to fulfil an interface requirement that returns an `IDisposable` but you have no specific implementation that is relevant. - -The other overload is the `Create` factory method which allows you to pass an `Action` to be invoked when the instance is disposed. The `Create` method will ensure the standard Dispose semantics, so calling `Dispose()` multiple times will only invoke the delegate you provide once: - -```csharp -var disposable = Disposable.Create(() => Console.WriteLine("Being disposed.")); -Console.WriteLine("Calling dispose..."); -disposable.Dispose(); -Console.WriteLine("Calling again..."); -disposable.Dispose(); -``` - -Output: - -``` -Calling dispose... -Being disposed. -Calling again... -``` - -Note that "Being disposed." is only printed once. In a later chapter we cover another useful method for binding the lifetime of a resource to that of a subscription in the [Observable.Using](11_AdvancedErrorHandling.html#Using) method. - - - -## Resource management vs. memory management - -It seems many .NET developers only have a vague understanding of the .NET runtime's Garbage Collector and specifically how it interacts with Finalizers and `IDisposable`. As the author of the [Framework Design Guidelines](http://msdn.microsoft.com/en-us/library/ms229042.aspx) points out, this may be due to the confusion between 'resource management' and 'memory management': - -> Many people who hear about the Dispose pattern for the first time complain that the GC isn't doing its job. They think it should collect resources, and that this is just like having to manage resources as you did in the unmanaged world. The truth is that the GC was never meant to manage resources. It was designed to manage memory and it is excellent in doing just that. - Krzysztof Cwalina from Joe Duffy's blog - -This is both a testament to Microsoft for making .NET so easy to work with and also a problem as it is a key part of the runtime to misunderstand. Considering this, I thought it was prudent to note that _subscriptions will not be automatically disposed of_. You can safely assume that the instance of `IDisposable` that is returned to you does not have a finalizer and will not be collected when it goes out of scope. If you call a `Subscribe` method and ignore the return value, you have lost your only handle to unsubscribe. The subscription will still exist, and you have effectively lost access to this resource, which could result in leaking memory and running unwanted processes. - -The exception to this cautionary note is when using the `Subscribe` extension methods. These methods will internally construct behavior that will _automatically detach_ subscriptions when the sequence completes or errors. Even with the automatic detach behavior; you still need to consider sequences that never terminate (by `OnCompleted` or `OnError`). You will need the instance of `IDisposable` to terminate the subscription to these infinite sequences explicitly. - -> You will find many of the examples in this book will not allocate the `IDisposable` return value. This is only for brevity and clarity of the sample. Usage guidelines and best practice information can be found in the appendix. - -By leveraging the common `IDisposable` interface, Rx offers the ability to have deterministic control over the lifetime of your subscriptions. Subscriptions are independent, so the disposable of one will not affect another. While some `Subscribe` extension methods utilize an automatically detaching observer, it is still considered best practice to explicitly manage your subscriptions, as you would with any other resource implementing `IDisposable`. As we will see in later chapters, a subscription may actually incur the cost of other resources such as event handles, caches and threads. It is also best practice to always provide an `OnError` handler to prevent an exception being thrown in an otherwise difficult to handle manner. - -With the knowledge of subscription lifetime management, you are able to keep a tight leash on subscriptions and their underlying resources. With judicious application of standard disposal patterns to your Rx code, you can keep your applications predictable, easier to maintain, easier to extend and hopefully bug free. \ No newline at end of file diff --git a/content/04_CreatingObservableSequences.md b/content/04_CreatingObservableSequences.md deleted file mode 100644 index ca3fe96..0000000 --- a/content/04_CreatingObservableSequences.md +++ /dev/null @@ -1,740 +0,0 @@ ---- -title : Creating a sequence ---- - -# PART 2 - Sequence basics - -So you want to get involved and write some Rx code, but how do you get started? We have looked at the key types, but know that we should not be creating our own implementations of `IObserver` or `IObservable` and should favor factory methods over using subjects. Even if we have an observable sequence, how do we pick out the data we want from it? We need to understand the basics of creating an observable sequence, getting values into it and picking out the values we want from them. - -In Part 2 we discover the basics for constructing and querying observable sequences. We assert that LINQ is fundamental to using and understanding Rx. On deeper inspection, we find that _functional programming_ concepts are core to having a deep understanding of LINQ and therefore enabling you to master Rx. To support this understanding, we classify the query operators into three main groups. Each of these groups proves to have a root operator that the other operators can be constructed from. Not only will this deconstruction exercise provide a deeper insight to Rx, functional programming and query composition; it should arm you with the ability to create custom operators where the general Rx operators do not meet your needs. - -# Creating a sequence - -In the previous chapters we used our first Rx extension method, the `Subscribe` method and its overloads. We also have seen our first factory method in `Subject.Create()`. We will start looking at the vast array of other methods that enrich `IObservable` to make Rx what it is. It may be surprising to see that there are relatively few public instance methods in the Rx library. There are however a large number of public static methods, and more specifically, a large number of extension methods. Due to the large number of methods and their overloads, we will break them down into categories. - -> Some readers may feel that they can skip over parts of the next few chapters. I would only suggest doing so if you are very confident with LINQ and functional composition. The intention of this book is to provide a step-by-step introduction to Rx, with the goal of you, the reader, being able to apply Rx to your software. -> The appropriate application of Rx will come through a sound understanding of the fundamentals of Rx. The most common mistakes people will make with Rx are due to a misunderstanding of the principles upon which Rx was built. With this in mind, I encourage you to read on. - -It seems sensible to follow on from our examination of our key types where we simply constructed new instances of subjects. Our first category of methods will be _creational_ methods: simple ways we can create instances of `IObservable` sequences. These methods generally take a seed to produce a sequence: either a single value of a type, or just the type itself. In functional programming this can be described as _anamorphism_ or referred to as an '_unfold_'. - -## Simple factory methods - -### Observable.Return - -In our first and most basic example we introduce `Observable.Return(T value)`. This method takes a value of `T` and returns an `IObservable` with the single value and then completes. It has _unfolded_ a value of `T` into an observable sequence. - -```csharp -var singleValue = Observable.Return("Value"); - -// which could have also been simulated with a replay subject -var subject = new ReplaySubject(); -subject.OnNext("Value"); -subject.OnCompleted(); -``` - -Note that in the example above that we could use the factory method or get the same effect by using the replay subject. The obvious difference is that the factory method is only one line and it allows for declarative over imperative programming style. In the example above we specified the type parameter as `string`, this is not necessary as it can be inferred from the argument provided. - -```csharp -singleValue = Observable.Return("Value"); -// Can be reduced to the following -singleValue = Observable.Return("Value"); -``` - -### Observable.Empty - -The next two examples only need the type parameter to unfold into an observable sequence. The first is `Observable.Empty()`. This returns an empty `IObservable` i.e. it just publishes an `OnCompleted` notification. - -```csharp -var empty = Observable.Empty(); -// Behaviorally equivalent to -var subject = new ReplaySubject(); -subject.OnCompleted(); -``` - -### Observable.Never - -The `Observable.Never()` method will return infinite sequence without any notifications. - -```csharp -var never = Observable.Never(); -// similar to a subject without notifications -var subject = new Subject(); -``` - -### Observable.Throw - -`Observable.Throw(Exception)` method needs the type parameter information, it also need the `Exception` that it will `OnError` with. This method creates a sequence with just a single `OnError` notification containing the exception passed to the factory. - -```csharp -var throws = Observable.Throw(new Exception()); -// Behaviorally equivalent to -var subject = new ReplaySubject(); -subject.OnError(new Exception()); -``` - -### Observable.Create - -The `Create` factory method is a little different to the above creation methods. -The method signature itself may be a bit overwhelming at first, but becomes quite natural once you have used it. - -```csharp -// Creates an observable sequence from a specified Subscribe method implementation. -public static IObservable Create( - Func, IDisposable> subscribe) -{...} -public static IObservable Create( - Func, Action> subscribe) -{...} -``` - -Essentially this method allows you to specify a delegate that will be executed anytime a subscription is made. The `IObserver` that made the subscription will be passed to your delegate so that you can call the `OnNext`/`OnError`/`OnCompleted` methods as you need. This is one of the few scenarios where you will need to concern yourself with the `IObserver` interface. Your delegate is a `Func` that returns an `IDisposable`. This `IDisposable` will have its `Dispose()` method called when the subscriber disposes from their subscription. - -The `Create` factory method is the preferred way to implement custom observable sequences. The usage of subjects should largely remain in the realms of samples and testing. Subjects are a great way to get started with Rx. They reduce the learning curve for new developers, however they pose several concerns that the `Create` method eliminates. Rx is effectively a functional programming paradigm. Using subjects means we are now managing state, which is potentially mutating. Mutating state and asynchronous programming are very hard to get right. Furthermore many of the operators (extension methods) have been carefully written to ensure correct and consistent lifetime of subscriptions and sequences are maintained. When you introduce subjects you can break this. Future releases may also see significant performance degradation if you explicitly use subjects. - -The `Create` method is also preferred over creating custom types that implement the `IObservable` interface. There really is no need to implement the observer/observable interfaces yourself. Rx tackles the intricacies that you may not think of such as thread safety of notifications and subscriptions. - -A significant benefit that the `Create` method has over subjects is that the sequence will be lazily evaluated. Lazy evaluation is a very important part of Rx. It opens doors to other powerful features such as scheduling and combination of sequences that we will see later. The delegate will only be invoked when a subscription is made. - -In this example we show how we might first return a sequence via standard blocking eagerly evaluated call, and then we show the correct way to return an observable sequence without blocking by lazy evaluation. - -```csharp -private IObservable BlockingMethod() -{ - var subject = new ReplaySubject(); - subject.OnNext("a"); - subject.OnNext("b"); - subject.OnCompleted(); - Thread.Sleep(1000); - - return subject; -} - -private IObservable NonBlocking() -{ - return Observable.Create( - (IObserver observer) => - { - observer.OnNext("a"); - observer.OnNext("b"); - observer.OnCompleted(); - Thread.Sleep(1000); - - return Disposable.Create(() => Console.WriteLine("Observer has unsubscribed")); - // or can return an Action like - // return () => Console.WriteLine("Observer has unsubscribed"); - }); -} -``` - -While the examples are somewhat contrived, the intention is to show that when a consumer calls the eagerly evaluated, blocking method, they will be blocked for at least 1 second before they even receive the `IObservable`, regardless of if they do actually subscribe to it or not. The non blocking method is lazily evaluated so the consumer immediately receives the `IObservable` and will only incur the cost of the thread sleep if they subscribe. - -As an exercise, try to build the `Empty`, `Return`, `Never` & `Throw` extension methods yourself using the `Create` method. If you have Visual Studio or [LINQPad](http://www.linqpad.net/) available to you right now, code it up as quickly as you can. If you don't (perhaps you are on the train on the way to work), try to conceptualize how you would solve this problem. When you are done move forward to see some examples of how it could be done... - -Examples of `Empty`, `Return`, `Never` and `Throw` recreated with `Observable.Create`: - -```csharp -public static IObservable Empty() -{ - return Observable.Create(o => - { - o.OnCompleted(); - return Disposable.Empty; - }); -} - -public static IObservable Return(T value) -{ - return Observable.Create(o => - { - o.OnNext(value); - o.OnCompleted(); - return Disposable.Empty; - }); -} - -public static IObservable Never() -{ - return Observable.Create(o => - { - return Disposable.Empty; - }); -} - -public static IObservable Throws(Exception exception) -{ - return Observable.Create(o => - { - o.OnError(exception); - return Disposable.Empty; - }); -} -``` - -You can see that `Observable.Create` provides the power to build our own factory methods if we wish. You may have noticed that in each of the examples we only are able to return our subscription token (the implementation of `IDisposable`) once we have produced all of our `OnNext` notifications. This is because inside of the delegate we provide, we are completely sequential. It also makes the token rather pointless. Now we look at how we can use the return value in a more useful way. First is an example where inside our delegate we create a Timer that will call the observer's `OnNext` each time the timer ticks. - -```csharp -// Example code only -public void NonBlocking_event_driven() -{ - var ob = Observable.Create( - observer => - { - var timer = new System.Timers.Timer(); - timer.Interval = 1000; - timer.Elapsed += (s, e) => observer.OnNext("tick"); - timer.Elapsed += OnTimerElapsed; - timer.Start(); - - return Disposable.Empty; - }); - - var subscription = ob.Subscribe(Console.WriteLine); - Console.ReadLine(); - subscription.Dispose(); -} - -private void OnTimerElapsed(object sender, ElapsedEventArgs e) -{ - Console.WriteLine(e.SignalTime); -} -``` - -Output: - -``` -tick -01/01/2012 12:00:00 -tick -01/01/2012 12:00:01 -tick -01/01/2012 12:00:02 -01/01/2012 12:00:03 -01/01/2012 12:00:04 -01/01/2012 12:00:05 -``` - -The example above is broken. When we dispose of our subscription, we will stop seeing "tick" being written to the screen; however we have not released our second event handler "`OnTimerElasped`" and have not disposed of the instance of the timer, so it will still be writing the `ElapsedEventArgs.SignalTime` to the console after our disposal. The extremely simple fix is to return `timer` as the `IDisposable` token. - -```csharp -// Example code only -var ob = Observable.Create( - observer => - { - var timer = new System.Timers.Timer(); - timer.Interval = 1000; - timer.Elapsed += (s, e) => observer.OnNext("tick"); - timer.Elapsed += OnTimerElapsed; - timer.Start(); - - return timer; - }); -``` - -Now when a consumer disposes of their subscription, the underlying `Timer` will be disposed of too. - -`Observable.Create` also has an overload that requires your `Func` to return an `Action` instead of an `IDisposable`. In a similar example to above, this one shows how you could use an action to un-register the event handler, preventing a memory leak by retaining the reference to the timer. - -```csharp -// Example code only -var ob = Observable.Create( - observer => - { - var timer = new System.Timers.Timer(); - timer.Enabled = true; - timer.Interval = 100; - timer.Elapsed += OnTimerElapsed; - timer.Start(); - - return ()=>{ - timer.Elapsed -= OnTimerElapsed; - timer.Dispose(); - }; - }); -``` - -These last few examples showed you how to use the `Observable.Create` method. These were just examples; there are actually better ways to produce values from a timer that we will look at soon. The intention is to show that `Observable.Create` provides you a lazily evaluated way to create observable sequences. We will dig much deeper into lazy evaluation and application of the `Create` factory method throughout the book especially when we cover concurrency and scheduling. - -## Functional unfolds - -As a functional programmer you would come to expect the ability to unfold a potentially infinite sequence. An issue we may face with `Observable.Create` is that is that it can be a clumsy way to produce an infinite sequence. Our timer example above is an example of an infinite sequence, and while this is a simple implementation it is an annoying amount of code for something that effectively is delegating all the work to the `System.Timers.Timer` class. The `Observable.Create` method also has poor support for unfolding sequences using corecursion. - -### Corecursion - -Corecursion is a function to apply to the current state to produce the next state. Using corecursion by taking a value, applying a function to it that extends that value and repeating we can create a sequence. A simple example might be to take the value 1 as the seed and a function that increments the given value by one. This could be used to create sequence of [1,2,3,4,5...]. - -Using corecursion to create an `IEnumerable` sequence is made simple with the `yield return` syntax. - -```csharp -private static IEnumerable Unfold(T seed, Func accumulator) -{ - var nextValue = seed; - while (true) - { - yield return nextValue; - nextValue = accumulator(nextValue); - } -} -``` - -The code above could be used to produce the sequence of natural numbers like this. - -```csharp -var naturalNumbers = Unfold(1, i => i + 1); -Console.WriteLine("1st 10 Natural numbers"); -foreach (var naturalNumber in naturalNumbers.Take(10)) -{ - Console.WriteLine(naturalNumber); -} -``` - -Output: - -``` -1st 10 Natural numbers -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -``` - -Note the `Take(10)` is used to terminate the infinite sequence. - -Infinite and arbitrary length sequences can be very useful. First we will look at some that come with Rx and then consider how we can generalize the creation of infinite observable sequences. - -### Observable.Range - -`Observable.Range(int, int)` simply returns a range of integers. The first integer is the initial value and the second is the number of values to yield. This example will write the values '10' through to '24' and then complete. - -```csharp -var range = Observable.Range(10, 15); -range.Subscribe(Console.WriteLine, ()=>Console.WriteLine("Completed")); -``` - -### Observable.Generate - -It is difficult to emulate the `Range` factory method using `Observable.Create`. It would be cumbersome to try and respect the principles that the code should be lazily evaluated and the consumer should be able to dispose of the subscription resources when they so choose. This is where we can use corecursion to provide a richer unfold. In Rx the unfold method is called `Observable.Generate`. - -The simple version of `Observable.Generate` takes the following parameters: - -- an initial state -- a predicate that defines when the sequence should terminate -- a function to apply to the current state to produce the next state -- a function to transform the state to the desired output - -```csharp -public static IObservable Generate( - TState initialState, - Func condition, - Func iterate, - Func resultSelector) -``` - -As an exercise, write your own `Range` factory method using `Observable.Generate`. - -Consider the `Range` signature `Range(int start, int count)`, which provides the seed and a value for the conditional predicate. You know how each new value is derived from the previous one; this becomes your iterate function. Finally, you probably don't need to transform the state so this makes the result selector function very simple. - -Continue when you have built your own version... - -Example of how you could use `Observable.Generate` to construct a similar `Range` factory method. - -```csharp -// Example code only -public static IObservable Range(int start, int count) -{ - var max = start + count; - return Observable.Generate( - start, - value => value < max, - value => value + 1, - value => value); -} -``` - -### Observable.Interval - -Earlier in the chapter we used a `System.Timers.Timer` in our observable to generate a continuous sequence of notifications. As mentioned in the example at the time, this is not the preferred way of working with timers in Rx. As Rx provides operators that give us this functionality it could be argued that to not use them is to re-invent the wheel. More importantly the Rx operators are the preferred way of working with timers due to their ability to substitute in schedulers which is desirable for easy substitution of the underlying timer. There are at least three various timers you could choose from for the example above: - -- `System.Timers.Timer` -- `System.Threading.Timer` -- `System.Windows.Threading.DispatcherTimer` - -By abstracting the timer away via a scheduler we are able to reuse the same code for multiple platforms. More importantly than being able to write platform independent code is the ability to substitute in a test-double scheduler/timer to enable testing. Schedulers are a complex subject that is out of scope for this chapter, but they are covered in detail in the later chapter on [Scheduling and threading](15_SchedulingAndThreading.html). - -There are three better ways of working with constant time events, each being a further generalization of the former. The first is `Observable.Interval(TimeSpan)` which will publish incremental values starting from zero, based on a frequency of your choosing. - -This example publishes values every 250 milliseconds. - -```csharp - var interval = Observable.Interval(TimeSpan.FromMilliseconds(250)); - interval.Subscribe( - Console.WriteLine, - () => Console.WriteLine("completed")); -``` - -Output: - -``` -0 -1 -2 -3 -4 -5 -``` - -Once subscribed, you must dispose of your subscription to stop the sequence. It is an example of an infinite sequence. - -### Observable.Timer - -The second factory method for producing constant time based sequences is `Observable.Timer`. It has several overloads; the first of which we will look at being very simple. The most basic overload of `Observable.Timer` takes just a `TimeSpan` as `Observable.Interval` does. The `Observable.Timer` will however only publish one value (0) after the period of time has elapsed, and then it will complete. - - var timer = Observable.Timer(TimeSpan.FromSeconds(1)); - timer.Subscribe( - Console.WriteLine, - () => Console.WriteLine("completed")); - -Output: - -``` -0 -completed -``` - -Alternatively, you can provide a `DateTimeOffset` for the `dueTime` parameter. This will produce the value 0 and complete at the due time. - -A further set of overloads adds a `TimeSpan` that indicates the period to produce subsequent values. This now allows us to produce infinite sequences and also construct `Observable.Interval` from `Observable.Timer`. - -```csharp -public static IObservable Interval(TimeSpan period) -{ - return Observable.Timer(period, period); -} -``` - -Note that this now returns an `IObservable` of `long` not `int`. While `Observable.Interval` would always wait the given period before producing the first value, this `Observable.Timer` overload gives the ability to start the sequence when you choose. With `Observable.Timer` you can write the following to have an interval sequence that started immediately. - -```csharp -Observable.Timer(TimeSpan.Zero, period); -``` - -This takes us to our third way and most general way for producing timer related sequences, back to `Observable.Generate`. This time however, we are looking at a more complex overload that allows you to provide a function that specifies the due time for the next value. - -```csharp -public static IObservable Generate( - TState initialState, - Func condition, - Func iterate, - Func resultSelector, - Func timeSelector) -``` - -Using this overload, and specifically the extra `timeSelector` argument, we can produce our own implementation of `Observable.Timer` and in turn, `Observable.Interval`. - -```csharp -public static IObservable Timer(TimeSpan dueTime) -{ - return Observable.Generate( - 0l, - i => i < 1, - i => i + 1, - i => i, - i => dueTime); -} - -public static IObservable Timer(TimeSpan dueTime, TimeSpan period) -{ - return Observable.Generate( - 0l, - i => true, - i => i + 1, - i => i, - i => i == 0 ? dueTime : period); -} - -public static IObservable Interval(TimeSpan period) -{ - return Observable.Generate( - 0l, - i => true, - i => i + 1, - i => i, - i => period); -} -``` - -This shows how you can use `Observable.Generate` to produce infinite sequences. I will leave it up to you the reader, as an exercise using `Observable.Generate`, to produce values at variable rates. I find using these methods invaluable not only in day to day work but especially for producing dummy data. - -## Transitioning into IObservable<T> - -Generation of an observable sequence covers the complicated aspects of functional programming i.e. corecursion and unfold. You can also start a sequence by simply making a transition from an existing synchronous or asynchronous paradigm into the Rx paradigm. - -### From delegates - -The `Observable.Start` method allows you to turn a long running `Func` or `Action` into a single value observable sequence. By default, the processing will be done asynchronously on a ThreadPool thread. If the overload you use is a `Func` then the return type will be `IObservable`. When the function returns its value, that value will be published and then the sequence completed. If you use the overload that takes an `Action`, then the returned sequence will be of type `IObservable`. The `Unit` type is a functional programming construct and is analogous to `void`. In this case `Unit` is used to publish an acknowledgement that the `Action` is complete, however this is rather inconsequential as the sequence is immediately completed straight after `Unit` anyway. The `Unit` type itself has no value; it just serves as an empty payload for the `OnNext` notification. Below is an example of using both overloads. - -```csharp -static void StartAction() -{ - var start = Observable.Start(() => - { - Console.Write("Working away"); - for (int i = 0; i < 10; i++) - { - Thread.Sleep(100); - Console.Write("."); - } - }); - - start.Subscribe( - unit => Console.WriteLine("Unit published"), - () => Console.WriteLine("Action completed")); -} - -static void StartFunc() -{ - var start = Observable.Start(() => - { - Console.Write("Working away"); - for (int i = 0; i < 10; i++) - { - Thread.Sleep(100); - Console.Write("."); - } - return "Published value"; - }); - - start.Subscribe( - Console.WriteLine, - () => Console.WriteLine("Action completed")); -} -``` - -Note the difference between `Observable.Start` and `Observable.Return`; `Start` lazily evaluates the value from a function, `Return` provided the value eagerly. This makes `Start` very much like a `Task`. This can also lead to some confusion on when to use each of the features. Both are valid tools and the choice come down to the context of the problem space. Tasks are well suited to parallelizing computational work and providing workflows via continuations for computationally heavy work. Tasks also have the benefit of documenting and enforcing single value semantics. Using `Start` is a good way to integrate computationally heavy work into an existing code base that is largely made up of observable sequences. We look at [composition of sequences](12_CombiningSequences.html) in more depth later in the book. - -### From events - -As we discussed early in the book, .NET already has the event model for providing a reactive, event driven programming model. While Rx is a more powerful and useful framework, it is late to the party and so needs to integrate with the existing event model. Rx provides methods to take an event and turn it into an observable sequence. There are several different varieties you can use. - -Here is a selection of common event patterns. - -```csharp -// Activated delegate is EventHandler -var appActivated = Observable.FromEventPattern( - h => Application.Current.Activated += h, - h => Application.Current.Activated -= h); - -// PropertyChanged is PropertyChangedEventHandler -var propChanged = Observable.FromEventPattern - ( - handler => handler.Invoke, - h => this.PropertyChanged += h, - h => this.PropertyChanged -= h); - -// FirstChanceException is EventHandler -var firstChanceException = Observable.FromEventPattern( - h => AppDomain.CurrentDomain.FirstChanceException += h, - h => AppDomain.CurrentDomain.FirstChanceException -= h); -``` - -So while the overloads can be confusing, they key is to find out what the event's signature is. If the signature is just the base `EventHandler` delegate then you can use the first example. If the delegate is a sub-class of the `EventHandler`, then you need to use the second example and provide the `EventHandler` sub-class and also its specific type of `EventArgs`. Alternatively, if the delegate is the newer generic `EventHandler`, then you need to use the third example and just specify what the generic type of the event argument is. - -It is very common to want to expose property changed events as observable sequences. These events can be exposed via `INotifyPropertyChanged` interface, a `DependencyProperty` or perhaps by events named appropriately to the Property they are representing. If you are looking at writing your own wrappers to do this sort of thing, I would strongly suggest looking at the Rxx library on [https://github.com/dotnet/reactive](https://github.com/dotnet/reactive) first. Many of these have been catered for in a very elegant fashion. - -### From Task - -Rx provides a useful, and well named set of overloads for transforming from other existing paradigms to the Observable paradigm. The `ToObservable()` method overloads provide a simple route to make the transition. - -As we mentioned earlier, the `AsyncSubject` is similar to a `Task`. They both return you a single value from an asynchronous source. They also both cache the result for any repeated or late requests for the value. The first `ToObservable()` extension method overload we look at is an extension to `Task`. The implementation is simple; - -- if the task is already in a status of `RanToCompletion` then the value is added to the sequence and then the sequence completed -- if the task is Cancelled then the sequence will error with a `TaskCanceledException` -- if the task is Faulted then the sequence will error with the task's inner exception -- if the task has not yet completed, then a continuation is added to the task to perform the above actions appropriately - -There are two reasons to use the extension method: - -- From Framework 4.5, almost all I/O-bound functions return `Task` -- If `Task` is a good fit, it's preferable to use it over `IObservable` - because it communicates single-value result in the type system. - -In other words, a function that returns a single value in the future should return a `Task`, not an `IObservable`. Then if you need to combine it with other observables, use `ToObservable()`. - -Usage of the extension method is also simple. - -```csharp -var t = Task.Factory.StartNew(()=>"Test"); -var source = t.ToObservable(); -source.Subscribe( - Console.WriteLine, - () => Console.WriteLine("completed")); -``` - -Output: - -``` -Test -completed -``` - -There is also an overload that converts a `Task` (non generic) to an `IObservable`. - -### From IEnumerable<T> - -The final overload of `ToObservable` takes an `IEnumerable`. This is semantically like a helper method for an `Observable.Create` with a `foreach` loop in it. - -```csharp -// Example code only -public static IObservable ToObservable(this IEnumerable source) -{ - return Observable.Create(o => - { - foreach (var item in source) - { - o.OnNext(item); - } - - // Incorrect disposal pattern - return Disposable.Empty; - }); -} -``` - -This crude implementation however is naive. It does not allow for correct disposal, it does not handle exceptions correctly and as we will see later in the book, it does not have a very nice concurrency model. The version in Rx of course caters for all of these tricky details so you don't need to worry. - -When transitioning from `IEnumerable` to `IObservable`, you should carefully consider what you are really trying to achieve. You should also carefully test and measure the performance impacts of your decisions. Consider that the blocking synchronous (pull) nature of `IEnumerable` sometimes just does not mix well with the asynchronous (push) nature of `IObservable`. Remember that it is completely valid to pass `IEnumerable`, `IEnumerable`, arrays or collections as the data type for an observable sequence. If the sequence can be materialized all at once, then you may want to avoid exposing it as an `IEnumerable`. If this seems like a fit for you then also consider passing immutable types like an array or a `ReadOnlyCollection`. We will see the use of `IObservable>` later for operators that provide batching of data. - -### From APM - -Finally we look at a set of overloads that take you from the [Asynchronous Programming Model](http://msdn.microsoft.com/en-us/magazine/cc163467.aspx) (APM) to an observable sequence. This is the style of programming found in .NET that can be identified with the use of two methods prefixed with `Begin...` and `End...` and the iconic `IAsyncResult` parameter type. This is commonly seen in the I/O APIs. - -```csharp -class webrequest -{ - public webresponse getresponse() - {...} - - public iasyncresult begingetresponse( - asynccallback callback, - object state) - {...} - - public webresponse endgetresponse(iasyncresult asyncresult) - {...} - ... -} -class stream -{ - public int read( - byte[] buffer, - int offset, - int count) - {...} - - public iasyncresult beginread( - byte[] buffer, - int offset, - int count, - asynccallback callback, - object state) - {...} - - public int endread(iasyncresult asyncresult) - {...} - ... -} -``` - -> At time of writing .NET 4.5 was still in preview release. Moving forward with .NET 4.5 the APM model will be replaced with `Task` and new `async` and `await` keywords. Rx 2.0 which is also in a beta release will integrate with these features. .NET 4.5 and Rx 2.0 are not in the scope of this book. - -APM, or the Async Pattern, has enabled a very powerful, yet clumsy way of for .NET programs to perform long running I/O bound work. If we were to use the synchronous access to IO, e.g. `WebRequest.GetResponse()` or `Stream.Read(...)`, we would be blocking a thread but not performing any work while we waited for the IO. This can be quite wasteful on busy servers performing a lot of concurrent work to hold a thread idle while waiting for I/O to complete. Depending on the implementation, APM can work at the hardware device driver layer and not require any threads while blocking. Information on how to follow the APM model is scarce. Of the documentation you can find it is pretty shaky, however, for more information on APM, see Jeffrey Richter's brilliant book CLR via C# or Joe Duffy's comprehensive Concurrent Programming on Windows. Most stuff on the internet is blatant plagiary of Richter's examples from his book. An in-depth examination of APM is outside of the scope of this book. - -To utilize the Asynchronous Programming Model but avoid its awkward API, we can use the `Observable.FromAsyncPattern` method. Jeffrey van Gogh gives a brilliant walk through of the `Observable.FromAsyncPattern` in [Part 1](http://blogs.msdn.com/b/jeffva/archive/2010/07/23/rx-on-the-server-part-1-of-n-asynchronous-system-io-stream-reading.aspx) of his Rx on the Server blog series. While the theory backing the Rx on the Server series is sound, it was written in mid 2010 and targets an old version of Rx. - -With 30 overloads of `Observable.FromAsyncPattern` we will look at the general concept so that you can pick the appropriate overload for yourself. First if we look at the normal pattern of APM we will see that the BeginXXX method will take zero or more data arguments followed by an `AsyncCallback` and an `Object`. The BeginXXX method will also return an `IAsyncResult` token. - -```csharp -// Standard Begin signature -IAsyncResult BeginXXX(AsyncCallback callback, Object state); - -// Standard Begin signature with data -IAsyncResult BeginYYY(string someParam1, AsyncCallback callback, object state); -``` - -The EndXXX method will accept an `IAsyncResult` which should be the token returned from the BeginXXX method. The EndXXX can also return a value. - -```csharp -// Standard EndXXX Signature -void EndXXX(IAsyncResult asyncResult); - -// Standard EndXXX Signature with data -int EndYYY(IAsyncResult asyncResult); -``` - -The generic arguments for the `FromAsyncPattern` method are just the BeginXXX data arguments if any, followed by the EndXXX return type if any. If we apply that to our `Stream.Read(byte[], int, int, AsyncResult, object)` example above we see that we have a `byte[]`, an `int` and another `int` as our data parameters for `BeginRead` method. - -```csharp - // IAsyncResult BeginRead( - // byte[] buffer, - // int offset, - // int count, - // AsyncCallback callback, object state) {...} - Observable.FromAsyncPattern -``` - -The result of the call to `Observable.FromAsyncPattern` does _not_ return an observable sequence. It returns a delegate that returns an observable sequence. The signature for this delegate will match the generic arguments of the call to `FromAsyncPattern`, except that the return type will be wrapped in an observable sequence. - -```csharp -var fileLength = (int) stream.Length; -// read is a Func> -var read = Observable.FromAsyncPattern( - stream.BeginRead, - stream.EndRead); -var buffer = new byte[fileLength]; -var bytesReadStream = read(buffer, 0, fileLength); -bytesReadStream.Subscribe(byteCount => -{ - Console.WriteLine("Number of bytes read={0}, buffer should be populated with data now.", byteCount); -}); -``` - -Note that this implementation is just an example. For a very well designed implementation that is built against the latest version of Rx you should look at the Rxx project on [https://github.com/dotnet/reactive](https://github.com/dotnet/reactive). - -This covers the first classification of query operators: creating observable sequences. We have looked at the various eager and lazy ways to create a sequence. We have introduced the concept of corecursion and show how we can use it with the `Generate` method to unfold potentially infinite sequences. We can now produce timer based sequences using the various factory methods. We should also be familiar with ways to transition from other synchronous and asynchronous paradigms and be able to decide when it is or is not appropriate to do so. - -As a quick recap: - -- Factory Methods - - Observable.Return - - Observable.Empty - - Observable.Never - - Observable.Throw - - Observable.Create - -- Unfold methods - - Observable.Range - - Observable.Interval - - Observable.Timer - - Observable.Generate - -- Paradigm Transition - - Observable.Start - - Observable.FromEventPattern - - Task.ToObservable - - Task<T>.ToObservable - - IEnumerable<T>.ToObservable - - Observable.FromAsyncPattern - -Creating an observable sequence is our first step to practical application of Rx: create the sequence and then expose it for consumption. Now that we have a firm grasp on how to create an observable sequence, we can discover the operators that allow us to query an observable sequence. \ No newline at end of file diff --git a/content/04_Part2.md b/content/04_Part2.md new file mode 100644 index 0000000..20ee224 --- /dev/null +++ b/content/04_Part2.md @@ -0,0 +1,17 @@ +--- +title: PART 2 +--- + +# PART 2 - From Events to Insights + +We live in an age where data is being created, stored, and distributed at a phenomenal rate. Consuming this data can be overwhelming, like trying to drink directly from a fire hose. We need the ability to identify the important data, meaning we need ways to determine what is and is not relevant. We need to take groups of data and process them collectively to discover patterns or other information that might not be apparent from any individual raw input. Users, customers and managers need to do this with more data than ever before, while still delivering higher performance and more useful outputs. + +Rx provides some powerful mechanisms for extracting meaningful insights from raw data streams. This is one of the main reasons for representing information as `IObservable` streams in the first place. The preceding chapter showed how to create an observable sequence, so now we will look at how to exploit the power this has unlocked using the the various Rx methods that can process and transform an observable sequence. + +Rx supports most of the standard LINQ operators. It also defines numerous additional operators. These fall broadly into categories, and each of the following chapters tackles one category: + +* [Filtering](05_Filtering.md) +* [Transformation](06_Transformation.md) +* [Aggregation](07_Aggregation.md) +* [Partitioning](08_Partitioning.md) +* [Combination](09_CombiningSequences.md) diff --git a/content/05_Filtering.md b/content/05_Filtering.md index c0e274a..60509c9 100644 --- a/content/05_Filtering.md +++ b/content/05_Filtering.md @@ -1,269 +1,314 @@ --- -title : Reducing a sequence +title : Filtering --- -# Reducing a sequence +# Filtering -We live in the information age. Data is being created, stored and distributed at a phenomenal rate. Consuming this data can be overwhelming, like trying to drink directly from the fire hose. We need the ability to pick out the data we need, choose what is and is not relevant, and roll up groups of data to make it relevant. Users, customers and managers need you do this with more data than ever before, while still delivering higher performance and tighter deadlines. +Rx provides us with tools to take potentially vast quantities of events and process these to produce higher level insights. This can often involve a reduction in volume. A small number of events may be more useful than a large number if the individual events in that lower-volume stream are, on average, more informative. The simplest mechanisms for achieving this involve simply filtering out events we don't want. Rx defines several operators that can do this. -Given that we know how to create an observable sequence, we will now look at the various methods that can reduce an observable sequence. -We can categorize operators that reduce a sequence to the following: +Just before we move on to introducing the new operators, we will quickly define an extension method to help illuminate several of the examples. This `Dump` extension method subscribes to any `IObservable` with handlers that display messages for each notification the source produces. This method takes a `name` argument, which will be shown as part of each message, enabling us to see where events came from in examples that subscribe to more than one source. -
-
Filter and partition operators
-
- Reduce the source sequence to a sequence with at most the same number of elements
-
Aggregation operators
-
- Reduce the source sequence to a sequence with a single element
-
Fold operators
-
- Reduce the source sequence to a single element as a scalar value
-
+```csharp +public static class SampleExtensions +{ + public static void Dump(this IObservable source, string name) + { + source.Subscribe( + value =>Console.WriteLine($"{name}-->{value}"), + ex => Console.WriteLine($"{name} failed-->{ex.Message}"), + () => Console.WriteLine($"{name} completed")); + } +} +``` -We discovered that the creation of an observable sequence from a scalar value is defined as _anamorphism_ or described as an _'unfold'_. We can think of the anamorphism from `T` to `IObservable` as an 'unfold'. This could also be referred to as "entering the monad" where in this case (and for most cases in this book) the monad is `IObservable`. What we will now start looking at are methods that eventually get us to the inverse which is defined as _catamorphism_ or a `fold`. Other popular names for fold are 'reduce', 'accumulate' and 'inject'. +## Where -## Where -Applying a filter to a sequence is an extremely common exercise and the most common filter is the `Where` clause. In Rx you can apply a where clause with the `Where` extension method. For those that are unfamiliar, the signature of the `Where` method is as follows: +Applying a filter to a sequence is an extremely common exercise and the most straightforward filter in LINQ is the `Where` operator. As usual with LINQ, Rx provides its operators in the form of extension methods. If you are already familiar with LINQ, the signature of Rx's `Where` method will come as no surprise: - IObservable Where(this IObservable source, Fun predicate) +```cs +IObservable Where(this IObservable source, Func predicate) +``` + +Note that the element type is the same for the `source` parameter as it is for the return type. This is because `Where` doesn't modify elements. It can filter some out, but those that it does not remove are passed through unaltered. -Note that both the source parameter and the return type are the same. This allows for a fluent interface, which is used heavily throughout Rx and other LINQ code. In this example we will use the `Where` to filter out all even values produced from a `Range` sequence. +This example uses `Where` to filter out all odd values produced from a `Range` sequence, meaning only even numbers will emerge. ```csharp -var oddNumbers = Observable.Range(0, 10) - .Where(i => i % 2 == 0) - .Subscribe( - Console.WriteLine, - () => Console.WriteLine("Completed")); +IObservable xs = Observable.Range(0, 10); // The numbers 0-9 + +IObservable evenNumbers = xs.Where(i => i % 2 == 0); + +evenNumbers.Dump("Where"); ``` Output: ``` -0 -2 -4 -6 -8 -Completed +Where-->0 +Where-->2 +Where-->4 +Where-->6 +Where-->8 +Where completed ``` -The `Where` operator is one of the many standard LINQ operators. This and other LINQ operators are common use in the various implementations of query operators, most notably the `IEnumerable` implementation. In most cases the operators behave just as they do in the `IEnumerable` implementations, but there are some exceptions. We will discuss each implementation and explain any variation as we go. By implementing these common operators Rx also gets language support for free via C# query comprehension syntax. For the examples in this book however, we will keep with using extension methods for consistency. +The `Where` operator is one of the many standard LINQ operators you'll find on all LINQ providers. LINQ to Objects, the `IEnumerable` implementation, provides an equivalent method, for example. In most cases, Rx's operators behave just as they do in the `IEnumerable` implementations, although there are some exceptions as we'll see later. We will discuss each implementation and explain any variation as we go. By implementing these common operators Rx also gets language support for free via C# query expression syntax. For example, we could have written the first statement this way, and it would have compiled to effectively identical code: -## Distinct and DistinctUntilChanged -As I am sure most readers are familiar with the `Where` extension method for `IEnumerable`, some will also know the `Distinct` method. In Rx, the `Distinct` method has been made available for observable sequences too. For those that are unfamiliar with `Distinct`, and as a recap for those that are, `Distinct` will only pass on values from the source that it has not seen before. +```cs +IObservable evenNumbers = + from i in xs + where i % 2 == 0 + select i; +``` -```csharp -var subject = new Subject(); -var distinct = subject.Distinct(); - -subject.Subscribe( - i => Console.WriteLine("{0}", i), - () => Console.WriteLine("subject.OnCompleted()")); +The examples in this book mostly use extension methods, not query expressions, partly because Rx implements some operators for which there is no corresponding query syntax, and partly because the method call approach can sometimes make it easier to see what is happening. -distinct.Subscribe( - i => Console.WriteLine("distinct.OnNext({0})", i), - () => Console.WriteLine("distinct.OnCompleted()")); +As with most Rx operators, `Where` does not subscribe immediately to its source. (Rx LINQ operators are much like those in LINQ to Objects: the `IEnumerable` version of `Where` returns without attempting to enumerate its source. It's only when something attempts to enumerate the `IEnumerable` that `Where` returns that it will in turn start enumerating the source.) Only when something calls `Subscribe` on the `IObservable` returned by `Where` will it call `Subscribe` on its source. And it will do so once for each such call to `Subscribe`. More generally, when you chain LINQ operators together, each `Subscribe` call on the resulting `IObservable` results in a cascading series of calls to `Subscribe` all the way down the chain. -subject.OnNext(1); -subject.OnNext(2); -subject.OnNext(3); -subject.OnNext(1); -subject.OnNext(1); -subject.OnNext(4); -subject.OnCompleted(); -``` +A side effect of this cascading `Subscribe` is that `Where` (like most other LINQ operators) is neither inherently _hot_ or _cold_: since it just subscribes to its source, then it will be hot if its source is hot, and cold if its source is cold. -Output: +The `Where` operator passes on all elements for which its `predicate` callback returns `true`. To be more precise, when you subscript to `Where`, it will create its own `IObserver` which it passes as the argument to `source.Subscribe`, and this observer invokes the `predicate` for each call to `OnNext`. If that predicate returns `true`, then and only then will the observer created by `Where` call `OnNext` on the observer that you passed to `Where`. -``` -1 -distinct.OnNext(1) -2 -distinct.OnNext(2) -3 -distinct.OnNext(3) -1 -1 -4 -distinct.OnNext(4) -subject.OnCompleted() -distinct.OnCompleted() +`Where` always passes the final call to either `OnComplete` or `OnError` through. That means that if you were to write this: + +```cs +IObservable dropEverything = xs.Where(_ => false); ``` -Take special note that the value 1 is pushed 3 times but only passed through the first time. There are overloads to `Distinct` that allow you to specialize the way an item is determined to be distinct or not. One way is to provide a function that returns a different value to use for comparison. Here we look at an example that uses a property from a custom class to define if a value is distinct. +then although this would filter out all elements (because the predicate ignores its argument and always returns `false`, instructing `Where` to drop everything), this won't filter out an error or completion. -```csharp -public class Account -{ - public int AccountId { get; set; } - //... etc -} +In fact if that's what you want—an operator that drops all the elements and just tells you when a source completes or fails—there's a simpler way. -public void Distinct_with_KeySelector() -{ - var subject = new Subject(); - var distinct = subject.Distinct(acc => acc.AccountId); -} +## IgnoreElements + +The `IgnoreElements` extension method allows you to receive just the `OnCompleted` or `OnError` notifications. It is equivalent to using the `Where` operator with a predicate that always returns `false`, as this example illustrates: + +```cs +IObservable xs = Observable.Range(1, 3); +IObservable dropEverything = xs.IgnoreElements(); + +xs.Dump("Unfiltered"); +dropEverything.Dump("IgnoreElements"); ``` -In addition to the `keySelector` function that can be provided, there is an overload that takes an `IEqualityComparer` instance. This is useful if you have a custom implementation that you can reuse to compare instances of your type `T`. Lastly there is an overload that takes a `keySelector` and an instance of `IEqualityComparer`. Note that the equality comparer in this case is aimed at the selected key type (`TKey`), not the type `T`. +As the output shows, the `xs` source produces the numbers 1 to 3 then completes, but if we run that through `IgnoreElements`, all we see is the `OnCompleted`. -A variation of `Distinct`, that is peculiar to Rx, is `DistinctUntilChanged`. This method will surface values only if they are different from the previous value. Reusing our first `Distinct` example, note the change in output. +``` +Unfiltered-->1 +Unfiltered-->2 +Unfiltered-->3 +Unfiltered completed +IgnoreElements completed +``` -```csharp -var subject = new Subject(); -var distinct = subject.DistinctUntilChanged(); - -subject.Subscribe( - i => Console.WriteLine("{0}", i), - () => Console.WriteLine("subject.OnCompleted()")); +## OfType -distinct.Subscribe( - i => Console.WriteLine("distinct.OnNext({0})", i), - () => Console.WriteLine("distinct.OnCompleted()")); +Some observable sequences produce items of various types. For example, consider an application that wants to keep track of ships as they move. This is possible with an AIS receiver. AIS is the Automatic Identification System, which most ocean-going ships use to report their location, heading, speed, and other information. There are numerous kinds of AIS message. Some report a ship's location and speed, but its name is reported in a different kind of message. (This is because most ships move more often than they change their names, so they broadcast these two types of information at quite different intervals.) -subject.OnNext(1); -subject.OnNext(2); -subject.OnNext(3); -subject.OnNext(1); -subject.OnNext(1); -subject.OnNext(4); -subject.OnCompleted(); +Imagine how this might look in Rx. Actually you don't have to imagine it. The open source [Ais.Net project](https://github.com/ais-dotnet) includes a [`ReceiverHost` class](https://github.com/ais-dotnet/Ais.Net.Receiver/blob/15de7b2908c3bd67cf421545578cfca59b24ed2c/Solutions/Ais.Net.Receiver/Ais/Net/Receiver/Receiver/ReceiverHost.cs) that makes AIS messages available through Rx. The `ReceiverHost` defines a `Messages` property of type `IObservable`. Since AIS defines numerous message types, this observable source can produce many different kinds of objects. Everything it emits will implement the [`IAisMessage` interface](https://github.com/ais-dotnet/Ais.Net.Receiver/blob/15de7b2908c3bd67cf421545578cfca59b24ed2c/Solutions/Ais.Net.Models/Ais/Net/Models/Abstractions/IAisMessage.cs), which reports the ship's unique identifier, but not much else. But the [`Ais.Net.Models` library](https://www.nuget.org/packages/Ais.Net.Models/) defines numerous other interfaces, including [`IVesselNavigation`](https://github.com/ais-dotnet/Ais.Net.Receiver/blob/15de7b2908c3bd67cf421545578cfca59b24ed2c/Solutions/Ais.Net.Models/Ais/Net/Models/Abstractions/IVesselNavigation.cs), which reports location, speed, and heading, and [`IVesselName`](https://github.com/ais-dotnet/Ais.Net.Receiver/blob/15de7b2908c3bd67cf421545578cfca59b24ed2c/Solutions/Ais.Net.Models/Ais/Net/Models/Abstractions/IVesselName.cs), which tells you the vessel's name. + +Suppose you are interested only in the locations of vessels in the water, and you don't care about the vessels' names. You will want to see all messages that implement the `IVesselNavigation` interface, and to ignore all those that don't. You could try to achieve this with the `Where` operator: + +```cs +// Won't compile! +IObservable vesselMovements = receiverHost.Messages + .Where(m => m is IVesselNavigation); ``` -Output: +However, that won't compile. You will get this error: ``` -1 -distinct.OnNext(1) -2 -distinct.OnNext(2) -3 -distinct.OnNext(3) -1 -distinct.OnNext(1) -1 -4 -distinct.OnNext(4) -subject.OnCompleted() -distinct.OnCompleted() +Cannot implicitly convert type 'System.IObservable' to 'System.IObservable' +``` + +Remember that the return type of `Where` is always the same as its input. Since `receiverHost.Messages` is of type `IObservable`, that's is also the type that `Where` will return. It so happens that our predicate ensures that only those messages that implement `IVesselNavigation` make it through, but there's no way for the C# compiler to understand the relationship between the predicate and the output. (For all it knows, `Where` might do the exact opposite, including only those elements for which the predicate returns `false`. In fact the compiler can't guess anything about how `Where` might use its predicate.) + +Fortunately, Rx provides an operator specialized for this case. `OfType` filters items down to just those that are of a particular type. Items must be either the exact type specified, or inherit from it, or, if it's an interface, they must implement it. This enables us to fix the last example: +```cs +IObservable vesselMovements = receiverHost.Messages + .OfType(); ``` -The difference between the two examples is that the value 1 is pushed twice. However the third time that the source pushes the value 1, it is immediately after the second time value 1 is pushed. In this case it is ignored. Teams I have worked with have found this method to be extremely useful in reducing any noise that a sequence may provide. +## Positional Filtering -## IgnoreElements +Sometimes, we don't care about what an element is, so much as where it is in the sequence. Rx defines a few operators that can help us with this. -The `IgnoreElements` extension method is a quirky little tool that allows you to receive the `OnCompleted` or `OnError` notifications. We could effectively recreate it by using a `Where` method with a predicate that always returns false. +### FirstAsync and FirstOrDefaultAsync -```csharp -var subject = new Subject(); +LINQ providers typically implement a `First` operator that provides the first element of a sequence. Rx is no exception, but the nature of Rx means we typically need this to work slightly differently. With providers for data at rest (such as LINQ to Objects or Entity Framework Core) the source elements already exist, so retrieving the first item is just a matter of reading it. But with Rx, sources produce data when they choose, so there's no way of knowing when the first item will become available. -// Could use subject.Where(_=>false); -var noElements = subject.IgnoreElements(); +So with Rx, we typically use `FirstAsync`. This returns an `IObservable` that will produce the first value that emerges from the source sequence and will then complete. (Rx does also offer a more conventional `First` method, but it can be problematic. See the [**Blocking Versions of First/Last/Single[OrDefault]** section later](#blocking-versions-of-firstlastsingleordefault) for details.) -subject.Subscribe( - i=>Console.WriteLine("subject.OnNext({0})", i), - () => Console.WriteLine("subject.OnCompleted()")); +For example, this code uses the AIS.NET source introduced earlier to report the first time a particular boat (the aptly named HMS Example, as it happens) reports that it is moving: -noElements.Subscribe( - i=>Console.WriteLine("noElements.OnNext({0})", i), - () => Console.WriteLine("noElements.OnCompleted()")); +```cs +uint exampleMmsi = 235009890; +IObservable moving = receiverHost.Messages + .Where(v => v.Mmsi == exampleMmsi) + .OfType() + .Where(vn => vn.SpeedOverGround > 1f) + .FirstAsync(); +``` -subject.OnNext(1); -subject.OnNext(2); -subject.OnNext(3); -subject.OnCompleted(); +As well as using `FirstAsync`, this also uses a couple of the other filter elements already described. It starts with a [`Where`](#where) step that filters messages down to those from the one boat we happen to be interested in. (Specifically, we filter based on that boat's [Maritime Mobile Service Identity, or MMSI](https://en.wikipedia.org/wiki/Maritime_Mobile_Service_Identity).) Then we use [`OfType`](#oftype) so that we are looking only at those messages that report how/whether the vessel is moving. Then we use another `Where` clause so that we can ignore messages indicating that the boat is not actually moving, finally, we use `FirstAsync` so that we get only the first message indicating movement. As soon as the boat moves, this `moving` source will emit a single `IVesselNavigation` event and will then immediately complete. + +We can simplify that query slightly, because `FirstAsync` optionally takes a predicate. This enables us to collapse the final `Where` and `FirstAsync` into a single operator: + +```cs +IObservable moving = receiverHost.Messages + .Where(v => v.Mmsi == exampleMmsi) + .OfType() + .FirstAsync(vn => vn.SpeedOverGround > 1f); ``` -Output: +What if the input to `FirstAsync` is empty? If its completes without ever producing an item, `FirstAsync` invokes its subscriber's `OnError`, passing an `InvalidOperationException` with an error message reporting that the sequence contains no elements. The same is true if we're using the form that takes a predicate (as in this second example), and no elements matching the predicate emerged. This is consistent with the LINQ to Objects `First` operator. (Note that we wouldn't expect this to happen with the examples just shown, because the source will continue to report AIS messages for as long as the application is running, meaning there's no reason for it ever to complete.) + +Sometimes, we might want to tolerate this kind of absence of events. Most LINQ providers offer not just `First` but `FirstOrDefault`. We can use this by modify the preceding example. This uses the [`TakeUntil` operator](#skipuntil-and-takeuntil) to introduce a cut-off time: this example is prepared to wait for 5 minutes, but gives up after that. (So although the AIS receiver can produce messages endlessly, this example has decided it won't wait forever.) And since that means we might complete without ever seeing the boat move, we've replaced `FirstAsync` with `FirstOrDefaultAsync`: +```cs +IObservable moving = receiverHost.Messages + .Where(v => v.Mmsi == exampleMmsi) + .OfType() + .TakeUntil(DateTimeOffset.Now.AddMinutes(5)) + .FirstOrDefaultAsync(vn => vn.SpeedOverGround > 1f); ``` -subject.OnNext(1) -subject.OnNext(2) -subject.OnNext(3) -subject.OnCompleted() -noElements.OnCompleted() +If, after 5 minutes, we've not seen a message from the boat indicating that it's moving at 1 knot or faster, `TakeUntil` will unsubscribe from its upstream source and will call `OnCompleted` on the observer supplied by `FirstOrDefaultAsync`. Whereas `FirstAsync` would treat this as an error, `FirstOrDefaultAsync` will produce the default value for its element type (`IVesselNavigation` in this case; the default value for an interface type is `null`), pass that to its subscriber's `OnNext`, and then call `OnCompleted`. + +In short, this `moving` observable will always produce exactly one item. Either it will produce an `IVesselNavigation` indicating that the boat has moved, or it will produce `null` to indicate that this didn't happen in the 5 minutes that this code has allowed. + +This production of a `null` might be an OK way to indicate that something didn't happen, but there's something slightly clunky about it: anything consuming this `moving` source now has to work out whether a notification signifies the event of interest, or the absence of any such event. If that happens to be convenient for your code, then great, but Rx provides a more direct way to represent the absence of an event: an empty sequence. + +You could imagine a _first or empty_ operator that worked this way. This wouldn't make sense for LINQ providers that return an actual value. For example, as LINQ to Objects' `First` returns `T`, not `IEnumerable`, so there's no way for it to return an empty sequence. But because Rx's offers `First`-like operators that return `IObservable`, it would be technically possible to have an operator that returns either the first item or no items at all. There is no such operator built into Rx, but we can get exactly the same effect by using a more generalised operator, `Take`. + +### Take + +`Take` is a standard LINQ operator that takes the first few items from a sequence and then discards the rest. + +In a sense, `Take` is a generalization of `First`: `Take(1)` returns only the first item, so you could think of LINQ's `First` as being a special case of `Take`. That's not strictly correct because these operators respond differently to missing elements: as we've just seen, `First` (and Rx's `FirstAsync`) insists on receiving at least one element, producing an `InvalidOperationException` if you supply it with an empty sequence. Even the more existentially relaxed `FirstOrDefault` still insists on producing something. `Take` works slightly differently. + +If the input to `Take` completes before producing as many elements as have been specified, `Take` does not complain—it just forwards whatever the source has provided. If the source did nothing other than call `OnCompleted`, then `Take` just calls `OnCompleted` on its observer. If we used `Take(5)`, but the source produced three items and then completed, `Take(5)` will forward those three items to its subscriber, and will then complete. This means we could use `Take` to implement the hypothetical `FirstOrEmpty` discussed in the preceding section: + +```cs +public static IObservable FirstOrEmpty(this IObservable src) => src.Take(1); ``` -As suggested earlier we could use a `Where` to produce the same result +Now would be a good time to remind you that most Rx operators (and all the ones in this chapter) are not intrinsically either hot or cold. They defer to their source. Given some hot `source`, `source.Take(1)` is also hot. The AIS.NET `receiverHost.Messages` source I've been using in these examples is hot (because it reports live message broadcasts from ships), so observable sequences derived from it are also hot. Why is now a good time to discuss this? Because it enables me to make the following absolutely dreadful pun: -```csharp -subject.IgnoreElements(); -// Equivalent to -subject.Where(value=>false); -// Or functional style that implies that the value is ignored. -subject.Where(_=>false); +```cs +IObservable hotTake = receiverHost.Messages.Take(1); ``` -Just before we leave `Where` and `IgnoreElements`, I wanted to just quickly look at the last line of code. Until recently, I personally was not aware that '`_`' was a valid variable name; however it is commonly used by functional programmers to indicate an ignored parameter. This is perfect for the above example; for each value we receive, we ignore it and always return false. The intention is to improve the readability of the code via convention. +Thank you. I'm here all week. + +The `FirstAsync` and `Take` operators work from the start of the sequence. What if we're interested only in the tail end? + +### LastAsync, LastOrDefaultAsync, and PublishLast + +LINQ providers typically provide `Last` and `LastOrDefault`. These do almost exactly the same thing as `First` or `FirstOrDefault` except, as the name suggests, they return the final element instead of the first one. As with `First`, the nature of Rx means that unlike with LINQ providers working with data at rest, the final element might not be just sitting there waiting to be fetched. So just as Rx offers `FirstAsync` and `FirstOrDefault`, it offers `LastAsync` and `LastOrDefaultAsync`. (It does also offer `Last`, but again, as the [Blocking Versions of First/Last/Single[OrDefault]](#blocking-versions-of-firstlastsingleordefault) section discusses, this can be problematic.) + +There is also [`PublishLast`](15_PublishingOperators.md#publishlast). This has similar semantics to `LastAsync` but it handles multiple subscriptions differently. Each time you subscribe to the `IObservable` that `LastAsync` returns, it will subscribe to the underlying source, but `PublishLast` makes only a single `Subscribe` call to the underlying source. (To provide control over exactly when this happens, `PublishLast` returns an `IConnectableObservable`. As the [Hot and Cold Sources section of Chapter 2](02_KeyTypes.md#hot-and-cold-sources) described, this provides a `Connect` method, and the connectable observable returned by `PublishLast` subscribes to its underlying source when you call this.) Once this single subscription receives an `OnComplete` notification from the source, it will deliver the final value to all subscribers. (It also remembers the final value, so if any new observers subscribe after the final value has been produced, they will immediately receive that value when they subscribe.) The final value is immediately followed by an `OnCompleted` notification. This is one of a family of operators based on the [`Multicast`](15_PublishingOperators.md#multicast) operator described in more detail in later chapters. + +The distinction between `LastAsync` and `LastOrDefaultAsync` is the same as with `FirstAsync` and `FirstOrDefaultAsync`. If the source completes having produced nothing, `LastAsync` reports an error, whereas `LastOrDefaultAsync` emits the default value for its element type and then completes. `PublishLast` handles an empty source differently again: if the source completes without producing any elements, the observable returned by `PublishLast` will do the same: it produces neither an error nor a default value in this scenario. -## Skip and Take +Reporting the final element of a sequence entails a challenge that `First` does not face. It's very easy to know when you've received the first element from a source: if the source produces an element, and it hasn't previously produced an element, then that's the first element right there. This means that operators such as `FirstAsync` can report the first element immediately. But `LastAsync` and `LastOrDefaultAsync` don't have that luxury. + +If you receive an element from a source, how do you know that it is the last element? In general, you can't know this at the instant that you receive it. You will only know that you have received the last element when the source goes on to invoke your `OnCompleted` method. This won't necessarily happen immediately. An earlier example used `TakeUntil(DateTimeOffset.Now.AddMinutes(5))` to bring a sequence to an end after 5 minutes, and if you do that, it's entirely possible that a significant amount of time might elapse between the final element being emitted, and `TakeUntil` shutting things down. In the AIS scenario, boats might only emit messages once every few minutes, so it's quite plausible that we could end up with `TakeUntil` forwarding a message, and then discovering a few minutes later that the cutoff time has been reached without any further messages coming in. Several minutes could have elapsed between the final `OnNext` and the `OnComplete`. + +Because of this. `LastAsync` and `LastOrDefaultAsync` emit nothing at all until their source completes. **This has an important consequence:** there might be a significant delay between `LastAsync` receiving the final element from the source, and it forwarding that element to its subscriber. + +### TakeLast + +Earlier we saw that Rx implements the standard `Take` operator, which forwards up to a specified number of elements from the start of a sequence and then stops. `TakeLast` forwards the elements at the end of a sequence. For example, `TakeLast(3)` asks for the final 3 elements of the source sequence. As with `Take`, `TakeLast` is tolerant of sources that produce too few items. If a source produces fewer than 3 items, `TaskLast(3)` will just forward the entire sequence. + +`TakeLast` faces the same challenge as `Last`: it doesn't know when it is near the end of the sequence. It therefore has to hold onto copies of the most recently seen values. It needs memory to hold onto however many values you've specified. If you write `TakeLast(1_000_000)`, it will need to allocate a buffer large enough to store 1,000,000 values. It doesn't know if the first element it receives will be one of the final million. It can't know that until either the source completes, or the source has emitted more than 1,000,000 items. When the source finally does complete, `TakeLast` will now know what the final million elements were and will need to pass all of them to its subscriber's `OnNext` method one after another. + +### Skip and SkipLast + +What if we want the exact opposite of the `Take` or `TakeLast` operators? Instead of taking the first 5 items from a source, maybe I want to discard the first 5 items instead? Perhaps I have some `IObservable` taking readings from a sensor, and I have discovered that the sensor produces garbage values for its first few readings, so I'd like to ignore those, and only start listening once it has settled down. I can achieve this with `Skip(5)`. + +`SkipLast` does the same thing at the end of the sequence: it omits the specified number of elements at the tail end. As with some of the other operators we've just been looking at, this has to deal with the problem that it can't tell when it's near the end of the sequence. It only gets to discover which were the last (say) 4 elements after the source has emitted all of them, followed by an `OnComplete`. So `SkipLast` will introduce a delay. If you use `SkipLast(4)`, it won't forward the first element that the source produces until the source produces a 5th element. So it doesn't need to wait for `OnCompleted` or `OnError` before it can start doing things, it just has to wait until its certain that an element is not one of the ones we want to discard. The other key methods to filtering are so similar I think we can look at them as one big group. First we will look at `Skip` and `Take`. These act just like they do for the `IEnumerable` implementations. These are the most simple and probably the most used of the Skip/Take methods. Both methods just have the one parameter; the number of values to skip or to take. -If we first look at `Skip`, in this example we have a range sequence of 10 items and we apply a `Skip(3)` to it. +### SingleAsync and SingleOrDefaultAsync -```csharp -Observable.Range(0, 10) - .Skip(3) - .Subscribe(Console.WriteLine, () => Console.WriteLine("Completed")); -``` +LINQ operators typically provide a `Single` operator, for use when a source should provide exactly one item, and it would be an error for it to contain more, or for it to be empty. The same Rx considerations apply here as for `First` and `Last`, so you will probably be unsurprised to learn that Rx offers a `SingleAsync` method that returns an `IObservable` that will either call its observer's `OnNext` exactly once, or will call its `OnError` to indicate either that the source reported an error, or that the source did not produce exactly one item. -Output: +With `SingleAsync`, you will get an error if the source is empty, just like with `FirstAsync` and `LastAsync`, but you will also get an error if the source contains multiple items. There is a `SingleOrDefault` which, like its first/last counterparts, tolerates an empty input sequence, generating a single element with the element type's default value in that case. -``` -3 -4 -5 -6 -7 -8 -9 -Completed +`Single` and `SingleAsync` share with `Last` and `LastAsync` the characteristic that they don't initially know when they receive an item from the source whether it should be the output. That may seem odd: since `Single` requires the source stream to provide just one item, surely it must know that the item it will deliver to its subscriber will be the first item it receives. This is true, but the thing it doesn't yet know when it receives the first item is whether the source is going to produce a second one. It can't forward the first item unless and until the source completes. We could say that `SingleAsync`'s job is to first verify that the source contains exactly one item, and then to forward that item if it does, but to report an error if it does not. In the error case, `SingleAsync` will know it has gone wrong if it ever receives a second item, so it can immediately call `OnError` on its subscriber at that point. But in the success scenario, it can't know that all is well until the source confirms that nothing more is coming by completing. Only then will `SingleAsync` emit the result. -``` +### Blocking Versions of First/Last/Single[OrDefault] -Note the first three values (0, 1 & 2) were all ignored from the output. Alternatively, if we used `Take(3)` we would get the opposite result; i.e. we would only get the first 3 values and then the Take operator would complete the sequence. +Several of the operators described in the preceding sections end in the name `Async`. This is a little strange because normally, .NET methods that end in `Async` return a `Task` or `Task`, and yet these all return an `IObservable`. Also, as already discussed, each of these methods corresponds to a standard LINQ operator which does not generally end in `Async`. (And to further add to the confusion, some LINQ providers such as Entity Framework Core do include `Async` versions of some of these operators, but they are different. Unlike Rx, these do in fact return a `Task`, so they still produce a single value, and not an `IQueryable` or `IEnumerable`.) This naming arises from an unfortunate choice early in Rx's design. -```csharp -Observable.Range(0, 10) - .Take(3) - .Subscribe(Console.WriteLine, () => Console.WriteLine("Completed")); +If Rx were being designed from scratch today, the relevant operators in the preceding section would just have the normal names: `First`, and `FirstOrDefault`, and so on. The reason they all end with `Async` is that these were added in Rx 2.0, and Rx 1.0 had already defined operators with those names. This example uses the `First` operator: + +```cs +int v = Observable.Range(1, 10).First(); +Console.WriteLine(v); ``` -Output: +This prints out the value `1`, which is the first item returned by `Range` here. But look at the type of that variable `v`. It's not an `IObservable`, it's just an `int`. What would happen if we used this on an Rx operator that didn't immediately produce values upon subscription? Here's one example: +```cs +long v = Observable.Timer(TimeSpan.FromSeconds(2)).First(); +Console.WriteLine(v); ``` -0 -1 -2 -Completed -``` +If you run this, you'll find that the call to `First` doesn't return until a value is produced. It is a _blocking_ operator. We typically avoid blocking operators in Rx, because it's easy to create deadlocks with them. The whole point of Rx is that we can create code that reacts to events, so to just sit and wait until a specific observable source produces a value is not really in the spirit of things. If you find yourself wanting to do that, there are often better ways to achieve the results you're looking for. (Or perhaps Rx isn't good model for whatever you're doing.) -Just in case that slipped past any readers, it is the `Take` operator that completes once it has received its count. We can prove this by applying it to an infinite sequence. +If you really do need to wait for a value like this, it might be better to use the `Async` forms in conjunction with Rx's integrated support for C#'s `async`/`await`: -```csharp -Observable.Interval(TimeSpan.FromMilliseconds(100)) - .Take(3) - .Subscribe(Console.WriteLine, () => Console.WriteLine("Completed")); +```cs +long v = await Observable.Timer(TimeSpan.FromSeconds(2)).FirstAsync(); +Console.WriteLine(v); ``` -Output: +This logically has the same effect, but because we're using `await`, this won't block the calling thread while it waits for the observable source to produce a value. This might reduce the chances of deadlock. + +The fact that we're able to use `await` makes some sense of the fact that these methods end with `Async`, but you might be wondering what's going on here. We've seen that these methods all return `IObservable`, not `Task`, so how are we able to use `await`? There's a [full explanation in the Leaving Rx's World chapter](13_LeavingIObservable.md#integration-with-async-and-await), but the short answer is that Rx provides extension methods that enable this to work. When you `await` an observable sequence, the `await` will complete once the source completes, and it will return the final value that emerges from the source. This works well for operators such as `FirstAsync` and `LastAsync` that produce exactly one item. + +Note that there are occasionally situations in which values are available immediately. For example, the [`BehaviourSubject` section in chapter 3](./03_CreatingObservableSequences.md#behaviorsubject), showed that the defining feature of `BehaviourSubject` is that it always has a current value. That means that Rx's `First` method won't actually block—it will subscribe to the `BehaviourSubject`, and `BehaviourSubject.Subscribe` calls `OnNext` on its subscriber's observable before returning. That enables `First` to return a value immediately without blocking. (Of course, if you use the overload of `First` that accepts a predicate, and if the `BehaviourSubject`'s value doesn't satisfy the predicate, `First` will then block.) +### ElementAt + +There is yet another standard LINQ operator for selecting one particular element from the source: `ElementAt`. You provide this with a number indicating the position in the sequence of the element you require. In data-at-rest LINQ providers, this is logically equivalent to accessing an array element by index. Rx implements this operator, but whereas most LINQ providers' `ElementAt` implementation returns a `T`, Rx's returns an `IObservable`. Unlike with `First`, `Last`, and `Single`, Rx does not provide a blocking form of `ElementAt`. But since you can await any `IObservable`, you can always do this: + +```cs +IAisMessage fourth = await receiverHost.Message.ElementAt(4); ``` -0 -1 -2 -Completed +If your source sequence only produces five values and we ask for `ElementAt(5)`, the sequence that `ElementAt` returns will report an `ArgumentOutOfRangeException` error to its subscriber when the source completes. There are three ways we can deal with this: + +- Handle the OnError gracefully +- Use `.Skip(5).Take(1);` This will ignore the first 5 values and the only take the 6th value. +If the sequence has less than 6 elements we just get an empty sequence, but no errors. +- Use `ElementAtOrDefault` + +`ElementAtOrDefault` extension method will protect us in case the index is out of range, by pushing the `default(T)` value. Currently there is not an option to provide your own default value. + +## Temporal Filtering + +The `Take` and `TakeLast` operators let us filter out everything except elements either at the very start or very end (and `Skip` and `SkipLast` let us see everything but those), but these all require us to know the exact number of elements. What if we want to specify the cut-off not in terms of an element count, but in terms of a particular instant in time? + +In fact you've already seen one example: earlier I used `TakeUntil` to convert an endless `IObservable` into one that would complete after five minutes. This is one of a family of operators. + +### SkipWhile and TakeWhile + +In the [`Skip` and `SkipLast` section](#skip-and-skiplast), I described a sensor that produces garbage values for its first few readings. This is quite common. For example, gas monitoring sensors often need to get some component up to a correct operating temperature before they can produce accurate readings. In the example in that section, I used `Skip(5)` to ignore the first few readings, but that is a crude solution. How do we know that 5 is enough? Or might it be ready sooner, in which case 5 is too few. + +What we really want to do is discard readings until we know the readings will be valid. And that's exactly the kind of scenario that `SkipWhile` can be useful for. Suppose we have a gas sensor that reports concentrations of some particular gas, but which also reports the temperature of the sensor plate that is performing the detection. Instead of hoping that 5 readings is a sensible number to skip, we could express the actual requirement: + +```cs +const int MinimumSensorTemperature = 74; +IObservable readings = sensor.RawReadings + .SkipUntil(r => r.SensorTemperature >= MinimumSensorTemperature); ``` -### SkipWhile and TakeWhile +This directly expresses the logic we require: this will discard readings until the device is up to its minimum operating temperature. The next set of methods allows you to skip or take values from a sequence while a predicate evaluates to true. For a `SkipWhile` operation this will filter out all values until a value fails the predicate, then the remaining sequence can be returned. @@ -325,138 +370,58 @@ Completed ``` -### SkipLast and TakeLast +### SkipUntil and TakeUntil -These methods become quite self explanatory now that we understand Skip/Take and SkipWhile/TakeWhile. Both methods require a number of elements at the end of a sequence to either skip or take. The implementation of the `SkipLast` could cache all values, wait for the source sequence to complete, and then replay all the values except for the last number of elements. The Rx team however, has been a bit smarter than that. The real implementation will queue the specified number of notifications and once the queue size exceeds the value, it can be sure that it may drain a value from the queue. +In addition to `SkipWhile` and `TakeWhile`, Rx defines `SkipUntil` and `TakeUntil`. These may sound like nothing more than an alternate expression of the same idea: you might expect `SkipUntil` to do almost exactly the same thing as `SkipWhile`, with the only difference being that `SkipWhile` runs for as long as its predicate returns `true`, whereas `SkipUntil` runs for as long as its predicate returns `false`. And there is an overload of `SkipUntil` that does exactly that (and a corresponding one for `TakeUntil`). If that's all these were they wouldn't be interesting. However, there are overloads of `SkipUntil` and `TakeUntil` that enable us to do things we can't do with `SkipWhile` and `TakeWhile`. -```csharp -var subject = new Subject(); -subject - .SkipLast(2) - .Subscribe(Console.WriteLine, () => Console.WriteLine("Completed")); -Console.WriteLine("Pushing 1"); -subject.OnNext(1); -Console.WriteLine("Pushing 2"); -subject.OnNext(2); -Console.WriteLine("Pushing 3"); -subject.OnNext(3); -Console.WriteLine("Pushing 4"); -subject.OnNext(4); -subject.OnCompleted(); -``` +You've already seen one example. The [`FirstAsync` and `FirstOrDefaultAsync`](#firstasync-and-firstordefaultasync) included an example that used an overload of `TakeUntil` that accepted a `DateTimeOffset`. This wraps any `IObservable`, returning an `IObservable` that will forward everything from the source until the specified time, at which point it will immediately complete (and will unsubscribe from the underlying source). -Output: - -``` -Pushing 1 -Pushing 2 -Pushing 3 -1 -Pushing 4 -2 -Completed +We couldn't have achieved this with `TakeWhile`, because that consults its predicate only when the source produces an item. If we want the source to complete at a specific time, the only way we could do that with `TakeWhile` is if its source happens to produce an item at the exact moment we wanted to finish. `TakeWhile` will only ever complete as a result of its source producing an item. `TakeUntil` can complete asynchronously. If we specified a time 5 minutes into the future, it doesn't matter if the source is completely idle when that time arrives. `TakeUntil` will complete anyway. (It relies on [Schedulers](11_SchedulingAndThreading.md#schedulers) to be able to do this.) -``` +We don't have to use a time, `TakeUntil` offers an overload that accept a second `IObservable`. This enables us to tell it to stop when something interesting happens, without needing to know in advance exactly when that will occur. This overload of `TakeUntil` forwards items from the source until that second `IObservable` produces a value. `SkipUntil` offers a similar overload in which the second `IObservable` determines when it should start forwarding items from the source. -Unlike `SkipLast`, `TakeLast` does have to wait for the source sequence to complete to be able to push its results. As per the example above, there are `Console.WriteLine` calls to indicate what the program is doing at each stage. +**Note**: these overloads require the second observable to produce a value in order to trigger the start or end. If that second observable completes without producing a single notification, then it has no effect—`TakeUntil` will continue to take items indefinitely; `SkipUntil` will never produce anything. In other words, these operators would treat `Observable.Empty()` as being effectively equivalent to `Observable.Never()`. -```csharp -var subject = new Subject(); -subject - .TakeLast(2) - .Subscribe(Console.WriteLine, () => Console.WriteLine("Completed")); -Console.WriteLine("Pushing 1"); -subject.OnNext(1); -Console.WriteLine("Pushing 2"); -subject.OnNext(2); -Console.WriteLine("Pushing 3"); -subject.OnNext(3); -Console.WriteLine("Pushing 4"); -subject.OnNext(4); -Console.WriteLine("Completing"); -subject.OnCompleted(); -``` +### Distinct and DistinctUntilChanged -Output: +`Distinct` is yet another standard LINQ operator. It removes duplicates from a sequence. To do this, it needs to remember all the values that its source has ever produced, so that it can filter out any items that it has seen before. Rx includes an implementation of `Distinct`, and this example uses it to display the unique identifier of vessels generating AIS messages, but ensuring that we only display each such identifier the first time we see it: -``` -Pushing 1 -Pushing 2 -Pushing 3 -Pushing 4 -Completing -3 -4 -Completed +```cs +IObservable newIds = receiverHost.Messages + .Select(m => m.Mmsi) + .Distinct(); +newIds.Subscribe(id => Console.WriteLine($"New vessel: {id}")); ``` -### SkipUntil and TakeUntil +(This is leaping ahead a little—it uses `Select`, which we'll get to in [the Transformation of Sequences Chapter](06_Transformation.md). However, this is a very widely used LINQ operator, so you are probably already familiar with it. I'm using it here to extract just the MMSI—the vessel identifier—from the message.) -Our last two methods make an exciting change to the methods we have previously looked. These will be the first two methods that we have discovered together that require two observable sequences. - -`SkipUntil` will skip all values until any value is produced by a secondary observable sequence. - -```csharp -var subject = new Subject(); -var otherSubject = new Subject(); -subject - .SkipUntil(otherSubject) - .Subscribe(Console.WriteLine, () => Console.WriteLine("Completed")); -subject.OnNext(1); -subject.OnNext(2); -subject.OnNext(3); -otherSubject.OnNext(Unit.Default); -subject.OnNext(4); -subject.OnNext(5); -subject.OnNext(6); -subject.OnNext(7); -subject.OnNext(8); +This example is fine if we are only interested in vessels' identifiers. But what if we want to inspect the detail of these messages? How can we retain the ability to see messages only for vessels we've never previously heard of, but still be able to look at the information in those message? The use of `Select` to extract the id stops us from doing this. Fortunately, `Distinct` provides an overload enabling us to change how it determines uniqueness. Instead of getting `Distinct` to look at the values it is processing, we can provide it with a function that lets us pick whatever characteristics we like. So instead of filtering the stream down to values that have never been seen before, we can instead filter the stream down to values that have some particular property or combination of properties we've never seen before. Here's a simple example: -subject.OnCompleted(); +```cs +IObservable newVesselMessages = receiverHost.Messages + .Distinct(m => m.Mmsi); ``` -Output: +Here, the input to `Distinct` is now an `IObservable`. (In the preceding example it was actually `IObservable`, because the `Select` clause picked out just the MMSI.) So `Distinct` now receives the entire `IAisMessage` each time the source emits one. But because we've supplied a callback, it's not going try and compare entire `IAisMessage` messages with one another. Instead, each time it receives one, it passes that to our callback, and then looks at the value our callback returns, and compares that with the values the callback returned for all previously seen messages, and lets the message through only if that's new. -``` -4 -5 -6 -7 -Completed +So the effect is similar to before. Messages will be allowed through only if they have an MMSI not previously seen. But the difference is that the `Distinct` operator's output here is `IObservable`, so when `Distinct` lets an item through, the entire original message remains available. -``` +In addition to the standard LINQ `Distinct` operator, Rx also provides `DistinctUntilChanged`. This only lets through notifications when something has changed, which it achieved by filtering out only adjacent duplicates. For example, given the sequence `1,2,2,3,4,4,5,4,3,3,2,1,1` it would produce `1,2,3,4,5,4,3,2,1`. Whereas `Distinct` remembers every value ever produced, `DistinctUntilChanged` remembers only the most recently emitted value, and filters out new values if and only if they match that most recent value. -Obviously, the converse is true for `TakeWhile`. When the secondary sequence produces a value, then the `TakeWhile` operator will complete the output sequence. +This example uses `DistinctUntilChanged` to detect when a particular vessel reports a change in `NavigationStatus`. -```csharp -var subject = new Subject(); -var otherSubject = new Subject(); -subject - .TakeUntil(otherSubject) - .Subscribe(Console.WriteLine, () => Console.WriteLine("Completed")); -subject.OnNext(1); -subject.OnNext(2); -subject.OnNext(3); -otherSubject.OnNext(Unit.Default); -subject.OnNext(4); -subject.OnNext(5); -subject.OnNext(6); -subject.OnNext(7); -subject.OnNext(8); - -subject.OnCompleted(); +```cs +uint exampleMmsi = 235009890; +IObservable statusChanges = receiverHost.Messages + .Where(v => v.Mmsi == exampleMmsi) + .OfType() + .DistinctUntilChanged(m => m.NavigationStatus) + .Skip(1); ``` -Output: - -``` -1 -2 -3 -Completed -``` +For example, if the vessel had repeatedly been reporting a status of `AtAnchor`, `DistinctUntilChanged` would drop each such message because the status was the same as it had previously been. But if the status were to change to `UnderwayUsingEngine`, that would cause `DistinctUntilChanged` to let the first message reporting that status through. It would then not allow any further messages through until there was another change in value, either back to `AtAnchor`, or to something else such as `Moored`. (The `Skip(1)` on the end is there because `DistinctUntilChanged` always lets through the very first message it sees. We have no way of knowing whether that actually represents a change in status, but it is very likely not to: ships report their status every few minutes, but they change that status much less often, so the first time we receive a report of a ship's status, it probably doesn't represent a change of status. By dropping that first item, we ensure that `statusChanges` provides notifications only when we can be certain that the status changed.) -That was our quick run through of the filtering methods available in Rx. While they are pretty simple, as we will see, the power in Rx is down to the composability of its operators. +That was our quick run through of the filtering methods available in Rx. While they are relatively simple, as we have already begun to see, the power in Rx is down to the composability of its operators. -These operators provide a good introduction to the filtering in Rx. The filter operators are your first stop for managing the potential deluge of data we can face in the information age. We now know how to remove unmatched data, duplicate data or excess data. Next, we will move on to the other two sub classifications of the reduction operators, inspection and aggregation. \ No newline at end of file +The filter operators are your first stop for managing the potential deluge of data we can face in this information-rich age. We now know how to apply various criteria to remove data. Next, we will move on to operators that can transform data. \ No newline at end of file diff --git a/content/06_Inspection.md b/content/06_Inspection.md deleted file mode 100644 index b031a74..0000000 --- a/content/06_Inspection.md +++ /dev/null @@ -1,353 +0,0 @@ ---- -title: Inspection ---- - -# Inspection - -Making sense of all the data we consume is not always about just filtering out the redundant and superfluous. Sometimes we need to pluck out data that is relevant or validate that a sequence even meets our expectations. Does this data have any values that meet this specification? Is this specific value in the sequence? Get me that specific value from the sequence! - -In the last chapter we looked at a series of ways to reduce your observable sequence via a variety of filters. Next we will look at operators that provide inspection functionality. Most of these operators will reduce your observable sequence down to a sequence with a single value in it. As the return value of these methods is not a scalar (it is still `IObservable`) these methods do not actually satisfy our definition of catamorphism, but suit our examination of reducing a sequence to a single value. - -The series of methods we will look at next are useful for inspecting a given sequence. Each of them returns an observable sequence with the single value containing the result. This proves useful, as by their nature they are asynchronous. They are all quite simple so we will be brief with each of them. - -## Any - -First we can look at the parameterless overload for the extension method `Any`. This will simply return an observable sequence that has the single value of `false` if the source completes without any values. If the source does produce a value however, then when the first value is produced, the result sequence will immediately push `true` and then complete. If the first notification it gets is an error, then it will pass that error on. - -```csharp -var subject = new Subject(); -subject.Subscribe(Console.WriteLine, () => Console.WriteLine("Subject completed")); -var any = subject.Any(); - -any.Subscribe(b => Console.WriteLine("The subject has any values? {0}", b)); - -subject.OnNext(1); -subject.OnCompleted(); -``` - -Output: - -``` -1 -The subject has any values? True -subject completed -``` - -If we now remove the OnNext(1), the output will change to the following - -``` -subject completed -The subject has any values? False -``` - -If the source errors it would only be interesting if it was the first notification, otherwise the `Any` method will have already pushed true. If the first notification is an error then `Any` will just pass it along as an `OnError` notification. - -```csharp -var subject = new Subject(); -subject.Subscribe(Console.WriteLine, - ex => Console.WriteLine("subject OnError : {0}", ex), - () => Console.WriteLine("Subject completed")); -var any = subject.Any(); - -any.Subscribe(b => Console.WriteLine("The subject has any values? {0}", b), - ex => Console.WriteLine(".Any() OnError : {0}", ex), - () => Console.WriteLine(".Any() completed")); - -subject.OnError(new Exception()); -``` - -Output: - -``` -subject OnError : System.Exception: Fail -.Any() OnError : System.Exception: Fail -``` - -The `Any` method also has an overload that takes a predicate. This effectively makes it a `Where` with an `Any` appended to it. - -```csharp -subject.Any(i => i > 2); -// Functionally equivalent to -subject.Where(i => i > 2).Any(); -``` - -As an exercise, write your own version of the two `Any` extension method overloads. While the answer may not be immediately obvious, we have covered enough material for you to create this using the methods you know... - -Example of the `Any` extension methods written with `Observable.Create`: - -```csharp -public static IObservable MyAny(this IObservable source) -{ - return Observable.Create( - o => - { - var hasValues = false; - return source - .Take(1) - .Subscribe( - _ => hasValues = true, - o.OnError, - () => - { - o.OnNext(hasValues); - o.OnCompleted(); - }); - }); -} - -public static IObservable MyAny( - this IObservable source, - Func predicate) -{ - return source - .Where(predicate) - .MyAny(); -} -``` - -## All - -The `All`() extension method works just like the `Any` method, except that all values must meet the predicate. As soon as a value does not meet the predicate a `false` value is returned then the output sequence completed. If the source is empty, then `All` will push `true` as its value. As per the `Any` method, and errors will be passed along to the subscriber of the `All` method. - -```csharp -var subject = new Subject(); -subject.Subscribe(Console.WriteLine, () => Console.WriteLine("Subject completed")); -var all = subject.All(i => i < 5); -all.Subscribe(b => Console.WriteLine("All values less than 5? {0}", b)); - -subject.OnNext(1); -subject.OnNext(2); -subject.OnNext(6); -subject.OnNext(2); -subject.OnNext(1); -subject.OnCompleted(); -``` - -Output: - -``` -1 -2 -6 -All values less than 5? False -all completed -2 -1 -subject completed -``` - -Early adopters of Rx may notice that the `IsEmpty` extension method is missing. You can easily replicate the missing method using the `All` extension method. - -```csharp -// IsEmpty() is deprecated now. -// var isEmpty = subject.IsEmpty(); -var isEmpty = subject.All(_ => false); -``` - -## Contains - -The `Contains` extension method overloads could sensibly be overloads to the `Any` extension method. The `Contains` extension method has the same behavior as `Any`, however it specifically targets the use of `IComparable` instead of the usage of predicates and is designed to seek a specific value instead of a value that fits the predicate. I believe that these are not overloads of `Any` for consistency with `IEnumerable`. - -```csharp -var subject = new Subject(); -subject.Subscribe( - Console.WriteLine, - () => Console.WriteLine("Subject completed")); - -var contains = subject.Contains(2); - -contains.Subscribe( - b => Console.WriteLine("Contains the value 2? {0}", b), - () => Console.WriteLine("contains completed")); - -subject.OnNext(1); -subject.OnNext(2); -subject.OnNext(3); - -subject.OnCompleted(); -``` - -Output: - -``` -1 -2 -Contains the value 2? True -contains completed -3 -Subject completed -``` - -There is also an overload to `Contains` that allows you to specify an implementation of `IEqualityComparer` other than the default for the type. This can be useful if you have a sequence of custom types that may have some special rules for equality depending on the use case. - -## DefaultIfEmpty - -The `DefaultIfEmpty` extension method will return a single value if the source sequence is empty. Depending on the overload used, it will either be the value provided as the default, or `Default(T)`. `Default(T)` will be the zero value for _struct_ types and will be `null` for classes. If the source is not empty then all values will be passed straight on through. - -In this example the source produces values, so the result of `DefaultIfEmpty` is just the source. - -```csharp -var subject = new Subject(); - -subject.Subscribe( - Console.WriteLine, - () => Console.WriteLine("Subject completed")); - -var defaultIfEmpty = subject.DefaultIfEmpty(); - -defaultIfEmpty.Subscribe( - b => Console.WriteLine("defaultIfEmpty value: {0}", b), - () => Console.WriteLine("defaultIfEmpty completed")); - -subject.OnNext(1); -subject.OnNext(2); -subject.OnNext(3); - -subject.OnCompleted(); -``` - -Output: - -``` -1 -defaultIfEmpty value: 1 -2 -defaultIfEmpty value: 2 -3 -defaultIfEmpty value: 3 -Subject completed -defaultIfEmpty completed -``` - -If the source is empty, we can use either the default value for the type (i.e. 0 for int) or provide our own value in this case 42. - -```csharp -var subject = new Subject(); - -subject.Subscribe( - Console.WriteLine, - () => Console.WriteLine("Subject completed")); - -var defaultIfEmpty = subject.DefaultIfEmpty(); - -defaultIfEmpty.Subscribe( - b => Console.WriteLine("defaultIfEmpty value: {0}", b), - () => Console.WriteLine("defaultIfEmpty completed")); - -var default42IfEmpty = subject.DefaultIfEmpty(42); - -default42IfEmpty.Subscribe( - b => Console.WriteLine("default42IfEmpty value: {0}", b), - () => Console.WriteLine("default42IfEmpty completed")); - -subject.OnCompleted(); -``` - -Output: - -``` -Subject completed -defaultIfEmpty value: 0 -defaultIfEmpty completed -default42IfEmpty value: 42 -default42IfEmpty completed -``` - -## ElementAt - -The `ElementAt` extension method allows us to "cherry pick" out a value at a given index. Like the `IEnumerable` version it is uses a 0 based index. - -```csharp -var subject = new Subject(); - -subject.Subscribe( - Console.WriteLine, - () => Console.WriteLine("Subject completed")); - -var elementAt1 = subject.ElementAt(1); - -elementAt1.Subscribe( - b => Console.WriteLine("elementAt1 value: {0}", b), - () => Console.WriteLine("elementAt1 completed")); - -subject.OnNext(1); -subject.OnNext(2); -subject.OnNext(3); - -subject.OnCompleted(); -``` - -Output: - -``` -1 -2 -elementAt1 value: 2 -elementAt1 completed -3 -subject completed -``` - -As we can't check the length of an observable sequence it is fair to assume that sometimes this method could cause problems. If your source sequence only produces five values and we ask for `ElementAt(5)`, the result sequence will error with an `ArgumentOutOfRangeException` inside when the source completes. - -There are three options we have: - -- Handle the OnError gracefully -- Use `.Skip(5).Take(1);` This will ignore the first 5 values and the only take the 6th value. -If the sequence has less than 6 elements we just get an empty sequence, but no errors. -- Use ElementAtOrDefault - -`ElementAtOrDefault` extension method will protect us in case the index is out of range, by pushing the `Default(T)` value. Currently there is not an option to provide your own default value. - -## SequenceEqual - -Finally `SequenceEqual` extension method is perhaps a stretch to put in a chapter that starts off talking about catamorphism and fold, but it does serve well for the theme of inspection. This method allows us to compare two observable sequences. As each source sequence produces values, they are compared to a cache of the other sequence to ensure that each sequence has the same values in the same order and that the sequences are the same length. This means that the result sequence can return `false` as soon as the source sequences produce diverging values, or `true` when both sources complete with the same values. - -```csharp -var subject1 = new Subject(); - -subject1.Subscribe( - i=>Console.WriteLine("subject1.OnNext({0})", i), - () => Console.WriteLine("subject1 completed")); - -var subject2 = new Subject(); - -subject2.Subscribe( - i=>Console.WriteLine("subject2.OnNext({0})", i), - () => Console.WriteLine("subject2 completed")); - -var areEqual = subject1.SequenceEqual(subject2); - -areEqual.Subscribe( - i => Console.WriteLine("areEqual.OnNext({0})", i), - () => Console.WriteLine("areEqual completed")); - -subject1.OnNext(1); -subject1.OnNext(2); - -subject2.OnNext(1); -subject2.OnNext(2); -subject2.OnNext(3); - -subject1.OnNext(3); - -subject1.OnCompleted(); -subject2.OnCompleted(); -``` - -Output: - -``` -subject1.OnNext(1) -subject1.OnNext(2) -subject2.OnNext(1) -subject2.OnNext(2) -subject2.OnNext(3) -subject1.OnNext(3) -subject1 completed -subject2 completed -areEqual.OnNext(True) -areEqual completed -``` - -This chapter covered a set of methods that allow us to inspect observable sequences. The result of each, generally, returns a sequence with a single value. We will continue to look at methods to reduce our sequence until we discover the elusive functional fold feature. \ No newline at end of file diff --git a/content/06_Transformation.md b/content/06_Transformation.md new file mode 100644 index 0000000..cb09188 --- /dev/null +++ b/content/06_Transformation.md @@ -0,0 +1,353 @@ +--- +title: Transformation of sequences +--- + +# Transformation of sequences + +The values from the sequences we consume are not always in the format we need. Sometimes there is more information than we need, and we need to pick out just the values of interest. Sometimes each value needs to be expanded either into a richer object or into more values. + +Up until now, we have looked at creation of sequences, transition into sequences, and, the reduction of sequences by filtering. In this chapter we will look at _transforming_ sequences. + + +## Select + +The most straightforward transformation method is `Select`. It allows you provide a function that takes a value of `TSource` and return a value of `TResult`. The signature for `Select` reflects its ability to transform a sequence's elements from one type to another type, i.e. `IObservable` to `IObservable`. + +```cs +IObservable Select( + this IObservable source, + Func selector) +``` + +You don't have to change the type—`TSource` and `TResult` can be the same if you want. This first example transforms a sequence of integers by adding 3, resulting in another sequence of integers. + +```cs +IObservable source = Observable.Range(0, 5); +source.Select(i => i+3) + .Dump("+3") +``` + +This uses the `Dump` extension method we defined at the start of [the Filtering Chapter](05_Filtering.md). It produces the following output: + +``` ++3 --> 3 ++3 --> 4 ++3 --> 5 ++3 --> 6 ++3 --> 7 ++3 completed +``` + +This next example transforms values in a way that changes their type. It converts integer values to characters. + +```cs +Observable.Range(1, 5); + .Select(i => (char)(i + 64)) + .Dump("char"); +``` + +Output: + +``` +char --> A +char --> B +char --> C +char --> D +char --> E +char completed +``` + +This example transforms our sequence of integers to a sequence where the elements have an anonymous type: + +```csharp +Observable.Range(1, 5) + .Select(i => new { Number = i, Character = (char)(i + 64) }) + .Dump("anon"); +``` + +Output: + +``` +anon --> { Number = 1, Character = A } +anon --> { Number = 2, Character = B } +anon --> { Number = 3, Character = C } +anon --> { Number = 4, Character = D } +anon --> { Number = 5, Character = E } +anon completed +``` + +`Select` is one of the standard LINQ operators supported by C#'s query expression syntax, so we could have written that last example like this: + +```csharp +var query = from i in Observable.Range(1, 5) + select new {Number = i, Character = (char) (i + 64)}; + +query.Dump("anon"); +``` + +In Rx, `Select` has another overload, in which the `selector` function takes two values. The additional argument is the element's index in the sequence. Use this method if the index of the element in the sequence is important to your selector function. + +## SelectMany + +Whereas `Select` produces one output for each input, `SelectMany` enables each input element to be transformed into any number of outputs. To see how this can work, let's first look at an example that uses just `Select`: + +```cs +Observable + .Range(1, 5) + .Select(i => new string((char)(i+64), i)) + .Dump("strings"); +``` + +which produces this output: + +``` +strings-->A +strings-->BB +strings-->CCC +strings-->DDDD +strings-->EEEEE +strings completed +``` + +As you can see, for each of the numbers produced by `Range`, our output contains a string whose length is that many characters. What if, instead of transforming each number into a string, we transformed it into an `IObservable`. We can do that just by adding `.ToObservable()` after constructing the string: + +```cs +Observable + .Range(1, 5) + .Select(i => new string((char)(i+64), i).ToObservable()) + .Dump("sequences"); +``` + +(Alternatively, we could have replaced the selection expression with `i => Observable.Repeat((char)(i+64), i)`. Either has exactly the same effect.) The output isn't terribly useful: + +``` +strings-->System.Reactive.Linq.ObservableImpl.ToObservableRecursive`1[System.Char] +strings-->System.Reactive.Linq.ObservableImpl.ToObservableRecursive`1[System.Char] +strings-->System.Reactive.Linq.ObservableImpl.ToObservableRecursive`1[System.Char] +strings-->System.Reactive.Linq.ObservableImpl.ToObservableRecursive`1[System.Char] +strings-->System.Reactive.Linq.ObservableImpl.ToObservableRecursive`1[System.Char] +strings completed +``` + +We have an observable sequence of observable sequences. But look at what happens if we now replace that `Select` with a `SelectMany`: + +```cs +Observable + .Range(1, 5) + .SelectMany(i => new string((char)(i+64), i).ToObservable()) + .Dump("chars"); +``` + +This gives us an `IObservable`, with this output: + +``` +chars-->A +chars-->B +chars-->B +chars-->C +chars-->C +chars-->D +chars-->C +chars-->D +chars-->E +chars-->D +chars-->E +chars-->D +chars-->E +chars-->E +chars-->E +chars completed +``` + +The order has become a little scrambled, but if you look carefully you'll see that the number of occurrences of each letter is the same as when we were emitting strings. There is just one `A`, for example, but `C` appears three times, and `E` five times. + +`SelectMany` expects the transformation function to return an `IObservable` for each input, and it then combines the result of those back into a single result. The LINQ to Objects equivalent is a little less chaotic. If you were to run this: + +```cs +Enumerable + .Range(1, 5) + .SelectMany(i => new string((char)(i+64), i)) + .ToList() +``` + +it would produce a list with these elements: + +``` +[ A, B, B, C, C, C, D, D, D, D, E, E, E, E, E ] +``` + +The order is less odd. It's worth exploring the reasons for this in a little more detail. + +### `IEnumerable` vs. `IObservable` `SelectMany` + +`IEnumerable` is pull based—sequences produce elements only when asked. `Enumerable.SelectMany` pulls items from its sources in a very particular order. It begins by asking its source `IEnumerable` (the one returned by `Range` in the preceding example) for the first value. `SelectMany` then invokes our callback, passing this first item, and then enumerates everything in the `IEnumerable` our callback returns. Only when it has exhausted this does it ask the source (`Range`) for a second item. Again, it passes that second item to our callback and then fully enumerates the `IEnumerable`, we return, and so on. So we get everything from the first nested sequence first, then everything from the second, etc. + +`Enumerable.SelectMany` is able to proceed in this way for two reasons. First, the pull-based nature of `IEnumerable` enables it to decide on the order in which it processes things. Second, with `IEnumerable` it is normal for operations to block, i.e., not to return until they have something for us. When the preceding example calls `ToList`, it won't return until it has fully populated a `List` with all of the results. + +Rx is not like that. First, consumers don't get to tell sources when to produce each item—sources emit items when they are ready to. Second, Rx typically models ongoing processes, so we don't expect method calls to block until they are done. There are some cases where Rx sequences will naturally produce all of their items very quickly and complete as soon as they can, but the kinds of information sources that we tend to want model with Rx typically don't behave that way. So most operations in Rx do not block—they immediately return something (such as an `IObservable`, or an `IDisposable` representing a subscription) and will then produce values later. + +The Rx version of the example we're currently examining is in fact one of these unusual cases where each of the sequences emits items as soon as it can. Logically speaking, all of the nested `IObservable` sequences are in progress concurrently. The result is a mess because each of the observable sources here attempts to produce every element as quickly as the source can consume them. The fact that they end up being interleaved has to do with the way these kinds of observable sources use Rx's _scheduler_ system, which we will describe in the [Scheduling and Threading chapter](11_SchedulingAndThreading.md). Schedulers ensure that even when we are modelling logically concurrent processes, the rules of Rx are maintained, and observers of the output of `SelectMany` will only be given one item at a time. The following marble diagram shows the events that lead to the scrambled output we see: + +![An Rx marble diagram showing 7 observables. The first illustrates the Range operator producing the values 1 through 5. These are colour coded as follows: green, blue, yellow, red, and pink. These colours correspond to observables further down in the diagram, as will be described shortly. The items in this first observable are not evenly spaced. The 2nd value immediately follows the 1st, but there are gaps before the 3rd, 4th, and 5th items. These gaps correspond with activity shown further down in the diagram. Beneath the first observable is code invoking the SelectMany operator, passing this lambda: "i => new string((char)(i+64),i).ToObservable()". Beneath this are 6 more observables. The first 5 show the individual observables that the lambda produces for each of the inputs. These are colour coded to show how they correspond: the first observable's item is green, to indicate that this observable corresponds to the first item emitted by Range, the second observable's items are blue showing that it corresponds to the second item emitted by Range, and so on with the same colour sequence as described earlier for Range. Each observable's first item is aligned horizontally with the corresponding item from Range, signifying the fact that each one of these observables starts when the Range observable emits a value. These 5 observables show the values produced by the observable returned by the lambda for each of the 5 values from Range. The first of these child observables shows a single item with value 'A', vertically aligned with the value 1 from the Range observable to indicate that this item is produced immediately when Range produces its first value. This child observable then immediately ends, indicating that only one item was produced. The second child observable contains two 'B' values, the third three 'C' values, the fourth four 'D' values and the fifth give 'E' values. The horizontal positioning of these items indicates that all of first 6 observables in the diagram (the Range observable, and the 5 observables produced by the lambda) overlap to some extent. Initially this overlap is minimal: the first of the lambda-produced observables starts at the same time the Range produces its first value so these two observables overlap, but since this first child completes immediately it overlaps with nothing else. The second child starts when Range produces its second value, and manages to produce two values and then completes before anything else happens, so thus far, the child observables produced by the lambda overlap only with the Range one, and not with each other. However, when Range produces its third value, the resulting child observable produces two 'C' values, but the next thing that happens (as denoted by the horizontal position of the items) is that Range manages to produce its 4th value and its corresponding child observable produces the first of its 'D' values next. After this, the third child observable produces its third and final 'C', so this third child overlaps not just with the Range observable, but also with the fourth child. Then the fourth observable produces its second 'D'. Then the Range produces its fifth and final value, and the corresponding child observable produces its first 'E'. Then the fourth and fifth child observable alternate, producing 'D', 'E' and 'D', at which point the fourth child observable is complete, and now the fifth child observable produces its final three 'E' values without interruption, because by this time it is the only observable still running. At the bottom of the diagram is the 7th observable representing the output of the SelectMany. This shows all the of the values from each of the 5 child observables each with the exact same horizontal position (signifying that the observable returned by SelectMany produces a value whenever any of its child observables produces a value). So we can see that this output observable produces the sequence 'ABBCCDCDEDEDEEE', which is exactly what we saw in the example output earlier.](GraphicsIntro/Ch06-Transformation-Marbles-Select-Many-Marbles.svg) + +We can make a small tweak to prevent the child sequences all from trying to run at the same time. (This also uses `Observable.Repeat` instead of the rather indirect route of constructing a `string` and then calling `ToObservable` on that. I did that in earlier examples to emphasize the similarity with the LINQ to Objects example, but you wouldn't really do it that way in Rx.) + +```cs +Observable + .Range(1, 5) + .SelectMany(i => Observable.Repeat((char)(i+64), i).Delay(TimeSpan.FromMilliseconds(i * 100))) + .Dump("chars"); +``` + +Now we get output consistent with the `IEnumerable` version: + +``` +chars-->A +chars-->B +chars-->B +chars-->C +chars-->C +chars-->C +chars-->D +chars-->D +chars-->D +chars-->D +chars-->E +chars-->E +chars-->E +chars-->E +chars-->E +chars completed +``` + +This clarifies that `SelectMany` lets you produce a sequence for each item that the source produces, and to have all of the items from all of those new sequences flattened back out into one sequence that contains everything. While that might make it easier to understand, you wouldn't want to introduce this sort of delay in reality purely for the goal of making it easier to understand. These delays mean it will take about a second and a half for all the elements to emerge. This marble diagram shows that the code above produces a sensible-looking ordering by making each child observable produce a little bunch of items, and we've just introduced dead time to get the separation: + +![An Rx marble diagram which, like the preceding diagram, shows 7 observables. The first illustrates the Range operator producing the values 1 through 5. These are colour coded as follows: green, blue, yellow, red, and pink. These colours correspond to observables further down in the diagram, as will be described shortly.](GraphicsIntro/Ch06-Transformation-Marbles-Select-Many-Marbles-Delay.svg) + +I introduced these gaps purely to provide a slightly less confusing example, but if you really wanted this sort of strictly-in-order handling, you wouldn't use `SelectMany` in this way in practice. For one thing, it's not completely guaranteed to work. (If you try this example, but modify it to use shorter and shorter timespans, eventually you reach a point where the items start getting jumbled up again. And since .NET is not a real-time programming system, there's actually no safe timespan you can specific here that guarantees the ordering.) If you absolutely need all the items from the first child sequence before seeing any from the second, there's actually a robust way to ask for that: + +```cs +Observable + .Range(1, 5) + .Select(i => Observable.Repeat((char)(i+64), i)) + .Concat()) + .Dump("chars"); +``` + +However, that would not have been a good way to show what `SelectMany` does, since this no longer uses it. (It uses `Concat`, which will be discussed in the [Combining Sequences](09_CombiningSequences.md) chapter.) We use `SelectMany` either when we know we're unwrapping a single-valued sequence, or when we don't have specific ordering requirements, and want to take elements as and when they emerge from child observables. + +### The Significance of SelectMany + +If you've been reading this book's chapters in order, you had already seen two examples of `SelectMany` in earlier chapters. The first example in the [**LINQ Operators and Composition** section of Chapter 2](./02_KeyTypes.md#linq-operators-and-composition) used it. Here's the relevant code: + +```cs +IObservable onoffs = + from _ in src + from delta in Observable.Return(1, scheduler).Concat(Observable.Return(-1, scheduler).Delay(minimumInactivityPeriod, scheduler)) + select delta; +``` + +(If you're wondering where the call to `SelectMany` is in that, remember that if a Query Expression contains two `from` clauses, the C# compiler turns those into a call to `SelectMany`.) This illustrates a common pattern in Rx, which might be described as fanning out, and then back in again. + +As you may recall, this example worked by creating a new, short-lived `IObservable` for each item produced by `src`. (These child sequences, represented by the `delta` range variable in the example, produce the value `1`, and then after the specified `minimumActivityPeriod`, they produce `-1`. This enabled us to keep count of the number of recent events emitted.) This is the _fanning out_ part, where items in a source sequence produce new observable sequences. `SelectMany` is crucial in these scenarios because it enables all of those new sequences to be flattened back out into a single output sequence. + +The second place I used `SelectMany` was slightly different: it was the final example of the [**Representing Filesystem Events in Rx** section in Chapter 3](./03_CreatingObservableSequences.md#representing-filesystem-events-in-rx). Although that example also combined multiple observable sources into a single observable, that list of observables was fixed: there was one for each of the different events from `FileSystemWatcher`. It used a different operator `Merge` (which we'll get to in [Combining Sequences](09_CombiningSequences.md)), which was simpler to use in that scenario because you just pass it the list of all the observables you'd like to combine. However, because of a few other things this code wanted to do (including deferred startup, automated disposal, and sharing a single source when multiple subscribers were active), the particular combination of operators used to achieve this meant our merging code that returned an `IObservable`, needed to be invoked as a transforming step. If we'd just used `Select`, the result would have been an `IObservable>`. The structure of the code meant that it would only ever produce a single `IObservable`, so the double-wrapped type would be rather inconvenient. `SelectMany` is very useful in these scenarios. If composition of operators has introduced an extra layer of observables-in-observables that you don't want, `SelectMany` can unwrap one layer for you. + +These two cases—fanning out then back in, and removing or avoiding a layer of observables of observables—come up quite often, which makes `SelectMany` an important method. (It's not surprising that I was unable to avoid using it in earlier examples.) + +As it happens, `SelectMany` is also a particularly important operator in the mathematical theory that Rx is based on. It is a fundamental operator, in the sense that it is possible to build many other Rx operators with it. [Section 'Recreating other operators with `SelectMany`' in Appendix D](./D_AlgebraicUnderpinnings.md#recreating-other-operators-with-selectmany) shows how you can implement `Select` and `Where` using `SelectMany`. + +## Cast + +C#'s type system is not omniscient. Sometimes we might know something about the type of the values emerging from an observable source that is not reflected in that source's type. This might be based on domain-specific knowledge. For example, with the AIS messages broadcast by ships, we might know that if the message type is 3, it will contain navigation information. That means we could write this: + +```cs +IObservable type3 = receiverHost.Messages + .Where(v => v.MessageType == 3) + .Cast(); +``` + +This uses `Cast`, a standard LINQ operator that we can use whenever we know that the items in some collection are of some more specific type than the type system has been able to deduce. + +The difference between `Cast` and the [`OfType` operator shown in Chapter 5](./05_Filtering.md#oftype) is the way in which they handle items that are not of the specified type. `OfType` is a filtering operator, so it just filters out any items that are not of the specified type. But with `Cast` (as with a normal C# cast expression) we are asserting that we expect the source items to be of the specified type, so the observable returned by `Cast` will invoke its subscriber's `OnError` if its source produces an item that is not compatible with the specified type. + +This distinction might be easier to see if we recreate the functionality of `Cast` and `OfType` using other more fundamental operators. + +```csharp +// source.Cast(); is equivalent to +source.Select(i => (int)i); + +// source.OfType(); +source.Where(i => i is int).Select(i => (int)i); +``` + +## Materialize and Dematerialize + +The `Materialize` operator transforms a source of `IObservable` into one of type `IObservable>`. It will provide one `Notification` for each item the source produces, and, if the sourced terminates, it will produce one final `Notification` indicating whether it completed successfully or with an error. + +This can be useful because it produces objects that describe a whole sequence. If you wanted to record the output of an observable in a way that could later be replayed...well you'd probably use a `ReplaySubject` because it is designed for precisely that job. But if you wanted to be able to do something other than merely replaying the sequence—inspecting the items or maybe even modifying them before replying, you might want to write your own code to store items. `Notification` can be helpful because it enables you to represent everything a source does in a uniform way. You don't need to store information about whether or how the sequence terminates separately—this information is just the final `Notification`. + +You could imagine using this in conjunction with `ToArray` in a unit test. This would enable you to get an array of type `Notification[]` containing a complete description of everything the source did, making it easy to write tests that ask, say, what the third item to emerge from the sequence was. (The Rx.NET source code itself uses `Notification` in many of its tests.) + +If we materialize a sequence, we can see the wrapped values being returned. + +```csharp +Observable.Range(1, 3) + .Materialize() + .Dump("Materialize"); +``` + +Output: + +``` +Materialize --> OnNext(1) +Materialize --> OnNext(2) +Materialize --> OnNext(3) +Materialize --> OnCompleted() +Materialize completed +``` + +Note that when the source sequence completes, the materialized sequence produces an 'OnCompleted' notification value and then completes. `Notification` is an abstract class with three implementations: + + * OnNextNotification + * OnErrorNotification + * OnCompletedNotification + +`Notification` exposes four public properties to help you inspect it: `Kind`, `HasValue`, `Value` and `Exception`. Obviously only `OnNextNotification` will return true for `HasValue` and have a useful implementation of `Value`. Similarly, `OnErrorNotification` is the only implementation that will have a value for `Exception`. The `Kind` property returns an `enum` which allows you to know which methods are appropriate to use. + +```csharp +public enum NotificationKind +{ + OnNext, + OnError, + OnCompleted, +} +``` + +In this next example we produce a faulted sequence. Note that the final value of the materialized sequence is an `OnErrorNotification`. Also that the materialized sequence does not error, it completes successfully. + +```csharp +var source = new Subject(); +source.Materialize() + .Dump("Materialize"); + +source.OnNext(1); +source.OnNext(2); +source.OnNext(3); + +source.OnError(new Exception("Fail?")); +``` + +Output: + +``` +Materialize --> OnNext(1) +Materialize --> OnNext(2) +Materialize --> OnNext(3) +Materialize --> OnError(System.Exception) +Materialize completed +``` + +Materializing a sequence can be very handy for performing analysis or logging of a sequence. You can unwrap a materialized sequence by applying the `Dematerialize` extension method. The `Dematerialize` will only work on `IObservable>`. + +This completes our tour of the transformation operators. Their common characteristic is that they produce an output (or, in the case of `SelectMany`, a set of outputs) for each input item. Next we will look at the operators that can combine information from multiple items in their source. \ No newline at end of file diff --git a/content/07_Aggregation.md b/content/07_Aggregation.md index fd21626..4383f9a 100644 --- a/content/07_Aggregation.md +++ b/content/07_Aggregation.md @@ -2,429 +2,475 @@ title: Aggregation --- -# Aggregation +# Aggregation -Data is not always valuable is its raw form. Sometimes we need to consolidate, collate, combine or condense the mountains of data we receive into more consumable bite sized chunks. Consider fast moving data from domains like instrumentation, finance, signal processing and operational intelligence. This kind of data can change at a rate of over ten values per second. Can a person actually consume this? Perhaps for human consumption, aggregate values like averages, minimums and maximums can be of more use. +Data is not always tractable is its raw form. Sometimes we need to consolidate, collate, combine or condense the mountains of data we receive. This might just be a case of reducing the volume of data to a manageable level. For example, consider fast moving data from domains like instrumentation, finance, signal processing and operational intelligence. This kind of data can change at a rate of over ten values per second for individual sources, and much higher rates if we're observing multiple sources. Can a person actually consume this? For human consumption, aggregate values like averages, minimums and maximums can be of more use. -Continuing with the theme of reducing an observable sequence, we will look at the aggregation functions that are available to us in Rx. Our first set of methods continues on from our last chapter, as they take an observable sequence and reduce it to a sequence with a single value. We then move on to find operators that can transition a sequence back to a scalar value, a functional fold. +We can often achieve more than this. The way in which we combine and correlate may enable us to reveal patterns, providing insights that would not be available from any individual message, or from simple reduction to a single statistical measure. Rx's composability enables us to express complex and subtle computations over streams of data enabling us not just to reduce the volume of messages that users have to deal with, but to increase the amount of value in each message a human receives. -Just before we move on to introducing the new operators, we will quickly create our own extension method. We will use this 'Dump' extension method to help build our samples. +We will start with the simplest aggregation functions, which reduce an observable sequence to a sequence with a single value in some specific way. We then move on to more general-purpose operators that enable you to define your own aggregation mechanisms. -```csharp -public static class SampleExtentions -{ - public static void Dump(this IObservable source, string name) - { - source.Subscribe( - i=>Console.WriteLine("{0}-->{1}", name, i), - ex=>Console.WriteLine("{0} failed-->{1}", name, ex.Message), - ()=>Console.WriteLine("{0} completed", name)); - } -} -``` +## Simple Numeric Aggregation -Those who use [LINQPad](http://www.linqpad.net/) will recognize that this is the source of inspiration. For those who have not used LINQPad, I highly recommend it. It is perfect for whipping up quick samples to validate a snippet of code. LINQPad also fully supports the `IObservable` type. +Rx supports various standard LINQ operators that reduce all of the values in a sequence down to a single numeric result. -## Count +### Count -`Count` is a very familiar extension method for those that use LINQ on `IEnumerable`. Like all good method names, it "does what it says on the tin". The Rx version deviates from the `IEnumerable` version as Rx will return an observable sequence, not a scalar value. The return sequence will have a single value being the count of the values in the source sequence. Obviously we cannot provide the count until the source sequence completes. +`Count` tells you how many elements a sequence contains. Although this is a standard LINQ operator, Rx's version deviates from the `IEnumerable` version as Rx will return an observable sequence, not a scalar value. As usual, this is because of the push-related nature of Rx. Rx's `Count` can't demand that its source supply all elements immediately, so it just has to wait until the source says that it has finished. The sequence that `Count` returns will always be of type `IObservable`, regardless of the source's element type. This will do nothing until the source completes, at which point it will emit a single value reporting how many elements the source produced, and then it will in turn immediately complete. This example uses `Count` with `Range`, because `Range` generates all of its values as quickly as possible and then completes, meaning we get a result from `Count` immediately: ```csharp -var numbers = Observable.Range(0,3); -numbers.Dump("numbers"); +IObservable numbers = Observable.Range(0,3); numbers.Count().Dump("count"); ``` Output: ``` -numbers-->1 -numbers-->2 -numbers-->3 -numbers Completed count-->3 count Completed ``` -If you are expecting your sequence to have more values than a 32 bit integer can hold, there is the option to use the `LongCount` extension method. This is just the same as `Count` except it returns an `IObservable`. +If you are expecting your sequence to have more values than a 32-bit signed integer can count, you can use the `LongCount` operator instead. This is just the same as `Count` except it returns an `IObservable`. -## Min, Max, Sum and Average +### Sum -Other common aggregations are `Min`, `Max`, `Sum` and `Average`. Just like `Count`, these all return a sequence with a single value. Once the source completes the result sequence will produce its value and then complete. +The `Sum` operator adds together all the values in its source, producing the total as its only output. As with `Count`, Rx's `Sum` differs from most other LINQ providers in that it does not produce a scalar as its output. It produces an observable sequence that does nothing until its source completes. When the source completes, the observable returned by `Sum` produces a single value and then immediately completes. This example shows it in use: -```csharp -var numbers = new Subject(); +```cs +IObservable numbers = Observable.Range(1,5); +numbers.Sum().Dump("sum"); +``` -numbers.Dump("numbers"); -numbers.Min().Dump("Min"); -numbers.Average().Dump("Average"); +The output shows the single result produced by `Sum`: -numbers.OnNext(1); -numbers.OnNext(2); -numbers.OnNext(3); -numbers.OnCompleted(); +``` +sum-->15 +sum completed ``` -Output: +`Sum` is only able to work with values of type `int`, `long`, `float`, `double` `decimal`, or the nullable versions of these. This means that there are types you might expect to be able to `Sum` that you can't. For example the `BigInteger` type in the `System.Numerics` namespace represents integer values whose size is limited only by available memory, and how long you're prepared to wait for it to perform calculations. (Even basic arithmetic gets very slow on numbers with millions of digits.) You can use `+` to add these together because the type defines an overload for that operator. But `Sum` has historically had no way to find that. The introduction of [generic math in C# 11.0](https://learn.microsoft.com/en-us/dotnet/standard/generics/math#operator-interfaces) means that it would technically be possible to introduce a version of `Sum` that would work for any type `T` that implemented [`IAdditionOperators`](https://learn.microsoft.com/en-us/dotnet/api/system.numerics.iadditionoperators-3). However, that would mean a dependency on .NET 7.0 or later (because generic math is not available in older versions), and at the time of writing this, Rx supports .NET 7.0 through its `net6.0` target. It could introduce a separate `net7.0` and/or `net8.0` target to enable this, but has not yet done so. (To be fair, [`Sum` in LINQ to Objects also doesn't support this yet](https://github.com/dotnet/runtime/issues/64031).) -``` -numbers-->1 -numbers-->2 -numbers-->3 -numbers Completed -min-->1 -min Completed -avg-->2 -avg Completed +If you supply `Sum` with the nullable versions of these types (e.g., your source is an `IObservable`) then `Sum` will also return a sequence with a nullable item type, and it will produce `null` if any of the input values is `null`. + +Although `Sum` can work only with a small, fixed list of numeric types, your source doesn't necessarily have to produce values of those types. `Sum` offers overloads that accept a lambda that extracts a suitable numeric value from each input element. For example, suppose you wanted to answer the following unlikely question: if the next 10 ships that happen to broadcast descriptions of themselves over AIS were put side by side, would they all fit in a channel of some particular width? We could do this by filtering the AIS messages down to those that provide ship size information, using `Take` to collect the next 10 such messages, and then using `Sum`. The Ais.NET library's `IVesselDimensions` interface does not implement addition (and even if it did, we already just saw that Rx wouldn't be able to exploit that), but that's fine: all we need to do is supply a lambda that can take an `IVesselDimensions` and return a value of some numeric type that `Sum` can process: + +```cs +IObservable vesselDimensions = receiverHost.Messages + .OfType(); + +IObservable totalVesselWidths = vesselDimensions + .Take(10) + .Sum(dimensions => checked((int)(dimensions.DimensionToPort + dimensions.DimensionToStarboard))); ``` -The `Min` and `Max` methods have overloads that allow you to provide a custom implementation of an `IComparer` to sort your values in a custom way. The `Average` extension method specifically calculates the mean (as opposed to median or mode) of the sequence. For sequences of integers (int or long) the output of `Average` will be an `IObservable`. If the source is of nullable integers then the output will be `IObservable`. All other numeric types (`float`, `double`, `decimal` and their nullable equivalents) will result in the output sequence being of the same type as the input sequence. +(If you're wondering what's with cast and the `checked` keyword here, AIS defines these values as unsigned integers, so the Ais.NET library reports them as `uint`, which is not a type Rx's `Sum` supports. In practice, it's very unlikely that a vessel will be wide enough to overflow a 32-bit signed integer, so we just cast it to `int`, and the `checked` keyword will throw an exception in the unlikely event that we encounter ship more than 2.1 billion metres wide.) -## Functional folds +### Average -Finally we arrive at the set of methods in Rx that meet the functional description of catamorphism/fold. These methods will take an `IObservable` and produce a `T`. +The standard LINQ operator `Average` effectively calculates the value that `Sum` would calculate, and then divides it by the value that `Count` would calculate. And once again, whereas most LINQ implementations would return a scalar, Rx's `Average` produces an observable. -Caution should be prescribed whenever using any of these fold methods on an observable sequence, as they are all blocking. The reason you need to be careful with blocking methods is that you are moving from an asynchronous paradigm to a synchronous one, and without care you can introduce concurrency problems such as locking UIs and deadlocks. We will take a deeper look into these problems in a later chapter when we look at concurrency. +Although `Average` can process values of the same numeric types as `Sum`, the output type will be different in some cases. If the source is `IObservable`, or if you use one of the overloads that takes a lambda that extracts the value from the source, and that lambda returns an `int`, the result will be a `double.` This is because the average of a set of whole numbers is not necessarily a whole number. Likewise, averaging `long` values produces a `double`. However, inputs of type `decimal` produce outputs of type `decimal`, and likewise `float` inputs produce a `float` output. -> It is worth noting that in the soon to be released .NET 4.5 and Rx 2.0 will provide support for avoiding these concurrency problems. The new `async`/`await` keywords and related features in Rx 2.0 can help exit the monad in a safer way. +As with `Sum`, if the inputs to `Average` are nullable, the output will be too. -### First +### Min and Max -The `First()` extension method simply returns the first value from a sequence. +Rx implements the standard LINQ `Min` and `Max` operators which find the element with the highest or lowest value. As with all the other operators in this section, these do not return scalars, and instead return an `IObservable` that produces a single value. -```csharp -var interval = Observable.Interval(TimeSpan.FromSeconds(3)); +Rx defines specialized implementations for the same numeric types that `Sum` and `Average` support. However, unlike those operators it also defines an overload that will accept source items of any type. When you use `Min` or `Max` on a source type where Rx does not define a specialized implementation, it uses [`Comparer.Default`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.comparer-1.default) to compare items. There is also an overload enabling you to pass a comparer. + +As with `Sum` and `Average` there are overloads that accept a callback. If you use these overloads, `Min` and `Max` will invoke this callback for each source item, and will look for the lowest or highest value that your callback returns. Note that the single output they eventually produce will be a value returned by the callback, and not the original source item from which that value was derived. To see what that means, look at this example: -// Will block for 3s before returning -Console.WriteLine(interval.First()); +```cs +IObservable widthOfWidestVessel = vesselDimensions + .Take(10) + .Max(dimensions => checked((int)(dimensions.DimensionToPort + dimensions.DimensionToStarboard))); ``` -If the source sequence does not have any values (i.e. is an empty sequence) then the `First` method will throw an exception. You can cater for this in three ways: +`Max` returns an `IObservable` here, which will be the width of the widest vessel out of the next 10 messages that report vessel dimensions. But what if you didn't just want to see the width? What if you wanted the whole message? -- Use a try/catch blocks around the `First()` call -- Use `Take(1)` instead. However, this will be asynchronous, not blocking. -- Use `FirstOrDefault` extension method instead +## MinBy and MaxBy -The `FirstOrDefault` will still block until the source produces any notification. If the notification is an `OnError` then it will be thrown. If the notification is an `OnNext` then that value will be returned, otherwise if it is an `OnCompleted` the default will be returned. As we have seen in earlier methods, we can either choose to use the parameterless method in which the default value will be `default(T)` (i.e. null for reference types or the zero value for value types), alternatively we can provide our own default value to use. +Rx offers two subtle variations on `Min` and `Max`: `MinBy` and `MaxBy`. These are similar to the callback-based `Min` and `Max` we just saw that enable us to work with sequences of elements that are not numeric values, but which may have numeric properties. The difference is that instead of returning the minimum or maximum value, `MinBy` and `MaxBy` tell you which source element produced that value. For example, suppose that instead of just discovering the width of the widest ship, we wanted to know what ship that actually was: -A special mention should be made for the unique relationship that `BehaviorSubject` and the `First()` extension method has. The reason behind this is that the `BehaviorSubject` is guaranteed to have a notification, be it a value, an error or a completion. This effectively removes the blocking nature of the `First` extension method when used with a `BehaviorSubject`. This can be used to make behavior subjects act like properties. +```cs +IObservable widthOfWidestVessel = vesselDimensions + .Take(10) + .MaxBy(dimensions => checked((int)(dimensions.DimensionToPort + dimensions.DimensionToStarboard))); +``` -### Last +This is very similar to the example in the preceding section. We're working with a sequence where the element type is `IVesselDimensions`, so we've supplied a callback that extracts the value we want to use for comparison purposes. (The same callback as last time, in fact.) Just like `Max`, `MaxBy` is trying to work out which element produces the highest value when passed to this callback. It can't know which that is until the source completes. If the source hasn't completed yet, all it can know is the highest _yet_, but that might be exceeded by a future value. So as with all the other operators we've looked at in this chapter, this produces nothing until the source completes, which is why I've put a `Take(10)` in there. -The `Last` and `LastOrDefault` will block until the source completes and then return the last value. Just like the `First()` method any `OnError` notifications will be thrown. If the sequence is empty then `Last()` will throw an `InvalidOperationException`, but you can use `LastOrDefault` to avoid this. +However, the type of sequence we get is different. `Max` returned an `IObservable`, because it invokes the callback for every item in the source, and then produces the highest of the values that our callback returned. But with `MaxBy`, we get back an `IObservable` because `MaxBy` tells us which source element produced that value. -### Single +Of course, there might be more than one item that has the highest width—there might be three equally large ships, for example. With `Max` this doesn't matter because it's only trying to return the actual value: it doesn't matter how many source items had the maximum value, because it's the same value in all cases. But with `MaxBy` we get back the +original items that produce the maximum, and if there were three that all did this, we wouldn't want Rx to pick just one arbitrarily. -The `Single` extension method is for getting the single value from a sequence. The difference between this and `First()` or `Last()` is that it helps to assert your assumption that the sequence will only contain a single value. The method will block until the source produces a value and then completes. If the sequence produces any other combination of notifications then the method will throw. This method works especially well with `AsyncSubject` instances as they only produce a single value sequences. +So unlike the other aggregation operators we've seen so far, an observable returned by `MinBy` or `MaxBy` doesn't necessarily produce just a single value. It might produce several. You might ask whether it really is an aggregation operator, since it's not reducing the input sequence to one output. But it is reducing it to a single value: the minimum (or maximum) returned by the callback. It's just that it presents the result slightly differently. It produces a sequence based on the result of the aggregation process. You could think of it as a combination of aggregation and filtering: it performs aggregation to determine the minimum or maximum, and then filters the source sequence down just to the elements for which the callback produces that value. -## Build your own aggregations +**Note**: LINQ to Objects also defines [`MinBy`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.minby) and [`MaxBy`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.maxby) methods, but they are slightly different. These LINQ to Objects versions do in fact arbitrarily pick a single source element—if there are multiple source values all producing the minimum (or maximum) result, LINQ to Objects gives you just one whereas Rx gives you all of them. Rx defined its versions of these operators years before .NET 6.0 added their LINQ to Objects namesakes, so if you're wondering why Rx does it differently, the real question is why did LINQ to Objects not follow Rx's precedent. -If the provided aggregations do not meet your needs, you can build your own. Rx provides two different ways to do this. +## Simple Boolean Aggregation -### Aggregate +LINQ defines several standard operators that reduce entire sequences to a single boolean value. -The `Aggregate` method allows you to apply an accumulator function to the sequence. For the basic overload, you need to provide a function that takes the current state of the accumulated value and the value that the sequence is pushing. The result of the function is the new accumulated value. +### Any -This overload signature is as follows: +The `Any` operator has two forms. The parameterless overload effectively asks the question "are there any elements in this sequence?" It returns an observable sequence that will produce a single value of `false` if the source completes without emitting any values. If the source does produce a value however, then when the first value is produced, the result sequence will immediately produce `true` and then complete. If the first notification it gets is an error, then it will pass that error on. ```csharp -IObservable Aggregate( - this IObservable source, - Func accumulator) +var subject = new Subject(); +subject.Subscribe(Console.WriteLine, () => Console.WriteLine("Subject completed")); +var any = subject.Any(); + +any.Subscribe(b => Console.WriteLine("The subject has any values? {0}", b)); + +subject.OnNext(1); +subject.OnCompleted(); ``` -If you wanted to produce your own version of `Sum` for `int` values, you could do so by providing a function that just adds to the current state of the accumulator. +Output: -```csharp -var sum = source.Aggregate((acc, currentValue) => acc + currentValue); +``` +1 +The subject has any values? True +subject completed ``` -This overload of `Aggregate` has several problems. First is that it requires the aggregated value must be the same type as the sequence values. We have already seen in other aggregates like `Average` this is not always the case. Secondly, this overload needs at least one value to be produced from the source or the output will error with an `InvalidOperationException`. +If we now remove the OnNext(1), the output will change to the following -It should be completely valid for us to use `Aggregate` to create our own `Count` or `Sum` on an empty sequence. To do this you need to use the other overload. This overload takes an extra parameter which is the seed. The seed value provides an initial accumulated value. It also allows the aggregate type to be different to the value type. +``` +subject completed +The subject has any values? False +``` + +In the case where the source does produce a value, `Any` immediately unsubscribes from it. So if the source wants to report an error, `Any` will only see this if that is the first notification it produces. ```csharp -IObservable Aggregate( - this IObservable source, - TAccumulate seed, - Func accumulator) +var subject = new Subject(); +subject.Subscribe(Console.WriteLine, + ex => Console.WriteLine("subject OnError : {0}", ex), + () => Console.WriteLine("Subject completed")); +IObservable any = subject.Any(); + +any.Subscribe(b => Console.WriteLine("The subject has any values? {0}", b), + ex => Console.WriteLine(".Any() OnError : {0}", ex), + () => Console.WriteLine(".Any() completed")); + +subject.OnError(new Exception()); +``` + +Output: + +``` +subject OnError : System.Exception: Exception of type 'System.Exception' was thrown. +.Any() OnError : System.Exception: Exception of type 'System.Exception' was thrown. ``` -To update our `Sum` implementation to use this overload is easy. Just add the seed which will be 0. This will now return 0 as the sum when the sequence is empty which is just what we want. You also now can also create your own version of `Count`. +But if the source were to generate a value before an exception, e.g.: -```csharp -var sum = source.Aggregate(0, (acc, currentValue) => acc + currentValue); -var count = source.Aggregate(0, (acc, currentValue) => acc + 1); -// or using '_' to signify that the value is not used. -var count = source.Aggregate(0, (acc, _) => acc + 1); +```cs +subject.OnNext(42); +subject.OnError(new Exception()); ``` -As an exercise write your own `Min` and `Max` methods using `Aggregate`. You will probably find the `IComparer` interface useful, and in particular the static `Comparer.Default` property. When you have done the exercise, continue to the example implementations... +we'd see this output instead: + +``` +42 +The subject has any values? True +.Any() completed +subject OnError : System.Exception: Exception of type 'System.Exception' was thrown. +``` -Examples of creating `Min` and `Max` from `Aggregate`: +Although the handler that subscribed directly to the source subject still sees the error, our `any` observable reported a value of `True` and then completed, meaning it did not report the error that followed. + +The `Any` method also has an overload that takes a predicate. This effectively asks a slightly different question: "are there any elements in this sequence that meet these criteria?" The effect is similar to using `Where` followed by the no-arguments form of `Any`. ```csharp -public static IObservable MyMin(this IObservable source) -{ - return source.Aggregate( - (min, current) => Comparer - .Default - .Compare(min, current) > 0 - ? current - : min); -} - -public static IObservable MyMax(this IObservable source) -{ - var comparer = Comparer.Default; - Func max = - (x, y) => - { - if(comparer.Compare(x, y) < 0) - { - return y; - } - return x; - }; - return source.Aggregate(max); -} +IObservable any = subject.Any(i => i > 2); +// Functionally equivalent to +IObservable longWindedAny = subject.Where(i => i > 2).Any(); ``` -### Scan -While `Aggregate` allows us to get a final value for sequences that will complete, sometimes this is not what we need. If we consider a use case that requires that we get a running total as we receive values, then `Aggregate` is not a good fit. `Aggregate` is also not a good fit for infinite sequences. The `Scan` extension method however meets this requirement perfectly. The signatures for both `Scan` and `Aggregate` are the same; the difference is that `Scan` will push the _result_ from every call to the accumulator function. +### All -So instead of being an aggregator that reduces a sequence to a single value sequence, it is an accumulator that we return an accumulated value for each value of the source sequence. In this example we produce a running total. +The `All` operator is similar to the `Any` method that takes a predicate, except that all values must satisfy the predicate. As soon as the predicate rejects a value, the observable returned by `All` produces a `false` value and then completes. If the source reaches its end without producing any elements that do not satisfy the predicate, then `All` will push `true` as its value. (A consequence of this is that if you use `All` on an empty sequence, the result will be a sequence that produces `true`. This is consistent with how `All` works in other LINQ providers, but it might be surprising for anyone not familiar with the formal logic convention known as [vacuous truth](https://en.wikipedia.org/wiki/Vacuous_truth).) -```csharp -var numbers = new Subject(); -var scan = numbers.Scan(0, (acc, current) => acc + current); +Once `All` decides to produce a `false` value, it immediately unsubscribes from the source (just like `Any` does as soon as it determines that it can produce `true`.) If the source produces an error before this happens, the error will be passed along to the subscriber of the `All` method. -numbers.Dump("numbers"); -scan.Dump("scan"); +```cs +var subject = new Subject(); +subject.Subscribe(Console.WriteLine, () => Console.WriteLine("Subject completed")); +IEnumerable all = subject.All(i => i < 5); +all.Subscribe(b => Console.WriteLine($"All values less than 5? {b}")); -numbers.OnNext(1); -numbers.OnNext(2); -numbers.OnNext(3); -numbers.OnCompleted(); +subject.OnNext(1); +subject.OnNext(2); +subject.OnNext(6); +subject.OnNext(2); +subject.OnNext(1); +subject.OnCompleted(); ``` Output: ``` -numbers-->1 -sum-->1 -numbers-->2 -sum-->3 -numbers-->3 -sum-->6 -numbers completed -sum completed +1 +2 +6 +All values less than 5? False +all completed +2 +1 +subject completed ``` -It is probably worth pointing out that you use `Scan` with `TakeLast()` to produce `Aggregate`. +### IsEmpty + +The LINQ `IsEmpty` operator is logically the opposite of the no-arguments `Any` method. It returns `true` if and only if the source completes without producing any elements. If the source produces an item, `IsEmpty` produces `false` and immediately unsubscribes. If the source produces an error, this forwards that error. + +### Contains + +The `Contains` operator determines whether a particular element is present in a sequence. You could implement it using `Any`, just supplying a callback that compares each item with the value you're looking for. However, it will typically be slightly more succinct, and may be a more direct expression of intent to write `Contains`. ```csharp -source.Aggregate(0, (acc, current) => acc + current); -// is equivalent to -source.Scan(0, (acc, current) => acc + current).TakeLast(); +var subject = new Subject(); +subject.Subscribe( + Console.WriteLine, + () => Console.WriteLine("Subject completed")); + +IEnumerable contains = subject.Contains(2); + +contains.Subscribe( + b => Console.WriteLine("Contains the value 2? {0}", b), + () => Console.WriteLine("contains completed")); + +subject.OnNext(1); +subject.OnNext(2); +subject.OnNext(3); + +subject.OnCompleted(); +``` + +Output: + +``` +1 +2 +Contains the value 2? True +contains completed +3 +Subject completed ``` -As another exercise, use the methods we have covered so far in the book to produce a sequence of running minimum and running maximums. The key here is that each time we receive a value that is less than (or more than for a Max operator) our current accumulator we should push that value and update the accumulator value. We don't however want to push duplicate values. For example, given a sequence of [2, 1, 3, 5, 0] we should see output like [2, 1, 0] for the running minimum, and [2, 3, 5] for the running maximum. We don't want to see [2, 1, 2, 2, 0] or [2, 2, 3, 5, 5]. Continue to see an example implementation. +There is also an overload to `Contains` that allows you to specify an implementation of `IEqualityComparer` other than the default for the type. This can be useful if you have a sequence of custom types that may have some special rules for equality depending on the use case. -Example of a running minimum: +## Build your own aggregations + +If the built-in aggregations described in the preceding sections do not meet your needs, you can build your own. Rx provides two different ways to do this. + +### Aggregate + +The `Aggregate` method is very flexible: it is possible to build any of the operators shown so far in this chapter with it. You supply it with a function, and it invokes that function once for every element. But it doesn't just pass the element into your function: it also provides a way for your function to _aggregate_ information. As well as the current element, it also passes in an _accumulator_. The accumulator can be any type you like—it will depend on what sort of information you're looking to accumulate. Whatever value your function returns becomes the new accumulator value, and it will pass that into the function along with the next element from the source. There are a few variations on this, but the simplest overload looks like this: ```csharp -var comparer = Comparer.Default; -Func minOf = (x, y) => comparer.Compare(x, y) < 0 ? x: y; -var min = source.Scan(minOf).DistinctUntilChanged(); +IObservable Aggregate( + this IObservable source, + Func accumulator) ``` -Example of a running maximum: +If you wanted to produce your own version of `Count` for `int` values, you could do so by providing a function that just adds 1 for each value the source produces: ```csharp -public static IObservable RunningMax(this IObservable source) -{ - return source.Scan(MaxOf) - .Distinct(); -} +IObservable sum = source.Aggregate((acc, element) => acc + 1); +``` -private static T MaxOf(T x, T y) -{ - var comparer = Comparer.Default; - if (comparer.Compare(x, y) < 0) - { - return y; - } - return x; -} +To understand exactly what this is doing, let's look at how `Aggregate` will call this lambda. To make that slightly easier to see, suppose we put that lambda in its own variable: + +```cs +Func c = (acc, element) => acc + 1; ``` -While the only functional differences between the two examples is checking greater instead of less than, the examples show two different styles. Some people prefer the terseness of the first example, others like their curly braces and the verbosity of the second example. The key here was to compose the `Scan` method with the `Distinct` or `DistinctUntilChanged` methods. It is probably preferable to use the `DistinctUntilChanged` so that we internally are not keeping a cache of all values. +Now suppose the source produces an item with the value 100. `Aggregate` will invoke our function: + +```cs +c(0, 100) // returns 1 +``` -## Partitioning +The first argument is the current accumulator. `Aggregate` has used `default(int)` for the initial accumulator value, which is 0. Our function returns 1, which becomes the new accumulator value. So if the source produces a second value, say, 200, `Aggregate` will pass the new accumulator, along with the second value from the source: -Rx also gives you the ability to partition your sequence with features like the standard LINQ operator `GroupBy`. This can be useful for taking a single sequence and fanning out to many subscribers or perhaps taking aggregates on partitions. +```cs +c(1, 200) // returns 2 +``` -### MinBy and MaxBy +This particular function completely ignores its second argument (the element from the source). It just adds one to the accumulator each time. So the accumulator is nothing more than a record of the number of times our function has been called. -The `MinBy` and `MaxBy` operators allow you to partition your sequence based on a key selector function. Key selector functions are common in other LINQ operators like the `IEnumerable` `ToDictionary` or `GroupBy` and the [`Distinct`}(05_Filtering.html#Distinct) method. Each method will return you the values from the key that was the minimum or maximum respectively. +Now let's look at how we might implement `Sum` using `Aggregate`: ```csharp -// Returns an observable sequence containing a list of zero or more elements that have a -// minimum key value. -public static IObservable> MinBy( - this IObservable source, - Func keySelector) -{...} +Func s = (acc, element) => acc + element +IObservable sum = source.Aggregate(s); +``` -public static IObservable> MinBy( - this IObservable source, - Func keySelector, - IComparer comparer) -{...} +For the first element, `Aggregate` will again pass the default value for our chosen accumulator type, `int`: 0. And it will pass the first element value. So again if the first element is 100 it does this: -// Returns an observable sequence containing a list of zero or more elements that have a -// maximum key value. -public static IObservable> MaxBy( - this IObservable source, - Func keySelector) -{...} +```cs +s(0, 100) // returns 100 +``` -public static IObservable> MaxBy( - this IObservable source, - Func keySelector, - IComparer comparer) -{...} +And then if the second element is 200, `Aggregate` will make this call: + +```cs +s(100, 200) // returns 300 ``` -Take note that each `Min` and `Max` operator has an overload that takes a comparer. This allows for comparing custom types or custom sorting of standard types. +Notice that this time, the first argument was 100, because that's what the previous invocation of `s` returned. So in this case, after seeing elements 100 and 200, the accumulator's value is 300, which is the sum of all the elements. -Consider a sequence from 0 to 10. If we apply a key selector that partitions the values in to groups based on their modulus of 3, we will have 3 groups of values. The values and their keys will be as follows: +What if we want the initial accumulator value to be something other than `default(TAccumulator)`? There's an overload for that. For example, here's how we could implement something like `All` with `Aggregate`: -```csharp -Func keySelector = i => i % 3; +```cs +IObservable all = source.Aggregate(true, (acc, element) => acc && element); ``` -- 0, key: 0 -- 1, key: 1 -- 2, key: 2 -- 3, key: 0 -- 4, key: 1 -- 5, key: 2 -- 6, key: 0 -- 7, key: 1 -- 8, key: 2 -- 9, key: 0 +This isn't exactly equivalent to the real `All` by the way: it handles errors differently. `All` instantly unsubscribes from its source if it sees a single element that is `false`, because it knows that nothing else the source produces can possibly change the outcome. That means that if the source had been about to produce an error, it will no longer have the opportunity to do so because `All` unsubscribed. But `Aggregate` has no way of knowing that the accumulator has entered a state from which it can never leave, so it will remain subscribed to the source until the source completes (or until whichever code subscribed to the `IObservable` returned by `Aggregate` unsubscribes). This means that if the source were to produce `true`, then `false`, `Aggregate` would, unlike `All`, remain subscribed to the source, so if the source goes on to call `OnError`, `Aggregate` will receive that error, and pass it on to its subscriber. -We can see here that the minimum key is 0 and the maximum key is 2. If therefore, we applied the `MinBy` operator our single value from the sequence would be the list of [0,3,6,9]. Applying the `MaxBy` operator would produce the list [2,5,8]. The `MinBy` and `MaxBy` operators will only yield a single value (like an `AsyncSubject`) and that value will be an `IList` with zero or more values. +Here's a way to think about `Aggregate` that some people find helpful. If your source produces the values 1 through 5, and if the function we pass to `Aggregate` is called `f`, then the value that `Aggregate` produces once the source completes will be this: -If instead of the values for the minimum/maximum key, you wanted to get the minimum value for each key, then you would need to look at `GroupBy`. +```cs +T result = f(f(f(f(f(default(T), 1), 2), 3), 4), 5); +``` -### GroupBy +So in the case of our recreation of `Count`, the accumulator type was `int`, so that becomes: -The `GroupBy` operator allows you to partition your sequence just as `IEnumerable`'s `GroupBy` operator does. In a similar fashion to how the `IEnumerable` operator returns an `IEnumerable>`, the `IObservable` `GroupBy` operator returns an `IObservable>`. +```cs +int sum = s(s(s(s(s(0, 1), 2), 3), 4), 5); +// Note: Aggregate doesn't return this directly - +// it returns an IObservable that produces this value. +``` -```csharp -// Transforms a sequence into a sequence of observable groups, -// each of which corresponds to a unique key value, -// containing all elements that share that same key value. -public static IObservable> GroupBy( - this IObservable source, - Func keySelector) -{...} +Rx's `Aggregate` doesn't perform all those invocations at once: it invokes the function each time the source produces an element, so the calculations will be spread out over time. If your callback is a _pure function_—one that is unaffected by global variables and other environmental factors, and which will always return the same result for any particular input—this doesn't matter. The result of `Aggregate` will be the same as if it had all happened in one big expression like the preceding example. But if your callback's behaviour is affected by, say, a global variable, or by the current contents of the filesystem, then the fact that it will be invoked when the source produces each value may be more significant. -public static IObservable> GroupBy( - this IObservable source, - Func keySelector, - IEqualityComparer comparer) -{...} +`Aggregate` has other names in some programming systems by the way. Some systems call it `reduce`. It is also often referred to as a _fold_. (Specifically a _left fold_. A right fold proceeds in reverse. Conventionally its function takes arguments in the reverse order, so it would look like `s(1, s(2, s(3, s(4, s(5, 0)))))`. Rx does not offer a built-in right fold. It would not be a natural fit because it would have to wait until it received the final element before it could begin, meaning it would need to hold onto every element in the entire sequence, and then evaluate the entire fold at once when the sequence completes.) -public static IObservable> GroupBy( - this IObservable source, - Func keySelector, - Func elementSelector) -{...} +You might have spotted that in my quest to re-implement some of the built-in aggregation operators, I went straight from `Sum` to `Any`. What about `Average`? It turns out we can't do that with the overloads I've shown you so far. And that's because `Average` needs to accumulate two pieces of information—the running total and the count—and it also needs to perform once final step right at the end: it needs to divide the total by the count. With the overloads shown so far, we can only get part way there: -public static IObservable> GroupBy( - this IObservable source, - Func keySelector, - Func elementSelector, - IEqualityComparer comparer) -{...} +```cs +IObservable nums = Observable.Range(1, 5); + +IObservable<(int Count, int Sum)> avgAcc = nums.Aggregate( + (Count: 0, Sum: 0), + (acc, element) => (Count: acc.Count + 1, Sum: acc.Sum + element)); ``` -I find the last two overloads a little redundant as we could easily just compose a `Select` operator to the query to get the same functionality. +This uses a tuple as the accumulator, enabling it to accumulate two values: the count and the sum. But the final accumulator value becomes the result, and that's not what we want. We're missing that final step that calculates the average by dividing the sum by the count. Fortunately, `Aggregate` offers a 3rd overload that enables us to provide this final step. We pass a second callback which will be invoked just once when the source completes. `Aggregate` passes the final accumulator value into this lambda, and then whatever it returns becomes the single item produced by the observable that `Aggregate` returns. + +```cs +IObservable avg = nums.Aggregate( + (Count: 0, Sum: 0), + (acc, element) => (Count: acc.Count + 1, Sum: acc.Sum + element), + acc => ((double) acc.Sum) / acc.Count); +``` -In a similar fashion that the `IGrouping` type extends the `IEnumerable`, the `IGroupedObservable` just extends `IObservable` by adding a `Key` property. The use of the `GroupBy` effectively gives us a nested observable sequence. +I've been showing how `Aggregate` can re-implement some of the built-in aggregation operators to illustrate that it is a powerful and very general operator. However, that's not what we use it for. `Aggregate` is useful precisely because it lets us define custom aggregation. -To use the `GroupBy` operator to get the minimum/maximum value for each key, we can first partition the sequence and then `Min`/`Max` each partition. +For example, suppose I wanted to build up a list of the names of all the ships that have broadcast their details over AIS. Here's one way to do that: -```csharp -var source = Observable.Interval(TimeSpan.FromSeconds(0.1)).Take(10); -var group = source.GroupBy(i => i % 3); -group.Subscribe( - grp => - grp.Min().Subscribe( - minValue => - Console.WriteLine("{0} min value = {1}", grp.Key, minValue)), - () => Console.WriteLine("Completed")); +```cs +IObservable> allNames = vesselNames + .Take(10) + .Aggregate( + ImmutableHashSet.Empty, + (set, name) => set.Add(name.VesselName)); +``` + +I've used `ImmutableHashSet` here because its usage patterns happen to fit `Aggregate` neatly. An ordinary `HashSet` would also have worked, but is a little less convenient because its `Add` method doesn't return the set, so our function needs an extra statement to return the accumulated set: + +```cs +IObservable> allNames = vesselNames + .Take(10) + .Aggregate( + new HashSet(), + (set, name) => + { + set.Add(name.VesselName); + return set; + }); +``` + +With either of these implementations, `vesselNames` will produce a single value that is a `IReadOnlySet` containing each vessel name seen in the first 10 messages that report a name. + +I've had to fudge an issue in these last two examples. I've made them work over just the first 10 suitable messages to emerge. Think about what would happen if I didn't have the `Take(10)` in there. The code would compile, but we'd have a problem. The AIS message source I've been using in various examples is an endless source. Ships will continue to move around the oceans for the foreseeable future. Ais.NET does not contain any code designed to detect either the end of civilisation, or the invention of technologies that will render the use of ships obsolete, so it will never call `OnCompleted` on its subscribers. The observable returned by `Aggregate` reports nothing until its source either completes or fails. So if we remove that `Take(10)`, the behaviour would be identical `Observable.Never>`. I had to force the input to `Aggregate` to come to an end to make it produce something. But there is another way. + +### Scan + +While `Aggregate` allows us to reduce complete sequences to a single, final value, sometimes this is not what we need. If we are dealing with an endless source, we might want something more like a running total, updated each time we receive a value. The `Scan` operator is designed for exactly this requirement. The signatures for both `Scan` and `Aggregate` are the same; the difference is that `Scan` doesn't wait for the end of its input. It produces the aggregated value after every item. + +We can use this to build up a set of vessel names as in the preceding section, but with `Scan` we don't have to wait until the end. This will report the current list every time it receives a message containing a name: + +``` +IObservable> allNames = vesselNames + .Scan( + ImmutableHashSet.Empty, + (set, name) => set.Add(name.VesselName)); +``` + +Note that this `allNames` observable will produce elements even if nothing has changed. If the accumulated set of names already contained the name that just emerged from `vesselNames`, the call to `set.Add` will do nothing, because that name will already be in the set. But `Scan` scan produces one output for each input, and doesn't care if the accumulator didn't change. Whether this matters will depend on what you are planning to do with this `allNames` observable, but if you need to, you can fix this easily with the [`DistinctUntilChanged` operator shown in Chapter 5](./05_Filtering.md#distinct-and-distinctuntilchanged). + +You could think of `Scan` as being a version of `Aggregate` that shows its working. If we wanted to see how the process of calculating an average aggregates the count and sum, we could write this: + +```cs +IObservable nums = Observable.Range(1, 5); + +IObservable<(int Count, int Sum)> avgAcc = nums.Scan( + (Count: 0, Sum: 0), + (acc, element) => (Count: acc.Count + 1, Sum: acc.Sum + element)); + +avgAcc.Dump("acc"); +``` + +That produces this output: + +``` +acc-->(1, 1) +acc-->(2, 3) +acc-->(3, 6) +acc-->(4, 10) +acc-->(5, 15) +acc completed ``` -The code above would work, but it is not good practice to have these nested subscribe calls. We have lost control of the nested subscription, and it is hard to read. When you find yourself creating nested subscriptions, you should consider how to apply a better pattern. In this case we can use `SelectMany` which we will look at in the next chapter. +You can see clearly here that `Scan` is emitting the current accumulated values each time the source produces a value. + +Unlike `Aggregate`, `Scan` doesn't offer an overload taking a second function to transform the accumulator into the result. So we can see the tuple containing the count and sum here, but not the actual average value we want. But we can achieve that by using the [`Select`](06_Transformation.md#select) operator described in the [Transformation chapter](./06_Transformation.md): + +```cs +IObservable avg = nums.Scan( + (Count: 0, Sum: 0), + (acc, element) => (Count: acc.Count + 1, Sum: acc.Sum + element)) + .Select(acc => ((double) acc.Sum) / acc.Count); + +avg.Dump("avg"); +``` + +Now we get this output: + +``` +avg-->1 +avg-->1.5 +avg-->2 +avg-->2.5 +avg-->3 +avg completed +``` + +`Scan` is a more generalised operator than `Aggregate`. You could implement `Aggregate` by combining `Scan` with the [`TakeLast()` operator described in the Filtering chapter](./05_Filtering.md#takelast). ```csharp -var source = Observable.Interval(TimeSpan.FromSeconds(0.1)).Take(10); -var group = source.GroupBy(i => i % 3); -group.SelectMany( - grp => - grp.Max() - .Select(value => new { grp.Key, value })) - .Dump("group"); -``` - -### Nested observables - -The concept of a sequence of sequences can be somewhat overwhelming at first, especially if both sequence types are `IObservable`. While it is an advanced topic, we will touch on it here as it is a common occurrence with Rx. I find it easier if I can conceptualize a scenario or example to understand concepts better. - -Examples of Observables of Observables: - -
-
Partitions of Data
-
- You may partition data from a single source so that it can easily be filtered and - shared to many sources. Partitioning data may also be useful for aggregates as we - have seen. This is commonly done with the `GroupBy` operator. -
-
Online Game servers
-
- Consider a sequence of servers. New values represent a server coming online. The - value itself is a sequence of latency values allowing the consumer to see real time - information of quantity and quality of servers available. If a server went down - then the inner sequence can signify that by completing. -
-
Financial data streams
-
- New markets or instruments may open and close during the day. These would then stream - price information and could complete when the market closes. -
-
Chat Room
-
- Users can join a chat (outer sequence), leave messages (inner sequence) and leave - a chat (completing the inner sequence). -
-
File watcher
-
- As files are added to a directory they could be watched for modifications (outer - sequence). The inner sequence could represent changes to the file, and completing - an inner sequence could represent deleting the file. -
-
- -Considering these examples, you could see how useful it could be to have the concept of nested observables. There are a suite of operators that work very well with nested observables such as `SelectMany`, `Merge` and `Switch` that we look at in future chapters. - -When working with nested observables, it can be handy to adopt the convention that a new sequence represents a creation (e.g. A new partition is created, new game host comes online, a market opens, users joins a chat, creating a file in a watched directory). You can then adopt the convention for what a completed inner sequence represents (e.g. Game host goes offline, Market Closes, User leave chat, File being watched is deleted). The great thing with nested observables is that a completed inner sequence can effectively be restarted by creating a new inner sequence. - -In this chapter we are starting to uncover the power of LINQ and how it applies to Rx. We chained methods together to recreate the effect that other methods already provide. While this is academically nice, it also allows us to starting thinking in terms of functional composition. We have also seen that some methods work nicely with certain types: `First()` + `BehaviorSubject`, `Single()` + `AsyncSubject`, `Single()` + `Aggregate()` etc. We have covered the second of our three classifications of operators, _catamorphism_. Next we will discover more methods to add to our functional composition tool belt and also find how Rx deals with our third functional concept, _bind_. - -Consolidating data into groups and aggregates enables sensible consumption of mass data. Fast moving data can be too overwhelming for batch processing systems and human consumption. Rx provides the ability to aggregate and partition on the fly, enabling real-time reporting without the need for expensive CEP or OLAP products. \ No newline at end of file +source.Aggregate(0, (acc, current) => acc + current); +// is equivalent to +source.Scan(0, (acc, current) => acc + current).TakeLast(); +``` + +Aggregation is useful for reducing volumes of data or combining multiple elements to produce averages, or other measures that incorporate information from multiple elements. But to perform some kinds of analysis we will also need to slice up or otherwise restructure our data before calculating aggregated values. So in the next chapter we'll look at the various mechanisms Rx offers for partitioning data. \ No newline at end of file diff --git a/content/08_Partitioning.md b/content/08_Partitioning.md new file mode 100644 index 0000000..a2fd954 --- /dev/null +++ b/content/08_Partitioning.md @@ -0,0 +1,506 @@ +--- +title: Partitioning +--- + +# Partitioning + +Rx can split a single sequence into multiple sequences. This can be useful for distributing items over many subscribers. When performing analytics, it can be useful to take aggregates on partitions. You may already be familiar with the standard LINQ operators `GroupBy`. Rx supports this, and also defines some of its own. + +## GroupBy + +The `GroupBy` operator allows you to partition your sequence just as `IEnumerable`'s `GroupBy` operator does. Once again, the open source [Ais.Net project](https://github.com/ais-dotnet) can provide a useful example. Its [`ReceiverHost` class](https://github.com/ais-dotnet/Ais.Net.Receiver/blob/15de7b2908c3bd67cf421545578cfca59b24ed2c/Solutions/Ais.Net.Receiver/Ais/Net/Receiver/Receiver/ReceiverHost.cs) makes AIS messages available through Rx, defining a `Messages` property of type `IObservable`. This is a very busy source, because it reports every message it is able to access. For example, if you connect the receiver to the AIS message source generously provided by the Norwegian government, it produces a notification every time _any_ ship broadcasts an AIS message anywhere on the Norwegian coast. There are a lot of ships moving around Norway, so this is a bit of a firehose. + +If we know exactly which ships we're interested in, you saw how to filter this stream in the [Filtering chapter](05_Filtering.md). But what if we don't, and yet we still want to be able to perform processing relating to individual ships? For example, perhaps we'd like to discover any time any ship changes its `NavigationStatus` (which reports values such as `AtAnchor`, or `Moored`). The [`Distinct` and `DistinctUntilChanged` section of the Filtering chapter](05_Filtering.md#distinct-and-distinctuntilchanged) showed how to do exactly that, but it began by filtering the stream down to messages from a single ship. If we tried to use `DistinctUntilChanged` directly on the all-ships stream it will not produce meaningful information. If ship A is moored and ship B is at anchor, and if we receive alternative status messages from ship A and ship B, `DistinctUntilChanged` would report each message as a change in status, even though neither ship's status has changed. + +We can fix this by splitting the "all the ships" sequence into lots of little sequences: + +```cs +IObservable> perShipObservables = receiverHost.Messages + .GroupBy(message => message.Mmsi); +``` + +This `perShipObservables` is an observable sequence of observable sequences. More specifically, it's an observable sequence of grouped observable sequences, but as you can see from [the definition of `IGroupedObservable`](https://github.com/dotnet/reactive/blob/95d9ea9d2786f6ec49a051c5cff47dc42591e54f/Rx.NET/Source/src/System.Reactive/Linq/IGroupedObservable.cs#L18), a grouped observable is just a specialized kind of observable: + +```cs +public interface IGroupedObservable : IObservable +{ + TKey Key { get; } +} +``` + +Each time `receiverHost.Message` reports an AIS message, the `GroupBy` operator will invoke the callback to find out which group this item belongs to. We refer to the value returned by the callback as the _key_, and `GroupBy` remembers each key it has already seen. If this is a new key, `GroupBy` creates a new `IGroupedObservable` whose `Key` property will be the value just returned by the callback. It emits this `IGroupedObservable` from the outer observable (the one we put in `perShipObservables`) and then immediately causes that new `IGroupedObservable` to emit the element (an `IAisMessage` in this example) that produced that key. But if the callback produces a key that `GroupBy` has seen before, it finds the `IGroupedObservable` that it already produced for that key, and causes that to emit the value. + +So in this example, the effect is that any time the `receiverHost` reports a message from a ship with we've not previously heard from, `perShipObservables` will emit a new observable that reports messages just for that ship. We could use this to report each time we learn about a new ship: + +```cs +perShipObservables.Subscribe(m => Console.WriteLine($"New ship! {m.Key}")); +``` + +But that doesn't do anything we couldn't have achieved with `Distinct`. The power of `GroupBy` is that we get an observable sequence for each ship here, so we can go on to set up some per-ship processing: + +```cs +IObservable> shipStatusChangeObservables = + perShipObservables.Select(shipMessages => shipMessages + .OfType() + .DistinctUntilChanged(m => m.NavigationStatus) + .Skip(1)); +``` + +This uses [`Select`](06_Transformation.md#select) (introduced in the Transformation chapter) to apply processing to each group that comes out of `perShipObservables`. Remember, each such group represents a distinct ship, so the callback we've passed to `Select` here will be invoked exactly once for each ship. This means it's now fine for us to use `DistinctUntilChanged`. The input this example supplies to `DistinctUntilChanged` is a sequence representing the messages from just one ship, so this will tell us when that ship changes its status. This is now able to do what we want because each ship gets its own instance of `DistinctUntilChanged`. `DistinctUntilChanged` always forwards the first event it receives—it only drops items when they are the same as the preceding item, and there is no preceding item in this case. But that is unlikely to be the right behaviour here. Suppose that the first message we see from some vessel named `A` reports a status of `Moored`. It's possible that immediately before we started running, it was in some different state, and that the very first report we received happened to represent a change in status. But it's more likely that it has been moored for some time before we started. We can't tell for certain, but the majority of status reports don't represent a change, so `DistinctUntilChanged`'s behaviour of always forwarding the first event is likely to be wrong here. So we use `Skip(1)` to drop the first message from each ship. + +At this point we have an observable sequence of observable sequences. The outer sequence produces a nested sequence for each distinct ship that it sees, and that nested sequence will report `NavigationStatus` changes for that particular ship. + +I'm going to make a small tweak: + +```cs +IObservable shipStatusChanges = + perShipObservables.SelectMany(shipMessages => shipMessages + .OfType() + .DistinctUntilChanged(m => m.NavigationStatus) + .Skip(1)); +``` + +I've replaced `Select` with [`SelectMany`, also described in the Transformation chapter](06_Transformation.md#selectmany). As you may recall, `SelectMany` flattens nested observables back into a single flat sequence. You can see this reflected in the return type: now we've got just an `IObservable` instead of a sequence of sequences. + +Wait a second! Haven't I just undone the work that `GroupBy` did? I asked it to partition the events by vessel id, so why am I now recombining it back into a single, flat stream? Isn't that what I started with? + +It's true that the stream type has the same shape as my original input: this will be a single observable sequence of AIS messages. (It's a little more specialized—the element type is `IAisMessageType1to3`, because that's where I can get `NavigationStatus` from, but these all still implement `IAisMessage`.) And all the different vessels will be mixed together in this one stream. But I've not actually negated the work that `GroupBy` did. This marble diagram illustrates what's going on: + + +![A marble diagram showing how an input observable named receiverHost.Messages is expanded into groups, processed, and then collapsed back into a single source. The input observable shows events from three different ships, 'A', 'B', and 'C'. Each event is labelled with the ship's reported status. All the messages from A report a status of Moored. B makes two AtAnchor status reports, followed by two UnderwayUsingEngine reports. C reports UnderwaySailing twice, then AtAnchor, and then UnderwaySailing again. The events from the three ships are intermingled—the order on the input line goes A, B, C, B, A, C, B, C, C, B, A. The next section is labelled as perShipObservables, and this shows the effect of grouping the events by vessel. The first line shows only the events from A, the second those from B, and the third those from C. The next section is laballed with the processing code from the preceding example, and shows three more observables, corresponding to the three groups in the preceding part of the diagram. But in this one, the source for A shows no events at all. The second line shows a single event for B, the first one where it reported UnderwayUsingEngine. And it shows two for C: the one where it reported AtAnchor, and then the one after that where it reported UnderwaySailing. The final line of the diagram is a single source, combining the events just described in the preceding section of the diagram.](GraphicsIntro/Ch08-Partitioning-Marbles-Status-Changes.svg) + +The `perShipObservables` section shows how `GroupBy` creates a separate observable for each distinct vessel. (This diagram shows three vessels, named `A`, `B`, and `C`. With the real source, there would be a lot more observables coming out of `GroupBy`, but the principle remains the same.) We do a bit of work on these group streams before flattening them. As already described, we use `DistinctUntilChanged` and `Skip(1)` to ensure we only produce an event when we know for certain that a vessel's status has changed. (Since we only ever saw `A` reporting a status of `Moored`, then as far as we know its status never changed, which is why its stream is completely empty.) Only then do we flatten it back into a single observable sequence. + +Marble diagrams need to be simple to fit on a page, so let's now take a quick look at some real output. This confirms that this is very different from the raw `receiverHost.Messages`. First, I need to attach a subscriber: + +```cs +shipStatusChanges.Subscribe(m => + Console.WriteLine($"Vessel {((IAisMessage)m).Mmsi} changed status to {m.NavigationStatus} at {DateTimeOffset.UtcNow}")); +``` + +If I then let the receiver run for about ten minutes, I see this output: + +``` +Vessel 257076860 changed status to UnderwayUsingEngine at 23/06/2023 06:42:48 +00:00 +Vessel 257006640 changed status to UnderwayUsingEngine at 23/06/2023 06:43:08 +00:00 +Vessel 259005960 changed status to UnderwayUsingEngine at 23/06/2023 06:44:23 +00:00 +Vessel 259112000 changed status to UnderwayUsingEngine at 23/06/2023 06:44:33 +00:00 +Vessel 259004130 changed status to Moored at 23/06/2023 06:44:43 +00:00 +Vessel 257076860 changed status to NotDefined at 23/06/2023 06:44:53 +00:00 +Vessel 258024800 changed status to Moored at 23/06/2023 06:45:24 +00:00 +Vessel 258006830 changed status to UnderwayUsingEngine at 23/06/2023 06:46:39 +00:00 +Vessel 257428000 changed status to Moored at 23/06/2023 06:46:49 +00:00 +Vessel 257812800 changed status to Moored at 23/06/2023 06:46:49 +00:00 +Vessel 257805000 changed status to Moored at 23/06/2023 06:47:54 +00:00 +Vessel 259366000 changed status to UnderwayUsingEngine at 23/06/2023 06:47:59 +00:00 +Vessel 257076860 changed status to UnderwayUsingEngine at 23/06/2023 06:48:59 +00:00 +Vessel 257020500 changed status to UnderwayUsingEngine at 23/06/2023 06:50:24 +00:00 +Vessel 257737000 changed status to UnderwayUsingEngine at 23/06/2023 06:50:39 +00:00 +Vessel 257076860 changed status to NotDefined at 23/06/2023 06:51:04 +00:00 +Vessel 259366000 changed status to Moored at 23/06/2023 06:51:54 +00:00 +Vessel 232026676 changed status to Moored at 23/06/2023 06:51:54 +00:00 +Vessel 259638000 changed status to UnderwayUsingEngine at 23/06/2023 06:52:34 +00:00 +``` + +The critical thing to understand here is that in the space of ten minutes, `receiverHost.Messages` produced _thousands_ of messages. (The rate varies by time of day, but it's typically over a thousand messages a minute. The code would have processed roughly ten thousand messages when I ran it to produce that output.) But as you can see, `shipStatusChanges` produced just 19 messages. + +This shows how Rx can tame high volume event sources in ways that are much more powerful than mere aggregation. We've not just reduced the data down to some statistical measure that can only provide an overview. Statistical measures such as averages or variance are often very useful, but they aren't always able to provide us with the domain-specific insights we want. They wouldn't be able to tell us anything about any particular ship for example. But here, every message tells us something about a particular ship. We've been able to retain that level of detail, despite the fact that we are looking at every ship. We've been able to instruct Rx to tell us any time any ship changes its status. + +It may seem like I'm making too big a deal of this, but it took so little effort to achieve this result that it can be easy to miss just how much work Rx is doing for us here. This code does all of the following: + +- monitors every single ship operating in Norwegian waters +- provides per-ship information +- reports events at a rate that a human could reasonably cope with + +It can take thousands of messages and perform the necessary processing to find the handful that really matter to us. + +This is an example of the "fanning out, and then back in again" technique I described in ['The Significance of SelectMany' in the Transformation chapter](06_Transformation.md#the-significance-of-selectmany). This code uses `GroupBy` to fan out from a single observable to multiple observables. The key to this step is to create nested observables that provide the right level of detail for the processing we want to do. In this example that level of detail was "one specific ship" but it wouldn't have to be. You could imagine wanting to group messages by region—perhaps we're interesting in comparing different ports, so we'd want to partition the source based on whichever port a vessel is closest to, or perhaps by its destination port. (AIS provides a way for vessels to broadcast their intended destination.) Having partitioned the data by whatever criteria we require, we then define the processing to be applied for each group. In this case, we just watched for changes to `NavigationStatus`. This step will typically be where the reduction in volume happens. For example, most vessels will only change their `NavigationStatus` a few times a day at most. Having then reduced the notification stream to just those events we really care about, we can combine it back into a single stream that provides the high-value notifications we want. + +This power comes at a cost, of course. It didn't take much code to get Rx to do this work for us, but we're causing it to work reasonably hard: it needs to remember every ship it has seen so far, and to maintain an observable source for each one. If our data source has broad enough reach to receive messages from tens of thousands of vessel, Rx will need to maintain tens of thousands of observable sources, one for each vessel. The example shown has nothing resembling an inactivity timeout—a vessel broadcasting even a single message will be remembered for as long as the program runs. (A malicious actor fabricating AIS messages each with a different made up identifier would eventually cause this code to crash by running out of memory.) Depending on your data sources you might need to take steps to avoid unbounded growth of memory usage, so real examples can become more complex than this, but the basic approach is powerful. + +Now that we've seen an example, let's look at `GroupBy` in a bit more detail. It comes in a few different flavours. We just used this overload: + +```cs +public static IObservable> GroupBy( + this IObservable source, + Func keySelector) +``` + +That overload uses whatever the default comparison behaviour is for your chosen key type. In our case we used `uint` (the type of the `Mmsi` property that uniquely identifies a vessel in an AIS message), which is just a number, so it's an intrinsically comparable type. In some cases you might want non-standard comparison. For example, if you use `string` as a key, you might want to be able to specify a locale-specific case-insensitive comparison. For these scenarios, there's an overload that takes a comparer: + +```cs +public static IObservable> GroupBy( + this IObservable source, + Func keySelector, + IEqualityComparer comparer) +``` + +There are two more overloads that extend the preceding two with an `elementSelector` argument: + +```cs +public static IObservable> GroupBy( + this IObservable source, + Func keySelector, + Func elementSelector) +{...} + +public static IObservable> GroupBy( + this IObservable source, + Func keySelector, + Func elementSelector, + IEqualityComparer comparer) +{...} +``` + +This is functionally equivalent to using the `Select` operator after `GroupBy`. + +By the way, when using `GroupBy` you might be tempted to `Subscribe` directly to the nested observables: + +```cs +// Don't do it this way. Use the earlier example. +perShipObservables.Subscribe(shipMessages => + shipMessages + .OfType() + .DistinctUntilChanged(m => m.NavigationStatus) + .Skip(1) + .Subscribe(m => + Console.WriteLine( + $"Ship {((IAisMessage)m).Mmsi} changed status to {m.NavigationStatus} at {DateTimeOffset.UtcNow}"))); +``` + +This may seem to have the same effect: `perShipObservables` here is the sequence returned by `GroupBy`, so it will produce a observable stream for each distinct ship. This example subscribes to that, and then uses the same operators as before on each nested sequence, but instead of collecting the results out into a single output observable with `SelectMany`, this explicitly calls `Subscribe` for each nested stream. + +This might seem like a more natural way to work if you're unfamiliar with Rx. But although this will seem to produce he same behaviour, it introduces a problem: Rx doesn't understand that these nested subscriptions are associated with the outer subscription. That won't necessarily cause a problem in this simple example, but it could if we start using additional operators. Consider this modification: + +```cs +IDisposable sub = perShipObservables.Subscribe(shipMessages => + shipMessages + .OfType() + .DistinctUntilChanged(m => m.NavigationStatus) + .Skip(1) + .Finally(() => Console.WriteLine($"Nested sub for {shipMessages.Key} ending")) + .Subscribe(m => + Console.WriteLine( + $"Ship {((IAisMessage)m).Mmsi} changed status to {m.NavigationStatus} at {DateTimeOffset.UtcNow}"))); +``` + +I've added a `Finally` operator for the nested sequence. This enables us to invoke a callback when a sequence comes to an end for any reason. But even if we unsubscribe from the outer sequence (by calling `sub.Dispose();`) this `Finally` will never do anything. That's because Rx has no way of knowing that these inner subscriptions are part of the outer one. + +If we made the same modification to the earlier version, in which these nested sequences were collected into one output sequence by `SelectMany`, Rx understands that subscriptions to the inner sequence exist only because of the subscription to the sequence returned by `SelectMany`. (In fact, `SelectMany` is what subscribes to those inner sequences.) So if we unsubscribe from the output sequence in that example, it will correctly run any `Finally` callbacks on any inners sequences. + +More generally, if you have lots of sequences coming into existence as part of a single processing chain, it is usually better to get Rx to manage the process from end to end. + +## Buffer + +The `Buffer` operator is useful if you need to deal with events in batches. This can be useful for performance, especially if you're storing data about events. Take the AIS example. If you wanted to log notifications to a persistent store, the cost of storing a single record is likely to be almost identical to the cost of storing several. Most storage devices operate with blocks of data often several kilobytes in size, so the amount of work required to store a single byte of data is often identical to the amount of work required to store several thousand bytes. The pattern of buffering up data until we have a reasonably large chunk of work crops up all the time in programming. The .NET runtime library's `Stream` class has built-in buffering for exactly this reason, so it's no surprise that it's built into Rx. + +Efficiency concerns are not the only reason you might want to process multiple events in one batch instead of individual ones. Suppose you wanted to generate a stream of continuously updated statistics about some source of data. By carving the source into chunks with `Buffer`, you can calculate, say, an average over the last 10 events. + +`Buffer` can partition the elements from a source stream, so it's a similar kind of operator to `GroupBy`, but there are a couple of significant differences. First, `Buffer` doesn't inspect the elements to determine how to partition them—it partitions purely based on the order in which elements emerge. Second, `Buffer` waits until it has completely filled a partition, and then presents all of the elements as an `IList`. This can make certain tasks a lot easier because everything in the partition is available for immediate use—values aren't buried in a nested `IObservable`. Third, `Buffer` offers some overloads that make it possible for a single element to turn up in more than one 'partition'. (In this case, `Buffer` is no longer strictly partitioning the data, but as you'll see, it's just a small variation on the other behaviours.) + +The simplest way to use `Buffer` is to gather up adjacent elements into chunks. (LINQ to Objects now has an equivalent operator that it calls [`Chunk`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.chunk). The reason Rx didn't use the same name is that Rx introduced this operator over 10 years before LINQ to Objects did. So the real question is why LINQ to Objects chose a different name. It might be because `Chunk` doesn't support all of the variations that Rx's `Buffer` does, but you'd need to ask the .NET runtime library team.) This overload of `Buffer` takes a single argument, indicating the chunk size you would like: + +```cs +public static IObservable> Buffer( + this IObservable source, + int count) +{...} +``` + +This example uses it to split navigation messages into chunks of 4, and then goes on to calculate the average speed across those 4 readings: + +```cs +IObservable> navigationChunks = receiverHost.Messages + .Where(v => v.Mmsi == 235009890) + .OfType() + .Where(n => n.SpeedOverGround.HasValue) + .Buffer(4); + +IObservable recentAverageSpeed = navigationChunks + .Select(chunk => chunk.Average(n => n.SpeedOverGround.Value)); +``` + +If the source completes, and has not produced an exact multiple of the chunk size, the final chunk will be smaller. We can see this with the following more artificial example: + +```cs +Observable + .Range(1, 5) + .Buffer(2) + .Select(chunk => string.Join(", ", chunk)) + .Dump("chunks"); +``` + +As you can see from this output, the final chunk has just a single item, even though we asked for 2 at a time: + +``` +chunks-->1, 2 +chunks-->3, 4 +chunks-->5 +chunks completed +``` + +`Buffer` had no choice here because the source completed, and if it hadn't produced that final under-sized chunk, we would never have seen the final item. But apart from this end-of-source case, this overload of `Buffer` waits until it has collected enough elements to fill a buffer of the specified size before passing it on. That means that `Buffer` introduces a delay. If source items are quite far apart (e.g., when a ship is not moving it might only report AIS navigation data every few minutes) this can lead to long delays. + +In some cases, we might want to handle multiple events in a batch when a source is busy without having to wait a long time when the source is operating more slowly. This would be useful in a user interface. If you want to provide fresh information, it might be better to accept an undersized chunk so that you can provide more timely information. For these scenarios, `Buffer` offers overloads that accept a `TimeSpan`: + +```csharp +public static IObservable> Buffer( + this IObservable source, + TimeSpan timeSpan) +{...} + +public static IObservable> Buffer( + this IObservable source, + TimeSpan timeSpan, + int count) +{...} +``` + +The first of these partitions the source based on nothing but timing. This will emit one chunk every second no matter the rate at which `source` produces value: + +```cs +IObservable> output = source.Buffer(TimeSpan.FromSeconds(1)); +``` + +If `source` happened to emit no values during any particular chunk's lifetime, `output` will emit an empty list. + +The second overload, taking both a `timespan` and a `count`, essentially imposes two upper limits: you'll never have to wait longer than `timespan` between chunks, and you'll never receive a chunk with more than `count` elements. As with the `timespan`-only overload, this can deliver under-full and even empty chunks if the source doesn't produce elements fast enough to fill the buffer within the time specified. + +### Overlapping buffers + +In the preceding section, I showed an example that collected chunks of 4 `IVesselNavigation` entries for a particular vessel, and calculated the average speed. This sort of averaging over multiple samples can be a useful way of smoothing out slight random variations in readings. So the goal in this case wasn't to process items in batches for efficiency, it was to enable a particular kind of calculation. + +But there was a problem with the example: because it was averaging 4 readings, it produced an output only once every 4 input messages. And since vessels might report their speed only once every few minutes if they are not moving, we might be waiting a very long time. + +There's an overload of `Buffer` that enables us to do a little better: instead of averaging the first 4 readings, and then the 4 readings after that, and then the 4 after that, and so on, we might want to calculate the average of the last 4 readings _every time the vessel reports a new reading. + +This is sometimes called a sliding window. We want to process readings 1, 2, 3, 4, then 2, 3, 4, 5, then 3, 4, 5, 6, and so on. There's an overload of buffer that can do this. This example shows the first statement from the earlier average speed example, but with one small modification: + +```cs +IObservable> navigationChunks = receiverHost.Messages + .Where(v => v.Mmsi == 235009890) + .OfType() + .Where(n => n.SpeedOverGround.HasValue) + .Buffer(4, 1); +``` + +This calls an overload of `Buffer` that takes two `int` arguments. The first does the same thing as before: it indicates that we want 4 items in each chunk. But the second argument indicates how often to produce a buffer. This says we want a buffer for every `1` element (i.e., every single element) that the source produces. (The overload that accepts just a `count` is equivalent to passing the same value for both arguments to this overload.) + +So this will wait until the source has produce 4 suitable messages (i.e., messages that satisfy the `Where` and `OfType` operators here) and will then report those first four readings in the first `IList` to emerge from `navigationChunks`. But the source only has to produce one more suitable message, and then this will emit another `IList`, containing 3 of the same value as were in the first chunk, and then the new value. When the next suitable message emerges, this will emit another list with the 3rd, 4th, 5th, and 6th messages, and so on. + +This marble diagram illustrates the behaviour for `Buffer(4, 1)`. + +![A marble diagram showing two sequences. The first is labelled "Range(1,6)" and shows the numbers 1 to 6. The second is labelled ".Buffer(4,1)", and it shows three events. The colour coding and horizontal position indicate that these emerge at the same time as he final three events in the top diagram. The first event on this second sequence contains a list of numbers, "1,2,3,4", the second shows "2,3,4,5" and the third shows "3,4,5,6".](GraphicsIntro/Ch08-Partitioning-Marbles-Buffer-Marbles.svg) + +If we fed this into the same `recentAverageSpeed` expression as the earlier example, we'd still get no output until the 4th suitable message emerges from the source, but from then on, every single suitable message to emerge from the source will emit a new average value. These average values will still always report the average of the 4 most recently reported speeds, but we will now get these averages four times as often. + +We could also use this to improve the example earlier that reported when ships changed their `NavigationStatus`. The last example told you what state a vessel had just entered, but this raises an obvious question: what state was it in before? We can use `Buffer(2, 1)` so that each time we see a message indicating a change in status, we also have access to the preceding change in status: + +```cs +IObservable> shipStatusChanges = + perShipObservables.SelectMany(shipMessages => shipMessages + .OfType() + .DistinctUntilChanged(m => m.NavigationStatus) + .Buffer(2, 1)); + +IDisposable sub = shipStatusChanges.Subscribe(m => Console.WriteLine( + $"Ship {((IAisMessage)m[0]).Mmsi} changed status from" + + $" {m[1].NavigationStatus} to {m[1].NavigationStatus}" + + $" at {DateTimeOffset.UtcNow}")); +``` + +As the output shows, we can now report the previous state as well as the state just entered: + +``` +Ship 259664000 changed status from UnderwayUsingEngine to Moored at 30/06/2023 + 13:36:39 +00:00 +Ship 257139000 changed status from AtAnchor to UnderwayUsingEngine at 30/06/20 +23 13:38:39 +00:00 +Ship 257798800 changed status from UnderwayUsingEngine to Moored at 30/06/2023 + 13:38:39 +00:00 +``` + +This change enabled us to remove the `Skip`. The earlier example had that because we can't tell whether the first message we receive from any particular ship after startup represents a change. But since we're telling `Buffer` we want pairs of messages, it won't give us anything for any single ship until it has seen messages with two different statuses. + +You can also ask for a sliding window defined by time instead of counts using this overload: + +```csharp +public static IObservable> Buffer( + this IObservable source, + TimeSpan timeSpan, + TimeSpan timeShift) +{...} +``` + +The `timeSpan` determines the length of time covered by each window, and the `timeShift` determines the interval at which new windows are started. + +## Window + +The `Window` operator is very similar to the `Buffer`. It can split the input into chunks based either on element count or time, and it also offers support for overlapping windows. However, it has a different return type. Whereas using `Buffer` on an `IObservable` will return an `IObservable>`, `Window` will return an `IObservable>`. This means that `Window` doesn't have to wait until it has filled a complete buffer before producing anything. You could say that `Window` more fully embraces the reactive paradigm than `Buffer`. Then again after some experience you might conclude that `Window` is harder to use than `Buffer` but is very rarely any more useful in practice. + +Because `Buffer` returns an `IObservable>`, it can't produce a chunk until it has all of the elements that will go into that chunk. `IList` supports random access—you can ask it how many elements it has, and you can retrieve any element by numeric index, and we expect these operations to complete immediately. (It would be technically possible to write an implementation of `IList` representing as yet unreceived data, and to make its `Count` and indexer properties block if you try to use them before that data is available, but this would be a strange thing to do. Developers expect lists to return information immediately, and the lists produced by Rx's `Buffer` meet that expectation.) So if you write, say, `Buffer(4)`, it can't produce anything until it has all 4 items that will constitute the first chunk. + +But because `Window` returns an observable that produces a nested observable to represent each chunk, it can emit that before necessarily having all of the elements. In fact, it emits a new window as soon as it knows it will need one. If you use `Window(4, 1)` for example, the observable it returns emits its first nested observable immediately. And then as soon as the source produces its first element, that nested observable will emit that element, and then the second nested observable will be produced. We passed `1` as the 2nd argument to `Window`, so we get a new window for every element the source produces. As soon as the first element has been emitted, the next item the source emits will appear in the second window (and also the first, since we've specified overlapping windows in this case), so the second window is effectively _open_ from immediately after the emergence of the first element. So the `IObservable>` that `Window` return produces a new `IObservable` at that point. + +Nested observables produce their items as and when they become available. They complete once `Window` knows there will be no further items in that window (i.e., at exactly the same point `Buffer` would have produced the completed `IList` for that window.) + +`Window` can seem like it is better than `Buffer` because it lets you get your hands on the individual items in a chunk the instant they are available. However, if you were doing calculations that required access to every single item in the chunk, this doesn't necessarily help you. You're not going to be able to complete your processing until you've received every item in the chunk, so you're not going to produce a final result any earlier, and your code might be more complicated because it can no longer count on having an `IList` conveniently making all of the items available at once. However, if you're calculating some sort of aggregation over the items in a chunk, `Window` might be more efficient because it enables you to process each item as it emerges and then discard it. If a chunk is very large, `Buffer` would have to hold onto every item until the chunk completes, which might use more memory. Moreover, in cases where you don't necessarily need to see every item in a chunk before you can do something useful with those items, `Window` might enable you to avoid introducing processing delays. + +`Window` doesn't help us in the AIS `NavigationStatus` example, because the goal there was to report both the _before_ and _after_ status for each change. We can't do that until we know what the _after_ value is, so we would get no benefit from receiving the _before_ value earlier. We need the second value to do what we're trying to do, so we might as well use `Buffer` because it's easier. But if you wanted to keep track of the number of distinct vessels that have reported movement so far today, `Window` would be an appropriate mechanism: you could set it up to produce one window per day, and you would be able to start seeing information within each window without needing to wait until the end of the day. + +In addition to supporting simple count-based or duration-based splitting, there are more flexible ways to define the window boundaries, such as this overload: + +```cs +// Projects each element of an observable sequence into consecutive non-overlapping windows. +// windowClosingSelector : A function invoked to define the boundaries of the produced +// windows. A new window is started when the previous one is closed. +public static IObservable> Window +( + this IObservable source, + Func> windowClosingSelector +) +``` + +The first of these complex overloads allows us to control when windows close. The `windowClosingSelector` function is called each time a window is created, and each windows will close when the corresponding sequence from the `windowClosingSelector` produces a value. The value is disregarded so it doesn't matter what type the sequence values are; in fact you can just complete the sequence from `windowClosingSelector` to close the window instead. + +In this example, we create a window with a closing selector. We return the same subject from that selector every time, then notify from the subject whenever a user presses enter from the console. + +```cs +int windowIdx = 0; +IObservable source = Observable.Interval(TimeSpan.FromSeconds(1)).Take(10); +var closer = new Subject(); +source.Window(() => closer) + .Subscribe(window => + { + int thisWindowIdx = windowIdx++; + Console.WriteLine("--Starting new window"); + string windowName = $"Window{thisWindowIdx}"; + window.Subscribe( + value => Console.WriteLine("{0} : {1}", windowName, value), + ex => Console.WriteLine("{0} : {1}", windowName, ex), + () => Console.WriteLine("{0} Completed", windowName)); + }, + () => Console.WriteLine("Completed")); + +string input = ""; +while (input != "exit") +{ + input = Console.ReadLine(); + closer.OnNext(Unit.Default); +} +``` + +Output (when I hit enter after '1' and '5' are displayed): + +``` +--Starting new window +window0 : 0 +window0 : 1 + +window0 Completed + +--Starting new window +window1 : 2 +window1 : 3 +window1 : 4 +window1 : 5 + +window1 Completed + +--Starting new window +window2 : 6 +window2 : 7 +window2 : 8 +window2 : 9 + +window2 Completed + +Completed +``` + +The most complex overload of `Window` allows us to create potentially overlapping windows. + +```csharp +// Projects each element of an observable sequence into zero or more windows. +// windowOpenings : Observable sequence whose elements denote the creation of new windows. +// windowClosingSelector : A function invoked to define the closing of each produced window. +public static IObservable> Window + +( + this IObservable source, + IObservable windowOpenings, + Func> windowClosingSelector +) +``` + +This overload takes three arguments + +1. The source sequence +2. A sequence that indicates when a new window should be opened +3. A function that takes a window opening value, and returns a window closing sequence + +This overload offers great flexibility in the way windows are opened and closed. Windows can be largely independent from each other; they can overlap, vary in size and even skip values from the source. + +To ease our way into this more complex overload, let's first try to use it to recreate a simpler version of `Window` (the overload that takes a count). To do so, we need to open a window once on the initial subscription, and once each time the source has produced then specified count. The window needs to close each time that count is reached. To achieve this we only need the source sequence. We will be subscribing to it multiple times, but for some kinds of sources that might cause problems, so we do so via the [`Publish`](15_PublishingOperators.md#publish) operator, which enables multiple subscribers while making only one subscription to the underlying source. + +```csharp +public static IObservable> MyWindow( + this IObservable source, + int count) +{ + IObservable shared = source.Publish().RefCount(); + IObservable windowEdge = shared + .Select((i, idx) => idx % count) + .Where(mod => mod == 0) + .Publish() + .RefCount(); + return shared.Window(windowEdge, _ => windowEdge); +} +``` + +If we now want to extend this method to offer skip functionality, we need to have two different sequences: one for opening and one for closing. We open a window on subscription and again after the `skip` items have passed. We close those windows after '`count`' items have passed since the window opened. + +```csharp +public static IObservable> MyWindow( + this IObservable source, + int count, + int skip) +{ + if (count <= 0) throw new ArgumentOutOfRangeException(); + if (skip <= 0) throw new ArgumentOutOfRangeException(); + + IObservable shared = source.Publish().RefCount(); + IObservable index = shared + .Select((i, idx) => idx) + .Publish() + .RefCount(); + + IObservable windowOpen = index.Where(idx => idx % skip == 0); + IObservable windowClose = index.Skip(count-1); + + return shared.Window(windowOpen, _ => windowClose); +} +``` + +We can see here that the `windowClose` sequence is re-subscribed to each time a window is opened, due to it being returned from a function. This allows us to reapply the skip (`Skip(count-1)`) for each window. Currently, we ignore the value that the `windowOpen` pushes to the `windowClose` selector, but if you require it for some logic, it is available to you. + +As you can see, the `Window` operator can be quite powerful. We can even use `Window` to replicate other operators; for instance we can create our own implementation of `Buffer` that way. We can have the `SelectMany` operator take a single value (the window) to produce zero or more values of another type (in our case, a single `IList`). To create the `IList` without blocking, we can apply the `Aggregate` method and use a new `List` as the seed. + +```csharp +public static IObservable> MyBuffer(this IObservable source, int count) +{ + return source.Window(count) + .SelectMany(window => + window.Aggregate( + new List(), + (list, item) => + { + list.Add(item); + return list; + })); +} +``` + +You might find it to be an interesting exercise to try implementing other time shifting methods, like `Sample` or `Throttle`, with `Window`. + +We've seen a few useful ways to spread a single stream of items across multiple output sequences, using either data-driven grouping criteria, or time-based chunking with either `Buffer` or `Window`. In the next chapter, we'll look at operators that can combine together data from multiple streams. \ No newline at end of file diff --git a/content/08_Transformation.md b/content/08_Transformation.md deleted file mode 100644 index 18c8440..0000000 --- a/content/08_Transformation.md +++ /dev/null @@ -1,684 +0,0 @@ ---- -title: Transformation of sequences ---- - -# Transformation of sequences - -The values from the sequences we consume are not always in the format we need. Sometimes there is too much noise in the data so we strip the values down. Sometimes each value needs to be expanded either into a richer object or into more values. By composing operators, Rx allows you to control the quality as well as the quantity of values in the observable sequences you consume. - -Up until now, we have looked at creation of sequences, transition into sequences, and, the reduction of sequences by filtering, aggregating or folding. In this chapter we will look at _transforming_ sequences. This allows us to introduce our third category of functional methods, _bind_. A bind function in Rx will take a sequence and apply some set of transformations on each element to produce a new sequence. - -To review: - -``` -Ana(morphism) T --> IObservable -Cata(morphism) IObservable --> T -Bind IObservable --> IObservable -``` - -Now that we have been introduced to all three of our higher order functions, you may find that you already know them. Bind and Cata(morphism) were made famous by [MapReduce](http://en.wikipedia.org/wiki/MapReduce) framework from Google. Here Google refer to Bind and Cata by their perhaps more common aliases; Map and Reduce. - -It may help to remember our terms as the `ABCs` of higher order functions. - -``` -Ana enters the sequence. T --> IObservable -Bind modifies the sequence. IObservable --> IObservable -Cata leaves the sequence. IObservable --> T -``` - -## Select - -The classic transformation method is `Select`. It allows you provide a function that takes a value of `TSource` and return a value of `TResult`. The signature for `Select` is nice and simple and suggests that its most common usage is to transform from one type to another type, i.e. `IObservable` to `IObservable`. - -```csharp -IObservable Select( - this IObservable source, - Func selector) -``` - -Note that there is no restriction that prevents `TSource` and `TResult` being the same thing. So for our first example, we will take a sequence of integers and transform each value by adding 3, resulting in another sequence of integers. - -```csharp -var source = Observable.Range(0, 5); -source.Select(i=>i+3) - .Dump("+3") -``` - -Output: - -``` -+3 --> 3 -+3 --> 4 -+3 --> 5 -+3 --> 6 -+3 --> 7 -+3 completed -``` - -While this can be useful, more common use is to transform values from one type to another. In this example we transform integer values to characters. - -```csharp -Observable.Range(1, 5); - .Select(i =>(char)(i + 64)) - .Dump("char"); -``` - -Output: - -``` -char --> A -char --> B -char --> C -char --> D -char --> E -char completed -``` - -If we really want to take advantage of LINQ we could transform our sequence of integers to a sequence of anonymous types. - -```csharp -Observable.Range(1, 5) - .Select(i => new { Number = i, Character = (char)(i + 64) }) - .Dump("anon"); -``` - -Output: - -``` -anon --> { Number = 1, Character = A } -anon --> { Number = 2, Character = B } -anon --> { Number = 3, Character = C } -anon --> { Number = 4, Character = D } -anon --> { Number = 5, Character = E } -anon completed -``` - -To further leverage LINQ we could write the above query using [query comprehension syntax](http://www.albahari.com/nutshell/linqsyntax.aspx). - -```csharp -var query = from i in Observable.Range(1, 5) - select new {Number = i, Character = (char) (i + 64)}; - -query.Dump("anon"); -``` - -In Rx, `Select` has another overload. The second overload provides two values to the `selector` function. The additional argument is the element's index in the sequence. Use this method if the index of the element in the sequence is important to your selector function. - -## Cast and OfType - -If you were to get a sequence of objects i.e. `IObservable`, you may find it less than useful. There is a method specifically for `IObservable` that will cast each element to a given type, and logically it is called `Cast()`. - -```csharp -var objects = new Subject(); -objects.Cast().Dump("cast"); -objects.OnNext(1); -objects.OnNext(2); -objects.OnNext(3); -objects.OnCompleted(); -``` - -Output: - -``` -cast --> 1 -cast --> 2 -cast --> 3 -cast completed -``` - -If however we were to add a value that could not be cast into the sequence then we get errors. - -```csharp -var objects = new Subject(); -objects.Cast().Dump("cast"); -objects.OnNext(1); -objects.OnNext(2); -objects.OnNext("3");//Fail -``` - -Output: - -``` -cast --> 1 -cast --> 2 -cast failed --> Specified cast is not valid. -``` - -Thankfully, if this is not what we want, we could use the alternative extension method `OfType()`. - -```csharp -var objects = new Subject(); -objects.OfType().Dump("OfType"); -objects.OnNext(1); -objects.OnNext(2); -objects.OnNext("3");//Ignored -objects.OnNext(4); -objects.OnCompleted(); -``` - -Output: - -``` -OfType --> 1 -OfType --> 2 -OfType --> 4 -OfType completed -``` - -It is fair to say that while these are convenient methods to have, we could have created them with the operators we already know about. - -```csharp -// source.Cast(); is equivalent to -source.Select(i=>(int)i); - -// source.OfType(); -source.Where(i=>i is int).Select(i=>(int)i); -``` - -## Timestamp and TimeInterval - -As observable sequences are asynchronous it can be convenient to know timings for when elements are received. The `Timestamp` extension method is a handy convenience method that wraps elements of a sequence in a light weight `Timestamped` structure. The `Timestamped` type is a struct that exposes the value of the element it wraps, and the timestamp it was created with as a `DateTimeOffset`. - -In this example we create a sequence of three values, one second apart, and then transform it to a time stamped sequence. The handy implementation of `ToString()` on `Timestamped` gives us a readable output. - -```csharp -Observable.Interval(TimeSpan.FromSeconds(1)) - .Take(3) - .Timestamp() - .Dump("TimeStamp"); -``` - -Output - -``` -TimeStamp --> 0@01/01/2012 12:00:01 a.m. +00:00 -TimeStamp --> 1@01/01/2012 12:00:02 a.m. +00:00 -TimeStamp --> 2@01/01/2012 12:00:03 a.m. +00:00 -TimeStamp completed -``` - -We can see that the values 0, 1 & 2 were each produced one second apart. An alternative to getting an absolute timestamp is to just get the interval since the last element. The `TimeInterval` extension method provides this. As per the `Timestamp` method, elements are wrapped in a light weight structure. This time the structure is the `TimeInterval` type. - -```csharp -Observable.Interval(TimeSpan.FromSeconds(1)) - .Take(3) - .TimeInterval() - .Dump("TimeInterval"); -``` - -Output: - -``` -TimeInterval --> 0@00:00:01.0180000 -TimeInterval --> 1@00:00:01.0010000 -TimeInterval --> 2@00:00:00.9980000 -TimeInterval completed -``` - -As you can see from the output, the timings are not exactly one second but are pretty close. - -## Materialize and Dematerialize - -The `Timestamp` and `TimeInterval` transform operators can prove useful for logging and debugging sequences, so too can the `Materialize` operator. `Materialize` transitions a sequence into a metadata representation of the sequence, taking an `IObservable` to an `IObservable>`. The `Notification` type provides meta data for the events of the sequence. - -If we materialize a sequence, we can see the wrapped values being returned. - -```csharp -Observable.Range(1, 3) - .Materialize() - .Dump("Materialize"); -``` - -Output: - -``` -Materialize --> OnNext(1) -Materialize --> OnNext(2) -Materialize --> OnNext(3) -Materialize --> OnCompleted() -Materialize completed -``` - -Note that when the source sequence completes, the materialized sequence produces an 'OnCompleted' notification value and then completes. `Notification` is an abstract class with three implementations: - - * OnNextNotification - * OnErrorNotification - * OnCompletedNotification - -`Notification` exposes four public properties to help you discover it: `Kind`, `HasValue`, `Value` and `Exception`. Obviously only `OnNextNotification` will return true for `HasValue` and have a useful implementation of `Value`. It should also be obvious that `OnErrorNotification` is the only implementation that will have a value for `Exception`. The `Kind` property returns an `enum` which should allow you to know which methods are appropriate to use. - -```csharp -public enum NotificationKind -{ - OnNext, - OnError, - OnCompleted, -} -``` - -In this next example we produce a faulted sequence. Note that the final value of the materialized sequence is an `OnErrorNotification`. Also that the materialized sequence does not error, it completes successfully. - -```csharp -var source = new Subject(); -source.Materialize() - .Dump("Materialize"); - -source.OnNext(1); -source.OnNext(2); -source.OnNext(3); - -source.OnError(new Exception("Fail?")); -``` - -Output: - -``` -Materialize --> OnNext(1) -Materialize --> OnNext(2) -Materialize --> OnNext(3) -Materialize --> OnError(System.Exception) -Materialize completed -``` - -Materializing a sequence can be very handy for performing analysis or logging of a sequence. You can unwrap a materialized sequence by applying the `Dematerialize` extension method. The `Dematerialize` will only work on `IObservable>`. - -## SelectMany - -Of the transformation operators above, we can see that `Select` is the most useful. It allows very broad flexibility in its transformation output and can even be used to reproduce some of the other transformation operators. The `SelectMany` operator however is even more powerful. In LINQ and therefore Rx, the _bind_ method is `SelectMany`. Most other transformation operators can be built with `SelectMany`. Considering this, it is a shame to think that `SelectMany` may be one of the most misunderstood methods in LINQ. - -In my personal discovery of Rx, I struggled to grasp the `SelectMany` extension method. One of my colleagues helped me understand `SelectMany` better by suggesting I think of it as from one, select many. An even better definition is From one, select zero or more. If we look at the signature for `SelectMany` we see that it takes a source sequence and a function as its parameters. - -```csharp -IObservable SelectMany( - this IObservable source, - Func> selector) -``` - -The `selector` parameter is a function that takes a single value of `T` and returns a sequence. Note that the sequence the `selector` returns does not have to be of the same type as the `source`. Finally, the `SelectMany` return type is the same as the `selector` return type. - -This method is very important to understand if you wish to work with Rx effectively, so let's step through this slowly. It is also important to note its subtle differences to `IEnumerable`'s `SelectMany` operator, which we will look at soon. - -Our first example will take a sequence with the single value '3' in it. The selector function we provide will produce a further sequence of numbers. This result sequence will be a range of numbers from 1 to the value provided i.e. 3. So we take the sequence [3] and return the sequence [1,2,3] from our `selector` function. - -```csharp -Observable.Return(3) - .SelectMany(i => Observable.Range(1, i)) - .Dump("SelectMany"); -``` - -Output: - -``` -SelectMany --> 1 -SelectMany --> 2 -SelectMany --> 3 -SelectMany completed -``` - -If we modify our source to be a sequence of [1,2,3] like this... - -```csharp -Observable.Range(1,3) - .SelectMany(i => Observable.Range(1, i)) - .Dump("SelectMany"); -``` - -...we will now get an output with the result of each sequence ([1], [1,2] and [1,2,3]) flattened to produce [1,1,2,1,2,3]. - -``` -SelectMany --> 1 -SelectMany --> 1 -SelectMany --> 2 -SelectMany --> 1 -SelectMany --> 2 -SelectMany --> 3 -SelectMany completed -``` - -This last example better illustrates how `SelectMany` can take a `single` value and expand it to many values. When we then apply this to a `sequence` of values, the result is each of the child sequences combined to produce the final sequence. In both examples, we have returned a sequence that is the same type as the source. This is not a restriction however, so in this next example we return a different type. We will reuse the `Select` example of transforming an integer to an ASCII character. To do this, the `selector` function just returns a char sequence with a single value. - -```csharp -Func letter = i => (char)(i + 64); -Observable.Return(1) - .SelectMany(i => Observable.Return(letter(i))); - .Dump("SelectMany"); -``` - -So with the input of [1] we return a sequence of [A]. - -``` -SelectMany --> A -SelectMany completed -``` - -Extending the source sequence to have many values, will give us a result with many values. - -```csharp -Func letter = i => (char)(i + 64); -Observable.Range(1,3) - .SelectMany(i => Observable.Return(letter(i))) - .Dump("SelectMany"); -``` - -Now the input of [1,2,3] produces [[A], [B], [C]] which is flattened to just [A,B,C]. - -``` -SelectMany --> A -SelectMany --> B -SelectMany --> C -``` - -Note that we have effectively recreated the `Select` operator. - -The last example maps a number to a letter. As there are only 26 letters, it would be nice to ignore values greater than 26. This is easy to do. While we must return a sequence for each element of the source, there aren't any rules that prevent it from being an empty sequence. In this case if the element value is a number outside of the range 1-26 we return an empty sequence. - -```csharp -Func letter = i => (char)(i + 64); -Observable.Range(1, 30) - .SelectMany( - i => - { - if (0 < i && i < 27) - { - return Observable.Return(letter(i)); - } - else - { - return Observable.Empty(); - } - }) - .Dump("SelectMany"); -``` - -Output: - -``` -A -B -C -... -X -Y -Z -Completed -``` - -To be clear, for the source sequence [1..30], the value 1 produced a sequence [A], the value 2 produced a sequence [B] and so on until value 26 produced a sequence [Z]. When the source produced value 27, the `selector` function returned the empty sequence []. Values 28, 29 and 30 also produced empty sequences. Once all the sequences from the calls to the selector had been fattened to produce the final result, we end up with the sequence [A..Z]. - -Now that we have covered the third of our three higher order functions, let us take time to reflect on some of the methods we have already learnt. First we can consider the `Where` extension method. We first looked at this method in the chapter on [Reducing a sequence](05_Filtering.html#Where). While this method does reduce a sequence, it is not a fit for a functional _fold_ as the result is still a sequence. Taking this into account, we find that `Where` is actually a fit for _bind_. As an exercise, try to write your own extension method version of `Where` using the `SelectMany` operator. Review the last example for some help... - -An example of a `Where` extension method written using `SelectMany`: - -```csharp -public static IObservable Where(this IObservable source, Func predicate) -{ - return source.SelectMany(item => - { - if (predicate(item)) - { - return Observable.Return(item); - } - else - { - return Observable.Empty(); - } - }); -} -``` - -Now that we know we can use `SelectMany` to produce `Where`, it should be a natural progression for you the reader to be able to extend this to reproduce other filters like `Skip` and `Take`. - -As another exercise, try to write your own version of the `Select` extension method using `SelectMany`. Refer to our example where we use `SelectMany` to convert `int` values into `char` values if you need some help... - -An example of a `Select` extension method written using `SelectMany`: - -```csharp -public static IObservable MySelect( - this IObservable source, - Func selector) -{ - return source.SelectMany(value => Observable.Return(selector(value))); -} -``` - -### IEnumerable vs. IObservable SelectMany - -It is worth noting the difference between the implementations of `IEnumerable` `SelectMany` and `IObservable` `SelectMany`. Consider that `IEnumerable` sequences are pull based and blocking. This means that when an `IEnumerable` is processed with a `SelectMany` it will pass one item at a time to the `selector` function and wait until it has processed all of the values from the `selector` before requesting (pulling) the next value from the source. - -Consider an `IEnumerable` source sequence of `[1,2,3]`. If we process that with a `SelectMany` operator that returns a sequence of `[x*10, (x*10)+1, (x*10)+2]`, we would get the `[[10,11,12]`, `[20,21,22]`, `[30,31,32]]`. - -```csharp -private IEnumerable GetSubValues(int offset) -{ - yield return offset * 10; - yield return (offset * 10) + 1; - yield return (offset * 10) + 2; -} -``` - -We then apply the `GetSubValues` method with the following code: - -```csharp -var enumerableSource = new [] {1, 2, 3}; -var enumerableResult = enumerableSource.SelectMany(GetSubValues); - -foreach (var value in enumerableResult) -{ - Console.WriteLine(value); -} -``` - -The resulting child sequences are flattened into [10,11,12,20,21,22,30,31,32]. - -``` -10 -11 -12 -20 -21 -22 -30 -31 -32 -``` - -The difference with `IObservable` sequences is that the call to the `SelectMany`'s `selector` function is not blocking and the result sequence can produce values over time. This means that subsequent 'child' sequences can overlap. Let us consider again a sequence of [1,2,3], but this time values are produced three second apart. The `selector` function will also produce sequence of [x*10, (x*10)+1, (x*10)+2] as per the example above, however these values will be four seconds apart. - -To visualize this kind of asynchronous data we need to represent space and time. - -### Visualizing sequences - -Let's divert quickly and talk about a technique we will use to help communicate the concepts relating to sequences. Marble diagrams are a way of visualizing sequences. Marble diagrams are great for sharing Rx concepts and describing composition of sequences. When using marble diagrams there are only a few things you need to know - -1. a sequence is represented by a horizontal line -2. time moves to the right (i.e. things on the left happened before things on the right) -3. notifications are represented by symbols: - *. '0' for OnNext - *. 'X' for an OnError - *. '|' for OnCompleted -1. many concurrent sequences can be visualized by creating rows of sequences - -This is a sample of a sequence of three values that completes: - -
-
--0--0--0-|
-
- -This is a sample of a sequence of four values then an error: - -
-
--0--0--0--0--X
-
- -Now going back to our `SelectMany` example, we can visualize our input sequence by using values in instead of the 0 marker. This is the marble diagram representation of the sequence [1,2,3] spaced three seconds apart (note each character represents one second). - -
-
--1--2--3|
-
- -Now we can leverage the power of marble diagrams by introducing the concept of time and space. Here we see the visualization of the sequence produced by the first value 1 which gives us the sequence [10,11,12]. These values were spaced four seconds apart, but the initial value is produce immediately. - -
-
1---1---1|
-
0   1   2|
-
- -As the values are double digit they cover two rows, so the value of 10 is not confused with the value 1 immediately followed by the value 0. We add a row for each sequence produced by the `selector` function. - -
-
--1--2--3|
-
 
-
  1---1---1|
-
  0   1   2|
-
 
-
     2---2---2|
-
     0   1   2|
-

-    
        3---3---3|
-
        0   1   2|
-
- -Now that we can visualize the source sequence and its child sequences, we should be able to deduce the expected output of the `SelectMany` operator. To create a result row for our marble diagram, we simple allow the values from each child sequence to 'fall' into the new result row. - -
-
--1--2--3|
-
 
-
  1---1---1|
-
  0   1   2|
-
 
-
     2---2---2|
-
     0   1   2|
-

-    
        3---3---3|
-
        0   1   2|
-

-    
--1--21-321-32--3|
-
  0  01 012 12  2|
-

-
- -If we take this exercise and now apply it to code, we can validate our marble diagram. First our method that will produce our child sequences: - -```csharp -private IObservable GetSubValues(long offset) -{ - //Produce values [x*10, (x*10)+1, (x*10)+2] 4 seconds apart, but starting immediately. - return Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(4)) - .Select(x => (offset*10) + x) - .Take(3); -} -``` - -This is the code that takes the source sequence to produce our final output: - -```csharp -// Values [1,2,3] 3 seconds apart. -Observable.Interval(TimeSpan.FromSeconds(3)) - .Select(i => i + 1) //Values start at 0, so add 1. - .Take(3) //We only want 3 values - .SelectMany(GetSubValues) //project into child sequences - .Dump("SelectMany"); -``` - -The output produced matches our expectations from the marble diagram. - -``` -SelectMany --> 10 -SelectMany --> 20 -SelectMany --> 11 -SelectMany --> 30 -SelectMany --> 21 -SelectMany --> 12 -SelectMany --> 31 -SelectMany --> 22 -SelectMany --> 32 -SelectMany completed -``` - -We have previously looked at the `Select` operator when it is used in Query Comprehension Syntax, so it is worth noting how you use the `SelectMany` operator. The `Select` extension method maps quite obviously to query comprehension syntax, `SelectMany` is not so obvious. As we saw in the earlier example, the simple implementation of just suing select is as follows: - -```csharp -var query = from i in Observable.Range(1, 5) - select i; -``` - -If we wanted to add a simple `where` clause we can do so like this: - -```csharp -var query = from i in Observable.Range(1, 5) - where i%2==0 - select i; -``` - -To add a `SelectMany` to the query, we actually add an extra `from` clause. - -```csharp -var query = from i in Observable.Range(1, 5) - where i%2==0 - from j in GetSubValues(i) - select j; - -// Equivalent to -var query = Observable.Range(1, 5) - .Where(i=>i%2==0) - .SelectMany(GetSubValues); -``` - -An advantage of using the query comprehension syntax is that you can easily access other variables in the scope of the query. In this example we select into an anon type both the value from the source and the child value. - -```csharp -var query = from i in Observable.Range(1, 5) - where i%2==0 - from j in GetSubValues(i) - select new {i, j}; - -query.Dump("SelectMany"); -``` - -Output - -``` -SelectMany --> { i = 2, j = 20 } -SelectMany --> { i = 4, j = 40 } -SelectMany --> { i = 2, j = 21 } -SelectMany --> { i = 4, j = 41 } -SelectMany --> { i = 2, j = 22 } -SelectMany --> { i = 4, j = 42 } -SelectMany completed -``` - -This brings us to a close on Part 2. The key takeaways from this were to allow you the reader to understand a key principal to Rx: functional composition. As we move through Part 2, examples became progressively more complex. We were leveraging the power of LINQ to chain extension methods together to compose complex queries. - -We didn't try to tackle all of the operators at once, we approached them in groups. - -- Creation -- Reduction -- Inspection -- Aggregation -- Transformation - -On deeper analysis of the operators we find that most of the operators are actually specialization of the higher order functional concepts. We named them the ABC's of functional programming: - -- Anamorphism, aka: - - Ana - - Unfold - - Generate -- Bind, aka: - - Map - - SelectMany - - Projection - - Transform -- Catamorphism, aka: - - Cata - - Fold - - Reduce - - Accumulate - - Inject - -Now you should feel that you have a strong understanding of how a sequence can be manipulated. What we have learnt up to this point however can all largely be applied to `IEnumerable` sequences too. Rx can be much more complex than what many people will have dealt with in `IEnumerable` world, as we have seen with the `SelectMany` operator. In the next part of the book we will uncover features specific to the asynchronous nature of Rx. With the foundation we have built so far we should be able to tackle the far more challenging and interesting features of Rx. \ No newline at end of file diff --git a/content/09_CombiningSequences.md b/content/09_CombiningSequences.md new file mode 100644 index 0000000..1351adf --- /dev/null +++ b/content/09_CombiningSequences.md @@ -0,0 +1,897 @@ +--- +title: Combining sequences +--- + +# Combining sequences + +Data sources are everywhere, and sometimes we need to consume data from more than just a single source. Common examples that have many inputs include: price feeds, sensor networks, news feeds, social media aggregators, file watchers, multi touch surfaces, heart-beating/polling servers, etc. The way we deal with these multiple stimuli is varied too. We may want to consume it all as a deluge of integrated data, or one sequence at a time as sequential data. We could also get it in an orderly fashion, pairing data values from two sources to be processed together, or perhaps just consume the data from the first source that responds to the request. + +Earlier chapters have also shown some examples of the _fan out and back in_ style of data processing, where we partition data, and perform processing on each partition to convert high-volume data into lower-volume higher-value events before recombining. This ability to restructure streams greatly enhances the benefits of operator composition. If Rx only enabled us to apply composition as a simple linear processing chain, it would be a good deal less powerful. Being able to pull streams apart gives us much more flexibility. So even when there is a single source of events, we often still need to combine multiple observable streams as part of our processing. Sequence composition enables you to create complex queries across multiple data sources. This unlocks the possibility to write some very powerful yet succinct code. + +We've already used [`SelectMany`](06_Transformation.md#selectmany) in earlier chapters. This is one of the fundamental operators in Rx. As we saw in the [Transformation chapter](06_Transformation.md), it's possible to build several other operators from `SelectMany`, and its ability to combine streams is part of what makes it powerful. But there are several more specialized combination operators available, which make it easier to solve certain problems than it would be using `SelectMany`. Also, some operators we've seen before (including `TakeUntil` and `Buffer`) have overloads we've not yet explored that can combine multiple sequences. + +## Sequential Combination + +We'll start with the simplest kind of combining operators, which do not attempt concurrent combination. They deal with one source sequence at a time. + +### Concat + +`Concat` is arguably the simplest way to combine sequences. It does the same thing as its namesake in other LINQ providers: it concatenates two sequences. The resulting sequence produces all of the elements from the first sequence, followed by all of the elements from the second sequence. The simplest signature for `Concat` is as follows. + +```cs +public static IObservable Concat( + this IObservable first, + IObservable second) +``` + +Since `Concat` is an extension method, we can invoke it as a method on any sequence, passing the second sequence in as the only argument: + +```cs +IObservable s1 = Observable.Range(0, 3); +IObservable s2 = Observable.Range(5, 5); +IObservable c = s1.Concat(s2); +IDisposable sub = c.Subscribe(Console.WriteLine, x => Console.WriteLine("Error: " + x)); +``` + +This marble diagram shows the items emerging from the two sources, `s1` and `s2`, and how `Concat` combines them into the result, `c`: + +![A marble diagram showing three sequences. The first, s1, produces the values 0, 1, and 2, and then completes shortly after. The second, s2 starts after s1 finishes, and produces the values 5, 6, 7, 8, and 9, and then completes. The final sequence, c, produces all of the values from s1 then s2, that is: 0, 1, 2, 5, 6, 7, 8, and 9. The positioning shows that each item in c is produced at the same time as the corresponding value from s1 or s2.](./GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Marbles.svg) + +Rx's `Concat` does nothing with its sources until something subscribes to the `IObservable` it returns. So in this case, when we call `Subscribe` on `c` (the source returned by `Concat`) it will subscribe to its first input, `s1`, and each time that produces a value, the `c` observable will emit that same value to its subscriber. If we went on to call `sub.Dispose()` before `s1` completes, `Concat` would unsubscribe from the first source, and would never subscribe to `s2`. If `s1` were to report an error, `c` would report that same error to is subscriber, and again, it will never subscribe to `s2`. Only if `s1` completes will the `Concat` operator subscribe to `s2`, at which point it will forward any items that second input produces until either the second source completes or fails, or the application unsubscribes from the concatenated observable. + +Although Rx's `Concat` has the same logical behaviour as the [LINQ to Objects `Concat`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.concat), there are some Rx-specific details to be aware of. In particular, timing is often more significant in Rx than with other LINQ implementations. For example, in Rx we distinguish between [_hot_ and _cold_ source](02_KeyTypes.md#hot-and-cold-sources). With a cold source it typically doesn't matter exactly when you subscribe, but hot sources are essentially live, so you only get notified of things that happen while you are subscribed. This can mean that hot sources might not be a good fit with `Concat` The following marble diagram illustrates a scenario in which this produces results that have the potential to surprise: + +![A marble diagram showing three sequences. The first, labelled 'cold', produces the values 0, 1, and 2, then completes. The second, labelled 'hot' produces values A, B, C, D, and E, but the positioning shows that these overlap partially with the 'cold' sequence. In particular, 'hot' produces A between items 0 and 1 from 'cold', and it produces B between 1 and 2, meaning that these A and B values occur before cold completes. The final sequence, labelled 'cold.Concat(hot)' shows all three values from 'cold' followed by only those values that 'hot' produced after 'cold' completes, i.e., just C, D, and E.](./GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Hot-Marbles.svg) + +Since `Concat` doesn't subscribe to its second input until the first has finished, it won't see the first couple of items that the `hot` source would deliver to any subscribers that been listening from the start. This might not be the behaviour you would expect: it certainly doesn't look like this concatenated all of the items from the first sequence with all of the items from the second one. It looks like it missed out `A` and `B` from `hot`. + +#### Marble Diagram Limitations + +This last example reveals that marble diagrams gloss over a detail: they show when a source starts, when it produces values, and when it finishes, but they ignore the fact that to be able to produce items at all, an observable source needs a subscriber. If nothing subscribes to an `IObservable`, then it doesn't really produce anything. `Concat` doesn't subscribe to its second input until the first completes, so arguably instead of the diagram above, it would be more accurate to show this: + +![A marble diagram showing three sequences. The first, labelled 'cold', produces the values 0, 1, and 2, then completes. The second, labelled 'hot' produces values C, D, and E, and the positioning shows that these are produced after the 'cold' sequence completes. The final sequence, labelled 'cold.Concat(hot)' shows all three values from 'cold' followed by all three values from 'hot', i.e., 0, 1, 2, C, D, and E. This entirely diagram looks almost the same as the preceding one, except the 'hot' sequence doesn't produce the A or B values, and starts directly after 'cold' finishes.](./GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Hot-Marbles-SubOnly.svg) + +This makes it easier to see why `Concat` produces the output it does. But since `hot` is a hot source here, this diagram fails to convey the fact that `hot` is producing items entirely on its own schedule. In a scenario where `hot` had multiple subscribers, then the earlier diagram would arguably be better because it correctly reflects every event available from `hot` (regardless of however many listeners might be subscribed at any particular moment). But although this convention works for hot sources, it doesn't work for cold ones, which typically start producing items upon subscription. A source returned by [`Timer`](03_CreatingObservableSequences.md#observabletimer) produces items on a regular schedule, but that schedule starts at the instant when subscription occurs. That means that if there are multiple subscriptions, there are multiple schedules. Even if I have just a single `IObservable` returned by `Observable.Timer`, each distinct subscriber will get items on its own schedule—subscribers receive events at a regular interval _starting from whenever they happened to subscribe_. So for cold observables, it typically makes sense to use the convention used by this second diagram, in which we're looking at the events received by one particular subscription to a source. + +Most of the time we can get away with ignoring this subtlety, quietly using whichever convention suits us. To paraphrase [Humpty Dumpty: when I use a marble diagram, it means just what I choose it to mean—neither more nor less](https://www.goodreads.com/quotes/12608-when-i-use-a-word-humpty-dumpty-said-in-rather). But when you're combining hot and cold sources together, there might not be one obviously best way to represent this in a marble diagram. We could even do something like this, where we describe the events that `hot` represents separately from the events seen by a particular subscription to `hot`. + +![This essentially combines the preceding two diagrams. It has the same first and last sequences. In between these it has a sequence labelled 'Events available from hot' which shows the same as the 'hot' sequence in the diagram before last. It then has a sequence labelled 'Concat subscription to hot' which shows the same as the 'hot' sequence from the preceding diagram.](./GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Hot-Marbles-SourceAndSub.svg) + + +We're using a distinct 'lane' in the marble diagram to represent the events seen by a particular subscription to a source. With this technique, we can also show what would happen if you pass the same cold source into `Concat` twice: + +![A marble diagram showing three sequences. The first, labelled 'Concat subscription to cold', produces the values 0, 1, and 2, then completes. The second, also labelled 'Concat subscription to cold' produces the values again, but positioned to shows that these are produced after the first sequence completes. The final sequence, labelled 'cold.Concat(cold)' shows the values twice, i.e., 0, 1, 2, 0, 1, and 2.](./GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Marbles-Cold-Twice.svg) + +This highlights the fact that that being a cold source, `cold` provides items separately to each subscription. We see the same three values emerging from the same source, but at different times. + +#### Concatenating Multiple Sources + +What if you wanted to concatenate more than two sequences? `Concat` has an overload accepting multiple observable sequences as an array. This is annotated with the `params` keyword, so you don't need to construct the array explicitly. You can just pass any number of arguments, and the C# compiler will generate the code to create the array for you. There's also an overload taking an `IEnumerable>`, in case the observables you want to concatenate are already in some collection. + +```cs +public static IObservable Concat( + params IObservable[] sources) + +public static IObservable Concat( + this IEnumerable> sources) +``` + +The `IEnumerable>` overload evaluates `sources` lazily. It won't begin to ask it for source observables until someone subscribes to the observable that `Concat` returns, and it only calls `MoveNext` again on the resulting `IEnumerator>` when the current source completes meaning it's ready to start on the text. To illustrate this, the following example is an iterator method that returns a sequence of sequences and is sprinkled with logging. It returns three observable sequences each with a single value [1], [2] and [3]. Each sequence returns its value on a timer delay. + +```cs +public IEnumerable> GetSequences() +{ + Console.WriteLine("GetSequences() called"); + Console.WriteLine("Yield 1st sequence"); + + yield return Observable.Create(o => + { + Console.WriteLine("1st subscribed to"); + return Observable.Timer(TimeSpan.FromMilliseconds(500)) + .Select(i => 1L) + .Finally(() => Console.WriteLine("1st finished")) + .Subscribe(o); + }); + + Console.WriteLine("Yield 2nd sequence"); + + yield return Observable.Create(o => + { + Console.WriteLine("2nd subscribed to"); + return Observable.Timer(TimeSpan.FromMilliseconds(300)) + .Select(i => 2L) + .Finally(() => Console.WriteLine("2nd finished")) + .Subscribe(o); + }); + + Thread.Sleep(1000); // Force a delay + + Console.WriteLine("Yield 3rd sequence"); + + yield return Observable.Create(o => + { + Console.WriteLine("3rd subscribed to"); + return Observable.Timer(TimeSpan.FromMilliseconds(100)) + .Select(i=>3L) + .Finally(() => Console.WriteLine("3rd finished")) + .Subscribe(o); + }); + + Console.WriteLine("GetSequences() complete"); +} +``` + +We can call this `GetSequences` method and pass the results to `Concat`, and then use our `Dump` extension method to watch what happens: + +```cs +GetSequences().Concat().Dump("Concat"); +``` + +Here's the output: + +``` +GetSequences() called +Yield 1st sequence +1st subscribed to +Concat-->1 +1st finished +Yield 2nd sequence +2nd subscribed to +Concat-->2 +2nd finished +Yield 3rd sequence +3rd subscribed to +Concat-->3 +3rd finished +GetSequences() complete +Concat completed +``` + +Below is a marble diagram of the `Concat` operator applied to the `GetSequences` method. 's1', 's2' and 's3' represent sequence 1, 2 and 3. Respectively, 'rs' represents the result sequence. + +![A marble diagram showing 4 sequences .The first, s1, waits for a while, then produces a single value, 1, then immediate completes. The second, s2, starts immediately after s1 completes. It waits for a slightly shorter interval, then produces the value 2, then immediately completes. The third, s3, starts some time after s2 completes, waits an even shorter time, produces the value 3, and then immediately completes. The final sequence, r, starts at the same time as s1, produces the values 1, 2, and 3 at exactly the same time as these values are produces by the earlier sources, and completes at the same time as s3.](./GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Marbles-Three.svg) + +You should note that once the iterator has executed its first `yield return` to return the first sequence, the iterator does not continue until the first sequence has completed. The iterator calls `Console.WriteLine` to display the text `Yield 2nd sequence` immediately after that first `yield return`, but you can see that message doesn't appear in the output until after we see the `Concat-->1` message showing the first output from `Concat`, and also the `1st finished` message, produced by the `Finally` operator, which runs only after that first sequence has completed. (The code also makes that first source delay for 500ms before producing its value, so that if you run this, you can see that everything stops for a bit until that first source produces its single value then completes.) Once the first source completes, the `GetSequences` method continues (because `Concat` will ask it for the next item once the first observable source completes). When `GetSequences` provides the second sequence with another `yield return`, `Concat` subscribes to that, and again `GetSequences` makes no further progress until that second observable sequence completes. When asked for the third sequence, the iterator itself waits for a second before producing that third and final value, which you can see from the gap between the end of `s2` and the start of `s3` in the diagram. + +### Prepend + +There's one particular scenario that `Concat` supports, but in a slightly cumbersome way. It can sometimes be useful to make a sequence that always emits some initial value immediately. Take the example I've been using a lot in this book, where ships transmit AIS messages to report their location and other information: in some applications you might not want to wait until the ship happens next to transmit a message. You could imagine an application that records the last known location of any vessel. This would make it possible for the application to offer, say, an `IObservable` which instantly reports the last known information upon subscription, and which then goes on to supply any newer messages if the vessel produces any. + +How would we implement this? We want initially cold-source-like behaviour, but transitioning into hot. So we could just concatenate two sources. We could use [`Observable.Return`](03_CreatingObservableSequences.md#observablereturn) to create a single-element cold source, and then concatenate that with the live stream: + +```cs +IVesselNavigation lastKnown = ais.GetLastReportedNavigationForVessel(mmsi); +IObservable live = ais.GetNavigationMessagesForVessel(mmsi); + +IObservable lastKnownThenLive = Observable.Concat( + Observable.Return(lastKnown), live); +``` + +This is a common enough requirement that Rx supplies `Prepend` that has a similar effect. We can replace the final line with: + +```cs +IObservable lastKnownThenLive = live.Prepend(lastKnown); +``` + +This observable will do exactly the same thing: subscribers will immediately receive the `lastKnown`, and then if the vessel should emit further navigation messages, they will receive those too. By the way, for this scenario you'd probably also want to ensure that the look up of the "last known" message happens as late as possible. We can delay this until the point of subscription by using [`Defer`](03_CreatingObservableSequences.md#observabledefer): + +```cs +public static IObservable GetLastKnownAndSubsequenceNavigationForVessel(uint mmsi) +{ + return Observable.Defer(() => + { + // This lambda will run each time someone subscribes. + IVesselNavigation lastKnown = ais.GetLastReportedNavigationForVessel(mmsi); + IObservable live = ais.GetNavigationMessagesForVessel(mmsi); + + return live.Prepend(lastKnown); + } +} +``` + +`StartWith` might remind you of [`BehaviorSubject`](03_CreatingObservableSequences.md#behaviorsubject), because that also ensures that consumers receive a value as soon as they subscribe. It's not quite the same: `BehaviorSubject` caches the last value its own source emits. You might think that would make it a better way to implement this vessel navigation example. However, since this example is able to return a source for any vessel (the `mmsi` argument is a [Maritime Mobile Service Identity](https://en.wikipedia.org/wiki/Maritime_Mobile_Service_Identity) uniquely identifying a vessel) it would need to keep a `BehaviorSubject` running for every single vessel you were interested in, which might be impractical. + +`BehaviorSubject` can hold onto only one value, which is fine for this AIS scenario, and `Prepend` shares this limitation. But what if you need a source to begin with some particular sequence? + +### StartWith + +`StartWith` is a generalization of `Prepend` that enables us to provide any number of values to emit immediately upon subscription. As with `Prepend`, it will then go on to forward any further notifications that emerge from the source. + +As you can see from its signature, this method takes a `params` array of values so you can pass in as many or as few values as you need: + +```cs +// prefixes a sequence of values to an observable sequence. +public static IObservable StartWith( + this IObservable source, + params TSource[] values) +``` + +There's also an overload that accepts an `IEnumerable`. Note that Rx will _not_ defer its enumeration of this. `StartWith` immediately converts the `IEnumerable` into an array before returning. + +`StartsWith` is not a common LINQ operator, and its existence is peculiar to Rx. If you imagine what `StartsWith` would look like in LINQ to Objects, it would not be meaningfully different from [`Concat`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.concat). There's a difference in Rx because `StartsWith` effectively bridges between _pull_ and _push_ worlds. It effectively converts the items we supply into an observable, and it then concatenates the `source` argument onto that. + +### Append + +The existence of `Prepend` might lead you to wonder whether there is an `Append` for adding a single item onto the end of any `IObservable`. After all, this is a common LINQ operator; [LINQ to Objects has an `Append` implementation](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.append), for example. And Rx does indeed supply such a thing: + +```cs +IObservable oneMore = arguments.Append("And another thing..."); +``` + +There is no corresponding `EndWith`. There's no fundamental reason that there couldn't be such a thing it's just that apparently there's not much demand—the [Rx repository](https://github.com/dotnet/reactive) has not yet had a feature request. So although the symmetry of `Prepend` and `Append` does suggest that there could be a similar symmetry between `StartWith` and an as-yet-hypothetical `EndWith`, the absence of this counterpart doesn't seem to have caused any problems. There's an obvious value to being able to create observable sources that always immediately produce a useful output; it's not clear what `EndWith` would be useful for, besides satisfying a craving for symmetry. + +### DefaultIfEmpty + +The next operator we'll examine doesn't strictly performs sequential combination. However, it's a very close relative of `Append` and `Prepend`. Like those operators, this will emit everything their source does. And like those operators, `DefaultIfEmpty` takes one additional item. The difference is that it won't always emit that additional item. + +Whereas `Prepend` emits its additional item at the start, and `Append` emits its additional item at the end, `DefaultIfEmpty` emits the additional item only if the source completes without producing anything. So this provides a way of guaranteeing that an observable will not be empty. + +You don't have to supply `DefaultIfEmpty` with a value. If you use the overload in which you supply no such value, it will just use `default(T)`. This will be a zero-like value for _struct_ types and `null` for reference types. + +### Repeat + +The final operator that combines sequences sequentially is `Repeat`. It allows you to simply repeat a sequence. It offers overloads where you can specify the number of times to repeat the input, and one that repeats infinitely: + +```cs +// Repeats the observable sequence a specified number of times. +public static IObservable Repeat( + this IObservable source, + int repeatCount) + +// Repeats the observable sequence indefinitely and sequentially. +public static IObservable Repeat( + this IObservable source) +``` + +`Repeat` resubscribes to the source for each repetition. This means that this will only strictly repeat if the source produces the same items each time you subscribe. Unlike the [`ReplaySubject`](03_CreatingObservableSequences.md#replaysubject), this doesn't store and replay the items that emerge from the source. This means that you normally won't want to call `Repeat` on a hot source. (If you really want repetition of the output of a hot source, a combination of [`Replay`](15_PublishingOperators.md#replay) and `Repeat` might fit the bill.) + +If you use the overload that repeats indefinitely, then the only way the sequence will stop is if there is an error or the subscription is disposed of. The overload that specifies a repeat count will stop on error, un-subscription, or when it reaches that count. This example shows the sequence [0,1,2] being repeated three times. + +```csharp +var source = Observable.Range(0, 3); +var result = source.Repeat(3); + +result.Subscribe( + Console.WriteLine, + () => Console.WriteLine("Completed")); +``` + +Output: + +``` +0 +1 +2 +0 +1 +2 +0 +1 +2 +Completed +``` + +## Concurrent sequences + +We'll now move on to operators for combining observable sequences that might produce values concurrently. + +### Amb + +`Amb` is a strangely named operator. It's short for _ambiguous_, but that doesn't tell us much more than `Amb`. If you're curious about the name you can read about the [origins of `Amb` in Appendix C](C_AlgebraicUnderpinnings#amb), but for now, let's look at what it actually does. +Rx's `Amb` takes any number of `IObservable` sources as inputs, and waits to see which, if any, first produces some sort of output. As soon as this happens, it immediately unsubscribes from all of the other sources, and forwards all notifications from the source that reacted first. + +Why is that useful? + +A common use case for `Amb` is when you want to produce some sort of result as quickly as possible, and you have multiple options for obtaining that result, but you don't know in advance which will be fastest. Perhaps there are multiple servers that could all potentially give you the answer you want, and it's impossible to predict which will have the lowest response time. You could send requests to all of them, and then just use the first to respond. If you model each individual request as its own `IObservable`, `Amb` can handle this for you. Note that this isn't very efficient: you're asking several servers all to do the same work, and you're going to discard the results from most of them. (Since `Amb` unsubscribes from all the sources it's not going to use as soon as the first reacts, it's possible that you might be able to send a message to all the other servers to cancel the request. But this is still somewhat wasteful.) But there may be scenarios in which timeliness is crucial, and for those cases it might be worth tolerating a bit of wasted effort to produce faster results. + +`Amb` is broadly similar to `Task.WhenAny`, in that it lets you detect when the first of multiple sources does something. However, the analogy is not precise. `Amb` automatically unsubscribes from all of the other sources, ensuring that everything is cleaned up. With `Task` you should always ensure that you eventually observe all tasks in case any of them faulted. + +To illustrate `Amb`'s behaviour, here's a marble diagram showing three sequences, `s1`, `s2`, and `s3`, each able to produce a sequence values. The line labelled `r` shows the result of passing all three sequences into `Amb`. As you can see, `r` provides exactly the same notifications as `s1`, because in this example, `s1` was the first sequence to produce a value. + +![A marble diagram showing 4 sequences. The first, s1, produces the values 1, 2, 3, and 4. The second, s2, starts at the same time as s1, but produces its first value, 99, after s1 produces 1, and produces its second value, 88, after s1 produces 2, and it then completes between s1 producing 2 and 3. The third source, s3, produces its first value, 8, after s2 produced 99, and before s1 produced 2, and it goes on to produce two more values, 7 and 6, interleaved with the activity from the earlier sources. The final sequence, r, is identical to s1.](GraphicsIntro/Ch09-CombiningSequences-Marbles-Amb-Marbles.svg) + +This code creates exactly the situation described in that marble diagram, to verify that this is indeed how `Amb` behaves: + +```cs +var s1 = new Subject(); +var s2 = new Subject(); +var s3 = new Subject(); + +var result = Observable.Amb(s1, s2, s3); + +result.Subscribe( + Console.WriteLine, + () => Console.WriteLine("Completed")); + +s1.OnNext(1); +s2.OnNext(99); +s3.OnNext(8); +s1.OnNext(2); +s2.OnNext(88); +s3.OnNext(7); +s2.OnCompleted(); +s1.OnNext(3); +s3.OnNext(6); +s1.OnNext(4); +s1.OnCompleted(); +s3.OnCompleted(); +``` + +Output: + +``` +1 +2 +3 +4 +Completed +``` + +If we changed the order so that `s2.OnNext(99)` came before the call to `s1.OnNext(1);` then s2 would produce values first and the marble diagram would look like this. + +![A marble diagram showing 4 sequences. The first, s1, produces the values 1, 2, 3, and 4. The second, s2, starts at the same time as s1, but produces its first value, 99, before s1 produces 1 (this being the key difference from the preceding diagram), and produces its second value, 88, after s1 produces 2, and it then completes between s1 producing 2 and 3. The third source, s3, produces its first value, 8, after s2 produced 99, and before s1 produced 2, and it goes on to produce two more values, 7 and 6, interleaved with the activity from the earlier sources. The final sequence, r, is identical to s2 (and not, as in the preceding diagram, s1).](GraphicsIntro/Ch09-CombiningSequences-Marbles-Amb-Marbles2.svg) + +There are a few overloads of `Amb`. The preceding example used the overload that takes a `params` array of sequences. There's also an overload that takes exactly two sources, avoiding the array allocation that occurs with `params`. Finally, you could pass in an `IEnumerable>`. (Note that there are no overloads that take an `IObservable>`. `Amb` requires all of the source observables it monitors to be supplied up front.) + +```csharp +// Propagates the observable sequence that reacts first. +public static IObservable Amb( + this IObservable first, + IObservable second) +{...} +public static IObservable Amb( + params IObservable[] sources) +{...} +public static IObservable Amb( + this IEnumerable> sources) +{...} +``` + +Reusing the `GetSequences` method from the `Concat` section, we see that `Amb` evaluates the outer (IEnumerable) sequence completely before subscribing to any of the sequences it returns. + +```csharp +GetSequences().Amb().Dump("Amb"); +``` + +Output: + +``` +GetSequences() called +Yield 1st sequence +Yield 2nd sequence +Yield 3rd sequence +GetSequences() complete +1st subscribed to +2nd subscribed to +3rd subscribed to +Amb-->3 +Amb completed +``` + +Here is the marble diagram illustrating how this code behaves: + +![A marble diagram showing four sequences. The first three all start at the same time, but significantly later than the fourth. The first, s1, waits for a while and then produces the value 1 and then completes. The second, s2, produces a value 2 before s1 produced its value, and immediately completes. The third, s3, produces its value, 3, before s2 produced 2, and then immediately completes. The final sequence, r, starts long before all the rest, and then produces 3 at the same time as s3 produced 3, and then immediately completes.](./GraphicsIntro/Ch09-CombiningSequences-Marbles-Amb-Marbles3.svg) + +Remember that `GetSequences` produces its first two observables as soon as it is asked for them, and then waits for 1 second before producing the third and final one. But unlike `Concat`, `Amb` won't subscribe to any of its sources until it has retrieved all of them from the iterator, which is why this marble diagram shows the subscriptions to all three sources starting after 1 second. (The first two sources were available earlier—`Amb` would have started enumerating the sources as soon as subscription occurred, but it waited until it had all three before subscribing, which is why they all appear over on the right.) The third sequence has the shortest delay between subscription and producing its value, so although it's the last observable returned, it is able to produce its value the fastest even though there are two sequences yielded one second before it (due to the `Thread.Sleep`). + + +### Merge + +The `Merge` extension method takes multiple sequences as its input. Any time any of those input sequences produces a value, the observable returned by `Merge` produces that same value. If the input sequences produce values at the same time on different threads, `Merge` handles this safely, ensuring that it delivers items one at a time. + +Since `Merge` returns a single observable sequence that includes all of the values from all of its input sequences, there's a sense in which it is similar to `Concat`. But whereas `Concat` waits until each input sequence completes before moving onto the next, `Merge` supports concurrently active sequences. As soon as you subscribe to the observable returned by `Merge`, it immediately subscribes to all of its inputs, forwarding everything any of them produces. This marble diagram shows two sequences, `s1` and `s2`, running concurrently and `r` shows the effect of combining these with `Merge`: the values from both source sequences emerge from the merged sequence. + +![A marble diagram showing three sequences. The first, s1, produces the value 1 three times in a row, with a gap between each value. The second, s2, produces the value 2 three times in a row, and it does so at the same interval as the values from s2, but starting slightly later. The third sequence, c, contains all the same values as s1 and s2 combined, and at the same time as they emerge from their respective source sequences. So c produces 1, 2, 1, 2, 1, 2.](GraphicsIntro/Ch09-CombiningSequences-Marbles-Merge-Marbles.svg) + +The result of a `Merge` will complete only once all input sequences complete. However, the `Merge` operator will error if any of the input sequences terminates erroneously (at which point it will unsubscribe from all its other inputs). + +If you read the [Creating Observables chapter](03_CreatingObservableSequences.md), you've already seen one example of `Merge`. I used it to combine the individual sequences representing the various events provided by a `FileSystemWatcher` into a single stream at the end of the ['Representing Filesystem Events in Rx'](03_CreatingObservableSequences.md#representing-filesystem-events-in-rx) section. As another example, let's look at AIS once again. There is no publicly available single global source that can provide all AIS messages across the entire globe as an `IObservable`. Any single source is likely to cover just one area, or maybe even just a single AIS receiver. With `Merge`, it's straightforward to combine these into a single source: + +```cs +IObservable station1 = aisStations.GetMessagesFromStation("AdurStation"); +IObservable station2 = aisStations.GetMessagesFromStation("EastbourneStation"); + +IObservable allMessages = station1.Merge(station2); +``` + +If you want to combine more than two sources, you have a few options: + +- Chain `Merge` operators together e.g. `s1.Merge(s2).Merge(s3)` +- Pass a `params` array of sequences to the `Observable.Merge` static method. e.g. `Observable.Merge(s1,s2,s3)` +- Apply the `Merge` operator to an `IEnumerable>`. +- Apply the `Merge` operator to an `IObservable>`. + +The overloads look like this: + +```csharp +/// Merges two observable sequences into a single observable sequence. +/// Returns a sequence that merges the elements of the given sequences. +public static IObservable Merge( + this IObservable first, + IObservable second) +{...} + +// Merges all the observable sequences into a single observable sequence. +// The observable sequence that merges the elements of the observable sequences. +public static IObservable Merge( + params IObservable[] sources) +{...} + +// Merges an enumerable sequence of observable sequences into a single observable sequence. +public static IObservable Merge( + this IEnumerable> sources) +{...} + +// Merges an observable sequence of observable sequences into an observable sequence. +// Merges all the elements of the inner sequences in to the output sequence. +public static IObservable Merge( + this IObservable> sources) +{...} +``` + +As the number of sources being merged goes up, the operators that take collections have an advantage over the first overload. (I.e., `s1.Merge(s2).Merge(s3)` performs slightly less well than `Observable.Merge(new[] { s1, s2, s3 })`, or the equivalent `Observable.Merge(s1, s2, s3)`.) However, for just three or four, the differences are small, so in practice you can choose between the first two overloads as a matter of your preferred style. (If you're merging 100 sources or more the differences are more pronounced, but by that stage, the you probably wouldn't want to use the chained call style anyway.) The third and fourth overloads allow to you merge sequences that can be evaluated lazily at run time. + +That last `Merge` overload that takes a sequence of sequences is particularly interesting, because it makes it possible for the set of sources being merged to grow over time. `Merge` will remain subscribed to `sources` for as long as your code remains subscribed to the `IObservable` that `Merge` returns. So if `sources` emits more and more `IObservable`s over time, these will all be included by `Merge`. + +That might sound familiar. The [`SelectMany` operator](06_Transformation.md#selectmany), which is able to flatten multiple observable sources back out into a single observable source. This is just another illustration of why I've described `SelectMany` as a fundamental operator in Rx: strictly speaking we don't need a lot of the operators that Rx gives us because we could build them using `SelectMany`. Here's a simple re-implementation of that last `Merge` overload using `SelectMany`: + +```cs +public static IObservable MyMerge(this IObservable> sources) => + sources.SelectMany(source => source); +``` + +As well as illustrating that we don't technically need Rx to provide that last `Merge` for us, it's also a good illustration of why it's helpful that it does. It's not immediately obvious what this does. Why are we passing a lambda that just returns its argument? Unless you've seen this before, it can take some thought to work out that `SelectMany` expects us to pass a callback that it invokes for each incoming item, but that our input items are already nested sequences, so we can just return each item directly, and `SelectMany` will then take that and merge everything it produces into its output stream. And even if you have internalized `SelectMany` so completely that you know right away that this will just flatten `sources`, you'd still probably find `Observable.Merge(sources)` a more direct expression of intent. (Also, since `Merge` is a more specialized operator, Rx is able to provide a very slightly more efficient implementation of it than the `SelectMany` version shown above.) + +If we again reuse the `GetSequences` method, we can see how the `Merge` operator works with a sequence of sequences. + +```csharp +GetSequences().Merge().Dump("Merge"); +``` + +Output: + +``` +GetSequences() called +Yield 1st sequence +1st subscribed to +Yield 2nd sequence +2nd subscribed to +Merge --> 2 +Merge --> 1 +Yield 3rd sequence +3rd subscribed to +GetSequences() complete +Merge --> 3 +Merge completed +``` + +As we can see from the marble diagram, s1 and s2 are yielded and subscribed to immediately. s3 is not yielded for one second and then is subscribed to. Once all input sequences have completed, the result sequence completes. + +![A marble diagram showing four sources. The first, s1, waits for a while and then produces the value 1 and immediately completes. The second, s2, starts at the same time as s1, but produces a single value, 2 and immediately completes before s1 produced its value. The third, s3, starts long after s1 and s3 have finished, waits a short while, produces the value 3, then immediately completes. The final source, r, starts when s1 and s2 start, completes when s3 completes, and produces the three values from each of the three other sources at the same as they do, so it shows 2, 1, then 3.](GraphicsIntro/Ch09-CombiningSequences-Marbles-Merge-Marbles-Multi.svg) + + +For each of the `Merge` overloads that accept variable numbers of sources (either via an array, an `IEnumerable>`, or an `IObservable>`) there's an additional overload adding a `maxconcurrent` parameter. For example: + +```cs +public static IObservable Merge(this IEnumerable> sources, int maxConcurrent) +``` + +This enables you to limit the number of sources that `Merge` accepts inputs from at any single time. If the number of sources available exceeds `maxConcurrent` (either because you passed in a collection with more sources, or because you used the `IObservable`-based overload and the source emitted more nested sources than `maxConcurrent`) `Merge` will wait for existing sources to complete before moving onto new ones. A `maxConcurrent` of 1 makes `Merge` behave in the same way as `Concat`. + +### Switch + +Rx's `Switch` operator takes an `IObservable>`, and produces notifications from the most recent nested observable. Each time its source produces a new nested `IObservable`, `Switch` unsubscribes from the previous nested source (unless this is the first source, in which case there won't be a previous one) and subscribes to the latest one. + +`Switch` can be used in a 'time to leave' type feature for a calendar application. In fact you can see the source code for a modified version of [how Bing provides (or at least provided; the implementation may have changed) notifications telling you that it's time to leave for an appointment](https://github.com/reaqtive/reaqtor/blob/c3ae17f93ae57f3fb75a53f76e60ae69299a509e/Reaqtor/Samples/Remoting/Reaqtor.Remoting.Samples/DomainFeeds.cs#L33-L76). Since that's derived from a real example, it's a little complex, so I'll describe just the essence here. + +The basic idea with a 'time to leave' notification is that we using map and route finding services to work out the expected journey time to get to wherever the appointment is, and to use the [`Timer` operator](03_CreatingObservableSequences.md#observabletimer) to create an `IObservable` that will produce a notification when it's time to leave. (Specifically this code produces an `IObservable` which reports the proposed route for the journey, and expected travel time.) However, there are two things that can change, rendering the initial predicted journey time useless. First, traffic conditions can change. When the user created their appointment, we have to guess the expected journey time based on how traffic normally flows at the time of day in question. However, if there turns out to be really bad traffic on the day, the estimate will need to be revised upwards, and we'll need to notify the user earlier. + +The other thing that can change is the user's location. This will also obviously affect the predicted journey time. + +To handle this, the system will need observable sources that can report changes in the user's location, and changes in traffic conditions affecting the proposed journey. Every time either of these reports a change, we will need to produce a new estimated journey time, and a new `IObservable` that will produce a notification when it's time to leave. + +Every time we revise our estimate, we want to abandon the previously created `IObservable`. (Otherwise, the user will receive a bewildering number of notifications telling them to leave, one for every time we recalculated the journey time.) We just want to use the latest one. And that's exactly what `Switch` does. + +You can see the [example for that scenario in the Reaqtor repo](https://github.com/reaqtive/reaqtor/blob/c3ae17f93ae57f3fb75a53f76e60ae69299a509e/Reaqtor/Samples/Remoting/Reaqtor.Remoting.Samples/DomainFeeds.cs#L33-L76). Here, I'm going to present a different, simpler scenario: live searches. As you type, the text is sent to a search service and the results are returned to you as an observable sequence. Most implementations have a slight delay before sending the request so that unnecessary work does not happen. Imagine I want to search for "Intro to Rx". I quickly type in "Into to" and realize I have missed the letter 'r'. I stop briefly and change the text to "Intro ". By now, two searches have been sent to the server. The first search will return results that I do not want. Furthermore, if I were to receive data for the first search merged together with results for the second search, it would be a very odd experience for the user. I really only want results corresponding to the latest search text. This scenario fits perfectly with the `Switch` method. + +In this example, there is an `IObservable` source that represents the search text—each new value the user types emerges from this source sequence. We also have a search function that produces a single search result for a given search term: + +```csharp +private IObservable SearchResults(string query) +{ + ... +} +``` + +This returns just a single value, but we model it as an `IObservable` partly to deal with the fact that it might take some time to perform the search, and also to be enable to use it with Rx. We can take our source of search terms, and then use `Select` to pass each new search value to this `SearchResults` function. This creates our resulting nested sequence, `IObservable>`. + +Suppose we were to then use `Merge` to process the results: + +```csharp +IObservable searchValues = ....; +IObservable> search = searchValues.Select(searchText => SearchResults(searchText)); + +var subscription = search + .Merge() + .Subscribe(Console.WriteLine); +``` + +If we were lucky and each search completed before the next element from `searchValues` was produced, the output would look sensible. However, it is much more likely, however that multiple searches will result in overlapped search results. This marble diagram shows what the `Merge` function could do in such a situation. + +![A marble diagram showing 6 sources. The first, searchValues, produces the values I, In, Int, and Intr, and is shown as continuing on beyond the time represented by the diagram. The second, 'results (I)', starts when `searchValues` produces its first value, I, and then a while later produces a single value, Self, before immediately completing. It is significant that this single value is produced after the searchValues source has already produced its second value, In. The third source is labelled 'results (In)'. It starts at the same time that searchValues produces its second value, In, and a while later produces a single value, Into, before immediately completing. It is significant that it produces its value after searchValues has already produced its third value, Int. The fourth source is labelled 'results (Int)'. It starts at the same time that searchValues produces its third value, Int, and a while later produces a single value, 42, before immediately completing. It is significant that it produces its value after searchValues has already produced its fourth value, Intr. The fifth source is labelled 'results (Intr)'. It starts at the same time that searchValues produces its fourth value, Intr, and a while later produces a single value, Start, before immediately completing. It is significant that it produces its value before the previous sequence produced its value. The final souce is labelled 'Merged results'. It starts at the same time that searchValues starts, and it contains each of the items produced by the 2nd, 3rd, 4th, and 5th sequences. It does not complete.](GraphicsIntro/Ch09-CombiningSequences-Marbles-Switch-Marbles-Bad-Merge.svg) + + +Note how the values from the search results are all mixed together. The fact that some search terms took longer to get a search result than others has also meant that they have come out in the wrong order. This is not what we want. If we use the `Switch` extension method we will get much better results. `Switch` will subscribe to the outer sequence and as each inner sequence is yielded it will subscribe to the new inner sequence and dispose of the subscription to the previous inner sequence. This will result in the following marble diagram: + + +![A marble diagram showing 6 sources. The first, searchValues, produces the values I, In, Int, and Intr, and is shown as continuing on beyond the time represented by the diagram. The second, 'results (I)', starts when `searchValues` produces its first value, I, and then a while later produces a single value, Self, before immediately completing. It is significant that this single value is produced after the searchValues source has already produced its second value, In. The third source is labelled 'results (In)'. It starts at the same time that searchValues produces its second value, In, and a while later produces a single value, Into, before immediately completing. It is significant that it produces its value after searchValues has already produced its third value, Int. The fourth source is labelled 'results (Int)'. It starts at the same time that searchValues produces its third value, Int, and a while later produces a single value, 42, before immediately completing. It is significant that it produces its value after searchValues has already produced its fourth value, Intr. The fifth source is labelled 'results (Intr)'. It starts at the same time that searchValues produces its fourth value, Intr, and a while later produces a single value, Start, before immediately completing. It is significant that it produces its value before the previous sequence produced its value. The final souce is labelled 'Merged results'. It starts at the same time that searchValues starts, and it reports just a single value, Start, at exactly the same time the `results (Intr)` source produces the same value. It does not complete.](GraphicsIntro/Ch09-CombiningSequences-Marbles-Switch-Marbles.svg) + + +Now, each time a new search term arrives, causing a new search to be kicked off, a corresponding new `IObservable` for that search's results appears, causing `Switch` to unsubscribe from the previous results. This means that any results that arrive too late (i.e., when the result is for a search term that is no longer the one in the search box) will be dropped. As it happens, in this particular example, this means that we only see the result for the final search term. All the intermediate values that we saw as the user was typing didn't hang around for long, because the user kept on pressing the next key before we'd received the previous value's results. Only at the end, when the user stopped typing for long enough that the search results came back before they became out of date, do we finally see a value from `Switch`. The net effect is that we've eliminated confusing results that are out of date. + +This is another diagram where the ambiguity of marble diagrams causes a slight issue. I've shown each of the single-value observables produced by each of the calls to `SearchResults`, but in practice `Switch` unsubscribes from all but the last of these before they've had a chance to produce a value. So this diagram is showing the values those sources could potentially produce, and not the values that they actually delivered as part of the subscription, because the subscriptions were cut short. + +## Pairing sequences + +The previous methods allowed us to flatten multiple sequences sharing a common type into a result sequence of the same type (with various strategies for deciding what to include and what to discard). The operators in this section still take multiple sequences as an input, but attempt to pair values from each sequence to produce a single value for the output sequence. In some cases, they also allow you to provide sequences of different types. + +### Zip + +`Zip` combines pairs of items from two sequences. So its first output is created by combining the first item from one input with the first item from the other. The second output combines the second item from each input. And so on. The name is meant to evoke a zipper on clothing or a bag, which brings the teeth on each half of the zipper together one pair at a time. + +Since `Zip` combines pairs of item in strict order, it will complete when the first of the sequences complete. If one of the sequence has reached its end, then even if the other continues to emit values, there will be nothing to pair any of these values with, so `Zip` just unsubscribes at this point, discards the unpairable values, and reports completion. + +If either of the sequences produces an error, the sequence returned by `Zip` will report that same error. + +If one of the source sequences publishes values faster than the other sequence, the rate of publishing will be dictated by the slower of the two sequences, because it can only emit an item when it has one from each source. + +Here's an example: + +```csharp +// Generate values 0,1,2 +var nums = Observable.Interval(TimeSpan.FromMilliseconds(250)) + .Take(3); + +// Generate values a,b,c,d,e,f +var chars = Observable.Interval(TimeSpan.FromMilliseconds(150)) + .Take(6) + .Select(i => Char.ConvertFromUtf32((int)i + 97)); + +// Zip values together +nums.Zip(chars, (lhs, rhs) => (lhs, rhs))) + .Dump("Zip"); + ``` + +The effect can be seen in this marble diagram below.: + +![A marble diagram showing three sequences. The first, s1 waits for a while and then produces the values 0, 1, and 2, with some time between each value and completes immediately after producing 2. The second source, s2, waits for slightly less time, producing the value a before s1 produce 0, and then it produces b and c between s1's 0 and 1, and then between s1's 1 and 2, it produces d. It produces e at roughly the same time as s1 produces 2 (and for the purposes of this example, it doesn't really matter whether those happen at exactly the same time, or before or after one another) and then goes on to produce f, then immediately completes. The third sequence, c, shows the value '0,a' at the same time s1 produces 0, then '1,b' when s1 produces 1, and '2,c` when s1 produces 2, and then immediately completes (at the same time s1 completes).](GraphicsIntro/Ch09-CombiningSequences-Marbles-Zip-Marbles.svg) + + +Here's the actual output of the code: + +``` +{ Left = 0, Right = a } +{ Left = 1, Right = b } +{ Left = 2, Right = c } +``` + +Note that the `nums` sequence only produced three values before completing, while the `chars` sequence produced six values. The result sequence produced three values, this was as many pairs is it could make. + +It is also worth noting that `Zip` has a second overload that takes an `IEnumerable` as the second input sequence. + +```csharp +// Merges an observable sequence and an enumerable sequence into one observable sequence +// containing the result of pair-wise combining the elements by using the selector function. +public static IObservable Zip( + this IObservable first, + IEnumerable second, + Func resultSelector) +{...} +``` + +This allows us to zip sequences from both `IEnumerable` and `IObservable` paradigms! + +### SequenceEqual + +There's another operator that processes pairs of items from two sources: `SequenceEqual`. But instead of producing an output for each pair of inputs, this compares each pair, and ultimately produces a single value indicating whether every pair of inputs was equal or not. + +In the case where the sources produce different values, `SequenceEqual` produces a single `false` value as soon as it detects this. But if the sources are equal, it can only report this when both have completed because until that happens, it doesn't yet know if there might a difference coming later. Here's an example illustrating its behaviour: + +```cs +var subject1 = new Subject(); + +subject1.Subscribe( + i => Console.WriteLine($"subject1.OnNext({i})"), + () => Console.WriteLine("subject1 completed")); + +var subject2 = new Subject(); + +subject2.Subscribe( + i => Console.WriteLine($"subject2.OnNext({i})"), + () => Console.WriteLine("subject2 completed")); + +var areEqual = subject1.SequenceEqual(subject2); + +areEqual.Subscribe( + i => Console.WriteLine($"areEqual.OnNext({i})"), + () => Console.WriteLine("areEqual completed")); + +subject1.OnNext(1); +subject1.OnNext(2); + +subject2.OnNext(1); +subject2.OnNext(2); +subject2.OnNext(3); + +subject1.OnNext(3); + +subject1.OnCompleted(); +subject2.OnCompleted(); +``` + +Output: + +``` +subject1.OnNext(1) +subject1.OnNext(2) +subject2.OnNext(1) +subject2.OnNext(2) +subject2.OnNext(3) +subject1.OnNext(3) +subject1 completed +subject2 completed +areEqual.OnNext(True) +areEqual completed +``` + +### CombineLatest + +The `CombineLatest` operator is similar to `Zip` in that it combines pairs of items from its sources. However, instead of pairing the first items, then the second, and so on, `CombineLatest` produces an output any time _either_ of its inputs produces a new value. For each new value to emerge from an input, `CombineLatest` uses that along with the most recently seen value from the other input. (To be precise, it doesn't produce anything until each input has produced at least one value, so if one input takes longer to get started than the other, there will be a period in which `CombineLatest` doesn't in fact produce an output each time one of its inputs does, because it's waiting for the other to produce its first value.) The signature is as follows. + +```csharp +// Composes two observable sequences into one observable sequence by using the selector +// function whenever one of the observable sequences produces an element. +public static IObservable CombineLatest( + this IObservable first, + IObservable second, + Func resultSelector) +{...} +``` + +The marble diagram below shows off usage of `CombineLatest` with one sequence that produces numbers (`s1`), and the other letters (`s2`). If the `resultSelector` function just joins the number and letter together as a pair, this would produce the result shown on the bottom line. I've colour coded each output to indicate which of the two sources caused it to emit that particular result, but as you can see, each output includes a value from each source. + +![A marble diagram showing three sequences. The first, s1, waits for a while then produces the values 1, 2, and 3, spaced out over time. The second, s2, starts at the same time as s1, and waits for less time, producing its first value, a, before s1 produces 1. Then after s1 has produced 2, s2 produces b and then c, both being produced before s1 produces 3. The third sequence, CombineLatest, shows '1,a' at the same time as s1 produces 1, then '2,a' when s1 produces 2, then '2,b' when s2 produces b, then '2,c' when s2 produces c, then '3,c' when s1 produces 3. All three sequences do not end within the time shown in the diagram.](GraphicsIntro/Ch09-CombiningSequences-Marbles-CombineLatest-Marbles.svg) + + +If we slowly walk through the above marble diagram, we first see that `s1` produces the letter 'a'. `s2` has not produced any value yet so there is nothing to pair, meaning that no value is produced for the result. Next, `s2` produces the number '1' so the result sequence can now produce a pair '1,a'. We then receive the number '2' from `s1`. The last letter is still 'a' so the next pair is '2,a'. The letter 'b' is then produced creating the pair '2,b', followed by 'c' giving '2,c'. Finally the number 3 is produced and we get the pair '3,c'. + +This is great in case you need to evaluate some combination of state which needs to be kept up-to-date when any single component of that state changes. A simple example would be a monitoring system. Each service is represented by a sequence that returns a Boolean indicating the availability of said service. The monitoring status is green if all services are available; we can achieve this by having the result selector perform a logical AND. +Here is an example. + +```csharp +IObservable webServerStatus = GetWebStatus(); +IObservable databaseStatus = GetDBStatus(); + +// Yields true when both systems are up. +var systemStatus = webServerStatus + .CombineLatest( + databaseStatus, + (webStatus, dbStatus) => webStatus && dbStatus); +``` + +You may have noticed that this method could produce a lot of duplicate values. For example, if the web server goes down the result sequence will yield '`false`'. If the database then goes down, another (unnecessary) '`false`' value will be yielded. This would be an appropriate time to use the `DistinctUntilChanged` extension method. The corrected code would look like the example below. + +```csharp +// Yields true when both systems are up, and only on change of status +var systemStatus = webServerStatus + .CombineLatest( + databaseStatus, + (webStatus, dbStatus) => webStatus && dbStatus) + .DistinctUntilChanged(); +``` + +## Join + +The `Join` operator allows you to logically join two sequences. Whereas the `Zip` operator would pair values from the two sequences based on their position within the sequence, the `Join` operator allows you join sequences based on when elements are emitted. + +Since the production of a value by an observable source is logically an instantaneous event, joins use a model of intersecting windows. Recall that with the [`Window`](08_Partitioning.md#window) operator, you can define the duration of each window using an observable sequence. The `Join` operator uses a similar concept: for each source, we can define a time window over which each element is considered to be 'current' and two elements from different sources will be joined if their time windows overlap. As the `Zip` operator, we also need to provide a selector function to produce the result item from each pair of values. Here's the `Join` operator: + +```csharp +public static IObservable Join +( + this IObservable left, + IObservable right, + Func> leftDurationSelector, + Func> rightDurationSelector, + Func resultSelector +) +``` + +This is a complex signature to try and understand in one go, so let's take it one parameter at a time. + +`IObservable left` is the first source sequence. `IObservable right` is the second source sequence. `Join` is looking to produce pairs of items, with each pair containing one element from `left` and one element from `right`. + +The `leftDurationSelector` argument enables us to define the time window for each item from `left`. A source item's time window begins when the source emits the item. To determine when the window for an item from `left` should close, `Join` will invoke the `leftDurationSelector`, passing in the value just produced by `left`. This selector must return an observable source. (It doesn't matter at all what the element type of this source is, because `Join` is only interested in _when_ it does things.) The item's time window ends as soon as the source returned for that item by `leftDurationSelector` either produces a value or completes. + +The `rightDurationSelector` argument defines the time window for each item from `right`. It works in exactly the same way as the `leftDurationSelector`. + +Initially, there are no current items. But as `left` and `right` produce items, these items' windows will start, so `Join` might have multiple items all with their windows currently open. Each time `left` produces a new item, `Join` looks to see if any items from `right` still have their windows open. If they do, `left` is now paired with each of them. (So a single item from one source might be joined with multiple items from the other source.) `Join` calls the `resultSelector` for each such pairing. Likewise, each time `right` produces an item, then if there are any currently open windows for items from `left`, that new item from `right` will be paired with each of these, and again, `resultSelector` will be called for each such pairing. + +The observable returned by `Join` produces the result of each call to `resultSelector`. + +Let us now imagine a scenario where the left sequence produces values twice as fast as the right sequence. Imagine that in addition we never close the left windows; we could do this by always returning `Observable.Never()` from the `leftDurationSelector` function. And imagine that we make the right windows close as soon as they possibly can, which we can achieve by making `rightDurationSelector` return `Observable.Empty()`. The following marble diagram illustrates this: + +![A marble diagram showing five groups of sequences. The first group, labelled left, contains a single sequence which immediately produces the value 0, then, at evenly space intervals, the values 1, 2, 3, 4, and 5. The second group, labelled 'left durations' shows a sequence for each of the values produced by left, each starting at exactly the moment left produces one of its value. None of these sequences produces any values or completes. The third group is labelled right. It waits until after left has produced its second value (1), and then produces A. Between left's 3 and 4, it produces B. After left's 5 it produces C. The next group is labelled 'right durations', and it shows three sequences, each starting at the time right produces one of its values, and each immediately ending—these are effectively instantaneously short sequences. The final group, Join, shows a single sequence. When right produces A, this immediately produces '0,A' and then '1,A'. When right produces B, it produces '0,B', '1,B', '2,B', and `3,B`. Then right produces C, the Join sequence produces '0,C', '1,C', '2,C', '3,C', '4,C', '5,C'. The diagram happens to show each set of value stacked vertically, but that's only because they are produced in such quick succession that we wouldn't otherwise be able to see them.](GraphicsIntro/Ch09-CombiningSequences-Marbles-Join-Marbles1.svg) + +Each time a left duration window intersects with a right duration window, we get an output. The right duration windows are all effectively of zero length, but this doesn't stop them from intersecting with the left duration windows, because those all never end. So the first item from right has a (zero-length) window that falls inside two of the windows for the `left` items, and so `Join` produces two results. I've stacked these vertically on the diagram to show that they happen at virtually the same time. Of course, the rules of `IObserver` mean that they can't actually happen at the same time: `Join` has to wait until the consumer's `OnNext` has finished processing `0,A` before it can go on to produce `1,A`. But it will produce all the pairs as quickly as possible any time a single event from one source overlaps with multiple windows for the other. + +If I also immediately closed the left window by returning `Observable.Empty`, or perhaps `Observable.Return(0)`, the windows would never overlap, so no pairs would ever get produced. (In theory if both left and right produce items at _exactly_ the same time, then perhaps we might get a pair, but since the timing of events is never absolutely precise, it would be a bad idea to design a system that depended on this.) + +What if I wanted to ensure that items from `right` only ever intersected with a single value from `left`? In that case, I'd need to ensure that the left durations did not overlap. One way to do that would be to have my `leftDurationSelector` always return the same sequence that I passed as the `left` sequence. This will result in `Join` making multiple subscriptions to the same source, and for some kinds of sources that might introduce unwanted side effects, but the [`Publish`](15_PublishingOperators.md#publish) and [`RefCount`](15_PublishingOperators.md#refcount) operators provide a way to deal with that, so this is in fact a reasonably strategy. If we do that, the results look more like this. + +![A marble diagram showing five groups of sequences. The first group, labelled left, contains a single sequence which immediately produces the value 0, then, at evenly space intervals, the values 1, 2, 3, 4, and 5. The second group, labelled 'left durations' shows a sequence for each of the values produced by left, each starting at exactly the moment left produces one of its value, and finishing at the same moment that the next one starts.The third group is labelled right. It waits until after left has produced its second value (1), and then produces A. Between left's 3 and 4, it produces B. After left's 5 it produces C. The next group is labelled 'right durations', and it shows three sequences, each starting at the time right produces one of its values, and each immediately ending—these are effectively instantaneously short sequences. The final group, Join, shows a single sequence. When right produces A, this immediately produces '1,A'. When right produces B, it produces `3,B`. Then right produces C, the Join sequence produces '5,C'.](GraphicsIntro/Ch09-CombiningSequences-Marbles-Join-Marbles2.svg) + + +The last example is very similar to [`CombineLatest`](12_CombiningSequences.html#CombineLatest), except that it is only producing a pair when the right sequence changes. We can easily make it work the same way by changing the right durations to work in the same way as the left durations. This code shows how (including the use of `Publish` and `RefCount` to ensure that we only get a single subscription to the underlying `left` and `right` sources despite providing then to `Join` many times over). + +```csharp +public static IObservable MyCombineLatest +( + IObservable left, + IObservable right, + Func resultSelector +) +{ + var refcountedLeft = left.Publish().RefCount(); + var refcountedRight = right.Publish().RefCount(); + + return Observable.Join( + refcountedLeft, + refcountedRight, + value => refcountedLeft, + value => refcountedRight, + resultSelector); +} +``` + +Obviously there's no need to write this—you can just use the built-in `CombineLatest`. (And that will be slightly more efficient because it has a specialized implementation.) But it shows that `Join` is a powerful operator. + +## GroupJoin + +When the `Join` operator pairs up values whose windows overlap, it will pass the scalar values left and right to the `resultSelector`. The `GroupJoin` operator is based on the same concept of overlapping windows, but its selector works slightly differently: `GroupJoin` still passes a single (scalar) value from the left source, but it passes an `IObservable` as the second argument. This argument represents all of the values from the right sequence that occur within the window for the particular left value for which it was invoked. + +So this lacks the symmetry of `Join`, because the left and right sources are handled differently. `GroupJoin` will call the `resultSelector` exactly once for each item produced by the `left` source. When a left value's window overlaps with the windows of multiple right values, `Group` would deal with that by calling the selector once for each such pairing, but `GroupJoin` deals with this by having the observable passed as the second argument to `resultSelector` emit each of the right items that overlap with that left item. (If a left item overlaps with nothing from the right, `resultSelector` will still be called with that item, it'll just be passed an `IObservable` that doesn't produce any items.) + +The `GroupJoin` signature is very similar to `Join`, but note the difference in the `resultSelector` parameter. + +```csharp +public static IObservable GroupJoin +( + this IObservable left, + IObservable right, + Func> leftDurationSelector, + Func> rightDurationSelector, + Func, TResult> resultSelector +) +``` + +If we went back to our first `Join` example where we had + +* the `left` producing values twice as fast as the right, +* the left never expiring +* the right immediately expiring + +This diagram shows those same inputs again, and also shows the observables `GroupJoin` would pass to the `resultSelector` for each of the items produced by `left`: + +![A marble diagram showing five groups of sequences. The first group, labelled left, contains a single sequence which immediately produces the value 0, then, at evenly space intervals, the values 1, 2, 3, 4, and 5. The second group, labelled 'left durations' shows a sequence for each of the values produced by left, each starting at exactly the moment left produces one of its value. None of these sequences produces any values or completes. The third group is labelled right. It waits until after left has produced its second value (1), and then produces A. Between left's 3 and 4, it produces B. After left's 5 it produces C. The next group is labelled 'right durations', and it shows three sequences, each starting at the time right produces one of its values, and each immediately ending—these are effectively instantaneously short sequences. The final group contains 6 sequences, each of which has a label of the form `Right observable passed to selector for 0`, with the digit at the end changing for each sequence (so "...for 1" then "...for 2" and so on). Each of these sequences in the final group starts at the same time as a corresponding value from the left sequence. The first two show A, B, and C at the same time that the right sequence produces these values. The next two start after right has produced A, so they show only B and C. The last two start after right produces B so they show only C.](GraphicsIntro/Ch09-CombiningSequences-Marbles-GroupJoin-Marbles.svg) + + +This produces events corresponding to all of the same events that `Join` produced, they're just distributed across six different `IObservable` sources. It may have occurred to you that with `GroupJoin` you could effectively re-create your own `Join` method by doing something like this: + +```csharp +public IObservable MyJoin( + IObservable left, + IObservable right, + Func> leftDurationSelector, + Func> rightDurationSelector, + Func resultSelector) +{ + return Observable.GroupJoin + ( + left, + right, + leftDurationSelector, + rightDurationSelector, + (leftValue, rightValues) => rightValues.Select(rightValue=>resultSelector(leftValue, rightValue)) + ) + .Merge(); +} +``` + +You could even create a crude version of `Window` with this code: + +```csharp +public IObservable> MyWindow(IObservable source, TimeSpan windowPeriod) +{ + return Observable.Create>(o =>; + { + var sharedSource = source + .Publish() + .RefCount(); + + var intervals = Observable.Return(0L) + .Concat(Observable.Interval(windowPeriod)) + .TakeUntil(sharedSource.TakeLast(1)) + .Publish() + .RefCount(); + + return intervals.GroupJoin( + sharedSource, + _ => intervals, + _ => Observable.Empty(), + (left, sourceValues) => sourceValues) + .Subscribe(o); + }); +} +``` + +Rx delivers yet another way to query data in motion by allowing you to interrogate sequences of coincidence. This enables you to solve the intrinsically complex problem of managing state and concurrency while performing matching from multiple sources. By encapsulating these low level operations, you are able to leverage Rx to design your software in an expressive and testable fashion. Using the Rx operators as building blocks, your code effectively becomes a composition of many simple operators. This allows the complexity of the domain code to be the focus, not the otherwise incidental supporting code. + + +### And-Then-When + +`Zip` can take only two sequences as an input. If that is a problem, then you can use a combination of the three `And`/`Then`/`When` methods. These methods are used slightly differently from most of the other Rx methods. Out of these three, `And` is the only extension method to `IObservable`. Unlike most Rx operators, it does not return a sequence; instead, it returns the mysterious type `Pattern`. The `Pattern` type is public (obviously), but all of its properties are internal. The only two (useful) things you can do with a `Pattern` are invoking its `And` or `Then` methods. The `And` method called on the `Pattern` returns a `Pattern`. On that type, you will also find the `And` and `Then` methods. The generic `Pattern` types are there to allow you to chain multiple `And` methods together, each one extending the generic type parameter list by one. You then bring them all together with the `Then` method overloads. The `Then` methods return you a `Plan` type. Finally, you pass this `Plan` to the `Observable.When` method in order to create your sequence. + +It may sound very complex, but comparing some code samples should make it easier to understand. It will also allow you to see which style you prefer to use. + +To `Zip` three sequences together, you can either use `Zip` methods chained together like this: + +```cs +IObservable one = Observable.Interval(TimeSpan.FromSeconds(1)).Take(5); +IObservable two = Observable.Interval(TimeSpan.FromMilliseconds(250)).Take(10); +IObservable three = Observable.Interval(TimeSpan.FromMilliseconds(150)).Take(14); + +// lhs represents 'Left Hand Side' +// rhs represents 'Right Hand Side' +IObservable<(long One, long Two, long Three)> zippedSequence = one + .Zip(two, (lhs, rhs) => (One: lhs, Two: rhs)) + .Zip(three, (lhs, rhs) => (lhs.One, lhs.Two, Three: rhs)); + +zippedSequence.Subscribe( + v => Console.WriteLine($"One: {v.One}, Two: {v.Two}, Three: {v.Three}"), + () => Console.WriteLine("Completed")); +``` + +Or perhaps use the nicer syntax of the `And`/`Then`/`When`: + +```cs +Pattern pattern = + one.And(two).And(three); +Plan<(long One, long Two, long Three)> plan = + pattern.Then((first, second, third) => (One: first, Two: second, Three: third)); +IObservable<(long One, long Two, long Three)> zippedSequence = Observable.When(plan); + +zippedSequence.Subscribe( + v => Console.WriteLine($"One: {v.One}, Two: {v.Two}, Three: {v.Three}"), + () => Console.WriteLine("Completed")); +``` + +This can be further reduced, if you prefer, to: + +```cs +IObservable<(long One, long Two, long Three)> zippedSequence = Observable.When( + one.And(two).And(three) + .Then((first, second, third) => + (One: first, Two: second, Three: third)) + ); + +zippedSequence.Subscribe( + v => Console.WriteLine($"One: {v.One}, Two: {v.Two}, Three: {v.Three}"), + () => Console.WriteLine("Completed")); +``` + +The `And`/`Then`/`When` trio has more overloads that enable you to group an even greater number of sequences. They also allow you to provide more than one 'plan' (the output of the `Then` method). This gives you the `Merge` feature but on the collection of 'plans'. I would suggest playing around with them if this functionality is of interest to you. The verbosity of enumerating all of the combinations of these methods would be of low value. You will get far more value out of using them and discovering for yourself. + +## Summary + +This chapter covered a set of methods that allow us to combine observable sequences. This brings us to a close on Part 2. We've looked at the operators that are mostly concerned with defining the computations we want to perform on the data. In Part 3 we will move onto practical concerns such as managing scheduling, side effects, and error handling. \ No newline at end of file diff --git a/content/09_SideEffects.md b/content/09_SideEffects.md deleted file mode 100644 index e939433..0000000 --- a/content/09_SideEffects.md +++ /dev/null @@ -1,450 +0,0 @@ ---- -title: Side effects ---- - -# PART 3 - Taming the sequence - -In the third part to this book we will look the features that allow us to apply Rx to more than just sample code. When building production quality code we often need to be able to handle error scenarios, log workflow, retry in certain circumstances, dispose of resources and other real-life problems that are regularly excluded from examples and demos. - -Part 3 of this book aims to equip you with the tools you need to be able to useRx as more than just a toy. If you use Rx properly, you will find it pervasive in your code base. You should not shy away from this, just like you would not shy away from using the `foreach` syntax with `IEnumerable` types, or, the `using` syntax with `IDisposable` types. Understanding and embracing Rx will improve your code base by reducing it, by making it more declarative, by identifying and eliminating race conditions, and therefore making it more maintainable. - -Maintenance of Rx code obviously requires Rx knowledge but this creates a "chicken and egg" problem. I choose to believe that Rx is here to stay. I believe this because it solves a targeted set of problems very well. It is also complimentary to other libraries and features such as TPL (Task Parallel Library) and the future `async`/`await` features of .NET 4.5. Considering this, if Rx improves our code base then we should embrace it! - -# Side effects - -Non-functional requirements of production systems often demand high availability, quality monitoring features and low lead time for defect resolution. Logging, debugging, instrumentation and journaling are common non-functional requirements that developers need to consider for production ready systems. These artifacts could be considered side effects of the main business workflow. Side effects are a real life problem that code samples and how-to guides often ignore, however Rx provides tools to help. - -In this chapter we will discuss the consequences of introducing side effects when working with an observable sequence. A function is considered to have a side effect if, in addition to any return value, it has some other observable effect. Generally the 'observable effect' is a modification of state. This observable effect could be - -* modification of a variable with a wider scope than the function (i.e. global, static or perhaps an argument) -* I/O such as a read/write from a file or network - -* updating a display - - -## Issues with side effects - -Functional programming in general tries to avoid creating any side effects. Functions with side effects, especially which modify state, require the programmer to understand more than just the inputs and outputs of the function. The surface area they are required to understand needs to now extend to the history and context of the state being modified. This can greatly increase the complexity of a function, and thus make it harder to correctly understand and maintain. - -Side effects are not always accidental, nor are they always intentional. An easy way to reduce the accidental side effects is to reduce the surface area for change. The simple actions coders can take are to reduce the visibility or scope of state and to make what you can immutable. You can reduce the visibility of a variable by scoping it to a code block like a method. You can reduce visibility of class members by making them private or protected. By definition immutable data can't be modified so cannot exhibit side effects. These are sensible encapsulation rules that will dramatically improve the maintainability of your Rx code. - -To provide a simple example of a query that has a side effect, we will try to output the index and value of the elements received by updating a variable (closure). - -```csharp -var letters = Observable.Range(0, 3) - .Select(i => (char)(i + 65)); - -var index = -1; -var result = letters.Select( - c => - { - index++; - return c; - }); - -result.Subscribe( - c => Console.WriteLine("Received {0} at index {1}", c, index), - () => Console.WriteLine("completed")); -``` - -Output: - -``` -Received A at index 0 -Received B at index 1 -Received C at index 2 -completed -``` - -While this seems harmless enough, imagine if another person sees this code and understands it to be the pattern the team is using. They in turn adopt this style themselves. For the sake of the example, we will add a duplicate subscription to our previous example. - -```csharp -var letters = Observable.Range(0, 3) - .Select(i => (char)(i + 65)); - -var index = -1; -var result = letters.Select( - c => - { - index++; - return c; - }); - -result.Subscribe( - c => Console.WriteLine("Received {0} at index {1}", c, index), - () => Console.WriteLine("completed")); - -result.Subscribe( - c => Console.WriteLine("Also received {0} at index {1}", c, index), - () => Console.WriteLine("2nd completed")); -``` - -Output - -``` -Received A at index 0 -Received B at index 1 -Received C at index 2 -completed -Also received A at index 3 -Also received B at index 4 -Also received C at index 5 -2nd completed -``` - - - -Now the second person's output is clearly nonsense. They will be expecting index values to be 0, 1 and 2 but get 3, 4 and 5 instead. I have seen far more sinister versions of side effects in code bases. The nasty ones often modify state that is a Boolean value e.g. `hasValues`, `isStreaming` etc. We will see in a later chapter far better ways of controlling workflow with observable sequences than using shared state. - -In addition to creating potentially unpredictable results in existing software, programs that exhibit side effects are far more difficult to test and maintain. Future refactoring, enhancements or other maintenance on programs that exhibits side effects are far more likely to be brittle. This is especially so in asynchronous or concurrent software. - -## Composing data in a pipeline - -The preferred way of capturing state is to introduce it to the pipeline. Ideally, we want each part of the pipeline to be independent and deterministic. That is, each function that makes up the pipeline should have its inputs and output as its only state. To correct our example we could enrich the data in the pipeline so that there is no shared state. This would be a great example where we could use the `Select` overload that exposes the index. - -```csharp -var source = Observable.Range(0, 3); -var result = source.Select((idx, value) => new - { - Index = idx, - Letter = (char) (value + 65) - }); - -result.Subscribe( - x => Console.WriteLine("Received {0} at index {1}", x.Letter, x.Index), - () => Console.WriteLine("completed")); - -result.Subscribe( - x => Console.WriteLine("Also received {0} at index {1}", x.Letter, x.Index), - () => Console.WriteLine("2nd completed")); -``` - -Output: - -``` -Received A at index 0 -Received B at index 1 -Received C at index 2 -completed -Also received A at index 0 -Also received B at index 1 -Also received C at index 2 -2nd completed -``` - -Thinking outside of the box, we could also use other features like `Scan` to achieve similar results. Here is an example. - -```csharp -var result = source.Scan( - new - { - Index = -1, - Letter = new char() - }, - (acc, value) => new - { - Index = acc.Index + 1, - Letter = (char)(value + 65) - }); -``` - -The key here is to isolate the state, and reduce or remove any side effects like mutating state. - -## Do - -We should aim to avoid side effects, but in some cases it is unavoidable. The `Do` extension method allows you to inject side effect behavior. The signature of the `Do` extension method looks very much like the `Select` method; - -- They both have various overloads to cater for combinations of `OnNext`, `OnError` and `OnCompleted` handlers -- They both return and take an observable sequence - -The overloads are as follows: - -```csharp -// Invokes an action with side effecting behavior for each element in the observable -// sequence. -public static IObservable Do( - this IObservable source, - Action onNext) -{...} - -// Invokes an action with side effecting behavior for each element in the observable -// sequence and invokes an action with side effecting behavior upon graceful termination -// of the observable sequence. -public static IObservable Do( - this IObservable source, - Action onNext, - Action onCompleted) -{...} - -// Invokes an action with side effecting behavior for each element in the observable -// sequence and invokes an action with side effecting behavior upon exceptional -// termination of the observable sequence. -public static IObservable Do( - this IObservable source, - Action onNext, - Action onError) -{...} - -// Invokes an action with side effecting behavior for each element in the observable -// sequence and invokes an action with side effecting behavior upon graceful or -// exceptional termination of the observable sequence. -public static IObservable Do( - this IObservable source, - Action onNext, - Action onError, - Action onCompleted) -{...} - -// Invokes the observer's methods for their side effects. -public static IObservable Do( - this IObservable source, - IObserver observer) -{...} -``` - -The `Select` overloads take `Func` arguments for their `OnNext` handlers and also provide the ability to return an observable sequence that is a different type to the source. In contrast, the `Do` methods only take an `Action` for the `OnNext` handler, and therefore can only return a sequence that is the same type as the source. As each of the arguments that can be passed to the `Do` overloads are actions, they implicitly cause side effects. - - - -For the next example, we first define the following methods for logging: - -```csharp -private static void Log(object onNextValue) -{ - Console.WriteLine("Logging OnNext({0}) @ {1}", onNextValue, DateTime.Now); -} -private static void Log(Exception onErrorValue) -{ - Console.WriteLine("Logging OnError({0}) @ {1}", onErrorValue, DateTime.Now); -} -private static void Log() -{ - Console.WriteLine("Logging OnCompleted()@ {0}", DateTime.Now); -} -``` - -This code can use `Do` to introduce some logging using the methods from above. - -```csharp -var source = Observable.Interval(TimeSpan.FromSeconds(1)) - .Take(3); - -var result = source.Do( - i => Log(i), - ex => Log(ex), - () => Log()); - -result.Subscribe( - Console.WriteLine, - () => Console.WriteLine("completed")); -``` - -Output: - -``` -Logging OnNext(0) @ 01/01/2012 12:00:00 -0 -Logging OnNext(1) @ 01/01/2012 12:00:01 -1 -Logging OnNext(2) @ 01/01/2012 12:00:02 -2 -Logging OnCompleted() @ 01/01/2012 12:00:02 -completed -``` - -Note that because the `Do` is earlier in the query chain than the `Subscribe`, it will receive the values first and therefore write to the console first. I like to think of the `Do` method as a [wire tap](http://en.wikipedia.org/wiki/Telephone_tapping) to a sequence. It gives you the ability to listen in on the sequence, without the ability to modify it. - -The most common acceptable side effect I see in Rx is the need to log. The signature of `Do` allows you to inject it into a query chain. This allows us to add logging into our sequence and retain encapsulation. When a repository, service agent or provider exposes an observable sequence, they have the ability to add their side effects (e.g. logging) to the sequence before exposing it publicly. Consumers can then append operators to the query (e.g. `Where`, `SelectMany`) and this will not affect the logging of the provider. - -Consider the method below. It produces numbers but also logs what it produces (to the console for simplicity). To the consuming code the logging is transparent. - -```csharp -private static IObservable GetNumbers() -{ - return Observable.Interval(TimeSpan.FromMilliseconds(250)) - .Do(i => Console.WriteLine("pushing {0} from GetNumbers", i)); -} -``` - -We then call it with this code. - -```csharp -var source = GetNumbers(); -var result = source.Where(i => i%3 == 0) - .Take(3) - .Select(i => (char) (i + 65)); - -result.Subscribe( - Console.WriteLine, - () => Console.WriteLine("completed")); -``` - -Output: - -``` -pushing 0 from GetNumbers -A -pushing 1 from GetNumbers -pushing 2 from GetNumbers -pushing 3 from GetNumbers -D -pushing 4 from GetNumbers -pushing 5 from GetNumbers -pushing 6 from GetNumbers -G -completed -``` - -This example shows how producers or intermediaries can apply logging to the sequence regardless of what the end consumer does. - -One overload to `Do` allows you to pass in an `IObserver`. In this overload, each of the `OnNext`, `OnError` and `OnCompleted` methods are passed to the other `Do` overload as each of the actions to perform. - -Applying a side effect adds complexity to a query. If side effects are a necessary evil, then being explicit will help your fellow coder understand your intentions. Using the `Do` method is the favored approach to doing so. This may seem trivial, but given the inherent complexity of a business domain mixed with asynchrony and concurrency, developers don't need the added complication of side effects hidden in a `Subscribe` or `Select` operator. - -## Encapsulating with AsObservable - -Poor encapsulation is a way developers can leave the door open for unintended side effects. Here is a handful of scenarios where carelessness leads to leaky abstractions. Our first example may seem harmless at a glance, but has numerous problems. - -```csharp -public class UltraLeakyLetterRepo -{ - public ReplaySubject Letters { get; set; } - - public UltraLeakyLetterRepo() - { - Letters = new ReplaySubject(); - Letters.OnNext("A"); - Letters.OnNext("B"); - Letters.OnNext("C"); - } -} -``` - -In this example we expose our observable sequence as a property. The first problem here is that it is a settable property. Consumers could change the entire subject out if they wanted. This would be a very poor experience for other consumers of this class. If we make some simple changes we can make a class that seems safe enough. - -```csharp -public class LeakyLetterRepo -{ - private readonly ReplaySubject _letters; - - public LeakyLetterRepo() - { - _letters = new ReplaySubject(); - _letters.OnNext("A"); - _letters.OnNext("B"); - _letters.OnNext("C"); - } - - public ReplaySubject Letters - { - get { return _letters; } - } -} -``` - -Now the `Letters` property only has a getter and is backed by a read-only field. This is much better. Keen readers will note that the `Letters` property returns a `ReplaySubject`. This is poor encapsulation, as consumers could call `OnNext`/`OnError`/`OnCompleted`. To close off that loophole we can simply make the return type an `IObservable`. - -```csharp -public IObservable Letters -{ - get { return _letters; } -} -``` - -The class now _looks_ much better. The improvement, however, is only cosmetic. There is still nothing preventing consumers from casting the result back to an `ISubject` and then calling whatever methods they like. In this example we see external code pushing their values into the sequence. - -```csharp -var repo = new ObscuredLeakinessLetterRepo(); -var good = repo.GetLetters(); -var evil = repo.GetLetters(); - -good.Subscribe(Console.WriteLine); - -// Be naughty -var asSubject = evil as ISubject; - -if (asSubject != null) -{ - // So naughty, 1 is not a letter! - asSubject.OnNext("1"); -} -else -{ - Console.WriteLine("could not sabotage"); -} -``` - -Output: - -``` -A -B -C -1 -``` - -The fix to this problem is quite simple. By applying the `AsObservable` extension method, the `_letters` field will be wrapped in a type that only implements `IObservable`. - -```csharp -public IObservable GetLetters() -{ - return _letters.AsObservable(); -} -``` - -Output: - -``` -A -B -C -could not sabotage -``` - -While I have used words like 'evil' and 'sabotage' in these examples, it is more often than not an oversight rather than malicious intent that causes problems. The failing falls first on the programmer who designed the leaky class. Designing interfaces is hard, but we should do our best to help consumers of our code fall into [the pit of success](http://blogs.msdn.com/b/brada/archive/2003/10/02/50420.aspx) by giving them discoverable and consistent types. Types become more discoverable if we reduce their surface area to expose only the features we intend our consumers to use. In this example we reduced the type's surface area. We did so by removing the property setter and returning a simpler type via the `AsObservable` method. - -## Mutable elements cannot be protected - -While the `AsObservable` method can encapsulate your sequence, you should still be aware that it gives no protection against mutable elements. Consider what consumers of a sequence of this class could do: - -```csharp -public class Account -{ - public int Id { get; set; } - public string Name { get; set; } -} -``` - -Here is a quick example of the kind of mess we can make if we choose to modify elements in a sequence. - -```csharp -var source = new Subject(); - -// Evil code. It modifies the Account object. -source.Subscribe(account => account.Name = "Garbage"); - -// unassuming well behaved code -source.Subscribe( - account=>Console.WriteLine("{0} {1}", account.Id, account.Name), - ()=>Console.WriteLine("completed")); - -source.OnNext(new Account {Id = 1, Name = "Microsoft"}); -source.OnNext(new Account {Id = 2, Name = "Google"}); -source.OnNext(new Account {Id = 3, Name = "IBM"}); -source.OnCompleted(); -``` - -Output: - -``` -1 Garbage -2 Garbage -3 Garbage -completed -``` - -Here the second consumer was expecting to get 'Microsoft', 'Google' and 'IBM' but received just 'Garbage'. - -Observable sequences will be perceived to be a sequence of resolved events: things that have happened as a statement of fact. This implies two things: first, each element represents a snapshot of state at the time of publication, secondly, the information emanates from a trustworthy source. We want to eliminate the possibility of tampering. Ideally the type `T` will be immutable, solving both of these problems. This way, consumers of the sequence can be assured that the data they get is the data that the source produced. Not being able to mutate elements may seem limiting as a consumer, but these needs are best met via the [Transformation](08_Transformation.html) operators which provide better encapsulation. - -Side effects should be avoided where possible. Any combination of concurrency with shared state will commonly demand the need for complex locking, deep understanding of CPU architectures and how they work with the locking and optimization features of the language you use. The simple and preferred approach is to avoid shared state, favor immutable data types and utilize query composition and transformation. Hiding side effects into `Where` or `Select` clauses can make for very confusing code. If a side effect is required, then the `Do` method expresses intent that you are creating a side effect by being explicit. \ No newline at end of file diff --git a/content/10_LeavingTheMonad.md b/content/10_LeavingTheMonad.md deleted file mode 100644 index 6026919..0000000 --- a/content/10_LeavingTheMonad.md +++ /dev/null @@ -1,511 +0,0 @@ ---- -title: Leaving the monad ---- - -# Leaving the monad - -An observable sequence is a useful construct, especially when we have the power of LINQ to compose complex queries over it. Even though we recognize the benefits of the observable sequence, sometimes it is required to leave the `IObservable` paradigm for another paradigm, maybe to enable you to integrate with an existing API (i.e. use events or `Task`). You might leave the observable paradigm if you find it easier for testing, or it may simply be easier for you to learn Rx by moving between an observable paradigm and a more familiar one. - -## What is a monad - -We have casually referred to the term _monad_ earlier in the book, but to most it will be a very foreign term. -I am going to try to avoid overcomplicating what a monad is, but give enough of an explanation to help us out with our next category of methods. -The full definition of a monad is quite abstract. -[Many others](http://www.haskell.org/haskellwiki/Monad_tutorials_timeline) have tried to provide their definition of a monad using all sorts of metaphors from astronauts to Alice in Wonderland. -Many of the tutorials for monadic programming use Haskell for the code examples which can add to the confusion. -For us, a monad is effectively a programming structure that represents computations. -Compare this to other programming structures: - -
-
Data structure
-
- Purely state e.g. a List, a Tree or a Tuple -
-
Contract
-
- Contract definition or abstract functionality e.g. an interface or abstract class -
-
Object-Orientated structure
-
- State and behavior together -
-
- -Generally a monadic structure allows you to chain together operators to produce a pipeline, just as we do with our extension methods. - -Monads are a kind of abstract data type constructor that encapsulate program logic instead of data in the domain model. - -This neat definition of a monad lifted from Wikipedia allows us to start viewing sequences as monads; the abstract data type in this case is the `IObservable` type. When we use an observable sequence, we compose functions onto the abstract data type (the `IObservable`) to create a query. This query becomes our encapsulated programming logic. - -The use of monads to define control flows is particularly useful when dealing with typically troublesome areas of programming such as IO, concurrency and exceptions. This just happens to be some of Rx's strong points! - -## Why leave the monad? - -There is a variety of reasons you may want to consume an observable sequence in a different paradigm. Libraries that need to expose functionality externally may be required to present it as events or as `Task` instances. In demonstration and sample code you may prefer to use blocking methods to limit the number of asynchronous moving parts. This may help make the learning curve to Rx a little less steep! - -In production code, it is rarely advised to 'break the monad', especially moving from an observable sequence to blocking methods. Switching between asynchronous and synchronous paradigms should be done with caution, as this is a common root cause for concurrency problems such as deadlock and scalability issues. - -In this chapter, we will look at the methods in Rx which allow you to leave the `IObservable` monad. - -## ForEach - -The `ForEach` method provides a way to process elements as they are received. The key difference between `ForEach` and `Subscribe` is that `ForEach` will block the current thread until the sequence completes. - -```csharp -var source = Observable.Interval(TimeSpan.FromSeconds(1)) - .Take(5); -source.ForEach(i => Console.WriteLine("received {0} @ {1}", i, DateTime.Now)); -Console.WriteLine("completed @ {0}", DateTime.Now); -``` - -Output: - -``` -received 0 @ 01/01/2012 12:00:01 a.m. -received 1 @ 01/01/2012 12:00:02 a.m. -received 2 @ 01/01/2012 12:00:03 a.m. -received 3 @ 01/01/2012 12:00:04 a.m. -received 4 @ 01/01/2012 12:00:05 a.m. -completed @ 01/01/2012 12:00:05 a.m. -``` - -Note that the completed line is last, as you would expect. To be clear, you can get similar functionality from the `Subscribe` extension method, but the `Subscribe` method will not block. So if we substitute the call to `ForEach` with a call to `Subscribe`, we will see the completed line happen first. - -```csharp -var source = Observable.Interval(TimeSpan.FromSeconds(1)) - .Take(5); -source.Subscribe(i => Console.WriteLine("received {0} @ {1}", i, DateTime.Now)); -Console.WriteLine("completed @ {0}", DateTime.Now); -``` - -Output: - -``` -completed @ 01/01/2012 12:00:00 a.m. -received 0 @ 01/01/2012 12:00:01 a.m. -received 1 @ 01/01/2012 12:00:02 a.m. -received 2 @ 01/01/2012 12:00:03 a.m. -received 3 @ 01/01/2012 12:00:04 a.m. -received 4 @ 01/01/2012 12:00:05 a.m. -``` - -Unlike the `Subscribe` extension method, `ForEach` has only the one overload; the one that take an `Action` as its single argument. In contrast, previous (pre-release) versions of Rx, the `ForEach` method had most of the same overloads as `Subscribe`. Those overloads of `ForEach` have been deprecated, and I think rightly so. There is no need to have an `OnCompleted` handler in a synchronous call, it is unnecessary. You can just place the call immediately after the `ForEach` call as we have done above. Also, the `OnError` handler can now be replaced with standard Structured Exception Handling like you would use for any other synchronous code, with a `try`/`catch` block. This also gives symmetry to the `ForEach` instance method on the `List` type. - -```csharp -var source = Observable.Throw(new Exception("Fail")); - -try -{ - source.ForEach(Console.WriteLine); -} -catch (Exception ex) -{ - Console.WriteLine("error @ {0} with {1}", DateTime.Now, ex.Message); -} -finally -{ - Console.WriteLine("completed @ {0}", DateTime.Now); -} -``` - -Output: - -``` -error @ 01/01/2012 12:00:00 a.m. with Fail -completed @ 01/01/2012 12:00:00 a.m. -``` - -The `ForEach` method, like its other blocking friends (`First` and `Last` etc.), should be used with care. -I would leave the `ForEach` method for spikes, tests and demo code only. -We will discuss the problems with introducing blocking calls when we look at concurrency. - - - - -## ToEnumerable - -An alternative way to switch out of the `IObservable` is to call the `ToEnumerable` extension method. -As a simple example: - -```csharp -var period = TimeSpan.FromMilliseconds(200); -var source = Observable.Timer(TimeSpan.Zero, period) - .Take(5); - -var result = source.ToEnumerable(); - -foreach (var value in result) -{ - Console.WriteLine(value); -} - -Console.WriteLine("done"); -``` - -Output: - -``` -0 -1 -2 -3 -4 -done -``` - -The source observable sequence will be subscribed to when you start to enumerate the sequence (i.e. lazily). -In contrast to the `ForEach` extension method, using the `ToEnumerable` method means you are only blocked when you try to move to the next element and it is not available. -Also, if the sequence produces values faster than you consume them, they will be cached for you. - -To cater for errors, you can wrap your `foreach` loop in a `try`/`catch` as you do with any other enumerable sequence: - -```csharp -try -{ - foreach (var value in result) - { - Console.WriteLine(value); - } -} -catch (Exception e) -{ - Console.WriteLine(e.Message); -} -``` - -As you are moving from a push to a pull model (non-blocking to blocking), the standard warning applies. - -## To a single collection - -To avoid having to oscillate between push and pull, you can use one of the next four methods to get the entire list back in a single notification. -They all have the same semantics, but just produce the data in a different format. -They are similar to their corresponding `IEnumerable` operators, but the return values differ in order to retain asynchronous behavior. - -### ToArray and ToList - -Both `ToArray` and `ToList` take an observable sequence and package it into an array or an instance of `List` respectively. -Once the observable sequence completes, the array or list will be pushed as the single value of the result sequence. - -```csharp -var period = TimeSpan.FromMilliseconds(200); -var source = Observable.Timer(TimeSpan.Zero, period).Take(5); -var result = source.ToArray(); - -result.Subscribe( - arr => { - Console.WriteLine("Received array"); - foreach (var value in arr) - { - Console.WriteLine(value); - } - }, - () => Console.WriteLine("Completed") -); - -Console.WriteLine("Subscribed"); -``` - -Output: - -``` -Subscribed -Received array -0 -1 -2 -3 -4 -Completed -``` - -As these methods still return observable sequences we can use our `OnError` handler for errors. Note that the source sequence is packaged to a single notification; you either get the whole sequence *or* the error. If the source produces values and then errors, you will not receive any of those values. All four operators (`ToArray`, `ToList`, `ToDictionary` and `ToLookup`) handle errors like this. - -### ToDictionary and ToLookup - -As an alternative to arrays and lists, Rx can package an observable sequence into a dictionary or lookup with the `ToDictionary` and `ToLookup` methods. Both methods have the same semantics as the `ToArray` and `ToList` methods, as they return a sequence with a single value and have the same error handling features. - -The `ToDictionary` extension method overloads: - -```csharp -// Creates a dictionary from an observable sequence according to a specified key selector -// function, a comparer, and an element selector function. -public static IObservable> ToDictionary( - this IObservable source, - Func keySelector, - Func elementSelector, - IEqualityComparer comparer) -{...} - -// Creates a dictionary from an observable sequence according to a specified key selector -// function, and an element selector function. -public static IObservable> ToDictionary( - this IObservable source, - Func keySelector, - Func elementSelector) -{...} - -// Creates a dictionary from an observable sequence according to a specified key selector -// function, and a comparer. -public static IObservable> ToDictionary( - this IObservable source, - Func keySelector, - IEqualityComparer comparer) -{...} - -// Creates a dictionary from an observable sequence according to a specified key selector -// function. -public static IObservable> ToDictionary( - this IObservable source, - Func keySelector) -{...} -``` - -The `ToLookup` extension method overloads: - -```csharp -// Creates a lookup from an observable sequence according to a specified key selector -// function, a comparer, and an element selector function. -public static IObservable> ToLookup( - this IObservable source, - Func keySelector, - Func elementSelector, - IEqualityComparer comparer) -{...} - -// Creates a lookup from an observable sequence according to a specified key selector -// function, and a comparer. -public static IObservable> ToLookup( - this IObservable source, - Func keySelector, - IEqualityComparer comparer) -{...} - -// Creates a lookup from an observable sequence according to a specified key selector -// function, and an element selector function. -public static IObservable> ToLookup( - this IObservable source, - Func keySelector, - Func elementSelector) -{...} - -// Creates a lookup from an observable sequence according to a specified key selector -// function. -public static IObservable> ToLookup( - this IObservable source, - Func keySelector) -{...} -``` - -Both `ToDictionary` and `ToLookup` require a function that can be applied each value to get its key. In addition, the `ToDictionary` method overloads mandate that all keys should be unique. If a duplicate key is found, it terminate the sequence with a `DuplicateKeyException`. On the other hand, the `ILookup` is designed to have multiple values grouped by the key. If you have many values per key, then `ToLookup` is probably the better option. - -## ToTask - -We have compared `AsyncSubject` to `Task` and even showed how to [transition from a task](04_CreatingObservableSequences.html#FromTask) to an observable sequence. The `ToTask` extension method will allow you to convert an observable sequence into a `Task`. Like an `AsyncSubject`, this method will ignore multiple values, only returning the last value. - -```csharp -// Returns a task that contains the last value of the observable sequence. -public static Task ToTask( - this IObservable observable) -{...} - -// Returns a task that contains the last value of the observable sequence, with state to -// use as the underlying task's AsyncState. -public static Task ToTask( - this IObservable observable, - object state) -{...} - -// Returns a task that contains the last value of the observable sequence. Requires a -// cancellation token that can be used to cancel the task, causing unsubscription from -// the observable sequence. -public static Task ToTask( - this IObservable observable, - CancellationToken cancellationToken) -{...} - -// Returns a task that contains the last value of the observable sequence, with state to -// use as the underlying task's AsyncState. Requires a cancellation token that can be used -// to cancel the task, causing unsubscription from the observable sequence. -public static Task ToTask( - this IObservable observable, - CancellationToken cancellationToken, - object state) -{...} -``` - -This is a simple example of how the `ToTask` operator can be used. -Note, the `ToTask` method is in the `System.Reactive.Threading.Tasks` namespace. - -```csharp -var source = Observable.Interval(TimeSpan.FromSeconds(1)) - .Take(5); -var result = source.ToTask(); //Will arrive in 5 seconds. -Console.WriteLine(result.Result); -``` - -Output: - -``` -4 -``` - -If the source sequence was to manifest error then the task would follow the error-handling semantics of tasks. - -```csharp -var source = Observable.Throw(new Exception("Fail!")); -var result = source.ToTask(); - -try -{ - Console.WriteLine(result.Result); -} -catch (AggregateException e) -{ - Console.WriteLine(e.InnerException.Message); -} -``` - -Output: - -``` -Fail! -``` - -Once you have your task, you can of course engage in all the features of the TPL such as continuations. - -## ToEvent<T> - -Just as you can use an event as the source for an observable sequence with [`FromEventPattern`](04_CreatingObservableSequences.html#FromEvent), you can also make your observable sequence look like a standard .NET event with the `ToEvent` extension methods. - -```csharp -// Exposes an observable sequence as an object with a .NET event. -public static IEventSource ToEvent(this IObservable source) -{...} - -// Exposes an observable sequence as an object with a .NET event. -public static IEventSource ToEvent( - this IObservable source) -{...} - -// Exposes an observable sequence as an object with a .NET event. -public static IEventPatternSource ToEventPattern( - this IObservable> source) - where TEventArgs : EventArgs -{...} -``` - -The `ToEvent` method returns an `IEventSource`, which will have a single event member on it: `OnNext`. - -```csharp -public interface IEventSource -{ - event Action OnNext; -} -``` - -When we convert the observable sequence with the `ToEvent` method, we can just subscribe by providing an `Action`, which we do here with a lambda. - -```csharp -var source = Observable.Interval(TimeSpan.FromSeconds(1)) - .Take(5); -var result = source.ToEvent(); -result.OnNext += val => Console.WriteLine(val); -``` - -Output: - -``` -0 -1 -2 -3 -4 -``` - -### ToEventPattern - -Note that this does not follow the standard pattern of events. Normally, when you subscribe to an event, you need to handle the `sender` and `EventArgs` parameters. In the example above, we just get the value. If you want to expose your sequence as an event that follows the standard pattern, you will need to use `ToEventPattern`. - -The `ToEventPattern` will take an `IObservable>` and convert that into an `IEventPatternSource`. The public interface for these types is quite simple. - -```csharp -public class EventPattern : IEquatable> - where TEventArgs : EventArgs -{ - public EventPattern(object sender, TEventArgs e) - { - this.Sender = sender; - this.EventArgs = e; - } - public object Sender { get; private set; } - public TEventArgs EventArgs { get; private set; } - //...equality overloads -} - -public interface IEventPatternSource where TEventArgs : EventArgs -{ - event EventHandler OnNext; -} -``` - -These look quite easy to work with. So if we create an `EventArgs` type and then apply a simple transform using `Select`, we can make a standard sequence fit the pattern. - -The `EventArgs` type: - -```csharp -public class MyEventArgs : EventArgs -{ - private readonly long _value; - - public MyEventArgs(long value) - { - _value = value; - } - - public long Value - { - get { return _value; } - } -} -``` - -The transform: - -```csharp -var source = Observable.Interval(TimeSpan.FromSeconds(1)) - .Select(i => new EventPattern(this, new MyEventArgs(i))); -``` - -Now that we have a sequence that is compatible, we can use the `ToEventPattern`, and in turn, a standard event handler. - -```csharp -var result = source.ToEventPattern(); -result.OnNext += (sender, eventArgs) => Console.WriteLine(eventArgs.Value); -``` - -Now that we know how to get back into .NET events, let's take a break and remember why Rx is a better model. - -- In C#, events have a curious interface. Some find the `+=` and `-=` operators an unnatural way to register a callback -- Events are difficult to compose -- Events do not offer the ability to be easily queried over time -- Events are a common cause of accidental memory leaks -- Events do not have a standard pattern for signaling completion -- Events provide almost no help for concurrency or multithreaded applications. For instance, raising an event on a separate thread requires you to do all of the plumbing - -The set of methods we have looked at in this chapter complete the circle started in the [Creating a Sequence](04_CreatingObservableSequences.html#TransitioningIntoIObservable) chapter. We now have the means to enter and leave the observable sequence monad. Take care when opting in and out of the `IObservable` monad. Doing so excessively can quickly make a mess of your code base, and may indicate a design flaw. \ No newline at end of file diff --git a/content/10_Part3.md b/content/10_Part3.md new file mode 100644 index 0000000..2da241f --- /dev/null +++ b/content/10_Part3.md @@ -0,0 +1,11 @@ +--- +title: Getting pragmatic +--- + +# PART 3 - Getting pragmatic. + +The first part of this book focused on the basic ideas and types of Rx. In the second part, I showed the operators Rx offers, enabling us to define the transformations and computations we want to apply to our source data. This second part was essentially functional programming. Rx's operators are mostly like mathematical functions, in that they will invariably behave in the same way for particular inputs. They are unaffected by the state of the world around them, and they also do nothing to change its state. In functional programming, such mechanisms are sometimes described as _pure_. + +This _purity_ can help us understand what our code will do. It means we don't need to know about the state of the rest of our program in order to understand how one particular part functions. However, code that is completely detached from the outside world is unlikely to achieve anything useful. In practice, we need to connect these pure computations with more pragmatic concerns. The [Creating Observable Sequences chapter](03_CreatingObservableSequences.md) already showed how to define observable streams, so we've already looked at how to connect real world inputs into the world of Rx. But what about the other end? How do we do something useful with the results of our processing? + +In some cases, it might be enough to do work inside `IObserver` implementations, or using the callback-based subscription mechanisms you've already seen. However, some situations will demand something more sophisticated. So in this third part of the book, we will look at some of the features Rx offers to help connect processes of the kind we looked at in part 2 with the rest of the world. diff --git a/content/11_AdvancedErrorHandling.md b/content/11_AdvancedErrorHandling.md deleted file mode 100644 index fadce95..0000000 --- a/content/11_AdvancedErrorHandling.md +++ /dev/null @@ -1,433 +0,0 @@ ---- -title: Advanced error handling ---- - -# Advanced error handling - -Exceptions happen. Exceptions themselves are not bad or good, however the way we raise or catch them can. Some exceptions are predictable and are due to sloppy code, for example a `DivideByZeroException`. Other exceptions cannot be prevented with defensive coding, for example an I/O exception like `FileNotFoundException` or `TimeoutException`. In these cases, we need to cater for the exception gracefully. Providing some sort of error message to the user, logging the error or perhaps retrying are all potential ways to handle these exceptions. - -The `IObserver` interface and `Subscribe` extension methods provide the ability to cater for sequences that terminate in error, however they leave the sequence terminated. They also do not offer a composable way to cater for different `Exception` types. A functional approach that enables composition of error handlers, allowing us to remain in the monad, would be more useful. -Again, Rx delivers. - -## Control flow constructs - -Using marble diagrams, we will examine various ways to handle different control flows. Just as with normal .NET code, we have flow control constructs such as `try`/`catch`/`finally`. In this chapter we see how they can be applied to observable sequences. - -### Catch - -Just like a catch in SEH (Structured Exception Handling), with Rx you have the option of swallowing an exception, wrapping it in another exception or performing some other logic. - -We already know that observable sequences can handle erroneous situations with the `OnError` construct. A useful method in Rx for handling an `OnError` notification is the `Catch` extension method. Catch allows you to intercept a specific `Exception` type and then continue with another sequence. - -Below is the signature for the simple overload of catch: - -```csharp -public static IObservable Catch( - this IObservable first, - IObservable second) -{ - ... -} -``` - -#### Swallowing exceptions - -With Rx, you can catch and swallow exceptions in a similar way to SEH. It is quite simple; we use the `Catch` extension method and provide an empty sequence as the second value. - -We can represent an exception being swallowed like this with a marble diagram. - -``` -S1--1--2--3--X -S2 -| -R --1--2--3----| -``` - -Here `S1` represents the first sequence that ends with an error (`X`). `S2` is the continuation sequence, an empty sequence. `R` is the result sequence which starts as `S1`, then continues with `S2` when `S1` terminates. - -```csharp -var source = new Subject(); -var result = source.Catch(Observable.Empty()); - -result.Dump("Catch");); - -source.OnNext(1); -source.OnNext(2); -source.OnError(new Exception("Fail!")); -``` - -Output: - -``` -Catch --> 1 -Catch --> 2 -Catch completed -``` - -The example above will catch and swallow all types of exceptions. -This is somewhat equivalent to the following with SEH: - -```csharp -try -{ - DoSomeWork(); -} -catch -{ -} -``` - -Just as it is generally avoided in SEH, you probably also want to limit your use of swallowing errors in Rx. You may, however, have a specific exception you want to handle. Catch has an overload that enables you specify the type of exception. Just as the following code would allow you to catch a `TimeoutException`: - -```csharp -try -{ - // -} -catch (TimeoutException tx) -{ - // -} -``` - -Rx also offers an overload of `Catch` to cater for this. - -```csharp -public static IObservable Catch( - this IObservable source, - Func> handler) - where TException : Exception -{ - ... -} -``` - -The following Rx code allows you to catch a `TimeoutException`. Instead of providing a second sequence, we provide a function that takes the exception and returns a sequence. This allows you to use a factory to create your continuation. In this example, we add the value -1 to the error sequence and then complete it. - -```csharp -var source = new Subject(); -var result = source.Catch(tx=>Observable.Return(-1)); - -result.Dump("Catch"); - -source.OnNext(1); -source.OnNext(2); -source.OnError(new TimeoutException()); -``` - -Output: - -``` -Catch-->1 -Catch-->2 -Catch-->-1 -Catch completed -``` - -If the sequence was to terminate with an `Exception` that could not be cast to a `TimeoutException`, then the error would not be caught and would flow through to the subscriber. - -```csharp -var source = new Subject(); -var result = source.Catch(tx=>Observable.Return(-1)); - -result.Dump("Catch"); - -source.OnNext(1); -source.OnNext(2); -source.OnError(new ArgumentException("Fail!")); -``` - -Output: - -``` -Catch-->1 -Catch-->2 -Catch failed-->Fail! -``` - -### Finally - -Similar to the `finally` statement with SEH, Rx exposes the ability to execute code on completion of a sequence, regardless of how it terminates. The `Finally` extension method accepts an `Action` as a parameter. This `Action` will be invoked if the sequence terminates normally or erroneously, or if the subscription is disposed of. - -```csharp -public static IObservable Finally( - this IObservable source, - Action finallyAction) -{ - ... -} -``` - -In this example, we have a sequence that completes. We provide an action and see that it is called after our `OnCompleted` handler. - -```csharp -var source = new Subject(); -var result = source.Finally(() => Console.WriteLine("Finally action ran")); -result.Dump("Finally"); -source.OnNext(1); -source.OnNext(2); -source.OnNext(3); -source.OnCompleted(); -``` - -Output: - -``` -Finally-->1 -Finally-->2 -Finally-->3 -Finally completed -Finally action ran -``` - -In contrast, the source sequence could have terminated with an exception. In that case, the exception would have been sent to the console, and then the delegate we provided would have been executed. - -Alternatively, we could have disposed of our subscription. In the next example, we see that the `Finally` action is invoked even though the sequence does not complete. - -```csharp -var source = new Subject(); -var result = source.Finally(() => Console.WriteLine("Finally")); -var subscription = result.Subscribe( - Console.WriteLine, - Console.WriteLine, - () => Console.WriteLine("Completed")); -source.OnNext(1); -source.OnNext(2); -source.OnNext(3); -subscription.Dispose(); -``` - -Output: - -``` -1 -2 -3 -Finally -``` - -Note that there is an anomaly in the current implementation of `Finally`. If there is no `OnError` handler provided, the error will be promoted to an exception and thrown. This will be done before the `Finally` action is invoked. We can reproduce this behavior easily by removing the `OnError` handler from our examples above. - -```csharp -var source = new Subject(); -var result = source.Finally(() => Console.WriteLine("Finally")); -result.Subscribe( - Console.WriteLine, - // Console.WriteLine, - () => Console.WriteLine("Completed")); -source.OnNext(1); -source.OnNext(2); -source.OnNext(3); - -// Brings the app down. Finally action is not called. -source.OnError(new Exception("Fail")); -``` - -Hopefully this will be identified as a bug and fixed by the time you read this in the next release of Rx. Out of academic interest, here is a sample of a `Finally` extension method that would work as expected. - -```csharp -public static IObservable MyFinally( - this IObservable source, - Action finallyAction) -{ - return Observable.Create(o => - { - var finallyOnce = Disposable.Create(finallyAction); - var subscription = source.Subscribe( - o.OnNext, - ex => - { - try { o.OnError(ex); } - finally { finallyOnce.Dispose(); } - }, - () => - { - try { o.OnCompleted(); } - finally { finallyOnce.Dispose(); } - }); - - return new CompositeDisposable(subscription, finallyOnce); - }); -} -``` - -### Using - -The `Using` factory method allows you to bind the lifetime of a resource to the lifetime of an observable sequence. The signature itself takes two factory methods; one to provide the resource and one to provide the sequence. This allows everything to be lazily evaluated. - -```csharp -public static IObservable Using( - Func resourceFactory, - Func> observableFactory) - where TResource : IDisposable -{ - ... -} -``` - -The `Using` method will invoke both the factories when you subscribe to the sequence. The resource will be disposed of when the sequence is terminated gracefully, terminated erroneously or when the subscription is disposed. - -To provide an example, we will reintroduce the `TimeIt` class from [Chapter 3](03_LifetimeManagement.html#IDisposable). I could use this handy little class to time the duration of a subscription. In the next example we create an observable sequence with the `Using` factory method. We provide a factory for a `TimeIt` resource and a function that returns a sequence. - -```csharp -var source = Observable.Interval(TimeSpan.FromSeconds(1)); -var result = Observable.Using( - () => new TimeIt("Subscription Timer"), - timeIt => source); -result.Take(5).Dump("Using"); -``` - -Output: - -``` -Using --> 0 -Using --> 1 -Using --> 2 -Using --> 3 -Using --> 4 -Using completed -Subscription Timer took 00:00:05.0138199 -``` - -Due to the `Take(5)` decorator, the sequence completes after five elements and thus the subscription is disposed of. Along with the subscription, the `TimeIt` resource is also disposed of, which invokes the logging of the elapsed time. - -This mechanism can find varied practical applications in the hands of an imaginative developer. The resource being an `IDisposable` is convenient; indeed, it makes it so that many types of resources can be bound to, such as other subscriptions, stream reader/writers, database connections, user controls and, with `Disposable.Create(Action)`, virtually anything else. - -### OnErrorResumeNext - -Just the title of this section will send a shudder down the spines of old VB developers! In Rx, there is an extension method called `OnErrorResumeNext` that has similar semantics to the VB keywords/statement that share the same name. This extension method allows the continuation of a sequence with another sequence regardless of whether the first sequence completes gracefully or due to an error. Under normal use, the two sequences would merge as below: - -``` -S1--0--0--| -S2 --0--| -R --0--0----0--| -``` - -In the event of a failure in the first sequence, then the sequences would still merge: - -``` -S1--0--0--X -S2 --0--| -R --0--0----0--| -``` - -The overloads to `OnErrorResumeNext` are as follows: - -```csharp -public static IObservable OnErrorResumeNext( - this IObservable first, - IObservable second) -{ - .. -} - -public static IObservable OnErrorResumeNext( - params IObservable[] sources) -{ - ... -} - -public static IObservable OnErrorResumeNext( - this IEnumerable> sources) -{ - ... -} -``` - -It is simple to use; you can pass in as many continuations sequences as you like using the various overloads. Usage should be limited however. Just as the `OnErrorResumeNext` keyword warranted mindful use in VB, so should it be used with caution in Rx. It will swallow exceptions quietly and can leave your program in an unknown state. Generally, this will make your code harder to maintain and debug. - -### Retry - -If you are expecting your sequence to encounter predictable issues, you might simply want to retry. One such example when you want to retry is when performing I/O (such as web request or disk access). I/O is notorious for intermittent failures. The `Retry` extension method offers the ability to retry on failure a specified number of times or until it succeeds. - -```csharp -// Repeats the source observable sequence until it successfully terminates. -public static IObservable Retry( - this IObservable source) -{ - ... -} - -// Repeats the source observable sequence the specified number of times or until it -// successfully terminates. -public static IObservable Retry( - this IObservable source, int retryCount) -{ - ... -} -``` - -In the diagram below, the sequence (`S`) produces values then fails. It is re-subscribed, after which it produces values and fails again; this happens a total of two times. The result sequence (`R`) is the concatenation of all the successive subscriptions to (`S`). - -``` -S --1--2--X - --1--2--3--X - --1 -R --1--2------1--2--3------1 -``` - -In the next example, we just use the simple overload that will always retry on any exception. - -```csharp -public static void RetrySample(IObservable source) -{ - source.Retry().Subscribe(t=>Console.WriteLine(t)); //Will always retry - Console.ReadKey(); -} -``` - -Given the source [0,1,2,X], the output would be: - -``` -0 -1 -2 -0 -1 -2 -0 -1 -2 -``` - -This output would continue forever, as we throw away the token from the subscribe method. As a marble diagram it would look like this: - -``` -S--0--1--2--x - --0--1--2--x - --0-- -R--0--1--2-----0--1--2-----0-- -``` - -Alternatively, we can specify the maximum number of retries. In this example, we only retry once, therefore the error that gets published on the second subscription will be passed up to the final subscription. Note that to retry once you pass a value of 2. Maybe the method should have been called `Try`? - -```csharp -source.Retry(2).Dump("Retry(2)"); -``` - -Output: - -``` -Retry(2)-->0 -Retry(2)-->1 -Retry(2)-->2 -Retry(2)-->0 -Retry(2)-->1 -Retry(2)-->2 -Retry(2) failed-->Test Exception -``` - -As a marble diagram, this would look like: - -``` -S--0--1--2--x - --0--1--2--x -R--0--1--2-----0--1--2--x -``` - -Proper care should be taken when using the infinite repeat overload. Obviously if there is a persistent problem with your underlying sequence, you may find yourself stuck in an infinite loop. Also, take note that there is no overload that allows you to specify the type of exception to retry on. - -A useful extension method to add to your own library might be a "Back Off and Retry" method. The teams I have worked with have found such a feature useful when performing I/O, especially network requests. The concept is to try, and on failure wait for a given period of time and then try again. Your version of this method may take into account the type of `Exception` you want to retry on, as well as the maximum number of times to retry. You may even want to lengthen the to wait period to be less aggressive on each subsequent retry. - - - -Requirements for exception management that go beyond simple `OnError` handlers are commonplace. Rx delivers the basic exception handling operators which you can use to compose complex and robust queries. In this chapter we have covered advanced error handling and some more resource management features from Rx. We looked at the `Catch`, `Finally` and `Using` methods as well as the other methods like `OnErrorResumeNext` and `Retry`, that allow you to play a little 'fast and loose'. We have also revisited the use of marble diagrams to help us visualize the combination of multiple sequences. This will help us in our next chapter where we will look at other ways of composing and aggregating observable sequences. \ No newline at end of file diff --git a/content/11_SchedulingAndThreading.md b/content/11_SchedulingAndThreading.md new file mode 100644 index 0000000..361be65 --- /dev/null +++ b/content/11_SchedulingAndThreading.md @@ -0,0 +1,1030 @@ +--- +title: Scheduling and threading +--- + +# Scheduling and Threading + +Rx is primarily a system for working with _data in motion_ asynchronously. If we are dealing with multiple information sources, they may well generate data concurrently. We may want some degree of parallelism when processing data to achieve our scalability targets. We will need control over these aspects of our system. + +So far, we have managed to avoid any explicit usage of threading or concurrency. We have seen some methods that must deal with timing to perform their jobs. (For example, `Buffer`, `Delay`, and `Sample` must arrange for work to happen on a particular schedule.) However, we have relied on the default behaviour, and although the defaults often do what we want, we sometimes need to exercise more control. This chapter will look at Rx's scheduling system, which offers an elegant way to manage these concerns. + +## Rx, Threads and Concurrency + +Rx does not impose constraints on which threads we use. An `IObservable` is free to invoke its subscribers' `OnNext/Completed/Error` methods on any thread, perhaps a different thread for each call. Despite this free-for-all, there is one aspect of Rx that prevents chaos: observable sources must obey the [Fundamental Rules of Rx Sequences](02_KeyTypes.md#the-fundamental-rules-of-rx-sequences) under all circumstances. + +When we first explored these rules, we focused on how they determine the ordering of calls into any single observer. There can be any number of calls to `OnNext`, but once either `OnError` or `OnCompleted` have been invoked, there must be no further calls. But now that we're looking at concurrency, a different aspect of these rules becomes more important: for any single subscription, an observable source must not make concurrent calls into that subscription's observer. So if a source calls `OnNext`, it must wait until that call returns before either calling `OnNext` again, or calling `OnError` or `OnComplete`. + +The upshot for observers is that as long as your observer is involved in just one subscription, it will only ever be asked to deal with one thing at a time. If doesn't matter if the source to which it is subscribed is a long and complex processing chain involving many different operators. Even if you build that source by combining multiple inputs (e.g., using [`Merge`](09_CombiningSequences.md#merge)), the fundamental rules require that if you called `Subscribe` just once on a single `IObservable`, that source is never allowed to make multiple concurrent calls into your `IObserver` methods. + +So although each call might come in on a different thread, the calls are strictly sequential (unless a single observer is involved in multiple subscriptions). + +Rx operators that receive incoming notifications as well as producing them will notify their observers on whatever thread the incoming notification happened to arrive on. Suppose you have a sequence of operators like this: + +```cs +source + .Where(x => x.MessageType == 3) + .Buffer(10) + .Take(20) + .Subscribe(x => Console.WriteLine(x)); +``` + +When that call to `Subscribe` happens, we end up with a chain of observers. The Rx-supplied observer that will invoke our callback was passed to the observable returned by `Take`, which will in turn create an observer that subscribed to the observable returned by `Buffer`, which will in turn create an observer subscribed to the `Where` observable, which will have created yet another observer which is subscribed to `source`. + +So when `source` decides to produce an item, it will invoke the `Where` operator's observer's `OnNext`. That will invoke the predicate, and if the `MessageType` is indeed 3, the `Where` observer will call `OnNext` on the `Buffer`'s observer, and it will do this on the same thread. The `Where` observer's `OnNext` isn't going to return until the `Buffer` observer's `OnNext` returns. Now if the `Buffer` observer determines that it has completely filled a buffer (e.g., it just received its 10th item), then it is also not going to return yet—it's going to invoke the `Take` observer's `OnNext`, and as long as `Take` hasn't already received 20 buffers, it's going to call `OnNext` on the Rx-supplied observer that will invoke our callback. + +So for the source notifications that make it all the way through to that `Console.WriteLine` in the callback passed to subscribe, we end up with a lot of nested calls on the stack: + +``` +`source` calls: + `Where` observer, which calls: + `Buffer` observer, which calls: + `Take` observer, which calls: + `Subscribe` observer, which calls our lambda +``` + +This is all happening on one thread. Most Rx operators don't have any one particular thread that they call home. They just do their work on whatever thread the call comes in on. This makes Rx pretty efficient. Passing data from one operator to the next merely involves a method call, and those are pretty fast. (In fact, there are typically a few more layers. Rx tends to add a few wrappers to handle errors and early unsubscription. So the call stack will look a bit more complex than what I've just shown. But it's still typically all just method calls.) + +You will sometimes hear Rx described as having a _free threaded_ model. All that means is that operators don't generally care what thread they use. As we will see, there are exceptions, but this direct calling by one operator of the next is the norm. + +An upshot of this is that it's typically the original source that determines which thread is used. This next example illustrates this by creating a subject, then calling `OnNext` on various threads and reporting the thread id. + +```cs +Console.WriteLine($"Main thread: {Environment.CurrentManagedThreadId}"); +var subject = new Subject(); + +subject.Subscribe( + m => Console.WriteLine($"Received {m} on thread: {Environment.CurrentManagedThreadId}")); + +object sync = new(); +ParameterizedThreadStart notify = arg => +{ + string message = arg?.ToString() ?? "null"; + Console.WriteLine( + $"OnNext({message}) on thread: {Environment.CurrentManagedThreadId}"); + lock (sync) + { + subject.OnNext(message); + } +}; + +notify("Main"); +new Thread(notify).Start("First worker thread"); +new Thread(notify).Start("Second worker thread"); +``` + +Output: + +``` +Main thread: 1 +OnNext(Main) on thread: 1 +Received Main on thread: 1 +OnNext(First worker thread) on thread: 10 +Received First worker thread on thread: 10 +OnNext(Second worker thread) on thread: 11 +Received Second worker thread on thread: 11 +``` + +In each case, the handler passed to `Subscribe` was called back on the same thread that made the call to `subject.OnNext`. This is straightforward and efficient. However, things are not always this simple. + +## Timed invocation + +Some notifications will not be the immediate result of a source providing an item. For example, Rx offers a [`Delay`](12_Timing.md#delay) operator, which time shifts the delivery of items. This next example is based on the preceding one, with the main difference being that we no longer subscribe directly to the source. We go via `Delay`: + +```cs +Console.WriteLine($"Main thread: {Environment.CurrentManagedThreadId}"); +var subject = new Subject(); + +subject + .Delay(TimeSpan.FromSeconds(0.25)) + .Subscribe( + m => Console.WriteLine($"Received {m} on thread: {Environment.CurrentManagedThreadId}")); + +object sync = new(); +ParameterizedThreadStart notify = arg => +{ + string message = arg?.ToString() ?? "null"; + Console.WriteLine( + $"OnNext({message}) on thread: {Environment.CurrentManagedThreadId}"); + lock (sync) + { + subject.OnNext(message); + } +}; + +notify("Main 1"); +Thread.Sleep(TimeSpan.FromSeconds(0.1)); +notify("Main 2"); +Thread.Sleep(TimeSpan.FromSeconds(0.3)); +notify("Main 3"); +new Thread(notify).Start("First worker thread"); +Thread.Sleep(TimeSpan.FromSeconds(0.1)); +new Thread(notify).Start("Second worker thread"); + +Thread.Sleep(TimeSpan.FromSeconds(2)); +``` + +This also waits for a while between sending source items, so we can see the effect of `Delay`. Here's the output: + +``` +Main thread: 1 +OnNext(Main 1) on thread: 1 +OnNext(Main 2) on thread: 1 +Received Main 1 on thread: 12 +Received Main 2 on thread: 12 +OnNext(Main 3) on thread: 1 +OnNext(First worker thread) on thread: 13 +OnNext(Second worker thread) on thread: 14 +Received Main 3 on thread: 12 +Received First worker thread on thread: 12 +Received Second worker thread on thread: 12 +``` + +Notice that in this case every `Received` message is on thread id 12, which is different from any of the three threads on which the notifications were raised. + +This shouldn't be entirely surprising. The only way Rx could have used the original thread here would be for `Delay` to block the thread for the specified time (a quarter of a second here) before forwarding the call. This would be unacceptable for most scenarios, so instead, the `Delay` operator arranges for a callback to occur after a suitable delay. As you can see from the output, these all seems to happen on one particular thread. No matter which thread calls `OnNext`, the delayed notification arrives on thread id 12. But this is not a thread created by the `Delay` operator. This is happening because `Delay` is using a _scheduler_. + +## Schedulers + +Schedulers do three things: + +* determining the context in which to execute work (e.g., a certain thread) +* deciding when to execute work (e.g., immediately, or deferred) +* keeping track of time + +Here's a simple example to explore the first two of those: + +```cs +Console.WriteLine($"Main thread: {Environment.CurrentManagedThreadId}"); + +Observable + .Range(1, 5) + .Subscribe( + m => Console.WriteLine($"Received {m} on thread: {Environment.CurrentManagedThreadId}")); + +Console.WriteLine("Subscribe returned"); +Console.ReadLine(); +``` + +It might not be obvious that this has anything to do with scheduling, but in fact, `Range` always uses a scheduler to do its work. We've just let it use its default scheduler. Here's the output: + +``` +Main thread: 1 +Received 1 on thread: 1 +Received 2 on thread: 1 +Received 3 on thread: 1 +Received 4 on thread: 1 +Received 5 on thread: 1 +Subscribe returned +``` + +Looking at the first two items in our list of what schedulers do, we can see that the context in which this has executed the work is the thread on which I called `Subscribe`. And as for when it has decided to execute the work, it has decided to do it all before `Subscribe` returns. So you might think that `Range` immediately produces all of the items we've asked for and then returns. However, it's not quite as simple as that. Let's look at what happens if we have multiple `Range` instances running simultaneously. This introduces an extra operator: a `SelectMany` that calls `Range` again: + +```cs +Observable + .Range(1, 5) + .SelectMany(i => Observable.Range(i * 10, 5)) + .Subscribe( + m => Console.WriteLine($"Received {m} on thread: {Environment.CurrentManagedThreadId}")); +``` + +The output shows that `Range` doesn't in fact necessarily produce all of its items immediately: + +``` +Received 10 on thread: 1 +Received 11 on thread: 1 +Received 20 on thread: 1 +Received 12 on thread: 1 +Received 21 on thread: 1 +Received 30 on thread: 1 +Received 13 on thread: 1 +Received 22 on thread: 1 +Received 31 on thread: 1 +Received 40 on thread: 1 +Received 14 on thread: 1 +Received 23 on thread: 1 +Received 32 on thread: 1 +Received 41 on thread: 1 +Received 50 on thread: 1 +Received 24 on thread: 1 +Received 33 on thread: 1 +Received 42 on thread: 1 +Received 51 on thread: 1 +Received 34 on thread: 1 +Received 43 on thread: 1 +Received 52 on thread: 1 +Received 44 on thread: 1 +Received 53 on thread: 1 +Received 54 on thread: 1 +Subscribe returned +``` + +The first nested `Range` produces by the `SelectMany` callback produces a couple of values (10 and 11) but then the second one manages to get its first value out (20) before the first one produces its third (12). You can see there's some interleaving of progress here. So although the context in which work is executed continues to be the thread on which we invoked `Subscribe`, the second choice the scheduler has to make—when to execute the work—is more subtle than it first seems. This tells us that `Range` is not as simple as this naive implementation: + +```cs +public static IObservable NaiveRange(int start, int count) +{ + return System.Reactive.Linq.Observable.Create(obs => + { + for (int i = 0; i < count; i++) + { + obs.OnNext(start + i); + } + + return Disposable.Empty; + }); +} +``` + +If `Range` worked like that, this code would produce all of the items from the first range returned by the `SelectMany` callback before moving on to the next. In fact, Rx does provide a scheduler that would give us that behaviour if that's what we want. This example passes `ImmediateScheduler.Instance` to the nested `Observable.Range` call: + +```cs +Observable + .Range(1, 5) + .SelectMany(i => Observable.Range(i * 10, 5, ImmediateScheduler.Instance)) + .Subscribe( + m => Console.WriteLine($"Received {m} on thread: {Environment.CurrentManagedThreadId}")); +``` + +Here's the outcome: + +``` +Received 10 on thread: 1 +Received 11 on thread: 1 +Received 12 on thread: 1 +Received 13 on thread: 1 +Received 14 on thread: 1 +Received 20 on thread: 1 +Received 21 on thread: 1 +Received 22 on thread: 1 +Received 23 on thread: 1 +Received 24 on thread: 1 +Received 30 on thread: 1 +Received 31 on thread: 1 +Received 32 on thread: 1 +Received 33 on thread: 1 +Received 34 on thread: 1 +Received 40 on thread: 1 +Received 41 on thread: 1 +Received 42 on thread: 1 +Received 43 on thread: 1 +Received 44 on thread: 1 +Received 50 on thread: 1 +Received 51 on thread: 1 +Received 52 on thread: 1 +Received 53 on thread: 1 +Received 54 on thread: 1 +Subscribe returned +``` + +By specifying `ImmediateScheduler.Instance` in the innermost call to `Observable.Range` we've asked for a particular policy: this invokes all work on the caller's thread, and it always does so immediately. There are a couple of reasons this is not `Range`'s default. (Its default is `Scheduler.CurrentThread`, which always returns an instance of `CurrentThreadScheduler`.) First, `ImmediateScheduler.Instance` can end up causing fairly deep call stacks. Most of the other schedulers maintain work queues, so if one operator decides it has new work to do while another is in the middle of doing something (e.g., a nested `Range` operator decides to start emitting its values), instead of starting that work immediately (which will involve invoking the method that will do the work) that work can be put on a queue instead, enabling the work already in progress to finish before starting on the next thing. Using the immediate scheduler everywhere can cause stack overflows when queries become complex. The second reason `Range` does not use the immediate scheduler by default is so that when multiple observables are all active at once, they can all make some progress—`Range` produces all of its items as quickly as it can, so it could end up starving other operators of CPU time if it didn't use a scheduler that enabled operators to take it in turns. + +Notice that the `Subscribe returned` message appears last in both examples. So although the `CurrentThreadScheduler` isn't quite as eager as the immediate scheduler, it still won't return to its caller until it has completed all outstanding work. It maintains a work queue, enabling slightly more fairness, and avoiding stack overflows, but as soon as anything asks the `CurrentThreadScheduler` to do something, it won't return until it has drained its queue. + +Not all schedulers have this characteristic. Here's a variation on the earlier example in which we have just a single call to `Range`, without any nested observables. This time I'm asking it to use the `TaskPoolScheduler`. + +```cs +Observable + .Range(1, 5, TaskPoolScheduler.Default) + .Subscribe( + m => Console.WriteLine($"Received {m} on thread: {Environment.CurrentManagedThreadId}")); +``` + +This makes a different decision about the context in which to run work, compared to the immediate and current thread schedulers, as we can see from its output: + +``` +Main thread: 1 +Subscribe returned +Received 1 on thread: 12 +Received 2 on thread: 12 +Received 3 on thread: 12 +Received 4 on thread: 12 +Received 5 on thread: 12 +``` + +Notice that the notifications all happened on a different thread (with id 12) than the thread on which we invoked `Subscribe` (id 1). That's because the `TaskPoolScheduler`'s defining feature is that it invokes all work through the Task Parallel Library's (TPL) task pool. That's why we see a different thread id: the task pool doesn't own our application's main thread. In this case, it hasn't seen any need to spin up multiple threads. That's reasonable, there's just a single source here providing item one at a time. It's good that we didn't get more threads in this case—the thread pool is at its most efficient when a single thread processes work items sequentially, because it avoids context switching overheads, and since there's no actual scope for concurrent work here, we would gain nothing if it had created multiple threads in this case. + +There's one other very significant difference with this scheduler: notice that the call to `Subscribe` returned before _any_ of the notifications reached our observer. That's because this is the first scheduler we've looked at that will introduce real parallelism. The `ImmediateScheduler` and `CurrentThreadScheduler` will never spin up new threads by themselves, no matter how much the operators executing might want to perform concurrent operations. And although the `TaskPoolScheduler` determined that there's no need for it to create multiple threads, the one thread it did create is a different thread from the application's main thread, meaning that the main thread can continue to run in parallel with this subscription. Since `TaskPoolScheduler` isn't going to do any work on the thread that initiated the work, it can return as soon as it has queued the work up, enabling the `Subscribe` method to return immediately. + +What if we use the `TaskPoolScheduler` in the example with nested observables? This uses it just on the inner call to `Range`, so the outer one will still use the default `CurrentThreadScheduler`: + +```cs +Observable + .Range(1, 5) + .SelectMany(i => Observable.Range(i * 10, 5, TaskPoolScheduler.Default)) + .Subscribe( + m => Console.WriteLine($"Received {m} on thread: {Environment.CurrentManagedThreadId}")); +``` + +Now we can see a few more threads getting involved: + +``` +Received 10 on thread: 13 +Received 11 on thread: 13 +Received 12 on thread: 13 +Received 13 on thread: 13 +Received 40 on thread: 16 +Received 41 on thread: 16 +Received 42 on thread: 16 +Received 43 on thread: 16 +Received 44 on thread: 16 +Received 50 on thread: 17 +Received 51 on thread: 17 +Received 52 on thread: 17 +Received 53 on thread: 17 +Received 54 on thread: 17 +Subscribe returned +Received 14 on thread: 13 +Received 20 on thread: 14 +Received 21 on thread: 14 +Received 22 on thread: 14 +Received 23 on thread: 14 +Received 24 on thread: 14 +Received 30 on thread: 15 +Received 31 on thread: 15 +Received 32 on thread: 15 +Received 33 on thread: 15 +Received 34 on thread: 15 +``` + +Since we have only a single observer in this example, the rules of Rx require it to be given items one at a time, so in practice there wasn't really any scope for parallelism here, but the more complex structure would have resulted in more work items initially going into the scheduler's queue than in the preceding example, which is probably why the work got picked up by more than one thread this time. In practice most of these threads would have spent most of their time blocked in the code inside `SelectMany` that ensures that it delivers one item at a time to its target observer. It's perhaps a little surprising that the items are not more scrambled. The subranges themselves seem to have emerged in a random order, but it has almost produced the items sequentially within each subrange (with item 14 being the one exception to that). This is a quirk relating to the way in which `Range` interacts with the `TaskPoolScheduler`. + +I've not yet talked about the scheduler's third job: keeping track of time. This doesn't arise with `Range` because it attempts to produce all of its items as quickly as it can. But for the `Delay` operator I showed in the [Timed Invocation](#timed-invocation) section, timing is obviously a critical element. In fact this would be a good point to show the API that schedulers offer: + +```cs +public interface IScheduler +{ + DateTimeOffset Now { get; } + IDisposable Schedule(TState state, Func action); + IDisposable Schedule(TState state, TimeSpan dueTime, Func action); + IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action); +} +``` + +You can see that all but one of these is concerned with timing. Only the first `Schedule` overload is not, and operators call that when they want to schedule work to run as soon as the scheduler will allow. That's the overload used by `Range`. (Strictly speaking, `Range` interrogates the scheduler to find out whether it supports long-running operations, in which an operator can take temporary control of a thread for an extended period. It prefers to use that when it can because it tends to be more efficient than submitting work to the scheduler for every single item it wishes to produce. The `TaskPoolScheduler` does support long running operations, which explains the slightly surprising output we saw earlier, but the `CurrentThreadScheduler`, `Range`'s default choice, does not. So by default, `Range` will invoke that first `Schedule` overload once for each item it wishes to produce.) + +`Delay` uses the second overload. The exact implementation is quite complex (mainly because of how it catches up efficiently when a busy source causes it to fall behind) but in essence, each time a new item arrives into the `Delay` operator, it schedules a work item to run after the configured delay, so that it can supply that item to its subscriber with the expected time shift. + +Schedulers have to be responsible for managing time, because .NET has several different timer mechanisms, and the choice of timer is often determined by the context in which you want to handle a timer callback. Since schedulers determine the context in which work runs, that means they must also choose the timer type. For example, UI frameworks typically provide timers that invoke their callbacks in a context suitable for making updates to the user interface. Rx provides some UI-framework-specific schedulers that use these timers, but these would be inappropriate choices for other scenarios. So each scheduler uses a timer suitable for the context in which it is going to run work items. + +There's a useful upshot of this: because `IScheduler` provides an abstraction for timing-related details, it is possible to virtualize time. This is very useful for testing. If you look at the extensive test suite in the [Rx repository](https://github.com/dotnet/reactive) you will find that there are many tests that verify timing-related behaviour. If these ran in real-time, the test suite would take far too long to run, and would also be likely to produce the odd spurious failure, because background tasks running on the same machine as the tests will occasionally change the speed of execution in a way that might confuse the test. Instead, these tests use a specialized scheduler that provides complete control over the passage of time. (For more information, see the [ Test Schedulers section later](#test-schedulers) and there's also a whole [testing chapter](16_TestingRx.md) coming up.) + +Notice that all three `IScheduler.Schedule` methods require a callback. A scheduler will invoke this at the time and in the context that it chooses. A scheduler callback takes another `IScheduler` as its first argument. This is used in scenarios where repetitive invocation is required, as we'll see later. + +Rx supplies several schedulers. The following sections describe the most widely used ones. + +### ImmediateScheduler + +`ImmediateScheduler` is the simplest scheduler Rx offers. As you saw in the preceding sections, whenever it is asked to schedule some work, it just runs it immediately. It does this inside its `IScheduler.Schedule` method. + +This is a very simple strategy, and it makes `ImmediateScheduler` very efficient. For this reason, many operators default to using `ImmediateScheduler`. However, it can be problematic with operators that instantly produce multiple items, especially when the number of items might be large. For example, Rx defines the [`ToObservable` extension method for `IEnumerable`](03_CreatingObservableSequences.md#from-ienumerablet). When you subscribe to an `IObservable` returned by this, it will start iterating over the collection immediately, and if you were to tell it to use the `ImmediateScheduler`, `Subscribe` would not return until it reached the end of the collection. That would obviously be a problem for an infinite sequence, and it's why operators of this kind do not use `ImmediateScheduler` by default. + +The `ImmediateScheduler` also has potentially surprising behaviour when you invoke the `Schedule` overload that takes a `TimeSpan`. This asks the scheduler to run some work after the specified length of time. The way it achieves this is to call `Thread.Sleep`. With most of Rx's schedulers, this overload will arrange for some sort of timer mechanism to run the code later, enabling the current thread to get on with its business, but `ImmediateScheduler` is true to its name here, in that it refuses to engage in such deferred execution. It just blocks the current thread until it is time to do the work. This means that time-based observables like those returned by `Interval` would work if you specified this scheduler, but at the cost of preventing the thread from doing anything else. + +The `Schedule` overload that takes a `DateTime` is slightly different. If you specify a time less than 10 seconds into the future, it will block the calling thread like it does when you use `TimeSpan`. But if you pass a `DateTime` that is further into the future, it gives up on immediate execution, and falls back to using a timer. + +### CurrentThreadScheduler + +The `CurrentThreadScheduler` is very similar to the `ImmediateScheduler`. The difference is how it handles requests to schedule work when an existing work item is already being handled on the current thread. This can happen if you chain together multiple operators that use schedulers to do their work. + +To understand what happens, it's helpful to know how sources that produce multiple items in quick succession, such as the [`ToObservable` extension method for `IEnumerable`](03_CreatingObservableSequences.md#from-ienumerablet) or [`Observable.Range`](03_CreatingObservableSequences.md#observablerange), use schedulers. These kinds of operators do not use normal `for` or `foreach` loops. They typically schedule a new work item for each iteration (unless the scheduler happens to make special provisions for long-running work). Whereas the `ImmediateScheduler` will run such work immediately, the `CurrentThreadScheduler` checks to see if it is already processing a work item. We saw that with this example from earlier: + +```cs +Observable + .Range(1, 5) + .SelectMany(i => Observable.Range(i * 10, 5)) + .Subscribe( + m => Console.WriteLine($"Received {m} on thread: {Environment.CurrentManagedThreadId}")); +``` + +Let's follow exactly what happens here. First, assume that this code is just running normally and not in any unusual context—perhaps inside the `Main` entry point of a program. When this code calls `Subscribe` on the `IObservable` returned by `SelectMany`, that will in turn will call `Subscribe` on the `IObservable` returned by the first `Observable.Range`, which will in turn schedule a work item for the generation of the first value in the range (`1`). + +Since we didn't pass a scheduler explicitly to `Range`, it will use its default choice, the `CurrentThreadScheduler`, and that will ask itself "Am I already in the middle of handling some work item on this thread?" In this case the answer will be "no," so it will run the work item immediately (before returning from the `Schedule` call made by the `Range` operator). The `Range` operator will then produce its first value, calling `OnNext` on the `IObserver` that the `SelectMany` operator provided when it subscribed to the range. + +The `SelectMany` operator's `OnNext` method will now invoke its lambda, passing in the argument supplied (the value `1` from the `Range` operator). You can see from the example above that this lambda calls `Observable.Range` again, returning a new `IObservable`. `SelectMany` will immediately subscribe to this (before returning from its `OnNext`). This is the second time this code has ended up calling `Subscribe` on an `IObservable` returned by a `Range` (but it's a different instance than the last time), and `Range` will once again default to using the `CurrentThreadScheduler`, and will once again schedule a work item to perform the first iteration. + +So once again,the `CurrentThreadScheduler` will ask itself "Am I already in the middle of handling some work item on this thread?" But this time, the answer will be yes. And this is where the behaviour is different than `ImmediateScheduler`. The `CurrentThreadScheduler` maintains a queue of work for each thread that it gets used on, and in this case it just adds the newly scheduled work to the queue, and returns back to the `SelectMany` operators `OnNext`. + +`SelectMany` has now completed its handling of this item (the value `1`) from the first `Range`, so its `OnNext` returns. At this point, this outer `Range` operator schedules another work item. Again, the `CurrentThreadScheduler` will detect that it is currently running a work item, so it just adds this to the queue. + +Having scheduled the work item that is going to generate its second value (`2`), the `Range` operator returns. Remember, the code in the `Range` operator that was running at this point was the callback for the first scheduled work item, so it's returning to the `CurrentThreadScheduler`—we are back inside its `Schedule` method (which was invoked by the range operator's `Subscribe` method). + +At this point, the `CurrentThreadScheduler` does not return from `Schedule` because it checks its work queue, and will see that there are now two items in the queue. (There's the work item that the nested `Range` observable scheduled to generate its first value, and there's also the work item that the top-level `Range` observable just scheduled to generate its second value.) The `CurrentThreadScheduler` will now execute the first of these: the nested `Range` operator now gets to generate its first value (which will be `10`), so it calls `OnNext` on the observer supplied by `SelectMany`, which will then call its observer, which was supplied thanks to the top-level call to `Subscribe` in the example. And that observer will just call the lambda we passed to `Subscribe`, causing our `Console.WriteLine` to run. After that returns, the nested `Range` operator will schedule another work item to generate its second item. Again, the `CurrentThreadScheduler` will realise that it's already in the middle of handling a work item on this thread, so it just puts it in the queue and then returns immediately from `Schedule`. The nested `Range` operator is now done for this iteration so it returns back to the scheduler. The scheduler will now pick up the next item in the queue, which in this case is the work item added by the top-level `Range` to produce the second item. + +And so it continues. This queuing of work items when work is already in progress is what enables multiple observable sources to make progress in parallel. + +By contrast, the `ImmediateScheduler` runs new work items immediately, which is why we don't see this parallel progress. + +(To be strictly accurate, there are certain scenarios in which `ImmediateScheduler` can't run work immediately. In these iterative scenarios, it actually supplies a slightly different scheduler that the operators use to schedule all work after the first item, and this checks whether it's being asked to process multiple work items simultaneously. If it is, it falls back to a queuing strategy similar to `CurrentThreadScheduler`, except it's a queue local to the initial work item, instead of a per-thread queue. This prevents problems due to multithreading, and it also avoids stack overflows that would otherwise occur when an iterative operator schedules a new work item inside the handler for the current work item. Since the queue is not shared across all work in the thread, this still has the effect of ensuring that any nested work queued up by a work item completes before the call to `Schedule` returns. So even when this queueing kicks in, we typically don't see interleaving of work from separate sources like we do with `CurrentThreadScheduler`. For example, if we told the nested `Range` to use `ImmediateScheduler`, this queueing behaviour would kick in as `Range` starts to iterate, but because the queue is local to initial work item executed by that nested `Range`, it will end up producing all of the nested `Range` items before returning.) + +### DefaultScheduler + +The `DefaultScheduler` is intended for work that may need to be spread out over time, or where you are likely to want concurrent execution. These features mean that this can't guarantee to run work on any particular thread, and in practice it schedules work via the CLR's thread pool. This is the default scheduler for all of Rx's time-based operators, and also for the `Observable.ToAsync` operator that can wrap a .NET method as an `IObservable`. + +Although this scheduler is useful if you would prefer work not to happen on your current thread—perhaps you're writing an application with a user interface and you prefer to avoid doing too much work on the thread responsible for updating the UI and responding to user input—the fact that it can end up running work on any thread may make like complicated. What if you want all the work to happen on one thread, just not the thread you're on now? There's another scheduler for that. + +### EventLoopScheduler + +The `EventLoopScheduler` provides one-at-a-time scheduling, queuing up newly scheduled work items. This is similar to how the `CurrentThreadScheduler` operates if you use it from just one thread. The difference is that `EventLoopScheduler` creates a dedicated thread for this work instead of using whatever thread you happen to schedule the work from. + +Unlike the schedulers we've examined so far, there is no static property for obtaining an `EventLoopScheduler`. That's because each one has its own thread, so you need to create one explicitly. It offers two constructors: + +```cs +public EventLoopScheduler() +public EventLoopScheduler(Func threadFactory) +``` + +The first creates a thread for you. The second lets you control the thread creation process. It invokes the callback you supply, and it will pass this its own callback that you are required to run on the newly created thread. + +The `EventLoopScheduler` implements `IDisposable`, and calling Dispose will allow the thread to terminate. This can work nicely with the `Observable.Using` method. The following example shows how to use an `EventLoopScheduler` to iterate over all contents of an `IEnumerable` on a dedicated thread, ensuring that the thread exits once we have finished: + +```cs +IEnumerable xs = GetNumbers(); +Observable + .Using( + () => new EventLoopScheduler(), + scheduler => xs.ToObservable(scheduler)) + .Subscribe(...); +``` + +### NewThreadScheduler + +The `NewThreadScheduler` creates a new thread to execute every work item it is given. This is unlikely to make sense in most scenarios. However, it might be useful in cases where you want to execute some long running work, and represent its completion through an `IObservable`. The `Observable.ToAsync` does exactly this, and will normally use the `DefaultScheduler`, meaning it will run the work on a thread pool thread. But if the work is likely to take more than second or two, the thread pool may not be a good choice, because it is optimized for short execution times, and its heuristics for managing the size of the thread pool are not designed with long-running operations in mind. The `NewThreadScheduler` may be a better choice in this case. + +Although each call to `Schedule` creates a new thread, the `NewThreadScheduler` passes a different scheduler into work item callbacks, meaning that anything that attempts to perform iterative work will not create a new thread for every iteration. For example, if you use `NewThreadScheduler` with `Observable.Range`, you will get a new thread each time you subscribe to the resulting `IObservable`, but you won't get a new thread for each item, even though `Range` does schedule a new work item for each value it produces. It schedules these per-value work items through the nested scheduler supplied to the work item callback, and the nested scheduler that `NewThreadScheduler` supplies in these cases invokes all such nested work items on the same thread. + +### SynchronizationContextScheduler + +This invokes all work through a [`SynchronizationContext`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext). This is useful in user interface scenarios. Most .NET client-side user interface frameworks make a `SynchronizationContext` available that can be used to invoke callbacks in a context suitable for making changes to the UI. (Typically this involves invoking them on the correct thread, but individual implementations can decide what constitutes the appropriate context.) + +### TaskPoolScheduler + +Invokes all work via the thread pool using [TPL tasks](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl). The TPL was introduced many years after the CLR thread pool, and is now the recommended way to launch work via the thread pool. At the time the TPL was added, the thread pool would use a slightly different algorithm when you scheduled work through tasks than it would use if you relied on the older thread pool APIs. This newer algorithm enabled it to be more efficient in some scenarios. The documentation is now rather vague about this, so it's not clear whether these differences still exist on modern .NET, but tasks continue to be the recommended mechanism for using the thread pool. Rx's DefaultScheduler uses the older CLR thread pool APIs for backwards compatibility reasons. In performance critical code you could try using the `TaskPoolScheduler` instead in cases where a lot of work is being run on thread pool threads to see if it offers any performance benefits for your workload. + +### ThreadPoolScheduler + +Invokes all work through the thread pool using the old pre-[TPL](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl) API. This type is a historical artifact, dating back to when not all platforms offered the same kind of thread pool. In almost all cases, if you want the behaviour for which this type was designed, you should use the `DefaultScheduler` (although [`TaskPoolScheduler`](#taskpoolscheduler) offers a different behaviour that might be). The only scenario in which using `ThreadPoolScheduler` makes any difference is when writing UWP applications. The UWP target of `System.Reactive` v6.0 provides a different implementation of this class than you get for all other targets. It uses `Windows.System.Threading.ThreadPool` whereas all other targets use `System.Threading.ThreadPool`. The UWP version provides properties letting you configure some features specific to the UWP thread pool. + +In practice it's best to avoid this class in new code. The only reason the UWP target had a different implementation was that UWP used not to provide `System.Threading.ThreadPool`. But that changed when UWP added support for .NET Standard 2.0 in Windows version 10.0.19041. There is no longer any good reason for there to be a UWP-specific `ThreadPoolScheduler`, and it's a source of confusion that this type is quite different in the UWP target but it has to remain for backwards compatibility purposes. (It may well be deprecated because Rx 7 will be addressing some problems arising from the fact that the `System.Reactive` component currently has direct dependencies on UI frameworks.) If you use the `DefaultScheduler` you will be using the `System.Threading.ThreadPool` no matter which platform you are running on. + +### UI Framework Schedulers: ControlScheduler, DispatcherScheduler and CoreDispatcherScheduler + +Although the `SynchronizationContextScheduler` will work for all widely used client-side UI frameworks in .NET, Rx offers more specialized schedulers. `ControlScheduler` is for Windows Forms applications, `DispatcherScheduler` for WPF, and `CoreDispatcherScheduler` for UWP. + +These more specialized types offer two benefits. First, you don't necessarily have to be on the target UI thread to obtain an instance of these schedulers. Whereas with `SynchronizationContextScheduler` the only way you can generally obtain the `SynchronizationContext` this requires is by retrieving `SynchronizationContext.Current` while running on the UI thread. But these other UI-framework-specific schedulers can be passed a suitable `Control`, `Dispatcher` or `CoreDispatcher`, which it's possible to obtain from a non-UI thread. Second, `DispatcherScheduler` and `CoreDispatcherScheduler` provide a way to use the prioritisation mechanism supported by the `Dispatcher` and `CoreDispatcher` types. + +### Test Schedulers + +The Rx libraries define several schedulers that virtualize time, including `HistoricalScheduler`, `TestScheduler`, `VirtualTimeScheduler`, and `VirtualTimeSchedulerBase`. We will look at this sort of scheduler in the [Testing chapter](16_TestingRx.md). + +## SubscribeOn and ObserveOn + +So far, I've talked about why some Rx sources need access to schedulers. This is necessary for timing-related behaviour, and also for sources that produce items as quickly as possible. But remember, schedulers control three things: + +- determining the context in which to execute work (e.g., a certain thread) +- deciding when to execute work (e.g., immediately, or deferred) +- keeping track of time + +The discussion so far as mostly focused on the 2nd and 3rd features. When it comes to our own application code, we are most likely to use schedulers to control that first aspect. Rx defines two extension methods to `IObservable` for this: `SubscribeOn` and `ObserveOn`. Both methods take an `IScheduler` and return an `IObservable` so you can chain more operators downstream of these. + +These methods do what their names suggest. If you use `SubscribeOn`, then when you call `Subscribe` on the resulting `IObservable` it arranges to call the original `IObservable`'s `Subscribe` method via the specified scheduler. Here's an example: + +```cs +Console.WriteLine($"[T:{Environment.CurrentManagedThreadId}] Main thread"); + +Observable + .Interval(TimeSpan.FromSeconds(1)) + .SubscribeOn(new EventLoopScheduler((start) => + { + Thread t = new(start) { IsBackground = false }; + Console.WriteLine($"[T:{t.ManagedThreadId}] Created thread for EventLoopScheduler"); + return t; + })) + .Subscribe( + tick => Console.WriteLine($"[T:{Environment.CurrentManagedThreadId}] {DateTime.Now}: Tick {tick}")); + +Console.WriteLine($"[T:{Environment.CurrentManagedThreadId}] {DateTime.Now}: Main thread exiting"); +``` + +This calls `Observable.Interval` (which uses `DefaultScheduler` by default), but instead of subscribing directly to this, it first takes the `IObservable` returned by `Interval` and invokes `SubscribeOn`. I've used an `EventLoopScheduler`, and I've passed it a factory callback for the thread that it will use to ensure that it is a non-background thread. (By default `EventLoopScheduler` creates itself a background thread, meaning that the thread won't force the process to stay alive. Normally that's what you'd want but I'm changing that in this example to show what's happening.) + +When I call `Subscribe` on the `IObservable` returned by `SubscribeOn`, it calls `Schedule` on the `EventLoopScheduler` that I supplied, and in the callback for that work item, it then calls `Subscribe` on the original `Interval` source. So the effect is that the subscription to the underlying source doesn't happen on my main thread, it happens on the thread created for my `EventLoopScheduler`. Running the program produces this output: + +``` +[T:1] Main thread +[T:12] Created thread for EventLoopScheduler +[T:1] 21/07/2023 14:57:21: Main thread exiting +[T:6] 21/07/2023 14:57:22: Tick 0 +[T:6] 21/07/2023 14:57:23: Tick 1 +[T:6] 21/07/2023 14:57:24: Tick 2 +... +``` + +Notice that my application's main thread exits before the source begins producing notifications. But also notice that the thread id for the newly created thread is 12, and yet my notifications are coming through on a different thread, with id 6! What's happening? + +This often catches people out. The scheduler on which you subscribe to an observable source doesn't necessarily have any impact on how the source behaves once it is up and running. Remember earlier that I said `Observable.Interval` uses `DefaultScheduler` by default? Well we've not specified a scheduler for the `Interval` here, so it will be using that default. It doesn't care what context we invoke its `Subscribe` method from. So really, the only effect of introducing the `EventLoopScheduler` here has been to keep the process alive even after its main thread exits. That scheduler thread never actually gets used again after it makes its initial `Subscribe` call into the `IObservable` returned by `Observable.Interval`. It just sits patiently waiting for further calls to `Schedule` that never come. + +Not all sources are completely unaffected by the context in which their `Subscribe` is invoked, though. If I were to replace this line: + +```cs + .Interval(TimeSpan.FromSeconds(1)) +``` + +with this: + +```cs + .Range(1, 5) +``` + +then we get this output: + +``` +[T:1] Main thread +[T:12] Created thread for EventLoopScheduler +[T:12] 21/07/2023 15:02:09: Tick 1 +[T:1] 21/07/2023 15:02:09: Main thread exiting +[T:12] 21/07/2023 15:02:09: Tick 2 +[T:12] 21/07/2023 15:02:09: Tick 3 +[T:12] 21/07/2023 15:02:09: Tick 4 +[T:12] 21/07/2023 15:02:09: Tick 5 +``` + +Now all the notifications are coming in on thread 12, the thread created for the `EventLoopScheduler`. Note that even here, `Range` isn't using that scheduler. The difference is that `Range` defaults to `CurrentThreadScheduler`, so it will generate its outputs from whatever thread you happen to call it from. So even though it's not actually using the `EventLoopScheduler`, it does end up using that scheduler's thread, because we used that scheduler to subscribe to the `Range`. + +So this illustrates that `SubscribeOn` is doing what it promises: it does determine the context from which `Subscribe` is invoked. It's just that it doesn't always matter what context that is. If `Subscribe` does non-trivial work, it can matter. For example, if you use [`Observable.Create`](03_CreatingObservableSequences.md#observablecreate) to create a custom sequence, `SubscribeOn` determines the context in which the callback you passed to `Create` is invoked. But Rx doesn't have a concept of a 'current' scheduler—there's no way to ask "which scheduler was I invoked from?"—so Rx operators don't just inherit their scheduler from the context on which they were subscribed. + +When it comes to emitting items, most of the sources Rx supplies fall into one of three categories. First, operators that produce outputs in response to inputs from an upstream source (e.g., `Where`, `Select`, or `GroupBy`) generally call their observers methods from inside their own `OnNext`. So whatever context their source observable was running in when it called `OnNext`, that's the context the operator will use when calling its observer. Second, operators that produce items either iteratively, or based on timing will use a scheduler (either explicitly supplied, or a default type when none is specified). Third, some sources just produce items from whatever context they like. For example, if an `async` method uses `await` and specifies `ConfigureAwait(false)` then it could be on more or less any thread and in any context after the `await` completes, and it might then go on to invoke `OnNext` on an observer. + +As long as a source follows [the fundamental rules of Rx sequences](02_KeyTypes.md#the-fundamental-rules-of-rx-sequences), it's allowed to invoke its observer's methods from any context it likes. It can choose to accept a scheduler as input and to use that, but it's under no obligation to. And if you have an unruly source of this kind that you'd like to tame, that's where the `ObserveOn` extension method comes in. Consider the following rather daft example: + +```cs +Observable + .Interval(TimeSpan.FromSeconds(1)) + .SelectMany(tick => Observable.Return(tick, NewThreadScheduler.Default)) + .Subscribe( + tick => Console.WriteLine($"{DateTime.Now}-{Environment.CurrentManagedThreadId}: Tick {tick}")); +``` + +This deliberately causes every notification to arrive on a different thread, as this output shows: + +``` +Main thread: 1 +21/07/2023 15:19:56-12: Tick 0 +21/07/2023 15:19:57-13: Tick 1 +21/07/2023 15:19:58-14: Tick 2 +21/07/2023 15:19:59-15: Tick 3 +... +``` + +(It's achieving this by calling `Observable.Return` for every single tick that emerges from `Interval`, and telling `Return` to use the `NewThreadScheduler`. Each such call to `Return` will create a new thread. This is a terrible idea, but it is an easy way to get a source that calls from a different context every time.) If I want to impose some order, I can add a call to `ObserveOn`: + +```cs +Observable + .Interval(TimeSpan.FromSeconds(1)) + .SelectMany(tick => Observable.Return(tick, NewThreadScheduler.Default)) + .ObserveOn(new EventLoopScheduler()) + .Subscribe( + tick => Console.WriteLine($"{DateTime.Now}-{Environment.CurrentManagedThreadId}: Tick {tick}")); +``` + +I've created an `EventLoopScheduler` here because it creates a single thread, and runs every scheduled work item on that thread. The output now shows the same thread id (13) every time: + +``` +Main thread: 1 +21/07/2023 15:24:23-13: Tick 0 +21/07/2023 15:24:24-13: Tick 1 +21/07/2023 15:24:25-13: Tick 2 +21/07/2023 15:24:26-13: Tick 3 +... +``` + +So although each new observable created by `Observable.Return` creates a brand new thread, `ObserveOn` ensures that my observer's `OnNext` (and `OnCompleted` or `OnError` in cases where those are called) is invoked via the specified scheduler. + +### SubscribeOn and ObserveOn in UI applications + +If you're using Rx in a user interface, `ObserveOn` is useful when you are dealing with information sources that don't provide notifications on the UI thread. You can wrap any `IObservable` with `ObserveOn`, passing a `SynchronizationContextScheduler` (or a framework-specific type such as `DispatcherScheduler`), to ensure that your observer receives notifications on the UI thread, making it safe to update the UI. + +`SubscribeOn` can also be useful in user interfaces as a way to ensure that any initialization work that an observable source does to get started does not happen on the UI thread. + +Most UI frameworks designate one particular thread for receiving notifications from the user and also for updating the UI, for any one window. It is critical to avoid blocking this UI thread, as doing so leads to a poor user experience—if you are doing work on the UI thread, it will be unavailable for responding to user input until that work is done. As a general rule, if you cause a user interface to become unresponsive for longer than 100ms, users will become irritated, so you should not be perform any work that will take longer than this on the UI thread. When Microsoft first introduced its application store (which came in with Windows 8) they specified an even more stringent limit: if your application blocked the UI thread for longer than 50ms, it might not be allowed into the store. With the processing power offered by modern processors, you can achieve a lot of processing 50ms. Even on the relatively low-powered processors in mobile devices that's long enough to execute millions of instructions. However, anything involving I/O (reading or writing files, or waiting for a response from any kind of network service) should not be done on the UI thread. The general pattern for creating responsive UI applications is: + +- receive a notification about some sort of user action +- if slow work is required, do this on a background thread +- pass the result back to the UI thread +- update the UI + +This is a great fit for Rx: responding to events, potentially composing multiple events, passing data to chained method calls. With the inclusion of scheduling, we even have the power to get off and back onto the UI thread for that responsive application feel that users demand. + +Consider a WPF application that used Rx to populate an `ObservableCollection`. You could use `SubscribeOn` to ensure that the main work was not done on the UI thread, followed by `ObserveOn` to ensure you were notified back on the correct thread. If you failed to use the `ObserveOn` method, then your `OnNext` handlers would be invoked on the same thread that raised the notification. In most UI frameworks, this would cause some sort of not-supported/cross-threading exception. In this example, we subscribe to a sequence of `Customers`. I'm using `Defer` so that if `GetCustomers` does any slow initial work before returning its `IObservable`, that won't happen until we subscribe. We then use `SubscribeOn` to call that method and perform the subscription on a task pool thread. Then we ensure that as we receive `Customer` notifications, we add them to the `Customers` collection on the `Dispatcher`. + +```cs +Observable + .Defer(() => _customerService.GetCustomers()) + .SubscribeOn(TaskPoolScheduler.Default) + .ObserveOn(DispatcherScheduler.Instance) + .Subscribe(Customers.Add); +``` + +Rx also offers `SubscribeOnDispatcher()` and `ObserveOnDispatcher()` extension methods to `IObservable`, that automatically use the current thread's `Dispatcher` (and equivalents for `CoreDispatcher`). While these might be slightly more convenient they can make it harder to test your code. We explain why in the [Testing Rx](16_TestingRx.md) chapter. + +## Concurrency pitfalls + +Introducing concurrency to your application will increase its complexity. If your application is not noticeably improved by adding a layer of concurrency, then you should avoid doing so. Concurrent applications can exhibit maintenance problems with symptoms surfacing in the areas of debugging, testing and refactoring. + +The common problem that concurrency introduces is unpredictable timing. Unpredictable timing can be caused by variable load on a system, as well as variations in system configurations (e.g. varying core clock speed and availability of processors). These can ultimately can result in [deadlocks](http://en.wikipedia.org/wiki/Deadlock), [livelocks](http://en.wikipedia.org/wiki/Deadlock#Livelock) and corrupted state. + +A particularly significant danger of introducing concurrency to an application is that you can silently introduce bugs. Bugs arising from unpredictable timing are notoriously difficult to detect, making it easy for these kinds of defects to slip past Development, QA and UAT and only manifest themselves in Production environments. Rx, however, does such a good job of simplifying the concurrent processing of observable sequences that many of these concerns can be mitigated. You can still create problems, but if you follow the guidelines then you can feel a lot safer in the knowledge that you have heavily reduced the capacity for unwanted race conditions. + +In a later chapter, [Testing Rx](16_TestingRx.md), we will look at how Rx improves your ability to test concurrent workflows. + +### Lock-ups + +Rx can simplify handling of concurrency, but it is not immune deadlock. Some calls (like `First`, `Last`, `Single` and `ForEach`) are blocking—they do not return until something that they are waiting for occurs. The following example shows that this makes it very easy for deadlock to occur: + +```cs +var sequence = new Subject(); + +Console.WriteLine("Next line should lock the system."); + +IEnumerable value = sequence.First(); +sequence.OnNext(1); + +Console.WriteLine("I can never execute...."); +``` + +The `First` method will not return until its source emits a sequence. But the code that causes this source to emit sequence is on the line _after_ the call to `First`. So the source can't emit a sequence until `First` returns. This style of deadlock, with two parties, each unable to proceed until the other proceeds, is often known as a _deadly embrace_. As this code shows, it's entirely possible for a deadly embrace to occur even in single threaded code. In fact, the single threaded nature of this code is what enables deadlock: we have two operations (waiting for the first notification, and sending the first notification) and only a single thread. That doesn't have to be a problem. If we'd used `FirstAsync` and attached an observer to that, `FirstAsync` would have executed its logic when the source `Subject` invoked its `OnNext`. But that is more complex than just calling `First` and assigning the result into a variable. + +This is an oversimplified example to illustrate the behaviour, and we would never write such code in production. (And even if we did, it fails so quickly and consistently that we would immediately become aware of a problem.) But in real application code, these kinds of problems can be harder to spot. Race conditions often slip into the system at integration points, so the problem isn't necessarily evidence in any one piece of code: timing problems can emerge as a result of how we plug multiple pieces of code together. + +The next example may be a little harder to detect, but is only small step away from our first, unrealistic example. The basic idea is that we've got a subject that represents button clicks in a user interface. Event handlers representing user input are invoked by the UI framework. We just provide the framework with event handler methods, and it calls them for us whenever the event of interest, such as a button being clicked, occurs. This code calls `First` on the subject representing clicks, but it's less obvious that this might cause a problem here than it was in the preceding example: + +```cs +public Window1() +{ + InitializeComponent(); + DataContext = this; + Value = "Default value"; + + // Deadlock! We need the dispatcher to continue to allow me to click the button to produce a value + Value = _subject.First(); + + // This will have the intended effect, but because it does not block, + // we can call this on the UI thread without deadlocking. + //_subject.FirstAsync(1).Subscribe(value => Value = value); +} + +private void MyButton_Click(object sender, RoutedEventArgs e) +{ + _subject.OnNext("New Value"); +} + +public string Value +{ + get { return _value; } + set + { + _value = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value")); + } +} +``` + +The earlier example called the subject's `OnNext` after `First` returned, making it relatively straightforward to see that if `First` didn't return, then the subject wouldn't emit a notification. But that's not as obvious here. The `MyButton_Click` event handler will be set up inside the call to `InitializeComponent` (as is normal in WPF code), so apparently we've done the necessary setup to enable events to flow. By the time we reach this call to `First`, the UI framework already knows that if the user clicks `MyButton`, it should call `MyButton_Click`, and that method is going to cause the subject to emit a value. + +There's nothing intrinsically wrong with that use of `First`. (Risky, yes, but there are scenarios in which that exact code would be absolutely fine.) The problem is the context in which we've used it. This code is in the constructor of a UI element, and these always run on a particular thread associated with that window's UI elements. (This happens to be a WPF example, but other UI frameworks work the same way.) And that's the same thread that the UI framework will use to deliver notifications about user input. If we block this UI thread, we prevent the UI framework from invoking our button click event handler. So this blocking call is waiting for an event that can only be raised from the very thread that it is blocking, thus creating a deadlock. + +You might be starting to get the impression that we should try to avoid blocking calls in Rx. This is a good rule of thumb. We can fix the code above by commenting out the line that uses `First`, and uncommenting the one below it containing this code: + +```cs +_subject.FirstAsync(1).Subscribe(value => Value = value); +``` + +This uses `FirstAsync` which does the same job, but with a different approach. It implements the same logic but it returns an `IObservable` to which we must subscribe if we want to receive the first value whenever it does eventually appear. It is more complex than the just assigning the result of `First` into the `Value` property, but it is better adapted to the fact that we can't know when that source will produce a value. + +If you do a lot of UI development, that last example might have seemed obviously wrong to you: we had code in the constructor for a window that wouldn't allow the constructor to complete until the user clicked a button in that window. The window isn't even going to appear until construction is complete so it makes no sense to wait for the user to click a button. That button's not even going to be visible on screen until after our constructor completes. Moreover, seasoned UI developers know that you don't just stop the world and wait for a specific action from the user. (Even modal dialogs, which effectively do demand a response before continuing, don't block the UI thread.) But as the next example shows, it's easy for problems to be harder to see. In this example, a button's click handler will try to get the first value from an observable sequence exposed via an interface. + +```cs +public partial class Window1 : INotifyPropertyChanged +{ + //Imagine DI here. + private readonly IMyService _service = new MyService(); + private int _value2; + + public Window1() + { + InitializeComponent(); + DataContext = this; + } + + public int Value2 + { + get { return _value2; } + set + { + _value2 = value; + var handler = PropertyChanged; + if (handler != null) handler(this, new PropertyChangedEventArgs(nameof(Value2))); + } + } + + #region INotifyPropertyChanged Members + public event PropertyChangedEventHandler PropertyChanged; + #endregion + + private void MyButton2_Click(object sender, RoutedEventArgs e) + { + Value2 = _service.GetTemperature().First(); + } +} +``` + +Unlike the earlier example, this does not attempt to block progress in the constructor. The blocking call to `First` occurs here in a button click handler (the `MyButton2_Click` method near the end). This example is more interesting because this sort of thing isn't necessarily wrong. Applications often perform blocking operations in click handlers: when we click a button to save a copy of a document, we expect the application to perform all necessary IO work to write our data out to storage. With modern solid state storage devices, this often happens so quickly as to appear instantaneous, but back in the days when mechanical hard drives were the norm, it was not unusual for an application to become briefly unresponsive while it saved our document. This can happen even today if your storage is remote, and networking issues are causing delays. + +So even if we've learned to be suspicious of blocking operations such as `First`, it's possible that it's OK in this example. It's not possible to tell for certain by looking at this code alone. It all depends on what sort of an observable `GetTemperature` returns, and the manner in which it produces its items. That call to `First` will block on the UI thread until a first item becomes available, so this will produce a deadlock if the production of that first item requires access to the UI thread. Here's a slightly contrived way to create that problem: + +```cs +class MyService : IMyService +{ + public IObservable GetTemperature() + { + return Observable.Create( + o => + { + o.OnNext(27); + o.OnNext(26); + o.OnNext(24); + return () => { }; + }) + .SubscribeOnDispatcher(); + } +} +``` + +This fakes up behaviour intended to simulate an actual temperature sensor by making a series of calls to `OnNext`. But it does some odd explicit scheduling: it calls `SubscribeOnDispatcher`. That's an extension method that effectively calls `SubscribeOn(DispatcherScheduler.Current.Dispatcher)`. This effectively tells Rx that when something tries to subscribe to the `IObservable` that `GetTemperature` returns, that subscription call should be done through a WPF-specific scheduler that runs its work items on the UI thread. (Strictly, speaking, WPF does allow multiple UI threads, so to more precise, this code only works if you call it on a UI thread, and if you do so, the scheduler will ensure that work items are scheduled onto the same UI thread.) + +The effect is that when our click handler calls `First`, that will in turn subscribe to the `IObservable` returned by `GetTemperature`, and because that used `SubscribeOnDispatcher`, this does not invoke the callback passed to `Observable.Create` immediately. Instead, it schedules a work item that will do that when the UI thread (i.e., the thread we're running on) becomes free. It's not considered to be free right now, because it's in the middle of handling the button click. Having handed this work item to the scheduler, the `Subscribe` call returns back to the `First` method. And the `First` method now sits and waits for the first item to emerge. Since it won't return until that happens, the UI thread will not be considered to be available until that happens, meaning that the scheduled work item that was supposed to produce that first item can never run, and we have deadlock. + +This boils down to the same basic problem as the first of these `First`-related deadlock examples. We have two processes: the generation of items, and waiting for an item to occur. These need to be in progress concurrently—we need the "wait for first item" logic to be up and running at the point when the source emits its first item. These examples all use just a single thread, which makes it a bad idea to use a single blocking call (`First`) both to set up the process of watching for the first item, and also to wait for that to happen. But even though it was the same basic problem in all three cases, it became harder to see as the code became more complex. With real application code, it's often a lot harder than this to see the root causes of deadlocks. + +So far, this chapter may seem to say that concurrency is all doom and gloom by focusing on the problems you could face, and the fact that they are often hard to spot in practice; this is not the intent though. +Although adopting Rx can't magically avoid classic concurrency problems, Rx can make it easier to get it right, provided you follow these two rules. + +- Only the top-level subscriber should make scheduling decisions +- Avoid using blocking calls: e.g. `First`, `Last` and `Single` + +The last example came unstuck with one simple problem; the `GetTemperature` service was dictating the scheduling model when, really, it had no business doing so. Code representing a temperature sensor shouldn't need to know that I'm using a particular UI framework, and certainly shouldn't be unilaterally deciding that it is going to run certain work on a WPF user interface thread. + +When getting started with Rx, it can be easy to convince yourself that baking scheduling decisions into lower layers is somehow being 'helpful'. "Look!" you might say. "Not only have I provided temperature readings, I've also made this automatically notify you on the UI thread, so you won't have to bother with `ObserveOn`." The intentions may be good, but it's all too easy to create a threading nightmare. + +Only the code that sets up a subscription and consumes its results can have a complete overview of the concurrency requirements, so that is the right level at which to choose which schedulers to use. Lower levels of code should not try to get involved; they should just do what they are told. (Rx arguably breaks this rule slightly itself by choosing default schedulers where they are needed. But it makes very conservative choices designed to minimize the chances of deadlock, and always allows applications to take control by specifying the scheduler.) + +Note that following either one of the two rules above would have been sufficient to prevent deadlock in this example. But it is best to follow both rules. + +This does leave one question unanswered: _how_ should the top-level subscriber make scheduling decisions? I've identified the area of the code that needs to make the decision, but what should the decision be? It will depend on the kind of application you are writing. For UI code, this pattern generally works well: "Subscribe on a background thread; Observe on the UI thread". With UI code, the risk of deadlock arises in because the UI thread is effectively a shared resource, and contention for that resource can produce deadlock. So the strategy is to avoid requiring that resource as much as possible: work that doesn't need to be on the thread should not be on that thread, which is why performing subscription on a worker thread (e.g., by using the `TaskPoolScheduler`) reduces the risk of deadlock. + +It follows that if you have observable sources that decide when to produce events (e.g., timers, or sources representing inputs from external information feeds or devices) you would also want those to schedule work on worker threads. It is only when we need to update the user interface that we need our code to run on the UI thread, and so we defer that until the last possible moment by using `ObserveOn` in conjunction with a suitable UI-aware scheduler (such as the WPF `DispatcherScheduler`). If we have a complex Rx query made up out of multiple operators, this `ObserveOn` should come right at the end, just before we call `Subscribe` to attach the handler that will update the UI. This way, only the final step, the updating of the UI, will need access to the UI thread. By the time this runs, all complex processing will be complete, and so this should be able to run very quickly, relinquishing control of the UI thread almost immediately, improving application responsiveness, and lowering the risk of deadlock. + +Other scenarios will require other strategies, but the general principle with deadlocks is always the same: understand which shared resources require exclusive access. For example, if you have a sensor library, it might create a dedicated thread to monitor devices and report new measurements, and if it were to stipulate that certain work had to be done on that thread, this would be very similar to the UI scenario: there is a particular thread that you will need to avoid blocking. The same approach would likely apply here. But this is not the only kind of scenario. + +You could imagine a data processing application in which certain data structures are shared. It's quite common in these cases to be allowed to access such data structures from any thread, but to be required to do so one thread at a time. Typically we would use thread synchronization primitives to protect against concurrent use of these critical data structures. In these cases, the risks of deadlock do not arise from the use of particular threads. Instead, they arise from the possibility that one thread can't progress because some other thread is using a shared data structure, but that other thread is waiting for the first thread to do something, and won't relinquish its lock on that data structure until that happens. The simplest way to avoid problems here is to avoid blocking wherever possible. Avoid methods like `First`, preferring their non-blocking equivalents such as `FirstAsync`. (If there are cases where you can't avoid blocking, try to avoid doing so while in possession of locks that guard access to shared data. And if you really can't avoid that either, then there are no simple answers. You'll now have to start thinking about lock hierarchies to systematically avoid deadlock, just as you would if you weren't using Rx.) The non-blocking style is the natural way to do things with Rx, and that's the main way Rx can help you avoid concurrency related problems in these cases. + +## Advanced features of schedulers + +Schedulers provide some features that are mainly of interest when writing observable sources that need to interact with a scheduler. The most common way to use schedulers is when setting up a subscription, either supplying them as arguments when creating observable sources, or passing them to `SubscribeOn` and `ObserveOn`. But if you need to write an observable source that produces items on some schedule of its own choosing (e.g., suppose you are writing a library that represents some external data source and you want to present that as an `IObservable`), you might need to use some of these more advanced features. + +### Passing state + +All of the methods defined by `IScheduler` take a `state` argument. Here's the interface definition again: + +```cs +public interface IScheduler +{ + DateTimeOffset Now { get; } + IDisposable Schedule(TState state, Func action); + IDisposable Schedule(TState state, TimeSpan dueTime, Func action); + IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action); +} +``` + +The scheduler does not care what is in this `state` argument. It just passes it unmodified into your callback when it executes your work item. This provides one way to provide context for that callback. It's not strictly necessary: the delegate we pass as the `action` can incorporate whatever state we need. The easiest way to do that is to capture variables in a lambda. However, if you look at the [Rx source code](https://github.com/dotnet/reactive/) you will find that it typically doesn't do that. For example, the heart of the `Range` operator is a method called `LoopRec` and if you look at [the source for `LoopRec`](https://github.com/dotnet/reactive/blob/95d9ea9d2786f6ec49a051c5cff47dc42591e54f/Rx.NET/Source/src/System.Reactive/Linq/Observable/Range.cs#L55-L73) you'll see that it includes this line: + +```cs +var next = scheduler.Schedule(this, static (innerScheduler, @this) => @this.LoopRec(innerScheduler)); +``` + +Logically, `Range` is just a loop that executes once for each item it produces. But to enable concurrent execution and to avoid stack overflows, it implements this by scheduling each iteration of the loop as an individual work item. (The method is called `LoopRec` because it is logically a recursive loop: it is kicked off by calling `Schedule`, and each time the scheduler calls this method, it calls `Schedule` again to ask for the next item to run. This doesn't actually cause recursion with any of Rx's built-in schedulers, even the `ImmediateScheduler`, because they all detect this and arrange to run the next item after the current one returns. But if you wrote the most naive scheduler possible, this would actually end up recursing at runtime, likely leading to stack overflows if you tried to create a large sequence.) + +Notice that the lambda passed to `Schedule` has been annotated with `static`. This tells the C# compiler that it is our intention _not_ to capture any variables, and that any attempt to do so should cause a compiler error. The advantage of this is that the compiler is able to generate code that reuses the same delegate instance for every call. The first time this runs, it will create a delegate and store it in a hidden field. On every subsequent execution of this (either in future iterations of the same range, or for completely new range instances) it can just use that same delegate again and again and again. This is possible because the delegate captures no state. This avoids allocating a new object each time round the loop. + +Couldn't the Rx library have used a more straightforward approach? We could choose not to use the state, passing a `null` state to scheduler, and then discarding the state argument passed to our callback: + +```cs +// Less weird, but less efficient: +var next = scheduler.Schedule(null, (innerScheduler, _) => LoopRec(innerScheduler)); +``` + +This avoids the previous example's weirdness of passing our own `this` argument: now we're just invoking the `LoopRec` instance member in the ordinary way: we're implicitly using the `this` reference that is in scope. So this will create a delegate the captures that implicit `this` reference. This works, but it's inefficient: it will force the compiler to generate code that allocates a couple of objects. It creates one object that has a field holding onto the captured `this`, and then it needs to create a distinct delegate instance that has a reference to that capture object. + +The more complex code that is actually in the `Range` implementation avoids this. It disables capture by annotating the lambda with `static`. That prevents code from relying on the implicit `this` reference. So it has had to arrange for the `this` reference to be available to the callback. And that's exactly the sort of thing the `state` argument is there for. It provides a way to pass in some per-work-item state so that you can avoid the overhead of capturing variables on each iteration. + +### Future scheduling + +I talked earlier about time-based operators, and also about the two time-based members of `ISchedule` that enable this, but I've not yet shown how to use it. These enable you to schedule an action to be executed in the future. (This relies on the process continuing to run for as long as necessary. As mentioned in earlier chapters, `System.Reactive` doesn't support persistent, durable subscriptions. So if you want to schedule something for days into the future, you might want to look at [Reaqtor](https://reaqtive.net/).) You can do so by specifying the exact point in time an action should be invoked by calling the overload of `Schedule` that takes a `DateTimeOffset`, or you can specify the period of time to wait until the action is invoked with the `TimeSpan`-based overload. + +You can use the `TimeSpan` overload like this: + +```cs +var delay = TimeSpan.FromSeconds(1); +Console.WriteLine("Before schedule at {0:o}", DateTime.Now); + +scheduler.Schedule(delay, () => Console.WriteLine("Inside schedule at {0:o}", DateTime.Now)); +Console.WriteLine("After schedule at {0:o}", DateTime.Now); +``` + +Output: + +``` +Before schedule at 2012-01-01T12:00:00.000000+00:00 +After schedule at 2012-01-01T12:00:00.058000+00:00 +Inside schedule at 2012-01-01T12:00:01.044000+00:00 +``` + +This illustrates that scheduling was non-blocking here, because the 'before' and 'after' calls are very close together in time. (It will be this way for most schedulers, but as discussed earlier, `ImmediateScheduler` works differently. In this case, you would see the After message after the Inside one. that's why none of the timed operators will use `ImmediateScheduler` by default.) You can also see that approximately one second after the action was scheduled, it was invoked. + +You can specify a specific point in time to schedule the task with the `DateTimeOffset` overload. If, for some reason, the point in time you specify is in the past, then the action is scheduled as soon as possible. Be aware that changes in the system clock complicate matters. Rx's schedulers do make some accommodations to deal with clock drift, but sudden large changes to the system clock can cause some short term chaos. + +### Cancellation + +Each of the overloads to `Schedule` returns an `IDisposable`, and calling `Dispose` on this will cancel the scheduled work. In the previous example, we scheduled work to be invoked in one second. We could cancel that work by disposing of the return value. + +```cs +var delay = TimeSpan.FromSeconds(1); +Console.WriteLine("Before schedule at {0:o}", DateTime.Now); + +var workItem = scheduler.Schedule(delay, () => Console.WriteLine("Inside schedule at {0:o}", DateTime.Now)); +Console.WriteLine("After schedule at {0:o}", DateTime.Now); + +workItem.Dispose(); +``` + +Output: + +``` +Before schedule at 2012-01-01T12:00:00.000000+00:00 +After schedule at 2012-01-01T12:00:00.058000+00:00 +``` + +Note that the scheduled action never occurred, because we cancelled it almost immediately. + +When the user cancels the scheduled action method before the scheduler is able to invoke it, that action is just removed from the queue of work. This is what we see in example above. It's possible to cancel scheduled work that is already running, and this is why the work item callback is required to return `IDisposable`: if work has already begun when you try to cancel the work item, Rx calls `Dispose` on the `IDisposable` that your work item callback returned. This gives a way for users to cancel out of a job that may already be running. This job could be some sort of I/O, heavy computations or perhaps usage of `Task` to perform some work. + +You may be wondering how this mechanism can be any use: the work item callback needs to have returned already for Rx to be able to invoke the `IDisposable` that it returns. This mechanism can only be used in practice if work continues after returning to the scheduler. You could fire up another thread so the work happens concurrently, although we generally try to avoid creating threads in Rx. Another possibility would be if the scheduled work item invoked some asynchronous API and returned without waiting for it to complete. If that API offered cancellation, you could return an `IDisposable` that cancelled it. + +To illustrate cancellation in operation, this slightly unrealistic example runs some work as a `Task` to enable it to continue after our callback returns. It just fakes some work by performing a spin wait and adding values to the `list` argument. The key here is that we create a `CancellationToken` to be able to tell the task we want it to stop, and we return an `IDisposable` that puts this token in to a cancelled state. + +```cs +public IDisposable Work(IScheduler scheduler, List list) +{ + CancellationTokenSource tokenSource = new(); + CancellationToken cancelToken = tokenSource.Token; + Task task = new(() => + { + Console.WriteLine(); + + for (int i = 0; i < 1000; i++) + { + SpinWait sw = new(); + + for (int j = 0; j < 3000; j++) sw.SpinOnce(); + + Console.Write("."); + + list.Add(i); + + if (cancelToken.IsCancellationRequested) + { + Console.WriteLine("Cancellation requested"); + + // cancelToken.ThrowIfCancellationRequested(); + + return; + } + } + }, cancelToken); + + task.Start(); + + return Disposable.Create(tokenSource.Cancel); +} +``` + +This code schedules the above code and allows the user to cancel the processing work by pressing Enter + +```cs +List list = new(); +Console.WriteLine("Enter to quit:"); + +IDisposable token = scheduler.Schedule(list, Work); +Console.ReadLine(); + +Console.WriteLine("Cancelling..."); + +token.Dispose(); + +Console.WriteLine("Cancelled"); +``` + +Output: + +``` +Enter to quit: +........ +Cancelling... +Cancelled +Cancellation requested +``` + +The problem here is that we have introduced explicit use of `Task` so we are increasing concurrency in a way that is outside of the control of the scheduler. The Rx library generally allows control over the way in which concurrency is introduced by accepting a scheduler parameter. If the goal is to enable long-running iterative work, we can avoid having to spin up new threads or tasks but using Rx recursive scheduler features instead. I already talked a bit about this in the [Passing state](#passing-state) section, but there are a few ways to go about it. + +### Recursion + +In addition to the `IScheduler` methods, Rx defines various overloads of `Schedule` in the form of extension methods. Some of these take some strange looking delegates as parameters. Take special note of the final parameter in each of these overloads of the `Schedule` extension method. + +```cs +public static IDisposable Schedule( + this IScheduler scheduler, + Action action) +{...} + +public static IDisposable Schedule( + this IScheduler scheduler, + TState state, + Action> action) +{...} + +public static IDisposable Schedule( + this IScheduler scheduler, + TimeSpan dueTime, + Action> action) +{...} + +public static IDisposable Schedule( + this IScheduler scheduler, + TState state, + TimeSpan dueTime, + Action> action) +{...} + +public static IDisposable Schedule( + this IScheduler scheduler, + DateTimeOffset dueTime, + Action> action) +{...} + +public static IDisposable Schedule( + this IScheduler scheduler, + TState state, DateTimeOffset dueTime, + Action> action) +{...} +``` + +Each of these overloads take a delegate "action" that allows you to call "action" recursively. This may seem a very odd signature, but it allows us to achieve a similar logically recursive iterative approach as you saw in [Passing state](#passing-state) section, but in a potentially simpler way. + +This example uses the simplest recursive overload. We have an `Action` that can be called recursively. + +```cs +Action work = (Action self) => +{ + Console.WriteLine("Running"); + self(); +}; + +var token = s.Schedule(work); + +Console.ReadLine(); +Console.WriteLine("Cancelling"); + +token.Dispose(); + +Console.WriteLine("Cancelled"); +``` + +Output: + +``` +Enter to quit: +Running +Running +Running +Running +Cancelling +Cancelled +Running +``` + +Note that we didn't have to write any cancellation code in our delegate. Rx handled the looping and checked for cancellation on our behalf. Since each individual iteration was scheduled as a separate work item, there are no long-running jobs, so it's enough to let the scheduler deal entirely with cancellation. + +The main difference between these overloads, and using the `IScheduler` methods directly, is that you don't need to pass another callback directly into the scheduler. You just invoke the supplied `Action` and it schedules another call to your method. They also enable you not to pass a state argument if you don't have any use for one. + +As mentioned in the earlier section, although this logically represents recursion, Rx protects us from stack overflows. The schedulers implement this style of recursion by waiting for the method to return before performing the recursive call. + +This concludes our tour of scheduling and threading. Next, we will look at the related topic of timing. \ No newline at end of file diff --git a/content/12_CombiningSequences.md b/content/12_CombiningSequences.md deleted file mode 100644 index 448d532..0000000 --- a/content/12_CombiningSequences.md +++ /dev/null @@ -1,735 +0,0 @@ ---- -title: Combining sequences ---- - -# Combining sequences - -Data sources are everywhere, and sometimes we need to consume data from more than just a single source. Common examples that have many inputs include: multi touch surfaces, news feeds, price feeds, social media aggregators, file watchers, heart-beating/polling servers, etc. The way we deal with these multiple stimuli is varied too. We may want to consume it all as a deluge of integrated data, or one sequence at a time as sequential data. We could also get it in an orderly fashion, pairing data values from two sources to be processed together, or perhaps just consume the data from the first source that responds to the request. - -We have uncovered the benefits of operator composition; now we turn our focus to sequence composition. Earlier on, we briefly looked at operators that work with multiple sequences such as `SelectMany`, `TakeUntil`/`SkipUntil`, `Catch` and `OnErrorResumeNext`. These give us a hint at the potential that sequence composition can deliver. By uncovering the features of sequence composition with Rx, we find yet another layer of game changing functionality. Sequence composition enables you to create complex queries across multiple data sources. This unlocks the possibility to write some very powerful and succinct code. - -Now we will build upon the concepts covered in the [Advanced Error Handling](11_AdvancedErrorHandling.html) chapter. There we were able to provide continuations for sequences that failed. We will now examine operators aimed at composing sequences that are still operational instead of sequences that have terminated due to an error. - -## Sequential concatenation - -The first methods we will look at are those that concatenate sequences sequentially. They are very similar to the methods we have seen before for dealing with faulted sequences. - -### Concat - -The `Concat` extension method is probably the most simple composition method. It simply concatenates two sequences. Once the first sequence completes, the second sequence is subscribed to and its values are passed on through to the result sequence. It behaves just like the `Catch` extension method, but will concatenate operational sequences when they complete, instead of faulted sequences when they `OnError`. The simple signature for `Concat` is as follows. - -// Concatenates two observable sequences. Returns an observable sequence that contains the -// elements of the first sequence, followed by those of the second the sequence. -public static IObservable Concat( - this IObservable first, - IObservable second) -{ - ... -} - -Usage of `Concat` is familiar. Just like `Catch` or `OnErrorResumeNext`, we pass the continuation sequence to the extension method. - -```csharp -// Generate values 0,1,2 -var s1 = Observable.Range(0, 3); -// Generate values 5,6,7,8,9 -var s2 = Observable.Range(5, 5); -s1.Concat(s2).Subscribe(Console.WriteLine); -``` - -Returns: - -``` -s1 --0--1--2-| -s2 -5--6--7--8--| -r --0--1--2--5--6--7--8--| -``` - -If either sequence was to fault so too would the result sequence. In particular, if `s1` produced an `OnError` notification, then `s2` would never be used. If you wanted `s2` to be used regardless of how s1 terminates, then `OnErrorResumeNext` would be your best option. - -`Concat` also has two useful overloads. These overloads allow you to pass multiple observable sequences as either a `params` array or an `IEnumerable>`. - -```csharp -public static IObservable Concat( - params IObservable[] sources) -{...} - -public static IObservable Concat( - this IEnumerable> sources) -{...} -``` - -The ability to pass an `IEnumerable>` means that the multiple sequences can be lazily evaluated. The overload that takes a `params` array is well-suited to times when we know how many sequences we want to merge at compile time, whereas the `IEnumerable>` overload is a better fit when we do not know this ahead of time. - -In the case of the lazily evaluated `IEnumerable>`, the `Concat` method will take one sequence, subscribe until it is completed and then switch to the next sequence. To help illustrate this, we create a method that returns a sequence of sequences and is sprinkled with logging. It returns three observable sequences each with a single value [1], [2] and [3]. Each sequence returns its value on a timer delay. - -```csharp -public IEnumerable> GetSequences() -{ - Console.WriteLine("GetSequences() called"); - Console.WriteLine("Yield 1st sequence"); - - yield return Observable.Create(o => - { - Console.WriteLine("1st subscribed to"); - return Observable.Timer(TimeSpan.FromMilliseconds(500)) - .Select(i=>1L) - .Subscribe(o); - }); - - Console.WriteLine("Yield 2nd sequence"); - - yield return Observable.Create(o => - { - Console.WriteLine("2nd subscribed to"); - return Observable.Timer(TimeSpan.FromMilliseconds(300)) - .Select(i=>2L) - .Subscribe(o); - }); - - Thread.Sleep(1000); // Force a delay - - Console.WriteLine("Yield 3rd sequence"); - - yield return Observable.Create(o => - { - Console.WriteLine("3rd subscribed to"); - return Observable.Timer(TimeSpan.FromMilliseconds(100)) - .Select(i=>3L) - .Subscribe(o); - }); - - Console.WriteLine("GetSequences() complete"); -} -``` - -When we call our `GetSequences` method and concatenate the results, we see the following output using our `Dump` extension method. - -```csharp -GetSequences().Concat().Dump("Concat"); -``` - -Output: - -``` -GetSequences() called -Yield 1st sequence -1st subscribed to -Concat-->1 -Yield 2nd sequence -2nd subscribed to -Concat-->2 -Yield 3rd sequence -3rd subscribed to -Concat-->3 -GetSequences() complete -Concat completed -``` - -Below is a marble diagram of the `Concat` operator applied to the `GetSequences` method. 's1', 's2' and 's3' represent sequence 1, 2 and 3. Respectively, 'rs' represents the result sequence. - -``` -s1-----1| -s2 ---2| -s3 -3| -rs-----1---2-3| -``` - -You should note that the second sequence is only yielded once the first sequence has completed. To prove this, we explicitly put in a 500ms delay on producing a value and completing. Once that happens, the second sequence is then subscribed to. When that sequence completes, then the third sequence is processed in the same fashion. - -### Repeat - -Another simple extension method is `Repeat`. It allows you to simply repeat a sequence, either a specified or an infinite number of times. - -```csharp -// Repeats the observable sequence indefinitely and sequentially. -public static IObservable Repeat( - this IObservable source) -{...} - -//Repeats the observable sequence a specified number of times. -public static IObservable Repeat( - this IObservable source, - int repeatCount) -{...} -``` - -If you use the overload that loops indefinitely, then the only way the sequence will stop is if there is an error or the subscription is disposed of. The overload that specifies a repeat count will stop on error, un-subscription, or when it reaches that count. This example shows the sequence [0,1,2] being repeated three times. - -```csharp -var source = Observable.Range(0, 3); -var result = source.Repeat(3); - -result.Subscribe( - Console.WriteLine, - () => Console.WriteLine("Completed")); -``` - -Output: - -``` -0 -1 -2 -0 -1 -2 -0 -1 -2 -Completed -``` - -### StartWith - -Another simple concatenation method is the `StartWith` extension method. It allows you to prefix values to a sequence. The method signature takes a `params` array of values so it is easy to pass in as many or as few values as you need. - -```csharp -// prefixes a sequence of values to an observable sequence. -public static IObservable StartWith( - this IObservable source, - params TSource[] values) -{ - ... -} -``` - -Using `StartWith` can give a similar effect to a `BehaviorSubject` by ensuring a value is provided as soon as a consumer subscribes. It is not the same as a `BehaviorSubject` however, as it will not cache the last value. - -In this example, we prefix the values -3, -2 and -1 to the sequence [0,1,2]. - -```csharp -//Generate values 0,1,2 -var source = Observable.Range(0, 3); -var result = source.StartWith(-3, -2, -1); - -result.Subscribe( - Console.WriteLine, - () => Console.WriteLine("Completed")); -``` -Output: - -``` --3 --2 --1 -0 -1 -2 -Completed -``` - -## Concurrent sequences - -The next set of methods aims to combine observable sequences that are producing values concurrently. This is an important step in our journey to understanding Rx. For the sake of simplicity, we have avoided introducing concepts related to concurrency until we had a broad understanding of the simple concepts. - -### Amb - -The `Amb` method was a new concept to me when I started using Rx. It is a non-deterministic function, first introduced by John McCarthy and is an abbreviation of the word _Ambiguous_. The Rx implementation will return values from the sequence that is first to produce values, and will completely ignore the other sequences. In the examples below I have three sequences that all produce values. The sequences can be represented as the marble diagram below. - -``` -s1 -1--1--| -s2 --2--2--| -s3 ---3--3--| -r -1--1--| -``` - -The code to produce the above is as follows. - -```csharp -var s1 = new Subject(); -var s2 = new Subject(); -var s3 = new Subject(); - -var result = Observable.Amb(s1, s2, s3); - -result.Subscribe( - Console.WriteLine, - () => Console.WriteLine("Completed")); - -s1.OnNext(1); -s2.OnNext(2); -s3.OnNext(3); -s1.OnNext(1); -s2.OnNext(2); -s3.OnNext(3); -s1.OnCompleted(); -s2.OnCompleted(); -s3.OnCompleted(); -``` - -Output: - -``` -1 -1 -Completed -``` - -If we comment out the first `s1.OnNext(1);` then s2 would produce values first and the marble diagram would look like this. - -``` -s1 ---1--| -s2 -2--2--| -s3 --3--3--| -r -2--2--| -``` - -The `Amb` feature can be useful if you have multiple cheap resources that can provide values, but latency is widely variable. For an example, you may have servers replicated around the world. Issuing a query is cheap for both the client to send and for the server to respond, however due to network conditions the latency is not predictable and varies considerably. Using the `Amb` operator, you can send the same request out to many servers and consume the result of the first that responds. - -There are other useful variants of the `Amb` method. We have used the overload that takes a `params` array of sequences. You could alternatively use it as an extension method and chain calls until you have included all the target sequences (e.g. s1.Amb(s2).Amb(s3)). Finally, you could pass in an `IEnumerable>`. - -```csharp -// Propagates the observable sequence that reacts first. -public static IObservable Amb( - this IObservable first, - IObservable second) -{...} -public static IObservable Amb( - params IObservable[] sources) -{...} -public static IObservable Amb( - this IEnumerable> sources) -{...} -``` - -Reusing the `GetSequences` method from the `Concat` section, we see that the evaluation of the outer (IEnumerable) sequence is eager. - -```csharp -GetSequences().Amb().Dump("Amb"); -``` - -Output: - -``` -GetSequences() called -Yield 1st sequence -Yield 2nd sequence -Yield 3rd sequence -GetSequences() complete -1st subscribed to -2nd subscribed to -3rd subscribed to -Amb-->3 -Amb completed -``` - -Marble: - -``` -s1-----1| -s2---2| -s3-3| -rs-3| -``` - -Take note that the inner observable sequences are not subscribed to until the outer sequence has yielded them all. This means that the third sequence is able to return values the fastest even though there are two sequences yielded one second before it (due to the `Thread.Sleep`). - -### Merge - -The `Merge` extension method does a primitive combination of multiple concurrent sequences. As values from any sequence are produced, those values become part of the result sequence. All sequences need to be of the same type, as per the previous methods. In this diagram, we can see `s1` and `s2` producing values concurrently and the values falling through to the result sequence as they occur. - -``` -s1 --1--1--1--| -s2 ---2---2---2| -r --12-1-21--2| -``` - -The result of a `Merge` will complete only once all input sequences complete. By contrast, the `Merge` operator will error if any of the input sequences terminates erroneously. - -```csharp -// Generate values 0,1,2 -var s1 = Observable.Interval(TimeSpan.FromMilliseconds(250)) - .Take(3); - -// Generate values 100,101,102,103,104 -var s2 = Observable.Interval(TimeSpan.FromMilliseconds(150)) - .Take(5) - .Select(i => i + 100); - -s1.Merge(s2) - .Subscribe( - Console.WriteLine, - ()=>Console.WriteLine("Completed")); -``` - -The code above could be represented by the marble diagram below. In this case, each unit of time is 50ms. As both sequences produce a value at 750ms, there is a race condition and we cannot be sure which value will be notified first in the result sequence (sR). - -``` -s1 ----0----0----0| -s2 --0--0--0--0--0| -sR --0-00--00-0--00| -``` - -Output: - -``` -100 -0 -101 -102 -1 -103 -104 // Note this is a race condition. 2 could be -2 // published before 104. -``` - -You can chain this overload of the `Merge` operator to merge multiple sequences. `Merge` also provides numerous other overloads that allow you to pass more than two source sequences. You can use the static method `Observable.Merge` which takes a `params` array of sequences that is known at compile time. You could pass in an `IEnumerable` of sequences like the `Concat` method. `Merge` also has the overload that takes an `IObservable>`, a nested observable. To summarize: - -- Chain `Merge` operators together e.g. `s1.Merge(s2).Merge(s3)` -- Pass a `params` array of sequences to the `Observable.Merge` static method. e.g. `Observable.Merge(s1,s2,s3)` -- Apply the `Merge` operator to an `IEnumerable>`. -- Apply the `Merge` operator to an `IObservable>`. - -Merge overloads: - -```csharp -/// Merges two observable sequences into a single observable sequence. -/// Returns a sequence that merges the elements of the given sequences. -public static IObservable Merge( - this IObservable first, - IObservable second) -{...} - -// Merges all the observable sequences into a single observable sequence. -// The observable sequence that merges the elements of the observable sequences. -public static IObservable Merge( - params IObservable[] sources) -{...} - -// Merges an enumerable sequence of observable sequences into a single observable sequence. -public static IObservable Merge( - this IEnumerable> sources) -{...} - -// Merges an observable sequence of observable sequences into an observable sequence. -// Merges all the elements of the inner sequences in to the output sequence. -public static IObservable Merge( - this IObservable> sources) -{...} -``` - -For merging a known number of sequences, the first two operators are effectively the same thing and which style you use is a matter of taste: either provide them as a `params` array or chain the operators together. The third and fourth overloads allow to you merge sequences that can be evaluated lazily at run time. The `Merge` operators that take a sequence of sequences make for an interesting concept. You can either pull or be pushed observable sequences, which will be subscribed to immediately. - -If we again reuse the `GetSequences` method, we can see how the `Merge` operator works with a sequence of sequences. - -```csharp -GetSequences().Merge().Dump("Merge"); -``` - -Output: - -``` -GetSequences() called -Yield 1st sequence -1st subscribed to -Yield 2nd sequence -2nd subscribed to -Merge --> 2 -Merge --> 1 -Yield 3rd sequence -3rd subscribed to -GetSequences() complete -Merge --> 3 -Merge completed -``` - -As we can see from the marble diagram, s1 and s2 are yielded and subscribed to immediately. s3 is not yielded for one second and then is subscribed to. Once all input sequences have completed, the result sequence completes. - -``` -s1-----1| -s2---2| -s3 -3| -rs---2-1-----3| -``` - -### Switch - -Receiving all values from a nested observable sequence is not always what you need. In some scenarios, instead of receiving everything, you may only want the values from the most recent inner sequence. A great example of this is live searches. As you type, the text is sent to a search service and the results are returned to you as an observable sequence. Most implementations have a slight delay before sending the request so that unnecessary work does not happen. Imagine I want to search for "Intro to Rx". I quickly type in "Into to" and realize I have missed the letter 'r'. I stop briefly and change the text to "Intro ". By now, two searches have been sent to the server. The first search will return results that I do not want. Furthermore, if I were to receive data for the first search merged together with results for the second search, it would be a very odd experience for the user. This scenario fits perfectly with the `Switch` method. - -In this example, there is a source that represents a sequence of search text. Values the user types are represented as the source sequence. Using `Select`, we pass the value of the search to a function that takes a `string` and returns an `IObservable`. This creates our resulting nested sequence, `IObservable>`. - -Search function signature: - -```csharp -private IObservable SearchResults(string query) -{ - ... -} -``` - -Using `Merge` with overlapping search: - -```csharp -IObservable searchValues = ....; -IObservable> search = searchValues.Select(searchText=>SearchResults(searchText)); - -var subscription = search - .Merge() - .Subscribe(Console.WriteLine); -``` - - - -If we were lucky and each search completed before the next element from `searchValues` was produced, the output would look sensible. It is much more likely, however that multiple searches will result in overlapped search results. This marble diagram shows what the `Merge` function could do in such a situation. - -- `SV` is the searchValues sequence -- `S1` is the search result sequence for the first value in searchValues/SV -- `S2` is the search result sequence for the second value in searchValues/SV -- `S3` is the search result sequence for the third value in searchValues/SV -- `RM` is the result sequence for the merged (`R`esult `M`erge) sequences - -``` -SV--1---2---3---| -S1 -1--1--1--1| -S2 --2-2--2--2| -S3 -3--3| -RM---1--1-2123123-2| -``` - -Note how the values from the search results are all mixed together. This is not what we want. If we use the `Switch` extension method we will get much better results. `Switch` will subscribe to the outer sequence and as each inner sequence is yielded it will subscribe to the new inner sequence and dispose of the subscription to the previous inner sequence. This will result in the following marble diagram where `RS` is the result sequence for the Switch (`R`esult `S`witch) sequences - -``` -SV--1---2---3---| -S1 -1--1--1--1| -S2 --2-2--2--2| -S3 -3--3| -RS --1--1-2-23--3| -``` - -Also note that, even though the results from S1 and S2 are still being pushed, they are ignored as their subscription has been disposed of. This eliminates the issue of overlapping values from the nested sequences. - -## Pairing sequences - -The previous methods allowed us to flatten multiple sequences sharing a common type into a result sequence of the same type. These next sets of methods still take multiple sequences as an input, but attempt to pair values from each sequence to produce a single value for the output sequence. In some cases, they also allow you to provide sequences of different types. - -### CombineLatest - -The `CombineLatest` extension method allows you to take the most recent value from two sequences, and with a given function transform those into a value for the result sequence. Each input sequence has the last value cached like `Replay(1)`. Once both sequences have produced at least one value, the latest output from each sequence is passed to the `resultSelector` function every time either sequence produces a value. The signature is as follows. - -```csharp -// Composes two observable sequences into one observable sequence by using the selector -// function whenever one of the observable sequences produces an element. -public static IObservable CombineLatest( - this IObservable first, - IObservable second, - Func resultSelector) -{...} -``` - -The marble diagram below shows off usage of `CombineLatest` with one sequence that produces numbers (N), and the other letters (L). If the `resultSelector` function just joins the number and letter together as a pair, this would be the result (R): - -``` -N---1---2---3--- -L--a------bc---- -R---1---2-223--- - a a bcc -``` - -If we slowly walk through the above marble diagram, we first see that `L` produces the letter 'a'. `N` has not produced any value yet so there is nothing to pair, no value is produced for the result (R). Next, `N` produces the number '1' so we now have a pair '1a' that is yielded in the result sequence. We then receive the number '2' from `N`. The last letter is still 'a' so the next pair is '2a'. The letter 'b' is then produced creating the pair '2b', followed by 'c' giving '2c'. Finally the number 3 is produced and we get the pair '3c'. - -This is great in case you need to evaluate some combination of state which needs to be kept up-to-date when the state changes. A simple example would be a monitoring system. Each service is represented by a sequence that returns a Boolean indicating the availability of said service. The monitoring status is green if all services are available; we can achieve this by having the result selector perform a logical AND. -Here is an example. - -```csharp -IObservable webServerStatus = GetWebStatus(); -IObservable databaseStatus = GetDBStatus(); - -// Yields true when both systems are up. -var systemStatus = webServerStatus - .CombineLatest( - databaseStatus, - (webStatus, dbStatus) => webStatus && dbStatus); -``` - -Some readers may have noticed that this method could produce a lot of duplicate values. For example, if the web server goes down the result sequence will yield '`false`'. If the database then goes down, another (unnecessary) '`false`' value will be yielded. This would be an appropriate time to use the `DistictUntilChanged` extension method. The corrected code would look like the example below. - -```csharp -// Yields true when both systems are up, and only on change of status -var systemStatus = webServerStatus - .CombineLatest( - databaseStatus, - (webStatus, dbStatus) => webStatus && dbStatus) - .DistinctUntilChanged(); -``` - -To provide an even better service, we could provide a default value by prefixing `false` to the sequence. - -```csharp -// Yields true when both systems are up, and only on change of status -var systemStatus = webServerStatus - .CombineLatest( - databaseStatus, - (webStatus, dbStatus) => webStatus && dbStatus) - .DistinctUntilChanged() - .StartWith(false); -``` - -### Zip - -The `Zip` extension method is another interesting merge feature. Just like a zipper on clothing or a bag, the `Zip` method brings together two sequences of values as pairs; two by two. Things to note about the `Zip` function is that the result sequence will complete when the first of the sequences complete, it will error if either of the sequences error and it will only publish once it has a pair of fresh values from each source sequence. So if one of the source sequences publishes values faster than the other sequence, the rate of publishing will be dictated by the slower of the two sequences. - -```csharp -// Generate values 0,1,2 -var nums = Observable.Interval(TimeSpan.FromMilliseconds(250)) - .Take(3); - -// Generate values a,b,c,d,e,f -var chars = Observable.Interval(TimeSpan.FromMilliseconds(150)) - .Take(6) - .Select(i => Char.ConvertFromUtf32((int)i + 97)); - -// Zip values together -nums.Zip(chars, (lhs, rhs) => new { Left = lhs, Right = rhs }) - .Dump("Zip"); -``` - -This can be seen in the marble diagram below. Note that the result uses two lines so that we can represent a complex type, i.e. the anonymous type with the properties Left and Right. - -``` -nums ----0----1----2| -chars --a--b--c--d--e--f| -result----0----1----2| - a b c| -``` - -The actual output of the code: - -``` -{ Left = 0, Right = a } -{ Left = 1, Right = b } -{ Left = 2, Right = c } -``` - -Note that the `nums` sequence only produced three values before completing, while the `chars` sequence produced six values. The result sequence thus has three values, as this was the most pairs that could be made. - -The first use I saw of `Zip` was to showcase drag and drop. [The example](http://channel9.msdn.com/Blogs/J.Van.Gogh/Writing-your-first-Rx-Application) tracked mouse movements from a `MouseMove` event that would produce event arguments with its current X,Y coordinates. First, the example turns the event into an observable sequence. Then they cleverly zipped the sequence with a `Skip(1)` version of the same sequence. This allows the code to get a delta of the mouse position, i.e. where it is now (sequence.Skip(1)) minus where it was (sequence). It then applied the delta to the control it was dragging. - -To visualize the concept, let us look at another marble diagram. Here we have the mouse movement (MM) and the Skip 1 (S1). The numbers represent the index of the mouse movement. - -``` -MM --1--2--3--4--5 -S1 --2--3--4--5 -Zip --1--2--3--4 - 2 3 4 5 -``` - -Here is a code sample where we fake out some mouse movements with our own subject. - -```csharp -var mm = new Subject(); -var s1 = mm.Skip(1); - -var delta = mm.Zip(s1, - (prev, curr) => new Coord - { - X = curr.X - prev.X, - Y = curr.Y - prev.Y - }); - -delta.Subscribe( - Console.WriteLine, - () => Console.WriteLine("Completed")); - -mm.OnNext(new Coord { X = 0, Y = 0 }); -mm.OnNext(new Coord { X = 1, Y = 0 }); //Move across 1 -mm.OnNext(new Coord { X = 3, Y = 2 }); //Diagonally up 2 -mm.OnNext(new Coord { X = 0, Y = 0 }); //Back to 0,0 -mm.OnCompleted(); -``` - -This is the simple Coord(inate) class we use. - -```csharp -public class Coord -{ - public int X { get; set; } - public int Y { get; set; } - public override string ToString() - { - return string.Format("{0},{1}", X, Y); - } -} -``` - -Output: - -``` -0,1 -2,2 --3,-2 -Completed -``` - -It is also worth noting that `Zip` has a second overload that takes an `IEnumerable` as the second input sequence. - -```csharp -// Merges an observable sequence and an enumerable sequence into one observable sequence -// containing the result of pair-wise combining the elements by using the selector function. -public static IObservable Zip( - this IObservable first, - IEnumerable second, - Func resultSelector) -{...} -``` - -This allows us to zip sequences from both `IEnumerable` and `IObservable` paradigms! - -### And-Then-When - -If `Zip` only taking two sequences as an input is a problem, then you can use a combination of the three `And`/`Then`/`When` methods. These methods are used slightly differently from most of the other Rx methods. Out of these three, `And` is the only extension method to `IObservable`. Unlike most Rx operators, it does not return a sequence; instead, it returns the mysterious type `Pattern`. The `Pattern` type is public (obviously), but all of its properties are internal. The only two (useful) things you can do with a `Pattern` are invoking its `And` or `Then` methods. The `And` method called on the `Pattern` returns a `Pattern`. On that type, you will also find the `And` and `Then` methods. The generic `Pattern` types are there to allow you to chain multiple `And` methods together, each one extending the generic type parameter list by one. You then bring them all together with the `Then` method overloads. The `Then` methods return you a `Plan` type. Finally, you pass this `Plan` to the `Observable.When` method in order to create your sequence. - -It may sound very complex, but comparing some code samples should make it easier to understand. It will also allow you to see which style you prefer to use. - -To `Zip` three sequences together, you can either use `Zip` methods chained together like this: - -```csharp -var one = Observable.Interval(TimeSpan.FromSeconds(1)).Take(5); -var two = Observable.Interval(TimeSpan.FromMilliseconds(250)).Take(10); -var three = Observable.Interval(TimeSpan.FromMilliseconds(150)).Take(14); - -// lhs represents 'Left Hand Side' -// rhs represents 'Right Hand Side' -var zippedSequence = one - .Zip(two, (lhs, rhs) => new {One = lhs, Two = rhs}) - .Zip(three, (lhs, rhs) => new {One = lhs.One, Two = lhs.Two, Three = rhs}); - -zippedSequence.Subscribe( - Console.WriteLine, - () => Console.WriteLine("Completed")); -``` - -Or perhaps use the nicer syntax of the `And`/`Then`/`When`: - -```csharp -var pattern = one.And(two).And(three); -var plan = pattern.Then((first, second, third)=>new{One=first, Two=second, Three=third}); -var zippedSequence = Observable.When(plan); - -zippedSequence.Subscribe( - Console.WriteLine, - () => Console.WriteLine("Completed")); -``` - -This can be further reduced, if you prefer, to: - -```csharp -var zippedSequence = Observable.When( - one.And(two) - .And(three) - .Then((first, second, third) => - new { - One = first, - Two = second, - Three = third - }) - ); - -zippedSequence.Subscribe( - Console.WriteLine, - () => Console.WriteLine("Completed")); -``` - -The `And`/`Then`/`When` trio has more overloads that enable you to group an even greater number of sequences. They also allow you to provide more than one 'plan' (the output of the `Then` method). This gives you the `Merge` feature but on the collection of 'plans'. I would suggest playing around with them if this functionality is of interest to you. The verbosity of enumerating all of the combinations of these methods would be of low value. You will get far more value out of using them and discovering for yourself. - -As we delve deeper into the depths of what the Rx libraries provide us, we can see more practical usages for it. Composing sequences with Rx allows us to easily make sense of the multiple data sources a problem domain is exposed to. We can concatenate values or sequences together sequentially with `StartWith`, `Concat` and `Repeat`. We can process multiple sequences concurrently with `Merge`, or process a single sequence at a time with `Amb` and `Switch`. Pairing values with `CombineLatest`, `Zip` and the `And`/`Then`/`When` operators can simplify otherwise fiddly operations like our drag-and-drop examples and monitoring system status. \ No newline at end of file diff --git a/content/12_Timing.md b/content/12_Timing.md new file mode 100644 index 0000000..4c9a962 --- /dev/null +++ b/content/12_Timing.md @@ -0,0 +1,306 @@ +--- +title: Time-based sequences +--- + +# Time-based sequences + +With event sources, timing is often important. In some cases, the only information of interest about some event might be the time at which it occurred. The core `IObservable` and `IObserver` interfaces don't mention timing at all in their method signatures, but they don't need to, because a source can decide when it calls an observer's `OnNext` method. A subscriber knows when an event occurred because it is occurring right now. This isn't always the most convenient way in which to work with timing, so the Rx library provides some timing-related operators. We've already seen a couple of operators that offer optional time-based operation: [`Buffer`](./08_Partitioning.md#buffer) and [`Window`](08_Partitioning#window). This chapter looks at the various operators that are all about timing. + +## Timestamp and TimeInterval + +As observable sequences are asynchronous it can be convenient to know when elements are received. Obviously, a subscriber can always just use `DateTimeOffset.Now`, but if you want to refer to the arrival time as part of a larger query, the `Timestamp` extension method is a handy convenience method that attaches a timestamp to each element. It wraps elements from its source sequence in a light weight `Timestamped` structure. The `Timestamped` type is a struct that exposes the value of the element it wraps, and also a `DateTimeOffset` indicating when `Timestamp` operator received it. + +In this example we create a sequence of three values, one second apart, and then transform it to a time stamped sequence. + +```csharp +Observable.Interval(TimeSpan.FromSeconds(1)) + .Take(3) + .Timestamp() + .Dump("Timestamp"); +``` + +As you can see, `Timestamped`'s implementation of `ToString()` gives us a readable output. + +``` +Timestamp-->0@07/08/2023 10:03:58 +00:00 +Timestamp-->1@07/08/2023 10:03:59 +00:00 +Timestamp-->2@07/08/2023 10:04:00 +00:00 +TimeStamp completed +``` + +We can see that the values 0, 1, and 2 were each produced one second apart. + +Rx also offers `TimeInterval`. Instead of reporting the time at which items arrived, it reports the interval between items (or, in the case of the first element, how long it took for that to emerge after subscription). Similarly to the `Timestamp` method, elements are wrapped in a light weight structure. But whereas `Timestamped` adorned each item with the arrival time, `TimeInterval` wraps each element with the `TimeInterval` type which adds a `TimeSpan`. We can modify the previous example to use `TimeInterval`: + +```csharp +Observable.Interval(TimeSpan.FromSeconds(1)) + .Take(3) + .TimeInterval() + .Dump("TimeInterval"); +``` + +As you can see, the output now reports the time between elements instead of the time of day at which they were received: + +``` +Timestamp-->0@00:00:01.0183771 +Timestamp-->1@00:00:00.9965679 +Timestamp-->2@00:00:00.9908958 +Timestamp completed +``` + +As you can see from the output, the timings are not exactly one second but are pretty close. Some of this will be measurement noise in the `TimeInterval` operator, but most of this variability is likely to arise from the `Observable.Interval` class. There will always be a limit to the precision with which a scheduler can honour the timing request of it. Some scheduler introduce more variation than others. The schedulers that deliver work via a UI thread are ultimately limited by how quickly that thread's message loop responds. But even in the most favourable condition, schedulers are limited by the fact that .NET is not built for use in real-time systems (and nor are most of the operating systems Rx can be used on). So with all of the operators in this section, you should be aware that timing is always a _best effort_ affair in Rx. + +In fact, the inherent variations in timing can make `Timestamp` particularly useful. The problem with simply looking at `DateTimeOffset.Now` is that it takes a non-zero amount of time to process an event, so you'll likely see a slightly different time each time you try to read the current time during the processing of one event. By attaching a timestamp once, we capture the time at which the event was observed, and then it doesn't matter how much delay downstream processing adds. The event will be annotated with a single, fixed time indicating when it passed through `Timestamp`. + +## Delay + +The `Delay` extension method time-shifts an entire sequence. `Delay` attempts to preserve the relative time intervals between the values. There is inevitably a limit to the precision with which it can do this—it won't recreate timing down to the nearest nanosecond. The exact precision is determined by the scheduler you use, and will typically get worse under heavy load, but it will typically reproduce timings to within a few milliseconds. + +There are overloads of `Delay` offering various different ways to specify the time shift. (With all the options, you can optionally pass a scheduler, but if you call the overloads that don't take one, it defaults to [`DefaultScheduler`](11_SchedulingAndThreading.md#defaultscheduler).) The most straightforward is to pass a `TimeSpan`, which will delay the sequence by the specified amount. And there are also delays that accept a `DateTimeOffset` which will wait until the specified time occurs, and then start replaying the input. (This second, absolute time based approach is essentially equivalent to the `TimeSpan` overloads. You would get more or less the same effect by subtracting the current time from the target time to get a `TimeSpan`, except the `DateTimeOffset` version attempts to deal with changes in the system clock that occur between `Delay` being called, and the specified time arriving.) + +To show the `Delay` method in action, this example creates a sequence of values one second apart and timestamps them. This will show that it is not the subscription that is being delayed, but the actual forwarding of the notifications to our final subscriber. + +```cs +IObservable> source = Observable + .Interval(TimeSpan.FromSeconds(1)) + .Take(5) + .Timestamp(); + +IObservable> delay = source.Delay(TimeSpan.FromSeconds(2)); + +delay.Subscribe( + value => Console.WriteLine($"Item {value.Value} with timestamp {value.Timestamp} received at {DateTimeOffset.Now}"), + () => Console.WriteLine("delay Completed")); +``` + +If you look at the timestamps in the output, you can see that the times captured by `Timestamp` are all two seconds earlier than the time reported by the subscription: + +``` +Item 0 with timestamp 09/11/2023 17:32:20 +00:00 received at 09/11/2023 17:32:22 +00:00 +Item 1 with timestamp 09/11/2023 17:32:21 +00:00 received at 09/11/2023 17:32:23 +00:00 +Item 2 with timestamp 09/11/2023 17:32:22 +00:00 received at 09/11/2023 17:32:24 +00:00 +Item 3 with timestamp 09/11/2023 17:32:23 +00:00 received at 09/11/2023 17:32:25 +00:00 +Item 4 with timestamp 09/11/2023 17:32:24 +00:00 received at 09/11/2023 17:32:26 +00:00 +delay Completed +``` + +Note that `Delay` will not time-shift `OnError` notifications. These will be propagated immediately. + +## Sample + +The `Sample` method produces items at whatever interval you ask. Each time it produces a value, it reports the last value that emerged from your source. If you have a source that produces data at a higher rate than you need (e.g. suppose you have an accelerometer that reports 100 measurements per second, but you only need to take a reading 10 times a second), `Sample` provides an easy way to reduce the data rate. This example shows `Sample` in action. + +```csharp +IObservable interval = Observable.Interval(TimeSpan.FromMilliseconds(150)); +interval.Sample(TimeSpan.FromSeconds(1)).Subscribe(Console.WriteLine); +``` + +Output: + +``` +5 +12 +18 +``` + +If you looked at these numbers closely, you might have noticed that the interval between the values is not the same each time. I chose a source interval of 150ms and a sample interval of 1 second to highlight an aspect of sampling that can require careful handling: if the rate at which a source produces items doesn't line up neatly with the sampling rate, this can mean that `Sample` introduces irregularities that weren't present in the source. If we list the times at which the underlying sequence produces values, and the times at which `Sample` takes each value, we can see that with these particular timings, the sample intervals only line up with the source timings every 3 seconds. + +| Relative time (ms) | Source value | Sampled value | +| :----------------- | :----------- | :------------ | +| 0 | | | +| 50 | | | +| 100 | | | +| 150 | 0 | | +| 200 | | | +| 250 | | | +| 300 | 1 | | +| 350 | | | +| 400 | | | +| 450 | 2 | | +| 500 | | | +| 550 | | | +| 600 | 3 | | +| 650 | | | +| 700 | | | +| 750 | 4 | | +| 800 | | | +| 850 | | | +| 900 | 5 | | +| 950 | | | +| 1000 | | 5 | +| 1050 | 6 | | +| 1100 | | | +| 1150 | | | +| 1200 | 7 | | +| 1250 | | | +| 1300 | | | +| 1350 | 8 | | +| 1400 | | | +| 1450 | | | +| 1500 | 9 | | +| 1550 | | | +| 1600 | | | +| 1650 | 10 | | +| 1700 | | | +| 1750 | | | +| 1800 | 11 | | +| 1850 | | | +| 1900 | | | +| 1950 | 12 | | +| 2000 | | 12 | +| 2050 | | | +| 2100 | 13 | | +| 2150 | | | +| 2200 | | | +| 2250 | 14 | | +| 2300 | | | +| 2350 | | | +| 2400 | 15 | | +| 2450 | | | +| 2500 | | | +| 2550 | 16 | | +| 2600 | | | +| 2650 | | | +| 2700 | 17 | | +| 2750 | | | +| 2800 | | | +| 2850 | 18 | | +| 2900 | | | +| 2950 | | | +| 3000 | 19 | 19 | + +Since the first sample is taken after the source emits five, and two thirds of the way into the gap after which it will produce six, there's a sense in which the "right" current value is something like 5.67, but `Sample` doesn't attempt any such interpolation. It just reports the last value to emerge from the source. A related consequence is that if the sampling interval is short enough that you're asking `Sample` to report values faster than they are emerging from the source, it will just repeat values. + +## Throttle + +The `Throttle` extension method provides a sort of protection against sequences that produce values at variable rates and sometimes too quickly. Like the `Sample` method, `Throttle` will return the last sampled value for a period of time. Unlike `Sample` though, `Throttle`'s period is a sliding window. Each time `Throttle` receives a value, the window is reset. Only once the period of time has elapsed will the last value be propagated. This means that the `Throttle` method is only useful for sequences that produce values at a variable rate. Sequences that produce values at a constant rate (like `Interval` or `Timer`) would have all of their values suppressed if they produced values faster than the throttle period, whereas all of their values would be propagated if they produced values slower than the throttle period. + +```csharp +// Ignores values from an observable sequence which are followed by another value before +// dueTime. +public static IObservable Throttle( + this IObservable source, + TimeSpan dueTime) +{...} +public static IObservable Throttle( + this IObservable source, + TimeSpan dueTime, + IScheduler scheduler) +{...} +``` + +We could apply `Throttle` to use a live search feature that makes suggestions as you type. We would typically want to wait until the user has stopped typing for a bit before searching for suggestions, because otherwise, we might end up kicking off several searches in a row, cancelling the last one each time the user presses another key. Only once there is a pause should we can execute a search with what they have typed so far. `Throttle` fits well with this scenario, because it won't allow any events through at all if the source is producing values faster than the specified rate. + +Note that the RxJS library decided to make their version of throttle work differently, so if you ever find yourself using both Rx.NET and RxJS, be aware that they don't work the same way. In RxJS, throttle doesn't shut off completely when the source exceeds the specified rate: it just drops enough items that the output never exceeds the specified rate. So RxJS's throttle implementation is a kind of rate limiter, whereas Rx.NET's `Throttle` is more like a self-resetting circuit breaker that shuts off completely during an overload. + +## Timeout + +The `Timeout` operator method allows us terminate a sequence with an error if the source does not produce any notifications for a given period. We can either specify the period as a sliding window with a `TimeSpan`, or as an absolute time that the sequence must complete by providing a `DateTimeOffset`. + +```csharp +// Returns either the observable sequence or a TimeoutException if the maximum duration +// between values elapses. +public static IObservable Timeout( + this IObservable source, + TimeSpan dueTime) +{...} +public static IObservable Timeout( + this IObservable source, + TimeSpan dueTime, + IScheduler scheduler) +{...} + +// Returns either the observable sequence or a TimeoutException if dueTime elapses. +public static IObservable Timeout( + this IObservable source, + DateTimeOffset dueTime) +{...} +public static IObservable Timeout( + this IObservable source, + DateTimeOffset dueTime, + IScheduler scheduler) +{...} +``` + +If we provide a `TimeSpan` and no values are produced within that time span, then the sequence fails with a `TimeoutException`. + +```csharp +var source = Observable.Interval(TimeSpan.FromMilliseconds(100)).Take(5) + .Concat(Observable.Interval(TimeSpan.FromSeconds(2))); + +var timeout = source.Timeout(TimeSpan.FromSeconds(1)); +timeout.Subscribe( + Console.WriteLine, + Console.WriteLine, + () => Console.WriteLine("Completed")); +``` + +Initially this produces values frequently enough to satisfy `Timeout`, so the observable returned by `Timeout` just forwards items from the source. But once the source stops producing items, we get an OnError: + +``` +0 +1 +2 +3 +4 +System.TimeoutException: The operation has timed out. +``` + +Alternatively, we can pass `Timeout` an absolute time; if the sequence does not complete by that time, it produces an error. + +```csharp +var dueDate = DateTimeOffset.UtcNow.AddSeconds(4); +var source = Observable.Interval(TimeSpan.FromSeconds(1)); +var timeout = source.Timeout(dueDate); +timeout.Subscribe( + Console.WriteLine, + Console.WriteLine, + () => Console.WriteLine("Completed")); +``` + +Output: + +``` +0 +1 +2 +System.TimeoutException: The operation has timed out. +``` + +There are other `Timeout` overloads enabling us to substitute an alternative sequence when a timeout occurs. + +```csharp +// Returns the source observable sequence or the other observable sequence if the maximum +// duration between values elapses. +public static IObservable Timeout( + this IObservable source, + TimeSpan dueTime, + IObservable other) +{...} + +public static IObservable Timeout( + this IObservable source, + TimeSpan dueTime, + IObservable other, + IScheduler scheduler) +{...} + +// Returns the source observable sequence or the other observable sequence if dueTime +// elapses. +public static IObservable Timeout( + this IObservable source, + DateTimeOffset dueTime, + IObservable other) +{...} + +public static IObservable Timeout( + this IObservable source, + DateTimeOffset dueTime, + IObservable other, + IScheduler scheduler) +{...} +``` + +As we've now seen, Rx provides features to manage timing in a reactive paradigm. Data can be timed, throttled, or sampled to meet your needs. Entire sequences can be shifted in time with the delay feature, and timeliness of data can be asserted with the `Timeout` operator. + +Next we will look at the boundary between Rx and the rest of the world. \ No newline at end of file diff --git a/content/13_LeavingIObservable.md b/content/13_LeavingIObservable.md new file mode 100644 index 0000000..8f5e706 --- /dev/null +++ b/content/13_LeavingIObservable.md @@ -0,0 +1,510 @@ +--- +title: Leaving Rx's World +--- + +# Leaving Rx's World + +An observable sequence is a useful construct, especially when we have the power of LINQ to compose complex queries over it. Even though we recognize the benefits of the observable sequence, sometimes we must leave the `IObservable` paradigm. This is necessary when we need to integrate with an existing non-Rx-based API (e.g. one that uses events or `Task`). You might leave the observable paradigm if you find it easier for testing, or it may simply be easier for you to learn Rx by moving between an observable paradigm and a more familiar one. + +Rx's compositional nature is the key to its power, but it can look like a problem when you need to integrate with a component that doesn't understand Rx. Most of the Rx library features we've looked at so far express their inputs and outputs as observables. How are you supposed to take some real world source of events and turn that into an observable? How are you supposed to do something meaningful with the output of an observable? + +You've already seen some answer to these questions. The [Creating Observable Sequences chapter](03_CreatingObservableSequences.md) showed various ways to create observable sources. But when it comes to handling the items that emerge from an `IObservable`, all we've really seen is how to implement [`IObserver`](02_KeyTypes.md#iobserver), and [how to use the callback based `Subscribe` extension methods to subscribe to an `IObservable`](02_KeyTypes.md#iobservable). + +In this chapter, we will look at the methods in Rx which allow you to leave the `IObservable` world, so you can take action based on the notifications that emerge from an Rx source. + +## Integration with `async` and `await` + +You can use C#'s `await` keyword with any `IObservable`. We saw this earlier with [`FirstAsync`](05_Filtering.md#blocking-versions-of-firstlastsingleordefault): + +```cs +long v = await Observable.Timer(TimeSpan.FromSeconds(2)).FirstAsync(); +Console.WriteLine(v); +``` + +Although `await` is most often used with `Task`, `Task`, or `ValueTask`, it is actually an extensible language feature. It's possible to make `await` work for more or less any type by supplying a method called `GetAwaiter`, typically as an extension method, and a suitable type for `GetAwaiter` to return, providing C# with the features `await` requires. That's precisely what Rx does. If your source file includes a `using System.Reactive.Linq;` directive, a suitable extension method will be available, so you can `await` any task. + +The way this actually works is that the relevant `GetAwaiter` extension method wraps the `IObservable` in an `AsyncSubject`, which provides everything that C# requires to support `await`. These wrappers work in such a way that there will be a call to `Subscribe` each time you execute an `await` against an `IObservable`. + +If a source reports an error by invoking its observer's `OnError`, Rx's `await` integration handles this by putting the task into a faulted state, so that the `await` will rethrow the exception. + +Sequences can be empty. They might call `OnCompleted` without ever having called `OnNext`. However, since there's no way to tell from the type of a source that it will be empty, this doesn't fit especially well with the `await` paradigm. With tasks, you can know at compile time whether you'll get a result by looking at whether you're awaiting a `Task` or `Task`, so the compiler is able to know whether a particular `await` expression produces a value. But when you `await` and `IObservable`, there's no compile-time distinction, so the only way Rx can report that a sequence is empty when you `await` is to throw an `InvalidOperationException` reporting that the sequence contains no elements. + +As you may recall from the [`AsyncSubject` section of chapter 3](./03_CreatingObservableSequences.md#asyncsubject), `AsyncSubject` reports only the final value to emerge from its source. So if you `await` a sequence that reports multiple items, all but the final item will be ignored. What if you want to see all of the items, but you'd still like to use `await` to handle completion and errors? + +## ForEachAsync + +The `ForEachAsync` method supports `await`, but it provides a way to process each element. You could think of it as a hybrid of the `await` behaviour described in the preceding section, and the callback-based `Subscribe`. We can still use `await` to detect completion and errors, but we supply a callback enabling us to handle every item: + +```cs +IObservable source = Observable.Interval(TimeSpan.FromSeconds(1)).Take(5); +await source.ForEachAsync(i => Console.WriteLine($"received {i} @ {DateTime.Now}")); +Console.WriteLine($"finished @ {DateTime.Now}"); +``` + +Output: + +``` +received 0 @ 02/08/2023 07:53:46 +received 1 @ 02/08/2023 07:53:47 +received 2 @ 02/08/2023 07:53:48 +received 3 @ 02/08/2023 07:53:49 +received 4 @ 02/08/2023 07:53:50 +finished @ 02/08/2023 07:53:50 +``` + +Note that the `finished` line is last, as you would expect. Let's compare this with the `Subscribe` extension method, which also lets us provide a single callback for handling items: + +```cs +IObservable source = Observable.Interval(TimeSpan.FromSeconds(1)) + .Take(5); +source.Subscribe(i => Console.WriteLine($"received {i} @ {DateTime.Now}")); +Console.WriteLine($"finished @ {DateTime.Now}"); +``` + +As the output shows, `Subscribe` returned immediately. Our per-item callback was invoked just like before, but this all happened later on: + +``` +finished @ 02/08/2023 07:55:42 +received 0 @ 02/08/2023 07:55:43 +received 1 @ 02/08/2023 07:55:44 +received 2 @ 02/08/2023 07:55:45 +received 3 @ 02/08/2023 07:55:46 +received 4 @ 02/08/2023 07:55:47 +``` + +This can be useful in batch-style programs that perform some work and then exit. The problem with using `Subscribe` in that scenario is that our program could easily exit without having finished the work it started. This is easy to avoid with `ForEachAsync` because we just use `await` to ensure that our method doesn't complete until the work is done. + +When we use `await` either directly against an `IObservable`, or through `ForEachAsync`, we are essentially choosing to handle sequence completion in a conventional way, not a reactive way. Error and completion handling are no longer callback driven—Rx supplies the `OnCompleted` and `OnError` handlers for us, and instead represents these through C#'s awaiter mechanism. (Specifically, when we `await` a source directly, Rx supplies a custom awaiter, and when we use `ForEachAsync` it just returns a `Task`.) + +Note that there are some circumstances in which `Subscribe` will block until its source completes. [`Observable.Return`](03_CreatingObservableSequences.md#observablereturn) will do this by default, as will [`Observable.Range`](03_CreatingObservableSequences.md#observablerange). We could try to make the last example do it by specifying a different scheduler: + +```cs +// Don't do this! +IObservable source = Observable.Interval(TimeSpan.FromSeconds(1), ImmediateScheduler.Instance) + .Take(5); +source.Subscribe(i => Console.WriteLine($"received {i} @ {DateTime.Now}")); +Console.WriteLine($"finished @ {DateTime.Now}"); +``` + +However, this highlights the dangers of non-async blocking calls: although this looks like it should work, in practice it deadlocks in the current version of Rx. Rx doesn't consider the `ImmediateScheduler` to be suitable for timer-based operations, which is why it's not the default, and this scenario is a good example of why that is. (The fundamental issue is that the only way to cancel a scheduled work item is to call `Dispose` on the object returned by the call to `Schedule`. `ImmediateScheduler` by definition doesn't return until after it has finished the work, meaning it effectively can't support cancellation. So the call to `Interval` effectively creates a periodically scheduled work item that can't be cancelled, and which is therefore doomed to run forever.) + +This is why we need `ForEachAsync`. It might look like we can get the same effect through clever use of schedulers, but in practice if you need to wait for something asynchronous to happen, it's always better to use `await` than to use an approach that entails blocking the calling thread. + +## ToEnumerable + +The two mechanisms we've explored so far convert completion and error handling from Rx's callback mechanism to a more conventional approach enabled by `await`, but we still had to supply a callback to be able to handle every individual item. But the `ToEnumerable` extension method goes a step further: it enables the entire sequence to be consumed with a conventional `foreach` loop: + +```cs +var period = TimeSpan.FromMilliseconds(200); +IObservable source = Observable + .Timer(TimeSpan.Zero, period) + .Take(5); + +IEnumerable result = source.ToEnumerable(); + +foreach (long value in result) +{ + Console.WriteLine(value); +} + +Console.WriteLine("done"); +``` + +Output: + +``` +0 +1 +2 +3 +4 +done +``` + +The source observable sequence will be subscribed to when you start to enumerate the sequence (i.e. lazily). +If no elements are available yet, or you have consumed all elements produced so far, the call that `foreach` makes to the enumerator's `MoveNext` will block until the source produces an element. So this approach relies on the source being able to generate elements from some other thread. (In this example, `Timer` defaults to the [`DefaultScheduler`](11_SchedulingAndThreading.md#defaultscheduler), which runs timed work on the thread pool.) If the sequence produces values faster than you consume them, they will be queued for you. (This means that it is technically possible to consume and generate items on the same thread when using `ToEnumerable` but this would rely on the producer always remaining ahead. This would be a dangerous approach because if the `foreach` loop ever caught up, it would then deadlock.) + +As with `await` and `ForEachAsync`, if the source reports an error, this will be thrown, so you can use ordinary C# exception handling as this example shows: + +```cs +try +{ + foreach (long value in result) + { + Console.WriteLine(value); + } +} +catch (Exception e) +{ + Console.WriteLine(e.Message); +} +``` + +## To a single collection + +Sometimes you will want all of the items a source produces as a single list. For example, perhaps you can't just process the elements individually because you will sometimes need to refer back to elements received earlier. The four operations described in following sections gather all of the items into a single collection. They all still produce an `IObservable` (e.g., an `IObservable` or an `IObservable>`), but these are all single-element observables, and as you've already seen, you can use the `await` keyword to get hold of this single output. + +### ToArray and ToList + +`ToArray` and `ToList` take an observable sequence and package it into an array or an instance of `List` respectively. As with all of the single collection operations, these return an observable source that waits for their input sequence to complete, and then produces the array or list as the single value, after which they immediately complete. This example uses `ToArray` to collect all 5 elements from a source sequence into an array, and uses `await` to extract that array from the sequence that `ToArray` returns: + +```cs +TimeSpan period = TimeSpan.FromMilliseconds(200); +IObservable source = Observable.Timer(TimeSpan.Zero, period).Take(5); +IObservable resultSource = source.ToArray(); + +long[] result = await resultSource; +foreach (long value in result) +{ + Console.WriteLine(value); +} +``` + +Output: + +``` +0 +1 +2 +3 +4 +``` + +As these methods still return observable sequences, you can also use the normal Rx `Subscribe` mechanisms, or use these as inputs to other operators. + +If the source produces values and then errors, you will not receive any of those values. All values received up to that point will be discarded, and the operator will invoke its observer's `OnError` (and in the example above, that will result in the exception being thrown from the `await`). All four operators (`ToArray`, `ToList`, `ToDictionary` and `ToLookup`) handle errors like this. + +### ToDictionary and ToLookup + +Rx can package an observable sequence into a dictionary or lookup with the `ToDictionary` and `ToLookup` methods. Both methods take the same basic approach as the `ToArray` and `ToList` methods: they return a single-element sequence that produces the collection once the input source completes. + +`ToDictionary` provides four overloads that correspond directly to the `ToDictionary` extension methods for `IEnumerable` defined by LINQ to Objects: + +```cs +// Creates a dictionary from an observable sequence according to a specified key selector +// function, a comparer, and an element selector function. +public static IObservable> ToDictionary( + this IObservable source, + Func keySelector, + Func elementSelector, + IEqualityComparer comparer) +{...} + +// Creates a dictionary from an observable sequence according to a specified key selector +// function, and an element selector function. +public static IObservable> ToDictionary( + this IObservable source, + Func keySelector, + Func elementSelector) +{...} + +// Creates a dictionary from an observable sequence according to a specified key selector +// function, and a comparer. +public static IObservable> ToDictionary( + this IObservable source, + Func keySelector, + IEqualityComparer comparer) +{...} + +// Creates a dictionary from an observable sequence according to a specified key selector +// function. +public static IObservable> ToDictionary( + this IObservable source, + Func keySelector) +{...} +``` + +The `ToLookup` extension offers near-identical-looking overloads, the difference being the return type (and the name, obviously). They all return an `IObservable>`. As with LINQ to Objects, the distinction between a dictionary and a lookup is that the `ILookup>` interface allows each key to have any number of values, whereas a dictionary maps each key to one value. + +## ToTask + +Although Rx provides direct support for using `await` with an `IObservable`, it can sometimes be useful to obtain a `Task` representing an `IObservable`. This is useful because some APIs expect a `Task`. You can call `ToTask()` on any `IObservable`, and this will subscribe to that observable, returning a `Task` that will complete when the task completes, producing the sequence's final output as the task's result. If the source completes without producing an element, the task will enter a faulted state, with an `InvalidOperation` exception complaining that the input sequence contains no elements. + +You can optionally pass a cancellation token. If you cancel this before the observable sequence completes, Rx will unsubscribe from the source, and put the task into a cancelled state. + +This is a simple example of how the `ToTask` operator can be used. +Note, the `ToTask` method is in the `System.Reactive.Threading.Tasks` namespace. + +```cs +IObservable source = Observable.Interval(TimeSpan.FromSeconds(1)) + .Take(5); +Task resultTask = source.ToTask(); +long result = await resultTask; // Will take 5 seconds. +Console.WriteLine(result); +``` + +Output: + +``` +4 +``` + +If the source sequence calls `OnError`, Rx puts the task in a faulted state, using the exception supplied. + +Once you have your task, you can of course use all the features of the TPL such as continuations. + +## ToEvent + +Just as you can use an event as the source for an observable sequence with [`FromEventPattern`](03_CreatingObservableSequences.md#from-events), you can also make your observable sequence look like a standard .NET event with the `ToEvent` extension methods. + +```csharp +// Exposes an observable sequence as an object with a .NET event. +public static IEventSource ToEvent(this IObservable source) +{...} + +// Exposes an observable sequence as an object with a .NET event. +public static IEventSource ToEvent( + this IObservable source) +{...} +``` + +The `ToEvent` method returns an `IEventSource`, which has a single member: an `OnNext` event. + +```cs +public interface IEventSource +{ + event Action OnNext; +} +``` + +When we convert the observable sequence with the `ToEvent` method, we can just subscribe by providing an `Action`, which we do here with a lambda. + +```csharp +var source = Observable.Interval(TimeSpan.FromSeconds(1)) + .Take(5); +var result = source.ToEvent(); +result.OnNext += val => Console.WriteLine(val); +``` + +Output: + +``` +0 +1 +2 +3 +4 +``` + +Although this is the simplest way to convert Rx notifications into events, it does not follow the standard .NET event pattern. We use a slightly different approach if we want that. + +### ToEventPattern + +Normally, .NET events supply `sender` and `EventArgs` arguments to their handlers. In the example above, we just get the value. If you want to expose your sequence as an event that follows the standard pattern, you will need to use `ToEventPattern`. + +```cs +// Exposes an observable sequence as an object with a .NET event. +public static IEventPatternSource ToEventPattern( + this IObservable> source) + where TEventArgs : EventArgs +``` + +The `ToEventPattern` will take an `IObservable>` and convert that into an `IEventPatternSource`. The public interface for these types is quite simple. + +```csharp +public class EventPattern : IEquatable> + where TEventArgs : EventArgs +{ + public EventPattern(object sender, TEventArgs e) + { + this.Sender = sender; + this.EventArgs = e; + } + public object Sender { get; private set; } + public TEventArgs EventArgs { get; private set; } + //...equality overloads +} + +public interface IEventPatternSource where TEventArgs : EventArgs +{ + event EventHandler OnNext; +} +``` + +To use this, we will need a suitable `EventArgs` type. You might be able to use one supplied by the .NET runtime libraries, but if not you can easily write your own: + +The `EventArgs` type: + +```csharp +public class MyEventArgs : EventArgs +{ + private readonly long _value; + + public MyEventArgs(long value) + { + _value = value; + } + + public long Value + { + get { return _value; } + } +} +``` + +We can then use this from Rx by applying a simple transform using `Select`: + +```cs +IObservable> source = Observable + .Interval(TimeSpan.FromSeconds(1)) + .Select(i => new EventPattern(this, new MyEventArgs(i))); +``` + +Now that we have a sequence that is compatible, we can use the `ToEventPattern`, and in turn, a standard event handler. + +```cs +IEventPatternSource result = source.ToEventPattern(); +result.OnNext += (sender, eventArgs) => Console.WriteLine(eventArgs.Value); +``` + +Now that we know how to get back into .NET events, let's take a break and remember why Rx is a better model. + +- Events are difficult to compose +- Events cannot be passed as argument or stored in fields +- Events do not offer the ability to be easily queried over time +- Events do not have a standard pattern for reporting errors +- Events do not have a standard pattern for indicating the end of a sequence of values +- Events provide almost no help for managing concurrency or multithreaded applications + +## Do + +Non-functional requirements of production systems often demand high availability, quality monitoring features and low lead time for defect resolution. Logging, debugging, instrumentation and journaling are common implementation choices for implementing non-functional requirements. To enable these it can often be useful to 'tap into' your Rx queries, making them deliver monitoring and diagnostic information as a side effect of their normal operation. + +The `Do` extension method allows you to inject side effect behaviour. From an Rx perspective, `Do` doesn't appear to do anything: you can apply it to any `IObservable`, and it returns another `IObservable` that reports exactly the same elements and error or completion as its source. However, its various overloads takes callback arguments that look just like those for `Subscribe`: you can provide callbacks for individual items, completion, and errors. Unlike `Subscribe`, `Do` is not the final destination—everything the `Do` callbacks see will also be forwarded onto `Do`'s subscribers. This makes it useful for logging and similar instrumentation because you can use it to report how information is flowing through an Rx query without changing that query's behaviour. + +You have to be a bit careful of course. Using `Do` will have a performance impact. And if the callback you supply to `Do` performs any operations that could change the inputs to the Rx query it is part of, you will have created a feedback loop making the behaviour altogether harder to understand. + +Let's first define some logging methods that we can go on to use in an example: + +```csharp +private static void Log(object onNextValue) +{ + Console.WriteLine($"Logging OnNext({onNextValue}) @ {DateTime.Now}"); +} +private static void Log(Exception error) +{ + Console.WriteLine($"Logging OnError({error}) @ {DateTime.Now}"); +} +private static void Log() +{ + Console.WriteLine($"Logging OnCompleted()@ {DateTime.Now}"); +} +``` + +This code uses `Do` to introduce some logging using the methods from above. + +```cs +IObservable source = Observable.Interval(TimeSpan.FromSeconds(1)) + .Take(3); + +IObservable loggedSource = source.Do( + i => Log(i), + ex => Log(ex), + () => Log()); + +loggedSource.Subscribe( + Console.WriteLine, + () => Console.WriteLine("completed")); +``` + +Output: + +``` +Logging OnNext(0) @ 01/01/2012 12:00:00 +0 +Logging OnNext(1) @ 01/01/2012 12:00:01 +1 +Logging OnNext(2) @ 01/01/2012 12:00:02 +2 +Logging OnCompleted() @ 01/01/2012 12:00:02 +completed +``` + +Note that because the `Do` is part of the query, it necessarily sees the values earlier than the `Subscribe`, which is the final link in the chain. That's why the log messages appear before the lines produced by the `Subscribe` callbacks. I like to think of the `Do` method as a [wire tap](http://en.wikipedia.org/wiki/Telephone_tapping) to a sequence. It gives you the ability to listen in on the sequence without modifying it. + +As with `Subscribe`, instead of passing callbacks, there are overloads that let you supply callbacks for whichever of the OnNext, OnError, and OnCompleted notifications you want, or you can pass `Do` an `IObserver`. + +## Encapsulating with AsObservable + +Poor encapsulation is a way developers can leave the door open for bugs. Here is a handful of scenarios where carelessness leads to leaky abstractions. Our first example may seem harmless at a glance, but has numerous problems. + +```cs +public class UltraLeakyLetterRepo +{ + public ReplaySubject Letters { get; } + + public UltraLeakyLetterRepo() + { + Letters = new ReplaySubject(); + Letters.OnNext("A"); + Letters.OnNext("B"); + Letters.OnNext("C"); + } +} +``` + +In this example we expose our observable sequence as a property. We've used a `ReplaySubject` so that each subscriber will receive all of the values upon subscription. However, revealing this implementation choice in the public type of the `Letters` property is poor encapsulation, as consumers could call `OnNext`/`OnError`/`OnCompleted`. To close off that loophole we can simply make the publicly visible property type an `IObservable`. + +```cs +public class ObscuredLeakinessLetterRepo +{ + public IObservable Letters { get; } + + public ObscuredLeakinessLetterRepo() + { + var letters = new ReplaySubject(); + letters.OnNext("A"); + letters.OnNext("B"); + letters.OnNext("C"); + this.Letters = letters; + } +} +``` + +This is a significant improvement: the compiler won't let someone using an instance of this source write `source.Letters.OnNext("1")`. So the API surface area properly encapsulates the implementation detail, but if we were paranoid, we could not that nothing prevents consumers from casting the result back to an `ISubject` and then calling whatever methods they like. In this example we see external code pushing their values into the sequence. + +```csharp +var repo = new ObscuredLeakinessLetterRepo(); +IObservable good = repo.GetLetters(); + +good.Subscribe(Console.WriteLine); + +// Be naughty +if (good is ISubject evil) +{ + // So naughty, 1 is not a letter! + evil.OnNext("1"); +} +else +{ + Console.WriteLine("could not sabotage"); +} +``` + +Output: + +``` +A +B +C +1 +``` + +Arguably, code that does this sort of thing is asking for trouble, but if we wanted actively to prevent it, the fix to this problem is quite simple. By applying the `AsObservable` extension method, we can modify the line of the constructor that sets `this.Letters` to wrap the subject in a type that only implements `IObservable`. + +```csharp +this.Letters = letters.AsObservable(); +``` + +Output: + +``` +A +B +C +could not sabotage +``` + +While I have used words like 'evil' and 'sabotage' in these examples, it is [more often than not an oversight rather than malicious intent](https://en.wikipedia.org/wiki/Hanlon%27s_razor) that causes problems. The failing falls first on the programmer who designed the leaky class. Designing interfaces is hard, but we should do our best to help consumers of our code fall into [the pit of success](https://learn.microsoft.com/en-gb/archive/blogs/brada/the-pit-of-success) by giving them discoverable and consistent types. Types become more discoverable if we reduce their surface area to expose only the features we intend our consumers to use. In this example we reduced the type's surface area. We did so by choosing a suitable public-facing type for the property, and then preventing access to the underlying type with the `AsObservable` method. + +The set of methods we have looked at in this chapter complete the circle started in the [Creating Sequences chapter](03_CreatingObservableSequences.md). We now have the means to enter and leave Rx's world. Take care when opting in and out of the `IObservable`. It's best not to transition back and forth—having a bit of Rx-based processing, then some more conventional code, and then plumbing the results of that back into Rx can quickly make a mess of your code base, and may indicate a design flaw. Typically it is better to keep all of your Rx logic together, so you only need to integrating with the outside world twice: once for input and once for output. diff --git a/content/13_TimeShiftedSequences.md b/content/13_TimeShiftedSequences.md deleted file mode 100644 index fa2f88e..0000000 --- a/content/13_TimeShiftedSequences.md +++ /dev/null @@ -1,521 +0,0 @@ ---- -title: Time-shifted sequences ---- - -# Time-shifted sequences - -When working with observable sequences, the time axis is an unknown quantity: when will the next notification arrive? When consuming an `IEnumerable` sequence, asynchrony is not a concern; when we call `MoveNext()`, we are blocked until the sequence yields. This chapter looks at the various methods we can apply to an observable sequence when its relationship with time is a concern. - -## Buffer - -Our first subject will be the `Buffer` method. In some situations, you may not want a deluge of individual notifications to process. Instead, you might prefer to work with batches of data. It may be the case that processing one item at a time is just too expensive, and the trade-off is to deal with messages in batches, at the cost of accepting a delay. - -The `Buffer` operator allows you to store away a range of values and then re-publish them as a list once the buffer is full. You can temporarily withhold a specified number of elements, stash away all the values for a given time span, or use a combination of both count and time. `Buffer` also offers more advanced overloads that we will look at in a future chapter. - -```csharp -public static IObservable> Buffer( - this IObservable source, - int count) -{...} - -public static IObservable> Buffer( - this IObservable source, - TimeSpan timeSpan) -{...} - -public static IObservable> Buffer( - this IObservable source, - TimeSpan timeSpan, - int count) -{...} -``` - -The two overloads of `Buffer` are straight forward and should make it simple for other developers to understand the intent of the code. - -```csharp -IObservable> bufferedSequence; -bufferedSequence = mySequence.Buffer(4); - -// or -bufferedSequence = mySequence.Buffer(TimeSpan.FromSeconds(1)) -``` - -For some use cases, it may not be enough to specify only a buffer size and a maximum delay period. Some systems may have a sweet spot for the size of a batch they can process, but also have a time constraint to ensure that data is not stale. In this case buffering by both time and count would be suitable. - -In this example below, we create a sequence that produces the first ten values one second apart, then a further hundred values within another second. We buffer by a maximum period of three seconds and a maximum batch size of fifteen values. - -```csharp -var idealBatchSize = 15; -var maxTimeDelay = TimeSpan.FromSeconds(3); -var source = Observable.Interval(TimeSpan.FromSeconds(1)).Take(10) - .Concat(Observable.Interval(TimeSpan.FromSeconds(0.01)).Take(100)); - -source.Buffer(maxTimeDelay, idealBatchSize) - .Subscribe( - buffer => Console.WriteLine("Buffer of {1} @ {0}", DateTime.Now, buffer.Count), - () => Console.WriteLine("Completed")); -``` - -Output: - -``` -Buffer of 3 @ 01/01/2012 12:00:03 -Buffer of 3 @ 01/01/2012 12:00:06 -Buffer of 3 @ 01/01/2012 12:00:09 -Buffer of 15 @ 01/01/2012 12:00:10 -Buffer of 15 @ 01/01/2012 12:00:10 -Buffer of 15 @ 01/01/2012 12:00:10 -Buffer of 15 @ 01/01/2012 12:00:11 -Buffer of 15 @ 01/01/2012 12:00:11 -Buffer of 15 @ 01/01/2012 12:00:11 -Buffer of 11 @ 01/01/2012 12:00:11 -``` - -Note the variations in time and buffer size. We never get a buffer containing more than fifteen elements, and we never wait more than three seconds. A practical application of this is when you are loading data from an external source into an `ObservableCollection` in a WPF application. It may be the case that adding one item at a time is just an unnecessary load on the dispatcher (especially if you are expecting over a hundred items). You may have also measured, for example that processing a batch of fifty items takes 100ms. You decide that this is the maximum amount of time you want to block the dispatcher, to keep the application responsive. This could give us two reasonable values to use: `source.Buffer(TimeSpan.FromMilliseconds(100), 50)`. This means the longest we will block the UI is about 100ms to process a batch of 50 values, and we will never have values waiting for longer than 100ms before they are processed. - -### Overlapping buffers - -`Buffer` also offers overloads to manipulate the overlapping of the buffers. The variants we have looked at so far do not overlap and have no gaps between buffers, i.e. all values from the source are propagated through. - -```csharp -public static IObservable> Buffer( - this IObservable source, - int count, - int skip) -{...} - -public static IObservable> Buffer( - this IObservable source, - TimeSpan timeSpan, - TimeSpan timeShift) -{...} -``` - -There are three interesting things you can do with overlapping buffers: - -- **Overlapping behavior**: Ensure that current buffer includes some or all values from previous buffer -- **Standard behavior**: Ensure that each new buffer only has new data -- **Skip behavior**: Ensure that each new buffer not only contains new data exclusively, but also ignores one or more values since the previous buffer - - -#### Overlapping buffers by count - -If you are specifying a buffer size as a count, then you need to use this overload. - -```csharp -public static IObservable> Buffer( - this IObservable source, - int count, - int skip) -{...} -``` - -You can apply the above scenarios as follows: - -- **Overlapping behavior**: `skip` < `count` * -- **Standard behavior**: `skip` = `count` -- **Skip behavior**: `skip` > `count` - -> *The `skip` parameter cannot be less than or equal to zero. If you want to use a value of zero (i.e. each buffer contains all values), then consider using the `Scan` method instead with an `IList` as the accumulator. - -Let's see each of these in action. In this example, we have a source that produces values every second. We apply each of the variations of the buffer overload. - -```csharp -var source = Observable.Interval(TimeSpan.FromSeconds(1)).Take(10); -source.Buffer(3, 1) - .Subscribe( - buffer => - { - Console.WriteLine("--Buffered values"); - foreach (var value in buffer) - { - Console.WriteLine(value); - } - }, () => Console.WriteLine("Completed")); -``` - -Output - -``` ---Buffered values -0 -1 -2 ---Buffered values -1 -2 -3 ---Buffered values -2 -3 -4 ---Buffered values -3 -4 -5 -etc.... -``` - -Note that in each buffer, one value is skipped from the previous batch. If we change the `skip` parameter from 1 to 3 (same as the buffer size), we see standard buffer behavior. - -```csharp -var source = Observable.Interval(TimeSpan.FromSeconds(1)).Take(10); -source.Buffer(3, 3) - ... -``` - -Output - -``` ---Buffered values -0 -1 -2 ---Buffered values -3 -4 -5 ---Buffered values -6 -7 -8 ---Buffered values -9 -Completed -``` - -Finally, if we change the `skip` parameter to 5 (a value greater than the count of 3), we can see that two values are lost between each buffer. - -```csharp -var source = Observable.Interval(TimeSpan.FromSeconds(1)).Take(10); -source.Buffer(3, 5) - ... -``` - -Output - -``` ---Buffered values -0 -1 -2 ---Buffered values -5 -6 -7 -Completed -``` - -#### Overlapping buffers by time - -You can, of course, apply the same three behaviors with buffers defined by time instead of count. - -```csharp -public static IObservable> Buffer( - this IObservable source, - TimeSpan timeSpan, - TimeSpan timeShift) -{...} -``` - -To exactly replicate the output from our [Overlapping Buffers By Count[(#OverlappingBuffersByCount) examples, we only need to provide the following arguments: - -```csharp -var source = Observable.Interval(TimeSpan.FromSeconds(1)).Take(10); -var overlapped = source.Buffer(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(1)); -var standard = source.Buffer(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3)); -var skipped = source.Buffer(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(5)); -``` - -As our source produces values consistently every second, we can use the same values from our count example but as seconds. - -## Delay - -The `Delay` extension method is a purely a way to time-shift an entire sequence. You can provide either a relative time the sequence should be delayed by using a `TimeSpan`, or an absolute point in time that the sequence should wait for using a `DateTimeOffset`. The relative time intervals between the values are preserved. - -```csharp -// Time-shifts the observable sequence by a relative time. -public static IObservable Delay( - this IObservable source, - TimeSpan dueTime) -{...} - -// Time-shifts the observable sequence by a relative time. -public static IObservable Delay( - this IObservable source, - TimeSpan dueTime, - IScheduler scheduler) -{...} - -// Time-shifts the observable sequence by an absolute time. -public static IObservable Delay( - this IObservable source, - DateTimeOffset dueTime) -{...} - -// Time-shifts the observable sequence by an absolute time. -public static IObservable Delay( - this IObservable source, - DateTimeOffset dueTime, - IScheduler scheduler) -{...} -``` - -To show the `Delay` method in action, we create a sequence of values one second apart and timestamp them. This will show that it is not the subscription that is being delayed, but the actual forwarding of the notifications to our final subscriber. - -```csharp -var source = Observable.Interval(TimeSpan.FromSeconds(1)) - .Take(5) - .Timestamp(); - -var delay = source.Delay(TimeSpan.FromSeconds(2)); - -source.Subscribe( - value => Console.WriteLine("source : {0}", value), - () => Console.WriteLine("source Completed")); -delay.Subscribe( - value => Console.WriteLine("delay : {0}", value), - () => Console.WriteLine("delay Completed")); -``` - -Output: - -``` -source : 0@01/01/2012 12:00:00 pm +00:00 -source : 1@01/01/2012 12:00:01 pm +00:00 -source : 2@01/01/2012 12:00:02 pm +00:00 -delay : 0@01/01/2012 12:00:00 pm +00:00 -source : 3@01/01/2012 12:00:03 pm +00:00 -delay : 1@01/01/2012 12:00:01 pm +00:00 -source : 4@01/01/2012 12:00:04 pm +00:00 -source Completed -delay : 2@01/01/2012 12:00:02 pm +00:00 -delay : 3@01/01/2012 12:00:03 pm +00:00 -delay : 4@01/01/2012 12:00:04 pm +00:00 -delay Completed -``` - -It is worth noting that `Delay` will not time-shift `OnError` notifications. These will be propagated immediately. - -## Sample - -The `Sample` method simply takes the last value for every specified `TimeSpan`. This is great for getting timely data from a sequence that produces too much information for your requirements. This example shows sample in action. - -```csharp -var interval = Observable.Interval(TimeSpan.FromMilliseconds(150)); -interval.Sample(TimeSpan.FromSeconds(1)).Subscribe(Console.WriteLine); -``` - -Output: - -``` -5 -12 -18 -``` - -This output is interesting and this is the reason why I choose the value of 150ms. If we plot the underlying sequence of values against the time they are produced, we can see that `Sample` is taking the last value it received for each period of one second. - -| Relative time (ms) | Source value | Sampled value | -| :----------------- | :----------- | :------------ | -| 0 | | | -| 50 | | | -| 100 | | | -| 150 | 0 | | -| 200 | | | -| 250 | | | -| 300 | 1 | | -| 350 | | | -| 400 | | | -| 450 | 2 | | -| 500 | | | -| 550 | | | -| 600 | 3 | | -| 650 | | | -| 700 | | | -| 750 | 4 | | -| 800 | | | -| 850 | | | -| 900 | 5 | | -| 950 | | | -| 1000 | | 5 | -| 1050 | 6 | | -| 1100 | | | -| 1150 | | | -| 1200 | 7 | | -| 1250 | | | -| 1300 | | | -| 1350 | 8 | | -| 1400 | | | -| 1450 | | | -| 1500 | 9 | | -| 1550 | | | -| 1600 | | | -| 1650 | 10 | | -| 1700 | | | -| 1750 | | | -| 1800 | 11 | | -| 1850 | | | -| 1900 | | | -| 1950 | 12 | | -| 2000 | | 12 | -| 2050 | | | -| 2100 | 13 | | -| 2150 | | | -| 2200 | | | -| 2250 | 14 | | -| 2300 | | | -| 2350 | | | -| 2400 | 15 | | -| 2450 | | | -| 2500 | | | -| 2550 | 16 | | -| 2600 | | | -| 2650 | | | -| 2700 | 17 | | -| 2750 | | | -| 2800 | | | -| 2850 | 18 | | -| 2900 | | | -| 2950 | | | -| 3000 | 19 | 19 | - -## Throttle - -The `Throttle` extension method provides a sort of protection against sequences that produce values at variable rates and sometimes too quickly. Like the `Sample` method, `Throttle` will return the last sampled value for a period of time. Unlike `Sample` though, `Throttle`'s period is a sliding window. Each time `Throttle` receives a value, the window is reset. Only once the period of time has elapsed will the last value be propagated. This means that the `Throttle` method is only useful for sequences that produce values at a variable rate. Sequences that produce values at a constant rate (like `Interval` or `Timer`) either would have all of their values suppressed if they produced values faster than the throttle period, or all of their values would be propagated if they produced values slower than the throttle period. - -```csharp -// Ignores values from an observable sequence which are followed by another value before -// dueTime. -public static IObservable Throttle( - this IObservable source, - TimeSpan dueTime) -{...} -public static IObservable Throttle( - this IObservable source, - TimeSpan dueTime, - IScheduler scheduler) -{...} -``` - -A great application of the `Throttle` method would be to use it with a live search like "Google Suggest". While the user is still typing we can hold off on the search. Once there is a pause for a given period, we can execute the search with what they have typed. The Rx team has a great example of this scenario in the [Rx Hands On Lab](http://download.microsoft.com/download/C/5/D/C5D669F9-01DF-4FAF-BBA9-29C096C462DB/Rx%20HOL%20.NET.pdf "Rx Hands On Lab as PDF - Mcrosoft.com"). - -## Timeout - -We have considered handling timeout exceptions previously in the chapter on [Flow control](11_AdvancedErrorHandling.html#CatchSwallowingException). The `Timeout` extension method allows us terminate the sequence with an error if we do not receive any notifications for a given period. We can either specify the period as a sliding window with a `TimeSpan`, or as an absolute time that the sequence must complete by providing a `DateTimeOffset`. - -```csharp -// Returns either the observable sequence or a TimeoutException if the maximum duration -// between values elapses. -public static IObservable Timeout( - this IObservable source, - TimeSpan dueTime) -{...} -public static IObservable Timeout( - this IObservable source, - TimeSpan dueTime, - IScheduler scheduler) -{...} - -// Returns either the observable sequence or a TimeoutException if dueTime elapses. -public static IObservable Timeout( - this IObservable source, - DateTimeOffset dueTime) -{...} -public static IObservable Timeout( - this IObservable source, - DateTimeOffset dueTime, - IScheduler scheduler) -{...} -``` - -If we provide a `TimeSpan` and no values are produced within that time span, then the sequence fails with a `TimeoutException`. - -```csharp -var source = Observable.Interval(TimeSpan.FromMilliseconds(100)).Take(10) - .Concat(Observable.Interval(TimeSpan.FromSeconds(2))); - -var timeout = source.Timeout(TimeSpan.FromSeconds(1)); -timeout.Subscribe( - Console.WriteLine, - Console.WriteLine, - () => Console.WriteLine("Completed")); -``` - -Output: - -``` -0 -1 -2 -3 -4 -System.TimeoutException: The operation has timed out. -``` - -Like the `Throttle` method, this overload is only useful for sequences that produce values at a variable rate. - -The alternative use of `Timeout` is to set an absolute time; the sequence must be completed by then. - -```csharp -var dueDate = DateTimeOffset.UtcNow.AddSeconds(4); -var source = Observable.Interval(TimeSpan.FromSeconds(1)); -var timeout = source.Timeout(dueDate); -timeout.Subscribe( - Console.WriteLine, - Console.WriteLine, - () => Console.WriteLine("Completed")); -``` - -Output: - -``` -0 -1 -2 -System.TimeoutException: The operation has timed out. -``` - -Perhaps an even more interesting usage of the `Timeout` method is to substitute in an alternative sequence when a timeout occurs. -The `Timeout` method has overloads the provide the option of specifying a continuation sequence to use if a timeout occurs. -This functionality behaves much like the [Catch](11_AdvancedErrorHandling.html#Catch) operator. -It is easy to imagine that the simple overloads actually just call through to these over loads and specify an `Observable.Throw` as the continuation sequence. - -```csharp -// Returns the source observable sequence or the other observable sequence if the maximum -// duration between values elapses. -public static IObservable Timeout( - this IObservable source, - TimeSpan dueTime, - IObservable other) -{...} - -public static IObservable Timeout( - this IObservable source, - TimeSpan dueTime, - IObservable other, - IScheduler scheduler) -{...} - -// Returns the source observable sequence or the other observable sequence if dueTime -// elapses. -public static IObservable Timeout( - this IObservable source, - DateTimeOffset dueTime, - IObservable other) -{...} - -public static IObservable Timeout( - this IObservable source, - DateTimeOffset dueTime, - IObservable other, - IScheduler scheduler) -{...} -``` - - - -Rx provides features to tame the unpredictable element of time in a reactive paradigm. Data can be buffered, throttled, sampled or delayed to meet your needs. Entire sequences can be shifted in time with the delay feature, and timeliness of data can be asserted with the `Timeout` operator. These simple yet powerful features further extend the developer's tool belt for querying data in motion. \ No newline at end of file diff --git a/content/14_ErrorHandlingOperators.md b/content/14_ErrorHandlingOperators.md new file mode 100644 index 0000000..007a5e6 --- /dev/null +++ b/content/14_ErrorHandlingOperators.md @@ -0,0 +1,214 @@ +--- +title: Error Handling Operators +--- + +Exceptions happen. Some exceptions are inherently avoidable, occurring only because of bugs in our code. For example, if we put the CLR into a situation where it has to raise a `DivideByZeroException`, we've done something wrong. But there are plenty of exceptions that cannot be prevented with defensive coding. For example, exceptions relating to I/O or networking failures such as like `FileNotFoundException` or `TimeoutException` can be caused by environmental factors outside of our code's control. In these cases, we need to handle the exception gracefully. The kind of handling will depend on the context. It might be appropriate to provide some sort of error message to the user; in some scenarios logging the error might be a more appropriate response. If the failure is likely to be transient, we could try to recover by retrying the operation that failed. + +The `IObserver` interface defines the `OnError` method so that a source can report an error, but since this terminates the sequence, it provides no direct means of working out what to do next. However, Rx provides operators that provide a variety of error handling mechanisms. + +## Catch + +Rx defines a `Catch` operator. The name is deliberately reminiscent of C#'s `try`/`catch` syntax because it lets you handle errors from an Rx source in a similar way to exceptions that emerge from normal execution of code. It can work in two different ways. You can just supply a function to which Rx will pass the error, and this function can return an `IObservable`, and `Catch` will now forward items from that instead of the original source. Or, instead of passing a function, you can just supply one or more additional sequences, and catch will move onto the next each time the current one fails. + +### Examining the exception + +`Catch` has an overload that enables you provide a handler to be invoked if the source produces an error: + +```cs +public static IObservable Catch( + this IObservable source, + Func> handler) + where TException : Exception +``` + +This is conceptually very similar to a C# `catch` block: we can write code that looks at the exception and then decides how to proceed. And as with a `catch` block we can decide which kinds of exceptions we are interested in. For example, we might know that the source will sometimes produce a `TimeoutException`, and we might just want to return an empty sequence in that case, instead of an error: + +```cs +IObservable result = source.Catch(_ => Observable.Empty()); +``` + +`Catch` will only invoke our function if the exception is of the type specified (or a type derived from that). If the sequence was to terminate with an `Exception` that could not be cast to a `TimeoutException`, then the error would not be caught and would flow through to the subscriber. + +This example returns `Observable.Empty()`. This is conceptually similar to 'swallowing' an exception in C#, i.e., choosing to take no action. This can be a reasonable response to an exception that you anticipate, but it is generally a bad idea to do this with the base `Exception` type. + +This last example ignored its input, because it was interested only in the exception type. However, we are free to examine the exception, and make more fine-grained decisions about what should emerge from the `Catch`: + +```cs +IObservable result = source.Catch( + (FileNotFoundException x) => x.FileName == "settings.txt" + ? Observable.Return(DefaultSettings) : Observable.Throw(x)); +``` + +This provides special handling for one particular file, but otherwise rethrows the exception. Returning `Observable.Throw(x)` here (where `x` is the original exception) is conceptually similar to writing `throw` in a catch block. (In C# there's an important distinction between `throw;` and `throw x;` because it changes how exception context is captured, but in Rx, `OnError` doesn't capture a stack trace, so there's no equivalent distinction.) + +You're also free to throw a completely different exception, of course. You can return any `IObservable` you like, as long as its element type is the same as the source's. + + +### Fallback + +The other overloads of `Catch` offer less discriminating behaviour: you can supply one or more additional sequences, and any time the current source fails, the exception will be ignored, and `Catch` will simply move onto the next sequence. Since you will never get to know what the exception is, this mechanism gives you no way of knowing whether the exception that occurred was one you anticipated, or a completely unexpected one, so you will normally want to avoid this form. But for completeness, here's how to use it: + +```cs +IObservable settings = settingsSource1.Catch(settingsSource2); +``` + +That form provides just a single fallback. There's also a static `Observable.Catch` method that takes a `params` array, so you can pass any number of sources. This is exactly equivalent to the preceding example: + +```cs +IObservable settings = Observable.Catch(settingsSource1, settingsSource2); +``` + +There's also an overload that accepts an `IEnumerable>`. + +If any of the sources reaches its end without reporting an exception, `Catch` also immediately reports completion and does not subscribe to any of the subsequent sources. If the very last source reports an exception, `Catch` will have no further sources to fall back on, so in that case it won't catch the exception. It will forward that final exception to its subscriber. + + +## Finally + +Similar to a `finally` block in C#, Rx enables us to execute some code on completion of a sequence, regardless of whether it runs to completion naturally or fails. Rx adds a third mode of completion that has no exact equivalent in `catch`/`finally`: the subscriber might unsubscribe before the source has a chance to complete. (This is conceptually similar to using `break` to terminate a `foreach` early.) The `Finally` extension method accepts an `Action` as a parameter. This `Action` will be invoked when the sequence terminates, regardless of whether `OnCompleted` or `OnError` was called. It will also invoke the action if the subscription is disposed of before it completes. + +```csharp +public static IObservable Finally( + this IObservable source, + Action finallyAction) +{ + ... +} +``` + +In this example, we have a sequence that completes. We provide an action and see that it is called after our `OnCompleted` handler. + +```csharp +var source = new Subject(); +IObservable result = source.Finally(() => Console.WriteLine("Finally action ran")); +result.Dump("Finally"); +source.OnNext(1); +source.OnNext(2); +source.OnNext(3); +source.OnCompleted(); +``` + +Output: + +``` +Finally-->1 +Finally-->2 +Finally-->3 +Finally completed +Finally action ran +``` + +The source sequence could also have terminated with an exception. In that case, the exception would have been sent to the subscriber's `OnError` (and we'd have seen that in the console output), and then the delegate we provided to `Finally` would have been executed. + +Alternatively, we could have disposed of our subscription. In the next example, we see that the `Finally` action is invoked even though the sequence does not complete. + +```csharp +var source = new Subject(); +var result = source.Finally(() => Console.WriteLine("Finally")); +var subscription = result.Subscribe( + Console.WriteLine, + Console.WriteLine, + () => Console.WriteLine("Completed")); +source.OnNext(1); +source.OnNext(2); +source.OnNext(3); +subscription.Dispose(); +``` + +Output: + +``` +1 +2 +3 +Finally +``` + +Note that if the subscriber's `OnError` throws an exception, and if the source calls `OnNext` without a `try`/`catch` block, the CLR's unhandled exception reporting mechanism kicks in, and in some circumstances this can result in the application shutting down before the `Finally` operator has had an opportunity to invoke the callback. We can create this scenario with the following code: + +```cs +var source = new Subject(); +var result = source.Finally(() => Console.WriteLine("Finally")); +result.Subscribe( + Console.WriteLine, + // Console.WriteLine, + () => Console.WriteLine("Completed")); +source.OnNext(1); +source.OnNext(2); +source.OnNext(3); + +// Brings the app down. Finally action might not be called. +source.OnError(new Exception("Fail")); +``` + +If you run this directly from the program's entry point, without wrapping it in a `try`/`catch`, you may or may not see the `Finally` message displayed, because exception handling works subtly differently in the case an exception reaches all the way to the top of the stack without being caught. (Oddly, it usually does run, but if you have a debugger attached, the program usually exits without running the `Finally` callback.) + +This is mostly just a curiosity: application frameworks such as ASP.NET Core or WPF typically install their own top-of-stack exception handlers, and in any case you shouldn't be subscribing to a source that you know will call `OnError` without supplying an error callback. This problem only emerges because the delegate-based `Subscribe` overload in use here supplies an `IObserver` implementation that throws in its `OnError`. However, if you're building console applications to experiment with Rx's behaviour you are quite likely to run into this. In practice, `Finally` will do the right thing in more normal situations. (But in any case, you shouldn't throw exceptions from an `OnError` handler.) + +## Using + +The `Using` factory method allows you to bind the lifetime of a resource to the lifetime of an observable sequence. The method takes two callbacks: one to create the disposable resource and one to provide the sequence. This allows everything to be lazily evaluated. These callbacks are invoked when code calls `Subscribe` on the `IObservable` that this method returns. + +```csharp +public static IObservable Using( + Func resourceFactory, + Func> observableFactory) + where TResource : IDisposable +{ + ... +} +``` + +The resource will be disposed of when the sequence terminates either with `OnCompleted` or `OnError`, or when the subscription is disposed. + +## OnErrorResumeNext + +Just the title of this section will send a shudder down the spines of old VB developers! (For those not familiar with this murky language feature, the VB language lets you instruct it to ignore any errors that occur during execution, and to just continue with the next statement after any failure.) In Rx, there is an extension method called `OnErrorResumeNext` that has similar semantics to the VB keywords/statement that share the same name. This extension method allows the continuation of a sequence with another sequence regardless of whether the first sequence completes gracefully or due to an error. + +This is very similar to the second form of `Catch` (as described in [Fallback](#fallback)). The difference is that with `Catch`, if any source sequence reaches its end without reporting an error, `Catch` will not move onto the next sequence. `OnErrorResumeNext` will forward all elements produced by all of its inputs, so it is similar to [`Concat`](09_CombiningSequences.md#concat), it just ignores all errors. + +Just as the `OnErrorResumeNext` keyword was best avoided for anything other than throwaway code in VB, so should it be used with caution in Rx. It will swallow exceptions quietly and can leave your program in an unknown state. Generally, this will make your code harder to maintain and debug. (The same applies for the fallback forms of `Catch`.) + +## Retry + +If you are expecting your sequence to encounter predictable failures, you might simply want to retry. For example, if you are running in a cloud environment, it's very common for operations to fail occasionally for no obvious reason. Cloud platforms often relocate services on a fairly regular basis for operational reasons, which means it's not unusual for operations to fail—you might make a request to a service just before the cloud provider decided to move that service to a different compute node—but for the exact same operation to succeed if you immediately retry it (because the retried request gets routed to the new node). Rx's `Retry` extension method offers the ability to retry on failure a specified number of times or until it succeeds. This works by resubscribing to the source if it reports an error. + +This example uses the simple overload, which will always retry on any exception. + +```csharp +public static void RetrySample(IObservable source) +{ + source.Retry().Subscribe(t => Console.WriteLine(t)); // Will always retry + Console.ReadKey(); +} +``` + +Given a source that produces the values 0, 1, and 2, and then calls `OnError`, the output would be the numbers 0, 1, 2 repeating over an over endlessly. This output would continue forever because this example never unsubscribes, and `Retry` will retry forever if you don't tell it otherwise. + +We can specify the maximum number of retries. In this next example, we only retry once, therefore the error that gets published on the second subscription will be passed up to the final subscription. Note that we tell `Retry` the maximum number of attempts, so if we want it to retry once, you pass a value of 2—that's the initial attempt plus one retry. + +```csharp +source.Retry(2).Dump("Retry(2)"); +``` + +Output: + +``` +Retry(2)-->0 +Retry(2)-->1 +Retry(2)-->2 +Retry(2)-->0 +Retry(2)-->1 +Retry(2)-->2 +Retry(2) failed-->Test Exception +``` + +Proper care should be taken when using the infinite repeat overload. Obviously if there is a persistent problem with your underlying sequence, you may find yourself stuck in an infinite loop. Also, take note that there is no overload that allows you to specify the type of exception to retry on. + +Rx also offers a `RetryWhen` method. This is similar to the first `Catch` overload we looked at: instead of handling all exceptions indiscriminately, it lets you supply code that can decide what to do. It works slightly differently: instead of invoking this callback once per error, it invokes it once passing in an `IObservable` through which it will supply all of the exceptions, and the callback returns an `IObservable` referred to as the _signal_ observable. The `T` can be anything, because the values this observable may return will be ignored: all that matters is which of the three `IObserver` methods is invoked. + +If, when receiving an exception, the signal observable calls `OnError`, `RetryWhen` will not retry, and will report that same error to its subscribers. If on the other hand the signal observable calls `OnCompleted`, again `RetryWhen` will not retry, and will complete without reporting an error. But if the signal observable calls `OnNext`, this causes `RetryWhen` to retry by resubscribing to the source. + + + +Applications often need exception management logic that goes beyond simple `OnError` handlers. Rx delivers exception handling operators similar to those we are used to in C#, which you can use to compose complex and robust queries. In this chapter we have covered advanced error handling and some more resource management features from Rx. \ No newline at end of file diff --git a/content/14_HotAndColdObservables.md b/content/14_HotAndColdObservables.md deleted file mode 100644 index 888ace6..0000000 --- a/content/14_HotAndColdObservables.md +++ /dev/null @@ -1,494 +0,0 @@ ---- -title : Hot and Cold observables ---- - - - - -# Hot and Cold observables - -In this chapter, we will look at how to describe and handle two styles of observable sequences: - -1. Sequences that are passive and start producing notifications on request (when subscribed to), and -2. Sequences that are active and produce notifications regardless of subscriptions. - -In this sense, passive sequences are `Cold` and active are described as being `Hot`. You can draw some similarities between implementations of the `IObservable` interface and implementations of the `IEnumerable`interface with regards to hot and cold. With `IEnumerable`, you could have an on-demand collection via the yield return syntax, or you could have an eagerly-evaluated collection by returning a populated `List`. We can compare the two styles by attempting to read just the first value from a sequence. We can do this with a method like this: - -```csharp -public void ReadFirstValue(IEnumerable list) -{ - foreach (var i in list) - { - Console.WriteLine("Read out first value of {0}", i); - break; - } -} -``` - -As an alternative to the `break` statement, we could apply a `Take(1)` to the `list`. If we then apply this to an eagerly-evaluated sequence, such as a list, we see the entire list is first constructed, and then returned. - -```csharp -public static void Main() -{ - ReadFirstValue(EagerEvaluation()); -} - -public IEnumerable EagerEvaluation() -{ - var result = new List(); - Console.WriteLine("About to return 1"); - result.Add(1); - //code below is executed but not used. - Console.WriteLine("About to return 2"); - result.Add(2); - return result; -} -``` - -Output: - -``` -About to return 1 -About to return 2 -Read out first value of 1 -``` - -We now apply the same code to a lazily-evaluated sequence. - -```csharp -public IEnumerable LazyEvaluation() -{ - Console.WriteLine("About to return 1"); - yield return 1; - - // Execution stops here in this example - Console.WriteLine("About to return 2"); - yield return 2; -} -``` - -Output: - -``` -About to return 1 -Read out first value of 1 -``` - -The lazily-evaluated sequence did not have to yield any more values than required. Lazy evaluation is good for on-demand queries whereas eager evaluation is good for sharing sequences so as to avoid re-evaluating multiple times. Implementations of `IObservable` can exhibit similar variations in style. - -Examples of hot observables that could publish regardless of whether there are any subscribers would be: - -- mouse movements -- timer events -- broadcasts like ESB channels or UDP network packets. -- price ticks from a trading exchange - -Some examples of cold observables would be: - -- asynchronous request (e.g. when using `Observable.FromAsyncPattern`) -- whenever `Observable.Create` is used -- subscriptions to queues -* on-demand sequences - -## Cold observables - -In this example, we fetch a list of products from a database. In our implementation, we choose to return an `IObservable` and, as we get the results, we publish them until we have the full list, then complete the sequence. - -```csharp -private const string connectionString = @"Data Source=.\SQLSERVER;Initial Catalog=AdventureWorksLT2008;Integrated Security=SSPI;" - -private static IObservable GetProducts() -{ - return Observable.Create( - o => - { - using(var conn = new SqlConnection(connectionString)) - using (var cmd = new SqlCommand("Select Name FROM SalesLT.ProductModel", conn)) - { - conn.Open(); - SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection); - while (reader.Read()) - { - o.OnNext(reader.GetString(0)); - } - o.OnCompleted(); - return Disposable.Create(()=>Console.WriteLine("--Disposed--")); - } - }); -} -``` - -This code is just like many existing data access layers that return an `IEnumerable`, however it would be much easier with Rx to access this in an asynchronous manner (using [SubscribeOn and ObserveOn](15_SchedulingAndThreading.html#SubscribeOnObserveOn)). This example of a data access layer is lazily evaluated and provides no caching. Each time the method is used, we reconnect to the database. This is typical of cold observables; calling the method does nothing. Subscribing to the returned `IObservable` will however invoke the create delegate which connects to the database. - -Here we have a consumer of our above code, but it explicitly only wants up to three values (the full set has 128 values). This code illustrates that the `Take(3)` expression will restrict what the consumer receives but `GetProducts()` method will still publish _all_ of the values. - -```csharp -public void ColdSample() -{ - var products = GetProducts().Take(3); - products.Subscribe(Console.WriteLine); - - Console.ReadLine(); -} -``` - -The `GetProducts()` code above is a pretty naive example, as it lacks the ability to cancel at any time. This means all values are read even though only three were requested. In the later chapter on [scheduling](15_SchedulingAndThreading.html), we cover examples on how to provide cancellation correctly. - -## Hot observables - -In our example above, the database was not accessed until the consumer of the `GetProducts()` method subscribed to the return value. Subsequent or even parallel calls to `GetProducts()` would return independent observable sequences and would each make their own independent calls to the database. By contrast, a hot observable is an observable sequence that is producing notifications even if there are no subscribers. The classic cases of hot observables are UI Events and Subjects. For example, if the mouse moves then the `MouseMove` event will be raised. If there are no event handlers registered for the event, then nothing happens. If, on the other hand, we create a `Subject`, we can inject values into it using `OnNext`, regardless of whether there are observers subscribed to the subject or not. - -Some observable sequences can appear to be hot when they are in fact cold. A couple of examples that surprise many is `Observable.Interval` and `Observable.Timer` (though it should not come as a shock to attentive readers of the [Creating observable sequences](04_CreatingObservableSequences.html#Unfold) chapter). In the example below, we subscribe twice to the same instance, created via the `Interval` factory method. The delay between the two subscriptions should demonstrate that while they are subscribed to the same observable instance, the values each subscription receives are independent, i.e. `Interval` is cold. - -```csharp -public void SimpleColdSample() -{ - var period = TimeSpan.FromSeconds(1); - var observable = Observable.Interval(period); - - observable.Subscribe(i => Console.WriteLine("first subscription : {0}", i)); - - Thread.Sleep(period); - - observable.Subscribe(i => Console.WriteLine("second subscription : {0}", i)); - - Console.ReadKey(); -} -``` - -Output: - -``` -first subscription : 0 -first subscription : 1 -second subscription : 0 -first subscription : 2 -second subscription : 1 -first subscription : 3 -second subscription : 2 -``` - -## Publish and Connect - -If we want to be able to share the actual data values and not just the observable instance, we can use the `Publish()` extension method. This will return an `IConnectableObservable`, which extends `IObservable` by adding a single `Connect()` method. By using the `Publish()` then `Connect()` method, we can get this sharing functionality. - -```csharp -var period = TimeSpan.FromSeconds(1); -var observable = Observable.Interval(period).Publish(); - -observable.Connect(); -observable.Subscribe(i => Console.WriteLine("first subscription : {0}", i)); - -Thread.Sleep(period); - -observable.Subscribe(i => Console.WriteLine("second subscription : {0}", i)); -``` - -Output: - -``` -first subscription : 0 -first subscription : 1 -second subscription : 1 -first subscription : 2 -second subscription : 2 -``` - -In the example above, the `observable` variable is an `IConnectableObservable`, and by calling `Connect()` it will subscribe to the underlying (the `Observable.Interval`). In this case, we are quick enough to subscribe before the first item is published, but only on the first subscription. The second subscription subscribes late and misses the first publication. We could move the invocation of the `Connect()` method until after all subscriptions have been made. That way, even with the call to `Thread.Sleep` we will not really subscribe to the underlying until after both subscriptions are made. This would be done as follows: - -```csharp -var period = TimeSpan.FromSeconds(1); -var observable = Observable.Interval(period).Publish(); - -observable.Subscribe(i => Console.WriteLine("first subscription : {0}", i)); - -Thread.Sleep(period); - -observable.Subscribe(i => Console.WriteLine("second subscription : {0}", i)); -observable.Connect(); -``` - -``` -first subscription : 0 -second subscription : 0 -first subscription : 1 -second subscription : 1 -first subscription : 2 -second subscription : 2 -``` - -As you can imagine, this is quite useful whenever an application needs to share sequences of data. In a financial trading application, if you wanted to consume a price stream for a certain asset in more than one place, you would want to try to reuse a single, common stream and avoid making another subscription to the server providing that data. In a social media application, many widgets may need to be notified whenever someone connects. `Publish` and `Connect` are perfect solutions for this. - -### Disposal of connections and subscriptions - -A point of interest is how disposal is performed. Indeed, we have not covered yet the fact that `Connect` returns an `IDisposable`. By disposing of the 'connection', you can turn the sequence on and off (`Connect()` to toggle it on, disposing toggles it off). In this example, we see that the sequence can be connected and disconnected multiple times. - -```csharp -var period = TimeSpan.FromSeconds(1); -var observable = Observable.Interval(period).Publish(); - -observable.Subscribe(i => Console.WriteLine("subscription : {0}", i)); - -var exit = false; - -while (!exit) -{ - Console.WriteLine("Press enter to connect, esc to exit."); - var key = Console.ReadKey(true); - - if(key.Key == ConsoleKey.Enter) - { - var connection = observable.Connect(); //--Connects here-- - Console.WriteLine("Press any key to dispose of connection."); - Console.ReadKey(); - connection.Dispose(); //--Disconnects here-- - } - - if(key.Key == ConsoleKey.Escape) - { - exit = true; - } -} -``` - -Output: - -``` -Press enter to connect, esc to exit. -Press any key to dispose of connection. -subscription : 0 -subscription : 1 -subscription : 2 -Press enter to connect, esc to exit. -Press any key to dispose of connection. -subscription : 0 -subscription : 1 -subscription : 2 -Press enter to connect, esc to exit. -``` - -Let us finally consider automatic disposal of a connection. We want a single sequence to be shared between subscriptions, as per the price stream example mentioned above. We also want to only have the sequence running hot if there are any subscribers. It seems therefore, not only obvious that there should be a mechanism for automatically connecting (once a subscription has been made), but also a mechanism for disconnecting (once there are no more subscriptions) from a sequence. First let us look at what happens to a sequence when we connect with no subscribers, and then later unsubscribe: - -```csharp -var period = TimeSpan.FromSeconds(1); -var observable = Observable.Interval(period) - .Do(l => Console.WriteLine("Publishing {0}", l)) //Side effect to show it is running - .Publish(); -observable.Connect(); - -Console.WriteLine("Press any key to subscribe"); -Console.ReadKey(); - -var subscription = observable.Subscribe(i => Console.WriteLine("subscription : {0}", i)); - -Console.WriteLine("Press any key to unsubscribe."); -Console.ReadKey(); - -subscription.Dispose(); - -Console.WriteLine("Press any key to exit."); -Console.ReadKey(); -``` - -Output: - -``` -Press any key to subscribe -Publishing 0 -Publishing 1 -Press any key to unsubscribe. -Publishing 2 -subscription : 2 -Publishing 3 -subscription : 3 -Press any key to exit. -Publishing 4 -Publishing 5 -``` - -A few things to note here: - - 1. I use the `Do` extension method to create side effects on the sequence (i.e. write to the console). -This allows us to see when the sequence is actually connected. - 2. We connect first and then subscribe, which means that we can publish without any live subscriptions i.e. make the sequence hot. - 3. We dispose of our subscription but do not dispose of the connection, which means the sequence will still be running. - -### RefCount - -Let us modify that last example by replacing uses of `Connnect()` by the extension method `RefCount`. This will "magically" implement our requirements for automatic disposal and lazy connection. `RefCount` will take an `IConnectableObservable` and turn it back into an `IObservable` while automatically implementing the "connect" and "disconnect" behavior we are looking for. - -```csharp -var period = TimeSpan.FromSeconds(1); -var observable = Observable.Interval(period) - .Do(l => Console.WriteLine("Publishing {0}", l)) //side effect to show it is running - .Publish() - .RefCount(); - -// observable.Connect(); Use RefCount instead now - -Console.WriteLine("Press any key to subscribe"); -Console.ReadKey(); - -var subscription = observable.Subscribe(i => Console.WriteLine("subscription : {0}", i)); - -Console.WriteLine("Press any key to unsubscribe."); -Console.ReadKey(); - -subscription.Dispose(); - -Console.WriteLine("Press any key to exit."); -Console.ReadKey(); -``` - -Output: - -``` -Press any key to subscribe -Press any key to unsubscribe. -Publishing 0 -subscription : 0 -Publishing 1 -subscription : 1 -Publishing 2 -subscription : 2 -Press any key to exit. -``` - -The `Publish`/`RefCount` pair is extremely useful for taking a cold observable and sharing it as a hot observable sequence for subsequent observers. `RefCount()` also allows us to avoid a race condition. In the example above, we subscribed to the sequence before a connection was established. This is not always possible, especially if we are exposing the sequence from a method. By using the `RefCount` method we can mitigate the subscribe/connect race condition because of the auto-connect behavior. - -##Other connectable observables - -The `Connect` method is not the only method that returns `IConnectableObservable` instances. The ability to connect or defer an operator's functionality is useful in other areas too. - -### PublishLast - -The `PublishLast()` method is effectively a non-blocking `Last()` call. You can consider it similar to an `AsyncSubject` wrapping your target sequence. You get equivalent semantics to `AsyncSubject` where only the last value is published, and only once the sequence completes. - -```csharp -var period = TimeSpan.FromSeconds(1); -var observable = Observable.Interval(period) - .Take(5) - .Do(l => Console.WriteLine("Publishing {0}", l)) //side effect to show it is running - .PublishLast(); - -observable.Connect(); - -Console.WriteLine("Press any key to subscribe"); -Console.ReadKey(); - -var subscription = observable.Subscribe(i => Console.WriteLine("subscription : {0}", i)); - -Console.WriteLine("Press any key to unsubscribe."); -Console.ReadKey(); - -subscription.Dispose(); - -Console.WriteLine("Press any key to exit."); -Console.ReadKey(); -``` - -Output: - -``` -Press any key to subscribe -Publishing 0 -Publishing 1 -Press any key to unsubscribe. -Publishing 2 -Publishing 3 -Publishing 4 -subscription : 4 -Press any key to exit. -``` - -### Replay - -The `Replay` extension method allows you take an existing observable sequence and give it 'replay' semantics as per `ReplaySubject`. As a reminder, the `ReplaySubject` will cache all values so that any late subscribers will also get all of the values. In this example, two subscriptions are made on time, and then a third subscription can be made after the sequence completes. Even though the third subscription is made after the underlying sequence has completed, we can still get all of the values. - -```csharp -var period = TimeSpan.FromSeconds(1); -var hot = Observable.Interval(period) - .Take(3) - .Publish(); - -hot.Connect(); - -Thread.Sleep(period); //Run hot and ensure a value is lost. - -var observable = hot.Replay(); - -observable.Connect(); - -observable.Subscribe(i => Console.WriteLine("first subscription : {0}", i)); - -Thread.Sleep(period); - -observable.Subscribe(i => Console.WriteLine("second subscription : {0}", i)); - -Console.ReadKey(); - -observable.Subscribe(i => Console.WriteLine("third subscription : {0}", i)); - -Console.ReadKey(); -``` - -Output: - -``` -first subscription : 1 -second subscription : 1 -first subscription : 2 -second subscription : 2 -third subscription : 1 -third subscription : 2 -``` - -The `Replay` extension method has several overloads that match the `ReplaySubject` constructor overloads; you are able to specify the buffer size by count or by time. - -### Multicast - -The `PublishLast` and `Replay` methods effectively apply `AsyncSubject` and `ReplaySubject` functionality to the underlying observable sequence. We could attempt to build a crude implementation ourselves. - -```csharp -var period = TimeSpan.FromSeconds(1); - -// var observable = Observable.Interval(period).Publish(); -var observable = Observable.Interval(period); -var shared = new Subject(); - -shared.Subscribe(i => Console.WriteLine("first subscription : {0}", i)); -observable.Subscribe(shared); //'Connect' the observable. - -Thread.Sleep(period); -Thread.Sleep(period); - -shared.Subscribe(i => Console.WriteLine("second subscription : {0}", i)); -``` - -Output: - -``` -first subscription : 0 -first subscription : 1 -second subscription : 1 -first subscription : 2 -second subscription : 2 -``` - -The Rx library supplies us with a great method to do this well though. You can apply subject behavior via the `Multicast` extension method. This allows you to share or "multicast" an observable sequence with the behavior of a specific subject. For example - - * `.Publish()` = `.Multicast(new Subject)` - * `.PublishLast()` = `.Multicast(new AsyncSubject)` - * `.Replay()` = `.Multicast(new ReplaySubject)` - -Hot and cold observables are two different styles of sharing an observable sequence. Both have equally valid applications but behave in different ways. Cold observables allow you to lazily evaluate an observable sequence independently for each subscriber. Hot observables allow you to share notifications by multicasting your sequence, even if there are no subscribers. The use of `RefCount` allows you to have lazily-evaluated, multicast observable sequences, coupled with eager disposal semantics once the last subscription is disposed. - - \ No newline at end of file diff --git a/content/15_PublishingOperators.md b/content/15_PublishingOperators.md new file mode 100644 index 0000000..bbff8f0 --- /dev/null +++ b/content/15_PublishingOperators.md @@ -0,0 +1,411 @@ +--- +title: Publishing operators +--- + +# Publishing operators + +Hot sources need to be able to deliver events to multiple subscribers. While we can implement the subscriber tracking ourselves, it can be easier to write an oversimplified source that works only for a single subscriber. And although that won't be a full implementation of `IObservable`, that won't matter if we then use one of Rx's _multicast_ operators to publish it as a multi-subscriber hot source. The example in ["Representing Filesystem Events in Rx"](03_CreatingObservableSequences.md#representing-filesystem-events-in-rx) used this trick, but as you'll see in this chapter there are a few variations on the theme. + +## Multicast + +Rx offers three operators enabling us to support multiple subscribers using just a single subscription to some underlying source: [`Publish`](#publish), [`PublishLast`](#publishlast), and [`Replay`](#replay). All three of these are wrappers around Rx's `Multicast` operator, which provides the common mechanism at the heart of all of them. + +`Multicast` turns any `IObservable` into an `IConnectableObservable` which, as you can see, just adds a `Connect` method: + +```cs +public interface IConnectableObservable : IObservable +{ + IDisposable Connect(); +} +``` + +Since it derives from `IObservable`, you can call `Subscribe` on an `IConnectableObservable`, but the implementation returned by `Multicast` won't call `Subscribe` on the underlying source when you do that. It only calls `Subscribe` on the underlying source when you call `Connect`. So that we can see this in action, let's define a source that prints out a message each time `Subscribe` is called: + +```cs +IObservable src = Observable.Create(obs => +{ + Console.WriteLine("Create callback called"); + obs.OnNext(1); + obs.OnNext(2); + obs.OnCompleted(); + return Disposable.Empty; +}); +``` + +Since this is only going to be invoked once no matter how many observers subscribe, `Multicast` can't pass on the `IObserver`s handed to its own `Subscribe` method, because there could be any number of them. It uses a [Subject](03_CreatingObservableSequences.md#subject) as the single `IObserver` that is passes to the underlying source, and this subject is also responsible for keeping track of all subscribers. If we call `Multicast` directly, we are required to pass in the subject we want to use: + +```cs +IConnectableObservable m = src.Multicast(new Subject()); +``` + +We can now subscribe to this a few times over: + +```cs +m.Subscribe(x => Console.WriteLine($"Sub1: {x}")); +m.Subscribe(x => Console.WriteLine($"Sub2: {x}")); +m.Subscribe(x => Console.WriteLine($"Sub3: {x}")); +``` + +None of these subscribers will receive anything unless we call `Connect`: + +```cs +m.Connect(); +``` + +**Note**: `Connect` returns an `IDisposable`. Calling `Dispose` on that unsubscribes from the underlying source. + +This call to `Connect` causes the following output: + +```cs +Create callback called +Sub1: 1 +Sub2: 1 +Sub3: 1 +Sub1: 2 +Sub2: 2 +Sub3: 2 +``` + +As you can see, the method we passed to `Create` runs only once, confirming that `Multicast` did only subscribe once, despite us calling `Subscribe` three times over. But each item went to all three subscriptions. + +The way `Multicast` works is fairly straightforward: it gets the subject do most of the work. Whenever you call `Subscribe` on an observable returned by `Multicast`, it just calls `Subscribe` on the subject. And when you call `Connect`, it just passes the subject into the underlying source's `Subscribe`. So this code would have had the same effect: + +```cs +var s = new Subject(); + +s.Subscribe(x => Console.WriteLine($"Sub1: {x}")); +s.Subscribe(x => Console.WriteLine($"Sub2: {x}")); +s.Subscribe(x => Console.WriteLine($"Sub3: {x}")); + +src.Subscribe(s); +``` + +However, an advantage of `Multicast` is that it returns `IConnectableObservable`, and as we'll see later, some other parts of Rx know how to work with this interface. + +`Multicast` offers an overload that works in a quite different way: it is intended for scenarios where you want to write a query that uses its source observable twice. For example, we might want to get adjacent pairs of items using `Zip`: + +```cs +IObservable<(int, int)> ps = src.Zip(src.Skip(1)); +ps.Subscribe(ps => Console.WriteLine(ps)); +``` + +(Although [`Buffer`](08_Partitioning.md#buffer) might seem like a more obvious way to do this, one advantage of this `Zip` approach is that it will never give us half of a pair. When we ask `Buffer` for pairs, it will give us a single-item buffer when we reach the end, which can require extra code to work around.) + +The problem with this approach is that the source will see two subscriptions: one directly from `Zip`, and then a second one through `Skip`. If we were to run the code above, we'd see this output: + +``` +Create callback called +Create callback called +(1, 2) +``` + +Our `Create` callback ran twice. The second `Multicast` overload lets us avoid that: + +```cs +IObservable<(int, int)> ps = src.Multicast(() => new Subject(), s => s.Zip(s.Skip(1))); +ps.Subscribe(ps => Console.WriteLine(ps)); +``` + +As the output shows, this avoids the multiple subscriptions: + +```cs +Create callback called +(1, 2) +``` + +This overload of `Multicast` returns a normal `IObservable`. This means we don't need to call `Connect`. But it also means that each subscription to the resulting `IObservable` causes a subscription to the underlying source. But for the scenario it is designed for this is fine: we're just trying to avoid getting twice as many subscriptions to the underlying source. + +The remaining operators defined in this section, `Publish`, `PublishLast`, and `Replay`, are all wrappers around `Multicast`, each supplying a specific type of subject for you. + +### Publish + +The `Publish` operator calls `Multicast` with a [`Subject`](03_CreatingObservableSequences.md#subject). The effect of this is that once you have called `Connect` on the result, any items produced by the source will be delivered to all subscribers. This enables me to replace this earlier example: + +```cs +IConnectableObservable m = src.Multicast(new Subject()); +``` + +with this: + +```cs +IConnectableObservable m = src.Publish(); +``` + +These are exactly equivalent. + +Because `Subject` forwards all incoming `OnNext` calls to each of its subscribers immediately, and because it doesn't store any previously made calls, the result is a hot source. If you attach some subscribers before calling `Connect`, and then you attached more subscribers after calling `Connect`, those later subscribers will only receive events that occurred after they subscribed. This example demonstrates that: + +```cs +IConnectableObservable publishedTicks = Observable + .Interval(TimeSpan.FromSeconds(1)) + .Take(4) + .Publish(); + +publishedTicks.Subscribe(x => Console.WriteLine($"Sub1: {x} ({DateTime.Now})")); +publishedTicks.Subscribe(x => Console.WriteLine($"Sub2: {x} ({DateTime.Now})")); + +publishedTicks.Connect(); +Thread.Sleep(2500); +Console.WriteLine(); +Console.WriteLine("Adding more subscribers"); +Console.WriteLine(); + +publishedTicks.Subscribe(x => Console.WriteLine($"Sub3: {x} ({DateTime.Now})")); +publishedTicks.Subscribe(x => Console.WriteLine($"Sub4: {x} ({DateTime.Now})")); +``` + +The following output shows that we only see output for the Sub3 and Sub4 subscriptions for the final 2 events: + +``` +Sub1: 0 (10/08/2023 16:04:02) +Sub2: 0 (10/08/2023 16:04:02) +Sub1: 1 (10/08/2023 16:04:03) +Sub2: 1 (10/08/2023 16:04:03) + +Adding more subscribers + +Sub1: 2 (10/08/2023 16:04:04) +Sub2: 2 (10/08/2023 16:04:04) +Sub3: 2 (10/08/2023 16:04:04) +Sub4: 2 (10/08/2023 16:04:04) +Sub1: 3 (10/08/2023 16:04:05) +Sub2: 3 (10/08/2023 16:04:05) +Sub3: 3 (10/08/2023 16:04:05) +Sub4: 3 (10/08/2023 16:04:05) +``` + +As with [`Multicast`](#multicast), `Publish` offers an overload that provides per-top-level-subscription multicast. This lets us simplify the example from the end of that section from this: + +```cs +IObservable<(int, int)> ps = src.Multicast(() => new Subject(), s => s.Zip(s.Skip(1))); +ps.Subscribe(ps => Console.WriteLine(ps)); +``` + +to this: + +```cs +IObservable<(int, int)> ps = src.Publish(s => s.Zip(s.Skip(1))); +ps.Subscribe(ps => Console.WriteLine(ps)); +``` + +`Publish` offers overloads that let you specify an initial value. These use [`BehaviorSubject`](03_CreatingObservableSequences.md#behaviorsubject) instead of `Subject`. The difference here is that all subscribers will immediately receive a value as soon as they subscribe. If the underlying source hasn't yet produced an item (or if `Connect` hasn't been called, meaning we've not even subscribed to the source yet) they will receive the initial value. And if at least one item has been received from the source, any new subscribers will instantly receive the latest value the source produced, and will then go on to receive any further new values. + +### PublishLast + +The `PublishLast` operator calls `Multicast` with an [`AsyncSubject`](03_CreatingObservableSequences.md#asyncsubject). The effect of this is that the final item produced by the source will be delivered to all subscribers. You still need to call `Connect`. This determines when subscription to the underlying source occurs. But all subscribers will receive the final event regardless of when they subscribe, because `AsyncSubject` remembers the final result. We can see this in action with the following example: + +```cs +IConnectableObservable pticks = Observable + .Interval(TimeSpan.FromSeconds(0.1)) + .Take(4) + .PublishLast(); + +pticks.Subscribe(x => Console.WriteLine($"Sub1: {x} ({DateTime.Now})")); +pticks.Subscribe(x => Console.WriteLine($"Sub2: {x} ({DateTime.Now})")); + +pticks.Connect(); +Thread.Sleep(3000); +Console.WriteLine(); +Console.WriteLine("Adding more subscribers"); +Console.WriteLine(); + +pticks.Subscribe(x => Console.WriteLine($"Sub3: {x} ({DateTime.Now})")); +pticks.Subscribe(x => Console.WriteLine($"Sub4: {x} ({DateTime.Now})")); +``` + +This creates a source that produces 4 values in the space of 0.4 seconds. It attaches a couple of subscribers to the `IConnectableObservable` returned by `PublishLast` and then immediately calls `Connect`. Then it sleeps for 1 second, which gives the source time to complete. This means that those first two subscribers will receive the one and only value they will ever receive (the last value in the sequence) before that call to `Thread.Sleep` returns. But we then go on to attach two more subscribers. As the output shows, these also receive that same final event: + +``` +Sub1: 3 (11/14/2023 9:15:46 AM) +Sub2: 3 (11/14/2023 9:15:46 AM) + +Adding more subscribers + +Sub3: 3 (11/14/2023 9:15:49 AM) +Sub4: 3 (11/14/2023 9:15:49 AM) +``` + +These last two subscribers receive the value later because they subscribed later, but the `AsyncSubject` created by `PublishLast` is just replaying the final value it received to these late subscribers. + + +### Replay + +The `Replay` operator calls `Multicast` with a [`ReplaySubject`](03_CreatingObservableSequences.md#replaysubject). The effect of this is that any subscribers attached before calling `Connect` just receive all events as the underlying source produces them, but any subscribers attached later effectively get to 'catch up', because the `ReplaySubject` remembers events it has already seen, and replays them to new subscribers. + +This example is very similar to the one used for `Publish`: + +```cs +IConnectableObservable pticks = Observable + .Interval(TimeSpan.FromSeconds(1)) + .Take(4) + .Replay(); + +pticks.Subscribe(x => Console.WriteLine($"Sub1: {x} ({DateTime.Now})")); +pticks.Subscribe(x => Console.WriteLine($"Sub2: {x} ({DateTime.Now})")); + +pticks.Connect(); +Thread.Sleep(2500); +Console.WriteLine(); +Console.WriteLine("Adding more subscribers"); +Console.WriteLine(); + +pticks.Subscribe(x => Console.WriteLine($"Sub3: {x} ({DateTime.Now})")); +pticks.Subscribe(x => Console.WriteLine($"Sub4: {x} ({DateTime.Now})")); +``` + +This creates a source that will produce items regularly for 4 seconds. It attaches two subscribers before calling `Connect`. It then waits long enough for the first two events to emerge before attaching two more subscribers. But unlike with `Publish`, those late subscribers will see the events that happened before they subscribed: + +``` +Sub1: 0 (10/08/2023 16:18:22) +Sub2: 0 (10/08/2023 16:18:22) +Sub1: 1 (10/08/2023 16:18:23) +Sub2: 1 (10/08/2023 16:18:23) + +Adding more subscribers + +Sub3: 0 (10/08/2023 16:18:24) +Sub3: 1 (10/08/2023 16:18:24) +Sub4: 0 (10/08/2023 16:18:24) +Sub4: 1 (10/08/2023 16:18:24) +Sub1: 2 (10/08/2023 16:18:24) +Sub2: 2 (10/08/2023 16:18:24) +Sub3: 2 (10/08/2023 16:18:24) +Sub4: 2 (10/08/2023 16:18:24) +Sub1: 3 (10/08/2023 16:18:25) +Sub2: 3 (10/08/2023 16:18:25) +Sub3: 3 (10/08/2023 16:18:25) +Sub4: 3 (10/08/2023 16:18:25) +``` + +They receive them late of course, because they subscribed late. So we see a quick flurry of events reported as `Sub3` and `Sub4` catch up, but once they have caught up, they then receive all further events immediately. + +The `ReplaySubject` that enables this behaviour will consume memory to store events. As you may recall, this subject type can be configured to store only a limited number of events, or not to hold onto events older than some specified time limit. The `Replay` operator provides overloads that enable you to configure these kinds of limits. + +`Replay` also supports the per-subscription-multicast model I showed for the other `Multicast`-based operators in this section. + + +## RefCount + +We saw in the preceding section that `Multicast` (and also its various wrappers) supports two usage models: + +* returning an `IConnectableObservable` to allow top-level control of when subscription to the underlying source occurs +* returning an ordinary `IObservable`, enabling us to avoid unnecessary multiple subscriptions to the source when using a query that uses the source in multiple places (e.g., `s.Zip(s.Take(1))`), but still making one `Subscribe` call to the underlying source for each top-level `Subscribe` + +`RefCount` offers a slightly different model. It enables subscription to the underlying source to be triggered by an ordinary `Subscribe`, but can still make just a single call to the underlying source. This might be useful in the AIS example used throughout this book. You might want to attach multiple subscribers to an observable source that reports the location messages broadcast by ships and other vessels, but you would normally want a library presenting an Rx-based API for this to connect only once to any underlying service providing those messages. And you would most likely want it to connect only when at least one subscriber is listening. `RefCount` would be ideal for this because it enables a single source to support multiple subscribers, and for the underlying source to know when we move between the "no subscribers" and "at least one subscriber" states. + +To be able to observe how `RefCount` operators, I'm going to use a modified version of the source that reports when subscription occurs: + +```cs +IObservable src = Observable.Create(async obs => +{ + Console.WriteLine("Create callback called"); + obs.OnNext(1); + await Task.Delay(250).ConfigureAwait(false); + obs.OnNext(2); + await Task.Delay(250).ConfigureAwait(false); + obs.OnNext(3); + await Task.Delay(250).ConfigureAwait(false); + obs.OnNext(4); + await Task.Delay(100).ConfigureAwait(false); + obs.OnCompleted(); +}); +``` + +Unlike the earlier example, this uses `async` and delays between each `OnNext` to ensure that the main thread has time to set up multiple subscriptions before all the items are produced. We can then wrap this with `RefCount`: + +```cs +IObservable rc = src + .Publish() + .RefCount(); +``` + +Notice that I have to call `Publish` first. This is because `RefCount` expects an `IConnectableObservable`. It wants to start the source only when something first subscribes. It will call `Connect` as soon as there's at least one subscriber. Let's try it: + +```cs +rc.Subscribe(x => Console.WriteLine($"Sub1: {x} ({DateTime.Now})")); +rc.Subscribe(x => Console.WriteLine($"Sub2: {x} ({DateTime.Now})")); +Thread.Sleep(600); +Console.WriteLine(); +Console.WriteLine("Adding more subscribers"); +Console.WriteLine(); +rc.Subscribe(x => Console.WriteLine($"Sub3: {x} ({DateTime.Now})")); +rc.Subscribe(x => Console.WriteLine($"Sub4: {x} ({DateTime.Now})")); +``` + +Here's the output: + +``` +Create callback called +Sub1: 1 (10/08/2023 16:36:44) +Sub1: 2 (10/08/2023 16:36:45) +Sub2: 2 (10/08/2023 16:36:45) +Sub1: 3 (10/08/2023 16:36:45) +Sub2: 3 (10/08/2023 16:36:45) + +Adding more subscribers + +Sub1: 4 (10/08/2023 16:36:45) +Sub2: 4 (10/08/2023 16:36:45) +Sub3: 4 (10/08/2023 16:36:45) +Sub4: 4 (10/08/2023 16:36:45) +``` + +Notice that only `Sub1` receives the very first event. That's because the callback passed to `Create` produces that immediately. Only when it invokes its first `await` does it return to the caller, enabling us to attach the second subscriber. That has already missed the first event, but as you can see it receives the 2nd and 3rd. The code waits long enough for the first three events to occur before attaching two more subscribers, and you can see that all four subscribers receive the final event. + +As the name suggests `RefCount` counts the number of active subscribers. If this ever drops to 0, it will call `Dispose` on the object that `Connect` returned, shutting down the subscription. If further subscribers attach, it will restart. This example shows that: + +```cs +IDisposable s1 = rc.Subscribe(x => Console.WriteLine($"Sub1: {x} ({DateTime.Now})")); +IDisposable s2 = rc.Subscribe(x => Console.WriteLine($"Sub2: {x} ({DateTime.Now})")); +Thread.Sleep(600); + +Console.WriteLine(); +Console.WriteLine("Removing subscribers"); +s1.Dispose(); +s2.Dispose(); +Thread.Sleep(600); +Console.WriteLine(); + +Console.WriteLine(); +Console.WriteLine("Adding more subscribers"); +Console.WriteLine(); +rc.Subscribe(x => Console.WriteLine($"Sub3: {x} ({DateTime.Now})")); +rc.Subscribe(x => Console.WriteLine($"Sub4: {x} ({DateTime.Now})")); +``` + +We get this output: + +``` +Create callback called +Sub1: 1 (10/08/2023 16:40:39) +Sub1: 2 (10/08/2023 16:40:39) +Sub2: 2 (10/08/2023 16:40:39) +Sub1: 3 (10/08/2023 16:40:39) +Sub2: 3 (10/08/2023 16:40:39) + +Removing subscribers + + +Adding more subscribers + +Create callback called +Sub3: 1 (10/08/2023 16:40:40) +Sub3: 2 (10/08/2023 16:40:40) +Sub4: 2 (10/08/2023 16:40:40) +Sub3: 3 (10/08/2023 16:40:41) +Sub4: 3 (10/08/2023 16:40:41) +Sub3: 4 (10/08/2023 16:40:41) +Sub4: 4 (10/08/2023 16:40:41) +``` + +This time, the `Create` callback ran twice. That's because the number of active subscribers dropped to 0, so `RefCount` called `Dispose` to shut things down. When new subscribers came along, it called `Connect` again to start things back up. There are some overloads enabling you to specify a `disconnectDelay`. This tells it to wait for the specified time after the number of subscribers drops to zero before disconnecting, to see if any new subscribers come along. But it will still disconnect if the specified time elapses. If that's not what you want, the next operator might be for you. + + +## AutoConnect + +The `AutoConnect` operator behaves in much the same way as `RefCount`, in that it calls `Connect` on its underlying `IConnectableObservable` when the first subscriber subscribers. The difference is that it doesn't attempt to detect when the number of active subscribers has dropped to zero: once it connects, it remains connected indefinitely, even if it has no subscribers. + +Although `AutoConnect` can be convenient, you need to be a little careful, because it can cause leaks: it will never disconnect automatically. It is still possible to tear down the connection it creates: `AutoConnect` accepts an optional argument of type `Action`. It invokes this when it first connects to the source, passing you the `IDisposable` returned by the source's `Connect` method. You can shut it down by calling `Dispose`. + + +The operators in this chapter can be useful whenever you have a source that is not well suited do dealing with multiple subscribers. It provides various ways to attach multiple subscribers while only triggering a single `Subscribe` to the underlying source. \ No newline at end of file diff --git a/content/15_SchedulingAndThreading.md b/content/15_SchedulingAndThreading.md deleted file mode 100644 index ea439a2..0000000 --- a/content/15_SchedulingAndThreading.md +++ /dev/null @@ -1,1190 +0,0 @@ ---- -title: Scheduling and threading ---- - -# PART 4 - Concurrency - -Rx is primarily a system for querying _data in motion_ asynchronously. To effectively provide the level of asynchrony that developers require, some level of concurrency control is required. We need the ability to generate sequence data concurrently to the consumption of the sequence data. - -In this fourth and final part of the book, we will look at the various concurrency considerations one must undertake when querying data in motion. We will look how to avoid concurrency when possible and use it correctly when justifiable. We will look at the excellent abstractions Rx provides, that enable concurrency to become declarative and also unit testable. In my opinion, theses two features are enough reason alone to adopt Rx into your code base. We will also look at the complex issue of querying concurrent sequences and analyzing data in sliding windows of time. - -# Scheduling and threading - -So far, we have managed to avoid any explicit usage of threading or concurrency. There are some methods that we have covered that implicitly introduce some level of concurrency to perform their jobs (e.g. `Buffer`, `Delay`, `Sample` each require a separate thread/scheduler/timer to work their magic). Most of this however, has been kindly abstracted away from us. This chapter will look at the elegant beauty of the Rx API and its ability to effectively remove the need for `WaitHandle` types, and any explicit calls to `Thread`s, the `ThreadPool` or `Task`s. - -## Rx is single-threaded by default - -To be more accurate, Rx is a free threaded model. - -A popular misconception is that Rx is multithreaded by default. It is perhaps more an idle assumption than a strong belief, much in the same way some assume that standard .NET events are multithreaded until they challenge that notion. We debunk this myth and assert that events are most certainly single threaded and synchronous in the [Appendix](19_DispellingMyths.html#DispellingEventMyths). - -Like events, Rx is just a way of chaining callbacks together for a given notification. While Rx is a free-threaded model, this does not mean that subscribing or calling `OnNext` will introduce multi-threading to your sequence. Being free-threaded means that you are not restricted to which thread you choose to do your work. For example, you can choose to do your work such as invoking a subscription, observing or producing notifications, on any thread you like. The alternative to a free-threaded model is a _Single Threaded Apartment_ (STA) model where you must interact with the system on a given thread. It is common to use the STA model when working with User Interfaces and some COM interop. So, just as a recap: if you do not introduce any scheduling, your callbacks will be invoked on the same thread that the `OnNext`/`OnError`/`OnCompleted` methods are invoked from. - -In this example, we create a subject then call `OnNext` on various threads and record the threadId in our handler. - -```csharp -Console.WriteLine("Starting on threadId:{0}", Thread.CurrentThread.ManagedThreadId); -var subject = new Subject(); - -subject.Subscribe( - o => Console.WriteLine("Received {1} on threadId:{0}", - Thread.CurrentThread.ManagedThreadId, - o)); - -ParameterizedThreadStart notify = obj => -{ - Console.WriteLine("OnNext({1}) on threadId:{0}", - Thread.CurrentThread.ManagedThreadId, - obj); - subject.OnNext(obj); -}; - -notify(1); -new Thread(notify).Start(2); -new Thread(notify).Start(3); -``` - -Output: - -``` -Starting on threadId:9 -OnNext(1) on threadId:9 -Received 1 on threadId:9 -OnNext(2) on threadId:10 -Received 2 on threadId:10 -OnNext(3) on threadId:11 -Received 3 on threadId:11 -``` - -Note that each `OnNext` was called back on the same thread that it was notified on. This is not always what we are looking for. Rx introduces a very handy mechanism for introducing concurrency and multithreading to your code: Scheduling. - -## SubscribeOn and ObserveOn - -In the Rx world, there are generally two things you want to control the concurrency model for: - -- The invocation of the subscription -- The observing of notifications - -As you could probably guess, these are exposed via two extension methods to `IObservable` called `SubscribeOn` and `ObserveOn`. Both methods have an overload that take an `IScheduler` (or `SynchronizationContext`) and return an `IObservable` so you can chain methods together. - -```csharp -public static class Observable -{ - public static IObservable ObserveOn( - this IObservable source, - IScheduler scheduler) - {...} - - public static IObservable ObserveOn( - this IObservable source, - SynchronizationContext context) - {...} - - public static IObservable SubscribeOn( - this IObservable source, - IScheduler scheduler) - {...} - - public static IObservable SubscribeOn( - this IObservable source, - SynchronizationContext context) - {...} -} -``` - -One pitfall I want to point out here is, the first few times I used these overloads, I was confused as to what they actually do. You should use the `SubscribeOn` method to describe how you want any warm-up and background processing code to be scheduled. For example, if you were to use `SubscribeOn` with `Observable.Create`, the delegate passed to the `Create` method would be run on the specified scheduler. - -In this example, we have a sequence produced by `Observable.Create` with a standard subscription. - -```csharp -Console.WriteLine("Starting on threadId:{0}", Thread.CurrentThread.ManagedThreadId); - -var source = Observable.Create(o => -{ - Console.WriteLine("Invoked on threadId:{0}", Thread.CurrentThread.ManagedThreadId); - o.OnNext(1); - o.OnNext(2); - o.OnNext(3); - o.OnCompleted(); - Console.WriteLine("Finished on threadId:{0}", - Thread.CurrentThread.ManagedThreadId); - return Disposable.Empty; -}); - -source - //.SubscribeOn(Scheduler.ThreadPool) - .Subscribe( - o => Console.WriteLine("Received {1} on threadId:{0}", Thread.CurrentThread.ManagedThreadId, o), - () => Console.WriteLine("OnCompleted on threadId:{0}", Thread.CurrentThread.ManagedThreadId) - ); - -Console.WriteLine("Subscribed on threadId:{0}", Thread.CurrentThread.ManagedThreadId); -``` - -Output: - -``` -Starting on threadId:9 -Invoked on threadId:9 -Received 1 on threadId:9 -Received 2 on threadId:9 -Received 3 on threadId:9 -OnCompleted on threadId:9 -Finished on threadId:9 -Subscribed on threadId:9 -``` - -You will notice that all actions were performed on the same thread. Also, note that everything is sequential. When the subscription is made, the `Create` delegate is called. When `OnNext(1)` is called, the `OnNext` handler is called, and so on. This all stays synchronous until the `Create` delegate is finished, and the `Subscribe` line can move on to the final line that declares we are subscribed on thread 9. - -If we apply `SubscribeOn` to the chain (i.e. un-comment it), the order of execution is quite different. - -``` -Starting on threadId:9 -Subscribed on threadId:9 -Invoked on threadId:10 -Received 1 on threadId:10 -Received 2 on threadId:10 -Received 3 on threadId:10 -OnCompleted on threadId:10 -Finished on threadId:10 -``` - -Observe that the subscribe call is now non-blocking. The `Create` delegate is executed on the thread pool and so are all our handlers. - -The `ObserveOn` method is used to declare where you want your notifications to be scheduled to. I would suggest the `ObserveOn` method is most useful when working with STA systems, most commonly UI applications. When writing UI applications, the `SubscribeOn`/`ObserveOn` pair is very useful for two reasons: - -- you do not want to block the UI thread -- but you do need to update UI objects on the UI thread. - -It is critical to avoid blocking the UI thread, as doing so leads to a poor user experience. General guidance for Silverlight and WPF is that any work that blocks for longer than 150-250ms should not be performed on the UI thread (Dispatcher). This is approximately the period of time over which a user can notice a lag in the UI (mouse becomes sticky, animations sluggish). In the upcoming Metro style apps for Windows 8, the maximum allowed blocking time is only 50ms. This more stringent rule is to ensure a consistent fast and fluid experience across applications. With the processing power offered by current desktop processors, you can achieve a lot of processing 50ms. However, as processor become more varied (single/multi/many core, plus high power desktop vs. lower power ARM tablet/phones), how much you can do in 50ms fluctuates widely. In general terms: any I/O, computational intensive work or any processing unrelated to the UI should be marshaled off the UI thread. The general pattern for creating responsive UI applications is: - -- respond to some sort of user action -- do work on a background thread -- pass the result back to the UI thread -- update the UI - -This is a great fit for Rx: responding to events, potentially composing multiple events, passing data to chained method calls. With the inclusion of scheduling, we even have the power to get off and back onto the UI thread for that responsive application feel that users demand. - -Consider a WPF application that used Rx to populate an `ObservableCollection`. You would almost certainly want to use `SubscribeOn` to leave the `Dispatcher`, followed by `ObserveOn` to ensure you were notified back on the Dispatcher. If you failed to use the `ObserveOn` method, then your `OnNext` handlers would be invoked on the same thread that raised the notification. In Silverlight/WPF, this would cause some sort of not-supported/cross-threading exception. In this example, we subscribe to a sequence of `Customers`. We perform the subscription on a new thread and ensure that as we receive `Customer` notifications, we add them to the `Customers` collection on the `Dispatcher`. - -```csharp -_customerService.GetCustomers() - .SubscribeOn(Scheduler.NewThread) - .ObserveOn(DispatcherScheduler.Instance) - // or .ObserveOnDispatcher() - .Subscribe(Customers.Add); -``` - -## Schedulers - -The `SubscribeOn` and `ObserveOn` methods required us to pass in an `IScheduler`. Here we will dig a little deeper and see what schedulers are, and what implementations are available to us. - -There are two main types we use when working with schedulers: - -
-
The `IScheduler` interface
-
A common interface for all schedulers
-
The static `Scheduler` class
-
Exposes both implementations of `IScheduler` and helpful extension methods to the `IScheduler` interface
-
- -The `IScheduler` interface is of less importance right now than the types that implement the interface. The key concept to understand is that an `IScheduler` in Rx is used to schedule some action to be performed, either as soon as possible or at a given point in the future. The implementation of the `IScheduler` defines how that action will be invoked i.e. asynchronously via a thread pool, a new thread or a message pump, or synchronously on the current thread. Depending on your platform (Silverlight 4, Silverlight 5, .NET 3.5, .NET 4.0), you will be exposed most of the implementations you will need via a static class `Scheduler`. - -Before we look at the `IScheduler` interface in detail, let's look at the extension method we will use the most often and then introduce the common implementations. - -This is the most commonly used (extension) method for `IScheduler`. It simply sets an action to be performed as soon as possible. - -```csharp -public static IDisposable Schedule(this IScheduler scheduler, Action action) -{...} -``` - -You could use the method like this: - -```csharp -IScheduler scheduler = ...; -scheduler.Schedule(()=>{ Console.WriteLine("Work to be scheduled"); }); -``` - -These are the static properties that you can find on the `Scheduler` type. - -- `Scheduler.Immediate` will ensure the action is not scheduled, but rather executed immediately. -- `Scheduler.CurrentThread` ensures that the actions are performed on the thread that made the original call. This is different from `Immediate`, as `CurrentThread` will queue the action to be performed. We will compare these two schedulers using a code example soon. -- `Scheduler.NewThread` will schedule work to be done on a new thread. -- `Scheduler.ThreadPool` will schedule all actions to take place on the Thread Pool. -- `Scheduler.TaskPool` will schedule actions onto the TaskPool. This is not available in Silverlight 4 or .NET 3.5 builds. - -If you are using WPF or Silverlight, then you will also have access to `DispatcherScheduler.Instance`. This allows you to schedule tasks onto the `Dispatcher` with the common interface, either now or in the future. There is the `SubscribeOnDispatcher()` and `ObserveOnDispatcher()` extension methods to `IObservable`, that also help you access the Dispatcher. While they appear useful, you will want to avoid these two methods for production code, and we explain why in the [Testing Rx](16_TestingRx.html) chapter. - -Most of the schedulers listed above are quite self explanatory for basic usage. We will take an in-depth look at all of the implementations of `IScheduler` later in the chapter. - -## Concurrency pitfalls - -Introducing concurrency to your application will increase its complexity. If your application is not noticeably improved by adding a layer of concurrency, then you should avoid doing so. Concurrent applications can exhibit maintenance problems with symptoms surfacing in the areas of debugging, testing and refactoring. - -The common problem that concurrency introduces is unpredictable timing. Unpredictable timing can be caused by variable load on a system, as well as variations in system configurations (e.g. varying core clock speed and availability of processors). These can ultimately can result in race conditions. Symptoms of race conditions include out-of-order execution, [deadlocks](http://en.wikipedia.org/wiki/Deadlock), [livelocks](http://en.wikipedia.org/wiki/Deadlock#Livelock) and corrupted state. - -In my opinion, the biggest danger when introducing concurrency haphazardly to an application, is that you can silently introduce bugs. These defects may slip past Development, QA and UAT and only manifest themselves in Production environments. Rx, however, does such a good job of simplifying the concurrent processing of observable sequences that many of these concerns can be mitigated. You can still create problems, but if you follow the guidelines then you can feel a lot safer in the knowledge that you have heavily reduced the capacity for unwanted race conditions. - -In a later chapter, [Testing Rx](16_TestingRx.html), we will look at how Rx improves your ability to test concurrent workflows. - -### Lock-ups - -When working on my first commercial application that used Rx, the team found out the hard way that Rx code can most certainly deadlock. When you consider that some calls (like `First`, `Last`, `Single` and `ForEach`) are blocking, and that we can schedule work to be done in the future, it becomes obvious that a race condition can occur. This example is the simplest block I could think of. Admittedly, it is fairly elementary but it will get the ball rolling. - -```csharp -var sequence = new Subject(); - -Console.WriteLine("Next line should lock the system."); - -var value = sequence.First(); -sequence.OnNext(1); - -Console.WriteLine("I can never execute...."); -``` - -Hopefully, we won't ever write such code though, and if we did, our tests would give us quick feedback that things went wrong. More realistically, race conditions often slip into the system at integration points. The next example may be a little harder to detect, but is only small step away from our first, unrealistic example. Here, we block in the constructor of a UI element which will always be created on the dispatcher. The blocking call is waiting for an event that can only be raised from the dispatcher, thus creating a deadlock. - -```csharp -public Window1() -{ - InitializeComponent(); - DataContext = this; - Value = "Default value"; - - // Deadlock! We need the dispatcher to continue to allow me to click the button to produce a value - Value = _subject.First(); - - // This will give same result but will not be blocking (deadlocking). - _subject.Take(1).Subscribe(value => Value = value); -} - -private void MyButton_Click(object sender, RoutedEventArgs e) -{ - _subject.OnNext("New Value"); -} - -public string Value -{ - get { return _value; } - set - { - _value = value; - var handler = PropertyChanged; - if (handler != null) handler(this, new PropertyChangedEventArgs("Value")); - } -} -``` - -Next, we start seeing things that can become more sinister. The button's click handler will try to get the first value from an observable sequence exposed via an interface. - -```csharp -public partial class Window1 : INotifyPropertyChanged -{ - //Imagine DI here. - private readonly IMyService _service = new MyService(); - private int _value2; - - public Window1() - { - InitializeComponent(); - DataContext = this; - } - - public int Value2 - { - get { return _value2; } - set - { - _value2 = value; - var handler = PropertyChanged; - if (handler != null) handler(this, new PropertyChangedEventArgs("Value2")); - } - } - - #region INotifyPropertyChanged Members - public event PropertyChangedEventHandler PropertyChanged; - #endregion - - private void MyButton2_Click(object sender, RoutedEventArgs e) - { - Value2 = _service.GetTemperature().First(); - } -} -``` - -There is only one small problem here in that we block on the `Dispatcher` thread (`First` is a blocking call), however this manifests itself into a deadlock if the service code is written incorrectly. - -```csharp -class MyService : IMyService -{ - public IObservable GetTemperature() - { - return Observable.Create( - o => - { - o.OnNext(27); - o.OnNext(26); - o.OnNext(24); - return () => { }; - }) - .SubscribeOnDispatcher(); - } -} -``` - -This odd implementation, with explicit scheduling, will cause the three `OnNext` calls to be scheduled once the `First()` call has finished; however, `that` is waiting for an `OnNext` to be called: we are deadlocked. - -So far, this chapter may seem to say that concurrency is all doom and gloom by focusing on the problems you could face; this is not the intent though. -We do not magically avoid classic concurrency problems simply by adopting Rx. -Rx will however make it easier to get it right, provided you follow these two simple rules. - -- Only the final subscriber should be setting the scheduling -- Avoid using blocking calls: e.g. `First`, `Last` and `Single` - -The last example came unstuck with one simple problem; the service was dictating the scheduling paradigm when, really, it had no business doing so. Before we had a clear idea of where we should be doing the scheduling in my first Rx project, we had all sorts of layers adding 'helpful' scheduling code. What it ended up creating was a threading nightmare. When we removed all the scheduling code and then confined it it in a single layer (at least in the Silverlight client), most of our concurrency problems went away. I recommend you do the same. At least in WPF/Silverlight applications, the pattern should be simple: "Subscribe on a Background thread; Observe on the Dispatcher". - -## Advanced features of schedulers - -We have only looked at the most simple usage of schedulers so far: - -- Scheduling an action to be executed as soon as possible -- Scheduling the subscription of an observable sequence -- Scheduling the observation of notifications coming from an observable sequence - -Schedulers also provide more advanced features that can help you with various problems. - -### Passing state - -In the extension method to `IScheduler` we have looked at, you could only provide an `Action` to execute. This `Action` did not accept any parameters. If you want to pass state to the `Action`, you could use a closure to share the data like this: - -```csharp -var myName = "Lee"; -Scheduler.NewThread.Schedule(() => Console.WriteLine("myName = {0}", myName)); -``` - -This could create a problem, as you are sharing state across two different scopes. I could modify the variable `myName` and get unexpected results. - -In this example, we use a closure as above to pass state. I immediately modify the closure and this creates a race condition: will my modification happen before or after the state is used by the scheduler? - -```csharp -var myName = "Lee"; -scheduler.Schedule(() => Console.WriteLine("myName = {0}", myName)); -myName = "John"; // What will get written to the console? -``` - -In my tests, "John" is generally written to the console when `scheduler` is an instance of `NewThreadScheduler`. If I use the `ImmediateScheduler` then "Lee" would be written. The problem with this is the non-deterministic nature of the code. - -A preferable way to pass state is to use the `Schedule` overloads that accept state. This example takes advantage of this overload, giving us certainty about our state. - -```csharp -var myName = "Lee"; -scheduler.Schedule(myName, - (_, state) => - { - Console.WriteLine(state); - return Disposable.Empty; - }); -myName = "John"; -``` - -Here, we pass `myName` as the state. We also pass a delegate that will take the state and return a disposable. The disposable is used for cancellation; we will look into that later. The delegate also takes an `IScheduler` parameter, which we name "_" (underscore). This is the convention to indicate we are ignoring the argument. When we pass `myName` as the state, a reference to the state is kept internally. So when we update the `myName` variable to "John", the reference to "Lee" is still maintained by the scheduler's internal workings. - -Note that in our previous example, we modify the `myName` variable to point to a new instance of a string. If we were to instead have an instance that we actually modified, we could still get unpredictable behavior. In the next example, we now use a list for our state. After scheduling an action to print out the element count of the list, we modify that list. - -```csharp -var list = new List(); -scheduler.Schedule(list, - (innerScheduler, state) => - { - Console.WriteLine(state.Count); - return Disposable.Empty; - }); -list.Add(1); -``` - -Now that we are modifying shared state, we can get unpredictable results. In this example, we don't even know what type the scheduler is, so we cannot predict the race conditions we are creating. As with any concurrent software, you should avoid modifying shared state. - -### Future scheduling - -As you would expect with a type called "IScheduler", you are able to schedule an action to be executed in the future. You can do so by specifying the exact point in time an action should be invoked, or you can specify the period of time to wait until the action is invoked. This is clearly useful for features such as buffering, timers etc. - -Scheduling in the future is thus made possible by two styles of overloads, one that takes a `TimeSpan` and one that takes a `DateTimeOffset`. These are the two most simple overloads that execute an action in the future. - -```csharp -public static IDisposable Schedule( - this IScheduler scheduler, - TimeSpan dueTime, - Action action) -{...} - -public static IDisposable Schedule( - this IScheduler scheduler, - DateTimeOffset dueTime, - Action action) -{...} -``` - -You can use the `TimeSpan` overload like this: - -```csharp -var delay = TimeSpan.FromSeconds(1); -Console.WriteLine("Before schedule at {0:o}", DateTime.Now); - -scheduler.Schedule(delay, () => Console.WriteLine("Inside schedule at {0:o}", DateTime.Now)); -Console.WriteLine("After schedule at {0:o}", DateTime.Now); -``` - -Output: - -``` -Before schedule at 2012-01-01T12:00:00.000000+00:00 -After schedule at 2012-01-01T12:00:00.058000+00:00 -Inside schedule at 2012-01-01T12:00:01.044000+00:00 -``` - -We can see therefore that scheduling is non-blocking as the 'before' and 'after' calls are very close together in time. You can also see that approximately one second after the action was scheduled, it was invoked. - -You can specify a specific point in time to schedule the task with the `DateTimeOffset` overload. If, for some reason, the point in time you specify is in the past, then the action is scheduled as soon as possible. - -### Cancellation - -Each of the overloads to `Schedule` returns an `IDisposable`; this way, a consumer can cancel the scheduled work. In the previous example, we scheduled work to be invoked in one second. We could cancel that work by disposing of the cancellation token (i.e. the return value). - -```csharp -var delay = TimeSpan.FromSeconds(1); -Console.WriteLine("Before schedule at {0:o}", DateTime.Now); - -var token = scheduler.Schedule(delay, () => Console.WriteLine("Inside schedule at {0:o}", DateTime.Now)); -Console.WriteLine("After schedule at {0:o}", DateTime.Now); - -token.Dispose(); -``` - -Output: - -``` -Before schedule at 2012-01-01T12:00:00.000000+00:00 -After schedule at 2012-01-01T12:00:00.058000+00:00 -``` - -Note that the scheduled action never occurs, as we have cancelled it almost immediately. - -When the user cancels the scheduled action method before the scheduler is able to invoke it, that action is just removed from the queue of work. This is what we see in example above. If you want to cancel scheduled work that is already running, then you can use one of the overloads to the `Schedule` method that takes a `Func`. This gives a way for users to cancel out of a job that may already be running. This job could be some sort of I/O, heavy computations or perhaps usage of `Task` to perform some work. - -Now this may create a problem; if you want to cancel work that has already been started, you need to dispose of an instance of `IDisposable`, but how do you return the disposable if you are still doing the work? You could fire up another thread so the work happens concurrently, but creating threads is something we are trying to steer away from. - -In this example, we have a method that we will use as the delegate to be scheduled. It just fakes some work by performing a spin wait and adding values to the `list` argument. The key here is that we allow the user to cancel with the `CancellationToken` via the disposable we return. - -```csharp -public IDisposable Work(IScheduler scheduler, List list) -{ - var tokenSource = new CancellationTokenSource(); - var cancelToken = tokenSource.Token; - var task = new Task(() => - { - Console.WriteLine(); - - for (int i = 0; i < 1000; i++) - { - var sw = new SpinWait(); - - for (int j = 0; j < 3000; j++) sw.SpinOnce(); - - Console.Write("."); - - list.Add(i); - - if (cancelToken.IsCancellationRequested) - { - Console.WriteLine("Cancelation requested"); - - // cancelToken.ThrowIfCancellationRequested(); - - return; - } - } - }, cancelToken); - - task.Start(); - - return Disposable.Create(tokenSource.Cancel); -} -``` - -This code schedules the above code and allows the user to cancel the processing work by pressing Enter - -```csharp -var list = new List(); -Console.WriteLine("Enter to quit:"); - -var token = scheduler.Schedule(list, Work); -Console.ReadLine(); - -Console.WriteLine("Cancelling..."); - -token.Dispose(); - -Console.WriteLine("Cancelled"); -``` - -Output: - -``` -Enter to quit: -........ -Cancelling... -Cancelled -CancelLation requested -``` - -The problem here is that we have introduced explicit use of `Task`. We can avoid explicit usage of a concurrency model if we use the Rx recursive scheduler features instead. - -### Recursion - -The more advanced overloads of `Schedule` extension methods take some strange looking delegates as parameters. Take special note of the final parameter in each of these overloads of the `Schedule` extension method. - -```csharp -public static IDisposable Schedule( - this IScheduler scheduler, - Action action) -{...} - -public static IDisposable Schedule( - this IScheduler scheduler, - TState state, - Action> action) -{...} - -public static IDisposable Schedule( - this IScheduler scheduler, - TimeSpan dueTime, - Action> action) -{...} - -public static IDisposable Schedule( - this IScheduler scheduler, - TState state, - TimeSpan dueTime, - Action> action) -{...} - -public static IDisposable Schedule( - this IScheduler scheduler, - DateTimeOffset dueTime, - Action> action) -{...} - -public static IDisposable Schedule( - this IScheduler scheduler, - TState state, DateTimeOffset dueTime, - Action> action) -{...} -``` - -Each of these overloads take a delegate "action" that allows you to call "action" recursively. This may seem a very odd signature, but it makes for a great API. This effectively allows you to create a recursive delegate call. This may be best shown with an example. - -This example uses the most simple recursive overload. We have an `Action` that can be called recursively. - -```csharp -Action work = (Action self) => -{ - Console.WriteLine("Running"); - self(); -}; - -var token = s.Schedule(work); - -Console.ReadLine(); -Console.WriteLine("Cancelling"); - -token.Dispose(); - -Console.WriteLine("Cancelled"); -``` - -Output: - -``` -Enter to quit: -Running -Running -Running -Running -Cancelling -Cancelled -Running -``` - -Note that we didn't have to write any cancellation code in our delegate. Rx handled the looping and checked for cancellation on our behalf. Brilliant! Unlike simple recursive methods in C#, we are also protected from stack overflows, as Rx provides an extra level of abstraction. Indeed, Rx takes our recursive method and transforms it to a loop structure instead. - -#### Creating your own iterator - -Earlier in the book, we looked at how we can use [Rx with APM](04_CreatingObservableSequences.html#FromAPM). In our example, we just read the entire file into memory. We also referenced Jeffrey van Gogh's [blog post](http://blogs.msdn.com/b/jeffva/archive/2010/07/23/rx-on-the-server-part-1-of-n-asynchronous-system-io-stream-reading.aspx), which sadly is now out of date; however, his concepts are still sound. Instead of the Iterator method from Jeffrey's post, we can use schedulers to achieve the same result. - -The goal of the following sample is to open a file and stream it in chunks. This enables us to work with files that are larger than the memory available to us, as we would only ever read and cache a portion of the file at a time. In addition to this, we can leverage the compositional nature of Rx to apply multiple transformations to the file such as encryption and compression. By reading chunks at a time, we are able to start the other transformations before we have finished reading the file. - -First, let us refresh our memory with how to get from the `FileStream`'s APM methods into Rx. - -```csharp -var source = new FileStream(@"C:\some-file.txt", FileMode.Open, FileAccess.Read); -var factory = Observable.FromAsyncPattern(source.BeginRead, source.EndRead); -var buffer = new byte[source.Length]; - -IObservable reader = factory(buffer, 0, (int)source.Length); - -reader.Subscribe(bytesRead => Console.WriteLine("Read {0} bytes from file into buffer", bytesRead)); -``` - -The example above uses `FromAsyncPattern` to create a factory. The factory will take a byte array (`buffer`), an offset (`0`) and a length (`source.Length`); it effectively returns the count of the bytes read as a single-value sequence. When the sequence (`reader`) is subscribed to, `BeginRead` will read values, starting from the offset, into the buffer. In this case, we will read the whole file. Once the file has been read into the buffer, the sequence (`reader`) will push the single value (`bytesRead`) in to the sequence. - -This is all fine, but if we want to read chunks of data at a time then this is not good enough. We need to specify the buffer size we want to use. Let's start with 4KB (4096 bytes). - -```csharp -var bufferSize = 4096; -var buffer = new byte[bufferSize]; -IObservable reader = factory(buffer, 0, bufferSize); -reader.Subscribe(bytesRead => Console.WriteLine("Read {0} bytes from file", bytesRead)); -``` - -This works but will only read a max of 4KB from the file. If the file is larger, we want to keep reading all of it. As the `Position` of the `FileStream` will have advanced to the point it stopped reading, we can reuse the `factory` to reload the buffer. Next, we want to start pushing these bytes into an observable sequence. Let's start by creating the signature of an extension method. - -```csharp -public static IObservable ToObservable( - this FileStream source, - int buffersize, - IScheduler scheduler) -{...} -``` - -We can ensure that our extension method is lazily evaluated by using `Observable.Create`. We can also ensure that the `FileStream` is closed when the consumer disposes of the subscription by taking advantage of the `Observable.Using` operator. - -```csharp -public static IObservable ToObservable( - this FileStream source, - int buffersize, - IScheduler scheduler) -{ - var bytes = Observable.Create(o => - { - ... - }); - - return Observable.Using(() => source, _ => bytes); -} -``` - -Next, we want to leverage the scheduler's recursive functionality to continuously read chunks of data while still providing the user with the ability to dispose/cancel when they choose. This creates a bit of a pickle; we can only pass in one state parameter but need to manage multiple moving parts (buffer, factory, filestream). To do this, we create our own private helper class: - -```csharp -private sealed class StreamReaderState -{ - private readonly int _bufferSize; - private readonly Func> _factory; - - public StreamReaderState(FileStream source, int bufferSize) - { - _bufferSize = bufferSize; - _factory = Observable.FromAsyncPattern( - source.BeginRead, - source.EndRead); - Buffer = new byte[bufferSize]; - } - - public IObservable ReadNext() - { - return _factory(Buffer, 0, _bufferSize); - } - - public byte[] Buffer { get; set; } -} -``` - -This class will allow us to read data into a buffer, then read the next chunk by calling `ReadNext()`. In our `Observable.Create` delegate, we instantiate our helper class and use it to push the buffer into our observable sequence. - -```csharp -public static IObservable ToObservable( - this FileStream source, - int buffersize, - IScheduler scheduler) -{ - var bytes = Observable.Create(o => - { - var initialState = new StreamReaderState(source, buffersize); - - initialState - .ReadNext() - .Subscribe(bytesRead => - { - for (int i = 0; i < bytesRead; i++) - { - o.OnNext(initialState.Buffer[i]); - } - }); - ... - }); - - return Observable.Using(() => source, _ => bytes); -} -``` - -So this gets us off the ground, but we are still do not support reading files larger than the buffer. Now, we need to add recursive scheduling. To do this, we need a delegate to fit the required signature. We will need one that accepts a `StreamReaderState` and can recursively call an `Action`. - -```csharp -public static IObservable ToObservable( - this FileStream source, - int buffersize, - IScheduler scheduler) -{ - var bytes = Observable.Create(o => - { - var initialState = new StreamReaderState(source, buffersize); - - Action> iterator; - iterator = (state, self) => - { - state.ReadNext() - .Subscribe(bytesRead => - { - for (int i = 0; i < bytesRead; i++) - { - o.OnNext(state.Buffer[i]); - } - self(state); - }); - }; - return scheduler.Schedule(initialState, iterator); - }); - - return Observable.Using(() => source, _ => bytes); -} -``` - -We now have an `iterator` action that will: - -- call `ReadNext()` -- subscribe to the result -- push the buffer into the observable sequence -- and recursively call itself. - -We also schedule this recursive action to be called on the provided scheduler. Next, we want to complete the sequence when we get to the end of the file. This is easy, we maintain the recursion until the `bytesRead` is 0. - -```csharp -public static IObservable ToObservable( - this FileStream source, - int buffersize, - IScheduler scheduler) -{ - var bytes = Observable.Create(o => - { - var initialState = new StreamReaderState(source, buffersize); - - Action> iterator; - iterator = (state, self) => - { - state.ReadNext() - .Subscribe(bytesRead => - { - for (int i = 0; i < bytesRead; i++) - { - o.OnNext(state.Buffer[i]); - } - if (bytesRead > 0) - self(state); - else - o.OnCompleted(); - }); - }; - return scheduler.Schedule(initialState, iterator); - }); - - return Observable.Using(() => source, _ => bytes); -} -``` - -At this point, we have an extension method that iterates on the bytes from a file stream. Finally, let us apply some clean up so that we correctly manage our resources and exceptions, and the finished method looks something like this: - -```csharp -public static IObservable ToObservable( - this FileStream source, - int buffersize, - IScheduler scheduler) -{ - var bytes = Observable.Create(o => - { - var initialState = new StreamReaderState(source, buffersize); - var currentStateSubscription = new SerialDisposable(); - Action> iterator = - (state, self) => - currentStateSubscription.Disposable = state.ReadNext() - .Subscribe( - bytesRead => - { - for (int i = 0; i < bytesRead; i++) - { - o.OnNext(state.Buffer[i]); - } - - if (bytesRead > 0) - self(state); - else - o.OnCompleted(); - }, - o.OnError); - - var scheduledWork = scheduler.Schedule(initialState, iterator); - return new CompositeDisposable(currentStateSubscription, scheduledWork); - }); - - return Observable.Using(() => source, _ => bytes); -} -``` - -This is example code and your mileage may vary. I find that increasing the buffer size and returning `IObservable>` suits me better, but the example above works fine too. The goal here was to provide an example of an iterator that provides concurrent I/O access with cancellation and resource-efficient buffering. - - - -#### Combinations of scheduler features - -We have discussed many features that you can use with the `IScheduler` interface. Most of these examples, however, are actually using extension methods to invoke the functionality that we are looking for. The interface itself exposes the richest overloads. The extension methods are effectively just making a trade-off; improving usability/discoverability by reducing the richness of the overload. If you want access to passing state, cancellation, future scheduling and recursion, it is all available directly from the interface methods. - -```csharp -namespace System.Reactive.Concurrency -{ - public interface IScheduler - { - - // Gets the scheduler's notion of current time. - DateTimeOffset Now { get; } - - // Schedules an action to be executed with given state. - // Returns a disposable object used to cancel the scheduled action (best effort). - IDisposable Schedule( - TState state, - Func action); - - // Schedules an action to be executed after dueTime with given state. - // Returns a disposable object used to cancel the scheduled action (best effort). - IDisposable Schedule( - TState state, - TimeSpan dueTime, - Func action); - - // Schedules an action to be executed at dueTime with given state. - // Returns a disposable object used to cancel the scheduled action (best effort). - IDisposable Schedule( - TState state, - DateTimeOffset dueTime, - Func action); - } -} -``` - -## Schedulers in-depth - -We have largely been concerned with the abstract concept of a scheduler and the `IScheduler` interface. This abstraction allows low-level plumbing to remain agnostic towards the implementation of the concurrency model. As in the file reader example above, there was no need for the code to know which implementation of `IScheduler` was passed, as this is a concern of the consuming code. - -Now we take an in-depth look at each implementation of `IScheduler`, consider the benefits and tradeoffs they each make, and when each is appropriate to use. - -### ImmediateScheduler - -The `ImmediateScheduler` is exposed via the `Scheduler.Immediate` static property. This is the most simple of schedulers as it does not actually schedule anything. If you call `Schedule(Action)` then it will just invoke the action. If you schedule the action to be invoked in the future, the `ImmediateScheduler` will invoke a `Thread.Sleep` for the given period of time and then execute the action. In summary, the `ImmediateScheduler` is synchronous. - -### CurrentThreadScheduler - -Like the `ImmediateScheduler`, the `CurrentThreadScheduler` is single-threaded. It is exposed via the `Scheduler.Current` static property. The key difference is that the `CurrentThreadScheduler` acts like a message queue or a _Trampoline_. If you schedule an action that itself schedules an action, the `CurrentThreadScheduler` will queue the inner action to be performed later; in contrast, the `ImmediateScheduler` would start working on the inner action straight away. This is probably best explained with an example. - -In this example, we analyze how `ImmediateScheduler` and `CurrentThreadScheduler` perform nested scheduling differently. - -```csharp -private static void ScheduleTasks(IScheduler scheduler) -{ - Action leafAction = () => Console.WriteLine("----leafAction."); - Action innerAction = () => - { - Console.WriteLine("--innerAction start."); - scheduler.Schedule(leafAction); - Console.WriteLine("--innerAction end."); - }; - Action outerAction = () => - { - Console.WriteLine("outer start."); - scheduler.Schedule(innerAction); - Console.WriteLine("outer end."); - }; - scheduler.Schedule(outerAction); -} - -public void CurrentThreadExample() -{ - ScheduleTasks(Scheduler.CurrentThread); - /*Output: - outer start. - outer end. - --innerAction start. - --innerAction end. - ----leafAction. - */ -} - -public void ImmediateExample() -{ - ScheduleTasks(Scheduler.Immediate); - /*Output: - outer start. - --innerAction start. - ----leafAction. - --innerAction end. - outer end. - */ -} -``` - -Note how the `ImmediateScheduler` does not really "schedule" anything at all, all work is performed immediately (synchronously). As soon as `Schedule` is called with a delegate, that delegate is invoked. The `CurrentThreadScheduler`, however, invokes the first delegate, and, when nested delegates are scheduled, queues them to be invoked later. Once the initial delegate is complete, the queue is checked for any remaining delegates (i.e. nested calls to `Schedule`) and they are invoked. The difference here is quite important as you can potentially get out-of-order execution, unexpected blocking, or even deadlocks by using the wrong one. - -### DispatcherScheduler - -The `DispatcherScheduler` is found in `System.Reactive.Window.Threading.dll` (for WPF, Silverlight 4 and Silverlight 5). When actions are scheduled using the `DispatcherScheduler`, they are effectively marshaled to the `Dispatcher`'s `BeginInvoke` method. This will add the action to the end of the dispatcher's _Normal_ priority queue of work. This provides similar queuing semantics to the `CurrentThreadScheduler` for nested calls to `Schedule`. - -When an action is scheduled for future work, then a `DispatcherTimer` is created with a matching interval. The callback for the timer's tick will stop the timer and re-schedule the work onto the `DispatcherScheduler`. If the `DispatcherScheduler` determines that the `dueTime` is actually not in the future then no timer is created, and the action will just be scheduled normally. - -I would like to highlight a hazard of using the `DispatcherScheduler`. You can construct your own instance of a `DispatcherScheduler` by passing in a reference to a `Dispatcher`. The alternative way is to use the static property `DispatcherScheduler.Instance`. This can introduce hard to understand problems if it is not used properly. The static property does not return a reference to a static field, but creates a new instance each time, with the static property `Dispatcher.CurrentDispatcher` as the constructor argument. If you access `Dispatcher.CurrentDispatcher` from a thread that is not the UI thread, it will thus give you a new instance of a `Dispatcher`, but it will not be the instance you were hoping for. - -For example, imagine that we have a WPF application with an `Observable.Create` method. In the delegate that we pass to `Observable.Create`, we want to schedule the notifications on the dispatcher. We think this is a good idea because any consumers of the sequence would get the notifications on the dispatcher for free. - -```csharp -var fileLines = Observable.Create(o => -{ - var dScheduler = DispatcherScheduler.Instance; - var lines = File.ReadAllLines(filePath); - - foreach (var line in lines) - { - var localLine = line; - dScheduler.Schedule(() => o.OnNext(localLine)); - } - - return Disposable.Empty; -}); -``` - -This code may intuitively seem correct, but actually takes away power from consumers of the sequence. When we subscribe to the sequence, we decide that reading a file on the UI thread is a bad idea. So we add in a `SubscribeOn(Scheduler.NewThread)` to the chain as below: - -```csharp -fileLines - .SubscribeOn(Scheduler.ThreadPool) - .Subscribe(line => Lines.Add(line)); -``` - -This causes the create delegate to be executed on a new thread. The delegate will read the file then get an instance of a `DispatcherScheduler`. The `DispatcherScheduler` tries to get the `Dispatcher` for the current thread, but we are no longer on the UI thread, so there isn't one. As such, it creates a new dispatcher that is used for the `DispatcherScheduler` instance. We schedule some work (the notifications), but, as the underlying `Dispatcher` has not been run, nothing happens; we do not even get an exception. I have seen this on a commercial project and it left quite a few people scratching their heads. - -This takes us to one of our guidelines regarding scheduling: the use of `SubscribeOn` and `ObserveOn` should only be invoked by the final subscriber. If you introduce scheduling in your own extension methods or service methods, you should allow the consumer to specify their own scheduler. We will see more reasons for this guidance in the next chapter. - -### EventLoopScheduler - -The `EventLoopScheduler` allows you to designate a specific thread to a scheduler. Like the `CurrentThreadScheduler` that acts like a trampoline for nested scheduled actions, the `EventLoopScheduler` provides the same trampoline mechanism. The difference is that you provide an `EventLoopScheduler` with the thread you want it to use for scheduling instead, of just picking up the current thread. - -The `EventLoopScheduler` can be created with an empty constructor, or you can pass it a thread factory delegate. - -```csharp -// Creates an object that schedules units of work on a designated thread. -public EventLoopScheduler() -{...} - -// Creates an object that schedules units of work on a designated thread created by the -// provided factory function. -public EventLoopScheduler(Func<ThreadStart, Thread> threadFactory) -{...} -``` - -The overload that allows you to pass a factory enables you to customize the thread before it is assigned to the `EventLoopScheduler`. For example, you can set the thread name, priority, culture and most importantly whether the thread is a background thread or not. Remember that if you do not set the thread's property `IsBackground` to false, then your application will not terminate until it the thread is terminated. The `EventLoopScheduler` implements `IDisposable`, and calling Dispose will allow the thread to terminate. As with any implementation of `IDisposable`, it is appropriate that you explicitly manage the lifetime of the resources you create. - -This can work nicely with the `Observable.Using` method, if you are so inclined. This allows you to bind the lifetime of your `EventLoopScheduler` to that of an observable sequence - for example, this `GetPrices` method that takes an `IScheduler` for an argument and returns an observable sequence. - -```csharp -private IObservable<Price> GetPrices(IScheduler scheduler) -{...} -``` - -Here we bind the lifetime of the `EventLoopScheduler` to that of the result from the `GetPrices` method. - -```csharp -Observable.Using(() => new EventLoopScheduler(), els => GetPrices(els)).Subscribe(...) -``` - -### New Thread - -If you do not wish to manage the resources of a thread or an `EventLoopScheduler`, then you can use `NewThreadScheduler`. You can create your own instance of `NewThreadScheduler` or get access to the static instance via the property `Scheduler.NewThread`. Like `EventLoopScheduler`, you can use the parameterless constructor or provide your own thread factory function. If you do provide your own factory, be careful to set the `IsBackground` property appropriately. - -When you call `Schedule` on the `NewThreadScheduler`, you are actually creating an `EventLoopScheduler` under the covers. This way, any nested scheduling will happen on the same thread. Subsequent (non-nested) calls to `Schedule` will create a new `EventLoopScheduler` and call the thread factory function for a new thread too. - -In this example we run a piece of code reminiscent of our comparison between `Immediate` and `Current` schedulers. The difference here, however, is that we track the `ThreadId` that the action is performed on. We use the `Schedule` overload that allows us to pass the Scheduler instance into our nested delegates. This allows us to correctly nest calls. - -```csharp -private static IDisposable OuterAction(IScheduler scheduler, string state) -{ - Console.WriteLine("{0} start. ThreadId:{1}", state, Thread.CurrentThread.ManagedThreadId); - - scheduler.Schedule(state + ".inner", InnerAction); - - Console.WriteLine("{0} end. ThreadId:{1}", state, Thread.CurrentThread.ManagedThreadId); - - return Disposable.Empty; -} - -private static IDisposable InnerAction(IScheduler scheduler, string state) -{ - Console.WriteLine("{0} start. ThreadId:{1}", state, Thread.CurrentThread.ManagedThreadId); - - scheduler.Schedule(state + ".Leaf", LeafAction); - - Console.WriteLine("{0} end. ThreadId:{1}", state, Thread.CurrentThread.ManagedThreadId); - - return Disposable.Empty; -} - -private static IDisposable LeafAction(IScheduler scheduler, string state) -{ - Console.WriteLine("{0}. ThreadId:{1}", state, Thread.CurrentThread.ManagedThreadId); - - return Disposable.Empty; -} -``` - -When executed with the `NewThreadScheduler` like this: - -```csharp -Console.WriteLine("Starting on thread :{0}", Thread.CurrentThread.ManagedThreadId); -Scheduler.NewThread.Schedule("A", OuterAction); -``` - -Output: - -``` -Starting on thread :9 -A start. ThreadId:10 -A end. ThreadId:10 -A.inner start . ThreadId:10 -A.inner end. ThreadId:10 -A.inner.Leaf. ThreadId:10 -``` - -As you can see, the results are very similar to the `CurrentThreadScheduler`, except that the trampoline happens on a separate thread. This is in fact exactly the output we would get if we used an `EventLoopScheduler`. The differences between usages of the `EventLoopScheduler` and the `NewThreadScheduler` start to appear when we introduce a second (non-nested) scheduled task. - -```csharp -Console.WriteLine("Starting on thread :{0}", Thread.CurrentThread.ManagedThreadId); -Scheduler.NewThread.Schedule("A", OuterAction); -Scheduler.NewThread.Schedule("B", OuterAction); -``` - -Output: - -``` -Starting on thread :9 -A start. ThreadId:10 -A end. ThreadId:10 -A.inner start . ThreadId:10 -A.inner end. ThreadId:10 -A.inner.Leaf. ThreadId:10 -B start. ThreadId:11 -B end. ThreadId:11 -B.inner start . ThreadId:11 -B.inner end. ThreadId:11 -B.inner.Leaf. ThreadId:11 -``` - -Note that there are now three threads at play here. Thread 9 is the thread we started on and threads 10 and 11 are performing the work for our two calls to Schedule. - -### Thread Pool - -The `ThreadPoolScheduler` will simply just tunnel requests to the `ThreadPool`. For requests that are scheduled as soon as possible, the action is just sent to `ThreadPool.QueueUserWorkItem`. For requests that are scheduled in the future, a `System.Threading.Timer` is used. - -As all actions are sent to the `ThreadPool`, actions can potentially run out of order. Unlike the previous schedulers we have looked at, nested calls are not guaranteed to be processed serially. We can see this by running the same test as above but with the `ThreadPoolScheduler`. - -```csharp -Console.WriteLine("Starting on thread :{0}", Thread.CurrentThread.ManagedThreadId); -Scheduler.ThreadPool.Schedule("A", OuterAction); -Scheduler.ThreadPool.Schedule("B", OuterAction); -``` - -The output - -``` -Starting on thread :9 -A start. ThreadId:10 -A end. ThreadId:10 -A.inner start . ThreadId:10 -A.inner end. ThreadId:10 -A.inner.Leaf. ThreadId:10 -B start. ThreadId:11 -B end. ThreadId:11 -B.inner start . ThreadId:10 -B.inner end. ThreadId:10 -B.inner.Leaf. ThreadId:11 -``` - -Note, that as per the `NewThreadScheduler` test, we initially start on one thread but all the scheduling happens on two other threads. The difference is that we can see that part of the second run "B" runs on thread 11 while another part of it runs on 10. - -### TaskPool - -The `TaskPoolScheduler` is very similar to the `ThreadPoolScheduler` and, when available (depending on your target framework), you should favor it over the later. Like the `ThreadPoolScheduler`, nested scheduled actions are not guaranteed to be run on the same thread. Running the same test with the `TaskPoolScheduler` shows us similar results. - -```csharp -Console.WriteLine("Starting on thread :{0}", Thread.CurrentThread.ManagedThreadId); -Scheduler.TaskPool.Schedule("A", OuterAction); -Scheduler.TaskPool.Schedule("B", OuterAction); -``` - -Output: - -``` -Starting on thread :9 -A start. ThreadId:10 -A end. ThreadId:10 -B start. ThreadId:11 -B end. ThreadId:11 -A.inner start . ThreadId:10 -A.inner end. ThreadId:10 -A.inner.Leaf. ThreadId:10 -B.inner start . ThreadId:11 -B.inner end. ThreadId:11 -B.inner.Leaf. ThreadId:10 -``` - -### TestScheduler - -It is worth noting that there is also a `TestScheduler` accompanied by its base classes `VirtualTimeScheduler` and `VirtualTimeSchedulerBase`. The latter two are not really in the scope of an introduction to Rx, but the former is. We will cover all things testing including the `TestScheduler` in the next chapter, [Testing Rx](16_TestingRx.html). - -## Selecting an appropriate scheduler - -With all of these options to choose from, it can be hard to know which scheduler to use and when. Here is a simple check list to help you in this daunting task: - -### UI Applications - -- The final subscriber is normally the presentation layer and should control the scheduling. -- Observe on the `DispatcherScheduler` to allow updating of ViewModels -- Subscribe on a background thread to prevent the UI from becoming unresponsive - - If the subscription will not block for more than 50ms then - - Use the `TaskPoolScheduler` if available, or - - Use the `ThreadPoolScheduler` - - If any part of the subscription could block for longer than 50ms, then you should use the `NewThreadScheduler`. - -### Service layer - -* If your service is reading data from a queue of some sort, consider using a dedicated `EventLoopScheduler`. -This way, you can preserve order of events -* If processing an item is expensive (>50ms or requires I/O), then consider using a `NewThreadScheduler` -* If you just need the scheduler for a timer, e.g. for `Observable.Interval` or `Observable.Timer`, then favor the `TaskPool`. -Use the `ThreadPool` if the `TaskPool` is not available for your platform. - -> The `ThreadPool` (and the `TaskPool` by proxy) have a time delay before they will increase the number of threads that they use. This delay is 500ms. Let us consider a PC with two cores that we will schedule four actions onto. By default, the thread pool size will be the number of cores (2). If each action takes 1000ms, then two actions will be sitting in the queue for 500ms before the thread pool size is increased. Instead of running all four actions in parallel, which would take one second in total, the work is not completed for 1.5 seconds as two of the actions sat in the queue for 500ms. For this reason, you should only schedule work that is very fast to execute (guideline 50ms) onto the ThreadPool or TaskPool. Conversely, creating a new thread is not free, but with the power of processors today the creation of a thread for work over 50ms is a small cost. - -Concurrency is hard. We can choose to make our life easier by taking advantage of Rx and its scheduling features. We can improve it even further by only using Rx where appropriate. While Rx has concurrency features, these should not be mistaken for a concurrency framework. Rx is designed for querying data, and as discussed in [the first chapter](01_WhyRx.html#Could), parallel computations or composition of asynchronous methods is more appropriate for other frameworks. - -Rx solves the issues for concurrently generating and consuming data via the `ObserveOn`/`SubscribeOn` methods. By using these appropriately, we can simplify our code base, increase responsiveness and reduce the surface area of our concurrency concerns. Schedulers provide a rich platform for processing work concurrently without the need to be exposed directly to threading primitives. They also help with common troublesome areas of concurrency such as cancellation, passing state and recursion. By reducing the concurrency surface area, Rx provides a (relatively) simple yet powerful set of concurrency features paving the way to the [pit of success](http://blogs.msdn.com/b/brada/archive/2003/10/02/50420.aspx). \ No newline at end of file diff --git a/content/16_TestingRx.md b/content/16_TestingRx.md index e3e3869..3dd2c89 100644 --- a/content/16_TestingRx.md +++ b/content/16_TestingRx.md @@ -2,76 +2,29 @@ title: Testing Rx --- -# Testing Rx +# Testing Rx -Testing software has its roots in debugging and demonstrating code. Having largely matured past manual tests that try to "break the application", modern quality assurance standards demand a level of automation that can help evaluate and prevent bugs. While teams of testing specialists are common, more and more coders are expected to provide quality guarantees via automated test suites. +Modern quality assurance standards demand comprehensive automated testing that can help evaluate and prevent bugs. It is good practice to have a suite of tests that verify correct behaviour and to run this as part of the build process to detect regressions early. -Up to this point, we have covered a broad scope of Rx, and we have almost enough knowledge to start using Rx in anger! Still, many developers would not dream of coding without first being able to write tests. Tests can be used to prove that code is in fact satisfying requirements, provide a safety net against regression and can even help document the code. This chapter makes the assumption that you are familiar with the concepts of dependency injection and unit testing with test-doubles, such as mocks or stubs. +The `System.Reactive` source code includes a comprehensive tests suite. Testing Rx-based code presents some challenges, especially when time-sensitive operators are involved. Rx.NET's test suite includes many tests designed to exercise awkward edge cases to ensure predictable behaviour under load. This is only possible because Rx.NET was designed to be testable. -Rx poses some interesting problems to our Test-Driven community: +In this chapter, we'll show how you can take advantage of Rx's testability in your own code. -- Scheduling, and therefore threading, is generally avoided in test scenarios as it can introduce race conditions which may lead to non-deterministic tests -- Tests should run as fast as possible -- For many, Rx is a new technology/library. Naturally, as we progress on our journey to mastering Rx, we may want to refactor some of our previous Rx code. We want to use tests to ensure that our refactoring has not altered the internal behavior of our code base -- Likewise, tests will ensure nothing breaks when we upgrade versions of Rx. +## Virtual Time -While we do want to test our code, we don't want to introduce slow or non-deterministic tests; indeed, the later would introduce false-negatives or false-positives. If we look at the Rx library, there are plenty of methods that involve scheduling (implicitly or explicitly), so using Rx effectively makes it hard to avoid scheduling. This LINQ query shows us that there are at least 26 extension methods that accept an `IScheduler` as a parameter. +It's common to deal with timing in Rx. As you've seen, it offers several operators that take time into account, and this presents a challenge. We don't want to introduce slow tests, because that can make test suites take too long to execute, but how might we test an application that waits for the user to stop typing for half a second before submitting a query? Non-deterministic tests can also be a problem: when there are race conditions it can be very hard to recreate these reliably. -```csharp -var query = from method in typeof(Observable).GetMethods() - from parameter in method.GetParameters() - where typeof(IScheduler).IsAssignableFrom(parameter.ParameterType) - group method by method.Name into m - orderby m.Key - select m.Key; - -foreach (var methodName in query) -{ - Console.WriteLine(methodName); -} -``` - -Output: - -``` -Buffer -Delay -Empty -Generate -Interval -Merge -ObserveOn -Range -Repeat -Replay -Return -Sample -Start -StartWith -Subscribe -SubscribeOn -Take -Throttle -Throw -TimeInterval -Timeout -Timer -Timestamp -ToAsync -ToObservable -Window -``` - -Many of these methods also have an overload that does not take an `IScheduler` and instead uses a default instance. TDD/Test First coders will want to opt for the overload that accepts the `IScheduler`, so that they can have some control over scheduling in our tests. I will explain why soon. +The [Scheduling and Threading](./11_SchedulingAndThreading.md) chapter described how schedulers use a virtualized representation of time. This is critical for enabling tests to validate time-related behaviour. It lets us control Rx's perception of the progression of time, enabling us to write tests that logically take seconds, but which execute in microseconds. Consider this example, where we create a sequence that publishes values every second for five seconds. ```csharp -var interval = Observable.Interval(TimeSpan.FromSeconds(1)) - .Take(5); +IObservable interval = Observable + .Interval(TimeSpan.FromSeconds(1)) + .Take(5); ``` -If we were to write a test that ensured that we received five values and they were each one second apart, it would take five seconds to run. That would be no good; I want hundreds if not thousands of tests to run in five seconds. Another very common requirement is to test a timeout. Here, we try to test a timeout of one minute. +A naive a test to ensure that this produces five values at one second intervals would take five seconds to run. That would be no good; we want hundreds if not thousands of tests to run in five seconds. Another very common requirement is to test a timeout. Here, we try to test a timeout of one minute. ```csharp var never = Observable.Never(); @@ -85,20 +38,19 @@ never.Timeout(TimeSpan.FromMinutes(1)) Assert.IsTrue(exceptionThrown); ``` -We have two problems here: +It looks like we would have no choice but to make our test wait for a minute before running that assert. In practice, we'd want to wait a little over a minute, because if the computer running the test is busy, it might trigger the timeout bit later than we've asked. This kind of scenario is notorious for causing tests to fail occasionally even when there's no real problem in the code being tested. -- either the `Assert` runs too soon, and the test is pointless as it always fails, or -- we have to add a delay of one minute to perform an accurate test - -For this test to be useful, it would therefore take one minute to run. Unit tests that take one minute to run are not acceptable. +Nobody wants slow, inconsistent tests. So let's look at how Rx helps us to avoid these problems. ## TestScheduler -To our rescue comes the `TestScheduler`; it introduces the concept of a virtual scheduler to allow us to emulate and control time. +The [Scheduling and Threading](11_SchedulingAndThreading.md) chapter explained that schedulers determine when and how to execute code, and that they keep track of time. Most of the schedulers we looked at in that chapter addressed various threading concerns, and when it came to timing, they all attempted to run work at the time requested. But Rx provides `TestScheduler`, which handles time completely differently. It takes advantage of the fact that schedulers control all time-related behaviour to allow us to emulate and control time. + +**Note:** `TestScheduler` is not in the main `System.Reactive` package. You will need to add a reference to `Microsoft.Reactive.Testing` to use it. -A virtual scheduler can be conceptualized as a queue of actions to be executed. Each are assigned a point in time when they should be executed. We use the `TestScheduler` as a substitute, or [test double](http://xunitpatterns.com/Test%20Double.html), for the production `IScheduler` types. Using this virtual scheduler, we can either execute all queued actions, or only those up to a specified point in time. +Any scheduler maintains a queue of actions to be executed. Each action is assigned a point in time when it should be executed. (Sometimes that time is "as soon as possible" but time-based operators will often schedule work to run at some specific time in the future.) If we use the `TestScheduler` it will effectively act as though time stands still until we tell it we want time to move on. -In this example, we schedule a task onto the queue to be run immediately by using the simple overload (`Schedule(Action)`). We then advance the virtual clock forward by one tick. By doing so, we execute everything scheduled up to that point in time. Note that even though we schedule an action to be executed immediately, it will not actually be executed until the clock is manually advanced. +In this example, we schedule a task to be run immediately by using the simplest `Schedule` overload. Even though this effectively asks for the work to be run as soon as possible, the `TestScheduler` always waits for us to tell it we're ready before processing newly queued work. We advance the virtual clock forward by one tick, at which point it will execute that queued work. (It runs all newly-queued "as soon as possible" work any time we advance the virtual time. If we advance the time far enough to mean that work that was previously logically in the future is now runnable, it runs that too.) ```csharp var scheduler = new TestScheduler(); @@ -109,31 +61,11 @@ scheduler.AdvanceBy(1); // execute 1 tick of queued actions Assert.IsTrue(wasExecuted); ``` -> Running and debugging this example may help you to better understand the basics of the `TestScheduler`. - -The `TestScheduler` implements the `IScheduler` interface (naturally) and also extends it to allow us to control and monitor virtual time. We are already familiar with the `IScheduler.Schedule` methods, however the `AdvanceBy(long)`, `AdvanceTo(long)` and `Start()` methods unique to the `TestScheduler` are of most interest. Likewise, the `Clock` property will also be of interest, as it can help us understand what is happening internally. +The `TestScheduler` implements the `IScheduler` interface and also defines methods allowing us to control and monitor virtual time. This shows these additional methods: ```csharp public class TestScheduler : // ... { - // Implementation of IScheduler - public DateTimeOffset Now { get; } - - public IDisposable Schedule( - TState state, - Func action) - - public IDisposable Schedule( - TState state, - TimeSpan dueTime, - Func action) - - public IDisposable Schedule( - TState state, - DateTimeOffset dueTime, - Func action) - - // Useful extensions for testing public bool IsEnabled { get; private set; } public TAbsolute Clock { get; protected set; } public void Start() @@ -141,14 +73,15 @@ public class TestScheduler : // ... public void AdvanceTo(long time) public void AdvanceBy(long time) - //` Other methods ... } ``` +`TestScheduler` works in the same units as [`TimeSpan.Ticks`](https://learn.microsoft.com/en-us/dotnet/api/system.timespan.ticks). If you want to move time forward by 1 second, you can call `scheduler.AdvanceBy(TimeSpan.FromSeconds(1).Ticks)`. One tick corresponds to 100ns, so 1 second is 10,000,000 ticks. + ### AdvanceTo -The `AdvanceTo(long)` method will execute all the actions that have been scheduled up to the absolute time specified. The `TestScheduler` uses ticks as its measurement of time. In this example, we schedule actions to be invoked now, in 10 ticks, and in 20 ticks. +The `AdvanceTo(long)` method sets the virtual time to the specified number of ticks. This will execute all the actions that have been scheduled up to that absolute time specified. The `TestScheduler` uses ticks as its measurement of time. In this example, we schedule actions to be invoked now, in 10 ticks, and in 20 ticks (1 and 2 microseconds respectively). ```csharp var scheduler = new TestScheduler(); @@ -185,7 +118,7 @@ Note that nothing happened when we advanced to 15 ticks. All work scheduled befo ### AdvanceBy -The `AdvanceBy(long)` method allows us to move the clock forward a relative amount of time. Again, the measurements are in ticks. We can take the last example and modify it to use `AdvanceBy(long)`. +The `AdvanceBy(long)` method allows us to move the clock forward by some amount of time. Unlike `AdvanceTo`, the argument here is relative to the current virtual time. Again, the measurements are in ticks. We can take the last example and modify it to use `AdvanceBy(long)`. ```csharp var scheduler = new TestScheduler(); @@ -218,9 +151,9 @@ scheduler.AdvanceBy(5); C ``` -### Start +### Start -The `TestScheduler`'s `Start()` method is an effective way to execute everything that has been scheduled. We take the same example again and swap out the `AdvanceBy(long)` calls for a single `Start()` call. +The `TestScheduler`'s `Start()` method runs everything that has been scheduled, advancing virtual time as necessary for work items that were queued for a specific time. We take the same example again and swap out the `AdvanceBy(long)` calls for a single `Start()` call. ```csharp var scheduler = new TestScheduler(); @@ -272,11 +205,11 @@ C scheduler.Clock:20 ``` -Note that the output is exactly the same; If we want our fourth action to be executed, we will have to call `Start()` again. +Note that the output is exactly the same; If we want our fourth action to be executed, we will have to call `Start()` (or `AdvanceTo` or `AdvanceBy`) again. -### Stop +### Stop -In previous releases of Rx, the `Start()` method was called `Run()`. Now there is a `Stop()` method whose name seems to imply some symmetry with `Start()`. All it does however, is set the `IsEnabled` property to false. This property is used as an internal flag to check whether the internal queue of actions should continue being executed. The processing of the queue may indeed be instigated by `Start()`, however `AdvanceTo` or `AdvanceBy` can be used too. +There is a `Stop()` method whose name seems to imply some symmetry with `Start()`. This sets the scheduler's `IsEnabled` property to false, and if `Start` is currently running, this means that it will stop inspecting the queue for further work, and will return as soon as the work item currently being processed completes. In this example, we show how you could use `Stop()` to pause processing of scheduled actions. @@ -301,7 +234,9 @@ B scheduler.Clock:15 ``` -Note that "C" never gets printed as we stop the clock at 15 ticks. I have been testing Rx successfully for nearly two years now, yet I have not found the need to use the `Stop()` method. I imagine that there are cases that warrant its use; however I just wanted to make the point that you do not have to be concerned about the lack of use of it in your tests. +Note that "C" never gets printed as we stop the clock at 15 ticks. + +Since `Start` automatically stops when it has drained the work queue, you're under no obligation to call `Stop`. It's there only if you want to call `Start` but then pause processing part way through the test. ### Schedule collision @@ -330,7 +265,7 @@ scheduler.Clock:10 Note that the virtual clock is at 10 ticks, the time we advanced to. -## Testing Rx cod +## Testing Rx code Now that we have learnt a little bit about the `TestScheduler`, let's look at how we could use it to test our two initial code snippets that use `Interval` and `Timeout`. We want to execute tests as fast as possible but still maintain the semantics of time. In this example we generate our five values one second apart but pass in our `TestScheduler` to the `Interval` method to use instead of the default scheduler. @@ -353,7 +288,7 @@ public void Testing_with_test_scheduler() } ``` -While this is mildly interesting, what I think is more important is how we would test a real piece of code. Imagine, if you will, a ViewModel that subscribes to a stream of prices. As prices are published, it adds them to a collection. Assuming this is a WPF or Silverlight implementation, we take the liberty of enforcing that the subscription be done on the `ThreadPool` and the observing is executed on the `Dispatcher`. +While this is mildly interesting, what I think is more important is how we would test a real piece of code. Imagine, if you will, a ViewModel that subscribes to a stream of prices. As prices are published, it adds them to a collection. Assuming this is a WPF implementation, we take the liberty of enforcing that the subscription be done on the `ThreadPool` and the observing is executed on the `Dispatcher`. ```csharp public class MyViewModel : IMyViewModel @@ -393,9 +328,9 @@ public class MyViewModel : IMyViewModel } ``` -### Injecting scheduler depende +### Injecting scheduler dependencies -While the snippet of code above may do what we want it to, it will be hard to test as it is accessing the schedulers via static properties. To help my testing, I have created my own interface that exposes the same `IScheduler` implementations that the `Scheduler` type does, i suggest you adopt this interface too. +While the snippet of code above may do what we want it to, it will be hard to test as it is accessing the schedulers via static properties. You will need some way of enabling tests to supply different schedulers during testing. In this example, we're going to define an interface for this purpose: ```csharp public interface ISchedulerProvider @@ -405,72 +340,48 @@ public interface ISchedulerProvider IScheduler Immediate { get; } IScheduler NewThread { get; } IScheduler ThreadPool { get; } - // IScheduler TaskPool { get; } + IScheduler TaskPool { get; } } ``` -Whether the `TaskPool` property should be included or not depends on your target platform. If you adopt this concept, feel free to name this type in accordance with your naming conventions e.g. `SchedulerService`, `Schedulers`. The default implementation that we would run in production is implemented as follows: +The default implementation that we would run in production is implemented as follows: ```csharp public sealed class SchedulerProvider : ISchedulerProvider { - public IScheduler CurrentThread - { - get { return Scheduler.CurrentThread; } - } - - public IScheduler Dispatcher - { - get { return DispatcherScheduler.Instance; } - } - - public IScheduler Immediate - { - get { return Scheduler.Immediate; } - } - - public IScheduler NewThread - { - get { return Scheduler.NewThread; } - } - - public IScheduler ThreadPool - { - get { return Scheduler.ThreadPool; } - } - - // public IScheduler TaskPool { get { return Scheduler.TaskPool; } } + public IScheduler CurrentThread => Scheduler.CurrentThread; + public IScheduler Dispatcher => DispatcherScheduler.Instance; + public IScheduler Immediate => Scheduler.Immediate; + public IScheduler NewThread => Scheduler.NewThread; + public IScheduler ThreadPool => Scheduler.ThreadPool; + public IScheduler TaskPool => Scheduler.TaskPool; } ``` -This now allows me to substitute implementations of `ISchedulerProvider` to help with testing. I could mock the `ISchedulerProvider`, but I find it easier to provide a test implementation. My implementation for testing is as follows. +We can substitute implementations of `ISchedulerProvider` to help with testing. For example: ```csharp public sealed class TestSchedulers : ISchedulerProvider { - private readonly TestScheduler _currentThread = new TestScheduler(); - private readonly TestScheduler _dispatcher = new TestScheduler(); - private readonly TestScheduler _immediate = new TestScheduler(); - private readonly TestScheduler _newThread = new TestScheduler(); - private readonly TestScheduler _threadPool = new TestScheduler(); + // Schedulers available as TestScheduler type + public TestScheduler CurrentThread { get; } = new TestScheduler(); + public TestScheduler Dispatcher { get; } = new TestScheduler(); + public TestScheduler Immediate { get; } = new TestScheduler(); + public TestScheduler NewThread { get; } = new TestScheduler(); + public TestScheduler ThreadPool { get; } = new TestScheduler(); - #region Explicit implementation of ISchedulerService - IScheduler ISchedulerProvider.CurrentThread { get { return _currentThread; } } - IScheduler ISchedulerProvider.Dispatcher { get { return _dispatcher; } } - IScheduler ISchedulerProvider.Immediate { get { return _immediate; } } - IScheduler ISchedulerProvider.NewThread { get { return _newThread; } } - IScheduler ISchedulerProvider.ThreadPool { get { return _threadPool; } } - #endregion - - public TestScheduler CurrentThread { get { return _currentThread; } } - public TestScheduler Dispatcher { get { return _dispatcher; } } - public TestScheduler Immediate { get { return _immediate; } } - public TestScheduler NewThread { get { return _newThread; } } - public TestScheduler ThreadPool { get { return _threadPool; } } + // ISchedulerService needs us to return IScheduler, but we want the properties + // to return TestScheduler for the convenience of test code, so we provide + // explicit implementations of all the properties to match ISchedulerService. + IScheduler ISchedulerProvider.CurrentThread => CurrentThread; + IScheduler ISchedulerProvider.Dispatcher => Dispatcher; + IScheduler ISchedulerProvider.Immediate => Immediate; + IScheduler ISchedulerProvider.NewThread => NewThread; + IScheduler ISchedulerProvider.ThreadPool => ThreadPool; } ``` -Note that `ISchedulerProvider` is implemented explicitly. This means that, in our tests, we can access the `TestScheduler` instances directly, but our system under test (SUT) still just sees the interface implementation. I can now write some tests for my ViewModel. Below, we test a modified version of the `MyViewModel` class that takes an `ISchedulerProvider` and uses that instead of the static schedulers from the `Scheduler` class. We also use the popular [Moq](http://code.google.com/p/moq) framework in order to mock out our model. +Note that `ISchedulerProvider` is implemented explicitly because that interface requires each property to return an `IScheduler`, but our tests will need to access the `TestScheduler` instances directly. I can now write some tests for my ViewModel. Below, we test a modified version of the `MyViewModel` class that takes an `ISchedulerProvider` and uses that instead of the static schedulers from the `Scheduler` class. We also use the popular [Moq](https://github.com/Moq) framework to provide a suitable fake implementation of our model. ```csharp [TestInitialize] @@ -494,7 +405,7 @@ public void Should_add_to_Prices_when_Model_publishes_price() _schedulerProvider.ThreadPool.Schedule(() => priceStream.OnNext(expected)); Assert.AreEqual(0, _viewModel.Prices.Count); - + // Execute the OnNext action _schedulerProvider.ThreadPool.AdvanceBy(1); Assert.AreEqual(0, _viewModel.Prices.Count); @@ -531,34 +442,34 @@ These two tests ensure five things: * That the `Price` property has prices added to it as the model produces them * That the sequence is subscribed to on the ThreadPool -* That the `Price` property is updated on the Dispatcher i.e. the sequence is observed on the Dispatcher -* That a timeout of 10 seconds between prices will set the ViewModel to disconnected. -* The tests run fast. +* That the `Price` property is updated on the Dispatcher i.e. the sequence is observed on the Dispatcher +* That a timeout of 10 seconds between prices will set the ViewModel to disconnected +* The tests run fast. -While the time to run the tests is not that impressive, most of that time seems to be spent warming up my test harness. Moreover, increasing the test count to 10 only adds 0.03seconds. In general, on a modern CPU, I expect to see unit tests run at a rate of +1000 tests per second +While the time to run the tests is not that impressive, most of that time seems to be spent warming up my test harness. Moreover, increasing the test count to 10 only adds 0.03seconds. In general, a modern CPU should be able to execute thousands of unit tests per second. -Usually, I would not have more than one assert/verify per test, but here it does help illustrate a point. In the first test, we can see that only once both the `ThreadPool` and the `Dispatcher` schedulers have been run will we get a result. In the second test, it helps to verify that the timeout is not less than 10 seconds. +In the first test, we can see that only once both the `ThreadPool` and the `Dispatcher` schedulers have been run will we get a result. In the second test, it helps to verify that the timeout is not less than 10 seconds. In some scenarios, you are not interested in the scheduler and you want to be focusing your tests on other functionality. If this is the case, then you may want to create another test implementation of the `ISchedulerProvider` that returns the `ImmediateScheduler` for all of its members. That can help reduce the noise in your tests. ```csharp public sealed class ImmediateSchedulers : ISchedulerService { - public IScheduler CurrentThread { get { return Scheduler.Immediate; } } - public IScheduler Dispatcher { get { return Scheduler.Immediate; } } - public IScheduler Immediate { get { return Scheduler.Immediate; } } - public IScheduler NewThread { get { return Scheduler.Immediate; } } - public IScheduler ThreadPool { get { return Scheduler.Immediate; } } + public IScheduler CurrentThread => Scheduler.Immediate; + public IScheduler Dispatcher => Scheduler.Immediate; + public IScheduler Immediate => Scheduler.Immediate; + public IScheduler NewThread => Scheduler.Immediate; + public IScheduler ThreadPool => Scheduler.Immediate; } ``` ## Advanced features - ITestableObserver -The `TestScheduler` provides further advanced features. I find that I am able to get by quite well without these methods, but others may find them useful. Perhaps this is because I have found myself accustomed to testing without them from using earlier versions of Rx. +The `TestScheduler` provides further advanced features. These can be useful when parts of your test setup need to run at particular virtual times. -### Start(Func>) +### `Start(Func>)` -There are three overloads to `Start`, which are used to start an observable sequence at a given time, record the notifications it makes and dispose of the subscription at a given time. This can be confusing at first, as the parameterless overload of `Start` is quite unrelated. These three overloads return an `ITestableObserver` which allows you to record the notifications from an observable sequence, much like the `Materialize` method we saw in the [Transformation chapter](08_Transformation.html#MaterializeAndDematerialize). +There are three overloads to `Start`, which are used to start an observable sequence at a given time, record the notifications it makes and dispose of the subscription at a given time. This can be confusing at first, as the parameterless overload of `Start` is quite unrelated. These three overloads return an `ITestableObserver` which allows you to record the notifications from an observable sequence, much like the `Materialize` method we saw in the [Transformation chapter](06_Transformation.md#materialize-and-dematerialize). ```csharp public interface ITestableObserver : IObserver @@ -649,25 +560,33 @@ There are two other overloads to this `TestScheduler.Start` method. public ITestableObserver Start(Func> create, long disposed) { if (create == null) - throw new ArgumentNullException("create"); + { + throw new ArgumentNullException("create"); + } else - return this.Start(create, 100L, 200L, disposed); + { + return this.Start(create, 100L, 200L, disposed); + } } public ITestableObserver Start(Func> create) { if (create == null) - throw new ArgumentNullException("create"); + { + throw new ArgumentNullException("create"); + } else - return this.Start(create, 100L, 200L, 1000L); + { + return this.Start(create, 100L, 200L, 1000L); + } } ``` -As you can see, these overloads just call through to the variant we have been looking at, but passing some default values. I am not sure why these default values are special; I can not imagine why you would want to use these two methods, unless your specific use case matched that specific configuration exactly. +As you can see, these overloads just call through to the variant we have been looking at, but passing some default values. These default values provide short gaps before creation and between creation and subscription, giving enough space to configure other things to happen between them. And then the disposal happens a bit later, allowing a little longer for the thing to run. There's nothing particularly magical about these default values, but if you value a lack of clutter over it being completely obvious what happens when, and are happy to rely on the invisible effects of convention, then you might prefer this. The Rx source code itself contains thousands of tests, and a very large number of them use the simplest `Start` overload, and developers working in the code base day in, day out soon get used to the idea that creation occurs at time 100, and subscription at time 200, and that you test everything you need to before 1000. ### CreateColdObservable -Just as we can record an observable sequence, we can also use `CreateColdObservable` to playback a set of `Recorded>`. The signature for `CreateColdObservable` simply takes a `params` array of recorded notifications. +Just as we can record an observable sequence, we can also use `CreateColdObservable` to play back a set of `Recorded>`. The signature for `CreateColdObservable` simply takes a `params` array of recorded notifications. ```csharp // Creates a cold observable from an array of notifications. @@ -758,7 +677,7 @@ OnCompleted() @ 40000000 Note that the output is almost the same. Scheduling of the creation and subscription do not affect the Hot Observable, therefore the notifications happen 1 tick earlier than their Cold counterparts. -We can see the major difference a Hot Observable bears by changing the virtual create time and virtual subscribe time to be different values. With a Cold Observable, the virtual create time has no real impact, as subscription is what initiates any action. This means we can not miss any early message on a Cold Observable. For Hot Observables, we can miss messages if we subscribe too late. Here, we create the Hot Observable immediately, but only subscribe to it after 1 second (thus missing the first message). +We can see the major difference a Hot Observable bears by changing the virtual create time and virtual subscribe time to be different values. With a Cold Observable, the virtual create time has no real impact, as subscription is what initiates any action. This means we cannot miss any early message on a Cold Observable. For Hot Observables, we can miss messages if we subscribe too late. Here, we create the Hot Observable immediately, but only subscribe to it after 1 second (thus missing the first message). ```csharp var scheduler = new TestScheduler(); diff --git a/content/17_SequencesOfCoincidence.md b/content/17_SequencesOfCoincidence.md deleted file mode 100644 index c48bfbb..0000000 --- a/content/17_SequencesOfCoincidence.md +++ /dev/null @@ -1,536 +0,0 @@ ---- -title: Sequences of coincidence title ---- - -# Sequences of coincidence - -We can conceptualize events that have _duration_ as `windows`. For example; - -- a server is up -- a person is in a room -- a button is pressed (and not yet released). - -The first example could be re-worded as "for this window of time, the server was up". An event from one source may have a greater value if it coincides with an event from another source. For example, while at a music festival, you may only be interested in tweets (event) about an artist while they are playing (window). In finance, you may only be interested in trades (event) for a certain instrument while the New York market is open (window). In operations, you may be interested in the user sessions (window) that remained active during an upgrade of a system (window). In that example, we would be querying for coinciding windows. - -Rx provides the power to query sequences of coincidence, sometimes called 'sliding windows'. We already recognize the benefit that Rx delivers when querying data in motion. By additionally providing the power to query sequences of coincidence, Rx exposes yet another dimension of possibilities. - -## Buffer revisited - -[`Buffer`](13_TimeShiftedSequences.html#Buffer) is not a new operator to us; however, it can now be conceptually grouped with the window operators. Each of these windowing operators act on a sequence and a window of time. Each operator will open a window when the source sequence produces a value. The way the window is closed, and which values are exposed, are the main differences between each of the operators. Let us just quickly recap the internal working of the `Buffer` operator and see how this maps to the concept of "windows of time". - -`Buffer` will create a window when the first value is produced. It will then put that value into an internal cache. The window will stay open until the count of values has been reached; each of these values will have been cached. When the count has been reached, the window will close and the cache will be published to the result sequence as an `IList`. When the next value is produced from the source, the cache is cleared and we start again. This means that `Buffer` will take an `IObservable` and return an `IObservable>`. - -`Example Buffer with count of 3` - -
-
source|-0-1-2-3-4-5-6-7-8-9|
-
result|-----0-----3-----6-9|
-
            1     4     7
-
            2     5     8
-
- -> In this marble diagram, I have represented the list of values being returned at a point in time as a column of data. That is, the values 0, 1 & 2 are all returned in the first buffer. - -Understanding buffer with time is only a small step away from understanding buffer with count; instead of passing a count, we pass a `TimeSpan`. The closing of the window (and therefore the buffer's cache) is now dictated by time instead of the number of values. This is now more complicated as we have introduced some sort of scheduling. To produce the `IList` at the correct point in time, we need a scheduler assigned to perform the timing. Incidentally, this makes testing a lot easier. - -`Example Buffer with time of 5 units` - -
-
source|-0-1-2-3-4-5-6-7-8-9-|
-
result|----0----2----5----7-|
-
           1    3    6    8
-
                4         9
-
- -## Window - -The `Window` operators are very similar to the `Buffer` operators; they only really differ by their return type. Where `Buffer` would take an `IObservable` and return an `IObservable>`, the Window operators return an `IObservable>`. It is also worth noting that the `Buffer` operators will not yield their buffers until the window closes. - -Here we can see the simple overloads to `Window`. There is a surprising symmetry with the `Window` and `Buffer` overloads. - -```csharp -public static IObservable> Window( - this IObservable source, - int count) -{...} - -public static IObservable> Window( - this IObservable source, - int count, - int skip) -{...} - -public static IObservable> Window( - this IObservable source, - TimeSpan timeSpan) -{...} - -public static IObservable> Window( - this IObservable source, - TimeSpan timeSpan, - int count) -{...} - -public static IObservable> Window( - this IObservable source, - TimeSpan timeSpan, - TimeSpan timeShift) -{...} - -public static IObservable> Window( - this IObservable source, - TimeSpan timeSpan, - IScheduler scheduler) -{...} - -public static IObservable> Window( - this IObservable source, - TimeSpan timeSpan, - TimeSpan timeShift, - IScheduler scheduler) -{...} - -public static IObservable> Window( - this IObservable source, - TimeSpan timeSpan, - int count, - IScheduler scheduler) -{...} -``` - -This is an example of `Window` with a count of 3 as a marble diagram: - -
-
source |-0-1-2-3-4-5-6-7-8-9|
-
window0|-0-1-2|
-
window1        3-4-5|
-
window2              6-7-8|
-
window3                    9|
-
- -For demonstration purposes, we could reconstruct that with this code. - -```csharp -var windowIdx = 0; -var source = Observable.Interval(TimeSpan.FromSeconds(1)).Take(10); -source.Window(3).Subscribe(window => -{ - var id = windowIdx++; - Console.WriteLine("--Starting new window"); - var windowName = "Window" + thisWindowIdx; - window.Subscribe( - value => Console.WriteLine("{0} : {1}", windowName, value), - ex => Console.WriteLine("{0} : {1}", windowName, ex), - () => Console.WriteLine("{0} Completed", windowName)); -}, -() => Console.WriteLine("Completed")); -``` - -Output: - -``` ---Starting new window -window0 : 0 -window0 : 1 -window0 : 2 -window0 Completed ---Starting new window -window1 : 3 -window1 : 4 -window1 : 5 -window1 Completed ---Starting new window -window2 : 6 -window2 : 7 -window2 : 8 -window2 Completed ---Starting new window -window3 : 9 -window3 Completed -Completed -``` - -`Example of Window with time of 5 units` - -
-
source |-0-1-2-3-4-5-6-7-8-9|
-
window0|-0-1-|
-
window1      2-3-4|
-
window2           -5-6-|
-
window3                7-8-9|
-
- -A major difference we see here is that the `Window` operators can notify you of values from the source as soon as they are produced. The `Buffer` operators, on the other hand, must wait until the window closes before the values can be notified as an entire list. - -### Flattening a Window operation - -I think it is worth noting, at least from an academic standpoint, that the `Window` operators produce `IObservable>`. We have explored the concept of [nested observables](07_Aggregation.html#NestedObservables) in the earlier chapter on [Aggregation](07_Aggregation.html). `Concat`, `Merge` and `Switch` each have an overload that takes an `IObservable>` and returns an `IObservable`. As the `Window` operators ensure that the windows (child sequences) do not overlap, we can use either of the `Concat`, `Switch` or `Merge` operators to turn a windowed sequence back into its original form. - -```csharp -// is the same as Observable.Interval(TimeSpan.FromMilliseconds(200)).Take(10) -var switchedWindow = Observable.Interval(TimeSpan.FromMilliseconds(200)).Take(10) -.Window(TimeSpan.FromMilliseconds(500)) -.Switch(); -``` - -### Customizing windows - -The overloads above provide simple ways to break a sequence into smaller nested windows using a count and/or a time span. Now we will look at the other overloads, that provide more flexibility over how windows are managed. - -```csharp -// Projects each element of an observable sequence into consecutive non-overlapping windows. -// windowClosingSelector : A function invoked to define the boundaries of the produced -// windows. A new window is started when the previous one is closed. -public static IObservable> Window -( - this IObservable source, - Func> windowClosingSelector -) -{...} -``` - -The first of these complex overloads allows us to control when windows should close. The `windowClosingSelector` function is called each time a window is created. Windows are created on subscription and immediately after a window closes; windows close when the sequence from the `windowClosingSelector` produces a value. The value is disregarded so it doesn't matter what type the sequence values are; in fact you can just complete the sequence from `windowClosingSelector` to close the window instead. - -In this example, we create a window with a closing selector. We return the same subject from that selector every time, then notify from the subject whenever a user presses enter from the console. - -```csharp -var windowIdx = 0; -var source = Observable.Interval(TimeSpan.FromSeconds(1)).Take(10); -var closer = new Subject(); -source.Window(() => closer) - .Subscribe(window => - { - var thisWindowIdx = windowIdx++; - Console.WriteLine("--Starting new window"); - var windowName = "Window" + thisWindowIdx; - window.Subscribe( - value => Console.WriteLine("{0} : {1}", windowName, value), - ex => Console.WriteLine("{0} : {1}", windowName, ex), - () => Console.WriteLine("{0} Completed", windowName)); - }, - () => Console.WriteLine("Completed")); - -var input = ""; -while (input!="exit") -{ - input = Console.ReadLine(); - closer.OnNext(Unit.Default); -} -``` -Output (when I hit enter after '1' and '5' are displayed): - -``` ---Starting new window -window0 : 0 -window0 : 1 - -window0 Completed - ---Starting new window -window1 : 2 -window1 : 3 -window1 : 4 -window1 : 5 - -window1 Completed - ---Starting new window -window2 : 6 -window2 : 7 -window2 : 8 -window2 : 9 - -window2 Completed - -Completed -``` - -The most complex overload of `Window` allows us to create potentially overlapping windows. - -```csharp -// Projects each element of an observable sequence into zero or more windows. -// windowOpenings : Observable sequence whose elements denote the creation of new windows. -// windowClosingSelector : A function invoked to define the closing of each produced window. -public static IObservable> Window - -( - this IObservable source, - IObservable windowOpenings, - Func> windowClosingSelector -) -{...} -``` - -This overload takes three arguments - -1. The source sequence -2. A sequence that indicates when a new window should be opened -3. A function that takes a window opening value, and returns a window closing sequence - -This overload offers great flexibility in the way windows are opened and closed. Windows can be largely independent from each other; they can overlap, vary in size and even skip values from the source. - -To ease our way into this more complex overload, let's first try to use it to recreate a simpler version of `Window` (the overload that takes a count). To do so, we need to open a window once on the initial subscription, and once each time the source has produced then specified count. The window needs to close each time that count is reached. To achieve this we only need the source sequence. We will share it by using the `Publish` method, then supply 'views' of the source as each of the arguments. - -```csharp -public static IObservable> MyWindow( - this IObservable source, - int count) -{ - var shared = source.Publish().RefCount(); - var windowEdge = shared - .Select((i, idx) => idx % count) - .Where(mod => mod == 0) - .Publish() - .RefCount(); - return shared.Window(windowEdge, _ => windowEdge); -} -``` - -If we now want to extend this method to offer skip functionality, we need to have two different sequences: one for opening and one for closing. We open a window on subscription and again after the `skip` items have passed. We close those windows after '`count`' items have passed since the window opened. - -```csharp -public static IObservable> MyWindow( - this IObservable source, - int count, - int skip) -{ - if (count <= 0) throw new ArgumentOutOfRangeException(); - if (skip <= 0) throw new ArgumentOutOfRangeException(); - - var shared = source.Publish().RefCount(); - var index = shared - .Select((i, idx) => idx) - .Publish() - .RefCount(); - - var windowOpen = index.Where(idx => idx % skip == 0); - var windowClose = index.Skip(count-1); - - return shared.Window(windowOpen, _ => windowClose); -} -``` - -We can see here that the `windowClose` sequence is re-subscribed to each time a window is opened, due to it being returned from a function. This allows us to reapply the skip (`Skip(count-1)`) for each window. Currently, we ignore the value that the `windowOpen` pushes to the `windowClose` selector, but if you require it for some logic, it is available to you. - -As you can see, the `Window` operator can be quite powerful. We can even use `Window` to replicate other operators; for instance we can create our own implementation of `Buffer` that way. We can have the `SelectMany` operator take a single value (the window) to produce zero or more values of another type (in our case, a single `IList`). To create the `IList` without blocking, we can apply the `Aggregate` method and use a new `List` as the seed. - -```csharp -public static IObservable> MyBuffer(this IObservable source, int count) -{ - return source.Window(count) - .SelectMany(window => - window.Aggregate( - new List(), - (list, item) => - { - list.Add(item); - return list; - })); -} -``` - -It may be an interesting exercise to try implementing other time shifting methods, like `Sample` or `Throttle`, with `Window`. - -## Join - -The `Join` operator allows you to logically join two sequences. Whereas the `Zip` operator would pair values from the two sequences together by index, the `Join` operator allows you join sequences by intersecting windows. Like the `Window` overload we just looked at, you can specify when a window should close via an observable sequence; this sequence is returned from a function that takes an opening value. The `Join` operator has two such functions, one for the first source sequence and one for the second source sequence. Like the `Zip` operator, we also need to provide a selector function to produce the result item from the pair of values. - -```csharp -public static IObservable Join -( - this IObservable left, - IObservable right, - Func> leftDurationSelector, - Func> rightDurationSelector, - Func resultSelector -) -``` - -This is a complex signature to try and understand in one go, so let's take it one parameter at a time. - -`IObservable left` is the source sequence that defines when a window starts. This is just like the `Buffer` and `Window` operators, except that every value published from this source opens a new window. In `Buffer` and `Window`, by contrast, some values just fell into an existing window. - -I like to think of `IObservable right` as the window value sequence. While the left sequence controls opening the windows, the right sequence will try to pair up with a value from the left sequence. - -Let us imagine that our left sequence produces a value, which creates a new window. If the right sequence produces a value while the window is open, then the `resultSelector` function is called with the two values. This is the crux of join, pairing two values from a sequence that occur within the same window. This then leads us to our next question; when does the window close? The answer illustrates both the power and the complexity of the `Join` operator. - -When `left` produces a value, a window is opened. That value is also passed, at that time, to the `leftDurationSelector` function, which returns an `IObservable`. When that sequence produces a value or completes, the window for that value is closed. Note that it is irrelevant what the type of `TLeftDuration` is. This initially left me with the feeling that `IObservable` was a bit excessive as you effectively just need some sort of event to say 'Closed'. However, by being allowed to use `IObservable`, you can do some clever manipulation as we will see later. - -Let us now imagine a scenario where the left sequence produces values twice as fast as the right sequence. Imagine that in addition we never close the windows; we could do this by always returning `Observable.Never()` from the `leftDurationSelector` function. This would result in the following pairs being produced. - -Left Sequence - -
-
L 0-1-2-3-4-5-
-
- -Right Sequence - -
-
R --A---B---C-
-
-
-
0, A
-
1, A
-
0, B
-
1, B
-
2, B
-
3, B
-
0, C
-
1, C
-
2, C
-
3, C
-
4, C
-
5, C
-
- -As you can see, the left values are cached and replayed each time the right produces a value. - -Now it seems fairly obvious that, if I immediately closed the window by returning `Observable.Empty`, or perhaps `Observable.Return(0)`, windows would never be opened thus no pairs would ever get produced. However, what could I do to make sure that these windows did not overlap- so that, once a second value was produced I would no longer see the first value? Well, if we returned the `left` sequence from the `leftDurationSelector`, that could do the trick. But wait, when we return the sequence `left` from the `leftDurationSelector`, it would try to create another subscription and that may introduce side effects. The quick answer to that is to `Publish` and `RefCount` the `left` sequence. If we do that, the results look more like this. - -
-
left  |-0-1-2-3-4-5|
-
right |---A---B---C|
-
result|---1---3---5
-
          A   B   C
-
- -The last example is very similar to `CombineLatest`, except that it is only producing a pair when the right sequence changes. We could use `Join` to produce our own version of [`CombineLatest`](12_CombiningSequences.html#CombineLatest). If the values from the left sequence expire when the next value from left was notified, then I would be well on my way to implementing my version of `CombineLatest`. However I need the same thing to happen for the right. Luckily the `Join` operator provides a `rightDurationSelector` that works just like the `leftDurationSelector`. This is simple to implement; all I need to do is return a reference to the same left sequence when a left value is produced, and do the same for the right. The code looks like this. - -```csharp -public static IObservable MyCombineLatest -( - IObservable left, - IObservable right, - Func resultSelector -) -{ - var refcountedLeft = left.Publish().RefCount(); - var refcountedRight = right.Publish().RefCount(); - - return Observable.Join( - refcountedLeft, - refcountedRight, - value => refcountedLeft, - value => refcountedRight, - resultSelector); -} -``` - -While the code above is not production quality (it would need to have some gates in place to mitigate race conditions), it shows how powerful `Join` is; we can actually use it to create other operators! - -## GroupJoin - -When the `Join` operator pairs up values that coincide within a window, it will pass the scalar values left and right to the `resultSelector`. The `GroupJoin` operator takes this one step further by passing the left (scalar) value immediately to the `resultSelector` with the right (sequence) value. The right parameter represents all of the values from the right sequences that occur within the window. Its signature is very similar to `Join`, but note the difference in the `resultSelector` parameter. - -```csharp -public static IObservable GroupJoin -( - this IObservable left, - IObservable right, - Func> leftDurationSelector, - Func> rightDurationSelector, - Func, TResult> resultSelector -) -``` - -If we went back to our first `Join` example where we had - -* the `left` producing values twice as fast as the right, -* the left never expiring -* the right immediately expiring - -this is what the result may look like - -
-
left              |-0-1-2-3-4-5|
-
right             |---A---B---C|
-
0th window values   --A---B---C|
-
1st window values     A---B---C|
-
2nd window values       --B---C|
-
3rd window values         B---C|
-
4th window values           --C|
-
5th window values             C|
-
- -We could switch it around and have the left expired immediately and the right never expire. The result would then look like this: - -
-
left              |-0-1-2-3-4-5|
-
right             |---A---B---C|
-
0th window values   |
-
1st window values     A|
-
2nd window values       A|
-
3rd window values         AB|
-
4th window values           AB|
-
5th window values             ABC|
-
- -This starts to make things interesting. Perceptive readers may have noticed that with `GroupJoin` you could effectively re-create your own `Join` method by doing something like this: - -```csharp -public IObservable MyJoin( - IObservable left, - IObservable right, - Func> leftDurationSelector, - Func> rightDurationSelector, - Func resultSelector) -{ - return Observable.GroupJoin - ( - left, - right, - leftDurationSelector, - rightDurationSelector, - (leftValue, rightValues)=> rightValues.Select(rightValue=>resultSelector(leftValue, rightValue)) - ) - .Merge(); -} -``` - -You could even create a crude version of `Window` with this code: - -```csharp -public IObservable> MyWindow(IObservable source, TimeSpan windowPeriod) -{ - return Observable.Create>(o =>; - { - var sharedSource = source - .Publish() - .RefCount(); - - var intervals = Observable.Return(0L) - .Concat(Observable.Interval(windowPeriod)) - .TakeUntil(sharedSource.TakeLast(1)) - .Publish() - .RefCount(); - - return intervals.GroupJoin( - sharedSource, - _ => intervals, - _ => Observable.Empty(), - (left, sourceValues) => sourceValues) - .Subscribe(o); - }); -} -``` - -For an alternative summary of reducing operators to a primitive set see Bart DeSmet's [excellent MINLINQ post](http://blogs.bartdesmet.net/blogs/bart/archive/2010/01/01/the-essence-of-linq-minlinq.aspx "The essence of LINQ - MinLINQ") (and [follow-up video](http://channel9.msdn.com/Shows/Going+Deep/Bart-De-Smet-MinLINQ-The-Essence-of-LINQ "The essence of LINQ - MINLINQ - Channel9") ). Bart is one of the key members of the team that built Rx, so it is great to get some insight on how the creators of Rx think. - -Showcasing `GroupJoin` and the use of other operators turned out to be a fun academic exercise. While watching videos and reading books on Rx will increase your familiarity with it, nothing replaces the experience of actually picking it apart and using it in earnest. - -`GroupJoin` and other window operators reduce the need for low-level plumbing of state and concurrency. By exposing a high-level API, code that would be otherwise difficult to write, becomes a cinch to put together. For example, those in the finance industry could use `GroupJoin` to easily produce real-time Volume or Time Weighted Average Prices (VWAP/TWAP). - -Rx delivers yet another way to query data in motion by allowing you to interrogate sequences of coincidence. This enables you to solve the intrinsically complex problem of managing state and concurrency while performing matching from multiple sources. By encapsulating these low level operations, you are able to leverage Rx to design your software in an expressive and testable fashion. Using the Rx operators as building blocks, your code effectively becomes a composition of many simple operators. This allows the complexity of the domain code to be the focus, not the otherwise incidental supporting code. - -# Summary - -When LINQ was first released, it brought the ability to query static data sources directly into the language. With the volume of data produced in modern times, only being able to query data-at-rest, limits your competitive advantage. Being able to make sense of information as it flows, opens an entirely new spectrum of software. We need more than just the ability to react to events, we have been able to do this for years. We need the ability to construct complex queries across multiple sources of flowing data. - -Rx brings event processing to the masses by allowing you to query data-in-motion directly from your favorite .NET language. Composition is king: you compose operators to create queries and you compose sequences to enrich the data. Rx leverages common types, patterns and language features to deliver an incredibly powerful library that can change the way you write modern software. - -Throughout the book you will have learnt the basic types and principle of Rx. You have discovered functional programming concepts and how they apply to observable sequences. You can identify potential pitfalls of certain patterns and how to avoid them. You understand the internal working of the operators and are even able to build your own implementations of many of them. Finally you are able to construct complex queries that manage concurrency in a safe and declarative way while still being testable. - -You have everything you need to confidently build applications using the Reactive Extensions for .NET. If you do find yourself at any time stuck, and not sure how to solve a problem or need help, you can probably solve it without outside stimulus. Remember to first draw a marble diagram of what you think the problem space is. This should allow you to see the patterns in the flow which will help you choose the correct operators. Secondly, remember to follow the [Guidelines](18_UsageGuidelines.html). - -Third, write a spike. Use [LINQPad](http://www.linqpad.net/) or a blank Visual Studio project to flesh out a small sample. Finally, if you are still stuck, your best place to look for help is the MSDN [Rx forum](http://social.msdn.microsoft.com/Forums/en-US/rx/). [StackOverflow.com](http://stackoverflow.com/) is another useful resource too, but with regards to Rx questions, the MSDN forum is dedicated to Rx and seems to have a higher quality of answers. \ No newline at end of file diff --git a/content/18_UsageGuidelines.md b/content/18_UsageGuidelines.md deleted file mode 100644 index 7b0302e..0000000 --- a/content/18_UsageGuidelines.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title : Appendix A Usage guidelines ---- - -# Appendix - -# Usage guidelines - -This is a list of quick guidelines intended to help you when writing Rx queries. - -- Members that return a sequence should never return null. This applies to `IEnumerable` and `IObservable` sequences. Return an empty sequence instead. -- Dispose of subscriptions. -- Subscriptions should match their scope. -- Always provide an `OnError` handler. -- Avoid breaking the monad with blocking operators such as `First`, `FirstOrDefault`, `Last`, `LastOrDefault`, `Single`, `SingleOrDefault` and `ForEach`. -- Avoid switching between monads, i.e. going from `IObservable` to `IEnumerable` and back to `IObservable`. -- Favor lazy evaluation over eager evaluation. -- Break large queries up into parts. Key indicators of a large query: - 1. nesting - 2. over 10 lines of query comprehension syntax - 3. using the into statement -- Name your queries well, i.e. avoid using the names like `query`, `q`, `xs`, `ys`, `subject` etc. -- Avoid creating side effects. If you must create side effects, be explicit by using the `Do` operator. -- Avoid the use of the subject types. Rx is effectively a functional programming paradigm. Using subjects means we are now managing state, which is potentially mutating. Dealing with both mutating state and asynchronous programming at the same time is very hard to get right.Furthermore, many of the operators (extension methods) have been carefully written to ensure that correct and consistent lifetime of subscriptions and sequences is maintained;when you introduce subjects, you can break this. Future releases may also see significant performance degradation if you explicitly use subjects. -- Avoid creating your own implementations of the `IObservable` interface. Favor using the `Observable.Create` factory method overloads instead. -- Avoid creating your own implementations of the `IObserver` interface. Favor using the `Subscribe` extension method overloads instead. -- The subscriber should define the concurrency model. The `SubscribeOn` and `ObserveOn` operators should only ever precede a `Subscribe` method. \ No newline at end of file diff --git a/content/19_DispellingMyths.md b/content/19_DispellingMyths.md deleted file mode 100644 index ee2371a..0000000 --- a/content/19_DispellingMyths.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title : Appendix B Dispelling myths ---- - -# Dispelling event myths - -The previous parts in this book should have given you a solid and broad foundation in the fundamentals of Rx. We will use this based to learn the really fun and sometimes complex parts of Rx. Just before we do, I want to first make sure we are all on the same page and dispel some common myths and misunderstandings. Carrying these misconceptions into a world of concurrency will make things seem magic and mysterious. This normally leads to problematic code. - -## Event myths - -Often in my career, I have found myself involved in the process of interviewing new candidates for developer roles. I have often been surprised about the lack of understanding developers had surrounding .NET events. Carrying these misconception into a world of concurrency will make things seem magic and mysterious. This normally leads to problematic code. Here is a short list of verifiable facts about events. - -
-
Events are just a syntactic implementation of the observer pattern
-
- The += and -= syntax in c# may lead you to think that there is something clever going on here, but it is just the observer pattern; you are providing a delegate to get called back on. Most events pass you data in the form of the sender and the EventArgs. -
-
Events are multicast
-
- Many consumers can register for the same event. Each delegate (handler) will be internally added to a callback list. -
-
Events are single threaded.
-
- There is nothing multithreaded about an event. Each of the callback handlers are each just called in the order that they registered, and they are called sequentially. -
-
Event handlers that throw exceptions stop other handlers being called
-
- Since handlers are called sequentially, they are also called on the same thread that raised the event. This means that, if one handler throws an exception, there cannot be a chance for any user code to intercept the exception. This means that the remaining handlers will not be called. -
-
- -Common myths about events that I have heard (or even believed at some point) include: - -* Handlers are called all at the same time on the thread pool, in parallel -* All handlers will get called. Throwing an exception from a handler will not affect other handlers -* You don't need to unregister an event handler, .NET is managed so it will garbage collect everything for you. - -The silly thing about these myths is that it only takes fifteen minutes to prove them all wrong; you just open up Visual Studio or [LINQPad](http://www.linqpad.net/) and test them out. In my opinion, there is something satisfying about proving something you only believed in good faith to be true. - - - -## Memory management myths - -* Event handles -* IDispose pattern -* Finalise is just as good as Dispose -* GC is free -* Setting to NULL is the same as Dispose or finalise - - \ No newline at end of file diff --git a/content/20_Disposables.md b/content/20_Disposables.md deleted file mode 100644 index da72624..0000000 --- a/content/20_Disposables.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -title : Appendix C Disposables ---- - -# Disposables - -Rx leverages the existing `IDisposable` interface for subscription management. This is an incredibly useful design decision, as users can work with a familiar type and reuse existing language features. Rx further extends its usage of the `IDisposable` type by providing several public implementations of the interface. These can be found in the `System.Reactive.Disposables` namespace. Here, we will briefly enumerate each of them. - - -
-
Disposable.Empty
-
- This static property exposes an implementation of IDisposable that performs no action when the Dispose method is invoked. This can be useful whenever you need to fulfil an interface requirement, like Observable.Create, but do not have any resource management that needs to take place.
-
Disposable.Create(Action)
-
- This static method exposes an implementation of IDisposable that performs the action provided when the Dispose method is invoked. As the implementation follows the guidance to be idempotent, the action will only be called on the first time the Dispose method is invoked.
-
BooleanDisposable
-
- This class simply has the Dispose method and a read-only property IsDisposed. IsDisposed is false when the class is constructed, and is set to true when the Dispose method is invoked. -
-
CancellationDisposable
-
- The CancellationDisposable class offers an integration point between the .NET cancellation paradigm (CancellationTokenSource) and the resource management paradigm (IDisposable). You can create an instance of the CancellationDisposable class by providing a CancellationTokenSource to the constructor, or by having the parameterless constructor create one for you. Calling Dispose will invoke the Cancel method on the CancellationTokenSource. There are two properties (Token and IsDisposed) that CancellationDisposable exposes; they are wrappers for the CancellationTokenSource properties, respectively Token and IsCancellationRequested. -
-
CompositeDisposable
-
- The CompositeDisposable type allows you to treat many disposable resources as one. Common usage is to create an instance of CompositeDisposable by passing in a params array of disposable resources. Calling Dispose on the CompositeDisposable will call dispose on each of these resources in the order they were provided. Additionally, the CompositeDisposable class implements ICollection<IDisposable>; this allows you to add and remove resources from the collection. After the CompositeDisposable has been disposed of, any further resources that are added to this collection will be disposed of instantly. Any item that is removed from the collection is also disposed of, regardless of whether the collection itself has been disposed of. This includes usage of both the Remove and Clear methods. -
-
ContextDisposable
-
- ContextDisposable allows you to enforce that disposal of a resource is performed on a given SynchronizationContext. The constructor requires both a SynchronizationContext and an IDisposable resource. When the Dispose method is invoked on the ContextDisposable, the provided resource will be disposed of on the specified context. -
-
MultipleAssignmentDisposable
-
- The MultipleAssignmentDisposable exposes a read-only IsDisposed property and a read/write property Disposable. Invoking the Dispose method on the MultipleAssignmentDisposable will dispose of the current value held by the Disposable property. It will then set that value to null. As long as the MultipleAssignmentDisposable has not been disposed of, you are able to set the Disposable property to IDisposable values as you would expect. Once the MultipleAssignmentDisposable has been disposed, attempting to set the Disposable property will cause the value to be instantly disposed of; meanwhile, Disposable will remain null. -
-
RefCountDisposable
-
- The RefCountDisposable offers the ability to prevent the disposal of an underlying resource until all dependent resources have been disposed. You need an underlying IDisposable value to construct a RefCountDisposable. You can then call the GetDisposable method on the RefCountDisposable instance to retrieve a dependent resource. Each time a call to GetDisposable is made, an internal counter is incremented. Each time one of the dependent disposables from GetDisposable is disposed, the counter is decremented. Only if the counter reaches zero will the underlying be disposed of. This allows you to call Dispose on the RefCountDisposable itself before or after the count is zero. -
-
ScheduledDisposable
-
- In a similar fashion to ContextDisposable, the ScheduledDisposable type allows you to specify a scheduler, onto which the underlying resource will be disposed. You need to pass both the instance of IScheduler and instance of IDisposable to the constructor. When the ScheduledDisposable instance is disposed of, the disposal of the underlying resource will be scheduled onto the provided scheduler. -
-
SerialDisposable
-
- SerialDisposable is very similar to MultipleAssignmentDisposable, as they both expose a read/write Disposable property. The contrast between them is that whenever the Disposable property is set on a SerialDisposable, the previous value is disposed of. Like the MultipleAssignmentDisposable, once the SerialDisposable has been disposed of, the Disposable property will be set to null and any further attempts to set it will have the value disposed of. The value will remain as null. -
-
SingleAssignmentDisposable
-
- The SingleAssignmentDisposable class also exposes IsDisposed and Disposable properties. Like MultipleAssignmentDisposable and SerialDisposable, the Disposable value will be set to null when the SingleAssignmentDisposable is disposed of. The difference in implementation here is that the SingleAssignmentDisposable will throw an InvalidOperationException if there is an attempt to set the Disposable property while the value is not null and the SingleAssignmentDisposable has not been disposed of. -
-
\ No newline at end of file diff --git a/content/A_IoStreams.md b/content/A_IoStreams.md new file mode 100644 index 0000000..bd24515 --- /dev/null +++ b/content/A_IoStreams.md @@ -0,0 +1,25 @@ +# Appendix A: What's Wrong with Classic IO Streams + +In the [Key Types](02_KeyTypes.md#what-about-streams) chapter, I stated that `System.IO.Stream` is not a good fit for modelling the kinds of event streams we work with in Rx. This appendix explains why. + +The abstraction that `System.IO.Stream` represents was designed as a way for an operating system to enable application code to communicate with devices that could receive and/or produce streams of bytes. This makes them a good model for the [reel to reel tape storage devices](https://en.wikipedia.org/wiki/IBM_7-track) that were commonplace back when this kind of stream was designed, but unnecessarily cumbersome if you just want to represent a sequence of values. Over the years, streams have been co-opted to represent an increasingly diverse range of things, including files, keyboards, network connections, and OS status information, meaning that by the time .NET came along in 2002, its `Stream` type needed a mixture of features to accommodate some quite diverse scenarios. And since not all streams are alike, it's quite common for some of these features to not to work on some streams. + +IO streams were designed to support efficient delivery of fairly high volumes of byte data, often with devices that inherently work with data in big chunks. In the main scenarios for which they were designed, read and write operations would involve calls into operating system APIs, which are typically relatively expensive, so the basic read and write operations expect to work with arrays of bytes. (If you make one system call to deliver thousands of bytes, the overhead of that single call is far lower than if you work one byte at a time.) While that's good for efficiency, it can be inconvenient for developers (and irksome if you were hoping to use streams purely to represent in-process event streams that don't actually need to make system calls, and therefore don't get to enjoy the upside of this performance/convenience trade off). + +There is a standard band-aid kind of a fix for this: libraries that present streams to application code often don't represent the underlying OS stream directly. Instead, they are often _buffered_, meaning that the library will perform reads fairly large chunks, and hold recently-fetched bytes in memory until the application code asks for them. This can enable methods like .NET's single-byte [`Stream.ReadByte`](https://learn.microsoft.com/en-us/dotnet/api/system.io.stream.readbyte) method to work reasonably efficiently: several thousand calls to that method might cause only one call to the operating system API that provides access to whatever physical device the stream represents. Likewise, if you're sending data into an IO stream, a buffered stream will wait until you've supplied some minimum quantity of data (4096 bytes is a common default with certain .NET `Stream`s) before it actually sends any data to its destination. + +But this could be a serious problem for the kinds of event sources we represent in Rx. If an IO stream deliberately insulates you from the real movement of data, that could introduce delays that might be disastrous in a financial application where delays in delivery and receipt of information can have enormous financial consequences. And even if there aren't direct financial implications, this kind of buffering would be unhelpful in representing events in a user interface. Nobody wants to have to click a button several thousand times before the application starts to act on that input. + +There's also the problem that you don't always know which kind of stream you've been given. If you know for a fact that you've got an unbuffered stream representing a file on disk (because you created that stream yourself) you'd typically write quite different code than you would if you knew you had a buffered stream. But if you've written a method that takes a `Stream` argument, it's not clear what you've got, so you don't necessarily know which coding strategy is best. + +Another problem is that because they are byte-oriented, there's no such thing as a `System.IO.Stream` that produces more complex values. If you want a stream of `int` values (which isn't a _much_ more complex idea than a stream of _byte_ values) `System.IO.Stream` does nothing to help you, and until very recently it might even hinder you. If you use the normal `Read` or `ReadAsync` methods, you can try reading four bytes at a time but a `System.IO.Stream` is at liberty to decide that it's only going to return three. (The reason streams are allowed to be petty in this way is that the original design presumes that a stream represents some underlying device that might inherently work with fixed size units of data. Disk drives and SSDs are incapable of reading or writing individual bytes; instead, each operation works with some whole number of 'sectors' each of which are hundreds or thousands of bytes long. So a read operation might simply be unable to give you exactly as many bytes as you asked for. This can also come into play for a stream that represents data coming in over the network: such streams might already have received some data, but less than you've asked for, and they might decide to return what they've already got instead of making you wait until the next network message arrives.) It's now the consuming code's problem to work out how to deal with that. .NET 7.0 finally fixed this problem (only about two decades after `Stream` first appeared) by adding the [`ReadExactly`](https://learn.microsoft.com/en-us/dotnet/api/system.io.stream.readexactly) and [`ReadExactlyAsync`](https://learn.microsoft.com/en-us/dotnet/api/system.io.stream.readexactlyasync) methods, but if you have to target .NET Framework, these methods are unavailable and you still have to solve this entirely yourself. + +Even if you use the new methods (or you write wrappers to deal with these issues caused by `Stream`'s origins as an abstraction for a magnetic tape storage device) there are still shortcomings. If you want the type system to help you to distinguish between a stream of `int` values and a stream of `float` values, `Stream` won't help you. You'll end up needing some different abstraction that has a type parameter. Something like `IObservable`. The fact that we know exactly what shape of data to expect from `IObservable` is critical to making many of the LINQ operators it supports practical. + +Another potential source of confusion is Unix's "everything is a file" design feature. The operating system represents all manner of things through the same OS abstractions as files, and this simplifies the OS design, and in some cases enables you to apply tools originally designed for files in creative ways. But the downside is that some streams are finicky. It's possible to end up with a stream that looks like any other from a .NET type system point of view, but which only works if you read or write in blocks of some particular size. + +Conversely, Rx's strictly defined rules for how observable sources interact with their subscribers means we know exactly where we stand. + +There isn't a clear model for how streams might support multiple subscribers. Programs such as the Unix `tail` command are able to 'follow' changes to a file, but the way they achieve this is nothing like as simple as two observers both calling `Subscribe`. + +And these are just the problems on the consumer side. It's not much fun if you want to implement a source of events as a `Stream` either. To implement your own type that derives from `Stream`, you'll need to implement all ten of the abstract members it defines: 5 properties and 5 methods. This is a far cry from the simple ways `System.Reactive` provides to implement an Rx event source. diff --git a/content/B_Disposables.md b/content/B_Disposables.md new file mode 100644 index 0000000..f2746f8 --- /dev/null +++ b/content/B_Disposables.md @@ -0,0 +1,101 @@ +--- +title : Appendix B Disposables +--- + +# Appendix B: Disposables + +Rx represents subscriptions using the existing `IDisposable` interface. This design choice means we can use existing language features that know how to work with this interface. Rx also provides several public implementations of `IDisposable`. These can be found in the `System.Reactive.Disposables` namespace. This appendix briefly describes each of them. + +With the exception of [`ScheduledDisposable`](#scheduleddisposable), these have no particular connection to Rx, and can be useful in any code that needs to work with `IDisposable`. (This code all lives in `System.Reactive` though, so although you could uses these features entirely outside of Rx-based code, you will still be taking a dependency on Rx.NET if you do so.) + +## `Disposable.Empty` +This static property exposes an implementation of `IDisposable` that performs no action when the `Dispose` method is invoked. This can be useful when you are obliged to supply an `IDisposable` (which can happen if you use `Observable.Create`) but don't need to do anything upon disposal. + +## `Disposable.Create(Action)` + +This static method exposes an implementation of `IDisposable` that invokes the method supplied when the `Dispose` method is invoked. As the implementation follows the guidance to be idempotent, the action will only be called on the first time the `Dispose` method is invoked. + +## `BooleanDisposable` + +This class implements `IDisposable.Dispose` method and also defines a read-only property `IsDisposed`. `IsDisposed` is false when the class is constructed, and is set to true when the `Dispose` method is invoked. + +## `CancellationDisposable` + +The `CancellationDisposable` class offers an integration point between the .NET [cancellation paradigm](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-cancellation) (`CancellationTokenSource`) and the resource management paradigm (`IDisposable`). You can create an instance of the `CancellationDisposable` class by providing a `CancellationTokenSource` to the constructor, or by having the parameterless constructor create one for you. Calling `Dispose` will invoke the `Cancel` method on the `CancellationTokenSource`. There are two properties (`Token` and `IsDisposed`) that `CancellationDisposable` exposes; they are wrappers for the `CancellationTokenSource` properties, respectively `Token` and `IsCancellationRequested`. + + +## `CompositeDisposable` + +The `CompositeDisposable` type allows you to treat many disposable resources as one. You can create an instance of `CompositeDisposable` by passing in a params array of disposable resources. Calling `Dispose` on the `CompositeDisposable` will call dispose on each of these resources in the order they were provided. Additionally, the `CompositeDisposable` class implements `ICollection`; this allows you to add and remove resources from the collection. After the `CompositeDisposable` has been disposed of, any further resources that are added to this collection will be disposed of instantly. Any item that is removed from the collection is also disposed of, regardless of whether the collection itself has been disposed of. This includes usage of both the `Remove` and `Clear` methods. + +## `ContextDisposable` +`ContextDisposable` allows you to enforce that disposal of a resource is performed on a given `SynchronizationContext`. The constructor requires both a `SynchronizationContext` and an `IDisposable` resource. When the `Dispose` method is invoked on the `ContextDisposable`, the provided resource will be disposed of on the specified context. + +## `MultipleAssignmentDisposable` + +The `MultipleAssignmentDisposable` exposes a read-only `IsDisposed` property and a read/write property `Disposable`. Invoking the `Dispose` method on the `MultipleAssignmentDisposable` will dispose of the current value held by the `Disposable` property. It will then set that value to null. As long as the `MultipleAssignmentDisposable` has not been disposed of, you are able to set the `Disposable` property to `IDisposable` values as you would expect. Once the `MultipleAssignmentDisposable` has been disposed, attempting to set the `Disposable` property will cause the value to be instantly disposed of; meanwhile, `Disposable` will remain null. + +## `RefCountDisposable` + +The `RefCountDisposable` offers the ability to prevent the disposal of an underlying resource until all dependent resources have been disposed. You need an underlying `IDisposable` value to construct a `RefCountDisposable`. You can then call the `GetDisposable` method on the `RefCountDisposable` instance to retrieve a dependent resource. Each time a call to `GetDisposable` is made, an internal counter is incremented. Each time one of the dependent disposables from `GetDisposable` is disposed, the counter is decremented. Only if the counter reaches zero will the underlying be disposed of. This allows you to call `Dispose` on the `RefCountDisposable` itself before or after the count is zero. + +## `ScheduledDisposable` + +In a similar fashion to `ContextDisposable`, the `ScheduledDisposable` type allows you to specify a scheduler, onto which the underlying resource will be disposed. You need to pass both the instance of `IScheduler` and instance of `IDisposable` to the constructor. When the `ScheduledDisposable` instance is disposed of, the disposal of the underlying resource will be executed through the provided scheduler. + +## `SerialDisposable` + +`SerialDisposable` is very similar to `MultipleAssignmentDisposable`, as they both expose a read/write `Disposable` property. The contrast between them is that whenever the `Disposable` property is set on a `SerialDisposable`, the previous value is disposed of. Like the `MultipleAssignmentDisposable`, once the `SerialDisposable` has been disposed of, the `Disposable` property will be set to null and any further attempts to set it will have the value disposed of. The value will remain as null. + +## `SingleAssignmentDisposable` + +The `SingleAssignmentDisposable` class also exposes `IsDisposed` and `Disposable` properties. Like `MultipleAssignmentDisposable` and `SerialDisposable`, the `Disposable` value will be set to null when the `SingleAssignmentDisposable` is disposed of. The difference in implementation here is that the `SingleAssignmentDisposable` will throw an `InvalidOperationException` if there is an attempt to set the `Disposable` property while the value is not null and the `SingleAssignmentDisposable` has not been disposed of. + + diff --git a/content/C_UsageGuidelines.md b/content/C_UsageGuidelines.md new file mode 100644 index 0000000..902059d --- /dev/null +++ b/content/C_UsageGuidelines.md @@ -0,0 +1,26 @@ +--- +title : Appendix C Usage guidelines +--- + +# Appendix C: Usage guidelines + +This is a list of quick guidelines intended to help you when writing Rx queries. + +- Members that return a sequence should never return null. This applies to `IEnumerable` and `IObservable` sequences. Return an empty sequence instead. +- Dispose of subscriptions only if you need to unsubscribe from them early. +- Always provide an `OnError` handler. +- Avoid blocking operators such as `First`, `FirstOrDefault`, `Last`, `LastOrDefault`, `Single`, `SingleOrDefault` and `ForEach`.; use the non-blocking alternative such as `FirstAsync`. +- Avoid switching back and forth between `IObservable` and `IEnumerable` +- Favour lazy evaluation over eager evaluation. +- Break large queries up into parts. Key indicators of a large query: + 1. nesting + 2. over 10 lines of query expression syntax + 3. using the `into` keyword +- Name your observables well, i.e. avoid using variable names like `query`, `q`, `xs`, `ys`, `subject` etc. +- Avoid creating side effects. If you really can't avoid it, don't bury the side effects in callbacks for operators designed to be use functionally such as `Select` or `Where`. be explicit by using the `Do` operator. +- Where possible, prefer `Observable.Create` to subjects as a means of defining new Rx sources. +- Avoid creating your own implementations of the `IObservable` interface. Use `Observable.Create` (or subjects if you really need to). +- Avoid creating your own implementations of the `IObserver` interface. Favour using the `Subscribe` extension method overloads instead. +- The application should define the concurrency model. + - If you need to schedule deferred work, use schedulers + - The `SubscribeOn` and `ObserveOn` operators should always be right before a `Subscribe` method. (So don't sandwich it, e.g. `source.SubscribeOn(s).Where(x => x.Foo)`.) \ No newline at end of file diff --git a/content/D_AlgebraicUnderpinnings.md b/content/D_AlgebraicUnderpinnings.md new file mode 100644 index 0000000..e51f814 --- /dev/null +++ b/content/D_AlgebraicUnderpinnings.md @@ -0,0 +1,600 @@ +--- +title : Appendix D Rx's Algebraic Underpinnings +--- + +# Appendix D: Rx's Algebraic Underpinnings + +Rx operators can be combined together in more or less any way you can imagine, and they generally combine without any problems. The fact that this works is not merely a happy accident. In general, integration between software components is often one of the largest sources of pain in software development, so the fact that it works so well is remarkable. This is in large part thanks to the fact that Rx relies on some underlying theory. Rx has been designed so that you don't need to know these details to use it, but curious developers typically want to know these things. + +The earlier sections of the book have already talked about one formal aspect of Rx: the contract between observable sources and their observables. There is a clearly defined grammar for what constitutes acceptable use of `IObserver`. This goes beyond what the .NET type system is able to enforce, so we are reliant on code doing the right thing. However, the `System.Reactive` library does always adhere to this contract, and it also has some guard types in place that detect when application code has not quite played by the rules, and to prevent this from wreaking havoc. + +The `IObserver` grammar is important. Components rely on it to ensure correct operation. Consider the `Where` operator, for example. It provides its own `IObserver` implementation with which it subscribes to the underlying source. This receives items from that source, and then decides which to forward to the observer that subscribed to the `IObservable` presented by `Where`. You could imagine it looking something like this: + + +```cs +public class OverSimplifiedWhereObserver : IObserver +{ + private IObserver downstreamSubscriber; + private readonly Func predicate; + + public OverSimplifiedWhereObserver( + IObserver downstreamSubscriber, Func predicate) + { + this.downstreamSubscriber = downstreamSubscriber; + this.predicate = predicate; + } + + public void OnNext(T value) + { + if (this.predicate(value)) + { + this.downstreamSubscriber.OnNext(value); + } + } + + public void OnCompleted() + { + this.downstreamSubscriber.OnCompleted(); + } + + public void OnError(Exception x) + { + this.downstreamSubscriber.OnCompleted(x); + } +} +``` + +This does not take any explicit steps to follow the `IObserver` grammar. It doesn't need to if the source to which it is subscribes also obeys those rules. Since this only ever calls its subscriber's `OnNext` in its own `OnNext`, and likewise for `OnCompleted` and `OnError`, then as long as the underlying source to which this operator is subscribed obeys the rules for how to call those three methods, this class will in turn also follow those rules automatically. + +In fact, `System.Reactive` is not quite that trusting. It does have some code that detects certain violations of the grammar, but even these measures just ensure that the grammar is adhered to once execution enters Rx. There are some checks at the boundaries of the system, but Rx's innards rely heavily on the fact that upstream sources will abide by the rules. + +However, the grammar for `IObservable` is not the only place where Rx relies on formalism to ensure correct operation. It also depends on a particular set of mathematical concepts: + +* Monads +* Catamorphisms +* Anamorphisms + +Standard LINQ operators can be expressed purely in terms of these three ideas. + +These concepts come from [category theory](https://en.wikipedia.org/wiki/Category_theory), a pretty abstract branch of mathematics concerned with mathematical structures. In the late 1980s, a few computer scientists were exploring this area of maths with a view to using them to model the behaviour of programs. [Eugenio Moggi](https://en.wikipedia.org/wiki/Eugenio_Moggi) (an Italian computer scientist who was, at the time, working at the University of Edinburgh) is generally credited for realising that monads in particular are well suited to describing computations, as his 1991 paper, [Notions of computations and monads](https://person.dibris.unige.it/moggi-eugenio/ftp/ic91.pdf) explains. These theoretical ideas and were incorporated into the Haskell programming language, primarily by Philip Wadler and Simon Peyton Jones, who published a proposal for [monadic handling of IO](https://www.microsoft.com/en-us/research/wp-content/uploads/1993/01/imperative.pdf) in 1992. By 1996, this had been fully incorporated into Haskell in its v1.3 release to enable programs' handling of input and output (e.g., handling user input, or writing data to files) to work in a way that was underpinned by strong mathematical foundations. This has widely been recognized as a significant improvement on Haskell's earlier attempts to model the messy realities of IO in a purely functional language. + +Why does any of this matter? These mathematical foundations are exactly why LINQ operators can be freely composed. + +The mathematical discipline of category theory has developed a very deep understanding of various mathematical structures, and one of the most useful upshots for programmers is that it offers certain rules which, if followed, can ensure that software elements will behave well when combined together. This is, admittedly, a rather hand-wavey explanation. If you'd like a detailed explanation of exactly how category theory can be applied to programming, and why it is useful to do so, I can highly recommend [Bartosz Milewski's 'Category Theory for Programmers'](https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface/). The sheer volume of information available there should make it clear why I'm not about to attempt a full explanation in this appendix. Instead, my goal is just to outline the basic concepts, and explain how they correspond to features of Rx. + +## Monads + +Monads are the most important mathematical concept underpinning LINQ's (and therefore Rx's) design. It's not necessary to have the faintest idea of what a monad is to be able to use Rx. The most important fact is that their mathematical characteristics (and in particular, their support for composition) are what enable Rx operators to combine together freely. From a practical perspective, all that really matters is that it just works, but if you've read this far, that probably won't satisfy you. + +It is often hard to describe precisely what mathematical objects really are, because they are inherently abstract. So before I get to the definition of a monad, it may be helpful to understand how LINQ uses this concept. LINQ treats a monad as a general purpose representation of a container of items. As developers, we know that there are many kinds of things that can contain items. There are arrays, and other collection types such as `IList`. There are also databases, and although there are many ways in which a database table is quite different from an array, there are also some ways in which they are similar. The basic insight underpinning LINQ is that there is a mathematical abstraction that captures the essence of what containers have in common. If we determine that some .NET type represents a monad, then all of the work that mathematicians have done over the years to understand the characteristics and behaviours of monads will be applicable to that .NET type. + +For example, `IEnumerable` is a monad, as is `IQueryable`. And crucially for Rx, `IObservable` is as well. LINQ's design relies on the properties of monads, so if you can determine that some .NET type is a monad, then it is a candidate for a LINQ implementation. (Conversely, if you try to create a LINQ provider for a type that is not a monad, you are likely to have problems.) + +So what are these characteristics that LINQ relies on? The first relates directly to containment: it must be possible to take some value and put it inside your monad. You'll notice that all the examples I've given so far are generic types, and that's no coincidence: monads are essentially type constructors, and the type argument indicates the kind of thing you want the monad to contain. So given some value of type `T`, it must be possible to wrap that in a monad for that type. Given an `int` we can get an `IEnumerable`, and if we couldn't do that, `IEnumerable` would not be monadic. The second characteristic is slightly harder to pin down without getting lost in high abstraction, but it essentially boils down to the idea that if we have functions that we can apply to individual contained items, and if those functions compose in useful ways, we can create new functions that operate not on individual values but on the containers, and crucially, those functions can also be composed in the same ways. + +This enables us to work with entire containers as freely as we can work with individual values. + +### The monadic operations: return and bind + +We've just seen that monads aren't just a type. They need to supply certain operations. This first operation, the ability to wrap a value in the monad, is sometimes called _unit_ in mathematical texts, but in a computing context it is more often known as _return_. This is how [`Observable.Return`](03_CreatingObservableSequences.md#observablereturn) got its name. + +There doesn't technically need to be an actual function. The monadic laws are satisfied as long as some mechanism is available to put a value into the monad. For example, unlike `Observable`, the `Enumerable` type does _not_ define a `Return` method, but it doesn't matter. You can just write `new[] { value }`, and that's enough. + +Monads are required to provide just one other operation. The mathematical literature calls it _bind_, some programming systems call it `flatMap`, and LINQ refers to it as `SelectMany`. This is the one that tends to cause the most head scratching, because although it has a clear formal definition, it's harder to say what it really does than with _return_. However, we're looking at monads through their ability to represent containers, and this offers a fairly straightforward way to understand bind/`SelectMany`: it lets us take a container where every item is a nested container (e.g., an array of arrays, or an `IEnumerable>`) and flatten it out. For example, a list of lists would become one list, containing every item from every list. As we'll soon see, this is not obviously related to the formal mathematical definition of bind, which is altogether more abstract, but it is compatible with it, which is all that's needed for us to enjoy the fruits of the mathematicians' labours. + +Critically, to qualify as a monad, the two operations just described (return and bind) must conform to certain rules, or _laws_ as they are often described in the literature. There are three laws. All of them govern how the bind operation works, and two of these are concerned with how return and bind interact with one another. These laws are the foundation of the composability of operations based on monads. The laws are somewhat abstract, so it isn't exactly obvious _why_ they enable this, but they are non-negotiable. If your type and operations don't follow these laws, then you don't have a monad, so you can't rely on the characteristics monads guarantee. + +So what does bind actually look like? Here's how it looks for `IEnumerable`: + +```cs +public static IEnumerable SelectMany ( + this IEnumerable source, + Func> selector); +``` + +So it is a function that takes two inputs. The first is an `IEnumerable`. The second input is itself a function which, when supplied with a `TSource` produces an `IEnumerable`. And when you invoke `SelectMany` (aka _bind_) with these two arguments, you get back an `IEnumerable`. Although formal definition of bind requires it to have this shape, it doesn't dictate any particular behaviour—anything that conforms to the laws is acceptable. But in the context of LINQ, we do expect a specific behaviour: this will invoke the function (the second argument) once for every `TSource` in the source enumerable (the first argument), and then collect all of the `TResult` values produced by all of the `IEnumerable` collections returned by all of the invocations of that function, wrapping them as a one big `IEnumerable`. In this specific case of `IEnumerable` we could describe `SelectMany` as getting one output collection for each input value, and then concatenating all of those output collections. + +But we've now got a little too specific. Even if we're looking specifically at LINQ's use of monads to represent generalised containers, `SelectMany` doesn't necessarily entail concatenation. It merely requires that the container returned by `SelectMany` contains all of the items produced by the function. Concatenation is one strategy, but Rx does something different. Since observables tend to produce values as and when they want to, the `IObservable` returned by `Observable.SelectMany` just produces a value each time any of the individual per-`TSource` `IObservable`s produced by the function produces a value. (It performs some synchronization to ensure that it follows Rx's rules for calls into `IObserver`, so if one of these observables produces a value while a call to the subscriber's `OnNext` is in progress, it will wait for that to return before pushing the next value. But other than that, it just pushes all values straight through.) So the source values are essentially interleaved here, instead of being concatenated. But the broader principle—that the result is a container with every value produced by the callback for the individual inputs—applies. + +The mathematical definition of a monadic bind has the same essential shape, it just doesn't dictate a particular behaviour. So any monad will have a bind operation that takes two inputs: an instance of the monadic type constructed for some input value type (`TSource`), and a function that takes a `TSource` as its input and produces an instance of the monadic type constructed for some output value type (`TResult`). When you invoke bind with these two inputs the result is an instance of the monadic type constructed for the output value type. We can't precisely represent this general idea in C#'s type system, but this sort of gives the broad flavour: + +```cs +// An impressionistic sketch of the general form of a monadic bind +public static M SelectMany ( + this M source, + Func> selector); +``` + +Substitute your chosen monadic type (`IObservable`, `IEnumerable`, `IQueryable`, or whatever) for `M`, and that tells you what bind should look like for that particular type. + +But it's not enough to provide the two functions, return and bind. Not only must they have the correct shape, they must also abide by the laws. + +### The monadic laws + +So a monad consists of a type constructor (e.g., `IObservable`) and two functions, `Return` and `SelectMany`. (From now on I'm just going to use these LINQy names.) But to qualify as a monad, these features must abide by three "laws" (given in a very compact form here, which I'll explain in the following sections): + +1. `Return` is a 'left-identity' for `SelectMany` +2. `Return` is a 'right-identity' for `SelectMany` +3. `SelectMany` should be, in effect, associative + +Let's look at each of these in a bit more detail + +#### Monadic law 1: `Return` is a 'left-identity' for `SelectMany` + +This law means that if you pass some value `x` into `Return` and then pass the result as one of the inputs to `SelectMany` where the other input is a function `SomeFunc`, then the result should be identical to just passing `x` directly into `SomeFunc`. For example: + +```cs +// Given a function like this: +// IObservable SomeFunc(int) +// then these two should be identical. +IObservable o1 = Observable.Return(42).SelectMany(SomeFunc); +IObservable o2 = SomeFunc(42); +``` + +Here's an informal way to understand this. `SelectMany` pushes every item in its input container through `SomeFunc`, and each such call produces a container of type `IObservable`, and it collects all these containers together into one big `IObservable` that contains items from all of the individual `IObservable` containers. But in this example, the input we provide to `SelectMany` contains just a single item, meaning that there's no collection work to be done. `SelectMany` is going to invoke our function just once with that one and only input, and that's going to produce just one output `IObservable`. `SelectMany` is obliged to return an `IObservable` that contains everything in the single `IObservable` it got from that single call to `SomeFunc`. There's no actual further processing for it to do in this case. Since there was only one call to `SomeFunc` it doesn't need to combine items from multiple containers in this case: that single output produced by the single call to `SomeFunc` contains everything that should be in the container that `SelectMany` is going to return. We can therefore just invoke `SomeFunc` directly with the single input item. + +It would be odd if `SelectMany` did anything else. If `o1` were different in some way, that would mean one of three things: + +* `o1` would contain items that aren't in `o2` (meaning it had somehow included items _not_ produced by `SomeFunc`) +* `o2` would contain items that aren't in `o1` (meaning that `SelectMany` had omitted some of the items produced by `SomeFunc`) +* `o1` and `o2` contain the same items but are different in some detectable sense specific to the monad type in use (e.g., the items came out in a different order) + +So this law essentially formalizes the idea that `SelectMany` shouldn't add or remove items, or fail to preserve characteristics that the monad in use would normally preserve such as ordering. (Note that in .NET LINQ providers, this doesn't generally require these to be exactly the same objects. They normally won't be. It just means that they must represent exactly the same thing. For example, in this case `o1` and `o2` are both `IEnumerable`, so it means they should each produce exactly the same sequence of `bool` values.) + +#### Monadic law 2: `Return` is a 'left-identity' for `SelectMany` + +This law means that if you pass `Return` as the function input to `SelectMany`, and then pass some value of the constructed monadic type in as the other argument, you should get that same value as the output. For example: + +```cs +// These two should be identical. +IObservable o1 = GetAnySource(); +IObservable o2 = o1.SelectMany(Observable.Return); +``` + +By using `Return` as the function for `SelectMany`, we are essentially asking to take every item in the input container and to wrap it in its very own container (`Return` wraps a single item) and then to flatten all of those containers back out into a single container. We are adding a layer of wrapping and then removing it again, so it makes sense that this should have no effect. + +#### Monadic law 3: `SelectMany` should be, in effect, associative + +Suppose we have two functions, `Tx1` and `Tx2`, each of a form suitable for passing as the argument to `SelectMany`. There are two ways we could apply these: + +```cs +// These two should be identical. +IObservable o1 = source.SelectMany(x => Tx1(x).SelectMany(Tx2)); +IObservable o2 = source.SelectMany(x => Tx1(x)).SelectMany(Tx2); +``` + +The difference here is just a slight change in the placements of the parentheses: all that changes is whether the call to `SelectMany` on the right-hand side is invoked inside the function passed to the other `SelectMany`, or it is invoked on the result of the other `SelectMany`. This next example adjusts the layout, and also replaces the lambda `x => Tx1(x)` with the exactly equivalent `Tx1`, which might make the difference in structure a bit easier to see: + +```cs +IObservable o1 = source + .SelectMany(x => Tx1(x).SelectMany(Tx2)); +IObservable o2 = source + .SelectMany(Tx1) + .SelectMany(Tx2); +``` + +The third law says that either of these should have the same effect. It shouldn't matter whether the second `SelectMany` call (for `Tx2`) happens "inside" or after the first `SelectMany` call. + +An informal way to think about this is that `SelectMany` effectively applies two operations: a transformation and an unwrap. The transformation is defined by whatever function you pass to `SelectMany`, but because that function returns the monad type (in LINQ terms it returns a container which may contain any number of items) `SelectMany` unwraps each container returned when it passes an item to the function, in order to collect all the items together into the single container it ultimately returns. When you nest this sort of operation, it doesn't matter which order that unwrapping occurs in. For example, consider these functions: + +```cs +IObservable Tx1(int i) => Observable.Range(1, i); +IObservable Tx2(int i) => Observable.Return(i.ToString()); +``` + +The first converts a number into a range of numbers of the same length. `1` becomes `[1]`, `3` becomes `[1,2,3]` and so on. Before we get to `SelectMany`, imagine what will happen if we use this with `Select` on an observable source that produces a range of numbers: + +```cs +IObservable input = Observable.Range(1, 3); // [1,2,3] +IObservable> expandTx1 = input.Select(Tx1); +``` + +We get a sequence of sequences. `expand2` is effectively this: + +``` +[ + [1], + [1,2], + [1,2,3], +] +``` + +If instead we had used `SelectMany`: + +```cs +IObservable expandTx1Collect = input.SelectMany(Tx1); +``` + +it would apply the same transformation, but then flatten the results back out into a single list: + +``` +[ + 1, + 1,2, + 1,2,3, +] +``` + +I've kept the line breaks to emphasize the connection between this and the preceding output, but I could just have written `[1,1,2,1,2,3]`. + +If we then want to apply the second transform, we could use `Select`: + +```cs +IObservable> expandTx1CollectExpandTx2 = expandTx1Collect + .SelectMany(Tx1) + .Select(Tx2); +``` + +This passes each number in `expandTx1Collect` to `Tx2`, which converts it into a sequence containing a single string: + +``` +[ + ["1"], + ["1"],["2"], + ["1"],["2"],["3"] +] +``` + +But if we use `SelectMany` on that final position too: + +```cs +IObservable expandTx1CollectExpandTx2Collect = expandTx1Collect + .SelectMany(Tx1) + .SelectMany(Tx2); +``` + +it flattens these back out into just the strings: + +``` +[ + "1", + "1","2", + "1","2","3" +] +``` + +The associative-like requirement says it shouldn't matter if we apply `Tx1` inside the function passed to the first `SelectMany` instead of applying it to the result of that first `SelectMany`. So instead of starting with this: + +```cs +IObservable> expandTx1 = input.Select(Tx1); +``` + +we might write this: + +```cs +IObservable>> expandTx1ExpandTx2 = + input.Select(x => Tx1(x).Select(Tx2)); +``` + +That's going to produce this: + +``` +[ + [["1"]], + [["1"],["2"]], + [["1"],["2"],["3"]] +] +``` + +If we change that to use `SelectMany` for the nested call: + +```cs +IObservable> expandTx1ExpandTx2Collect = + input.Select(x => Tx1(x).SelectMany(Tx2)); +``` + +That's going to flatten out the inner items (but we're still using `Select` on the outside, so we still get a list of lists) producing this: + +``` +[ + ["1"], + ["1","2"], + ["1","2","3"] +] +``` + +And then if we change that first `Select` to `SelectMany`: + +```cs +IObservable expandTx1ExpandTx2CollectCollect = + input.SelectMany(x => Tx1(x).SelectMany(Tx2)); +``` + +it will flatten that outer layer of lists, giving us: + +``` +[ + "1", + "1","2", + "1","2","3" +] +``` + +That's the same final result we got earlier, as the 3rd monad law requires. + +To summarize, the two processes here were: + +* expand and transform Tx1, flatten, expand and transform Tx2, flatten +* expand and transform Tx1, expand and transform Tx2, flatten, flatten + +Both of these apply both transforms, and flatten out the extra layers of containment added by these transforms, and so although the intermediate steps looked different, we ended up with the same result, because it doesn't matter whether you unwrap after each transform, or you perform both transforms before unwrapping. + +#### Why these laws matter + +These three laws directly reflect laws that hold true for composition of straightforward functions over numbers. If we have two functions, $f$, and $g$, we could write a new function $h$, defined as $g(f(x))$. This way of combining function is called _composition_, and is often written as $g \circ f$. If the identity function is called $id$, then the following statements are true: + +* $id \circ f$ is equivalent to just $f$ +* $f \circ id$ is equivalent to just $f$ +* $(f \circ g) \circ s$ is equivalent to $f \circ (g \circ s)$ + +These correspond directly to the three monad laws. Informally speaking, this reflects the fact that the monadic bind operation (`SelectMany`) has deep structurally similarity to function composition. This is why we can combine LINQ operators together freely. + +### Recreating other operators with `SelectMany` + +Remember that there are three mathematical concepts at the heart of LINQ: monads, anamorphisms and catamorphisms. So although the preceding discussion has focused on `SelectMany`, the significance is much wider because we can express other standard LINQ operators in terms of these primitives. For example, this shows how we could implement [`Where`](05_Filtering.md#where) using just `Return` and `SelectMany`: + +```cs +public static IObservable Where(this IObservable source, Func predicate) +{ + return source.SelectMany(item => + predicate(item) + ? Observable.Return(item) + : Observable.Empty()); +} +``` + +This implements `Select`: + +```cs +public static IObservable Select( + this IObservable source, Func f) +{ + return source.SelectMany(item => Observable.Return(f(item))); +} +``` + +Some operators require anamorphisms or catamorphisms, so let's look at those now. + +## Catamorphisms + +A catamorphism is essentially a generalization of any kind of processing that takes every item in a container into account. In practice in LINQ, this typically means processes that inspect all of the values, and produce a single value as a result, such as [Observable.Sum](07_Aggregation.md#sum). More generally, aggregation of any kind constitutes catamorphism. The mathematical definition of catamorphism is more general than this—it doesn't necessarily have to reduce things all the way down to a single value for example—but for the purposes of understanding LINQ, this container-oriented viewpoint is the most straightforward way to think about this construct. + +Catamorphisms are one of the fundamental building blocks of LINQ because you can't construct catamorphisms out of the other elements. But there are numerous LINQ operators that can be built out of LINQ's most elemental catamorphism, the [`Aggregate`](07_Aggregation.md#aggregate) operator. For example, here's one way to implement `Count` in terms of `Aggregate`: + +```cs +public static IObservable MyCount(this IObservable items) + => items.Aggregate(0, (total, _) => total + 1); +``` + +We could implement `Sum` thus: + +```cs +public static IObservable MySum(this IObservable items) + where T : INumber + => items.Aggregate(T.Zero, (total, x) => x + total); +``` + +This is more flexible than the similar sum example I showed in the [Aggregation chapter](07_Aggregation.md), because that worked only with an `IObservable`. Here I'm using the _generic math_ feature added in C# 11.0 and .NET 7.0 to enable `MySum` to work across any number-like type. But the basic principle of operation is the same. + +If you came here for the theory, it probably won't be enough for you just to see that the various aggregating operators are all special cases of `Aggregate`. What really is a catamorphism? One definition is as "the unique homomorphism from an initial algebra into some other algebra" but as is typical with category theory, that's one of those explanations that's easiest to understand if you already understand the concepts it's trying to describe. If you try to understand this description in terms of the school mathematics form of algebra, in which we write equations where some values are represented by letters, it's hard to make sense of this definition. That's because catamorphisms take a much more general view of what constitutes "algebra," meaning essentially some system by which expressions of some kind can be constructed and evaluated. + +To be more precise, Catamorphisms are described in relation to something called an F-algebra. That's a combination of three things: + +1. a Functor, _F_, that defines some sort of structure over some category _C_ +2. some object _A_ in the category _C_ +3. a morphism from _F A_ to _A_ that effectively evaluates the structure + +But that opens up more questions than it answers. So let's start with the obvious one: what's a Functor? From a LINQ perspective, it's essentially anything that implements `Select`. (Some programming systems call this `fmap`.) From our container-oriented viewpoint it's two things: 1) a type constructor that is container-like (e.g. something like `IEnumerable` or `IObservable`) and 2) some means of applying a function to everything in the container. So if you have a function that converts from `string` to `int`, a Functor lets you apply that to everything it contains in a single step. + +The combination of `IEnumerable` and its `Select` extension method is a Functor. You can use `Select` to convert an `IEnumerable` to an `IEnumerable`. `IObservable` and its `Select` form another Functor, and we can use these to get from an `IObservable` to an `IObservable`. What about that "over some category _C_" part? That alludes to the fact that the mathematical description of a Functor is rather broader. When developers use category theory, we generally stick to a category that represents types (as in programming language types like `int`) and functions. (Strictly speaking a Functor maps from one category to another, so in the most general case, a Functor maps objects and morphisms in some category _C_ into objects and morphisms in some category _D_. But for programming purposes, we are always using the category representing types, so for the Functors we use _C_ and _D_ will be the same thing. Strictly speaking this means we should be calling them Endofunctors, but nobody seems to bother. In practice we use the name for the more general form, Functor, and it's just taken as read that we mean an Endofunctor over the category of types and functions.) + +So, that's the Functor part. Let's move onto 2, "some object _A_ in the category _C_." Well _C_ is the Functor's category, and we just established that objects in that category are types, so _A_ here might be the `string` type. If our chosen Functor is the combination of `IObservable` and its `Select` method, then _F A_ would be `IObservable`. + +So what about the "morphisms" in 3? Again, for our purposes we're just using Endofunctors over types and functions, so in this context, morphisms are just functions. So we could recast the definition of an F-algebra in more familiar terms as: + +1. some container-like generic type such as `IObservable` +2. an item type `A` (e.g., `string`, or `int`) +3. a function that takes an `IObservable` and returns a value of type `A` (e.g. `Observable.Aggregate`) + +This is a good deal more specific. Category theory is typically concerned with capturing the most general truths about mathematical structures, and this reformulation throws that generality away. However, from the perspective of a programmer looking to lean on mathematical theory, this is fine. As long as what we're doing fits the F-algebra mould, all the general results that mathematicians have derived will apply to our more specialized application of the theory. + +Nonetheless, to give you an idea of the sorts of things the general concept of F-algebras can enable, it's possible for the Functor to be a type that represents expressions in a programming language, and you could create an F-algebra that evaluates those expressions. That's a similar idea to LINQ's `Aggregate`, in that it walks over the entire structure represented by the Functor (every element in a list if it's an `IEnumerable`; every subexpression if you're representing an expression) and reduces the whole thing to a single value, but instead of our Functor representing a sequence of things, it has a more complex structure: expressions in some programming language. + +So that's an F-algebra. And from a theory point of view, it's important that the third part doesn't necessarily have to reduce things. Theoretically, the types can be recursive, with the item type _A_ being _F A_. (This is important for inherently recursive structures such as expressions.) And there is typically a maximally general F-algebra in which the function (or morphism) in 3 only deals with the structure, and which doesn't actually perform any reduction at all. (E.g., given some expression syntax, you could imagine code that embodies all of the knowledge required to walk through every single subexpression of an expression, but which has no particular opinion on what processing to apply.) The idea of a catamorphism is that there are less other F-algebras available for the same Functor that are less general. + +For example, with `IObservable` the general purpose notion is that every item produced by some source can be processed by repeatedly applying some function of two arguments, one of which is a value of type `T` from the container, and the other of which is some sort of accumulator, representing all information aggregated so far. And this function would return the updated accumulator, ready to be passed into the function again along with the next `T`. And then there are more specific forms in which specific accumulation logic (e.g., summation, or determination of a maximum value) is applied. Technically, the catamorphism here is the connection from the general form to the more specialized form. But in practice it's common to refer to the specific specialized forms (such as [`Sum`](07_Aggregation.md#sum) or [`Average`](07_Aggregation.md#average)) as catamorphisms. + +### Remaining inside the container + +Although in general a catamorphism can strip off the container (e.g., `Sum` for `IEnumerable` produces an `int`), this isn't absolutely necessary, and with Rx most catamorphisms don't do this. As described in the threading and scheduling chapter's [Lock-ups](11_SchedulingAndThreading.md#lock-ups) section, blocking some thread while waiting for a result that will only occur once an `IObservable` has done something in particular (e.g., if you want to calculate the sum of items, you have to wait until you've seen all the items) is a recipe for deadlock in practice. + +For this reason, most of the catamorphisms perform some sort of reduction but continue to produce a result wrapped in an `IObservable`. + +## Anamorphisms + +Anamorphisms are, roughly speaking, the opposite of catamorphisms. While catamorphisms essentially collapse some sort of structure down to something simpler, an anamorphism expands some input into a more complex structure. For example, given some number (e.g., 5) we could imagine a mechanism for turning that into a sequence with the specified number of elements in it (e.g., [0,1,2,3,4]). + +In fact we don't have to imagine such a thing: that's what [`Observable.Range`](03_CreatingObservableSequences.md#observablerange) does. + +We could think of the monadic `Return` operation as a very simple anamorphism. Given some value of type `T`, [`Observable.Return`](03_CreatingObservableSequences.md#observablereturn) expands this into an `IObservable`. Anamorphisms are essentially the generalization of this sort of idea. + +The mathematical definition of an anamorphism is "the assignment of a coalgebra to its unique morphism to the final coalgebra of an endofunctor." This is the "dual" of the definition of a catamorphism, which from a category theory point of view essentially means that you reverse the direction of all of the morphisms. In our not-completely-general application of category theory, the morphisms in question here are the reduction of items to some output in a catamorphism, and so with an anamorphism this turns into the expansion of some value into the some instance of the container type (e.g., from an `int` to an `IObservable`). + +I'm not going to go into as much detail as with catamorphisms. Instead, I'm going to point out the key part at the heart of this: the most general F-algebra for a Functor embodies some understanding of the essential structure of the Functor, and catamorphisms make use of that to define various reductions. Similarly, the most general coalgebra for a Functor also embodies some understanding of the essential structure of the Functor and anamorphisms make use of that to define various expansions. + +[`Observable.Generate`](03_CreatingObservableSequences.md#observablegenerate) represents this most general capability: it has the capability to produce an `IObservable` but needs to be supplied with some specialized expansion function to generate any particular observable. + +## So much for theory + +Now we've reviewed the theoretical concepts behind LINQ, let's step back and look at how we use them. We have three kinds of operations: + +* Anamorphisms enter the sequence: `T1 --> IObservable` +* Bind modifies the sequence. `IObservable --> IObservable` +* Catamorphisms leave the sequence. Logically `IObservable --> T2`, but in practice typically `IObservable --> IObservable` where the output observable produces just a single value + +As an aside, bind and catamorphism were made famous by Google's [MapReduce](http://en.wikipedia.org/wiki/MapReduce) framework from Google. Here Google, refer to Bind and Catamorphism by names more commonly used in some functional languages, Map and Reduce. + +Most Rx operators are actually specializations of the higher order functional concepts. To give a few examples: + +- Anamorphisms: + - [`Generate`](03_CreatingObservableSequences.md#observablegenerate) + - [`Range`](03_CreatingObservableSequences.md#observablerange) + - [`Return`](03_CreatingObservableSequences.md#observablereturn) +- Bind: + - [`SelectMany`](06_Transformation.md#selectmany) + - [`Select`](06_Transformation.md#select) + - [`Where`](05_Filtering.md) +- Catamorphism: + - [`Aggregate`](07_Aggregation.md#aggregate) + - [`Sum`](07_Aggregation.md#sum) + - [`Min` and `Max`](07_Aggregation.md#min-and-max) + + +## Amb + +The `Amb` method was a new concept to me when I started using Rx. This function was first introduced by [John McCarthy](https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)), in his 1961 paper ['A Basis for a Mathematical Theory of Computation'](https://www.cambridge.org/core/journals/journal-of-symbolic-logic/article/abs/john-mccarthy-a-basis-for-a-mathematical-theory-of-computation-preliminary-report-proceedings-of-the-western-joint-computer-conference-papers-presented-at-the-joint-ireaieeacm-computer-conference-los-angeles-calif-may-911-1961-western-joint-computer-conference-1961-pp-225238-john-mccarthy-a-basis-for-a-mathematical-theory-of-computation-computer-programming-and-formal-systems-edited-by-p-braffort-and-d-hirschberg-studies-in-logic-and-the-foundations-of-mathematics-northholland-publishing-company-amsterdam1963-pp-3370/D1AD4E0CDB7FBE099B04BB4DAF24AFFA) in the Proceedings of the Western Joint Computer Conference. (A digital copy of this is hard to find, but a later version was published in [1963](http://www-formal.stanford.edu/jmc/basis1.pdf) in 'Computer Programming and Format Systems'.) It is an abbreviation of the word _Ambiguous_. Rx diverges slightly from normal .NET class library naming conventions here in using this abbreviation, partly because `amb` is the established name for this operator, but also as a tribute to McCarthy, whose work was an inspiration for the design of Rx. + +But what does `Amb` do? The basic idea of an [_ambiguous function_](http://www-formal.stanford.edu/jmc/basis1/node7.html) is that we are allowed to define multiple ways to produce a result, and that some or all of these might in practice prove unable to produce a result. Suppose we've defined some ambiguous function called `equivocate`, and perhaps that for some particular input value, all of `equivocate`'s component parts—all the different ways we gave it of calculating a result—are unable to process the value. (Maybe every one of them divides a number by the input. If we supply an input of `0`, then none of the components can produce a value for this input because they would all attempt to divide by 0.) In cases such as these where none of `equivocate`'s component parts is able to produce a result, `equivocate` itself is unable to produce a result. But suppose we supply some input where exactly one of its component parts is able to produce a result. In that case this result becomes the result of `equivocate` for that input. + +So in essence, we're supplying a bunch of different ways to process the input, and if exactly one of those is able to produce a result, we select that result. And if none of the ways of processing the input produces anything, then our ambiguous function also produces nothing. + +Where it gets slightly more weird (and where Rx departs from the original definition of `amb`) is when more than one of an ambiguous function's constituents produces a result. In McCarthy's theoretical formulation, the ambiguous function effectively produces all of the results as possible outputs. (This is technically known as _nondeterministic_ computation, although that name can be misleading: it makes it sound like the result will be unpredictable. But that's not what we mean by _nondeterministic_ when talking about computation. It is as though the computer evaluating the ambiguous function clones itself, producing a copy for each possible result, continuing to execute every single copy. You could imagine an multithreaded implementation of such a system, where every time an ambiguous function produces multiple possible results, we create that many new threads so as to be able to evaluate all possible outcomes. This is a reasonable mental model for nondeterministic computation, but it's not what actually happens with Rx's `Amb` operator.) In the kinds of theoretical work ambiguous functions were introduced for, the ambiguity often vanishes in the end. There may have been an enormous number of ways in which a computation could have proceeded, but they might all, finally, produce the same result. However, such theoretical concerns are taking us away from what Rx's `Amb` does, and how we might use it in practice. + +[Rx's `Amb`](09_CombiningSequences.md#amb) provides the behaviour described in the cases where either none of the inputs produces anything, or exactly one of them does. However, it makes no attempt to support non-deterministic computation, so its handling of the case where multiple constituents are able to produce value is oversimplified, but then McCarthy's `amb` was first and foremost an analytical construct, so any practical implementation of it is always going to fall short. + + +## Staying inside the monad + +It can be tempting to flip between programming styles when using Rx. For the parts where it's easy to see how Rx applies, then we will naturally use Rx. But when things get tricky, it might seem easiest to change tracks. It might seem like the easiest thing to do would be to `await` an observable, and then proceed with ordinary sequential code. Or maybe it might seem simplest to make callbacks passed to operators like `Select` or `Where` perform operations in addition to their main jobs—to have side effects that do useful things. + +Although this can sometimes work, switching between paradigms should be done with caution, as this is a common root cause for concurrency problems such as deadlock and scalability issues. The basic reason for this is that for as long as you remain within Rx's way of doing things, you will benefit from the basic soundness of the mathematical underpinnings. But for this to work, you need to use a functional style. Functions should process their inputs and deterministically produce outputs based on those inputs, and they should neither depend on external state nor change it. This can be a tall order, and it won't always be possible, but a lot of the theory falls apart if you break these rules. Composition doesn't work as reliably as it can. So using a functional style, and keeping your code within Rx's idiom will tend to improve reliability. + +## Issues with side effects + +Programs always have to have some side effects if they are to do anything useful—if the world is no different as a result of a program having run, then you may as well not have run it—so it can be useful to explore the issues with side effects, so that we can know how best to deal with them when they are necessary. So we will now discuss the consequences of introducing side effects when working with an observable sequence. A function is considered to have a side effect if, in addition to any return value, it has some other observable effect. Generally the 'observable effect' is a modification of state. This observable effect could be: + +* modification of a variable with a wider scope than the function (i.e. global, static or perhaps an argument) +* I/O such as a read from or modifying a file, sending or receiving network messages, or updating a display +* causing physical activity, such as when a vending machine dispenses an item, or directs a coin into its coin box + +Functional programming in general tries to avoid creating any side effects. Functions with side effects, especially those which modify state, require the programmer to understand more than just the inputs and outputs of the function. Fully understanding the function's operation could entail knowing the full history and context of the state being modified. This can greatly increase the complexity of a function, and making it harder to correctly understand and maintain. + +Side effects are not always intentional. An easy way to reduce accidental side effects is to reduce the surface area for change. Here are two simple action coders can take: reduce the visibility or scope of state and make what you can immutable. You can reduce the visibility of a variable by scoping it to a code block like a method (instead of a field or property). You can reduce visibility of class members by making them private or protected. By definition immutable data can't be modified so it can't exhibit side effects. These are sensible encapsulation rules that will dramatically improve the maintainability of your Rx code. + +To provide a simple example of a query that has a side effect, we will try to output the index and value of the elements that a subscription receives by updating a variable (closure). + +```cs +IObservable letters = Observable + .Range(0, 3) + .Select(i => (char)(i + 65)); + +int index = -1; +IObservable result = letters.Select( + c => + { + index++; + return c; + }); + +result.Subscribe( + c => Console.WriteLine("Received {0} at index {1}", c, index), + () => Console.WriteLine("completed")); +``` + +Output: + +``` +Received A at index 0 +Received B at index 1 +Received C at index 2 +completed +``` + +While this seems harmless enough, imagine if another person sees this code and understands it to be the pattern the team is using. They in turn adopt this style themselves. For the sake of the example, we will add a duplicate subscription to our previous example. + +```csharp +var letters = Observable.Range(0, 3) + .Select(i => (char)(i + 65)); + +var index = -1; +var result = letters.Select( + c => + { + index++; + return c; + }); + +result.Subscribe( + c => Console.WriteLine("Received {0} at index {1}", c, index), + () => Console.WriteLine("completed")); + +result.Subscribe( + c => Console.WriteLine("Also received {0} at index {1}", c, index), + () => Console.WriteLine("2nd completed")); +``` + +Output + +``` +Received A at index 0 +Received B at index 1 +Received C at index 2 +completed +Also received A at index 3 +Also received B at index 4 +Also received C at index 5 +2nd completed +``` + +Now the second person's output is clearly nonsense. They will be expecting index values to be 0, 1 and 2 but get 3, 4 and 5 instead. I have seen far more sinister versions of side effects in code bases. The nasty ones often modify state that is a Boolean value e.g. `hasValues`, `isStreaming` etc. + +In addition to creating potentially unpredictable results in existing software, programs that exhibit side effects are far more difficult to test and maintain. Future refactoring, enhancements or other maintenance on programs that exhibits side effects are far more likely to be brittle. This is especially so in asynchronous or concurrent software. + +## Composing data in a pipeline + +The preferred way of capturing state is as part of the information flowing through the pipeline of Rx operators making up your subscription. Ideally, we want each part of the pipeline to be independent and deterministic. That is, each function that makes up the pipeline should have its inputs and output as its only state. To correct our example we could enrich the data in the pipeline so that there is no shared state. This would be a great example where we could use the `Select` overload that exposes the index. + +```cs +IObservable source = Observable.Range(0, 3); +IObservable<(int Index, char Letter)> result = source.Select( + (idx, value) => (Index: idx, Letter: (char) (value + 65))); + +result.Subscribe( + x => Console.WriteLine($"Received {x.Letter} at index {x.Index}"), + () => Console.WriteLine("completed")); + +result.Subscribe( + x => Console.WriteLine($"Also received {x.Letter} at index {x.Index}"), + () => Console.WriteLine("2nd completed")); +``` + +Output: + +``` +Received A at index 0 +Received B at index 1 +Received C at index 2 +completed +Also received A at index 0 +Also received B at index 1 +Also received C at index 2 +2nd completed +``` + +Thinking outside of the box, we could also use other features like `Scan` to achieve similar results. Here is an example. + +```csharp +var result = source.Scan( + new + { + Index = -1, + Letter = new char() + }, + (acc, value) => new + { + Index = acc.Index + 1, + Letter = (char)(value + 65) + }); +``` + +The key here is to isolate the state, and reduce or remove any side effects like mutating state. diff --git a/content/GraphicsIntro/Ch02-Quiescent-Marbles-Input-And-Output.svg b/content/GraphicsIntro/Ch02-Quiescent-Marbles-Input-And-Output.svg new file mode 100644 index 0000000..a10ff94 --- /dev/null +++ b/content/GraphicsIntro/Ch02-Quiescent-Marbles-Input-And-Output.svg @@ -0,0 +1,83 @@ + + + + + + + + source + source.Quiescent(TimeSpan.FromSeconds(2), Scheduler.Default) + + + 1 + + + + 2 + + + + [1, 2] + + + + [3,4,5] + + + + [6] + + + + 3 + + + + 4 + + + + 5 + + + + 6 + + + + + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch02-Quiescent-Marbles-On-Offs.svg b/content/GraphicsIntro/Ch02-Quiescent-Marbles-On-Offs.svg new file mode 100644 index 0000000..c6db024 --- /dev/null +++ b/content/GraphicsIntro/Ch02-Quiescent-Marbles-On-Offs.svg @@ -0,0 +1,260 @@ + + + + + + + + + -1 + + + + outstanding + + + 0 + + + + 0 + + + + 0 + + + + 1 + + + + 1 + + + + 1 + + + + + + 2 + + + + 2 + + + + 1 + + + + 1 + + + + 2 + + + + 1 + + + source + onoffs (collected by select delta, which uses SelectMany) + from _ in srcfrom delta in Observable .Return(1, scheduler) .Concat(Observable.Return(-1, scheduler).Delay(minimumInactivityPeriod, scheduler)) + + + 1 + + + + 2 + + + + 3 + + + + 4 + + + + 5 + + + + 6 + + + + + + + + + 1 + + + + 1 + + + + + -1 + + + + + 1 + + + + -1 + + + + 1 + + + + + -1 + + + + + 1 + + + + 1 + + + + + -1 + + + + + 1 + + + + 1 + + + + + -1 + + + + + 1 + + + + -1 + + + + + -1 + + + + + 1 + + + + -1 + + + + 1 + + + + -1 + + + + -1 + + + + 1 + + + + -1 + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch02-Quiescent-Marbles-Outstanding-Value.svg b/content/GraphicsIntro/Ch02-Quiescent-Marbles-Outstanding-Value.svg new file mode 100644 index 0000000..95a33d3 --- /dev/null +++ b/content/GraphicsIntro/Ch02-Quiescent-Marbles-Outstanding-Value.svg @@ -0,0 +1,96 @@ + + + + + + + source + outstanding value + + + 1 + + + + 2 + + + + 3 + + + + 4 + + + + 5 + + + + 6 + + + + + + + + 0 + 1 + 2 + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch02-Quiescent-Marbles-Outstanding.svg b/content/GraphicsIntro/Ch02-Quiescent-Marbles-Outstanding.svg new file mode 100644 index 0000000..653247f --- /dev/null +++ b/content/GraphicsIntro/Ch02-Quiescent-Marbles-Outstanding.svg @@ -0,0 +1,131 @@ + + + + + + + + source + outstanding + + + 1 + + + + 0 + + + + 0 + + + + 0 + + + + 1 + + + + 1 + + + + 1 + + + + 2 + + + + 3 + + + + 4 + + + + 5 + + + + 6 + + + + + + + + 2 + + + + 2 + + + + 1 + + + + 1 + + + + 2 + + + + 1 + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch02-Quiescent-Marbles.ai b/content/GraphicsIntro/Ch02-Quiescent-Marbles.ai new file mode 100644 index 0000000..f9f4cf3 --- /dev/null +++ b/content/GraphicsIntro/Ch02-Quiescent-Marbles.ai @@ -0,0 +1,1734 @@ +%PDF-1.6 % +1 0 obj <>/OCGs[25 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + application/pdf + + + Ch02-Quiescent-Marbles + + + Adobe Illustrator 27.5 (Windows) + 2023-05-24T09:57:03+01:00 + 2023-05-24T09:57:03+01:00 + 2023-05-24T09:57:03+01:00 + + + + 120 + 256 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAB4AwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9B+cPLPlTXriyh18NSJJ WtmFzJbAUkgdv7t46mqLTwFfHFWL6FpX5faJ5jtrrTreSKW2hd47h7yaQmBx6TTvEzMrI3Xkxr1a lS1a/FF06/8AlGHicFHnV9LZRaefdJuNTezqsaosTs7P8apOSsTuhAAVyOoY070xGQE0nH2hCU+G juaBTz9LaV/y2wf8jU/rljnu/S2lf8tsH/I1P64qk2u+eNJ0ox/vIpUeSKEzGULEJZ24Rx81Enxs abe48chKdOHqNbHFLhok1e3csvPPulwNAqcT60ot6SyLExnIr6KCjc5AAajp7negOUNWTtKEaoE7 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 I0bkoPzoP8/Hrir/AP/Z + + + + proof:pdf + uuid:65E6390686CF11DBA6E2D887CEACB407 + xmp.did:14709273-42b8-4844-95f8-dd7dcca7f08e + uuid:27c6700f-edc2-4596-8c7e-6d5a89794448 + + uuid:d12d0cae-8e1f-654f-a69a-77158b6e30dd + xmp.did:142446db-8e24-4829-9ba0-84db788810a6 + uuid:65E6390686CF11DBA6E2D887CEACB407 + proof:pdf + + + + + saved + xmp.iid:14709273-42b8-4844-95f8-dd7dcca7f08e + 2023-05-24T06:57:15+01:00 + Adobe Illustrator 27.5 (Windows) + / + + + + Web + Document + AIRobin + 1 + False + False + + 504.807339 + 492.571400 + Pixels + + + + + Tahoma + Tahoma + Regular + Open Type + Version 7.01 + False + tahoma.ttf + + + Consolas + Consolas + Regular + Open Type + Version 7.00 + False + consola.ttf + + + + + + Cyan + Magenta + Yellow + Black + + + + + + Default Swatch Group + 0 + + + + White + RGB + PROCESS + 255 + 255 + 255 + + + Black + RGB + PROCESS + 0 + 0 + 0 + + + code keyword + PROCESS + 100.000000 + RGB + 44 + 50 + 200 + + + RGB Red + RGB + PROCESS + 255 + 0 + 0 + + + RGB Yellow + RGB + PROCESS + 255 + 255 + 0 + + + RGB Green + RGB + PROCESS + 0 + 255 + 0 + + + RGB Cyan + RGB + PROCESS + 0 + 255 + 255 + + + RGB Blue + RGB + PROCESS + 0 + 0 + 255 + + + RGB Magenta + RGB + PROCESS + 255 + 0 + 255 + + + R=193 G=39 B=45 + RGB + PROCESS + 193 + 39 + 45 + + + R=237 G=28 B=36 + RGB + PROCESS + 237 + 28 + 36 + + + R=241 G=90 B=36 + RGB + PROCESS + 241 + 90 + 36 + + + R=247 G=147 B=30 + RGB + PROCESS + 247 + 147 + 30 + + + R=251 G=176 B=59 + RGB + PROCESS + 251 + 176 + 59 + + + R=252 G=238 B=33 + RGB + PROCESS + 252 + 238 + 33 + + + R=217 G=224 B=33 + RGB + PROCESS + 217 + 224 + 33 + + + R=140 G=198 B=63 + RGB + PROCESS + 140 + 198 + 63 + + + R=57 G=181 B=74 + RGB + PROCESS + 57 + 181 + 74 + + + R=0 G=146 B=69 + RGB + PROCESS + 0 + 146 + 69 + + + R=0 G=104 B=55 + RGB + PROCESS + 0 + 104 + 55 + + + R=34 G=181 B=115 + RGB + PROCESS + 34 + 181 + 115 + + + R=0 G=169 B=157 + RGB + PROCESS + 0 + 169 + 157 + + + R=41 G=171 B=226 + RGB + PROCESS + 41 + 171 + 226 + + + R=0 G=113 B=188 + RGB + PROCESS + 0 + 113 + 188 + + + R=46 G=49 B=146 + RGB + PROCESS + 46 + 49 + 146 + + + R=27 G=20 B=100 + RGB + PROCESS + 27 + 20 + 100 + + + R=102 G=45 B=145 + RGB + PROCESS + 102 + 45 + 145 + + + R=147 G=39 B=143 + RGB + PROCESS + 147 + 39 + 143 + + + R=158 G=0 B=93 + RGB + PROCESS + 158 + 0 + 93 + + + R=212 G=20 B=90 + RGB + PROCESS + 212 + 20 + 90 + + + R=237 G=30 B=121 + RGB + PROCESS + 237 + 30 + 121 + + + R=199 G=178 B=153 + RGB + PROCESS + 199 + 178 + 153 + + + R=153 G=134 B=117 + RGB + PROCESS + 153 + 134 + 117 + + + R=115 G=99 B=87 + RGB + PROCESS + 115 + 99 + 87 + + + R=83 G=71 B=65 + RGB + PROCESS + 83 + 71 + 65 + + + R=198 G=156 B=109 + RGB + PROCESS + 198 + 156 + 109 + + + R=166 G=124 B=82 + RGB + PROCESS + 166 + 124 + 82 + + + R=140 G=98 B=57 + RGB + PROCESS + 140 + 98 + 57 + + + R=117 G=76 B=36 + RGB + PROCESS + 117 + 76 + 36 + + + R=96 G=56 B=19 + RGB + PROCESS + 96 + 56 + 19 + + + R=66 G=33 B=11 + RGB + PROCESS + 66 + 33 + 11 + + + + + + Grays + 1 + + + + R=0 G=0 B=0 + RGB + PROCESS + 0 + 0 + 0 + + + R=26 G=26 B=26 + RGB + PROCESS + 26 + 26 + 26 + + + R=51 G=51 B=51 + RGB + PROCESS + 51 + 51 + 51 + + + R=77 G=77 B=77 + RGB + PROCESS + 77 + 77 + 77 + + + R=102 G=102 B=102 + RGB + PROCESS + 102 + 102 + 102 + + + R=128 G=128 B=128 + RGB + PROCESS + 128 + 128 + 128 + + + R=153 G=153 B=153 + RGB + PROCESS + 153 + 153 + 153 + + + R=179 G=179 B=179 + RGB + PROCESS + 179 + 179 + 179 + + + R=204 G=204 B=204 + RGB + PROCESS + 204 + 204 + 204 + + + R=230 G=230 B=230 + RGB + PROCESS + 230 + 230 + 230 + + + R=242 G=242 B=242 + RGB + PROCESS + 242 + 242 + 242 + + + + + + Web Color Group + 1 + + + + R=63 G=169 B=245 + RGB + PROCESS + 63 + 169 + 245 + + + R=122 G=201 B=67 + RGB + PROCESS + 122 + 201 + 67 + + + R=255 G=147 B=30 + RGB + PROCESS + 255 + 147 + 30 + + + R=255 G=29 B=37 + RGB + PROCESS + 255 + 29 + 37 + + + R=255 G=123 B=172 + RGB + PROCESS + 255 + 123 + 172 + + + R=189 G=204 B=212 + RGB + PROCESS + 189 + 204 + 212 + + + + + + + Adobe PDF library 17.00 + 21.0.0 + + + + + + + + + + + + + + + + + + + + + + + + + +endstream endobj 3 0 obj <> endobj 5 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]/Properties<>>>/TrimBox[0.0 0.0 504.807 168.651]/Type/Page/PieceInfo<>>> endobj 23 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]/Properties<>>>/TrimBox[0.0 0.0 504.807 168.651]/Type/Page/PieceInfo<>>> endobj 24 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]/Properties<>>>/TrimBox[0.0 0.0 504.807 205.477]/Type/Page/PieceInfo<>>> endobj 27 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]/Properties<>>>/TrimBox[0.0 0.0 504.807 492.571]/Type/Page/PieceInfo<>>> endobj 35 0 obj <>stream +HW]O+}ЏFZ7]կˮDl,D`6>53V"{Sӟ3ǧNΝ9;71gY>u9@!2'1Y>~RD3q#oe! ,d&/yJ¨RNGS>8$)z}X2EYy_}l"fN`~Q]J¨2N|=7QkZayB띬RMh1(_@Hd 8#Z.AqH"ƚDό{Fa<D,7Qժ&jQ\9d|;u1Fq 1fZ_zEՃbZ;_gՕGVu# at|ݼ5ަFr&9ꛛbB\$[Mmi E6>xC1YoY>{\.f1vvcY= +l/a|\|+C!W\yLr:1K\)}5_x H/O.zaf88<2b`W&mem槯_Z4Wxvcqq1nrbvt1^|=;)w.,.gO>}ş揷W>bfN$jP}ܖJwU p6wlF14"ak̒[j+[ocR\9$ W&H]z3Z [Cɺ)m"P=⨳fTNG7}fj+Vt8ꤧ$IOUdUd]/*>.`z<7!!XTfMb}S`p!u]%"F-u + ȚsI! o+/FZG yYvyKQU D\=CZ/m)bH+ y;D}5iE-V1Z9ú:º:ìDC<Iυ8P^RK33X\E/\@Y<%2$ոg-jqԵfTjǺ:#º:ìDCaYN!H2zgC$WO⨪ţlY0ʅpO6gg,jqԍYKqdԹPcnr?l`mDI>V>Тw\\E/\H>r>q~ill+qY;ޅTJv f'ǵTkAñ @FĀtAwՕkeWceK!wlg${&u8c(?<ʪ@`~19KrE8b;ͳC"f}WjqY,Gz;/Q_z]-: ,/gf?iQ' +endstream endobj 8 0 obj <> endobj 9 0 obj <> endobj 10 0 obj <>stream +%!PS-Adobe-3.0 +%%Creator: Adobe Illustrator(R) 24.0 +%%AI8_CreatorVersion: 27.5.0 +%%For: (Ian Griffiths) () +%%Title: (Ch02-Quiescent-Marbles.ai) +%%CreationDate: 5/24/2023 9:57 AM +%%Canvassize: 16383 +%%BoundingBox: 11 -1083 501 -15 +%%HiResBoundingBox: 11.3603515625 -1082.88091908026 500.9033203125 -15.64892578125 +%%DocumentProcessColors: Cyan Magenta Yellow Black +%AI5_FileFormat 14.0 +%AI12_BuildNumber: 695 +%AI3_ColorUsage: Color +%AI7_ImageSettings: 0 +%%RGBProcessColor: 0 0 0 ([Registration]) +%%+ 0.172549024224281 0.196078434586525 0.7843137383461 (code keyword) +%AI3_Cropmarks: 0 -1111.50717975022 504.807339449542 -618.935779816515 +%AI3_TemplateBox: 683.5 -384.5 683.5 -384.5 +%AI3_TileBox: -168.466320512286 -1162.82148588688 673.393659959146 -567.801527390788 +%AI3_DocumentPreview: None +%AI5_ArtSize: 14400 14400 +%AI5_RulerUnits: 6 +%AI24_LargeCanvasScale: 1 +%AI9_ColorModel: 1 +%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 +%AI5_TargetResolution: 800 +%AI5_NumLayers: 1 +%AI17_Begin_Content_if_version_gt:24 4 +%AI10_OpenToVie: -842.715596330274 101.34862385321 1.00925925925926 0 7713.57798165138 8341.26605504587 4318 2607 18 1 0 89 170 0 0 0 1 1 0 1 1 0 0 +%AI17_Alternate_Content +%AI9_OpenToView: -842.715596330274 101.34862385321 1.00925925925926 4318 2607 18 1 0 89 170 0 0 0 1 1 0 1 1 0 0 +%AI17_End_Versioned_Content +%AI5_OpenViewLayers: 7 +%AI17_Begin_Content_if_version_gt:24 4 +%AI17_Alternate_Content +%AI17_End_Versioned_Content +%%PageOrigin:283 -684 +%AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 +%AI9_Flatten: 1 +%AI12_CMSettings: 00.MS +%%EndComments + +endstream endobj 11 0 obj <>stream +%AI24_ZStandard_Data(/X / MkZp*\0wl73Gz"7]1irxSo h(DEbz$ΰHE=P$YAb/TJ]]"Y8w<=Vu`@4tɢFP$P$cz4P40-5-`qkl1ܸѝjtƳlxxx+\'^T^;_f7T43Rz6{'!UCEe{)RGh_}"L9@Z "7r(žFzx5Uu_EXBv-pcAp`CV$ qGne$dkٵrhJ6)ҪHѠr\@!shkaFetH2Nx"jj&BKCU*Z:J}׈]B !xC5=AXsp\5 / GpУgU*YHc=+=Βs1sBRPA?R)'khK-Sj0ɂH:;3*3р4(븃p8 ?}x8!ABa@D(GHް){(E[b!ͽr"tBHT$EZyxVǂH jU$_Eϊ CC8t0:X<Iz[j(R$D< ⫉E.I߬6vn_J@[NIDDDIKjIU?~ϾgYK봆ʓh4R(+#cʪ: U$"qa( \" ) ϼkHYKƭ"i8J*2p(}a Ed""Q$vS:Hr)U$E)d$#p<0u0[0_EERՎzЛoEYBԡ)3.kSiEb+"BX|HVKK@⩉P$ dWO\(/Hep)k*x=я]-CIPQ$l J,&r!,CӞkvt3<TH$АC << E"ϯc[nu"**B%n R^OH>٩l)Pɡrz捈j;ۡU JC)*{Mt-^}/EV4T͒6V*GfG[[պmTa,8J]wLЖ,TX`P$ZdXܣP$ƃh0 cq"Y2fA](zH]0zVz#MEB/O<W$H C,HP$BC8IH_EVV$*=+CU+=BNE0#U< +RVkpH~8a$;/P0Bκm*Tm*½Av ]8Q")0= bt:;<8=rBCD@>Yiy\$#GBZjzz](CЇB[k{HvelcXH~qwqiE-lq \Bb`, ⱀ, 1(1d(cg, F`<5ܨ5xh<H::X8x8 zQ{>X8bH!$!0 0P$P$eCP""9%P$Ç"Hȃ\ERԅH`4H"9S$x0}C>a{z@p4 "a8u#89ƣh4І6k\֨Ԁ0 X0X"QF2cfp, bb.na ?x/+:d2v]\[Z/-DІ.zjj%$$HF.rҲI A: <.&"!85qޝ]bkÞH Xf RR bZ /vkMxfH5b48vi)I)I"QY@0_,jH_R$xV.SA C q(^HV6`tZЂHIyfR$P$ʙ 0E"ADP(x4@X8'%D&$6 0  4,@CāD"<8"AD ,(&@h "20ЀA pₒĄFah@  +LD0а 4, X1Q""    $""!⁇ "%@Hᡁa!A`! 8@!b!2 h0" &8DXh>Na%F"8pH[.҅H$$=ZvN[T\{1 +6^%AsQET; noy,<;rǴK5B4Uw_14y\fM ̼k/^uj[m!RF~ݓuk4zSU?29C]]9tFIǷy?Yv\kޣe1ΧYMWvVq6N̆&ڛé[iߕ:JNp_K~=/yi̼WYj&'ϩwwݑ!ofSh)U HsuZDVU<&V֩PoKZMQіҟ.dʛfUc:jGyxG-y%!Ks7U:v'K)f^6e %/HVe-[ruT!Z]Ҕ$oj\̗v&Z]zߦV!]7XTcs))Kl"iP9ՙ6qZc۵ ~>?"xcDe?Y5KK5nTG;^=`sUh_M;IA*❶lU$)}5iJ;hn]dIՉ x|)P'knyY\f}nR.%vLjO"wuylÓ)׶Ÿ&הuS'~[Us.Mj^u0mS"lUթ2ʧz8.dFzջQ;h[Z>IxY6VVcʯ,5Oq'Mb7M[}es!.š;Q GzDע3Yx:&1 "ꦍn/5s5e=Z_<=㳮nJm:7ٗnF鍧IrY],'*$)yw!=o _wխVycS EiJgxKo#Lפ3*UkX}]M3Ћ{u+ܫf|ݴS/蓮:QZ<}s\Εw?峛nwՆ\EՌfJ*YnECTIu?j [9xϔ.||]ׇGץ_v#!Iud}q.Rnn> 4~ %u vsj#m5CTtZK;)_OR_fZLyC<2=RQ.Lw,{syZ钕9YhVNe$ӾsO9Veѯ1UA"ĒYw))~&ܽcMQ+ՄҤzR3ܺLHr2c#$}%w=k-AL>O6Y-JC vT\ʠdhOsWo'\KŧuW32{~UŷITNY[kkʪZNEpeIu?,|ATRͽGdO%i*y<=ieUNIHyKIuzR=k,5bk3biZgW.7u.Fܩ-hjyؙᛃ/fYr&!&]VWKvѼYllN>j9oRd֣NQ$XU=z|iG#C*T,)*zN Z 9Hɰ~Ct\cX"00/FM]%)6~x#tG#\ūk'AMz~}Ot'qw+o\|2Z&M1yDžvxM2}u+iI72릷S YYޞZ&Z*ݜR=HGylnAl ^M:[MM2|r iRqTؖ"\B_Ζ5Oje85^qr7o5k/Cgm)Jl5͗=8eEMT542b'n-/Vy%>ZK43C^^|gSTIh5s,/ZgdRM7~&=l ˧.ʚd(\rU5ZY\;G6.J;nR vweQƮ"5!d;g{:#):+޽6w!(+kҦnRݵD,;.]jZ{tFr^Uli*muWbcVV7f-:7W\oy2SZ>pGSghwwXI!'*ixm.T22`qnw.=V^R PG]y/՚tUã҅;FL̀oYU;3D?ѴW{{uWjE[ZzxkSUˏp{i5x*D;W+ hOzZӬp56tDy&S}2V%:ceUsOgiɷj-~v4\[-jn=Ѧ_PK\._h-Ob9B\r^ɼLJL=PtdCA}.jaMfsiLk|5L#̴SJOȃW&R$uSOiOg,ZMriw/sڳEGGV\cl¼#]ҩnKn.lTdXhb$#Z۳ZhKds+OMEyZ9:)a]k^g03~ʙ>Hfk4W]e9TӭáũCZ";ZCҵΝ%T⥿$oV7sjMqsri9DeqU)m ʿ; }Z]-%RZz.\453,FYyM+z31w}E4Wb9qnƊZ{Tz.5ӆc(wɦ8ihǺC[{Ͳb{hi&έZmKcXIrON4-*E?Oߩ]UL弖hwFcK+}>;}?=>zR"21ڍg][;$׎4o\r}|U[νy»Ά7̳Ќ.MGjsWz_]y hv4SYʺi2BNO:;~%UnZ^کݙcufi[v>,)7W_.{ͺ-]uΨqndmODڱCvdB-G)-YN+uk6zЦ4C#4줯=fXV;ivWfɲ3;;&mU{}c-y\Ɇ2?ҝ*үEs=MU%ReЋLv/5~NW.+i%-*TS\:$u6+ՕjZw-w|7zL4˦Y*}AK]bݸ:8<{wnjlKkt_tR})yw]-(lun_tN2yvYOk9ͧvO\J>=r)KU}Tx  0a`DP"<4DLXP$0  P-kVVZjݏ&U~vV-LƪnV2Xixi +_)D[9nVR {B!R-mՙTC$e̼'i1zxiKMnb 5Z!yuіu"-]}]nQmFFyxW%D[Rocx{HXV"Mwg.eUSiTK,M,u 1QJ=6 +]5o3+Ûͭm˶C3\hn}[:Y.gƯn2 HJ{lwJR4ӡ*_S4jjL8*Ki#=Qu~xbW˦?;RPͫܭ*M +-^wzUoUt4YRD+2UanKz,HL7IhXJHX5j7T.ohWow; U:T6]RSu,}w\ӸjϊVrh*vN$M^x/ιt\Q֍%jGV*wq/1Ӿg7eU9o)J8V5mc%s bIoJh.x7,OU;"*M<}x%m%ţJ&HiHMEs嚫YVZC.^\gӬ/iilc?!gʢ'Vx/ݠp͜SW~;ʱM᪭zxkՅ/-,/EyS1\g,v7V=<Лxgxʭ+T+Yj*ghѭHqCe¥)ӊ_9[,f#n%as5յW[4UCK,%,\K,]Zx(U^ΦtOORSШUYm~V-! ?5HǴ;fVj3Kq2K]EH4{\x軺p,~̮:F^3Z>zsʇć\7~?@8TRݐsW~b7$e JSbxT| |8' L*L$ JX=Z[z :|MYK|SqDE@MZͯƷp.ypʒ5%-plQ@aÆumS"uS-~K3q'f#' s[9TVc`7c%‡;jb9gOss͹=Cl6{ ;9G?nFOE.3lkM$>Kf\|2#hVuj*XbV9ckįH*?B Y?i"=g<̈CK!U~@\!ςc&z E͚E49S>0R6"cXę9@P>?2$XlTLQ7*>+ AwLRĵL_4MdPTK=:D;hW9wVlRS)q=X^No#'=>1TbV +ʤ66{HZoH3.΅Ҳeaz 1 K"8^Vp*!ݻө /Er.'TrLVZh?LC6ܲ5UG"faIQϻm"lNH>@gJt)`H8A'bd-@uGBVH +8@:Kuw|4Xuӷ&=n/Ze{,(nzgږD8JwK9aO۳r=55ئTT$<9 C&A`$Y],?7W4f$_>( è*9yN? +~#l[uX# HD&Z9f 7BA319 7 +kwU=0j3O@ĂѱU "f})b -IoPPx^A!py[t=Uj *Z~aL& /r+Ę;ȡ`=&M{.¿4* deS̈́I,ߥ_{OIߦ0qŅϖYpm[*0XjSS;+׎_D5|fUцE4S0צ,.[JB8D4Wwݹ`uw>m|Lh|0.}icҵZ}h(Gdj [ ?:|)iE8X`zq+/Rk2!UA8ŶwL}۠i~x=8w#4 _n $&uJAB; ¾~[㥄$ s2 +mgztg.O!Ӽ7yiөݾczeH9x?۾tH(g=oe&( -FXKiA9:b'Yd`Q _~V GUCTwĄ$qn7KbAC zx VlԺ z m2-EԍY7$^sjPX ?WCޢ6 +Ǣ1JyY =jj44NLHU< LгnC?>K * ,_Z'ֲaE@N:~2`S9Jݏ]&|sISh_ X/xHM[YN)qCI+),A-vuD1tkq "OP`>:˳v>uaZ"tN-v;\>I_!k{JSoj%}yCG-]tOvL=><׍`] w2|ܭ&̍/+ 4P>e \pJy xX+o߾ ,G` l+=Evݵa&]HY*/ʓYMӕsD_tR2H"#"@Y-{ eG e1*N {{w+9)] Tjxz~#F%!y=XITy9%dFr.uQ_` R;&Y(IO+u>5ܖ)e >;K)yo +wfB 3IP, P-\RS`6j !\w) mbAQcW=TF +!Y* %;ʐll'\FywEV${RNdptt;tK٢7@'! 'u* 11gq%={9QuVE}yN  +`0fS--zq:mC_ +p;5\ˌX/!( c.6͚sOLz owbNny(Ӂ3? 5p 6BN Nj[Ecf gLr'/IN!`ޮb6VvzS+6 +l$#Oba掃uS!ݜ,(wWN\Q摬fpټ 6qRW/'@r@;vK,JU <31 +B(6擃393z- 6-lIbp C4(54wiHo}/ b.w z8,{ il|D~1lD( + 7+1@DiAǗZyU%2Y&fbaM]:<>)Pjo.{+~Dbx LaZ28jŒ'wYTUۢWM®g6עTaK8aD$o,t#QE Y%3HӾb窉+"Z|A0eK38O5薩п`0a 6b'ai0F?fp"z Qhib$ƆItňʂQ]iTreN8A6J{ Qf$ +JqLPpQwbPO_o9 Wb}Ja8r-]=;z4 h6&K àSU6xoŕ\|ۣ%u0;_GnyM0ɁaWuBVe6qF9HuS$ umQd|Z?I{HU%e.?|G!Y:ZEY^VR9jgB:בq?wE6)곕[U~UđJQ&oی-y-: ަG~@[9qHCn%oBRoUb0ثSb{C#jp3%}~ܚZ2s<\NrZFW׆;:{H3,e9 k >+y0UΔx4Nv\B P0㭱h74~hV }Rԕdh._.bP hQI YZ -=ȜquFYQp_Ú0dM` +iVv4in\)~&k=v.0smMO{xkDK)_w`ڕWBb5OfžNj$:vRBpڞd&M~ W%@@*8KK{YWgQw5j!_3ª2 xvͥ@%.kw_5h1- *m}(J?oxIKMUt6UɕmNogX.򹂋ҟ=Tm/CQntXfòRS_WXrz9잾/C3NR#9;7<]ZAbOn l|n&$* Lwj82!L9 410-۴ƂV/fVǶ_8%W*I 5C"ܤ31Lb犛,Ha?og@WS61Kỵ@%>$84 q+ų])$Jb9B5uOSb¦X;i0!~w$')qs px +kZh>Qt24+guv|!Σ X ̸Aت s!^OIC05ğdzbHH,9(1jKd-c&_(oIIWOVc >e42 43&QìWCojZ*PRwGU$QBuӵV\&.ЇJu +|":M`ƸjÚc,1qxK=gˬw2`? + J} ~'Rznh #OduA ('Η5tK +>ɵfg=v9OL1wFK)%khky Rߐ;a:1+wF)S>=|ɴ?CIYX)6Qt)SټǞ#[àqrDk>.@7dG~ZNO" PlŃtStvs?h^=7[.i6i +* qiVcvw Cͣ$Ä,hAxycOGN˸^P00. O3͵ᄣuJti0 +8 SS;@O& ‚č>aYڧ\y;-M +9JL my!x̕Z6 VۆY:k@Zb _l(z=$^-a%6)ʟ=gT"W0HGi(qt<ͦU`.xrP^|~5,=^D9)ܴmT#|U4ViX|Y瓒/G8W&VF!5lhse2`m׵oϑP鼵1€)2nU&.vGi  B'CJ Q`*…?24B(x!m<îQDUC='5JCTvp($s%cY0TeP` (DExW\~MśGњ-uP(l)*%d?r =;eSU{Wh:\S_6،yoń +NLFN^pC~/0|Y1vJmߗ;f]o3-ak8씩:Xv +~ +*Ir6IXE$*&,Ut#c*4a}u|{B2D/*|H';3oJgN8q/6QhsÀ + +Zy[sL(9O*fL "z$[X0۱A(o&^oj&⫑j}gOP#{y26;`+k,0͙98< +yǎM{2J`т I|'cyk5_>t/RBJ\ wU &Q#O] |V[vbޒue4 +B!R&(yOe HjPQ $to %) ~JPw+2 Cg^EX:$3,} YdtHP.,ïBz, + +*4قH!bBvEHxˎ\j?"'Ha .W"~0g %|~w)rJZ˽ޗWQJW7) %^}^ +"*2¨&MX_ESJ-#O.{c<븀81%y{4 +=kd[oN'goj wQh8S1r!;'Gfu))rqP"'NŅzTiNwۇ,(w6,F?BPS^R}@Aut PkdDQ*@VS$Tv6;B-nɜUUz?8 +P.m+Vӝ}KB[2`UH_F'wK}M<:n p; e5ba_ңRσ'x&5ҭeLS A>HȞ} 1нթQOlz:3ϘהfO&6C7M4vjeRtR ?a IJ؏ {l7Pپ=MOeYU@Lܨzepj'AhD-KX4 Ժ[?Cwl<|m'KGOC)Cy*M+!,FɪLԻnxoA"WqRChp d}Twj*'6(Fe""MP-wq"*Q7 {rT(_' ʄMM+{R_ Jвma;+^q#amykAl 3KӲ(8ܴ|Q v; m H%0y Ũ+64-PbRăBg""ps-x$7c_#w'-00eFv4>ב*(fY͕୛"Ad[*v&/R;4a*T M(`qd'rGOcqB* [&l} zE[;BYtM.ZTmdJnto)<@K)VަVT~eUp楪mޔX"uI!IJW0XҺ VNǴ4M*VVXaZT~/ε" ʦMyt77*qXSdo$O8!uŠ˨(H1[Ad2Kբy> k+a>ܡP%6-#T h=u@bYWWׂs*3,= @^ĕ0^U[^p$ͮDʒ 0x ̋ +5QE^ DUg' !)͠C@A*@j#9WeBv6hg8?|a\xjFJnʌG6$AFۆaPAD"!>`Ǣ,AoQt@;DYbD@pVEL<ĚV,l!JVTYlC( }NFC("C DB-۴Bd&9؄T&+I7-Ɏn;du|c)WSkaJf2-5=uPI;kw NvD+<T ^*,D0$D2 աvfT"p ,a/[|0lSAYfRĚ32d4cK_Ά<z0scN"Y1`Z ~.17ܧ %+ҟ9C(J$^(s1\ǫowB2o6@ +y7}Ym)p=i8e[^'ڦܥ۳6ƬEVh_`G& + xuH25Zm;u5/+OwY"6r9*'>jCΣ4I\Gہ;WT+ ?<)Iy(% Ȩ'{ rχpc<=q}n#J;nIɒଣ8z \f7+_lA֤ Mx~4?kP + &2\ +6{a]]QjGr+kTrDk$I%b6wBp]i.Xx]F*RTY`EL*ȢAٳ-~6Bz^R߃MmUPl$>| 53D@ /AD=p U|E?e&뭃6xcK\F#o2zK˸odkhkYYR|dҼ0o_UƸl=5d-+NUՓJ`!3픙cbW&`ۼ/uKc4E+Y,_5K*X{: +AZU;RU8EtSӚ e~eY c"0a轊UDžmnѦ+"uv˧]PgfQnQ)놑]Dwu8؆1l->[XW[cUgtӸ=EĈyCkz8P^Ȯ;Ls[{z=K|aN +zjwg +~o9їQ*rr '7Wy"߫ol.0c%dE +D'mdj^ 3ůfª|S +Ц®$Sl`腑0.p[j`|~xи'Zr%Ġ\Ls_⭴@<vL!^l4]:>;qE^pzx / Il7 ZKd㓁>"# Go둠Qºk ؔ& Mk&=|2E.]~-Q$<0Yy2_h ax;, 5䕽jc{_𨕙H›Dm`DT'rJ|'"FA)TWϫ]Ut*ԹwNM:ԝ>U0'ㅠxW:fS叏JX;X|<ݙ1)>I7(&=,˥_6BT-7^a$սE'TUWnNe #g-OcDDH3*p ?cΣ4~Dv_UoBf;1Bw:|aXzb OWSW'[kGOg+u`nvưxdLDžŒli}A.~+.P^ +ſ bhX!TGh3@.!#kO- zY@ ',$͈p5o&vʅM0> +FMc;./8 Am"iD7sl>R0*=s9]dPU!t_]65@05Z9~Ƨӏ'(6뀬mfCf؄2Hݑx[0/|A̴[׆ +Oa:v/jX!DQ;|%㾕},wl{SkH;1F 3cВ7vY"hu܆gFOn1b{ ݧL,%\uvBpsXM/ 6ؒn3ዧLX(G$XΪ"c@1 \"IsMiFCa܊ܮŝBgM{UsHAWU\~Dg"B5 G?LLT +cN%%cljmlFSйf)%'Y|8ɼq.ڗK$}0- ]\_,\/!Fw:o;lp:>1Ox!d},.3 |UpD,#*ʼo +='lK+ nX!ie}{']* +IIm +OwǬ;Q^p Ha,]+8KeNq%UPct"2[omM"ghd2rW Y|u%$O>87l 'b+3-RfsݣDQ]ߢ\9܊u(E -;hش ;zd.w_~-Z)QPtI#F3kw]q6T٪Icqa1/cUwC_JTK2?ݤҎKi'uT|P#uqe&w3lUCFH:K*gS#8ܤ 'xo^#z+V';f\*xМK-C33gZyTKmwDg昻iIa9fL!Y" jDpemd]r{65iL;ˊ$ZYيZ'F2f Yr๮ONH~g_牠mF[dhrj( 46 kA!2U44#*jwVUm+f3/+ٕ uPacRk`pZRErYiWdI @]ҨM+ .-(BY[aQa۪NI:'pgk uֵ-QٲsSj&]j%{QTґEÙXla- e(#;pqD$@=]"iY{y<$Hw$yx`;z^mqaf}ֆ'ĖCv[YO0$L"_U]&#K[%@qrvn5'.>"z枒r }5@F\يj\ ,v'+MOIK+JRfU"CsODZ03$ +JxLsMHN5JP[0p +UOFAtHi:&dKnhqo'LJEÿ:t]'ld͐'7 +qS_ ٝܧvUKhbKϙ}( i>:⩻Iu4[}i|;=7Weyh nFaA#\)NoAu?WT戄qXo!|;UFs5_ ^W;ymP77μ[Kv>X!77RslT3~oT;bvݵt":trCXfıVlCÛ  Eڟ*p^cW;T`RLl8NT 'ޖ9& /%S;1G$Tf]saj,e^#PgzE qW"VT]?Q%P$)TP9,F~O/0S׏;4ݰHM^"B3߀7a`wVZ5&,A6"n$ʝ7sVN[SkVbQqc. KK{'.dFf漸 ba(htr~l< +M}Ϛ~ha'a6G`[9LJJ?~-!L-h}՜/ͭBL)@ +FZ0\:Bk i=MJmݙG(bWVKGOL-AQFS`QF[X_oopXMX42nC?>>mE=A5=yN]Y|vP\2܆GW3~jԛl?B}F&s59qͼYKAQ=#"TMh.ԞL;mv7UviPz· qj/3撻Qme=ZnCF쀕8qIPVG֝ap5ŃKУ%zKt-7W-lp  HS,gFPS&|ϫ]tE.L.Ν )o>d.S5>|@͌AՅN !X9>/˭ܾSh4HLX%İ(ÎUL(0T^A{-̝@ ybON洩sMX<@$^ H/~y?_zEY"!ߢJX/KSbN! XUX@݃ŲxVXsX<ֿ +bb + qADrX`. 62Lh4o븀,M.UB0fK=>.[hOL~&@ t:Nߴ}~3K{;m*03}0Pu_YD +d<>6ai-}eBX;L[oٸ+h|D>㑄^m|EeXQG )8_0-"7(wB{> 7!9>C7F=i|ӵ3di_ BD6 tv'mABipɾred4 U{\?CpQMp@pJsceͬnHE-;8xC27H5 Y@J9!ƆZmp|=7yO˒cGڰvwQg`AQ=p_d8v^XƵ#?ZN 3`ys%"EҒ#fL@Qːm# y_ۨnz.5?'E[ g  ssYGTR5p'|CƗ#`мQ#$ra,Ģ#yvw(c̱# 6+Ro¬_"e L ri 9ƀUI/cCsxeMO{ZM9.5څE`N1@ f +0wvTb_̀`?cnRrRB_R0*/}D]>1?ferNDwߞoN113l=UCRZJQ63"R/.=њ'`(l29ku~EnnA^ F[ze9[J;؈= Wܒ y>I#oQ ܍qƞ_- [n>@:;*4 +;;~( M45g%7a2؛qI[_F0vl'BgնvA,&l1Q#LP/Qs.o ΧKRqS9J2I$ D{INo6!ɺr{DG#=ް#QRyRse@BӬPdo!+>Gu'(/@]]@n!\Ar['b.fC#MM6fG,Tu-쒚c? mQg`r^1 Xi9VU2'Br΅l)%.9(Opk0gk\Pk? wozQKj}S -8Fݸ3>\\8KnÑHk&pmV`X. WsVv)ַ|lo1aax›]I'`\ݓ&,=B8ũˮ+]oqp2m9>2$B:\h=s:;m$*HEEanJ/ꊶ_xD i7^Bд5߻ +#1/*ƸE|j\FR"/b`M ];yݡEs'; + h,[CIQ*̼".nr.X V7~%f`XGQVZIn A5S-#hMs-/jUd7NHһv{űHm3Q/#k*V3nHF j` X皣dpm鄁s.~aX5}V]C +~:-o%&tsvʯ+iR7)h @T]32o ]Hq؄V(4Z}@GDHeJ41e_Pma¸r9/jD+Tao㡇h+"HˈchUy%^^,tk>8- ̰7zPQFw dUJδT (y@~d_N"&53z_}1}2$~_Rю43ٮ,pͬOfk~m2I7\L@஥:rh9!J=}~T [hU{Lkya\n||؎iuj{9#kR#&Ǎ;Ж1g #l`j :#fP'|A%ʅp : Xa1=c@9 JP AM)VA҉1]5<L޳HGP2Üvf+ϥ5z]}쁽xY!{WN5cX$1v<ºAW-H$ +97[On,1o>WoC;cɤkMPj&X3qk{$IUnꕗoքu52^``^#ci +gڅ$/ 6d2/<4BCi4&c$$P!5nܷEr7-H9my%0'Ŧ'GhZXiw/->y5iۖ)eހi`VZ{ h(S}F1Y6ǰԤUߍ[l[ >`5HWW .Ў]D^>(})>JI r %;]*՚rHO<cߘ*]e!^cz +- r5~%Ջ&etjB x$ N%3n4z<:RrH,D1H-;cmm&?Bt4P2Y`,KCG ܯ>ľXIgM`/@V?U+{ȇLUp]WbTj zWR(Js+-aXX!iNcvs{) }+fU_-՞/c5'?ְh ) (8$(aژAl*ZQ g?\hJ%`[SUAF:?BVpIB=N v'qFJRI""gW/+Udh?!%Q%S5UPtrOc +3c]crb'Vx*wvΔOٌF$\U(^B2]Q$,²cW2lnC-HDD&Cތ!AؤoV!FΗ8/&,P\'*O ܼ= fa6ed)FFbjb;+Hȃ US:QhS@DB$-BUeNE,_pWp\26&x*j&8hnM6)ؘ׋jsnN +Č󽝇.P 5fyabon̽;2E#r3=bHb26nٱafgބeuBuyna=xs"hN5Ҕ>})3{f'!bָy̬Q+xEeP!#aJy1+5f8֜R&iaF#-_lȎꂿDM kU?Hd*6\};mvA2W)L NfzhfCTHWEq]Lr*n(XƳ59/tØP\3*2.ZقDyl&fahDZ*{^UG A14m~ryv= u!ST ]өn8k oBqs{^t:*͘CWQ\;ct,-fo^?~O|\  Qrm'j=Sz~,ʣlO0.ԜǓY3&$s&tkظ+dF7+V b6"d8s m6nqm: +cȋfnFZuK:LzIL mD' ⋈H"%ITNo q)3ܺjСƶreѯFyc;u=N*H2_5(Qj(ZrVފ9ᢒ %aG3OSԈZ"&#ިGj]yBAل5 39^̼]"hľZ樑l8Cgѽ9ѻolA!42U԰f +wm.W9%|$e,m}۲ƁYLZ'OmJHܱBH(Z gnmBL#V)-XBD?M| |w*߈aqcD!t*qJSb;틝Нe6QiDirOz*Otc] ;T@i/e/K(bxy"Fa +{FN5?]"/^*)$I?yHHb1S^2/"-Ɠ]mdN7Wapb&e4X!qj۷(EDPƤtJBjZCfa)~8CyvIhTxyTiO%3߮.a N&g1rJȀ4$%)m'{GЭhGJbc)Q?w 0}HME u4~ +1b7j +'A +|M-%*R./{3مZָͩ\Jy1J hW65XDx"Aj98=t9oҗN{?}۴5hVx1VIJZ҄94 ÓΕ1tf#H' EX dӗW݅fBFowu9^3Mr,"F9SZbePT0"ˀTThf +' +'Tex\nT#RP*@i$iFhčIa#hм!`hZ|Y<橎;1qIpH +sNř ƛakÊ@e-CG(>,rڌ~ +0ׄ;yͼ0}Ӈ;$ÿKZ;\?3hFz#^7Kw!^&eH&p c˜B:LԄ[ʲMжi%5ԁ4L 8&+Ŏ@A< Ha\r AZ*T3* y6LƜʁm ;ėה3 bv,*1,kح@L)JVUR4m:ԡ:1I &ga.S1RRca3,e j[um2 NeɱcN(K9-2^X[zTe%_Ih Lw_FOӂJ-?0 @A.u2xCwS!Jc: I-l6/g(R11MeІg>Q3C3.W#tBe fUϨ92E`eT`N JYhR. Jh+rٟQB3LEU2**C!!!:AY˔5AB$HH;Ad" +xPG(EH2 v,E# F㓘e*c:5# c)8@ F%T,.!߿2WmБVЭ[@jLx)Tc"DžȖAi/D*R^^bL8&zaL)T) c8Jd+C\u5.QGD +!)]&PJe`0' +AU  `#!#)ClO$P`+0%AjpR"e*@A+ gh՞hQ{ɃNk4,W߾f`2=.L$F0|b&÷ F@'+4NK8n~ ̛9JxAo&m$uk2F m]fjq<%n}\:a%[~t5@G85!U|vT&uy?'}P؇M k,8{Gm\uy#d.(D\3S<ƚՔF}=]|FpTx&m=Re) +ah(.D6HIr'\D"YҰON 9D,3l˭w +hfUI)l#+srRC!Va`~~3-AzpV{I{;kVy q CI*"s/So9$WIDŽhvٷjڹ6f/ xz&zY[=RkT^w՝)GS&#LJ&6ڛgY5u{W̯" ;d7uȝ)A[#pGrĶYԈ4YoutaZ,'a6i%#T_^vzdeGb,:4;qdKy8ɳՏ>[iT%Q|L0#/i$WIHi61e%2Oi +ytq +8y:ۛ4=~|Jz]X +a!e)׮% #~i::M[-XmScmVxؖ-iJdnt R )(Ldd<.[^@"/q:HU-z(R[;4zyV^iTOiF`960e':?77:Ӷd=P15i6/qq{6Vue{-y0BRޡ^WSOfuJ,` ]%[G!3h|(#\,S/`v'kz +󐚔FUp0A=arTJ" ?:M(qj'\C.m[IC0Jc6M?|i3<[_G..67Sk !j-R%9p4 ㈴*aQJd P ?d*ehZBKa8Y]D(@k-EbyeMvKNR3"UK9zY ͞fxSGځWl *Sx@Z/DŽ2@ؚALE[ဏ`o'Pň7b>ƝT +A(>l퉛95js2uy G tT;AJ1Έ6Wd߳nDE$6= h0)p߅Ć-_*CQY8ӓQcaْqw 1~nܡB˒M.)u[!:j*DպS +伶Cr$y-7lS̙])92Ibv;ms! gY-?S>) +v+!*K |> +݊r1kz24PEnuC%(Cjg +o[ҫh +>mP3!iX,Тŧ.r͉|Nz&8ekGk68˔mM@ː#ZC)g! &#FMm;=a|K:ވ2ැĀ%SqHqXA@"ޕL_Ak1/Fh)z dgH +^(<u93${_oRvdox 6%Fʇh؍yw $*2i(VV@^^5/8MСN-0p? # 6ls %\ҚXvͯ'luwޘCwh붠$[y wI~˜KSh .h*da{D$@D5ɰWnЖ׿̅nژZ5npꫠ0Q`ƸWǶن ]=Ł``{\x%VGl앏jRW(2If.EgUɜܓD +0,9;YL;,gP? j *G}IeOp0E$D|/,%ލb|Fw>9R/,+0 +-;O؊]KG2:'W0څz@V2X>Qɟ'§({# 5Y'Llc-,$"X t|){JcND5!xgy DdZxU$c]+q5[Mq:P}3}ȓ3 dǟ TjbOPY<ا@ x~])M$angrhP%.NK]xEs[^}}mz.A/G֖N^E/uP)Eaϳ!9̱s )_8X./}Є } +ԕ.G |zq_E(4qwhs6f̄a2Ҙ o Dm1Ճ2CDНHG0ƻkqb5#C~=݁L9@X5&io: טTIy$2 (rAkL @DZ8Fk3abI"&E(#L[*$E>oaH@FgDOFlGu`RGP L K?$4$`$n7$!/*uș\"$%FQB2)oW )o ,!K%_.!I-!`b eIAeX"%5+Z6qV{J@URNP% QI7 Dh}R"'s,N޸L +JZ@]O$IH= +F9zRsIbk$[R @q]j +1RmG%| +$>IDmI"Qub$\*z$xPJ4zfn0b D$yVR}X+[1"!ثHxDbWH %ća1aH؊c OMY@H=3Edy0ԃ<'-G*>"Z =bZkayl!-tJ*-;[H#b )U.Ș#.υ8bkhN{]p]lF9/xU./s^xF瑣Aw/ޟea+>/L5.v"")U0d#Tb$qF,00#EN`ËbE01q]T1^Z$۝Y$;cHE(3+bE Sd>EM2TK $BXEd[HN-C'"DF2Pi"^d LDF3+#DՠDH!jЫhh!" k D^ ?HiCbzH^!A u 6ąq3ސamxe4m`C eYX"Cqbtm~!úo^Fvļ֖@4)ݨ;)d|jЭ88LI#&8d!@BTGt|wAhnnxu䨛P 5B  aÚ悘19D }'H֏iK+D`C#':r |^ǚec,젿zq ܱ Y +J <@  *wAUPA%wk|lRK|< @pDpy@{Ã(ǎ!j0L}sA (A&c?2`^=;9! kd 7.gN{tAqzQ#Va^#삈\=@[)SABxR8Q "鑅!$σD_灋KTSW<(%7!<(A1۫Q<,F#ǃ{dyaVE C .k~ 2l5 F | )YX2c3 K"W M4lR>P#mF` & ҙژS+m0mp8\ < 0d( B-L6H FB0]czט<;k{ !ȎX)v5td'_AW'8Q$ 54OOix 5 +!,ab4 'pB#:"h҅j4§gh, 8}H!4" n?33;Ȟab3_;PH3ND Т$Dی5CjŠ!GR1KEzWff2-CQ2z FD^cVa)#L 3bd8L#WLkDBF-]$#J baǸ$1i71͏~SY 4F> bH@c) =$W DbN D1ڒD8b4uIyj㠙Du$20$HWPaXd1M`LJQ8FWQ Y ! +Jt wQ"8QPBPJ$ D=~уJ)B*_Q^8N%^(^o8q'$o^’EM%*& SEp<){Oo +|;%51IL1P` +E07A])pTJ(9):]($%vC,RQh6"QpQExQc}66 +^EQLE(zHPXBR2YF +(W)@OKPpfB?qۘ> x) dS8:a-m hթOYMa>OSp) CE`2FЬ^T2UDN&<'h-7Ĝ*˄dUѓL֍s)11A $DT[Eb/UZ"(mUd G^ {+wz(2rDx)!lAl(!{d!\XȁFeE{ae=#@.+.2bV3)BSVwZp!e@ )R$ŊNXǚ +VSP:'X)Iɍ#A2Hp%M$TB"1aE_`& HITew^EB!8*pD|ګFUK# Έg;qD'^"߳h*Bed*P3Zˉ}U^" $B"ȑ)"H6XE1BdJ~U7D4h5D Zz Q7QUe zU>b!*SbƫDFb+X?T@ThbOV +dZ"D܊ W&H zjJKWF_wEL + +I"az_7!IP2T$N `Ht9ghRaN-ղ{>q-` oAW(i˝ѶH<[ A"z=[-\ءE#8㢎y"ݡ a.là H:z,zq߿B;iwTi"Exv0.^rh&ۼ` }^੽l\bU:_l0{9X$* zx3_~0#π`@,IM#qP^0`G8`0 0{Tl ;o0HTo0<r3ynp݈7Ƚ%m0*fؠRpc1nц^C5 cܙ sC6a"y-5 1UWHY !ǑQE1dD6 +&SDTdZGFCRF@KeV$m 633 22sjfd v4#efV A07utf(23@KхQЇ$h.T T ihL &hHw,F#cG,4;F`p z1 ui|ۦӼYL^HQT/15^[H/.SNBK?=LC ]4Fe\Y8uHoټF=n4 ~mADZ @rBj;l: *e7l cAm7m|oUت6FwymƁgm A+Hy\93?ōR Yʍl'S*^^7g׍TQ]*H7F7\~O)(7jSoW +#rWZ5Kf0LD= +%8z((I8h r* (? 2`dqs8;_H:9?IaH`t#Qi6ŕc_BPrE"̄`ȄaB ʑ%Yt 2Z„-]p?Pe&Ad`I=0bh$<<"A0 9?c5g#Q#-9BѵsPx@g`$t"8-acٮ69LVg|:Q#q":ڰ3z #Yi uh@dԾ f] uPEpFi:2S)>@dnnSO}0H}`fCzڱbd}nduAz(cQ|43=|5?pUYB=0}}\Ue{@ȥ G@Xt**il +x ۃ[.РefmDQ9ؗep^PS"8 9$84 K=#8Aa @.FRy5{-CA6#nEp.xD"R6RB~U"c;}$m6\Y5l(-qQzekV-HbZ^9PKԧ_M8DBgDfttA4 M!c94`R +sx'R3 \&}?G< ;@ĭw$,>c3" V7Wh ֘$V{ce-j,rhB%CU g[]@r)ɤ޻ [* "sq09/)p\P9 Ẁ}{wPla ӂ$R@OEx`+,H^J7>mƂvNň@fh3f1CaofJPa\C¬U`3! + SVΦ.T /.YD'hq{)>r1F+axƌϑ6 &F<#Fz۲D? 5[´N˫acy)'ZDRO ޢW;UZ!'6v:F2 *X,+C&PWsLDޢ z{]i O{qjPO[/Ajh_門$,J UzK _ +8 /'B% h։p'w|bP ʉq{ H{ `#u#ʺY.0SipF"@lއTzR x irP;Ǯ1FuN[[?pفzgk|B8Tr$LxB vDŽ( cq#hhEfkр}]9gU&Dn ⩸Lq[G:5ʹݻhOEq tGD0 Dv_)ʀ nXH <|21o/bdL+a`7##tz 80Xt_@wyBA(L!0y'jj H(4m%J$icJ1% lvnxҮ́@UjchD9/e K$@H+NxR ZWh$N|l3b0PoAw3ģ0Bs榜WV0ˉeZu (?MH6@ < [0''Os[/B lU^n=;R:0~(GFM|# DYDdЭ 9e^^8KG!0qy 0U!.&MS,w5A\NfwJF`kX,:`vz"9sq+ tހ+8 pE V-?""A!@bYx 5xfhxc+ +BL/-?T/@bA5\zg'HSgHinsӽ.zT@PDSvZ +O\Uwo"M&ྌ%e s nl.JP- C +sx<4&04Zj V'YN% Q'xƬG]!x*[8@:ko-sjxb"A>h + 62@qxZ A6 ̺+ "VRN +P|4\ 匵'7rC) faų+99-p  E""PTW\8:PШ3Wh֌ӚUCq=ܸWگPب. EY4T_@z%pJ@`X 6?P#'F!`4J^\UfE +8ńe`c(,@|8mK + n)f^ + +o8?0)^OĊcp x6R^ջΏ:ε~1:|^>^"|FЕ +K-gD8xJ`@x /0~'F8fQK/*)şZbz6' z*(ʇroK]Xt=JE,~A92%ٻ#h?U<$ff3)l' +o ^SⓎVYsȓ3 +AC?jT|\?ʅIU3.u{EXnLmڪZ?<O !˯7l;l_(Y)mq~h|8d~`{)XƟO ZOJ9fW:&ßib#1ME.̈pׅCM$-ُ[\Q|s%3B}vyjUco+"I }U $F)F(hZVp qHf훙' i@Z~żpdc}^]H=/{ʽ\0x׿'f`Y6YP-/K!Qtq2'`;A[KE=Js[l{TV-!qRQ +z~_ն2 PK?K+;뇱^*ROkaeGįqU1w }w< +tsmoen!}̾S.<]sk Wro>P;sC=bP 0s\UHR6ǜ?ߤ[7bQ|ÒĮ Qv- +)5|3ODCoFt; O] \_69:P9`Da|ބlF;̠C1Х濭Glqhdj>2ܓW1#L?`i>nКd*4b|jl`hS`H$OXJ 62wU +^@ǵs77FA|'ow)<ڶE>R6 \Vʊ6z?HK8c:1K\&Z}>CJjVxi"- βyQ=2>dM_$& {MpV甲}L~3K4n5 Ѳ2Bԩ3o @dB`;:j:D:G]i.?{}{Z+(cf\3-FȭV'WMǝ){j?vKx.aRW2䏯xMyJ)Ո|w["SRaCy+ =حP ^\zyXF y CO_Ù%`+f[gzY7<}>j~7ҨOcFP!D K T*y: +;gsX⪨ cҳ.Z'3z0LcDZ>yO _~[R"$d@7 +b.䷪z`]ʡC߀KÄހD)oFj(9 +葹Ox"=O 8$44XqKC5rRd +V='NL&Ss>u]!xdnrl{£9MvI <46%׼{-=1O8cO )-(dM<~൅ޤ r 8Ŗw\1߬<7} Y xV^1.W퉁KoFyIHYZO$RGR'>U=)I+loümGbL{Z`?`|M}nW½dzaGq1!ᲁŷe#*Eu|mZ*.YuFG&#Cm&[:MdSR<^kA 'UA&Q1亗DuAowQ įp>jьxAIox"W|UTXS,9g ydDWFYTxH_ZngB̉J9xfE*_{|g*$Yr?ӇiV h `r ++W/a˙6J/!. ֿӸIio IiēLߙL>H~پ kB }'YԊBȃs6ܒݹ܉T(wke AUT?e\Q߃bz 0хX5i7yOPsKFyojndLp>Dz(2v_KP!3RF+st6}:\V3캗OUBpX[,oKft*ݹ*Ȩ|> `ܕz, +TOQ>lk=_I _=[Z½K[m7WhPpȷ ^tn<7kDwLmwl2腯]?vZ(2 5gQ{o%HӞX}r';'ex- n`8}:.cʉgkuHxv勉LUgbʛTS,UÓܛZ}k/{qm(ϳz+5V=RcQ*dfXe-"},h/ kv3zB[K +zgX `rtf\iMdUPr wϛ$`γlw!7ɏٶ3-+hLJDxV"c?JVO%sq =.z$3p1, JbK-;bKKUzJ:h]>OފNọ8GX Ia9+|ui +:΋&D=VvKODi[ +1 ^wXH x*&bw(B)WbutbG}$"s>8nx!axLcUi9\OA]sfoZ{j 'cqX0M')03G!t#hc%if.oJR(x_fxI(>O/\$'%L%ݩDe }A^i }Qz y^V99)z+_4 #D2pz!."G ?t %-eFQB$~x"_Uf]8ƜS tiLy*tʓ &ȫF]\%GZkJ,0uӗI*m%}':.bWE(Cb4@ (Эp|}ob@ K%TkR}Yl6NO&'@|07ɇfnNTbt6r7,M|Tw=c&$)-%Ӌ?C*}΄܂:-YYK9V7!̶=$3{:)@sAsWiL;񃯼Pu`1x.[3y+ÝWZLCΏc̙-1F& b:}D(93>:n#?c·8'oI!8oe̶!r30pz<D VTQͅKOUuid +=\ȑny [U3g3MӽsCakH+Ȼ"wK|@+QQ٥0/pkYg'UXdzw&V$1wGwEeŪup騩7Zn1&]3*r]oXjz VSsiPDj?XN$rO.Y(t>H!U(*u }Aeg"*OHq#[Iy)`3:ʃy/p*UɏQiP5|䪑~eܬu*:&L+.[G+(`n16O \,o?lq(r7A9cȝCKdwQ93cV*?9l`|+pٜ򾔯C". gf$BWχU~x]wHz~ܩ8.Q|M}j͏Gd"fo1?ޓ B9.М7vtZ45E~ط#?N>xE>\ΉC@^hŊN7 + y6`tT/%I' N+˥oiHU& +tξ ⷬ#'jXuN9+P#$>T-/ {Tqpa7\Du!&'p6s4txPrQKBAp7 ``@pi6gww]VǷluͤdN![אj7I_MӷUF  +U{wXξ"}ahЪ1oDΧзfmm хoj0>X VvE$;"GP{GIOao[ˎeS#;>[:Slaag,EuXy4|2Ȼ|{Gs`+(#ޡ~8њ +Qv}|ws+& RU%aw/cJҠfv?GQۓ?h7-vC2Tg>c _{M~sѺj}Uw8c,u; L^۹y*ͭxQ06,/,QhST^Rdԉ嶲!Ԥ[@H)6M$ i[\^[1j73tZqe'eĽuV 7Aܕ V[- r/H'?W6@cR~~5~7}FŠ#VIOaކ0 5Yt (Y?@ân;*BNۙimHXi"AAF*"0±myb\Qj;\IGmEI+[r{Ebl@lgnV$гƓN)Rh^zv^JU{{4e;d4<rP;(W Ct~yP;S@9'UrFc̟gmOTǾghڡ&*@{(vZ>p2 ƳQzv՘QtSPYdyYeAZKmn2l62Zt%gyR_Tko7leҤus'[.&r  , z> ;sH6coIpyhͲRyv㬖PUiY<3=ʚ۸YP= +Uco3lޟMCM =M+na:'J_,=jkԲ(S鰀MUhľ ΥS\:mh#x γf ߩ +endstream endobj 12 0 obj <>stream +0LGkXڤ;O*BTy*r^m._ \+#"FGbVE]$(361 @Mww:@f#N(/8Ӕ̷3Trk +G[;.!k Y:j-RiXYQnVAmgE9K<ʬw(eXG6Ӓ;lSd% vOpJҴD}Xk~b'<%xnwqVWJ +i)4:ǘ}'quNtx;QEj "mp r=44WmH_uLE$.6QZ:KJoc]~IVwMU;Ne<!{Ck15n/ U*yUmV*j9*V!GlJ=cJZ ߕ*$%ytWI[esq`-ži<>ŪXc}ݚufIO+iYS" +)jM@ՉG,j2a[˩[c'իsWbF>[;ؖk+Sέ#ڕPfBt&>gR?%M=˄[ +{,,b_/2]M:{u!UK +X\u0& 3`aڸ6Dt0Δl ڊ~S +}Æı*ڠǞZj K ȾT* y,]s~Uq+K7/sBSVeY&7 -zcIor99%>r$wy , T2W0U?(wĤ6S0QlLaerZ ]-;jCM*1N\fmɉRX+`+iv!\ٖ-϶\\xj+:5~;,v&F&sLxn n]d˟6ҙFI~o1< pb' |QpE2sqhN6Ξ!g(q!(17EdT"[4\ <__B{](vrOw.-_]#F=~a7eaD`;O[0_ܕj{wC). +xK5]^G鯎ɔeUe.FU "ݙHzԋf^2=\Ff ma! +F, !V|q8 |qIїOLWmt}!:_ho^1iD+q^~:OLjh KIk w@6s +X$^SE0 cN065xœq)Z@R..VpHw;e3Z}J_H' X`~;D,,Û ~3CO wsa*tXH\=<`q&$;"5l ^*;U]ZЉqDR 450Z&Z^pXC"z^ 8'Gh;2|gZ +Te-4JA3I8uo1yqK8*{dWmr^tH]tQݼޖz9{zA-qp2r|FfMYgc8_?JJO˺hpoVrbsibb- 9NNԔ 0dzYY9v2$?!l4nrl:(,oZr\NnR*LbR9ecJrR?a1<6V3 J+ǘ.o08 1%^1`2?,8jrPn@ +p(c(lōNe4#Q6{G61 ʶi~nc/2.sEiqqCWlc:/-m<#Mxւ!{O@û-5߫Cû{Suݤ@<$:i-^+"v= ')kq-i794xd0XqP6{yĢ0-mJ`8;/LL C +cQ*c%JXl>#MXtewXU7`< cw),$;&1ڝqXg1& + Z׺E^\,sv\Îq!_N̘PE+ŒAɤϥc<Ȩ1̸j'䍘q4FE7, +ȸV CC c̝D-B~D + +0;>/.文E%%-;c0VPS1p3^=d'_#6<^kjvPtw=_ǹ;X7 1@=N8N\E@#mLt +&(a1o F4Aj͢mhʭ%z8-{0ʻmO4d={<˵VU?QK^ \ioiFkb٨:ye#a9P54sGh>G!88txSv9]nW.GzO_v/lzw/N1dNU}~̲AR_L%/V-`~_(2vmŗ`6hneJ&xCWu *rc/5,ިE:zh&DCLƸ _|sCV/]J6ٸJ5ajt!F?^fgwv wϮ!f gPl^"4̌4=e#]-9R",bZ Vsx"3Q&ހngKv5 +~˰& ؉e3@?xff[nYЎ7*P/=)zG4S҇U?CA}  +A3Sнg[b 5)`% ͖`:&PcAJk'ÃoA P>l)Oк)";VJa0œv:_D1u6 2M{Т[wqfC!<}BG4b7LP\} z:Ag͚69'y4*i3ʪB 6&jBSE/tLAO3 N)Ȕ<R$& E ~p?(wvX-VnC_W*6t295mhow.".f`D?BB3F7t)(׬/7SVO)Z]gv.:HFYEkJߎz@k(}N~$1+M:$Q1eqz5Uc8zd{hOe'8͘ aJOd&>A݇s?La 'RK3Z_Qa7kv? ?T43:(t3fQSsiHS` X#d&k[!<+qE(19w[7$A:ݴ BOu]]GťC0h0B.!.!luِ¬kBtI^g . :F>Oh0m`H-fʬk>v7ƖXB&Bnu'imQIѦ#Hh]Zm pn/{w8PjaAu;Y' nق`Gen}^mQ'R)VmÑu/춌~ovp$bmAbf8-Qp!=}wi v + JsJu(;`>w`9NgTŇg GW3=3oá@Eߖхx%=}{N]$$G@{ɷKtʔ&ecmnegNw}) ͠SC'}ct o9p0xƷ//D7Im/_Uo|wmxX oGЭk.޾-Q8mtc&<ofx;3"mM^Q].PŧpZE nkl 9K'@$6<{pJ +? ,M5ˁn爟yn>t c_"mP͖nҬOo5߶gb!ڷ=% X3jm\~`m $٩]x8/+M ו_bF:e&$!6if[98 TbFA%oi&wqan*ju? Wf^"VfR5z)Ie6Qcq9m$/WJr۠ۯVٞM 0Zvmqa7a;aˌf;jvm~Ok?Lݺ(~g#V%xݶs耪/n[owLhQo۩ycdcNJqCZNɲ@F=$S0$wέf$2TNH~D6q1^{PX6`#HCƵ?;xwxT4wibv@ψd pnm;伻3Ԯ,v7#=39lgnd +Cnwv(QD)ׄNЈc󂜻աOM\SU>pL%ԣs䵁#|h-MB!e:Zك[ x6V`a<䁝Y +QFpck 7?bcc>JtndntF9ed߭Hwo wB +ޝ8_-xs+[vR`ζ6 -ɗ.-Gݳ]9wxy\J8EZ f9Y[eo%ߒ[@9t rg K9vU(^ț:JJ^CQ7[~ˀvX0 u]~;cT^2U 9 ڨ8fQ_jZ1nl! [Y/*lS{2ܢHi+PNĻN|yo{7"ϗA|ẟ%RTm%#PaXʩ: B䞕#X8Y*GxN 7=.KWcKQn }C*NB(aXk%uzuHLUedsBdP/ehs&ש2Zgg$1ݿ^3A_=D;9Gv,;9κB/qèt YI;6G(@"n{ZB!!{F#wQ>1$g1 iԱV٬i9E>2NJ d3ysaL^.(_HISG/$LRb!6ޜ>H9B}<ASzj[_u(vw Å#UT/IhH1}B;ftŲʼn=(:Ծo= E-0$!)P>g(782ޕfnU~ TAgC``#-NO7ce;kK /n/":t~(ʘc%y`_^U) =C hCP|VV(/'UkKA s)ʞn@0b&ޘm':9j0Ӊ~.ȥnUn OEeI3^-}4eUL*B18y>_;w/{SL%;/7*řhڿ/!߲JD@|~ß Si$rDȤ!v?mݯ?w"O_Ј0]GSS[/!yfF10jSKK} cZ+q2Û 0l3QdK8eXHԑHMӱۍ-IEmzRiBl5(a@rlZTԩe7}!9FaRCa?"P'ʐߍ*ؖEO +!q]@8AR,NYs ]rD+%2xRS79T:<؃@%P0'azyut0? h@\$c9^;O‹ ՋG'x*^~秤b[3$?ɑ-Ga{MC6f Eky;cUg>Jt٠V4ݠ?h8<u_ԡՓ_L{| +C bF\\r\b,"3o$=cv8UIa<\GA& h2U] +=mb- ]C[ +62@)F8VlVd *ҙ нI/pUm& PhkK!<69;_w #W1؂ ADf0m7cO`9S;5Ԇxi::z}B?nӼqF "6#ksBgT(DsMihtwajꭋL.PJtbF+8p5B::z[;*EGрCX`@ASvfAG 63 '1-gw T]XVՒÆ# 'D_{Sf} ]g1ꏹ~H<ʟBg}Ef;ڛۄk{)G;SXʮs Z2j*— eɚp}srh^ a6^X^Y?5c_:ݏXEGNI\L+EF ̾ػhN()]76SÔ&g+0}.;F*ED#H&3cBg}]W]g*.uL\gG@F "mw[Ʀ∔>OY^ВL4/-&M\~hNe `MASjtqh&?d *xn: +!sb Z1SJ) n״<7pkq+0GYqxE1Չ(:.ygnӴCq +RRt:n):d<:L;{iVߜ)[Pe ͉y'$Z 4/NᎠqA҇k#Jmy>&#*)ܒ ud#ߪ@. + 4< H ǧoTUNF" .ldur%n% #?bq^pOhΙ{!)%)9C TDE +"##yg5& ϚBgG^FwY{r]ָbY:lsP[ bZY8yQunM }VN A@u]ΒY/81Ye(6tk}F7d02F!u$Tb*?J+v6 ):*DG +=T"E 4OZh ȾdXT~%Û@z]OT-,u~ܗكkCcHm22~qĵNT>1uo;aijizL|sH drY'PUI_yyu>~ L@%$=R衍`9#BGTdwؑ>q[ydߤk +j¹n3Xm ~W,ַ8bC:A(ȯL+G)eQ>|U..{d8xi&lG*B~u@4z=ΩU;&@6 yCuw6R65;$yz|͡ܧ!EiUF~ ?u} lҮgSLvoC} +ڪ&͠d}5#>={*RDT+,h϶5rFPx>z]I{'1UmVY88}SPHec֪I_B68bwƅ;w{>o0^BވLï%ͅvb]:iU3zm&^su&/+3&HL8]V)L[sPj |cfd XD hT[;>g.34+O$yHâDdreIy~g*EL÷bGIl#}@Ev)Txe>}6Ox 0 !JA>z^qehö>mHߞJUsz9evG?swޟs7thUP6QfsXg2Jnۏ[m6f9|7`:Rإq.Pg#ׇ. ѧ` :WB~^A +G?F@bh7:+m:cߧyīe虿8D}KC"gFș5r2i5̞cF}A ֔;bODvJt + JH) KOTys m[84{#3#ПCfBM;|47n쫛RP$2̺(t]u%UĚkfM!J)@d0xir62L +q4xJߏsyj GDvJ:jdtBQ * +Ee?w{8K`U#Z N(y"NA K{h=8G_Hej޻6 mvwD$Mt9h$H=b?b a9gor6ً6Ȓ+e fykG4*8w?5I`'m +|d+h]$Fw w6Й0vݙp4CU/tG 0xf2 _Lk +0^y1&';z]FPKe@:oq^߀j@:0{m ~IgY32)R(V|'Q|hHd qvL,&8߻^Skz8aAG|<#@ih0_̶0zkܜ{gxOd',B~ k㼍ܯ6t/GraBu3{oFPNa +HO߷!5s}QgxN^'$J^;R߀뺌LbG +=IE절XńvDT7u'y8~?{GR3mu;q^jeEүۈ\ߺd1~kGp6=Dk L:R,< &p_B~(}":ХiwzT FG(VQg/ufܗ:Rwkt,Wkq֥Z@w|&1~mmuH>)Bgh?7ZpQ#}`huMᜯ!|4n]1F5 TL%R_C(cd7pZW"yq^쫑H^y} `>~HWD~jm PP X` NHK|Mᜏce:䟍CwZUB#OQ ԊH?{os^g8} }ǼDgi~Fou(yJos>*'Hi{7iU45s6픪dh@,=|Ϳ:;[ik;"v:ƽw,7^_Ca?37ҩO RXz_2y.jt$: hEc':v+PYg|xS- e;y{o %wj] ^Hz'EkM ݷ99޺nc#g$ļ?'Gġ/rftN Q +>|(#}=^64qr^}OdI=~ucq^ø6?؇2 {N89uO$2?ԇE*F&Dd_2?>'DozeQ!ҩqOI؋@ {ǺA Z 5±GpM_Y(>CXV7&'D+߄*:(y;x$}!ADh7">?0B;lm?~1>NT]Ix8$g<v1xg 8||fl-P@~f"5Qh)1{jtߏ2hKߏXG1|j2 \q4 }QnݚAfP{5rf0>: gY{4m^&00vha]hKA~ @:3͖ad]={pkD}^3sQ;=?< ?(Z"Muw6O,}ϣzsy}FPhD¸f0E;Om>N6s?/t w&|lEens|$N#cmq'j0^yqP={'Lc[~ro ^]sGٵs^θ0tlKWo)l3nNκl͍Se;]C6ء\a˶}m86 +-1tnL9*!݇;_3η8}û5r, O +~ClW8a<:'Y +L߄Nb]N q`?-H7R衽t*Z@k3I5><^jwbeJ`;Hr@>;|~7,?}wݓ{>%?- " < \bgs]Ҹc.i[[1m>Ma}6: ˲q^wokn[ߺ27eM&rmr4"&0yi,ܽͶ}-d.jVƏm_\4nĸ됍Z f &~i4ouopD纱&%SqsGQ eP*WJ`Q7t^h2!p,>'<mi<{F06sHV"=6ݯ)l:{F,U8y͡ܯ 1uj ֕c=4NMy G[rzoBne( +6LQ"M7k [E7 5CF4km*h5~s}1r< o(>4Lh Tq]n뺐_w, x>!}㍳eh3fB2m˙L,lÆnYvej.;u^vIvc*pOgR[u/q].Gsie6/k'$4^8%mK9\❌Coe>5 f:j6X9tpQɲs]~ѻ0zm04-K[\걬Mmdٳ>ș8:{wlq6z"KÞD4B/)q.@ǒL:|aobEIw#i̻qus6Ã`?@d7`rpZ +PH<We! <1 2q؁G5x06Vrhx%_$F!AdO0q($Cč<\%^ď8R!8xAfP?5F4 +g?܈p$.ė΄0 dj؂)Z$~b,栒J\X4=}DyJʤ3NU0d~@m4 `W 0CG3xIUQ*r P$>rHs'"Q E GM4s$RLI{: cdb79&H@N$@O$@,o 28 c Yf)(Agx9)WO eUb@c>bK. Ųh [53 "Qh-LP4Y`Q!mʕ:PVF(NV6c\I,Mԕikl&l[ +a1t\\bքQE2+N$yI:7nwnz6$bH"˙dZ`)[!W Y[K@%;2 I4,"sp K@%8,`< Q;SLiSdID*OI,[ øX-ZRHXn")HTdSēv\ke += +$S܃Ӓ2H1#'HRE؀J||#?l'7""]X(>M1=`|z{$2#EfDB` = D,*-!DJ.mmr7>=":+șZeؙ=uڸ'sKE!WV-@rNlxNN5xO!D&1r%0ދ[f{ƺr=IPr,I[ʉ;- +k9Ư?.v]!AJ9@R9CN7?RdaxĆe JrX.a eDLpTzf6 A 79rs0emicRJ#:Ur3(0 +NiRkrv#K +ъ2,8ʔdrI'jzʫӚ̀W&WV[Hc[< 5]PQ4Wb扪vr ˚iǏ!A.VR hDvP-="RRLH_ܕ2 q +zxq N31̸ѴPeAX+uz&l#1fXh桐F\5DX;`v6&a4Tm GT'H1%}[DRPq- {䖷J堔V@+͊ (FbFdYG,Ah{RzsM>| 79ӦaU\-XOjGe_gВZ9Ia%@do(lӃP%z+E:QG>9O:A퉂Jۨ)QJ#aF#a!AN?Y V6!6'ĺ]Q eE>)k2}P0s"s£\:)=H"SMUL4DZG"Npdj|P *Iqqyq!Uʅ~@eaיX6PsƔH&Yl!YnOO6SParw2 H0T ++j9/ol14Q]'GDA=]!.ȸ7Y쇮'F>5fJR%f2!y{$1].5u 't4!Ia786f`d״ +HzQ-;KwҐ C +$#h6UrGe)R_TIeY`Z^ QL%VԳ`HB;#\-u -fN>KqMNTNW2uT9agXhЀF`&c-s6PSF?8-%.s!o#PɌާZV(!Ai᜸ȰmMfN܍OXhDJA5SRf{DF/ 6%+ 0>ZG64*i*u :/z2s6K"܎JxBp |f[FqBG.b72PDt+P*&yݰ/_ 2}Bu@E̬ 7M'DD6&eF[bþ;q1o93$s1 ړ=.aT^>I!gwKdhm۶Ԩ!cܒMؚ>}F@)Bʛ"LP L4]QjXԌX?F9KbK8"pVSL-#ܤUB[+&!Kj‚J_:gJ5aeP\qaq# ,pGnhr-߁[+\/ni-.0Fep$Ѣ/H[@TKmdpl[<>+&@sS7afRC@663N*bcb;5IRޠ=̍rbWb̶uMEEmk1IRERQQIlAwȘg*,-DD`@  !f'0<6 ]Fe]7!值q&EwI UsZw%':Z +ۆRNI~U9“RyWkȐ侣F;5SF3:+>j*?j,9hlqOnt5(ݽb=Y^XPKDwD?Mak3HQ/(4)tH3!#иEVd_I!, |͈v(+V[䄍׃it@T!ѽyJftiAĘ EeC@6: Y )bv n1Yaw7ӦoVhۀsNw 1w0@A ,-]@B *]CVBj텄F^bJXWa +k DcJSr:kऩjT^TReGLgCV|2֌m&'o¢oi(%zs!j1Ǵ)(S}dtƠu|mOdx7$/~7I,Kz +BUځYҙ+'%c֪Xf>zgEQ3?cxBg$geLVm +NHk/*/.B@ $[kDVĄݑݹ$1l; kqA] atsXpt<+BhԌQxj ۴)pF@V`- YP2*WD:KP',* j'-sJv\T/uhD*GHT9*i Pي0[rlRǒiěthr+4)Z!Ϛ[DNP BD)>5P$k/' VXiو{^2«Qqq)* jxT<,s=E?[NL>ƒfFJ^ +_;k'(j7&0l:)9^߬ +'\ WPrWk[<>g.!2""2"s$r&4%~,ke+ĢZ>8/.'=A_'(@utC/9_PX r+)K=~IQ'0AD`t`t‚ZYq9Di_SPpdvEHgU /Hh \ò5QGŽϯ=E h3׮% +ƤG@# I+~%7t'E<<{*-E]Rѿhx{t6f3hbH>W0MDZ8\BW:8)"k! " I dobEb!6_J= ~ >ziGh :yѹC>A@:cĊ<(5*s)Ę&lu 3݂+.R3~k◊!YcȤxO!i- Nzj%%*XVE$Tq9Wᨤv|r +LHM%\ RXxO?I4jm +LpT`vOZ |59_ɸ!Kޮ%ୣhÓtNAr4#ؠSjr]rI?1:{1np NDŮ ĺh+~H 5<0 pdv)vԫwZE /PE6Ұّ'Dy+&="*^i؅( G;fl. <9Xo餭lXIߎed@7ưNc_̶1l\;83_[mp]&pmN7a9UŎ@O>F(SG?/vNm]FFX86$>~І +EgrR+({R*'"EؙTU3$}Q+,"3Ҫ*[h*_hJk褗NO03@1ٳppX10{ 5yzjhY88gމu4 ,TIa Dio j>UbOEKR($}ˏ23l8J`6ՊJģ__?4X;=Dg5#g|`A`MP mJŤãMI}Z;+W&U_BFHZaA`ܝhZxzPz nf,/{;,<یTjH7G|缠[68#CEՌˡzsIg[84ddv&_~re G"w/TIF0A?{q*x?Z3@(|Mhם\dpY3<%=>cS*=dYB6ΕÓ~ +؁2zH?h~S&.T)RQYO<4~$OMIW@" +DĢĺTzT^Jdw $lIꗰT/tg>mЦ m#b!0 gM9)\!F==՚ˇ'quO$?}I^\DHF' |~.l^2{!)Z;1HYb=x +PLeS?J_B1 'XF5btR#*pU ?+u$pW@"30!H$HfqN L`˅tnb/hp[9>(9b:a +ɿny&%֍>AI)U$zJ_mN/͠[\1yk2:0Ǎ3+b[e'UFSP_r U5E7#}$j轆vjxҏ#~E6O"',^5o0lɿTşãĺ4 w"_AI{/P<4|(=fG5qOكwwݛ:vIZ~s8 `| 2[hrJsFdɗTn"~B]<]%OD[ D0U-2U:Jd? +8ƻQ-GQi5;> +mY]gl,u(>fmc,]rVܩmsh<ǒ =7ZwZIAP7:o+)H[) `=k YPu `LP)k*$2ďDj\& HH2|n5鷻ewP_u'U7J?f.淬}y3n(/#[G|a,f:q϶jqDTI7^=sf1wk>C EeaJ+Jj@TfBMG^OI$n``MtO^{ mbn2 2xn5ms4: "tӪ1^a`}e\S7\7t2ҪaIXM7w04m6͠[GT*.Ҩ߀8\G]~L@Gw8= +JNg RZ#BwPDTr:UYF$CQ!ǫkGgI;*~Jlܟ_h6/V̔>wda'gEJөm4n +M^$}*$UobL, v GB4VZ ±Q05$T Ejo~'SF[hks(y%2X^?6~TE)4LDYhՂZaJ(rC}XfpV/0;%Nb?f|ChqO'Wܵԩ/qhLZMmOc-tץ!||i04mW0uj3Ŭ]jm hx,"T..k]Dm)~ fzeHU;27?~BVoaɿ`c-c/u6¸x Π! ns$L3Gu4q b\Vo91>F6}e?5PBԙ wj/m铫i<=i}A:;|}(WѸU/*O$qs +!F{x/~ejA? i̻af#{&/-r*r," 𰕂a9:%f[15 '|7O T<\r mL CG 3nrOe_Ǜ\3h7eyKaڇ,?` t98qvR> SX_rxhS.*/2$Oݺu-[4Z21!5ż~<P,nF'\,,\ YNeL?okeS%v*0a)ez5%1* n Oci1W@ VK񽖠T..BUkM)-A lq4Tž L,hmܭtkB~]!  8eZ<7݌1ylܬVk9l\] 0`ʌP#+=ƹea1yn\'u\rzhr+dyRPunAB%([_Y)1!JR!*n3"#cP\}׏R .%"uNbLC8'xfGy'2VJ^SVZG:QaM~]1^w < +y𝜝,]v('ioK^Wh70([H@U&RRB eu4qnHObl R@VȔ> }"j]=*H +R0,g xöĒrF!dP6꣇bmQ!"{ceG^ʅ-h@ٵ̷a8 xJߪ!T0id.k\,')\#[ul)XIn Imخ=yˮxnBE`Dq*:%*gHU2U"7{@T65Wb?NNfJ {/&ӮU3&.gZ961vjo خy̫}0/vjd Làb``x  … ܊o Iq*WM,ϧv TAR>pHDZA>^ZZL@5vܝDjtdj#lQv@AQ1g*,d +Y#}_n;uZ>ցCp`_x`+4<<bt,>}\52پyes1d~X["HDZ%@3s*% BprRZ^ZV1iDbXX?iKB_ȡPmKᎀ$*:i&BۛȈj-*A1iEualZu9\̺Xyy .+7qcմY<[ 68EXpbTv`Sk Cll, dJ]1nk%dTuh=n>lxNvt +B n-(AE`lg$?rCHy)Q. +5B #S؏B +LyWzD +xF8G[G|gevC[\*z]Z\Ev=ȃG4 C&.l'OE=%ƬG\w֏On.Lt&#ܠNѮ4Pm|d(;{3^ؙ3p37[ڪt ^^dv^Preabac8uo`&6ΒZ.Ma Ӫ:ƥ6K2- JNAt an!EQf2{lC؀0 +!vdz<.mp n\.PKKƜŅ2`u URW2?X]퉌NKSLx.$u`Wg򎻡d @&ƿvRE&Kz*ltc2|zc0ovcCcccbWpKԔ7tCAD [c0NPD 34=p0n0MF4@n_f%cTfR[XxjR8i-؀ +BXUNǒJɤhic cs)ec-\qc11y|w$m|8dt51$,YN3@$pÐu*}E='`WŸcjށLlR%{$x"".}qld[L0T:P/#al1c ,BP6(D_o˘L;.zx`v=s[./zZGG7qoxV@R BĕBF+N cZ@-GH(|T:a\Y ,vZpBD*` Ъ . +ly /C@i;\ m|C 1 Zf`L=.HY{-CTbX 8\)ΧOJ@ P|n?[ĵLJG7pHBxM&7l"- m,Rb1)P߫Ó,*vCҏH ~\%,#޷ڳq4|Y[\@OG :1\6X7xiAx +Y!^QcƯtLn|▿_pEyU@ɢג@4Ĺx,`4Bzh,I6ӐG_-4:ZdxyE#,fk~28t]BbZg+KLXr+cm-bQ,Lg2‘bi S\RlyAOiM| A2BW?G|K)$a&xj[OV/ q;B\饇ȼ9C?;)H@7RJO[Ǭa l\X[N.:8v&9)F'fLd`TTJlXVAʪxkMS pA3) %q FGB**ZDG|w KDRT705q/%&d3\LM6hq5"; +)!J \!-42EԐN4<8Zp.ńm   P4~u2륄,K,3!i{ q0#.TJRbNF<̩[Hj3ĕ^?kMLKU)@Yfha L92H/06~p~m$)1bS%%jPn12 +c% GH9&#xFpsUMmI1qF +H+M8@!M5C\( OK3@?@3ęn8 $(,0k~fII3t ߧ#J_6Rk )M,h +6’=sPo,c .(8qʪQLmR_I4тNJMo">2%s|Džg˲mw02ZxD砑,Bxi'" 7zk$ciEKX2|9; 4 >De(">8_2۰ :.zx"eSĉA|g<4AՁ,\WX8|m]qbqa*^m@*>˥[q/G5 HD +Kp3?< +&/,:Ç`z8SP1eGk§CXX!LL\Iɢ +beU)LR9p2LaƧ*:KRƢ.m5+'%dįpx S7> Y>B|+hsq@FJ? >ùKV煈`b`/Ё3 `fL4 { X`Lđayf"VMعIZ"a ;-|7q|n2|āyxJo0SNP5q5=wO1?|)Ï9\e'q"~o / +1,X*k!>R7' nf1 btC'G7oD!3QN"4f2j_q? + 8A!F|p.S_xON'T-a;D98\pzuiĈGl)bX$H:8|"0yĮQfg<MURbF9r] +P 7;!<~Z s*tu"dd&xIU c'EEr#Éx7p2gLc85,ךjl5o -4 B0ͯVN B<*76ht" !w OD.87^tk|!JIo1"` 3`?p]<_pxosacCx YK6";J67Zhzu@1SL#Ŧ*'cD8X;i<iCMU") +rHIXh8 0|e8. &!R tez`RE!Jqzd'r=>5=TE@_Ѯʾ Q7DIq@%hCyhN_~pĔ꿊I'z{<m ID Ĥg`bkJsZdUdW12iL1qzYW\Qٟ^?ѥ?Vq$Pe?=RTGA{AI"/iĻDZeQ!= }42 k@ ""< x#z*:zMW"~#qt/T#RiIq7`:oc dd'U_HWC8W+&v,OR9*D̔؏:hۉ8 Zd[:@e&U{]L׷hhv =H*G`ձ3&z"KC?)*TD{T{" `wscdEO"1*SȬ@EwG6?)Sg5Cs1ʍ@H7JL}t|bHZt*AP J@~@FDDg s ̾eWE>| PHz#D4ج&@J K䑧d_'EC] +*;B;#3 +-<{Ɛ l0{vUGhZ12?WEûgE<$jS,~(Nyg-&ƒ٫ZP-sL;qvuX1vl1zj Nc' osާ>OW/4JB5$=WPRZH}Of !(}5s!*k`ZgJ'gh,ֵ;=BD'4Q& 4 + LP-63+#h6,B[ĤWF<9Xg-ĮW/6kb,}TȞ`DU-D)і#G1ZS=Y%Y?Ɉ[ KV o^B! @Ji%P_9x]:YKZghv mOĶ0u~,ggpBuzRf`#>  w5p57'$,&p̜[F8vuNXfj,H@}7PArۙ=zg褳ddv lyW[01nfjw<>~)l8}3: +a~3>}J9tU.$'TF +?u'IwU $.m1B΅+жED/ +HUvK*E1DD DN'VɚH(% Rm(WEIK ^#p!:oLXvAvW:=cxR#*{S*n"%O T^\qV(ѹjG_ȬqtFF6Ͻ Sg+ 9BJv'5=G2#A@LHHn"gѸ[3:{ib$ཱི"qJ0z6R'bФ٫^\N &~A SiټZ3P9`<6v5ށWDKem%6> {?.P?ҧmC8ouDAo2ʲlɻe!KA f~vGPYԳuxP-a3Gӎyppz <@=792T?C˂ BL63Zh7Mbd_b!:G5z/XzQ8OFgri?&KtSX/:4 :Ǥ[Ub>8B:zwB]L!!Ġu"dn53&!(k?Lri%n LNVϹLzr5r xYKl[Mc6tvXA7(I>Fg +@B=W*6XqYKD'.^Ĥ±Rh<|_ϭkqv^f* 'VFպh/(!@,("mPE3%O; DwFE&͵#TIO.vL#Gu^ŝ4QG|4jx:M \ g&g:Kږsb^ T4o5'a#zeDw ZHw.i2^c 7VokjSZBSYN2_pJWN=( ܳ4"˯ruIS8BZoH^ F}C;$oiOv, ?S"VV+6k&]3ah>8cx, ?7V +YG`[JdZhCP: 1nö{ם\=%r-s&EK0BgzxvNî`\fNI3˂ŌW,2;% JU/.%1h|x_BJ=5V !*=Q`gnBk:J9gd*6car3]f w˳gcS!5|$&+VUF@6:}DW ]Qve=We!s1} +8!ً: =Lޚ2mr'cq^ $)i-^L!6kE mᣣ!|/se+;xRnՍ͹ȓ3|p7^:'EL)"1}ius]f%s2|mH^(Y( b; +ģO@'ze -gjΌ鳻" o%NĮө}4n +M\ +V=kgX˧ PRk:5aȇ.7Hh]exn$MnX+jbm>IۡB," NK)m`qggɈ_@L}U t +e}R;B.AHhGqQ N>GúcVܭ@;3A 6 8O0uj44Ŭfĸo|5oBM^\("SC%b`MqIB[Z1Ё,y,u ˚ފi]:Y6?ƽܡs`Ĺ&jc^hO9Q)V* 5.$$ fj]54}" &L2w2}k^LGl7o"[)-,<oP-twH3|n[ɾ;c.P0|fଡ@4ej;5yNB=Y7uwm ND~2#ޣkhɾUbSx*-1Y@R#( M[Ik'qz>|L] yz6<{1zj&@f dk|\%ƻbcH}熍4y@FGR[<>g MT KPi&ߝI;b=:οVrFoef0aF>jdr|5<&elMn3qlY̟w)D 홿Ң[!6LU(,Tjݳovm'~w>Į K{ źJx+z$@mAny< vbX~. !m3X[12aH\$B;;x庁5tu&Zٻr|R$&}砐!pF\Gun~mBFعldFȒЬGw,ڙU@~v_ ΣN0qpQ&Od}a5 ZC_藷ήs/tpIjoj:x;2zNBJ+G)υt8_/mrv ۚͶMw-sjmcHPo +KJi >u3i@,4>}v@d=$ōaJѥ\YOX2JW`rJgp7,Q_DH?֋̾$a^ruR+"]mv O6'!d5n/"Fďhp3m&BP;J^Pmhs(sdJn\{}5WӾMjFăD[pr@c}@Ul#}zm"ONvK;A i8sѢɫ P?#sXwg.-1;)BB:+zmEΓ0 Sk#ηΆC2~i=yY2׹1{l\>M),Jk=/F^"Չu3(!w{vZ!;DLb'ϮA;ek k㟿*pG' G8(- o/1`] L ?P]'M#׷8dj#^ 2|m[6w.&l70X']“SU,ʫXDqZA٥L\uk駋iU)*횿֕c`Wni}!)!I-(}b')XK#INRJ#y~?M[MC2 o AB +LN +ONi GF sSkbG;|VR$G>(33{<"8ٷ[+#5B<5(T]AIZ oj^#u{N_Ɏt|`CR_5P'3jKH,)SǞ8GܩKBFze9a6N[GIU} Y\m^8]&Roi,g0yJ0 +y"hZc/ugZAJnhu|=I4ܕBx u#NYsjKRJ>|[2.m&RC6ZKVSPr:SXRJ3"z& .  \179'NoJHD盷tH D4XSn+=,KkA tupVi%o- +=Lk{+":I=+z=Az=)K7;OKFf_: w ITbM OL=&}#hF*Y98 QX +MJk' `@~} CP:KA=oM+3&6ptM J]i!.xDG3Yԣ3LiE\.3A [T*.7ә Фa_`OAZw謉0 혽6[hpv }`Ck:=alr[.{yY.;ׁ&v<Ӡs3d9NZh:}~;:mXX@DPЂFJPx@H8yT*."ev&Dw6/6s&Hy"Bg>Z,\w#G;h}.9424ŵx 1~k9YMo|(GLc3܏B}+ńta+WK(W @ %?%DI?b\R'( nBPa1A TdL(vY?zQ2o[I7<"LÎJV2=,x*h;1~;(Lb8%ԥmuBc5" H$/tfK]WQK`tqD\t?QkNo0vctn:MB7cshy]:>𗱃ifL@[j Ťr@UR%(?UzE%}zwjq90ri4۱l'[a6osh7YGߞ#Ϋmk7dt>`"*_:8\K!fM&@ P0$z^fNZ0)P͓4?9#z}Ȏ@$ 4 cۼS"lPRם]иdpw8 rwocִÄ/΅s@Eˁ ' y:{dT083  $Y+.j&;|5VO.R_Ȥ3IJ]!!a`Q@Jf݀e:AYSbJk8ZŞ Ӫc48_c̠#G q]챬'klfڛ]tB^K'u91wVVѭmvOÚ.l6VeP_%Dۤ*5t~L~;&&ՌL:' y.$\4d_bMYhX2bJ{ )ps^?~ѯ]~$oԇCTpU:*Chcyqn1~n\Dj} "]}h7my&d ?:QOKԥm{,Q)!9م.쫩``Q$"io?P"v*6VK Ƨ3|r5U.EZ7ȉ;i4L@*q>` iTZW K#c.m\8b5?n\ p2/ 4qZDr#XAnVNj^a[aw ̸-m52+ h,\"NC@c2lQLbG=tfP\t)#f V\!t }{+\7>\KPW8@ RLByPK_AĢmò+xR)" !s`R3m,i̿.9X/;He<{0/3фs'\۟JA.M}/tƫH0]EBe Y@p#09"09pr"^$BoZ4t䯴,KQ}S/+霡OzGj]J&9a_SuuA"V)S_R|`T)Xt!;9Gbp~vsq%u˫q|$_%&Wo hgvvknCo[%ݍ=ώwOf)R|" Zg Gq5uN'-h_v9̚{,wz%r0C{& 3\h gBW_@$^FpY2մT7Y8ʚZ,gqsu ޝuh?dI0o? 7Bp>Ͻ +n Ϲ|XU XLV&`?;-y}]k+1sGT :΃REI]-=-_޺wȍړ~㞇.ӫi?ԋ aaxQnH%U2+͙՛:>r3,B:=E&nJ `;O5 +ƻ)[ߏ!?T% d+!Ql-#x, eU6%vtX>YHۻOi];lar;hC鴳>_bin^! yJoD +5;,\c]$ӡ*z.4?aF"ȕJg<<Uߨe }wޯ6}Qv kp|C}IawOwr +"(}0|*ͯ_E5+a$,wءˣ 6GEyT՛QS:GsV d6'f#y^sPx:oݣP DR0=O k[ 0?5wS!ʙM z0bAw(M mKrTA[6T?E1xsb7yYKIa>,ۙ Փ/^S6:[ߥWʮhafDN}Dκ8{7KVm9!+rUYgޟaU6c] {%jmTa{XZ%ggAi 5!p{KrZzFFO0P~+ +~ h3Uzuu4VLީk/HU`? D\ۓȁ ǀ&Ԛ&;.]Z1|67(Z ǝaGvCRtkK͹ SO{F3GWT. |#~<7_#9t&ޕXf|} +brtsNISD 6{~6=.Zmo#l{F`AK^H--`+8jhck*!9ToW`p!g0LMmIR 43so]̯Q80dzFC dϳKiX $$oOķzKn|ڇd, \M/}WQwՃf~Q]\a@拫}]LL/2گR}c|xó ?Pk X;V;ͤw,' ZLu.3HM„nVM?D]*Q^HukV8~b+%cnV %<)Cx58Afp<]*] -Ԋm J<ޭiJ#ܪ C^G%{RE} %543TIoe7u774hcKϑR(|~]'6IeyG<c(f4FNh&m3%pwx]u~F(/6t|Xj0?kD)ҋʟZ[T 9mT1 k}Р;ވ yEDA}RVr\)'o=@S} Cr`“qF#'N\%` ?9W3_gW\^TNh]3L +6QKާNr> ][ztѕ8=R`M?Ld{+DkiM#sNgۺtNuŝ r%n60#x8JNP/KGeCWA<~\ʗij&*bOcGf=c`Xl+Ib}S~`pߵ) _Ωz Kp2SH }AS /+HK=཰m2FfL,R +g3.e~gT+yHd $sP^p!i-dgV_H_SuL&1 :,s9Ӵ-OƸEak?a2]Ht<WAWiҹ%m Ë}4l'/>#d~ot$⊺3WbQp Wl=rI}tQAQJjLEv SE]p62hٍʢQסm*OJmm~s +a|!Rw S #-ԗ Fױ絫gh^ucP. +|e@I]luÂc6:m;lkO|=FÅ d74-b9"RԽ tC1΋T\T<3S9PPmf׮L4V%ƙoX+ rUY$=Wҍ6[U{fl3Ymբ1sIX+U_Tl+ak? +Ay(AĶ50+5 w>IPs RtӶJؠ 7'0%Um0} ZχYJW&!@Duk#52 KzͅS?73Z~RxV_HZ uvl $() lzo#No]*"Ԟ2QoV&Uҗ&|>f/3Kzodʅ3ҍTxbT8{,rdJ4UWv1F +Tiw!uCTSA˨#*( +v(%وsgtSt:2cdGLax[5,:t'.4#K_~D(帑:XavUv4jܥmÞuiTNz>ڠu ,)>p#Q2Zai*g tdzq +aݵlYEH|W8%u;D`oDU~+pX@,4 Oը(ERǵ} &Z~Pjs#8GR G {m{ՐH]p^C|&67#eWC6;,B<ߩG ]sv†0cCnzD(.!fC63(3"ߕ,!BN∤@k+4S*A+K)@Ѩ%]/~jوG0Α$6Dw$.4r 9ΰenfJ;H[]燠dƜ6'!e]#mEwRnGqxvi>?8n_LN}J2ܽ8²KRUq.qkR+T@go&/=ބF_ .Sd… k;W-\~1p)*G@x̛XU\O J r=>&etC/JIDt>c.V -z%OnAr!(jװB SRjaKN# 9kyZkW%pL+7rѢρElzDvB%N\G<\I\5?k~|(Dk4Px HX(Z )I-;,7}?w9&@O9KOk,w!sR򉘶!^Q+B$`(Yqna ~M80uxrzhys4zlӮsIdF3V&F@Zw B8M&mlpOx?|fTv(-#;ar4R}+v:f:K@hU#.o +YXʊY,kCΐ_ +9'ϔ+d(]<DT5Uw(iw)q/>xXhp@ YwKLW*eEd۶. Z&-'*g,{P8W#vBI^ݖ싑JEc1HQ`&82I+|BZ?D+ fCK>I`jI!0,d=2+'N!BݼTRhW=m4X)+|$uQ%h˼z{\C\}Z1>+L32ezqdUu ~&ƧqΗG\vݻՇc1)Gc}X%@n8+dLI N ;>Dpy@cfS.R&Msf55)dȘŷsTZIaC +d`$?ܱ0;W^`4h{iq:X)Z*ɕ|=Jv=BpJ,ʅ>CufQMߝ`X\ TcfͰNJZVpssӣyf[ˬpz'aT*&T.n#g8w t+Kɡ9;d5kl|V騺 +Ovy݀1JS"D#\H"+#ϷǘAݐsU.}DVX.ԸZ#3]JXՀUa\YXaE6msJT7č0эP Xr8/ØKhce3")ZH6%[%!HɱTD#ڸie8Vm'&NA5ʔc~ҫZdil՝)WHA6:DU,+`V-\Uouȹ :D2(Tެ~mۘxO =fp@xr8xɮM?Cs$,wB%6dCU]D4Š;߉"Q Y"S'Bgh?kr$B׬7%\b D*p)FϬyȫ6fI Bg4I&l Ƨk +TpL1h1ϼj|MvI*U -͢7PW7ZmiN+ ѨX 8JK>`Aב o C"}jBL;>M`!摧]c|'.P~Vw8)̣z5nIz֊&F&FoJh8/}PƩxpV>gsPv24\׹C El" MGe߫~^;u[--[7Ӟw~+<[wfl5ݪ}ZZk:;vy9]z9mswci?;z߸nalgs2mҿiOm~^g.Syw/xZ7_qQ:oSnw<:Vsy)V|)~ÕwXgM9Kkj9xnurrOo{tSO{~i}Moq^-6NN9͟x,;퉳vvPjCmmt?֚um3O/77w7wXzǙNN㧜{N_ܩy;\L<3797_57Rx8}w[S]u\>S뼵8q[w\MyGgo-~~M377[l-19˿ܴTj9>3͙s~riX:^7TgZ1)g*L:_jc7][NɵŖb̩};u {_spSsvr㦞z97;xt>\5ݝwMxf"}Gyw?w9g+W9Jr3m}޿~n{Z{_y덉 +:uNub]uu u]jGW)v<:9~ibL}k;~?+]flf/ŏ99ugkc1Z?u͹};ݟ+juPo)pl3E +jp@ 6Ԇ0NqPjk9̏ +27[l?m99\_2hP 4{farsVyrfm6jCm@>YĘY1P +9̄L:GHoG\Ϙc-[cw;[^gYs7ݺn[5oi榮Sw]K{1wzol{K_]o|sn4ougomSVߊb|Ss9#g u{cK̚[̷n+gmƹgZk?ko-tt]:?sT}kw";ܼ^kݺs<:975ݝ=קx8?+ Pq 6,ѦWӯ(R)6`s<:8ǣtsGG91\Tͬ3ĕL=VNj\s~ޣY_3s/ooov7+][=VN=:579^nbnb{͙YNk|nf9nhb7777~ojo_5x@WzyxĝqXv^5?٭rZkܴvK{NsG3kf)g}sss/x;֯o>gi~r;oiN{9yqxfx,7@DbY8+p B:js<,4sݭT?D92 +>X ?+jCm<:\쩞y=k2QjCm 6ԆPjCm 6ԆPjCm 6Ԇڴ㱚uνfGW9TO+Νs:^/-wq_~onbXg/ݖkPgxn1z;}_ySz5vy[nn37uboǝѱ7gSoow<|ͷZnvZN?w-v\+il9i&~ygsk}׺W8LL/lZD6e1,2F2kB;:7}U8Ky% { &*=]2/4aAꃥWz0]7]y;6ȹǖ'#Co,gmZ/;Ο^ >$Ao/IkyVu7nd1lwY{V]989ڕyY_,ZܳZ +}+UcUR9[i^9ﮕ?uew=Jaq=$˙ůI$B Ou)_*W\珲!E*+g*pLJ hïU{/&E]L;Xmǔ +gR3޷Bp3y`G  !,F &L"C  +jCm4wCQrߨM! `orAU(?ج. L`}]ߡq`2z0_a^jƯ$q!8o8Q&WT&v`4`3hRm :}*]*%@8V_*)1s׽ՖuuV +'L~,&v`` +,=YL<$rHa/pnΚWn-usy +.ڐLdWQa HrRT4`}HyEIAj6Q+Ed0oFOL7{,?Is¯4EodA)0"&)CP$@Ky_ky*#=+CJ3QA)L([ZcfR",KW ,d"` jVqSU`'kp<8ĝoyn֏x_fnjny_{sW[ƻbv^|ۭ3IPcL͔(6Ù^7;yֶL?yW^~̟s֛̍?Nqj~͵gB[7Zo3̈́6m[g8Lh\Wo9֫sݷs{e[~늿mM_ 7Wuo [ycMƹWPBHQ`"E_Y~0 +?.lQfXd[fr[wޫռck?_;ܘҟu.w(>\g=ʓ09`Fo K5UKu&GΦL=ZwS,q|iP#b#͊{z{^7[mgjoδR4RVس KƯbI~* +?`ǶҺq@ٙfm͵nlijm|_?_f\Z5W_uW_1rͫ^oYuگҺZ׺|Sq^ksg/5}uV}/]g?m[\q[ZZmΟυ6j\+ƶ]v5k yMd۷7s5N+LMj+'/3 +$CQ B`fT},g~nR>Gࣔ"XTp< VV7vZ\&Jbrhzf,Նe :S!}.bqƪzXi1*'M8 \UJY:HA$y&RB'qJ@.ROUZx}ɘp%PSDODEEX6(U9h 03=D` + 06CB14 ,uqQn M\hRVZBޢ|bbDB*`VE*C2.ѫ5a@8Lh`(艏5J@6^!jm$68 ui\gbxH0UD0Xp""'%"*[\@4YA)U„LcZh0TTIJ/("Ì=Jiv "E<%M^ VЈ:x.b ,`ћWk_4ȂԌNWF'WgLLxp,*Qҕ{_$"@Z8P4ɵ0-\"f* ck:*Fi D-%&^#ɣaUa\6B#ՆKCET +Y:@]!1Q,g]D|* ce<R=ĀJ: eih:\KlE58Gg2Nl9 M46,ĦEl@ n^[%hŔ0{ 9`'< 0 «q(lU4" C#hFYaʨ\^Ch2 +.Q8\p@r@NAPDJɂʠ@`0`9 X s}!B\X&0B% /7ǣ:H@0ou7ac&݈ܹohT$mmV6D-B@>^&k7fO$Χn1Gb<9E5߇TN5B**`CԔ&OX [8`(:YqLT0w 0сD%('9[JDӑBkEkƹJō$.2{OȂ1p nl8B8W*F*K  *D~,DAB9)/4. .H4\X5sIA .( FRXb,,eO\u@4M#AiZiᵅJMdM6tLE4o A(ߨ +T r DdiZe㔐5M\З qii@haLi>]Yhdb4OFE +( BkF \@`/ +J +XUQ nL̸y \BCqH}4m[ϓH1m0`0z!|G~u] =YW@tRd&+@h36"+ąIS7a^d4%jx |0)QL 7DLyHi`jZPLPЖdeDG}帉#Ub(ƲP=^*P1|I,q~l¼0l EK5# +d,glz3g )s}ᜉ$gzo =l> Iz󃁬9CoBpGs 0VG\Fo~炠daqFoz>BxJ}%^! !>28BP|0R*=xca")KTlJoZ_ dC"+}-2 +\}P _&Kl L$n”RQ!e؈+3-؎Mi.#jb"5ƅĄ$x +endstream endobj 13 0 obj <>stream +;ȹl\l\4$|gژƄUeĥ!lǃxOI< xtw:/I<]$)d4i|W)W_H)dd(VP;8Cv.(%)y`[ +dp72 +"2"{d#\*(۸i. ~!Fws<b`.d} /¥01aU\DqvqBJa7cPD$H#5Eb؎dUGDS!E>5(㮝*4r5o +-kzL,)8 +J LSHc*9Q6Q~5AP bf6 CYQe&`DP@U7#\"q<8ŧ$a<BT2FQ\ ~PFP +U_GBI_ (>ǂvX#*D B$Eċi|usehd{Ń%\02РQN "Ҙ0*s ;AHB0( ^k,1ARWh3. tEqpp:.AstcEHt ԎRΝv0iPa/2@H2 b0(E M"e0*@ՓP\ ++Ym gi ^3)/zyqᢋp86h%*z(' PILZD.B@q"CBHns11 KDeİF#PxAddUA:rę0nGdET;Ph&vT%l鯁%cHV D89l!1P לgs<:b$8l<*bBr!hQ7rh94T 0*(7(% ɐp t Wv &$n݉R-,ɩv,6ǃp`2ߊcW` <^ hk +jB>:pɶdUGZF891ځCB4Bus6a6%Ұp- +(c*V-$Zny Mp@@c= 1PE4-Dt."-1q wi mw.sAxMŠD{cyaD$VP.OH-P8L"Pt%xN71+ F <pjgv)$ +lCAf x>UO``†GDE<Ԛ@B\xlfULK AXB2 KMKAblG{4 f85PEJ;˓p$$&a!kS9p$*2N=-Kfs:[)&)j(*eJW<<܂lGL ]0:!\vF\/l "t2}JSd +LIɓ2A'X-RdNS gRg@Xg~h"a*׊DTSdQ%FJXt5>r&FY )QZHJʾGeF*# `7Sc,X(TF ` L#&I̱޿3[L㗂L+ɒ~-/QMk(djV$sQlg/Y25 LEe`L6fAenQE?G~aX2Q&Z_2lEːEGLEGQ5TQ2TH+֒f$-CVzTF@X_WcT2SQi_w> H +Yu0ئDA@wB !Q_4@m&UжM?z$6&olǂve(IqVvID tQ?N + 3$4 H?|`L|Z$d˅IC"is %RDH0(BI:diZ2N>4"H#1(bdC5e}Z۽1cʵ,m} "09xsįCxzNpvC~CK k5-.BY :3qd_G YcUȳ ȸ[g1IOqǫG +oyd(x]i{} K 6,i@#[pRڅ\%BhJ^iv/ +tA6_59ou~&F 淪w *+@F&ſ:mS6pwaM0'coD-^:NOd%WCŇ>%k׹K'IJ%+m+B|VMXA8m鷑 (YuѶJѪl#ъ5Xv|7d~+B=xj zh@ @Έr :뢭koRP\t"uіG7Cc#8bc;}ͰdJt||GXOP.Ac> eLJwL +Xg̈CPݜ:w.$T`> Y6Qh@pJB$0O W1O@%Z&ͫ J0|m*lKH%(æR֤tJ2Κ* [I-'v^mYF])hSmXB+ ƒZwX\UMFO9()! +V̾U *zv" k<<"T_pIMNM~|KޱhBb + #6w *` :S.OP[U˦^G#OLM. g ϸj*wC@t9TuDTfj K7nOAQ]8T!ѸCI gNdi@x<ɈF 0AA@;{*;W#ih|⊤T}$ZI=l8M"#R L)kS|D:U\${=}/=KW@05+RY |RYvGJ +h+Ey|vD7.j$6Iv\U?$˃SOB=OJ6vAVfgiŷtRJvm >sf[NX-f( + -D=͏^'!HYmho+36ڏ?hZP&Fdm""*``ZmN򘃊qH~1hq#mj'l;ٝl7:=ȲGQѵ]Q`qqejqnAҊ͜Bq1dEw/7O譕gtѯX:_MDU ?~+\W?kV7mzl06Ɍ/KYՕCCgQƒ2@y"3!wݐv6)(M5h"`h*u`ӴRN/[;hQ4v{赒9%d,?XT lEѧ|vkgpmM}H0[L7 +0Tie +h,2&E9`2jA3\Pn;#}dv <:}Aـe8k%lS֓?99{{ +KH)-a{9˟ZQ9謴R0[΀H3vRNk}GTԢ/Z}y)U3G eęÂȾr[i EU>Sx!2W MjhnSøcXT' ٚ+wSD(4Slڦ`mQ'z{&;nlS$pjwm)\^|%[jn/) Vsqi+95?ݹEG2]L*-%?HZW<3Lhnz -ĩuE36>Rd yv2Y x_I3%2˽wxP–X@>w +{*fbTxXc%1!I?\P.he?xI ;w#Y |+ X{\kd~*~`yn>?쏟1[.?(vVYǧT՚,+B`Q[% ~ 3 `5uJ[Z`<挱4j*R%gev$VvcYww0$ JGTLK.#2ZKRVuVJ+Y%O)wQi׿q 6/.iXV.@GoshÐ.O_: )D֤kEr,@KHAxh8ڿXk}i:\ 3$aQVҳ4 "`Gas. +{V7ueHvX`4\iHHOCq63CZNlN$~5v81qi΅Ns׍װw +v.](I&23X@7R[WC귢JWٵ>?{$ [[%wC#¤Zi7g 0 q_ñ!2{(f;@xղi@g!8Ё`BDŽZDĄcHc7O1ʭ?ֲoM;da4< +CP+-Cb<|?|c~B52L"&B t" +'$ ^ =T"%8=y4 +2A94OlE ?33YթԒ3b.9p$I!""wj|Qbd:Hs)p3E ^X"H=_CrX'8C06dAd9AMOksAK$8ٚ89ċ^^:xYkH-WL~C0iKŒ6ƂZ%'dfQ1I$shbQ,hAqRf݅ {@vuNvYB8pZ\5op HE1Hr~PauBd=׈x<;ȇGwlV=\^A4 qNNu M'c5麿>8h)77]P#F=K6>OS;G'0܈"!XaeOɛ +I+L/T7ǟn;}n6YSFJ cde埠I[ K"-$'gplAǘ$kJv4 А?,sDxJ"2lZ|KX 'xd$PC"շ&7' dғ^BK2$Ǵ <1i? 8.9Ongљ dosH 2⚦{|eӭCi*tFq 5q Ǧ_EIhls14O9 {nAxfnV9ØRciq=&%/1n( [^XѐLjs{yq龯ON <IuWwLP"0$=Ҿ,{&):@cܬ F+DDIOW%lˬUaKs)ige +'ߜ ]%S S1 =­*uTuI8-"}R)*2#w#d)~^YxbGzGtnV~82Gqc%#ЃA r:0(\k\ god)$q49`IXTLBgLI-̥ 8ցu<@tg$ +&r?g@\ ;djnvnF'')+bmoLšǭD5\6pp +A*՗CɖAFq2YW}ʼ~Epc˙hwf (Bt2a'v(-ΙӶʞg'N YDzxșJ`'W@`lb]Լk(-WOYRM g {wLTD*: ZP-⏼a%:%*Z MgO^my9P nΓ_eCjC%עdieIIﺅD/_!%t1lݪV~̌r_M):p0ԭE0HkBVؿ\vk]: |y>M{r-I!@NqV0R'j@C73q_mٽuR3y.9ƚpa q]b Έra&(o_kYe1%_kV;eU[ϛ籠X7iOj:io9@8 1VԚ=P8 H"bGaG'[d"~&"?l[ +75xͬBSE?ǡ< K$3Օ$0/a] z#.9b.ST4 s䮝&4.}7k mUhΫ&gORu+2mخgiױB ?O47mn!)h7y<͌;-5e6*r56 TDŤ)0R]U'yb'$lRVZJ.4kx`(pC׷x2_BD,Q,0C2;j*hXqPJц5&uF^gL!)[-!m)8K7xUll=求2px՜~Pi&cw𰊛YWb)E|ӑ[@#DEFc)9fs>f:0'kxw&@&e }z\̀f@@йB'17kNpR +œ=r!y/9k8jG,$GmYбDqcq9hюi2i˂E!gSYG;IY殘xw2 ~VT[.! +*\)? '%ywLXCΊ<@QP ]X^Rh6@?/ӂ>`$''Τˊvۢ Dx,cr 2B|֔/d 0&`i^OC-{=$}pY0̦% oVaqM1Cơp:$*=@O~0}5cY#4QM9Q%I=pxs5U@Ji?]($tc|V:!rJ& +%KI,3=Δ:-+C}np Җ Ft+zm4 > >akCP1Y/4*v +qb77M cC +ſŢq&DL&\ %k)z}nVwJ5]'N6N}ы8bo dne$V1*O}X3G>-,#pqMM`)H]Ā>%`Hpђ.}E[w;a @HD8p`6åHnl ̘Ȗ'5tTV4n+&H۪30;B-/k7|01#]9Ú},4X1HGDm"( +B!dY$byNh1="G)V Jz).BBljXК=@d yjӟ‚s;/M-7D>.\<UxjFza_D.|qc8\Ob~?AB>jWXWp;ieD7rJ|VT\6u̞@Jrtr\ȘlN;& L?!3(= +VбmdօP^& f'vK7Yy*b-!TINYRIlB!M$~$xCJq~xOKziSꂸR!3.6q7c`@`ų;MVy,eLydl`&V`q+k2BH,@lW?sIn:P4$Â#UD3ۻqn…`sG6A7 Q MI|gNMk=l`xiZ!*3B/$;ci$63糦LMC/qKzow8/x)^qV2CVӐhի]_+C"6]ң"H4#]W[1 nAєU +]0uZ4E"MEXQ%9x󐌧Dry0Cn+~HX9d<`VFhkAohdl-$?0*Fz!: +@U%l%* V&fN!_ +ja 9tR1b`4u ~o@B][<j/W&=װ)5O䠐hL +A4"B))up;c,  +E=l]`i: +5T7Mʽ$|=-\6}32A׾tK(o1>KgS'U4 )t&G"N F=USS +4({jdWRhwujͶ +p*RU&-d:"*)V>--m?%8-LeU4[YzK ]5} Q 佣 X7w /֟} 1[E=f'ys_ ᯉIa#4I  +qluqBSPplj_h!cg!dEQ%mHhq#S l0rKz%݇Jػs$X,"%=6^vtlQ_/&=8#h}, H]w9ԗK0rO6OWr`?;xgs">E7mD֞=5'QUF̟04 `&بdΓUSU!7km^szjMADFVVOPY*O}mNAW@4X_vhQ,a[Yv ~ ǺJ"ck[!9!;h.Rd"i>sU\, )/5%uFnZSVִOʮ,"WN_Ro CR˩D8Gz Mrkq I }߰t Ћ((㮽_Fe)n(Ph{kṣ r7ZŪY~gx0h* ) +۔B_aL~/HnpayQLHڈ^4&Bh < [6?9aMp6;7̀*;۵(-݂1mMB=#'וxYve$De.ga0zYX஽pz0!TpVHilLۉɹ p&+e&.V$On>/c!k^L iYLB\u"q+1vz_W,~O> mu鈫IE8 + D??I4Q%\>*dZCW'- ]".IӹX1Dڣڟg}w| ?)d]TKLޱQ5/(|;KˇBDZBmW>AHЌpw#DaŀbxЇ6*m('<^bCq u?p9e \qH9B@#p_ e`kƕjpCsan eTo!r;>:#7_| D(ށ,/,lJn^?~)qkF Blq:\-%0KX)8, PUѨN-tM-(k)HkjFq&4r)耡x=!ު/y E&&l(ᕱ8, Ʀm, cJ'+WaHSnଖY5Ml˿HPꝨX:^B,o ;)!6n)Kfa%*0|b kfJ['2ƜHd1nPt_)h철a UB3K"E/,|͝>>AdѠJqGB*<,E30<'*k22+YRemCJ*zt=9x'!H6@<15UR)@P_}3t VJZ=%"ZKh!w-L0qkW@D0Շ ۺ`TDøm-)cӃ"Գ5tڤX)GbfUKȖ&\AVͷQpR}Jh Nӻc$.$*Ғǔ(Dyz4&:Lv(Jцhz@°s!'x/1s5񕜩0pI + ͖!+ԟ4ĽqWYɌH0w0CPN#+p&::nv;%/Q5lU )e]r)],Ϥhp38 j5ڨ"{qr`8n *>BCSg"3ܲB#?;)fW C0|H5)M[^n8S?,wbmZWHcWXG<$% +IovˬJ><&!2!%\~[g @SyR݃Ɵ|ܸ<φj ;0h#%j;oV:9D\DljY45ar8?JF`CI +m4XS:Ґfb*4-$IoX|{w"[:g*Ѥ-,xA 5L +*8>̓-x`CX+B1FRHJM4=Gv2<(S쩂ld(cOB8,Js ŽG|3!L`ROT!c]ygB|9ֳ.wR6Cȴ=ũlTpflfʿg5g`$fRʠ{dOEɎ86U~icu#7'8ɲj9.}U/+{|%46=pŗۚUkY^Xximaw m%^mmݨ;UU]Řϔ:dI7vCu$znzJ f8֏ +EɊSvL#$Τ>OER`C@K*BvU[s:B,0ZX-7M@yM4'} }#sH-W`-6 r1A +q!:;\w|cZH=Y_^|)D"e@vIVD1YHzChq-]inmՏN&DDz7s +, 3EQ(ˎa7/EƼNˬ_%`BGdžo;AWlBPcK X h M.+)萟W*DDDZcWcP>d5{>/A +VvIX0F^DK+v@Ljn)Gsh#tL!0D.01B8 L7G8:r)pYEQ[zo#M/WH,SqW@e|D",V{՜R51JT3W˾uWO%U318B#տz-GqC#63 rc*.eI_I_t2>@]wN +dol~I97ߦ"\^6yIUFoQHot%PǵL9pFgj b% b?41`xr7!0, 0&6Zn-2r-,K}n$bS#[G0yh.DxKsSZ2&^QOljy]xa & kVs%*+G ɗBKd 7"UF!@#F `s{6=[P49Q +00RK& %0 oJ!UMZo80sĢf.#hsB yVvۯj0$E% ոJKrA5\py`@8)bI?.n !$ti6]*'%QFoPyݥk-5 B/'cH؛f_( UK$뙙.?[hzs/2N +|˃&t UNfɓ<PcfG<Ȥ >EYQ$D ZRȐ3\m{H3t\v3tܧ8S5x` -$~wfGfsj}o1D4|\<@ +~a߀f1gFPi7NP/G?P cf8aA3ZBFhf~mۑIfT_<=H ָ zS@!p|R.kD |GAsAΆn UvITqhyigPG={3lyEUT{e"'"TF%sRN˦iYܐ%sxr $Hu%Fڄ yUK^Bѡ]@cp- ߂ msɼ&%6H?d)qMI1i01^G\7VU ,쐊l0fżҜ8W\ wt?FrTgӤMs1#qq<=U?ȇ~R#,ii. +K+:0BL aWt="!(L Q׷*Hq5JkF(0r]|ˢi,)*[¸K1dhQuG;btM?~UUMSnZ$i;&(Ket6:4vA쯇l?"IOێW\+ _J_zGX`|!ХPidM-ⶅ}L/~{d :E~:͘oKkHXY]@xUpWT)H-WVgs'8cN7!$[%SbI@+lԅ뢿O%= I C$+H{4Uϫ ֿ; GHP8eO PsǷ_lQ;h:)-+`Sm(k2]H$ ??&MfE*"̉Rv}c2g, Szl#̞kDd> } |Ok2R(IǺw[t p ޝumӻ#cWO;xiUn֞Xq_Sg>"iZ.}yoE-PG߫*8wհY=(?\Ǔ{rJzTkP({1+>OdD6;eĤ=r G_]܎\% ^5*ګk|yi,5$P( 'NUڇdxRZ,cfUM,Uoɂf$pj)D>D yC zX%22W!_";F9X mΙIŀrtfW n५+9L1,=z H*P$6'L%Qmp%&xVq5qb +X|u(e߅ayXYgQ7 V@4uZ*Js#d.as/(Xf;mGn^?ʸ^go;$iUMUs|(5Bh -8ET@ n +nLK^s $WJ:IDIY/-i15مmq?W[^v Sz7@]Tߌt)*6ZɆTKO̚ŵh1Qe~PkM4Z.F6=\c"%uRaڲz®Eo2MyYVXsFy-$mDz M "JBAӫcNCpuRR`d-@j?WXD^_.1ee syȱfo 1ݻ֤) GC*^z&M&E|cZ~VHU +m]L#f-밋{uP埠|N{%z 2) +>?~ 1RI,I-o|M qyŴQy.K0ª ȅ`.w?]9!șɥEЄ ~OM_&`ڐH-\ Ԝ3T(#Ŧt; ]Iv~h5uHrULRdi--8tJGl\S1p"9x-v֥+9w(*0 + + PB LWݛc"y}/z +RGHE&.AЩz=PB8Bix!M\!k/i [{̱:QZlʮR-ߦ!$瘁.mB>Ňc|J0UsTEU㬕 z@N&ANEU^]6 _C:e<{m'HH*iCZh\* mB+X2 Yfp#Q%* o+\`r:Iwb!5O_YㅸAO@ۋ(7C]=UrxzOW P;.xeeQ²,Upfekz +ayJ~hJd_P 2%72vcֹ/; V +]uIylػup7Y>~mWY4,Y>+sA9<}x2mǗC'$8Pʽ |.XWUPoB ׈J:NQq(%': *CH0BvnF)M޸H,Ke#-Dи4xkXP0O l 6"GȨGC곮 ᅃ?o*>T$OZR!˟Q?sns5U![- V='1&}hc02~tQw/N6ءSktgeHqw>{q% غT.W,z]徬{@i@ `^=6U: Zn%AUkwqW:~SoyH> ɍ&? f RYsk  ;qq{IS R$/g} T!!f ++Qw,`1iPYM\LCT`?TQ0.Nzs<'1Ո}cT~c;:)%1Eҫo)TJ7s͏eʤ@~ *oOb/'(rm F u->n'7E­0vsVE^|YOj~a7`$;6,#;]rJV4pJF LWrGx4TTD>!T1Awˁl\ҷ S-/'7e]3P  \N@@"~ċdX d~f|%{rUS_#7lպ+v'zep +U}B7¹r\4ր ֞޻)Y1ڧ7suy9ڪ n-du99`R75Ho @n{ %*;::zIY? `дᣃ4.)pXNK#q@ԘOt` srTaM +ļDsB'mGbFmD \^vJ&lR&W} \Mt FH&CL0S  TxHBm¾:gV ~<+epM4#)[UE+B%>G>`?㵈Hǹt,~@(5bZ},|ڷg66*'b6m03:4ErWr.F 8~ sQ3oj)' b8is i * d!P@%@b@@ *<~^$.XZD? %8FMO UtKP-M0#h^޽g&ٛ/0;mCH/̢0i4FRu, F8f i(%L@@®Fpry:|, 1b9A+BQ0Jrl+-%Ld$YF)q"[1dh99*T:mk@@;K]%:z&{@YQꓕ-'<le:PU.c@&t+EpJ98.:[)a[YXd=paG 5<$,p965 "QIZ54e%isMOVKʎ-5Нɯקy2D-]m>X(Tfg +r +1 +EeĢ/"Y^7ZrM0 +X!7NС &4" KkvH+#s$peMuƈm$I?GNqI5ho d*7Y9|h)kUJ@>V?`ERCcc~)l<͋ |H݄4ʇǠ A1);:1 .!: 9GJh6u+=@^?ea|RFh6<(E bV;7(Jo\ 1'h v^C&E\B.'mDAys<O!P@.atʂwRBɕGwسR䬀ҢF,dRe2Lq-m(50P (T]b:#tͳ)H3'bYXǂ|| :ZC['>~ ;HdmxYty{sf~&(~|΁Vˢ;H"Z$ PC)H=.eɄS,@!!4|6KP;5/WHJu5qIX((48zĺ $$m"=qUD:5Mhe=F`F44IM= >}@ +W7qլH^eDtQTDz ƀ#|1 ]01эP:4%99jd({Z=aC>f/L':;ŋoM XD"Y{T#v0iN#NgPi +J[5 B`BVigy6Q2 +* Q̄aVZbfHwyEEYZ~7Jq/iwvց2JOIJ##=J_c4R$5wRuR7!yVg-CgVPiì￀,\8NsĻ Rt +LkZGkHB t7w68bƳ b #o_ 7wRjև#ڳ;q>)0Zw""ՓXb@.`⾴bOZ?f(!wEJ1Y[g__V*OfTHldE[$%)1434-w k#AR6A +R!)^0A]'q-ERf@5TtݏT+Usi?4 >J?%0׬_:t#o|zdpK度%nZ9/|UFp.~mrɱGc2inL"" "E3adj4M4hQ)qv^DiGL3b_3qakDH9%/r[WV.!^7 {" 9=Ln%5NHsP[6lR*퍖Tfq7!/#+K؇['n/^KJ"=Ԭ?)9؟aB]j˙;$j"8:e_)' abm#n|k_1ٛsluHh*-V>n4,)=/$޻)7i/BedMfR&UӞ,A)`kM#n5vo&07tQӎiFp.-nH;S4""b0yf_2qzFj [ +J%{=\ygFJ! ҽ_#-SXlGw:rM[j"n7H@6\>L)MaxJ~ip8a]1wG-*RA>.cohH2P$IZ{\TYʰ5U3-n[4+;<]Zᕒim˴cgmž!qjyiĭ e x|;Tz9^a +/ #!f'>+kuR 4|2CaPCaCnYT9df0S'8C>jp#ZSbP{n4Õ]M) bїYo\5- 1r?m&J2 YBǐXY d<]l.2C|@ )㦒d[p6Ig}/4{& |88zkz(kRI&]o5%%w ,nI`.[9 'W؎}y3xd Ғ)JegqUP)WDX=h‰Z;dDHׁ>.)݀ZИZ>bުBXT쐽[2Pq#iTs@.OPap1r”cPd$a_(d RI_Aj=ü+ cú]nVh_LO)R20|"sO`7~},0rxVay;t`+uP$C@*S +fn3Tsʐ5 G2 PGp<rl.CKy:3)oeh&Kp-CXSL$–~R*)Y3О}"h)& 4qj_.?MǍ_M4jbo09wLD0 G!"%[{:rY[ gYu 1^o Op֝`q!+f 46Y-6wi\~ףt. rlJikߟ R=[wvaxNz8{ )Ŀ;Fw DH&iF{"T7YeMIpOz gNO;exvB ފMpҞ7Éy|Dr7kJ`rf}~|]GVv3 +Kg `H"%onUKW#bi3(N]> m"*1$|rN{Y@BBڒ@@q9r'!rH TU\菤>oR9/KJ~"-kޓSD:O F:-1"<‰K^@m~6ϔ'm݉;3P2ԲvtY;-%@>P &(]wI0x+ӞxZ@#=FvGV2Ӯ-ugWc+)&%2F/O ~+CʃzuI9d5^ӎ7z\|v"Zl{cˀ_Hu_>5=y=*әY$m#*&ḩJɒ?퉪 T.uhg 1gmT =̻Zݡ@C +䈂+) \̻C'#(p2TץJkVTrCH9-{J\@b\IN;j]#;$dPn<7؛WlZĥ+ڜüzrr1Vћ51]V#Ơl$nuMae#rAX_r*Y_5pΠvIiN4! Ʉ'R@9b2P\ !gVz ÕL$*gfx}\&\])).~sKҋL eJ@MR>haR~zd8 &AD`|b.kbpJS~_5e+#TqB +nŘ)jl)MpNH˳/ٛ -A *{|"fm555n,U2Yh&Us!*T%JǜwX; 5g헨kX?hG*}q| < ao!է+\RO;RgU%FO&+$*H[#xpHi5Y/XSZ2p~q)=wKdagOIm3 /fQ17D12 C1Oءw ^4*Uo-GpsW{iPw/whO%")9xx="#2`q,nשּ8w YR2;׽AOpݡ W3sC0s+.m yBC"=a!擏wٖX-gG˱ArJZ$_Ә=oƬ7tL!n.[yvjxhFK7|IWx.ߡ`Pٛ=#ߊ༹N[r•ҲM:i?mǮ8j* qa(1##dEMBQ{=ArސGā}N0&Ow-LP18mJuX4zr1DR#Duvݍe$GA =ĀSkGiF?֚ʏ"f& G45dR`mpaHO;"~؛-A`M\Cz^N? VPӮ"{s!7D*[(e _N/\3r졑{HشSBɟY3_cYޏ{XmXm>#A}`"Rs5R[ST.N4QcߺYJɂHڃoP( [Ztm\Wefp)645$1cSMI$psd<1HNYlf_*0_H tTSoH!;f&Ѻ wx`wE_#ϑтs۩{%HWU@ssdJK^O^uGJ#SƒڇM&,wTvʎ>7j.(ق/WvPz5 -% jLG "W nќaȚ~4=Ko!oBxl$gW-ۃf4ˍ)/r4Xٺˏlu]qoEc %q)g%m~ 9 (jm!'¤"ŧ}|YZRYu(^%kH~0?Aт Dʼn9;iyݻߺaMh1SLC81 u'"2pcR(+Sʔ 'W]<5#oYt_L[*H==_RRV~/C\Np ⶁ n`m$JZ +{Qz? zʍ)W_uBhL<~Ai7 |:X4@R1e?UNC(Lh]pU]-n +~H xo3PF#KXhct)mMN/ִ C ʗ%YMr J^CDJf_ ߃ϡCd dQk z +̴+j7 Oh=7Huݱ=/.`C;y{5 YV? J{r$`A +&_[5SPDSY!Zp2ܢ IѪ B(^<<{;l4Rf6SeY$)Cqҽf#ҎdkA5Ri04"o0S(嵖F}(zЅ?ېP ҆ġKћ=S#lz~-S-8>4%ҊWe_2B|jʛrO +`vDը|ǑY?Q-OZ׎{FAbȑs X"WQW(MTJMfd6dBc:3FRuP.NBN&( &E#aH (1XΠ?6N5㜾:I/]q@vOhږ2>F'f)_a.:GxU猂,wC!&Uj>+"t pfPPIARb;3, =,RTjRrYZU(^gekW0y"ER"P !D*0:Q3 ʌ +rt[ɖZ"ITLPVքb6`I>)7H>[ ?.pXK>L*߁FH{Fb| !(x&i$+ja$N"@ꦨLn+<` P7Pqp/0ݙdi7Ō= +b558nD U[' ٧j!FWiWמ>m|?`˅O.ɤAJ7K>I)&j(A;{'4 +AQv4q/ f֏KuhɸX @&AXrbIdA lk` +2 + ]vðEb:̒0HOCA%\k]#Þ85~Ԓ<Ȓ4Akf9IDn˹g @17!`PGrPKBj.S,5Ov2Exosۈy%:@8xd'eas6`jEfC%ҷ{p~fbhd!?ܘyB 6zpoiH5]!>̟ _{F Ab^> cpЪC43Aȴ/;;3MB H(!_ㅖ‹[A!).Z"Κ8BԮxNlp8 ;!.e ?,b f'{wqN.o ! +s 4!ijN& 2^;Dpg8Ћ"+!Q18I&d`Ssr_1jއPK"K+Ǭ qu~!GJREЇ`-Tr!ݢM;t|Lez9S + zt2(t~Jsԛ>9`%9"W2ztǑ>wӅ:bcVaG'fV"B Gx/j8B4PrMNwp w)ݦ|aAEW;µ8@iAT)PYu}Ha{HhϦ&<Kd\ԽYA¢$[ۛI'#V{:# + FߜdA>O?-c%Mab>;;,),rĒx0q*y70^C1BRg~k5A~S0ݢ#R@f5њjfE"HX49{,8C¬!!dj+hd!mGHp:О0זWiWS*A! ,~%_yyg >H'ѽqH|QLB̊ DOD? +r/r,,:QSz&txeؐ'O::DmHKȷ +ŨO'jf82h!twS28 vȪNa)5!Ĉ巳M]vf+e>MG%gAF&fȽ^_ Vޑ4o> 2a +X2pf! *d$0? 8N7"Ets"HM7E1GZ$$I7[})^?;&|fu5AL7aOF!3BfzH$ +$ -UQQnz~\mh Aθ<ЧUeq n~x\-W !M +q`,l!V?s^EegM)sۀKSHiCHpBm:m~C`knŕ_h2]-DcXnu ~u$M/s-{s5Coj'Aȩ;# s0EXJ爻ze5NL;p!;[>3;-zSQ0" {dIPeɤ eC~DQ#؛bշL֙6^†"(ȩw#IEapyfEr ~ {XMe$#HyW$tg_ zEzAk:n%_؏2!B+ \o[ hvbz Bj4x=K9fjV;Ěf{w,n=?c' F ,װI&90Q +]x gs^׹~oY: +9IW.+jGOdx7a˨;MaG^F_bKv|)?RD^Nm5rNx(O%H0 l?fi4Pr֩H}9=A'LJo@ͱ3w}>gCѥ?*_^!Iz%oܒޝvOqVjW L`s-4O'g2u$>?T֝ hM2qYɿ-9p8&33>6 B!AHFqt˷һ P߶&`5=8 G 9ry'Idt}Te_.@OA?)֏8f1ƈKYBFOoRd!9@!'Ÿ{ߠFZEO X?@?v14'g$fo# 9!׍5vZ~0_lLb"tҐMߺd>#iQZ!d_,fzGw.3d]L)AS@Qy]ڧh=f*;H~Wr7Wڰ7bqT/@ QB5ZUv,eDCpkwyꥪ 3$cHHQe zR( pFr25Xg8;p9T): ~eD I2}AL%H-O j7z{i1|?9HDw`.MEd7fvAEaX;: ':8 O -dpDl䍇i7Β'/Y4 4/z`4N$ U` V\H ' +u|B&$޼Bb B1$aOHιB~\l}{Oԕƻ"=Q0}24]c=),Ω>#Hua\B@3^O&*M#DX Z]L<!:@NUa;k +pw+8nQ/2$KU@W!:^24Z![Ь%gXE/=Oxb s@) { P.aƒ2 Qm'ϱ`w:WQeT0j(00:]f A9D?RdqPĠ= M9o.pt5oyMp՟ ʐ%{2nR) *kFԒ(sYJ)\}WeV.rz/N ]]yx;y8 DMJ] j"$!3(!4!"|l1}#40#! >IA-xѸiB vTM 3p؄xRٜN\'God+UOҜNR/Af`tGBl۴g(S-ANI:f|OɈ򲫹;]Vwűq+$=RRĻ7!D{pGo5pyx҅ylNAk# EZғW.yWPD:S0 /Iv[͡LnueCi!AI|OKEEf_ AyBkyt\v,RW=OTJ'JrV/ᅠ>k{YwYh&El K3&&Ó/flQ +eo%m:3;H6uOFTis"&̭3q軰P:X6@8|r ]EAn_qMKF1St}zśH]ׯ[y)tи9P/@78e^4]yQ}QRVr@zD뼺qLBRgS۹砞U`QSBx;YGkL'^1/Ԃj*y!M+)z+Az_a2_fN'(BP߀0k + փ/8u FBOG\'=OqNO]SƒBq2=%M:3 (QvtL# \f_nj9MphzjP/,% SVMohbNzpdS짶ηr%`q'Ϟ?FUȝqIPu!i$c([(OZڔ^'u9s;CTf#ix ǁIdmVFCVNK\EXi3UQ[Ռ,->!rA#pԕתmۜG ɶMq+14:4ª64hf" G7O_;m%gN ytЛ)Ӑ +OފgƋPpzgzL1~79yrm )@qM~_>1YBM[n;`GLȏ_-yz lCIW֜:g6؇. +2dݺF#|"I g;,{tגD[{IԕX>䔲Wtu\WgPfh$İ3`óNd"[-Clq9ז |-tꠡ<#GFNn%5PMb/i& @&V:ee'쮳 yi28AļL'?lC%s:}dcD=g5'j^C Q:q*NB(Q8l6uHFhido)"~qc0X{ܗ  +ƞHBuQ]hՇNgCHuH|j*}:6:VңR-"eaD{.fOeֽȄ7'p'.b={w$YPY፹0;=JƉaV Hk.t B!IN|Pv\a{^<5$KMA +"/,ʾT4  5#Nr!~aQ-+b[s"3.R11P~#jDM\LLKcpT/h;l2Q(<3e"_u󄭋=4+ܶ"~ͣ^\&^(@ KҠ>4h<5MaN*:h!ڡO0SI#jTp m.֋`ygg5=#Ĥ{KHRGg0PjvIGװD 0/C =zR&ǒ*]lcid~Xi lY66} =7y{b~Bﭣ4t175H#I +NME eJªfS/b{9@Q/3 wV@'V#~pyʍcS6ŵOkGzR}+$[dZM!1{֫|sݟ t}QvbTM ;ߒ=r3yP4Y4 52A5Kw$p)M-g~{G*-SlE["P-aN0,:42ObVOA_p`UT.Sie3.FFkvjrd('z2iE46=DݣGXM|d7WZnIJ ~:3g:gI 3hþqYw4n {9uٰYZ:9lxL wy`ÊviSl9&$\jsӾ|%Ԝj?y!iN>i²S{/ḓi0d7JO@X%K넮i +c"hqF9mHK qmA#`{]'c[<`}s5βHr-@d#.($ xCj/1acnCY* ]}uЗ>OLw.oqbA2s|m<}wz1:|dzJOU}!wꊈҐHʒ]()Ԑp ZmWAN+lglEx$l/ʁ:Ժs=k.6x4Q~A^Q bE, Ja-T5>[cA׃ C=sx5ZZz,CdH7³&%7"o >q3`OB|E8E8jh-Rc='nh,|5qODVgf9.k> 㥴OKa3U} *pם8N + zP&8MᓤvZ<>jؽP%ķZva"< +0Qʛ6S +*)"$RoПYY঑77]t$7~بv H p>zhq 'w ۢ)[${4/ ~$u+ E%*#{ bvXpƻu,^YkV,4am˲i۠|uɢǥެBHj߲6E5ţ8 t +s%smjox-#A R^=ȟjwXT9 +%p%BI+\eEHN57:ha~ *p#U%aτs *H8 ܕR;s~j7fjӴԶSNZT|%Kч?xo̒Zj!I1M[$֑ixjA?jhrϦSDPb*WkS8 뚑,y>@.4)U*!Cx14Ķ\aku٦"8lNG3 I)Z#=? STQ25cd҄ -h>qh$]&s%_B#BޞN0XD.tU#keQYDe uwBqg~ٷ/cavP1> ދw0{W,ՎXTʻrGPF5*qABi +7f>,tN+A[Gi@e3C5X74yN֦5Hj[%i_=pوޭ;.&MV5Q&%IdU#1~S 7э͠0{>uT82c`WP86=*M7/ĎsAX0GXKN;biYCא .(/_Ϳ+q͒"A@F NЈP.*Уrpzn5,v;q*px%EI_RY9P) NfK}͒,>:%rawa|ZnsH 8LzQ +`c;2Uq՝qlk\DgoF3CU~;o&) 3D=`vkKheg|CF6ft`9N)I (0,)*בuPo֩2-H_,&5B\vdKt?`x\} vaDl幝c%=8~Mv\Aki'06*8j7z+8mR{D0s'G>D{r=ΤP %$Pa s6OI(!=Qwz9z|Obn +&WOb9YZ@+<*iRW %oK߃Uu%&%^iRďS &P 4B3vhRAJ&̖gaՉm1 dAbeCuX^B,wT|>{ @`LLM$Dך+f\xv#usf@23Ű(^O8&2؟[lB7v޾mj)O۝z.1iBp]bS9 $TTįu)J<"o`gȏpV_ vtR`5,*["&gR: 90/a5xL>bzr|!Vf[dGa!1AĂO5G_t }\Ge7%CN2w/I"kn0D";[󣃊Z-l9N 7V@δ>2BI\Vq`nq'v{ #q|Z?M{kds9{ۄ'[3K$e`tt9m]!zp;23HKs!Te =CMPF\3ʆ=j# 5Lx7DŤ0,!8]:-m.:~/u2R*ΡK"jb&bZ^KW2'wP$!%12!!qGZct!m1t=Z/2c:]HCdDOI/Ռhb^\EQ8Bс*XJE؇Are BliB|i@Cs؁ lݜlCLm&Q(1s)ˆ=d xFcH_qZUeBt_J"e@}p /f,>R86 ",.ȓl>#ۧDhdi-(rNU[`<] Zp.`bÐZnln`d%\Ҁ@mU<>~5=cZTo4d*󴻈mHB9ŀ("/ϐmS;(z;`)\-ێ+8S\eze+O'VaoL!vZ!I $ys~tͻkk5rJP 䋳 +B^-R3eA@u( I !~dC.}f@$%e#̦3طD:d@_Da@TrJA&2|և K|^Y@1OM'gv[`v xqt> [Ռ4h05\q⣃oަZplj0RNB>O /8jV<c5Qw&kY 606]!Rs1l0;d+T$e1<$K*WKJ7cGj=[òBIul4Yp %\R8!I*͸FE9'^( ^N0橥=ު85+lgHZ9<^^w_2:q +CrléKo g$4ۀD\T1S+d{?ҤĨ3& Eforkl4D;J*}J6m'I#ωCk<1F`ϢM[Mj%opnym^FToY{ #x_Yp65!?-S"ً(e4EZH(cGESKs Μtp9ЖfiIyT修AU _O,fā^rP1.($*71rl^O6YZÖ Űe@tWDSϣzzx"Hf*cÓMk7)Õv914mhblBZƉ+-cZ5_a=K[yi0J0hL1+!J0jgBHF]XR"L=&'`d}J3|mR&/yJ-0)6A$\E @ؾ1t3^(/;0@8kf +N=2t(@i, X+eq]״/-Vw[Gi;X~96xﭪ5Nx7jh.7=$-590p{ IG|8{LVIK=Z; 9~z]p~emN֘$0DfS!2skPJ|I+xn T-<;+ <30;<L$9Ԁu3 >{kJ,Yi0J⭅x \W:ޭ +vضnyue;U6 +Kl䮯oHYB}6鮯_k?X\%WVs6G}(EOދkãqtNﳍ8oMu9ke:e!{ӘSSXm*ïlyOU:07n\9{=pp -% 0Ӫ2T¡,gA"qgjTplmjH>]pbp_K.ᾘT[=!%}0 <6/.,W0_P4Gȡކc꒝v0!S<ȲP釠)|>lC X{uBJ] s^dpW4GzO`0O +<7V>$$@N}`sL!51o+>ĂC q m=Gn4iY")eo5ҟW-dث #B[K9]aT‡c-pxX$3E\ytD!6;c I#C0JM.%)`PeS m/H%xGfXK\H6&lL Pc`IAcyƌ n{O<#2 #NәF4&EHRK; Lh@w{ C┦0GCKً1)? gGzTcj2NكR?Uw9T`X\Hsw` N\tjⓃBi9T_TkBhy>? VYB,z{G];I>|m@p7\vRuZFnsnOiv [| 3YE>K ڇ7 .a?%a6_u;k0Wpi +^\5΋[E $HYh˟*4,Xhd:W.0w *J]NBSE*P4zD"Dt^Ӛ+m@[OIo@MTS >U50ZiGZiM/8U(k,Dcn~ ziD wF X1""ȲĀI c&0C1'3~ymAq֐bGLItFXt$da%h^ d`d*CT 5%{D.E7CCmrx H0vSCurlU86Ge/ѣ0OD0#j1&\X .+fJ'VCA<{Tb`3aDc(@Amң1"(Qd2 9#>60\eMFh {DR#p VВ` +\+LcX<HF5<ԕ +/J2p ^Aj$N:|=.0JmzgDBtSEKUTzK``(d٘g~ŠJȍL#"Ss) =%bD5K.SV al]ƌ/1OVX5dLYfL>։@; v!z%;`" |k!*23#" P|:F Bj(I7LX&"OR1z9@4V'P>PB#긩&V'˽sR1esJ:nM*f%LH7?rLX4Lf36ύn˫ڲi}?B.9r]!*~ʓ:.$RqeF+G'p8i'0Sb} }@M<Qٵsظc,$|H`B^^y 5‡bA)xaeātjpbvA367o H@(DбF\q=p;ɳ$DCP|KW)!US +%&X ;d;D{ b +# T@iQH$`{R6{E+1xQH8AVM8p"bl>e&6O`W1`40D`-BR= Q5!1 P늅(RT5HXHi4^T ,)@LJ4?a< P,Ps 6 HXƿQ9t|P/H@x[$D?+mBW$[ 6mC#;j!%.|k 6|MA|9xHWa$"b! o nbt"sLgCD3;'ex:MPPT +D3< qA vj 4.zaRJ7JT:p$S +%~i\R8I1fJi<2L)L c4}#=`yt8'tx\6p_:t$A⠡u ǹ4JE"iцI!Th5|$ 4%Hv-jv,% n6b՜{TO RoOzk{U%$MD0@EC G h#P7a *Q ц@,LD?)G8;GC06< U؜Gal7E33NH~B@4D<.N0z(`?0XXo'xM0cP, f2Y[s `E>F^|Zxu|C<`ʼ@4 | r' w"4zUBXSVS' R`ƪӤp1L=: H@:֋̓Dc" +TEgrjDC͙d(9 +# il2Q\̓gY@$TQȰqks踥|Ƨ*UHu DOgFbń/GLӅH&<Aj􏪄φ<#*`A9H'.CJ($D59-I~X!- LDW:}N+iX+%>Yt.ȓ!DpK[G|  1Ib}Z 55^`֥,jK]8: iQ5&.䱈ᅄѤ@DS(B:qMvh!H +l,( P,;|ٛVbtT'0Dbj5QP"(}(!rÎJI ŚwA8@Sb7(/ڔ=L, |H`mdM$Tg3Ñm7B7N/2:n!KtG@f&OF@:l˓RT RF*@qif L<ա q( gr J8GھVk4,Pr҆q~rG RѶTCa5rm6i +E +@b 0ٚ}N 5;8*Rn\ſqc۳ld4 ըymKu,ίU*U5)kHOrW Q#%b!)P,DnznlR`cJ[|3ڈb1 0q$E) +@T{Y* @] EP,Zq+H!By9IS6 +v2xXx +:A9v]GYP'KrP,bu P,jC& +lu4n5|̃c e #P\jjK6{AۗBeC#,jh~.)T%B? ٗOsAy.f;Cn0^┳F=ro@wT /H&q8,>l[P,.bPxcOobmHjZ<A$-QK F`FP,H p<XAO4by)4p>"m&q@L`àq BgtF$gqu|~cs\ȱHbe: 7rb"99.Z9@TB ]7/̃&D% ^؇b0[pH'O{ LДt~Pp"V0@: fBXzJ4B7#(Ye ʛ]Ң'6Fiwu PE>eQCsD@S'ր;V.֤1"N%>!j"_l +Di%祈Շi*2Cš70DrbPb=cgX:phFG8f?bz~)A z>q;?77NcA HJ*YTHIcEͧI[P,p1 +PQD4aiD:9yS)lb *@/Y`'pD?C!JJP +G"A[=h؅cPO{.C04&Es"oB^DeʜLgh|VC!S_0E;r6͑((P,PvHXN P ?k!" e bq.x;  ;.Sة'uGw>W?DA)#q6`Q9'5;P,:@2GuyH2?i\Ag*aiQ,@h.'. |1hI@G* P04.#$". +1qI#OrM'$HZ!U#{ J:6܋Ea,e\n n!2s< GO9*XXdkxҿHC ~, Q +"!ލ N%H!>HJV5ɫ( O"-_@T]JOW&~]%5{dIwzOiyfҥ;W\ku[sU?n9\_֭YުGnW]gVkz-_}_ۿRkukk+θR.owoޟo=oQ||uk[osiM};cf#'EZfN[MrE+ٲ$ҵigso.Z\U;b}ouSjk5h7k}֙׺}g;oSjZw~o꼯,1S1lAgVm/8bp$5iz.P0J;S9%$kCٍ^ p0^CEKMϥ:bU|zkH%`< >B + (QyƗDHZKJ^g0g֑YeH.u$<5&VZyF,W82p'[|0[d>! [#B9`*y-nգy1+DbxzmzWBB&Hmȑ&Jb̤)&b%=V+IQ㠰3r y3Tr nxC$A8,RQOcؘ[~Uڛ"oޤI;-+-fn $+ Xܠ{lF]PH+N/tJ +jY$\Հ'=Z@[:TPඩ+/ .&~"A>rkG3q +NmLxC}mUKZ i}-1U`l_ߕCs3̀JZ}|+A!N[YQƲnTCo?"9K6MMKG.*Pqd|O?5ZJݯKGf2Q_֜ +y₊_w4 ՋK/lns᭝n?e 4$ jҰX,r&WjdV@yVOL[RB2I%Wz<'P\e}bgBVc GXnMz,yI`&[iڗ,V+7LW(%ݑ`?1A3\q +9M4+2ɒH`o=HRzm-y,#B0^\F0׫>)4r=B5T(1}נ-ڒClKq^wa:Pw.&tR\ +Mg^e%yN[hED~EU <yyARs+)7{!'ݬhQݨ@'R(+z9_Ӂ&AxcfDh &ύ݉bAΌ*17(;khxZe +ُ_` *ؽEƜFڵ$OlhI3e؞ibHU],v7SG) OwJgSh[kuW^^"tkѥ<˚"$1z٭ݚb zsjV:MI2DzɠJBT~E, &3[X90֫C Rɪ|3Ef]B " s +XD8BiS pA#))$]L bvc(r*')R;;,m@@Ha)Je6c6bi.9Č|BFa 93xE]hPH% W~ s"kb2#otbKk $-ȫqxD>4 dHmOK&{/ey45P*,K'dQS1#G;k{r9MwYV޾=rRbUI^! +nM|L&I(tf`23b\$ rv}8ܷdß".nq%56%WHE6#<$}8 +2]5!SDcfq2՘GIYbm oeG{47M?ϟď=u~p8A&VO1:fH:1FSԐ8uja]ETCz"YMX1~힯SRiXOi,::s| ks]O~S[Sv:%Ytu|3ֶz;GT1EOK~Xrz=nmSoxC")oj,b[ƚsߋvDiQwv/s/PH}BQcʌCZ8u fA[Sw(n 9ʉ(^QDqFI\DㅡX"XDu_T +6Lq7v n -Iָ9 +I fVAFRUNqk]ٹs^'t-,[^"!Ȃ1kU1R#@/cIhQz"yۋ@$(#D9cFh7 Q;L\&@+$f;<.KCW;VqV +0pbb9#ཧ)' /3h!c$hr6!Y>_ W +2, embf^͜Kp[ʧѝer‘MϞb%1I])L,M'A(&!c~|FpNݍ[{;AsةTD[.d{պVᦩZkrHHԺo(JFdT6ڼpx#dtp[?!z3:H̳3: +qP%<25Y0wi26ZwiGV624Vַ>J=:Uy V*3;Wl_J3kwbUGÎ6@r&wv(aZ5˿l=2 +ZL6fb a(X1z{*2~6͈RB4qJ-Y및 Ujdِ7=ۋ3dn~Z3Lɚy(sk.WjѿDЮ;X j"6, cDR +.mH;Nu2v2fo'sؒO1+}+ԁOmwd ٳ[_ +N]L#|HlG΂؏,L >K_eɋ_;aA{*M  l)i$C*XdĀx!Y)& "78,ҍE :[g~(8o. +8&΄"O|+h8]#3cqqẉ?&IVA5Ze958=M{|r~u~&TP'2q=-A%hN\'iw&I=KU6+_;{Fiq\XX3@jrFRX e/-4 ?@.Ծ#Yʠ\'H'Ի0+o L]wi'[ݤ$u['*䒁U5 d4ZP*D BatzJD3BjB 9v;Uo%b']9K5zHt1 *SD D=xj! GE~߻gޚ TU:p@"Nc2IdT[BaXѨ,K(bPE=yMbNS#yңϜt؈H$"C9U(k0q&ی#Di`ś•Q]\IBWJx̨wp0Lr:u m(Y'#Ϋu+ ^%Oy2Ά"CvlX DR8`JDghSO +8!JRXZR)ה0>0M ? ?3L'7.Ντt[v+OgiSq'˺ݒ,DXekS3 ,~04ExЎ ttFt}' Df%Zm7OKMzCh1KKƒ/r g.)o~{V5cR{uGxDaTT|**^t]NI&xi&Z`pɬbqes:o h2f<{lSP9+:^Wp_W> ]}}% ͜A6 [-8wvr#Y!RPaf[‘yF\0?y䠑N$Gܝbkn'@ 1AI V;\NQ*g*`c/uBo H3'iXWbQ`?k[vfR45WZdPK]?JɩT# "bcirLH#PXka"M#d`)#Q/dlP\g!V#1>!q nvuhȘ+75ؾuSbO<-k (6T;僢ׁιQ+rl+j]SNσ܏dPO, 0ASkﰾRvya\7m 6b(|HVP_̶l ρɜvӈ?XdѰz^mȒ!ń5f-[}er,6gvP3a:ҨFNaDfѱ-󉖩Ci(-@^،HxZ(;vZ=R!IZz̳C,!_5 ]҄;X4I¬DpMbk7B{dWA6MAZkK1_C6C@FH}x7\A6FHLOBk0[S1,5xguDh 2 uj ~u>X$zi1gmWG{(ƭ9﹵Akiϥ񼧒d053Cp{|cwe\xf.(-}b˪OjCg lIj)aǁAԄH6_{adOu!ʵXg`K/Fј$ 5!i`b8'SBKH1܎8 wu]\}]wĺ;no +~/hg i>D?T,f|@tŘذ_VKc^`WȷxhƄ$8#WF(6 9aA0q7-jtF_ɴ]d{䤳H|<(TO/ 1z + F?r5n7NY) |T2}Z G..= [.͉<_yjS-(AӴ5G+bؔ[ޯ1B +B BɒR%׷9F9;K9b>`*$x@@F1B"HVKFPG&~JFO^A͇')9w8ߗi2«\5%d9[p۱4o4#QL3;E_e` +1YnjDtAvX=唤8XuF1^E}!kbg>(xԂY!nOo +$yt4>5@gNE'wId9!^{C k:3be9x&5:h\W%h3#8f&Sڸ, 7<}k,LKHy& Zyx?]$ص#!"aA/?V^>4=U9ݩᶱMx9a ̫_@ v01* <( bp,ϛ>4=ISP /זtHd/Ћ|} +FrP6*Mvhr+&aZM%E%o4]E^<3 +FvB;1s]0,ļo r |̯NW;TLr#j{ oD*yJs2ecu^Y C \l`"]o\5PUk +X>Hqw¬nPz@ӤYqӓ\2i6_;Iщ8l@ش`ϵ +>/cB2;oMG>r@*S\vt-nں3xMAq|@d"KeiiZvv !Pkw>NW?: 8Ϳ6S Rc\_GW$"q]V­?i^r稯L1Sn(gu^sDd]e/ $~+[]9A2Z|k.UG`?eE-%J ekKaՠ< l#1PHhsZqTܤA_xz2+ 9`KwQ~XaAPZϔ;fTUS]hkԞ3;8NֱIv@h,D Ф'\*ҫ~ +.Ng^R;}0ƸG}whka6z0DsW`a̦&#C9;Z 6Vٗ'עw5:dvC\Pߘڳ\Fׄ-t!u?+TZA?zз{]RODYp۞voOq̀߫4c KS\ +X(ܪ^l31>I%X`[>v{YG)dJ ]$;P=fxAbHR=vkR3s X& Q̞L6ViwkXƜŵT?9wvFXd@~r#_\qDh>jH0T3E!sđYs_PU11BdG* d 9Ųt?ܖ_.}9~-QiitHF]OrǃHBtOYC?ub@ic@ e؀#r.|@MwҴ0P:\2>Vs$@t_cI1^4(.6v3‘I + klԍ4Ǖ"w|RPt&YHίnЮ$ .܂H{J2m%X٫Q~33^HgX ~QٙH.S!B{ϮC IQR %n +endstream endobj 14 0 obj <>stream +_056kDܺ)NJCF3k OF> 3ncO2RSʚd@ۑH쐷6R:mܛv2"z#HHN#GEٺUO \h%2iTwK# };Ƒ#96$,wmG;G(K$)8H V3l=ȥ+*?tT]?Ds(8Ҏİ(j{B:ju#rȑ y~.Ĝ8:e'+Rhᐉ)ܗLӽ~9wޕ]n +_g{6lPgď ;ݘ.n(3hmJW*#8)_T̃6"B$re'o;2Ň?jK+m a^ 5Cl[:+mj*V"&bպDIwiNƭb[/:p5vщjLzKӢ?Y}' z-T?SʎY2#ݽqF Ġ}{Eϴrئh\(Fi>"B$4}aw" !M3tVHjBsZ#CgX|:1W4\FQN喀$(]OlW1!g88;YBQwbthAInqQ$D5:D `N'qqWhqvD\~FXl]LhA͝Gamn*ArܻK>RZ}hCIZOlF7!_xnL\,E $%67> ,n<_<%X n(F(1Å3Jև"Ed>NrjɮY1z,hMwFBԬQ{7[(2eVTm43>>bCi*eG$/,IY2`~J48U)7v#_I7r]¢[Rf+cP QBT͡n$2+1 \#IR]S<#^>,>J2je,J9XJz E}ěNτ8U|2n"oQ~Vsg# RucE<뚴(mB<σ:2d dA%nc.l<&30}̐XI< @o4rÚ9n%C zQ)䧺bRI*j_'E!H .F pQ)QnR@ (5ǃ#mHP]Ggͫ|^pjHxf.!_Og AP zEAHA!x^^oOa \(p> Q؇PB>J #!@I +p<=hk[yFzi\vtA23%/<զבB~&PYM)6QQDO)-HKNl) +"vtXVL .Sh^S[nE+Uz+ǕtVSiVƅ|lDraҶSY +KuKE4;Fq sӳ)]Sﵐ̧s1OZ.BIh0)h{'g) SG YN+k$[\3AA|U_^Ȭ^,[g閉bo(H(ȹgZwcKe-3LHLoq2\Ӿ+ﶆ^mvCXsw.N=VX=M41&8sfChvP79%fl?k>n-;w+kVd)Lʧ>AB+& lƖ4thL𩐡TJkHQ1fH "$W[wC;7!s ?+q de!h|Oź av̭jgo/$:lFmyU%QΩKg z+l>Zq9>%0zfH|fiZQ^df irT82m6qٮC- "tIH +W335NcHӭ9uH 5iH!TH6ӑ*cFo?K S6k2z}&Jӿ +SU+.䨑#ȑiAZ +qɰ<-Ud :ӓ@#Jw3M̺++ӻ.EXg}ix|qqϊ{ 8gש5_jۻ +jyt ZLFax'S4vU(t jƒv&kbeTb\ +3RUvX}CoJ$sWc~nCMs4=$)SѤ'tBX9e s>ϑOv;9G\3b02mK_.̈́CTѭhӚ" #Zt[!@.F,螎Un22}aMYB+O]Y36~L&Ʃ(j5gҤn-٪>9Bx&Ͱ67; [zV1m3Zxou͢BHI7Xwu;h&jPL۠4P=ea:N%šCL5 Yjǜ1AӘBff%Wovsn2Sɮ S/aB?^7ejzi;ĀffÇ>5,V*L7ae|+* ʷ{W+kfX\el.,DƐyjx6$,(򩘗^V1r$P3%T6. x(TM)9NZziiY6KaeWj*ES {Gƻp?^7BH&ØL|ȶG퐽#^Unm֘hfhW{lYw ZiWvڻ4k޶ڠZǘi mܩ77r}^?yy = +E-x2!"'L[&+a:~ wMLVev۾dr͖s5VlCjrWqv_ax4k፽-ߦBTJmޮ .vDuS9!5=nDZjV$&H SF!(6[o\4@ƼZn;Ľlf]poϲWV%}2ժ3V^3p{D{63 ҔG +:2ht9R$9hkrt) 9w ^>/Qk:Ğ̊Sur ;۾moﯻB-V9Ѫ.\+˰Zo~;n12 +gŐB!#3$S(8T $ 2*L>Aa0,Q$-W 5׾!Ӝph5Xj(!zHinʼY2v6LbҡqZ&5ei<;)qUZZMy7PK=ĝWي`Hg\5ur]āHiD=)+=H_ "S>+p-퍩]?vWa+bKRW&j7ٍJ@/b @5٤:o@ZhX05+<9ۮ$J@m^hTW ٝI&1:_\ +JH2ՁHnHΰ,Brz>O T7Lf=YmIT71zGZ".N!c;[VV`٢ vaDp"ɨd:2c~ +IM{+{DMS4<0D/5jخ$D MH7E 1D/L,x 4^tueDL0)ƅ:=-ke"f8(#D[(GAR`$ljflǿU#Ѥ_S f2aGf9vGo$a bTPw=j&4u900*sPfY\OgF(6mILhrQh@"R# y ++|kh 9qOQ(cӬ*'KWENnL<:YLb D(!2OOQ +DJ!F)MDD2,%[f +韴,C’8e8= ؛w JfsL;Y`SJ„,Sh +x+a/BgsZC?W0 -4e%Iܳ+=}?($ OaFTy2w.d9@t *WsD+,GR[k^fk` 32M{a +Q_<,X aGY9V<ύֆ1ڈoз9'd )S~TWN +MrrB q}Й1G7UBJ <ЇQX$, s^{(-}Pٿ Gjg[/ݹۅr/Lg"iC iz(ʛi!E>E@;*eUϑe뭷fAԟ_);P^"iŽVP @.5C& +O#T"Rjn}`j[|5zQ5L,`uǀSI Zog,o]0:/$]}V#a]YgX> ꆾ^eDV6U]c-%HR㷯N /j׳f$;!܄D O3RUS"8 +@KTr535.{¸%%#9y v*U>7$ےbnI>9RG]Xw%حۡtv~Is 2bѦEOhNź}Xe:q!-/K(+ÁT,u.qYw/(Ev DӤ?So]'2B[$KУ|UEJCNl#*zps~ +'k6,.WEN\tiI2s۝J2.?@97a쁁R^MH>ζ%B:gFJ`ۤ#=rO:( aa#Zh )u%S,s4t%Fb-yҵ Ƽ!ۮ"~Cy'+kyMg +aG>dKXJڴ@A=V<~c2f6aN1k2 oKG x\6dy1Mb?jzҼm@#c!?cp]qBD/%` wr.(yI>DD-e`4&LdZb;uY8R>dKRi5@MGhY +F^4c!( +@BZѧll#-]R 撟f;WAyűddd-(P&_cH #Y@e =$Bz+``c|'R4!_+P4N7i PeAR,,D]S.[10ѮfVkf7 +#4Q14ۋOw[N4؟i?(i 2fփ2%*z[dZ*IskAIS3{0aLx"4f ,8JDO'd+]Inޡ'JZ4WFnR\XnؠX ' rw5$#FBVdWTu+*c:[^wМW͗0d&]Q1+'M;V2v13i#8s A՞(`6,ؼF j434wnI:їus~?8s/\d<$8>"p~!GÄK08l* |愿) 43[IM!紡TEm;vĩ7RBdzV݆bBuIC`ϕ^PskGd⢛}eF9#3)oBhЄ̀Wtb +/9(Se +9I8nк1(gaXx!j,KK]JFcx%[.[ĝǙt ,(,pVz#.MuoQ}GR"QVHn:{_'LBV*Q5<7T^G/#eT딝pFe' 7O|J_A_[- cܘˬÆÔQԖS0Y.\6 smeD쀷Z]6V Ԇ_NK -`\]+NJh +ӣ ۏ0w`3=׍RVD̢} ?:LUDmI6I.6;zɭuRVѫ3D0(Z \f|[DY 'a *R%Pp STF]қӒBHE) h#' $Enj穬Rwp0Xx=dήRGJw9 +|ـ35MULM}N#,!~-/$%S3YM4bnf"|(;Vb=#v4`*cA2ˣoՍSH,:-4 ]RSf3 pk-*`%%6g.ڊωiX4ѱ:LDsr: \EĹg[KN]e97&~+03!<76f/ŧXъgO"_KdVC(M~+zȚNL݃?ԙ8#ݚu#;pgLuF?Yfwk6XuP8C=Wrǀ3v ŊeCRªWjL-7"@hCCyn_ZN""2R̖n]u&!H@Mܛ(,6QVÞPAm[TrЬhAA@SSƸmb^^gkʘg |b#b֦̔#%aaÏ^7[aGC\YK6Ҹpt#:k*AAVGHt1Y%.4g%ZqűIQ#s;ey)5 97%ٿm H,YmhY4X жHdïI$1]}6rǢl_#aKWpk7ZLr5agךzIځ%JiTruNۛ!7+ybLzߍֽ*+Oy7E9/(JD q<ϹP^ ]ݚ]SnGOaO*Q+]0>on>.ZA8+pMR+0B= X} XTK8o+HOUv- CG,CƠ0 +.(?D"|X' ͓/N  ȟ鑖 "Y0,Iuλ$,*v p&-0IJn~CsqUQ]1̹<׻lgfLdž`‚^/L<.a>X5S)C2Ƈ +4Y!V+chCJH#*uArL>҄ A!n1.6[DEI4I4t 0#▀+P4.bGtb'x.yѱȕ7:?uDlǫkHm[gRGs/=ɰf,Ю߰=zzs 0v/8#,P"pOc-&m(&uh՜"c +18d5z$UJJ@"$I܉n(aT}=ε*{}RF汼aĞ7[ۡHfا¼ӬIV emůFvI 7:.W'!]x-5"3/)i =N3Լ{4 [qetô՜f NdLPÁ=Đn)9 b'$Zv;I 7pt޶mo6zR}xcY2W< \=ä! lڠ}. +endstream endobj 25 0 obj <> endobj 36 0 obj [/View/Design] endobj 37 0 obj <>>> endobj 32 0 obj <> endobj 31 0 obj <> endobj 39 0 obj <> endobj 40 0 obj <>stream +HUy\~fvev[/ +* r(Q(@GFc5j$֦51'[5D&M Mw3;;̼<52($2z 9R&͚a<^@3kЯ7405k|FTg@r'J]QNK%ܔ + l+ Kn~~2rf }(@@Ĥ$t7}ϰjr Fƫm)ݸ^r?YSs*`Ǚ씬VNJb t"JEP~u Ս8:.ϥ<9RU!QW=f .[ܸ,S'x4F[n_&?u:fy2prY[g,ųi< hhh_c|wxXXB, +D<k6c6l:+C.A[Qw!>8R,_uq3&b +4Lfn֛fos3s+b`d0)~?kZZ۪j1vR+j_iZjIZ&Z2ۅ eUXy^S|yz%K+tۅԙbFKhD ӾJ EU"zԉJrXBؕ'O*To' 5DP j% 744-45n,Q4Gm5';QV{5m>/Q_PXҝX%j]lR֣f5Ϝ廷MmU%g^+t.wUw]wo#{Pg wNENk򎷮(?qq[8V9pG#WP;jk/ߨ˰þѾ^l/nϐf3v*_vv٭ﻗ^ +$[#ݯ#&S"/sq˥GȭE6@t oC!!p9|Cl Ke{*' St5F^ø=M{Fc(3äY5:Ɏ]yHɱ$~%Ql}<籞4[)0X6w,Y"+dC$ӇJSϑG;yr H@g@7K88p|8q=)^I)i?>@|A8="p?b( ׈E 7o1kkcQܬU$.q+&oH}&fLcdGLϘ_RPxlY\@. +`.xDF+Ɛ"^%o PSaDH"ERs1^ ԂZR.&ğ"ڊɤLEB$,*]G#'f)fF#fS;K>4S.9b.| rEuoP'B +,ޤbEP(uK(L,nb$ +XNb,VRQDUԓzj#P_VoS?XV멿@D1  "&"60D E#b;"r]?fAn{sޗni;DЋJJW)a4c#c v/'+Vp{Z_*V7;Zw ܑ6r'ĝi3w܍DpwA[h+m{qo}h'ݴ^Gxƃ5ᷜ8N 4A R JP.iimnl~[~l BJ"-f-eK2-ꍺ<4lE[V!jP[ְ5m-[i*Vkmhƶmj1qao[cmk={>#ؕr]W֕}*B0 b8#P#4xU x/a$^(b*FC;WU[x`<&`"LJS|7b$ jwbSJ*LPVF9('ܔQ*H0bTJUJBU)jP-qp-u.pa ZXal 043`&̂0< `!,Ű2X+`%հ6i8gDV;`'ݰ~pA8Jpq8'Dy D"դ2ROKs)' 4R_KC(&+}}@9#RCjIS)*UTfRL*H#)ť4lƤv%F1jҘ&ƙ&jʦ 1ULnjڦkj2&i2|.2.eu]6WewLlq9]Qs]qǕpi:rA'gΥת:Lp L%i 5uZ[h]OmcF:^mNf:YmSS-uZgg:K:[ms _|.PWX%.LuvUIWkg]]tvuMAFn^E{V۴n~CNQwO[{gݧ~a:XzP!~i~gY~y~_E~_e~_WUTP#jLMԚP[jG_Iyc/ot1Gy< Os޽g4%~5C^R!ڪ1T +Y$hL7\ ԦKupESnuĥ彜Db~_|{sι? <]M'qqZg qF 1T b&>el !RϋbX ObP,E*6Nlnx[tl!eSqAQqQT.2B6ⶸ%J Q!DcEl,_MS. -%fb$+# YP1QLs1O6Y"mr!w/.[{>lm+e2TɐKJO%Uy_TҔ^JoyH)P +ؖ-QJ<;lưQ,fcXrYǼ, +X!&l̦lflH'IޚiVg9ތuyQ^yU~-irB;, Y*d)+-y/󖼕Z[œ<'-l2mdxOVc0?98|B\q"W*\kp-b\0 ?Ǎ|(~M|n-XV܆@܁;K܅q¿~<?cxOAxOi ^GiOc.>>}@S4qj6ͮ ދ}x_ޏN.Jxs>w❹o`831 Ga6ƿzMvU7RUe*媦ԲQZ6Zhc +)zWqW +Bm^nii%Rm +6gjmV[`[ՑtvwXJS94K.9n+695.5K}pwɗ`ߦ65jRw}cZ%?Rr jZP{4O WVA7*ShI=hJSfii + F@$3`$dh yf(BsaCx@H63<+q`k*fI0:2fG'HdB4BgTϖgM| ~s`u|!,>%V@s,d9s<2I *ugmiMSTTJ T>0R-Eji̬O(SY<U`&0yMLs-,ߓٙjE+a2_N$m (EYF]cy/|̶ kysH'֓dVa_J=mBv{`/UI;a7pҏy(pPNQO.e +uˠn]CC:f"SnqN$Ē=#+F +o(+9} 0!MSe`W9hF.I@*ޣJ͉30}@~}|^i=Sz$'u^»]ݝ17^_Wor.mQjr7~12aX ԯtOh(ݦLG7}ټml1_].9MKU"A#!vnjRCBiXVRW%ڨ7j޼yo jTh>ڶF +}"1pHήJ CL1^,Utҫ(K0!7gC[s 8v>JXc b#"ם|z]@NhN]hoPSM1-g(0^H{# j^)#gh0 +uU:Tve찖QUD{vuhl ̏F>IlQi0z/C/!@dK"(3 +!* +E .\~XH^Q Xp +0JUT"d` `3`A4~&WfURmSp `RM(rR3 t,cS.?O9Fg|'7r@т?cUaڕȧǴd )腇$vng- M܂p+-l,ZAi'H%2'/&B]a u[ڝ9 :q$ܩ P/pp2 HuH%E ab24~<3ث.ߐič x9췇d@܀ȰN|j812ml@F *Gy$caˮ6@x۱9lM$ \Шr) `@u.Umӗ%PLQi. +hNA3a f ˢ#/cրZN;v2!g QIB{l8X$73\3 +a0cxSڱy]aOϔ.=E〫RFH*ZDt 6n.\~LM?\Û[k-,GM _Gu4cJ0;Hj^ZSywy@$Ax}WI&$\Vi_<!aV͕◗!Nu+#Qc2:iCdMcA|%9aQT;(uuP Gm w_^%#` הF J!IhUؑ +0B7 HH55?? + 'qDJCB U!dVta0wFV0 +{zYO [U&vÿ(XRK&/2:1YqeiW*I@@bӢ4zMk.((6{It"m%M] EctF?;1:wdOoyD\j*5]LҦHȉV^uEdPe`r/&Ihi_g*x|b6 ׺JC],w,l:%^;KKgjbHpWJS7G:rm5^8Kߝ4WJۗI{ I@VF rwơEܝSd2vۑc:moYG40^'e{2 l`w"8a S+.)d?Vu8繿쁗:LJpm=\&Ayjc7Nr*} +_wbfx[^u.mO%bWC܋N[`>mk%&BBD_L1UTG}&w:ZssKOo +/'GrV yR=B렓FEUdUe V 5[A&#¼w6NI($4MMZ|/ xM3_XQr /}@m_Vcj&B3ct i d> `:x'(wGtК/hŋKqط|PUWp9%a}HfST'Z V(Ys9vFŢX_؀OwDcٺ2ԿJO` bhX%, l L;>a*|$Fik_977psewƕ UDSN1eyZ=sjE}ٽmM [X1xnTrx㿼y^3lSN% +[`\W PSg\$r$!J8#PB'ZRV-SeuujuKu\;=f펝f*J{/`a}wa0C#Xz\^⇢7 ]|cBG_d{o~Џ;D]/v6w~zJ2Y^42?yP}缤5A>|wjGO`fYOd **d\_3 ?)2PP/ttSx{En[nˋ䝿? t[֚lGsЁz׆/ΜBKF@:ʪLAӈr($IgIRIK%haY$}99}q0( +V@I8ΰU{l].v'lbxgDiDj=~ۂaؾsz>8_Ye'r4+ע%A`uW,d5".Ͼ##x4dEChl.:$kr`{T۲$eQX*)/w'YSZmon}ˇW 6ƻ1[aU*V_wV-Y`Ӵ>wgoGK[.4G~(!\v[gRLɞ:ЙWܸ͕%Lt\l9J^ވ:ЄBe;x$+KW'⃉?R\#1cۉ1~ߓÈG{Pw1`9`7;j14̶Qǀ\K݄&`VBd*c}G¸%q:>C{IP7hb UlQ}2؊}XjfیynOSBSeB?a[Ӷ`q˃-e?Y="C@v/]?;ݲp5(MΉ`^gP<^`ltwH'g $\ lc S y!!8z%q`wgmh_RgQѪ=%zZopdi!f"BI15EɈL#Ȥ):" K#gm3LdVIS 4>bJ<Ε=#\`ermsRPsjyS_cxw.;;ɩ߮0UgDy̫#L&lR.^13ZeuP0JG8cų[ ZOD%|P08*Xaq&ƉG|MϜpDjMU#z|śO1O'L+|Y$ϞRrArϕmk>9lo"Cs KHC* +)ÁdTJ RȨT.)$'U,*Ж}nddUћQJB&"T$@2lno.'/ZO6|`hz( ,'xrh`:w}$HmB1Z*S!+.`?]tpp(Мlt67;3 2CXeue(AV*EKw 7!&.#Iچ/ +Po'P,A_MȒIW*cLu(r~»LAzRt.ع[pw+8-HZ2xaω¬2d2so׫9NrOl1gyVE+3m1nr8bN&O L勣0 +9sL*"RRF"/GaXU0*"@G @$ӟ۟?qOvx ~O +\ju @muR\VJ18j?ҫ +^^ I 7 @cHdnJ`uЍ>?NqZ]RtpBQT0YݙN3uv\SdV,y`;+;̗r{9ߕo!Q  {S/])uf]4h:heUc> k. +S6M2ⳝV-&"; YIq*3}q)4kbկ 7pbELa!ӗ7,8%ޒ -e| ҙ vR3bm=jk^AIڜS,ǘ2 ne}y䬈y ~& E֘ 21UHLZr/FRPB+!1l!"[*!l]j(48@^)RA][|Iy,πJqb+hd_ +|[z沰XDbqnx 䮫X_ܜH; +6Qj][$tDGZ>MEeRY{^KCk$zkPT9aʤ + I|5,bG>Vyǣ yuQxw9빽_? +dI?5 2p<` X׌q4!mD:fL&2IBOPً™Cmo*}su+rsfKq'`IB]Y;151hww"EP*Z^Ou T64˟>Dnjp&*46:0Z3"*ҧ A|E9 N i,{)uvFgLem{-qox\GC떒C+ͩ+6ǻu.JƱsr[-ֹe3F׼oVg;22VZ2\6tZRxJmҩ*7^[LVZI 3xV &8򜹻R^HhXKv[uѳܟ̎it0ؘ-^9,2:VÛ J@BM TF!tD"tՈ@vG Ks\e>~y +-?=̡P}<1bU9|XFKU^ȦVl._QJW*92{r,Nj',Pk g*sa{ypa>fkN9:n J3G-Lb Y?^ \VfW=l/L. ;ɰ: ԆF3PS]4Iii@4xb"Il+\u5ӒP[`Ml]^)Ü`WV'2G!ˁ̒ήanYtxFv!vScA)(6ϠwSD)u2!׊f%&]v$;[ތ̂tsR2Oկ6C %Pϲ.1,Adu:cZ:HExzH,&fF'jK,%=EzH{r0'V[lndNH X  C,p 6:MfF6ҢtrW5!#E89,,6!1<]yM];ݸ*3 ΣǏ7w9_՗a 1kGjP k5 *GQl&MkXFT|n!h(oߓ8T{^|!H%ÖdȒ1U)̵qk~G== :oO:I~HN阤?I/EC-]6EbK$|2Do x AV/*7YKYu8B| b?)/o'|^` CQxsM>q)]#>$Il=BKa _ޫʫ gRud +"TMB7*2*N fB;JƲ,1r30\B &fơ2p42 nӬ><~_@<׌!ACWx}!OKHɷn=MeJ\Ew37&=b#ůRDz6c$8tI}q +:ts<[Nټ}C_, >0֏,}]ԚI1{ +-%h:9 E<{URǸ)gn؆:ضu={&j~!OC7 ѷCqSUm!*n`W94,q~La+=9؟ؘ(--E㜝}1R;$6gc]03wr/u1z>_BZ-w.:q8.{ vٱ꺖^߷{9_gTİ4a~ \8WGuq3:1flayqb-;`{9/ƏA#u@V϶>& -E'g$o?~†Gͫwg} 6q:j$myN }lM!& h^s?߿~otz~0CW:ac*ĻBH 1WO;?];La o|ѫD<}HPW(!{+Ft0A):!m6>]I{/CNBv;Nȁ3_a$ͤԛ_K!9֗@~JEL&e< SIVPAt7g@-rq`j/5Ǐ~9ɯUccqė |>@e:)z7ϴt]}-ĉ5&GO䦷~3w>x3kMtu_(2qmKKedgƘ|Bpξ`b\&|Cggnk%oSa{ ^NoD?s ǂ{]!I~zyn,cfЂ?lQ Q_4j5yd4V7L~=>_nMdџrغ+»0u~~K{!Z{kz"&7yO9XۛgHYU/n֙z觍tVеQMej&~3j d  wd.}x{:H&=7b }._xs"O:h|䛢>& 5Uȴ!:n}Ƹ<1ٺX Wf #S F ;yyr9w߹r@Uc.>?g\=x%DgM!Y-% DzkRю %BtWzY*eo4%1 bb7Σ_l}97]Fmg) vD& F7q~jbvrdds<΃{#Fʑ<'߬<<2qW$@ܲ$l%Xgv+l-7tX'd|o]t9{P4ć&AYtk83AG5LyDޏۍwm"<27QA^F0#1$-:-"#13#*&8ĞunKKNKMe+96-KKMK y!-y=)1^+qDb|@ 7z,]'ӻGNw@^Ao-侶_ `ߥ7?MsbA[¾^f[؏=g{X{c=G;Ͻml}\`58q9ƞ_aWPY7,@/93Tp l (I|o<_3Qv55O2&~4)ջMKkj6!h&8]Y~OH}d8>8r&QwpeyQNH.Ra![ԃsBAz[jԧ{xU/ v +)!xpmlQw~S}ep2HOՃ-Eכ#eP7TVVqkJ%K^<7#*mS0EoSJmPsdC]PTIj*UEj6[N~!U"P5*רv=I-Q*f7%Ji㔬dnUijջHp*<,[-{GSPm`+5Pf;Pw gtz^ꭚQl"8;+UbR/F)U}q| JcßQ&(ϜߞrA SRJhJܯQt_*HJ +nUE<}NX꨿TQ+;7qE=OZGkvߺ*=wwtCG;LeŽTW5TmTCR1ʣ*ī4@yTo:ƅJVbWIbK%DlK-qp:f,=cN;k,l{T[^ٻ%k=%;dw3kԶ6txܩ4kۓ8\`=)q%$Z=qqGwr;əˋb+괦yq휞Vw5-1&xRcRɮ_iV @r?MW$'[SO|~~ح$e'؞뛙lTw8cpUXXZou MBC{*D'G%Hܗ'qWm[7%nH\&qUeK%.H8'qVď?H8%qRqcG%H8$qP~}{%H%Sbvm[%Hl$Qbzuk%HX%+RbreK%H,ȑX$w %% %$J̑-1Kb $JL,1IbD$J-1Jb_K Jb$H|)_ ~}%H)]]%:Kt(.&*pK$KHh+F%( /'+#Z)JD $>Dc$>@!LDchFK4/DD] DD[u$^-QK5J"*QCDDp7~*V-齱K;1p}d|k꽰o`fShcn9z7ԲXgjzi3p \% 3Ի` [QQN27ݾz:´;nq-ɺ%:l8[# miJoaidsКٚ:šjMLo[cG#HˌR(Gil6#ToՓAuQSTW ՙDuҩTJyT +妒vTՖjCD*X*jM9VTK) ՜x}H}@9fTS ՘QS {T}]*zަQT]NSa_7:TmdkUzz( R5:BS6EIתQUU?S2{&LU=;2UjB"UQe2TiUz*AQATQU*d}*HT>*/ +tJTSx  n + .8΁ NS$8c(8C 8}EX=`7v`;-`36 `=Xւ5`5XGb+r ,Kbw`!XPe>\0.m3t0 LS`2&L ƃq G_el!e  F`W`(`ZJe1_߯MqI\?]+RJO'nEi֤u[*WDUpo=ȥuQSuo[[N-* <=w=C%ޒh1Fcfcg6X͑cI/ Q()҆()SzPkMRzF/D/ ]Ǝ_㜼<)УRF7rPZkr0%5,1UM9 r\%rg]Q5VUU#]BQݚj֫- %Z[Zh CU/49#g $)K _ o +/ OqwqըmMu\U\e\E\yC%*pgpp'ppGppP {؅؁(A16ll&l:EXXXXXXB y9lBfbcb +&'I NBq1Qa!, @ @C_Aod'z'ґ$# 膮ÆXNhvh6hVhC,KYurj[nS9 +endstream endobj 38 0 obj <> endobj 41 0 obj <>stream +H|y\Uǟ?ALx_.n0nIj +# +XΌアHi:hVJ"z9`Ss~g!&"W%-Ѿӏ}\=WGǦdy#0!S%VDHĴHLdDK&==T֑(دKl?Ch[&eOuB KM_,n4O1tZN]Pa?jY$; b)"E֟FDɂ[=A3z + !..nԷ׏/? - Ncq>M<6=oڽiߕ J)RfK{yPr)k QвY4i4H֖.AK|&=8H!Ew[iPZ*ו0%UUreQ)U(/)rY;oHtHlHBȄv9O->_|عc`b;kckzUU9َZYt6p* "}G}~~\@@c Q}Ф7%HPCj& )W\itL^&UJ2 r\ 6F%D%$I(JI#%A{#A":ŕ;:`*Hi`?_;. pL$jN j0_}iG'D3_ֺ]SU-^i{ZKۭ֭۬"&kuuu-5{#ĻX]bѽ*2<GTA-U7w}wZ>QO%Zfj&֯SNj[+Y([퐭ж޶ζbSw$$ooZ㿌k|l|K +QCo*Z*s4ǔc5537o ͛E-b6vsykAd؄"l}-؊blv`J vSnEFLdp"C8#_(1 |N qpp\e+kO n&n6A=<#nKEQ3:H)a:Bt 1adAǑO":e ,PEQ Q%Q +QeQQQ QfڣARn馔Etr(N):Myt9݌H2]t I6ݡt>=T@1=CL̬Xpdeap.Ÿ8\Ks.vvmرvo'؉vlةvngؙ6Ml;ε|.b.x1'Z_5T S5AMRTjZVjU*UmU;^!o:riuN]R uomw}~|}߽z7t7tn!#~O)?S!ER:Dwi@DžHX "TR[KcUG*Hi+Ei/ tnC5+K/D/UmJzG/.#e2UfY Kd K$I"u]G> 9,,ɕSu>YmyrV3r^.E$\kr]nM%ܕ{/?eS$"$"½n[ָ .%n2\r.ϝwuw4pd~~~ `WU{Ϲ=Kw 0n+9CDERnPBD1@yV66və%䑀R@H1)!\+r]nx缋~G~7[N~gw{=^~o?ΏDﲔRFJ9)/TREJTRCjJH--&!%7t't!P' w:˜ +g3w&8Idg3ՙLwrLg{޽Ͻvw{}}>vO?ݿܿgs_?}v߸ow2QF2d3Mc&g)d +")f)eJ2)gʛ +d** 2LuS4&2M 3&D(S5Ѧ1MĚq,1缜s.ȅ0\s .ɥ4\sȕ2Wո:!\ks(q8Gp$Gq\c>7ˍ17܌s nɭ5܎sȝ3w܍sɽ7q<'p"܏>ȇ0| >IũRTPY*GUJTPU +jTjPM +E)("(եhG1TPCFԘPSjFͩVԚP[jGuNԙPWFݩ^ԛ((P_Gi%Q2 JA4(2(h lFiQ4XGiMI4TF)fLEiͥy4BZDi -eVJZE{=Ϟo/5z@imi }@[C>m>Oi}F;eL 4UJT}T_OWTJVUJU`UTYj05\P#(5ZQc85^MP$5YMQS45]nv;Nn{^:A'>:I':EAzN:Cg,{HgazGQ2]rd̔Y2vnC{i}N A:DctNI:E2CPmfiFfz~1NkbZ{} {c41+xwe̕y2_mwϮkG{=Yv,xO?ޟ_o{^y7[,Pp yp<#G^C~Y$e,e\VsY)dPPEpE1G D)Y/PePPPy p*B%TFTE:j8;]뙽Թܷ#CuT:wssぃC[rBNJwPA-Զ!aG":hC ꣁUQM-ښ6hvzGtD'tFtzBo! HDE?$!T `!D`(0 $10c0 +{ +11 1S1 ӑY9yXEX%XeXXUX5XuX ؈M|>lz׬c븵Z$ۭ&v!Zg_K5× +_kB^ʫ@@@3w|C^y+<˳=i|1a;>؁ϰ{9 |8C8̙888888pp5e\U\÷p&~ ?2pwO4v qAEqkp5{[ƨь1jcƅ(Q&:$%j\PAM3zrN9S֭Us?U5ukZjmG葖--,ծjMS[\ +*a + : Ax UQ AMBmԁ ;^F^A=G4D8"FBc4AS4Cs@KBkA[C{tN:#1p.XtCz} Dp#)Ta8F jKW{##q0c0Y񘀉x S0030Ϙxs00 Fxs8 qIO3L}>JY8}>AO'S4}>W K &x + +6`-_b=l&؂c|؅؃||؇|8B 2\Q/=[k7QJRN]{"@ `PۜlN1!Gcq|R>%W5\,ߐoʷGǛRQ(l>D Os~_M VNR5 +L}2MYDr_O4zTPCLi`$ЎIS ER#ԄR3j{e#o+kLg9̷c*3],}Gw3˜_kC[KRaZ FhZF1C,V(K N0c:8 sN9ϴpI utS zK-QK[;zO^A>ҴE]+R7SZA3|u9Kݧ~Zm$m6f17Io>B=eS3ed[Vjf5a\Gz 61n-0pv&Dq}B;wg^Kq?Bf b,/Kt1:N'$si:C_1Mt"]LW*]c,t)J6淾qgO?'G!C?#K[J8 b:|0N eeOyEkE=!J+>ExR +[&Pϖb,⇧bP,bi4\!b;{A{TB:(I4AZ+=cPm"ɱQN"[ki|B~wƳG#1Qޒ6EcX@,kM+y9UH%!H.L j:bnGN(!. +u ddHL&bs8foLq߬b ouȊsy$1{Mx@t)t2[uX$qb G-M"o[_]1T+1VΊhnNi&N?.kѣFff2tA\߫go׶MV-[4oִIF ԯJ:k Q=Z՗Vh_ٯb"YW/<$44Yѯ|ߠ0PCTynR F5_z_i! =:ƻpp"А E +;U9R3 zt1vpޏpŷv9 !,YYyU'NGZ ϰ־*W+C m{w$Wh*Y +;nszܞY)vɋd9ܼk|"[19.Nr7'cHzҍ|b}('7{w=1xuN>f:6bJ6cnve'Ǝb7kMi@M?htԾ@DgcS A*m$H>T"4^~άT`=3s=su8ldpђO%#w78thn{C4):/4v6Q +;_aNyrE%JEYY^eqĬϯԟ3PN ?RґҪ埤}s m8S!cwK/KW +T9|aJOZsj HbWPQ ^B7c9B,Bh6Ѷ֗\^hOo~Z!;hm,7Ru@ HH1,A%Aq᩹kң$04J>ٖ'EX;N9#H)b)X^@ F&KfI=MP7DFۊtOl@< Ou\ݚ¢*W F<`*L4 򜌖b i\$P *0bȊKUS;JBJԪj;(De4BƦ%{/O)rU(?Nƛ5ZrrFѼAoe֏ qLc%.dT+_D)Z3΂c==2\) y!4t4bBg +Y{W.Oodz|KjNuaona&]"aA6n]*@KБG*cU ǽ +Cţf\t?;#9 7 ¹n}1& =B3p#4yʶ*s8EgTa+C! Y<%w>Uς2nv/{<h:]F^dȣ #4s!(`L1~ >}ϓvRxEfOA#Y"D{DN»e~ nȬz_ݕN#u8 S`XSEDtm7>\0t˳⚲e1v9imOLK$wqllm;9Lv%y*=s-ZԈhaf0ϊŮ|O'>Xr+/ڵ̮]Yٹ^\~ߜx?6ҡw?]OמklOyuFcP"JG6|$ߑggҩ 2YUF'JIea}yZ2_1wWogx 3#._{W}Kѿ'~~/pm(y}y[`E~o9}ۢC8\1˾ΒBD6S]L@Gڎƭ~{>ti]h9&EgiNO$ lC&af).)~Ýl-E)+C|T}moDӱf-h.&IY6֪qkMF B*%wTUF tZ/CּҸ8l,R!|+P`= hKM +.'4 J* +ŘV42C nXm4oH +9-ݾK)+%8pKN*]<st$vA;2ggQdݏdP: ;z=zlӆ 7?:5͝Ʀǧ'/?o,7.}Ç>xW0yh0 Fۻp$"j_ +;j;jmZ_TZD >`QI0-όg F֤3{B/t/ѰgG4+/vsj n{Wj%kɖ+ɶֶl=Vl#+`bDx瀣qic~Ii36%I$80L'Ӗ!?h'Ӗthd&qXw%?N=x9뽯sw]Fw@4x]8d"&&cJ*n5d)}^@5wOl|ǁ򵺏0ux yd,vOZܳBlx>l5 B9d%ⶄ FI1)ќ+ffiCֶ3; #]>iT#iN&*P(gc!~|hS6M0Gk>*fǼ׍@c&Gzّ +̩ 6#L +i2ooo'!ɂz8U,㉍łH*\yB|b1TGWB$6S +ݻZZ2ndov=F"z7?Tުv97FĖg$Ƿ>ݷ\ hoj[[9}Hz N5eudxAl 7uC$S q`DH5nGe +(WI K$|qDXNPf+| 7qXD*5;gIwBN~~\"k寴dK2+z}MF~>KKk@E;d*++(1aPH|8bjR*P5Q*V{|L7dH%Jn9URyל #KsKDB<.zt&IaM5bN3r}TkIt2aO)AP渒^չӻv2f㺇J,/{2ſi7֯}R3[9j$ǿrrw@_ 2j;Ug1EJ.{֣Mg᭶hU8u=CpMшB& E1~ +(!-"N"*hTF0L J$fgh2T俾~4JvZAvhHwSv&.zG~r'( ک\W4_ڐ^MO2_;19*x{Fݫ{e!BR GBg[n:IO)TLxY çVtJ/paa;΅tw0W b4Aw ?\e,,c|9;ZFP$ݡ,d$̐S2ZRZ$XI|"->%SJᇬ^֜? +na{fJ{xEg<\2@ ō/g[Y*!N?AqNB=\%;7ٻ͋ E X39^3"mar͋ty^ƼL* +Q! (Gh"0IZ2S^a$c:*P7 +Hxb%N6°K8ivGin`ϩ.>}g׳KMݢc\JjTYMs88goJ1v})m_%gyz&56̎w=;3׬}l7+qG䶫"PJyRZ)%E*)EjJ J?QAM' WЈ SU +I PE=g {w?j+_ Xr@ +X([2S7dA{2e{z3,˕H +h9]rd4ZcZ>L 3T*N<}=b PX;U0U<&s)eF 2*lAr僙 La +,~ HE?RH[J|NrK^p%?BI93==;71qнM*f+xֽ؎wΝsC Folߝ-\P;}ZaճU1Eސ[ӺCF"fOezmfzɄLEuH/!1Ei;5 hxhriZ2$T͌A#P3]sɩM,,ՆMOy+"X?Hk#Hq:Rqӡq:ҡs5CC:FwXqjtHlªز^8͞xmA`AhE\]-ٙ=/nf'~Ӌ?7W;̖]+;=;?MWoN[+}:S0=@މIԫz~AB)RQV)Ft?ǯ̋*:EUy܁VA ȵp豐tTzֺ̄@v +y,fX; IƎQ'UN3EΒQ]=" )n\VС( +nTt)*@q.H 2 P,:-3UW[U7 beSF +ܦC@(cqq7;Գp4%˻T&wy!"iWYrGת1%>7@b_3OT~vȳplFE\|ە5ŝ<7KͲgLQàqw ҙEEY 0f&4lmSy}:<'&1j@,莾6>47uJ%b4ȋy+OUr0.[}}ۖc,9l@P*:~ >ЩDC){Z=珝8qBe4Jqʅ +oI-B֜ae6JuI_ F$?>jv@sy1cIp+,,;GIJ{g^~+oE߷YiY+v5tli%L'Ğ}Bx,tp̌Ez̞Ȩ9Hov~i{6E*&Ḙ- ϡ9OqʭP9կqbRh(]z{Vim#b'Nat͕d)k[|bVa0`M-^o$Uq]d/ 2+ + g=ۏv"{4f-1^Uwgwpo$7C,0  2S2}P +~%"F`kg- [0m0C( QPdP)j:S4INs.I(mٳoc'k^(o=ZSųo ~M0f +SZr}d@d=X:z|F5^sM"aÓk=)'g¸k?apQϗXlxrPAKh-OmP-ƱsPHi tn;l(*Q|_*EP(KR[.WIeF)-VlK^>6u‚DD^f`NQ,>cz.*!-K9?/W-xXp~$_eA|ڞgMn(e'r㮈=Bw՗xdaA2dEֹ>\fK Aݖ |iș]Ä[_Z* +U+*ټCV2=i!ֳP]O'(.B֝p7pdd  Ph\>7\vM+hj2c쁔a^F;'G]3r4R:+ Nf}uIWq\OMḾg9:EExyAb5WDD~@<' +=9:fLfFEXOz[YG2S2$lm^,aCr^9d9ⱞQ1eL1,?%MY2ھh 'ˠ En˭zX2̪0:#e8mQG',iOAP8]bO*OT<-ˉ!™Hiu]'Ypӄ +nP_.$cQ|蒖VTdj&0w?S:VxqOĽ^7[J:)YАuO$))RzOKʷZvub7qZk>&q#"zq'ۧG~E.ߞ׺=o5ť?(s{_֋nem*]ŴT:hIc^#3i>0qg|g]G`ȸ . @` 0x gXg84ʷh36uuj;4Z/] =׳jHO5&ͽ]G՜ ?ϝװư)p~}7"~7e4mthɺ +,^Of~W`ƥlϨ!UfYoi˾Z?O8=Xy֡І\m<w1w_H 0)hZv:;PL}z  zc}zavq\u !Ϸ}O{ye!4+&!kwzzv䳣q)p7Fm<?CgREG` ɔ ɨ/FyvR6 |:s~= +q.#8'y0q߮owMcK2mݖSc/9+h}.K<+o7A1gbQ'1bx2ޕw>#sc~5;?:~U5{<}?S1; )}|0%jho}{֙8fASCȡ:ebƔ^_\w}:9M>4Տ͘_@;5`}sf_ZcyLjl]w~ kIr_%x{rP@#Qs+QT|wqx*Ƨk;ykw0;Ī[y1zCnq6.)뿉|wGA&)Qij:WÿPWk4~߬ pTfE-ӖeT*ƘHFFTA *hj3V-Tit0S#::T;VъAl f$߹M%lPww޻{9ʭ*n|[t~Ij|qxx<EviYܥoۏ'FnI<)%ǟr\bj=b 00g4A}(~N3Zc`%t4 ߛ3'ڼd_}-'->9-萦\s٫lg\'Ma2Tځټ. to'~&溰? >==:bg>*k8"Ix6{_aÎGr\$x +pkU+_y!<8x5S0%D0_*7}շ 3^Ws.n! FΏ?^pGɽ!Yv\Hl{YG}OZyKك XuQ;qX}̉Si^:t0F'md9+J,Vڃ˷ KnvjFe7mF FXijmn&Zmyr޷>~lng{c&c\߼av-`lp>M5"T:(M-|8)NgڵJrZj ݧNXsϣXƗļ]s4kM#ϊljX=ی5[yv5;u>u:V#ԱY9T&{j6QA>X}0\ WcÓX̱O+SR&Q@s2X.>gNT9Pyd\_)WSٻZ$wf*ri"ď +1ls_.čr8ǃ6b7=>(I{:xnHmL|-Afk#/{>5!'gl(BgqIZh/O-+Qbt1󂇸]\y'/~G; +112c$v/ۅ5n(>ލo{}dܾ|YޟޒF\|&fK&TrĭLSz dkq[ mM>%~p\ȜEEa>|p#_G}' +]p=9IxIσw7PeA^! ߋc]>M ?rm=7g~Q?]P$kx>}vs1po(+5|G__]kЏ9:lz&쐚A-%WW,ɽK<^E]u=ʧǙܶG'.]-aSVgS\9]p GZͯ@N9d|a[Q*T}'0!_F]e2ubP) d][񡬮+m؛?ҖmݹNnMj33BNlNXcW¾r>EsTSO=f!>2oԌ`ۏxi2[]'8xT&ݢUm3ba=ߧlcTt!?&>Wm>+jIoǯ2 JDȤddA 'IABO(C6.5)riRkwR_5pːZ$: GHk6`wѱ}b"2D8 ]enD eI9x@*Jw^r7: pݷ?!*1**RV'jd<0Q$JLkh;XcD;!h +#Z:XAkit?!L3ޱE].mv#nܹ{ISv2}hO\zo8\qx  gJYp$-223J~x DyPyfO☩&C~Kر}0[ +W}b DwL\XT_|xjܒyS +\[E}؊mp,7{dlv*[1~# +ma;{*.G{ve$kh?w%cfuJYӇ'wal%~g&Orw0ViЫ&H^eI%߲Ӡ?XGOA_|]a=}i=:A?-bXT! 񱊦RYډ,(#,31Cm|eJo+`%(Ÿ)E)1!hξGP-;}qkbk&ѦX2ݏ)1II~^͎ھO8WJj^!9U2{]4 5{ r`gNmjlKVRl *a%!ʒ& *{LO=v/m[);9wHyy'3f@:UQƫ9d^dYw&Bd㠃v)qgFOs</bOwG(C|T|w X&djB9VIF"dk|z[׳y*"5ے%^rO2AcHJ[z6~1^Zl 2k}TO}lq@u6DNՄn9fV}}1R=69/,cgr}OGt48QsH98_sEn^EyԸ(תmU!g7"{RhNmCh?lyӒ*}H-8 Y{vljO  Ăen+g'OGeq$!Wl+w̄{goqg޹%2(.2~ĝg#+b]Σ:~p[V}#Q?s$L1)򧐇dOIN ωUȻG}lry_ Asli9PMVwߤ4Gj9Ȼ'h~_B'៶ss=n|\ r+AKhxDMjy Ι;d2ܯ>kgPi,cyA|Ho P-WFsM1{\J>*onKKVT.<>!'͗KIqb?mWtwG>ޟ!ꔹ˂ԯQ g:SxZb8}yd~{v`&3VsY2"Koƿzl|r8&L7];ܱ_:ʒ܉ĚUrFHcB!R?l s -y9ǟwoMvӱA:c3=?Aebٱ~rRx9=K8dUfM>KslePÖH:~3^F_v7}dGn1DeROt}G&rj$k1 Rd;1 +gfK%?! Y^o䃏2I~J~Y&0]WS^ribgi؞u\,bvhB[`#ld<^6gnǑצřܱ3|ř?-zzr^f]^ƪJ.Jy]7h^D$HnԨ^GNw"r&KeNp`9sѸƯgzqљ$[ŝHbc2rqH1 |* Vr~E}]q,aOvx(&! K‚PV@^jSC;h[T]HF$3X_}1k5tL8ﹿ߂ĚLDž=;sQVWB||g> wr1Kb))b|2tND\EĥZFS,#jq> om0V7wA[(>gQYr(~?ԫ7%{p߇CC1{Tʹ C1Ǻ8=j.bU~geXʸ Ba÷0n |~D 4v;U(ǹw.1w9 #D)⡵COPH906lJb>| @N!zk,=K;mqԱO\ c wx_QDY6:bRD]KsTϰ/w?Buاۍ:3w=R*|iːgc*E ٮp{g6}:\m}JS4 >Pu]vcRvR5,z@ӈ_\|Oa^^1b]5ICcuvJV.YjpyAE,w<@|b2T6ToQKvPGq`SY_ +]1O[WLmns+EbqS77VqQN`[Ny )Ǻ G6N.ofm.MEG{וh`ݎG Cfܳ}&ky>-b{Ѿb$,ʀKf_a_Zo=օ}numz1ݑ|Q|9*7@e_Ũ:yǫ(mn=8 1?D^P`BZΰM# >"{j|R(d^E|~In6rl[lkNn"nٌ>+p.<_"9>*i7v&j]Rw+}$aɴKpHj2X==ЫIG]%$ݡc4^&Or)2*GfZ9\N!,]"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"HgqvTR1$N{!u"m^( CNrdR:*zF3Ytu#rd uC&oQE0j٬pCˆ+<)r~*F6jvPOhR0Z!5\< R~7zjvQCY L"kMW10R%m[0z7 +iXϖf$p\8BLG"b2e˜w9${p_XnoLbٹAYle-riJ Z,[*K9ŒFċc)-9B(6U{ +'Y,M(1e^̿_d^Y>a613QF4Dy@hI Z7O{g D}iOi;kliC[|ũ=7qj;mףFvX~iGxO?kE?颸N1턴SGX@-`%8xU* 2e +ĎYy!T/)5^SZZ QGEFp(^v$XVLvӺ ZOL>N$m%84oZٜy2$cQXRMKs)ϸFkH@X"2ڿB9Ni_c8wqm>\T +>xwƽw]:ė*Cȇ@Ƽl8Jb\Y7z0f$q`CPˡϐHX:`CzG\34 +HISZw:k[O,QGv%@ +endstream endobj 30 0 obj <> endobj 29 0 obj [/ICCBased 42 0 R] endobj 42 0 obj <>stream +HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  + 2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 +V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= +x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- +ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 +N')].uJr + wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 +n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! +zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km +endstream endobj 34 0 obj <>stream +HKk1::5n(RC!8!qѮ>n)>X͌$[OK\=f\],Z~4jߙgK x +ɒ@1f~4e8O9i{cV9!n5*i;Cܤd 0XrVf{z{YߞU507&Ӡ'Ź84( +S}td Y24!zޣk1D! udVǐc1п;O9K_?fvI +endstream endobj 33 0 obj <>stream +HԗKo0 :v*Da m6m'Ml+FP&VDϟGgW苯VjkhfJ=ilv`<тqZxPʃ"&ğ\,qH--ubqbXY]7V[hM": FtVϋ/?6ˤkuzk.k?`L=I}&H0ΑH&(P:/ԹE>lWl4hWh}ެta ZK=0beiKwaG-_ +l5&%o}b G_[H@J7lț7GӉPQ$G5rd'UXEd=WuēTԥ\ӥ(]L[i+H +֟|2 W\r!8oT$Aސ3h(B {#..X{r8ɽGݝ4CP\(Swh2pIQQ~K. Xe122@ȉ֞NNVG=A}'4 tǯ}ȬeܷNxvåP`)aٍ6*x#oGŦȇC:Ik6i2r\ {"-V{i2&o+#{Ņj@\"qD6)ͩ\|2 UQNZd +BgV&LʩY}us& 2B9_^UZ`_ +endstream endobj 28 0 obj <>stream +HԗMo0 :&@ʈ|  P!Iפd: +9ؖB/Z?25ɴT2r\{2*CɛfkBHY%"x +rLvxo:& ڤL.6r{b[yQ^Hh0gV*l_wN*p{e~_LUyX×*v1tz- \UObZ;.7**YTVګJ~u"BugFL7@$\&:jY2XgήerAں y@Ju0<+J7:4X[eERJBtVʴ.) >j /m]8RZwhm +x}\*yhoebiw8St?t6[У1 )@LQQFo 8|uG+c!Cty7 O(kѵ.xu vB͑v`hu SgMzjeG=ZԽjGݎv4ėޙ.ZKHȤΐse, mC&r ~`]KijH-# +sG^Mv]dJ.> +endstream endobj 26 0 obj [25 0 R] endobj 43 0 obj <> endobj xref +0 44 +0000000004 65535 f +0000000016 00000 n +0000000147 00000 n +0000049675 00000 n +0000000000 00000 f +0000049747 00000 n +0000000000 00000 f +0000000000 00000 f +0000053611 00000 n +0000053683 00000 n +0000053900 00000 n +0000055637 00000 n +0000121226 00000 n +0000186815 00000 n +0000252404 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000050191 00000 n +0000050636 00000 n +0000261723 00000 n +0000313689 00000 n +0000051081 00000 n +0000312960 00000 n +0000308610 00000 n +0000308497 00000 n +0000262300 00000 n +0000261910 00000 n +0000312093 00000 n +0000311294 00000 n +0000051526 00000 n +0000261794 00000 n +0000261825 00000 n +0000281988 00000 n +0000262714 00000 n +0000262964 00000 n +0000282235 00000 n +0000308645 00000 n +0000313714 00000 n +trailer +<]>> +startxref +313937 +%%EOF diff --git a/content/GraphicsIntro/Ch02-Quiescent-Marbles.svg b/content/GraphicsIntro/Ch02-Quiescent-Marbles.svg new file mode 100644 index 0000000..4ce2ab9 --- /dev/null +++ b/content/GraphicsIntro/Ch02-Quiescent-Marbles.svg @@ -0,0 +1,99 @@ + + + + + + + + source + source.Quiescent(TimeSpan.FromSeconds(2), Scheduler.Default) + + + 1 + + + + 2 + + + + [1, 2] + + + + [3,4,5] + + + + [6] + + + + 3 + + + + 4 + + + + 5 + + + + 6 + + + + + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch03-Creating-Marbles.ai b/content/GraphicsIntro/Ch03-Creating-Marbles.ai new file mode 100644 index 0000000..d3e2945 --- /dev/null +++ b/content/GraphicsIntro/Ch03-Creating-Marbles.ai @@ -0,0 +1,1677 @@ +%PDF-1.6 % +1 0 obj <> endobj 2 0 obj <>stream + + + + + application/vnd.adobe.illustrator + + + Ch08-Partitioning-Marbles + + + Adobe Illustrator 28.0 (Windows) + 2023-11-02T14:40:45+01:00 + 2023-12-12T11:34:14Z + 2023-12-12T11:34:14Z + + + + 216 + 256 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAADYAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4qx38xdfvfL3kfWtZs eH1uytnkheUco422X1XUdVjrzYeAxV5f5h8wee7PzRZeWLDzGdTmh1bRnh1W5jjjWl/a35ltrqOw Fok0Y+rJMqfCSGUFujYqnD/md5yXy5pOvXENpaaMJb+DzBrkdnNfW8Zsrw20csdul1bzRW80cby+ rWXjsCO5VTKw89+fdT/MG90vTtFjm8s6VqP6N1K5YwrLGPqyzG49RrxZftSLSIWZBXf1OwVS7R/P f5nSeRIfPGoJo0mkPbQ39xa20N2lxFbpcKLrd5pEf/RfUkDbcWABVx8WKu8x/mp5ltdDXUNMW2a4 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 7FXYq7FXYq7FXYq7FX//2Q== + + + + default + uuid:65E6390686CF11DBA6E2D887CEACB407 + xmp.did:6d601e58-17dd-9b47-826d-deaa351f2a23 + uuid:0f3f6fb2-cc8d-44b0-80d9-aad3487c1c69 + + xmp.iid:d5e0e1a1-24df-8f4c-9817-d3a41a08c4f3 + xmp.did:d5e0e1a1-24df-8f4c-9817-d3a41a08c4f3 + uuid:65E6390686CF11DBA6E2D887CEACB407 + proof:pdf + + + + + saved + xmp.iid:14709273-42b8-4844-95f8-dd7dcca7f08e + 2023-05-24T06:57:15+01:00 + Adobe Illustrator 27.5 (Windows) + / + + + saved + xmp.iid:6d601e58-17dd-9b47-826d-deaa351f2a23 + 2023-11-02T14:40:39Z + Adobe Illustrator 28.0 (Windows) + / + + + + Web + Document + Adobe Illustrator + 1 + False + False + + 251.335674 + 302.907242 + Pixels + + + + + Consolas + Consolas + Regular + Open Type + Version 7.00 + False + consola.ttf + + + Tahoma + Tahoma + Regular + Open Type + Version 7.01 + False + tahoma.ttf + + + + + + Cyan + Magenta + Yellow + Black + + + + + + Default Swatch Group + 0 + + + + White + RGB + PROCESS + 255 + 255 + 255 + + + Black + RGB + PROCESS + 0 + 0 + 0 + + + code keyword + PROCESS + 100.000000 + RGB + 44 + 50 + 200 + + + RGB Red + RGB + PROCESS + 255 + 0 + 0 + + + RGB Yellow + RGB + PROCESS + 255 + 255 + 0 + + + RGB Green + RGB + PROCESS + 0 + 255 + 0 + + + RGB Cyan + RGB + PROCESS + 0 + 255 + 255 + + + RGB Blue + RGB + PROCESS + 0 + 0 + 255 + + + RGB Magenta + RGB + PROCESS + 255 + 0 + 255 + + + R=193 G=39 B=45 + RGB + PROCESS + 193 + 39 + 45 + + + R=237 G=28 B=36 + RGB + PROCESS + 237 + 28 + 36 + + + R=241 G=90 B=36 + RGB + PROCESS + 241 + 90 + 36 + + + R=247 G=147 B=30 + RGB + PROCESS + 247 + 147 + 30 + + + R=251 G=176 B=59 + RGB + PROCESS + 251 + 176 + 59 + + + R=252 G=238 B=33 + RGB + PROCESS + 252 + 238 + 33 + + + R=217 G=224 B=33 + RGB + PROCESS + 217 + 224 + 33 + + + R=140 G=198 B=63 + RGB + PROCESS + 140 + 198 + 63 + + + R=57 G=181 B=74 + RGB + PROCESS + 57 + 181 + 74 + + + R=0 G=146 B=69 + RGB + PROCESS + 0 + 146 + 69 + + + R=0 G=104 B=55 + RGB + PROCESS + 0 + 104 + 55 + + + R=34 G=181 B=115 + RGB + PROCESS + 34 + 181 + 115 + + + R=0 G=169 B=157 + RGB + PROCESS + 0 + 169 + 157 + + + R=41 G=171 B=226 + RGB + PROCESS + 41 + 171 + 226 + + + R=0 G=113 B=188 + RGB + PROCESS + 0 + 113 + 188 + + + R=46 G=49 B=146 + RGB + PROCESS + 46 + 49 + 146 + + + R=27 G=20 B=100 + RGB + PROCESS + 27 + 20 + 100 + + + R=102 G=45 B=145 + RGB + PROCESS + 102 + 45 + 145 + + + R=147 G=39 B=143 + RGB + PROCESS + 147 + 39 + 143 + + + R=158 G=0 B=93 + RGB + PROCESS + 158 + 0 + 93 + + + R=212 G=20 B=90 + RGB + PROCESS + 212 + 20 + 90 + + + R=237 G=30 B=121 + RGB + PROCESS + 237 + 30 + 121 + + + R=199 G=178 B=153 + RGB + PROCESS + 199 + 178 + 153 + + + R=153 G=134 B=117 + RGB + PROCESS + 153 + 134 + 117 + + + R=115 G=99 B=87 + RGB + PROCESS + 115 + 99 + 87 + + + R=83 G=71 B=65 + RGB + PROCESS + 83 + 71 + 65 + + + R=198 G=156 B=109 + RGB + PROCESS + 198 + 156 + 109 + + + R=166 G=124 B=82 + RGB + PROCESS + 166 + 124 + 82 + + + R=140 G=98 B=57 + RGB + PROCESS + 140 + 98 + 57 + + + R=117 G=76 B=36 + RGB + PROCESS + 117 + 76 + 36 + + + R=96 G=56 B=19 + RGB + PROCESS + 96 + 56 + 19 + + + R=66 G=33 B=11 + RGB + PROCESS + 66 + 33 + 11 + + + + + + Grays + 1 + + + + R=0 G=0 B=0 + RGB + PROCESS + 0 + 0 + 0 + + + R=26 G=26 B=26 + RGB + PROCESS + 26 + 26 + 26 + + + R=51 G=51 B=51 + RGB + PROCESS + 51 + 51 + 51 + + + R=77 G=77 B=77 + RGB + PROCESS + 77 + 77 + 77 + + + R=102 G=102 B=102 + RGB + PROCESS + 102 + 102 + 102 + + + R=128 G=128 B=128 + RGB + PROCESS + 128 + 128 + 128 + + + R=153 G=153 B=153 + RGB + PROCESS + 153 + 153 + 153 + + + R=179 G=179 B=179 + RGB + PROCESS + 179 + 179 + 179 + + + R=204 G=204 B=204 + RGB + PROCESS + 204 + 204 + 204 + + + R=230 G=230 B=230 + RGB + PROCESS + 230 + 230 + 230 + + + R=242 G=242 B=242 + RGB + PROCESS + 242 + 242 + 242 + + + + + + Web Color Group + 1 + + + + R=63 G=169 B=245 + RGB + PROCESS + 63 + 169 + 245 + + + R=122 G=201 B=67 + RGB + PROCESS + 122 + 201 + 67 + + + R=255 G=147 B=30 + RGB + PROCESS + 255 + 147 + 30 + + + R=255 G=29 B=37 + RGB + PROCESS + 255 + 29 + 37 + + + R=255 G=123 B=172 + RGB + PROCESS + 255 + 123 + 172 + + + R=189 G=204 B=212 + RGB + PROCESS + 189 + 204 + 212 + + + + + + + Adobe PDF library 17.00 + 21.0.0 + + + + + + + + + + + + + + + + + + + + + + + + + +endstream endobj 3 0 obj <> endobj 6 0 obj <>>>/Thumb 128 0 R/TrimBox[0.0 0.0 251.336 302.907]/Type/Page/PieceInfo<>>> endobj 126 0 obj <>stream +H= +A >6&X,@,WY_ː.|hTPC,q=>O V&A Lj ŕҭUXtYo.>l;U`&Ijaqh\,sf4gos0g/޹\ +endstream endobj 128 0 obj <>stream +8;Xp,SI!#]$P9'NQgX/Je2EgF%J0`NI\fB0O_H`l#%2X5q(>1bN#!npdH/J+S17*m +DKZ\i@CQC>!!!$!rr<$!s8N0$BP52"~> +endstream endobj 129 0 obj <> endobj 131 0 obj <> endobj 132 0 obj <>stream +%!PS-Adobe-3.0 +%%Creator: Adobe Illustrator(R) 24.0 +%%AI8_CreatorVersion: 28.1.0 +%%For: (Ian Griffiths) () +%%Title: (Ch03-Creating-Marbles.ai) +%%CreationDate: 12/12/2023 11:34 AM +%%Canvassize: 16383 +%%BoundingBox: 260 -288 487 -15 +%%HiResBoundingBox: 260.207014254745 -287.97607421875 486.56298828125 -15.6484375 +%%DocumentProcessColors: Cyan Magenta Yellow Black +%AI5_FileFormat 14.0 +%AI12_BuildNumber: 141 +%AI3_ColorUsage: Color +%AI7_ImageSettings: 0 +%%RGBProcessColor: 0 0 0 ([Registration]) +%%+ 0.172549024224281 0.196078434586525 0.7843137383461 (code keyword) +%AI3_Cropmarks: 247.366972477064 -302.907242023221 498.702646748397 0 +%AI3_TemplateBox: 683.5 -384.5 683.5 -384.5 +%AI3_TileBox: 75.4348035092153 -572.443611245984 670.454762005309 269.416369225447 +%AI3_DocumentPreview: None +%AI5_ArtSize: 14400 14400 +%AI5_RulerUnits: 6 +%AI24_LargeCanvasScale: 1 +%AI9_ColorModel: 1 +%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 +%AI5_TargetResolution: 800 +%AI5_NumLayers: 1 +%AI17_Begin_Content_if_version_gt:24 4 +%AI10_OpenToVie: -137.801778030367 127.623731428107 2.26 0 7838.33981488999 7963.22582609402 4318 2607 18 1 0 89 170 0 0 0 1 1 0 1 1 0 0 +%AI17_Alternate_Content +%AI9_OpenToView: -137.801778030367 127.623731428107 2.26 4318 2607 18 1 0 89 170 0 0 0 1 1 0 1 1 0 0 +%AI17_End_Versioned_Content +%AI5_OpenViewLayers: 7 +%AI17_Begin_Content_if_version_gt:24 4 +%AI17_Alternate_Content +%AI17_End_Versioned_Content +%%PageOrigin:283 -684 +%AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 +%AI9_Flatten: 1 +%AI12_CMSettings: 00.MS +%%EndComments + +endstream endobj 133 0 obj <>stream +%AI24_ZStandard_Data(/Xh>E -&m8^JWkB){sYdSre?%  B Z*-KɤJ?Dae2 Uh3D-Aϻ&i)2joswHX:6%0D#A`BFz$Yqޱ.єtmH_SꙥIbIpl(f2xz)\XvKNJtx{* ۉwn*km"4+^3dDPkCvP| m\h05F1|ƕAp`, 5qVE*YHc=+=Βs1sBRPA?R)'khK-Sjr(aH` Iވ$3 )W( 'cXP2q`8nXxC`4 ;́uܡp0 Ic@!5DB-iwsH҉$DZ9gh$n$"II`,HH2 Gdj[ Z I!i@cH|5q9.( KKh[)PHV.~}ϲi 'h:AG‘Y! (&I$($H$H [xP0Th$@c]P$QsDP ^bʪtEB ed1Awc,=HXEcX:h$H¡@ + ) ϼkHYKƭI\VH$Uī` >Th$C$CLEeI r!a""h8lHTF&ӽa#"UDZ$vCKBoıu(CC+.kI*4HRF$E{$Y=,-$q,S$ TWI`$D"9ʁ$RBh$Ha!uʮ6ڕZ"YO'yP1ݶu{;|9tI7P77TtgiV~POyfNÝZ 3i1|4YvIz;ta݇AF;}B$TEqɇ $yBX4lT I0 n"% +æ $Y( +Ja2,HrBuU 2rgep0O$Q+U$D._ "7MMSbQI8E"ѮE1lSF2@-.t \4sP =r0Ip +P(P&d1 a)BB=fy<̩BI(ppD84 +$ϯc[nu"**B%n R^OH>٩l)Pɡrz捈j;ۡU JC)*{Mt-^}/EV4T͒6V*GfG[[պmTa,8 &@ +(LPcZdRHI$H\c@h, BIV +UYAbN #BWjF2rDt,zyxHhI IpIKDc($U4$ $HVFGH|DHbz$iPdD$r0%>#Vq +FHvO (P :κ]"I.vpe$NX$  ϐD I`, 1I;$I,H$v$Ģ$P +W.Dg0 \jpX9ar0:5ECh0`(e$ +w ƙUeuJŨF9Vfv$k)la k|uvwxyz{)nqk*p lYiyL"F:ZjzzMBІ:[k{$*vmc~~?qgq)D%,q LdBx"P$ Fp$V@E*T+ +b`( +^]|`, ǂq d$CH3 c`0 nTF67 rQH& +p4hA.;̅.DO(HRvO(*HrKb$`>@E IQv; łp$"Ԣh$Ʒ md#ظ5Q 7`4 `,H`$ PF2q c1ƂX,/v ]-lQ /hP4 BP\ +$QE*PSV‘`$ E"xb&.a s7'# ݱebn.m֡ eBz]UM=5:ґ\"L$tR: T`F5qS劉wgWs!əY 4Ê=rPJ;`bZL+0pb׻D4:3$a`0H tڥ$$$Q%$DB(O$*$߯fiab! T;p;HPw‰U4le; )($SEJ3I  &(gr(Z\ (RAãDE]`G (  k^IiҜ?ͶvNIg׵MCA34R'mUDr|+0HƠVWi4%>ɛ,VWti+U8yHd;<\ +mE=f.Hi1wNuMb֘vmy¼.㏈n-2.;Q}n{Gy3|͒RͿժ뎪};X;\xScNRyexx-[{9IeJ{_MR7<[!Y}RNҗFSY{tdYv{ fn5ջFPk׉tgu 7Kso/Z;?ͤ2ο[ӼT[}l3-(s/'S_GG +w[%]-F6.vIgTXtI+9o8fcW6~Wi^'] tEˣx\+&g75ܼ )RU4ܬߋj.+~r.hu)7]a}MVK]GB˓;2]2˥6Oݢ|.iJ$3Z.PFkTKc鲙v.?R04y".xez\\X?׳u-W1%+s6Ь.I}Zrʢ_cD8%SSFd8Y'zu2Kim6UsYnMdkqoRKGQf}6uUI콎xAr[Mb9PbOPsZjgUoyFJ*feg2 F3GCkBH,@D$1m"$ywF +P$A ,g2rKQfk:;lPQiLL,*uuH`xj$ -uPItOHX8t /ĞcHYyHr+ $i, Vc C -5%Pq[QAw^.ґfϾpzSy$:|Pѳ;Zwϔ#*K-J을dx/#2U.yxIc{SWlq1+Bee&gz;u<1͎.U)r9w(iiR^ӟ4 V)Rvx%25*/hu0Ӝ/SMz!jUeU7z*jkEȰ/&i*fi[׃R\˱r,evkG6R*lVciXtUl~kHYGyC ZPj2q制Z #R}VykNeQޟJFWM6Wu2W|j7"u1x#4ʓ'qS/&tUmQWc[{{Vu5Wm{M]ؖ]WQrT*#Zr׎lwZ(9-VhsDO"ukƖj3O1wҷ樵r6T IczR=&NuOSDܟ֬mh.p:'"\٠'-ӳw̹D{JuN?>Pz+72` 2KI5K6M{&Y4zۛӛMx4S[%Co0ֆ=1]Oxv4;޵ jjӺުMmxsHݛ}rEt/N)ab1`^)vJR|m,?F!j]=FWNz񫗃 T7+=-NV޸KVzIS Gwqᢥf{_݊Znҍ m7xCV绖lEuJ7gu{T!Reۻb*WGVz ߫\CڥTu=e{PWe|͓Fxu;ox}]E/Cݼخm 5 V*tZ>6_~]6kvd{{SUДPy6޺ɳ[Yi-Ӹj E{znt|kR\lG5Ś;+ܢVmj5 T$c )D%WK/ˤSNo5NOV1W}<VZ-l6ƴW4L=u*kL/4ZyvϢմ.z2=.ZtxDkυ{#t޳?&>=ZvN[T\{1 +6^%AsQET; noy,<;rǴK5B4Uw_14y\fM ̼k/^uj[m!RF~ݓuk4zSU?29C]]9tFIǷy?YYh7ռ=W!na.||mwo#i{_:tlh9H>t[&\:rHi6V>WsP]{ES5R͵RѥGR%>lJI0)5eZkZ5ŶY+sWTwıR3,kiw̴gd<-%ޑh +wu]"YYe]ufg:}f+m7}y<^e5;w +^+饥MuSCc^tZJɴlTJ.f5KK4TO_j*מ[O4=:;4=[C.nb524[>W7zrњYZsFJoǪwxg*M66|K]DOM2iҚtseݑ(4dw]b\$cy,)/z iӼD7iiQb]hB}}2Fғ%Lns4[itW9;͟/Yϔg~5vqJN%t 륤zoHIo[IL53*қ7JS͙J7De1lL33gͱQ3m63\ʹic#=+u6]:Jhz .P n뒮jhMݯꥵk7P! tk;llՖ~e=&"25L+itNʨ!馕Vө EU7;6QgDW]_Z5Z+-hoLgp~WԜ͘gRU1ڸnZK[j7jR?顴Y줧cYpsk2+s*yJ8wiܹ,kyS˂+wggQShaTd+㭼1WfJv2xj̣_-ϜW>]IKuIsV1<Ѻ[ou\yvwڻҟ%֑NWzTJOhzf'I[ECVʶCVrm3V3j2oOKl͒ɮ{tx^9a-+JO]=nHWzk36yUCteP5an7u5lxBRlnV5 +WLuvIF9\-KzVR\]Q=?YR4٥6w%H+ggzs2]tK*:fɻ'E%\ڱcrYf1|K;5m-%4\Zs=aދEYJc5_Шl0˵*8oJ9u+J{G_|R=+gGfsEWςūlu7Ww*7RSk+uoyg{B`@AGzN!d~TxWJp1RninO-q;>z%j^f$܂]T/+:t%E@㕯;,|b-D(Ϛ[l.0==~7.Z?\/hDQJU*@cPc/lnyf S %X")i. +"r" +Da/Uuv[35ZwgUG{Ñf)Wmİ2L ҉<$Wx@o;m\q 3'O%.=3)\9LQVtKͬ(=5]^yL `,]\&>2RdD:$w',20Ucrn7&(jDILpXcz}<}Wau Bx\EukMYxhx!szi0>sT\/Ѷ0*<(TӪ.Wgރu4焒Jȣה!eu\b<\E1gVdM6 KwʿxvI,,owl*A}'Q)V:fvM=#%v+ޭg~(\6%$jǾ)r[: )11HS3TOf6}NLH? +4SKXG6TF<%-%7AwR A(BPF6ByE^bPK7myq|管{Y_HtW"hqŕ#+sXD2߾}ަ8O l#+b#QA0RH=V*w2'nO,d%sk;\镑ApԛoY{Q^ha3!oOT.Ǿ +2<ٓ=1-L$Tz#3F]f^"\V +m#ΦCdL0 R1bdmLL¬UsW$ֆHb$X- o䵾*tzBܧ̅Nn*8+5lWB6IK$Qb"r.*~7G[2EtViT.{ht;Hҥٸsч>K؀lٲ]a^Yٓrlh>݄zzXګ3Tڿ'IЭȁ;5ۡd`˩'Wg\hPmy>Oӿ&_2=%>x~0;bl 4SlB +~Y\"_ &τ f)Axї!G$D}_\upɦcw}U m爯UDGqGKeu#CSX!jhMYf4*U%WH8I|w W1.*)ug#Džeo噳,X=0h:z\}F#1$P$;{9^SK?Mjtptޘ3AXRfR>oGվT_{^{J}(^#fB@5ǼVΊI\XLioTl +c +ֆ5#1ɶÃ`L硿6)A晑Ja xeEPr =}G7Ow؟RȎf 1aŒG2`4=K 6_b!M˯XMY@iP?Rmi݃iP.ӈ͛m#"ۃ7dp-%+GVɯef]g %sԄtZ{(@64F6y۵kN 9;lNӣ$w(/3o,kXPk9#`5U{EUV\j +P`Ɛ0QObvoDkU>VUE %ܘX rG7@)qR0|< ՞-å9%\/mJ_5sR/% O_LwXܔ,\( `/Ou!xD[fޞK~tc 獞fNC|7r3D^$׵]TN|NdlCs qIϛ{f|9p@Y]o+quMc#BW|A*ɚYN!;5e~p&?Oju.eC keh͂bӮVz`@7'>/7N3XӁ}IjEZ{sJ![*=QTߞ;HrAmW&v݊Ꝡu #~m%S+u7xnhJ.tGEy\{30l5d\fKY\ &MH? cuopt2A'Xsf8Ø6H@h'/A )?GNh/n; 5L*B-7?jR#VC3PJvE͘#Kͭ\%iYj:9Q,0ҋ7ޮH+f rR6⣏ lĂ&9Oxg˪VR ~WTw%xk6ےX9=[e&ܦ!>ΩSM]]Q)yuvýf zY +:$a\#ܪn}2$?iCui=6s)ѳ"g2dmLnK3k]8neƝ"QF +W֝T0YÛ+Hw Ȃj7c9ىOi'6vIR։:k+$~q~;;WYG+%!cڛe @Wݩ(ּ ؎o. 4aմݑjyMf8MZ).~пy)!K,hD!_~W*܆YH&ǴŠw:ζ%b_Ae5 (Aәn~h\Ă1,Vaꀧ8 ˀYj*BԫcނfS; +)odڄƭjNUBʈ7g01~d@;[t&k<w#CىåAX5^r4MY tx%&ze{)t~Jcj5ix3YHt&q>b6L3f31 ' 2ΞK>g60Wi{ vZlcԾUwm.0NWКVSʨSni`vTMbP^5n̷.1l5~B(wTi淔4K0@䖷`".F?SLc~>en:e~3St0,KRotʜ~6heʱ0x$FNh,ގNFCXNdJcZ -;,7"Wr6y-< ig]Ȫ}K!,P͠0xè* ӥ-[0.3>bI0`{>rdkhixe16'p&Y$[3qI(@aO0w5U!A rgb!, f{lr~j(A+ԡ#Dc)~b[*7O9y,F\Jvmt|e|Ś۴Emq Aԭ'6Mt!WL0&I_wq!t<V?x 8܎rbM_/3510<.()a(T|(؞ЦPL 4#?'DSS!;jO Q1G8g3Z/D;Ph[M>8 >Ac+iq\s 8q k{Ud_W]"QJCrm'P{Щsxk<"Vďt8&/`Z0\둝VD$|ar\Zd̯) L4Hڔud + +;3] + ]E@hz3my2%f,edƾg1 -51"xznLp}`wtb"N&fbV Ϥ~1sH4Q1z Xc3#~c +vJ4 iN̮hD/#/&+j=MBhUT35;e;Rj% pl8P]6(*Q" [T:d>tv 9Ozr5kbMj~vB2" +? <7m +U1&(RY麖MDDQNw(P˦OfHF)@wF%* M߼xhB # mbRq+pqD*zx^xdu%8'=*?،?uF퀲8?Ubw !IRz eV3ʠo = i4^$4^^_RDW+6 jmcvYw5S;FE,˓%`@qP%RcD|vduku.dPXg uyDJDԘ:L;ۯ +ЛQ0r[Z-qX'^/pU/S#JpȻ׵' J4?y)M争yⰧtUp(>!P]iZܽIs8& )iCFy-mAkO/2fotal;v콷L"x<]PKvPbH\3TCSE=N)᷼R"i +]a;be@PO.z\6e2 ^{<wI1F j@tx IhNx~$S~,iaX7.:aےjɒ:tJPyP9&1Ev3W,N u@l^DR-A Oݙc *Ӭ'ҋs q@0tz.uHy RB"4+pݭb(DgWZR?jVPwDs>StF/QG%謌.ZtP8)N I2`.Kc0X5:ſ)ڢ4i &Z'0Hsа:Za~- &|J^m4a55EMe63Zba-mvf_4a.Z8Ec#PڰZ=QQCI@"cl"z="ݜ;:!vMzspneܐнֆnPBl6/PB"HнPr^KYA"d*z2nC9@AWwVwdzvUPUV,l6A$觟@}4,w#PB2ʴGW}eHS661yXY5dIGF)tJlk23S0wS'\΢s :*\*ɨZ6 >#l7p~4UR{0(D@-\k2*Ih@iu)ƒ\%2"8_y% HT?P,gSPm_sDD#R@/O$ð0Qd(ocgZ@;]2RJIL$ H9cx +YL]2NXGQ3'fFCS_]Ꮏ \ZG!bBưHU2%?͌ugIE\KȈoySm +<]X,<Q:ӺlDr̒TJ j%>;%Ͱl+/tX@gc#pJVGWe;GC`R`;BP'%y CWT9)0/zhCdui vVӴ*~Æυ҃3d():#DsA_SiB^#${?#f5<3(:6FUO&4Q*<^i*n fG]gMX@ b dž,Y<#9RA W6*2,F3PVpo77ˌ|nh2 _ +  tydEHbr 58M)g2b$Qo:4MʍgtPotG@*Ir\de_ƼSE)v~_Rr<BBY(#\@걞|`IJΜV,U/j8Gv]М,J_[+';01 ow;L]i z)0(CaUT+,"$~ZwH04\ȁE1{o_XGZF" Pr4BR(إN7s>[y&0+-8yOdLل ћͶֽwC]+\$8vёG$sseJS è@-_*pdԑTLK[;@z"㔒B(#%")F\$fKW>#@a_uˠ  :`рG>m)zGQ_^.Q^UՑ7 Ԝqb!SR/{8=kfEs'إ*x#kd~eGCfvMDeF[b)cpJJ4j&(}AxVMe*"tNҊn`@ܸ:fk A-"׎2gͼ5+Vv5f*<#Ml7Qԅ$>zֶQ)ea3|l^/<Njzzl~(k pWbB8h9`!߾a3 1hwddKB¾cf rXBHXӸ5&0ݲ>PlAXxLrNMI,D;}Vek~5]J9* Aх`VZX%D0 +š7rZ(^,aȂ VR* k5~QdS~R\G_}d?Rm[Am`7P(`#[̥P>|k234,:w=GGw r/D$9{hT!W(rwoTN̵][wk>ud2ٚcaMopN d nki`x ȨդD2Zn_N vonm=xšBa .Xv1TY;{));cUBM=h5CGVnHzKu[oORC=LDΣ[7&"-$"=_ϝ2Ǝ\AhyJ49+I-h|Kgj2Bs/D5pb +) i 8J婠$Yd OqyU@zO4"J#Pt*xve٩$AyMNJ薮ATVMbj*D a/Y-l4J!ؒG/ɇmPRziM8ӯϽezgJ"YDL`ƢwdYHr + oeSE` y@_4Ԙ/W*ETP hD %J*K!xNf/k]b'Loq7h(_(GLO *h8:GUgJ0*"!c:JO*۱ n tq-$/>Ѳw+%9렷Y\Kr T N/V.ǺٗmޔUU_AS%TW4d^!;<$P;{J[NXaG)j {^{9$M}oA]}0A}HЗo~1$(rD0x +S'pq/| ;l^Wgy +S@!5Iܲ)aבdS`Aȅ<<}2aB\i+KmYpU)оf!n3IHgTuzsXCW9̉N_Q#_;@9Cbd~iT$6j>hYJ018:u*l)C@rY3a[`k3{Tjȱ:)M((C\مy^J(pF`sD9{EӜ?砶Ύz](N,K2?eWxٰr]rEDY%Tඃs  "SA?jcq]H؄j$RP~~ QLydxWJhP.z%zT@;"=q~UEGlT94ޱ=k +<3Wg?* 'Dӯx/ ?~;g<AvΓlc %8G\TSM +"liuGCS ' +xpleaП@tLs2#誰β^$=D Lr +QưÁ @$$%!yn+TG/WA7z[C{Av@)IpcL\c }A{ɴPA/ r}+|u~s"JJ({CcbH&~i "9'u98Z 'uUjV wAq/b.ZdnϞJ RU m$GxҷcJ|MQ:w.4 /˅'b[F6Ϋ D!SCnf0 u4|"6O#vX6T|wI*(M3^^_x0$W1 _E`z>:^\"2o4G#]t(y|.e&l\ҒC@}1?gLv3/ad]z[p:Dc@Uxʊa!IO.h;F'Gѕwtiraʑ_`fAI<L"0bX;(r_X @x3*}. C& RUTZ76U}TLH30(_pf,u)~|C.-I!‰aP݃G*…5np&@񦵍{UC 2mFÕr;uwh[APT$)ڣE31:[khZ޸IX+ibJn&{`?Q̔R{'e37M\#䑒#_!6ܬqmaO0+?lD;\!i-B z]|Ӎ! C^^E7^[}WUվ7_/Oy?^ .:gJwԖ88Xkpg{"Ao+4%Fx v s+/O\RHiD+oK(p ˠh~_s߀ݚڒlPЯWXȁ4΍B`z7tvn V!qxw}XFs<î4'FW8ȍ-#/fUa9\O(XI}IY$p>Ŋp9p'e+VJXMUy(Y)wlŸF깿q~*HOv.yK׸.5sy|k@LwܽVOgʪy4GWLbǦ 7D,4JIˢ#z6ab(8`trr l(OsS<T5gw=dL80K+{" +wBUWh%CŒ,LbR9$–2~d\9nh r_WJ~8mJpv| {FjRNY湫'Zf.}`qq=I҅F®F̉/!P+1$s~Fw*#\]Ad&vt+E]Jg@ LT{2gV8yղVﻟk5b|>t FIz 6CG 1-Lsq1ɐ53MFLP qr ˈKeƀ7U9~ROybmn5?0\ wcms!D}Y!-!e`ZCb2A*dQ%; ,W^[Kr֜c;aE ]1Q{awpH $|شizJ0sWQǣ +LÆД֘TCsхD20:o B>Nvf{W"NLV F,(K_L'Ud8,q-Z)܌#z%=aA7Żh~nmkRh+ɤ.E z{3]!b2C=*oUFEKЌ\6S(?m@OTף,ƒ;6`KDY'"WW~n=KCˊH"*伬 +qEid4)YSyrA Sb0OF~i|DӄD_S|=ΪY^JL ݡH#Sq2^VpFƈ y)=sSs^DFMH" =H)GfGZ_YaMҸ JK*:w~5x,,q&j8 CD.H {lQuK~p|+["MM+7ˤ Y'nכ +aD +I&ԪM;f6DfMɞ' A^hӾ'׆U[o #ob Q~Ed<.h,RR8nJb~_/ gI/LÕ +I" е M ELnN3c<帱5ZxuT{ux +%Zn +KC"HSL=\&4խVj5Z c5=H5yQ LuEg + λ`M6Q](^9!{Œ޸eI| N e)!A('h{U- ɬ:W睭Å``[A=(vWTPq6&a]bf{154s# z榫jP1p:Խ&/_jbb_8Ս5fk!ugW1GV WR3?ۦ_$̏uW)G! m4?< T&~cVj@PP7VjK67 C5"S;ux"@477y)yu";9w&^8a5+Dh'*clD +=ChzVCMyP!"(BR/Rտ"+A!-DYYk~>Kgg "xjW%d03T! 7 +;"A48Zݴ{DxL|FHs,CߔrJB5T(Mτ Pߚ]PEPB(gK@=WUě2!,fJ6UK&ĚLCHY=1V6|pX̲X4b)eUEc. @7P/=ɞU䧱ӷtߓQz%iC9X#U:E#$w$>/n{ڜ-1=Ihd&cmNh.%sO \NOD;W;ȍ/.zryBd 9&OX E),#i32B< ՗kb2F+yeQG% /M,lJ+C}LfhªI6(^E85{+d8پ}6}yH A]%a]Իyct䣡 Fv-촀Rz}`mA#̻0y%N1 #gN=̥n}pqxXXg ZJΥU tR`Kfﳧ@:eFѯ'}{,kU0*C_FZ8K`!%q$DM9lL/O*R +@ +'#=NaUJnq 6zHq݆ɶ*a5eb%OV:o|Je甔 + G-޴N{k+^S4`Fb~hD/%2Υ2@ Mϵ(JNHTk!eGjhJF7ǣjBmzQ hm=DGHVkqaaU=!uٯ%nZSjWMbd*'VJE ǔ &Yb-Bgu?$oЄ\Ɯ.sr{@s˱FGgPhScݱs + AjG_4;ܠ8e +w4iMaڅeSezIJ>e ӶF|9ƶmYUr8H4œrNу3P(Yt_Ev8xnBbRRmTj2Nn!G`6Vn +&qr2WvmvIXCvpwHߺx6Jxϛj )y_>?o]f:~%<>Opg%+-nԤ@T`1mW+0渒LWޕzgo<3;zM}X^KU2 j̝wD):"8:F0׷s"PxH O>C9)cBeDgf'B^yj C%B߼ +BFK~JJo\+@)˅gd:C,zAALCzɷ +Nd!R⒏f|E]BXjΏHf\0<Q"|1d!v7ŊhL< (nRIj):)#3 deHmK`V5t[լ|K' Mv$;IHΒUuE?I7"bw Kw@?vƨ~yI3F'`V׭JޫRg*IQI)YM[J5)i@(Qݍ:Di/ň|N["J{8(Fs#H~4~j 5SnZ-;D-[ /.Dp|\cc5 TKΧFKӎ!7d0KZnA +u R}hV\|e^ȐGG&uCzD{JV``5l8Ejzp~Sr#Pa~e+M1Mj>nj#ܼ195 scLGM0_)ܩYCikST#+AvǦsO͓05F(E '(Mf "s?+܎I6`.%` %şO'$D$iF"=:$<%x}ĽGe:s$uy#'x޾v{zeRtUoZ1=S*({'6#FD. ԜdM)пTN7߳ ?z\Ukb=5\ fE3`'7/y{srRvؚ1Y]O( 3avwCA7C}T +_,PPο޸3hL=h'7~쥄s~fs57[fpg?@çf3w>̃^Qm`cJH +J.X-ӸW?۩G5d{Ys)ށ>ˡ;'GU9mOi!ٗ q\jZRb4CoJx'x](;!|XAhYn+AXg? DᨾK{iԏc K@p3t\q8 +KKw1- &$,ق=pస 8xԕJ}Q__ʹwAz l5{7;eF5%B,8ť _eHp]=4cb4A|N ҋx6ش + gh}NՄ7O ;Ux .Q*1ri6zaG Ƌ[^oe} prٶp-'Rzey>] Q^؈sw["dV7tC6#@YVVu#L䚙 XxcSTңrA$ \D>#c\\3ʞ T.3"vbq3М[!fc +,,ao@cYX W1wm5-/Z, Uu;coMihP+GCj0oɠ +Aң=+iR3`0)BБГU_ÃEjlv(Og-?rF +0a#烤uATq001r@vN'BjVۘ!6Ȧ"#37` P.nbeS.&f^0NՖo>c08i +;.ϐQҔ:'kHR][(]lY"xfnI6{3.ҭdd]lײ@5@m."0sӹL$`.C1 (}45!K%G$>j.[\- SKi!z42'~M>;ɼbr D>:CpeQ%JrEcjY([T;"[%jѻ<x5mj@U;ϗs.we֝ƈkAx?Ilㄦ LZ{;"P(W +Xpln'Pz;De̎pL8pM-|a}{ļ-}8F9g=@uťL9vJk %`IrLl b#nntfA8F3IDC b(%uSEZN,+\U&BoѾ/;g}Y<$'˦IIYuGݬ͈ k鿎0HHJȥ;3U6?^CՉm`沁khVζ#ekp9)zģ{VoV Ӝwgj *`cEAA];PjM%;g8G8{Ix41dY֣Oncι>G[&@A實xK+?@a@FgnOH~`M+Q=FÃv F +z2Fģf_y{\9*DǦ_9Wn0ڎծN@ƿc?nA#ΓRB=zCF#B +,A"P +,Hk4"J1$rnR"g9_cK݌*xt=bb9eEF0'MDAGo"bO , Q'J !_B ֧_+W?-Z5Ia=( 9d!ZpIB=;Rd<*qcc^/F59*Px:1HeD(ֿLfPxld?r?OO_V Yt2 ҄P(b Ӥ&  qT$T1!2`I  +EC#A1Q6"9V>T(~ `#'+ C!f!b1& 1aG=| p:X(pHPtQ(!$3H ˄Q +*ժj:pؾmq , aJ_K6mҁN; n=ٮ`'- +S8 JC8k|#:;9+J d:{.RSSX&LMUT)j 'I`U2bBq +:1&TUH7wa&E̞NU"&MM8aURVZj΄U+BMt@s +5E 5PFPp 2r{lMq]L,ׅ{f5\.Ί +B LB65#e(bYM@S)p +i6És;y8[" +XcF&6{ ܛ#s\4>.9c/6$/cjh1fvMxuBuyna=xs"hN5Ҕ>})3{f'!bָy̬*!9?D +|4gQTEeȈgmC^LJ%qͻ5Ԣ)EIZGшꢯDuA/QàZ1WmjhFN]oPLk3AjAJ.v. {H +⊾(.INeP-K0x&.rk-!C36ժiEI \RjFN9^5#5 oyIE<.U} rܱ(_P‘#$/ lPPCQ!l4AKDф y,#'XDrhr$SaNEr*JRb1K¤>1wfO`|ETψ. jק".bDF5lȤl6D ?7DY^FF XVlR: a I= N'`܂ׂ9fO$]eP"ϧD@ެ$:Y>5 DDFEAe&l Rfտj*t6!A$A :Cԑ:(rs"%+i_h$i|D+K.[vYF|Ղx~HBM`S80Ufk +jSrm hp +QfZ^ݥY&fMCySǜzmnƫw]H{ %V_fi$uݾV yum:99!BAb#QKpj>xhq*OT|DM?59⤠Th'6(*3nGaHGHk6Hܬ\2ш4i[PhXV$J䉶LEȱ5sd)ձ TӏU5R撘CQ4^D; T;:,7OTFVf=SD12;#!z%%(pM)od8 %^"Qa2 xՅˆ!f15DT +j ++aE"+mn;6* BĚ".!H9$ @4D銋q0tpxY4TDp!*i+ bY6/ЫTZ+}B]p8xw3oC<sR բUxےhTHX.z(N._Ly4)! + OIG^TNMo=&{>*2.ZقDyl&fGJd2*P5PR1q+G" +ΪYetL9C(U}!. SUT^Ubb'i,|'PP{lC` S% +%py+ AGXSl& 6%ai+u.֪ +*ƝC\775њWf̃JaxW +前~ccC4 h@?ܬl^7ŸJiEā%yʗ͓b&dcP:8Zř&\JS(Ք(B + ˪,Hٜ v> 2F6}>mY,H&U_%$UXDd!$3 +6!K+I\Up,F|!nEcY;|ȍoD참H"RT:wEY45 &cݾ y_\f՝H+[o.DH;fŰCEʨ\VI 7^lDEQ|žSMw`?ȋ"ŮJb23G$tbPIM6}jH|]n&l/KxGްi2dǔd Y4ʝFCCy]S#*.jӣox:hm-򭼊 hb}^K(̦xT~EBWB\t#N.] ߹ѷ/lEFEVAt$ٔ24s _;E1 } Xpi`EjpRD5LM/;F ]4nZ2D{d !dXQV!GAoy! +h=j6%21DXx͸\jj\@(" ADFT=FHASJiC9*,gQnJr/(e:D s 9X^3!Uy+`LUfCć GLX\e},C i %wD$ BAJ#ˠs/رPE_x&<ObA' DLԬlŋTDP BT`@^AY2CGZMBn=|.ibوhjG0i ] +`< Fax<=f@^;̓}P~ ꄶ6REP~% ѰtCe`0[{\(^)D~ ek1A.a!"z{ xY&Z5P@SnOİYI;X@*n$ٟ +j<02y ;0u$˿Rᕊ +rTe©4 ( cnN2'HHQ"]D(⪤fȃ(.QGD +!)]&PJep 8O>=>3HQ*APdQG4"BO!rA)FRz@ #,) R / Ѐ 0uD M 0/yGCS@}S٩n|*U< ]pL}D3ckW!E:5 ˦'íU"E%e# bIӢ2h /,MEeQ.5*iͿ:7u4H( (V\s&baK5cME:?B\Qy/ZԄ5mNelb5dkd6شד8oiHP )Etz TNf_7cZ?Q~D?wYZ 7?j?~ǟ$5B `@ +Cbv|4~B aqPPZ  pB8O NH~H_Iu=~ +FѦ +Z5M6  ^@JHʜ@Ҹ{!.tF :GU S<, + ĺ1ch|uXH$DrR.AO"׋Djxǫ|-a}1aa fZM1U wd$АCwHv0hAYv3ч1*^ùGuאbo=iJS\9srɻpb1oK/[C0s&zI˨}@xt@BS}Λ;HCl gU6'h-Vѯ +rs# +2 HPl}-W7)Z/Lmerq:VO fϘR Q9iG~u(·6_ps'lb)o <;^HھSrtqamSĂ[H.B%R&vKay߀4O{k#o&;X.BmXF=g`Iuui$zpl-XM)bm:å.U432C:AR*asp&uLJrRqpGB{;t;1Q^p=/ݳ2FyVQ4O`96v0nA'>7?:SP]*q=65} yZ^bڂ{6G +J0x0Z`4 !3r0VsCX yD01M!WTWDPAL}m*Z#͌zM? ͎2c1Gṫ +G* @I6_:cHUrf'ǀ("4܂C~2fzR{ +s(#*[x"J=ԝ- +^d-V90j({@y&6I!SGD>J8pɖ7V9KADo.!LgAg8%ʂ qBP<[;"oUd5ǐ?-,=eS+l[4PGt2n>DwbS +rcͲ易~ɂGDs%Ʊ=48@,;cQň%NH,5!V (wq:+"ǝ} +_|Њb3 m?hG @=xq}'K=) O98:D{ 쭢*!D.䮆 3爁&&8Qdpo24!*SH?t[I%Ƌ. ~o╠TŘLSؙi/@(t(lxGF'Sf岱».> u)jp})|0W"v7J" &o{kN9v=LfMcsi3Mn9?_?]|v$Ѐ'1OHMDs@h!h,<@ ҀfߣXE](@cjA.)3LbZe'Rk*[Wt5T,HC>PijɜT%{hBdHP\G^h鄻@,6v+ MvnOz"K1!RB dRVhGӉcDpqN,ؗz&ĵ͜":sLExuNx F D]-AJ(ί7VnţEW65 @S48)fEN4 _*r8ӯPb٧q?:cJn~nQCΒM{.)j[+:*xٺ۳8d}GA30hn+XA&p *!T߲7IHZ񝨵jƈÊPxwt\e|L0i-[4;H\x@v#ER0 .pFFVO#Uq&ASe{ LLM"V(f[V 'v슬D  j +9 IQLN2pS.M"l +D#Iʂ[GAMs9B&4)^E8ن>>kpn;-^K|HWd-1(lC$ fx4+( dѿG '1@_o4GByl+E@s֦݇}{!)S[\ Hu| 2";ҺY~nd0(G2|wECPlΊ|@^' +w%TMƟ(>|[@eo=De%r>oݙuE7 +Z֝)$ ¤BQ!Ql*I?eOB jYq2ރ"mbFtcPαæs]T 0 oѤJuK^;>W/x a"Uq$6[Gƺ*@bFͅUG1dYHErV(5r4ETo( eCU(ZbhF.f%ۀuplq2@sR2ܞO nmGU`+C/ )LbqfAt#JsO*t4嗦 tj1ٰ.3EKvxӼn/.d" G4acٷ(4ǼP3l"8 +VY8AI܄1VTw7decmFaENxl+* ƯeFJm)5(^ +t/tl0=ص+з3eŃBdd} üa+c:v5O6GWw$ _86L/hG:-\dP*zkP '!\ܴ<(T~: e +Cy?Sp B!M@D k3☥]}@5nѫ F}bʷE +\Ux~}.~ RD.|+-FZ6ҋ2N$QpyuɁ&]c*%hB~hY~G$Aa1{Hɲ|c&'HLf)<'z^=,C{LL:^k|}>tS?N@е L< M(,m@P#‚~db1M:Tba.BbR!5&2 r/}L-Éa1L d& 4LH&!$ET&2PmRK">+0j!#63FtwG:`ҥGt&=m8K?$b$P($Q/aTϙ\rIKZQ)S]%lޒ2ė.LK ZK@%2&j% bc3/,Y&}%_=]֚(*DAk%+٘&Y%PtUBJL OcOjJr':)1;PL +YP@; wB !l<> ܣ`c0O +%3MWMX\0IKMM%Td£XD;**:1$/ U%"T G^ %fF߀湬({(+U${`"I+B$E8ͰX0$DzO, $s5Xh8GGZBqZZkQyl [L%&;-H#ǍqQ ++̑)8RchsF1]$87ȏd,/8D`Ό]ѽ@?"%jD]Et4k + QQ`P#'a +Qat^\ā1g4hE2E3=,B;-"!~ PE ΧH}Al)b ڤcPVi2{"L_/M̠hJ6.'';+UdFcFaШ*"flr!RvѠƸ"H"Ki!w1 zH耇XA|Pj\AqoGf6$֠XC$}4$3 Q+6d٨Dĥ0DڈB܎B^mxHrCt#nv|l7Ĥ Pno0nBv5ecp+P G |A჌,9H#GQARd9 V9\[̯ >)H ϣn 4C|BRuЉqx@_;|;@bz@}?d΄0 m ãb{E@V}ʖ#$ڇHA0Zq㽼>rH60Rv6ayGpÇ6ɇvig$ T@.P9=0ʬ&8i㺧T7X!WC r'^S`,Ճ.X:FA,.{x_f b韚A|]WNك+]$AqTh bP WOD`P#C)d iz$+Fz4SvmXc W +ќ0<Rh9:Z) >lJөJPR/ȃAx?aZxP?J +@xʥ(; pL#Q +k( ZBX[v@hH:_@!ZnutX:e>Qh!4iԺ$3dAҁҊՖ`硃?AdB\@@Z9꟱9V]9f9l%T r,Q!]D9&er2$yA Hx?K NԻA{@ ,(#˰p#a opf$8 L[olk3VBn5Ɠoz#@h U$ +ިF +C!Ӎύ9ʍQƍHh *SBĜ @.ĸHрX6*e@p#H%c%̀4D lR@(f@hSAez $ |o,YԆ,Jic%!hc؊l,Ιl dFQOnaD +l E1Pjlp k &Y)@"bj9Ah~up<=j\`P×i4\A9l¨i8Bbٱ 1D!xY040THqፅp}4nu!)@X4ÐBqUC(ׁƼB0Cb}gC8)D&r" f`M"6Kܚ4CHyPPJR>q"ŌhW ̰e2a2 F^1BPRF8uC3R(Fژ HčL#$]*G  #;c#4n@IǠ2 HFh#ABŠX!i 'Z1vIÃH&%FSHBZK$TQKadC8~f_&aP'0vA Yg(%-E 5) +hrA0%4  XP1^_|%fP³_%VJDm-T {|!:y*" ~ ,yATRI=^;qCXC]S%- SY}2 b tU\L\.M.XQ8.|$XIp +X[+!U?Hϕd);lpO,[]IkkqS +hxp%VBh2h!gP6g!9YpbZ4[Iea+YH:#E8 K^,ɒ„ ELXD +d 5ZZïз%{3r 풆^RǭpK[ rE1+޸4&l}ZjdB{xV>X&\|4V +wʎ``WfM8ll͇78 6!;I77TXIgP9!U:A4*bN +BEݝ*Oh|xzFo +O '2@ ), +~Bt9(R椐BawR)) H +RILƔ9oPM@ƨ4&4җRj"W&$J.L7-N˄fUaH& %"11Q* 0Du[b/AU*:ļmBUdF_+z+*zq7%RHJOp5+RB@AX/'QB  ,)"㔘Wª# +B`JkFX^ V`U +cE$+ zR`GQtaV0Q+HۃXx,V2Z„KBY~Jb,X!$Kb$7 GBq@0Pɡ V:$jC +% G +c# {nC.G!W#rۈ^A]!2pFsd|2R +(>"1ExAUhSČVa"*쫒&WfG JYX)1*a:<>!WC!KFW-}/6VUǫ2g1R| +R=!xHLX&VJDL=;+V +ެ\L+ݩJAͭGZ\(! x8! K; )_2 gX`z9pA J"^\.j(fa1D; ,?D,@XV0K1n5 Ä|0gi,Rf~;-܇؇N(CS !Z\ E0)[|ʖbi[pq +r(a^:jUzripa b\L(6( 0ƃsaaz  yPK][64 ov;`]*. IiBPv.^૲ ؼ`<z)Oj {AT:_hĺ/VnErim-/*0FrcB)0Xn@ȡ q0`b780&00M-6kf:ߠM0pj0|sal#Ÿ!O$m&Amƣ 1`17ȆL=lPt ++M1K>g; 5 +2!4!92@n&&d̚% kڜkU2HC:hHa#`LAP&ex{-ShgP2-9̸P LB ^43/C>alv+C6CL`gM u!N:Wdз3%]P i8h,&t/tQC^ ^p%V]Pw(.- W 5ʅ:q?\C .5dy j~5(ڂl(4q=W!edy %z֏_7C@ s@xCoTPe=K Eh=ԕB,oZ0~CRb)GV~ GAQ78BÅ(سp/SeuPHX#U?,SNX8l너7'HA* r|*#&R:O&PȘM(ʇV9`` !nfB9dBd0!W9 ] u-aLKXJYO8?^QHpODJˡ$$IP14d]#"ApFH9?BSG)9!~д49FPO/m$N+-M:quѦZXuI>)ʉu]Î7ڑf\v ꬝VDvvn};qBw,Ervv a9x{¾;Dtc 65Ep"TDPnB$3t"2<5y9*i/A:j8% A2/%ɞڃ@a4u`n! 4a 6B) 8Ӟ`'g=Eu9ޣ{Ѿ+O/XgFN> -}*3Vj)~F "5ĭ?=71X,}Sx>13dGV E1Yq׾?^Hb?Y^k{@|\dm*c,ز<}>z=xr`I*۱=@.V ۃ 9ۃ)Ĝ,+^kSmdvz\{q&}؜j DVA"ràG={C( 5zh`-X'_d5WGxYٌ-ZaG\0UY XF􍚘9ڱ< +x!rb (%D b uuB`F܆U"KjYăyM&WRlAddH.ysؚDKn{<|d5sV 0C3wPi4~vw6h(0vmLLlQN%;yCu04 YA >>wQjߔZ̋Q2'`ߴmyW4 m O ۦtg}h#QEg0p94Y~9@/q66_DÚPO&Uh%r +*3F8BE-]VLv;-(i/,yμ%<qa0 PhuP ElR 4PsB,:ۀBҩ>ҏ `ai@m!|W >mM:5RX?gnEqEy"ҀX85D%@ +Ut (o_"X׹iinp bJ K;DR +~FVzd@Dy<>?0:03fC@Sf0xϙMՖhRA-G@'_jà^EQIlba\#Lz~J#p$S{K + L#`y2lAt<-`$U,|b{ς6|)woXюQ1AkW,P 5(l9Lm*UA3/V@D~5ܾ +3c})ت)aqnߚ +K QAA?n v +LOԖyL&ՄR0֌1ZH}՗8((8 !,MLJiyàx^2P?*^ Zʒ`4EVNSU#AM;2P޴ p\UHo4~&I5 +2(^LP|%h_ ]qx=Jo)m;ƽ@`{ Gf_rH bT '0$H;u@)|Xt3U3&ޢ7i^눭 XC&aη" . xKģ}Lk"#J(g1>{\$-}spR[XȼwIQh XE!/B ?PfW؂F +QT+A@4HHv @?.y19Y ++`0kTށlXzzmT8;~tq|:Ibr :ЃXzLh=r}ZS*xg8lF禮tvcB˂@Hs"+l !/#JQa57<= ; in[1^Yπ+y$̀IJJo ,e@kG]l1哧QA#v$a@*8/PN@ر| e]BR*eF.df*~\@~iMD}H ( +o1-Y`>tKߑvonb^8c]hU%AL\/nXJ%8EXmWK@KҏH#Q/Lĺ,)"hnؼr}^|,нK E ၽ@9 }&$xU.$`ʿhwjԄ9?j* X<ʺHtDNie v굜\WkbLu>z@M*~@<ۄ%b6c6Sn5bxz5e+ ++IH|oI0x3Ll6j_ <)D;_M\1t+e7 w0@Jr& 8OBc ُؐ9n02fò>,Y@bZ[eUh^?3ad7UF,3]ԖvV՛'H$L  %cEWI)s8 d %<* -%`4x#Vb so-NF\ѩ@B=3<^Qo#hO|j@ۚo 9-`b +B4+ x Pc6J)gˆ8x\ >щ-}lIE١ؘ UH"& OBÍ{)ÉIADDD}݄b\䱠+0(?i9עؿ{#)R_Kv谱O1,Z1 2* +˕U% BDCU/3lT`weV!/u\"zx0}Ky?~/A$.Wõ(q>2%C,ރA"u#¯M&K UI67gg 4a"ouhsFO\cbHuקЕvBQ?\ 3tqǗ|_{D@+=Ų\ RL9l CEчK6cupaB82C({=g*YFbfr~Oj'^}5K,Z Qt6ľR}rP>hweQ(㭍0lG=yҝ<>Sn,!0~c]N6y-rIds 9(%xG/ ~_71uRG+~S^cfڼBh~dH u({]rz3Uj+x~+o-(<њx<@fT2gZFFO}6tddt >(q8#(oynܑp@.NPK؎Y^T_xd2(.OY> \à1wlڕ BVFwie*/}"c,J&R +ZwudoHTL5:…}L}7zYARGw|s%H<_( -qqۛ|P͜L-G 2ko Wމˈm$sM[%ߔ Xen>PHquhDcڛ|T,|g7mD;A(Mћm^lslj~Pe3!A((8|w|̖il3͗U~>,xY[nʓl\ d*7͎q3s`+GH|~(hEtZp!)Ȯ%eQ$ +_Q\}Ǹx3V yKeIss=ÿ/BC;O,XR'![Eȍp҃OZA-)bRܓ _H߯A:V! S5fw%Q [fP͇@ geH{fǸ[oP%ת5{/H-pJGޒy{@SG}˂VY-r犇K!^"Y7YG8w(HÍLobb +rPS,/,Bڏo +x+0n.lZșlc͆ALIk/CEhI4iJyٷ:03%mҲ l n@J4T}3%gc6e =sѦ$LQ' +}|!D ^F|x9!: Bbo (Oˬ7 taְ=-]א\|NCW̺O.w:s-=O4qJez'?p"%^6LYZe#ӷY*¦c*97z +υQ.֊T^6ő`4E.}l0[Y$a3D| +m䷊ui(Kꄞ)=x}䛃&ZܲS痑"j +{<*7Ih,Rm㶻A5^1!vިΧ&SԂ Cg:c{P-/ZІcXޚ<Kd xL|D&bS.݌BzMvC=r+12`,9Ty[ ^7-M]U5LʩWot0|(| e82- }P%e]ȍ 72~t t{D~=f[ feQ}'ϵTz) 7wHQ`!J+SB4[$Im>/X(Greϙww (vs#*F=+ ӁwS$Z +gw?2#Y0oX8 ѕ5 ~P(>nD4=B1ߍzMHAwϥ\M*vܟ)64/S at)kS|a*qZkXR'YK4{bEvӕCIӇvf@|&SsK;}x.3Sk'aF>0Z=j}UNk: O#)_ {*G_%#oم9W&o- .Q:BT|kNx,3ڷ륇Z ++{'ʲ"d_AeR++di"t9V$kq!Jeb&Im-vC-䨋@mMTF(W˓ v?oCFgߞEKH`0_7>f.8+WPyB[&R]*;:f@7\d+F%TFxIbY, +HZZ7BIL]--{z $ֹ92焯B\_4WE؊ZdfX > /V]̃:Vƫţr +DM'͠^t3u8"h`"mK% n8CZTt[9=/6&hxӏ n!Úb{(PA64GQ5'2ǐ1_XU`"t^ [G#')1dI*HcyH7FH ↧BSFB_TeǕ.t MAܢgDD"K?& $ѱ4g`Q#ށqj7ttEO\赹[gKyЃ$5>VUYdS^#B]hSs:pj.kndQ[H?wZ^9>̪sD&q2<]NVG .pF< r)еN<`@f0 1<QЏ(UtE*&&j@o|S='r3!꨸:SeS1ف ^bU`3%_ǐhs/@rC!%H dfobf xo}N<t >MhCzz{~hN*}lby8+mrEv3~8{Jy +KW99k EL\g^wsbHyŪ.fpT5//[+Cd9`3a9s3iun|J5(3笔뒹ѼC/Dz`Ϸq5m3][ׯ1_NRBLOxvdB{FRgsy V&X[djqVc5㴚Ny;,wװ[k*6~gr#-ԠaaTNFqk;O2J$GY䫨 _SD|Еڮ ?U+%.pi_SwU\}~7a9?nQi+! s"% F'ZX;"Fejj0 ],WNR1Ij.^ +Qr ȡ.>$w} S @bbJC ’ +ރi4eQ6+ ?X#*ϫ +Ԋi> 9` "SFAx|G'zV~B.ߥkBS=GE0ι*6k둞idivS g*ȓ=t|DA~%a,Ad7C. +rzP' +r6D 9 ^ujROLqİJ4p =zc>8 Q@0$n\ +v_>*stTqk&4ֈ6^q,ᢌw= %N0!{}`\$yYАcnѠD]+LJ zsE .pcDC +8$crh0|7]PE'#6@'L )ۘok&&p +=>XD?d_2J .xܑ\T+}OГ=\vPݕv=0 Dخ7xgp)|Wf 7Zu_cDȽ9:;=nd{ѸUBV"--7nt}/Iz4r Vmm(E竲v?siHwc|vD +%ZeŒm nN`uL?(D6XyD߄ +=bCW&QPN +c0 +иZxZȻrۃ1HmyaF6}רtvGLHG6FmVy~]j!]CnI6m`>(W) +=xJ)4 i;j[eUkGS/CNɓLT6ڒrpfЫNʃ;5lIi^l4*.\aFgk=)aQkvxT~EaٞZEMXAiFKm[3ObCeZ66cX-M>-l߄qmX!=([g?dE hyl2m#IM2?X=Vʲ8i!YŖ>-2JLǝ|bZ tGyUr1`[c= M܊@I P{E(m&Er`K3^@ˑؗE +Tb07qB=J0MG*Qu.sp絁-;SWåw=x׽"CpE5kt$ +CtE@6)tqNߙvµi6*:gH~{%#D%D|)ӨXOT:95}=D5z۲ +u5f ~2#|C)k^\ $ZLobَ쯑N-MC$gkvrFV$ zΔr*{zTH!["K&/#ӉLOv gdڷ v1ಿljI WE@K jC8Ҫ8#sVI?k)U/diת-&qGEz<&(,qL1ԲjLݢ~ӦӪydI >a N9j6 +5tj+6 +u B8mA7 +5fJ +R2e`ub +POp2i\]N[kq9w?H9#XӦaxŁ^vVH}OeNL.!bA]j6{TM5v}A]1 +5f ΞS4GBScQP?~]C +!:̸W 2ŸO᪰hPP3B}7H>\v@Kޖ i~>7k=lIo7JOf4 {yt6nIJxN+T%zkjW7=:4qMN1jUS-fS82+_2|=Q\EqH\9L3m,إ5u]ZxZz+,NIbicYtr#JxbVkttC,GAJtoaGhHUQIr ~gꗃi>vFAK6Y$N_IІJګZ@ʐPʮ' ٌҖᄆJ_R\+:l9Uo-5N:*}Er)vZ{iG20MJ6VSt2vThIj(}2GB鲔^< qMJr:pv)zhw^TWP3F:.TZ`j)l4ġѢv65`ӣ. + i5R,1\I5t '-R5utjel 0y9ކJYHUF hqH0T+u0&wgX-yk-k[BnKƫj҉j_ߓƖ*d2okcu7֕=>ǽw͊WOzwjuQQV/aock] mtQ֗;rkު-QVwK9vu뻓ZP-HtxHy0ymꀐ 3 +ia!'|B[d=w]񯥄,,?(`kա_3DAf!Ω q&U+gibLV,T&S^D*X "T=hM1IFVdTɾ7d,Kx`^ٿǼD$EOe"9^9iEԂch!R#vw*I{D@=*źiv3BM=ȺnU[%S IWKFf"X[ E6 " +endstream endobj 134 0 obj <>stream +ɇʖhYzyEP[0p鶛$̭u#U"Ykʼ4LrB]z{P[dHE w,ۈ[A1[bүL?A%¦rp Ni^D\zps׏)4L}D[jܫxi~=nEnwH?]w裺i{u 9bp# +-qO5a|Aa w.р+p? QP*RjwR~qgwRk{A4Sת9| wh a@`8 9vDQ?R^rYkwr ~dZ(qQFJ\3Qƀ I.aT@6GJ``9V' q;h?L h9C%=XIAX`# *d;+D2SPwJbat$9C }`3ƞ% IUpk']2E}8D!~1!&x0Q*ڈ%2A/HV''JE3#Ywhw?e?0D|+` Y1>ʸWo)+/8<)E$y%_H /a /UjtX͆K5C4g,y$c,-M3Wإ:&DWFpLh_qve\;*ǐ R%!U2jZ)ur9.cRV=Q3AR uK,qtBe\7qaUEB:rXoգxo?m[đQ&Lrz~ +YrʱɺxPۅ*Ǫc32c.#81& G/1`T21?8VAr;5JAFg0v*g'(ōnc4à6{6QyorcO+2)T̷q 9mcCƛ&U6ipnr'tP|pq`0\cc{1AklK5VXD`Bqc^lk[CC[;o"h%řw}qC,H//HZalŎyl4E7MfME`\z'E$,zj1h(a,$^B`jBcAM&Ì9?!Wbے9ʝp)BnT<4/q$*2&փ d0! +2q3 yIwKy/B-1J"x'%mq5 q0f^N0eU;+xxԖN:{Fc h:c$Qqyas`G^ a + xG),eu 5)aL +5 cP3 ,aB_ ] +^ObBw=[MbӹA]mSfon0WLl%8y%"ays 'A7(V SxP.4V.azn%ۻ /.{ U}qQfY G/j8ֿXz2Z1%0`T]YI!.P+"c}݂,b 2lGqڍb j 5 wqcVB/F ÐUWLŤDf n]: E/a"%Lj!xĜjܶ60W9"a8~"X 8o°הӇ?TqPLwׁƥoY?}q i/ k86&oHJSn\ b-o5~BqYҳ.ZOXʦ1jG1dP' ڋfc 4X,]c8ǀ85(ݹo&dX(w1p;!8,J[gK!W[mtCr)cKrG`(ͱJ;hD.9X^f2zo}Nj0ܛNK)GU`2cc[y2~d,I-/etNt+Ӟ߾7W>8 +RKŬچVs<&sD/3&3yM;]fYl#)ם 3Y Bdh,l&?P{),= y, + +1.kK[~Bgf?gvʲK*BPcSA#{fzXtç ݫ&GA'Ԧg9˶fuANK@)` S m :az%6w#{z(;MTs/@{ZFgqPĚ-.loO.Chyv|t5L7pw66?ko1kMymOOlF8ڷ勭Se!TjB Oxmir[_J2Եۧ?d6QXXt=(@@Рa&xv66me nY96qƋebrcnђ^,i<',62N +>R/Я5n+ e.n{Bc఩u*)4re1#DeΓ'y[t=$ +nYlOO/m9j-6*EVHPaMT}<>Ys}i+ +2ચ|'ło[.0qA.sr5~.Hr\rC4I7\Nh;rpិ&24틒fCx9 2ݴbGA%Asژl\t9R"*q˱)dH]1nacmS͸oqu$2XܢppK̶-+0 lPoFuaҌN d n|ahw5ly0lزl{͠o=ijszn{ofY8P[T~!wYm|T͈L{nKnMRR#=꺷&sS6')(&ʹ/̃+I߃~s+a4]EtTw԰Jn1Z!G.Elx U4/zb゙G?=8vxݿ4n{;&n0v7?,nv*Vql}e*Lé 8&h #y wpim`pZ}ﹻ {pVqu%Ħъ>&b%<(K)jtp:  u܍ϥl CbG_U JﶡQ~G9 +\xE#DD7fJnex {b`'/ަûv2y["<'lmPO(96-ur^@m^t`V{\mSW8 SROݐ7%2v쭋+?;|sڡ{~7Z2<.me\ 2Oe·Qa10xcLQsC3ۛ$cTzd^M. +ۧe{xPo;Ɲ_3Pӡ7&IRKNKE zhf׮@w9 4`//89B Y"CNQkW8(c8 +޲y#>G8w֓eCBB&Mi @zS|ab:3҆-œ_`N s9Π7i#Sd.N vvWWy?USSQ@NNwrIGtD ܨiXrH5{'G_bfZrD%Xje#s^~Ku{ƴhzu!ڙ %inwl8y!4οsv)/u%_ Xscz!wi-G Y2)>3V<[Go.WYv:a2zG sv~8_zV5r>.yn/N:O`8CjW=WOXӭ&hi]({K=0>Ԯ4;a1 +Bx"`- "@h̄ide-7h?Rvcگv!tۙT,fwuEqw >})+WǺX6݃q }KA6{Wenn}Bq&G xLNf 5&d:c +=zii+@ks~[uөƋ8>fJvY`so!/n 䡲1[2|ǥ Y~W.o2[|UcFƮS&祀V?Sܴqud<U3 xǍ˝uR|F"Ѩ Sp^Lʘѐ}Xo؛mS|z"%KQLsPPW($?MV:_ <_ږAZՌnӰd*]7fӑyot狡ͺ*@nn7(H}V0t8[GN؃)$퐧ЌVn4#2?qpwӶgK_/ѪsϳKL'Y~Jp +}z>G6ym䆭?sya~֤ycۏkB_9ڿ ._\?.ک&Z/!m2`$,K@ ءcO$p/iVn,]i?& \r?P'б-R*)In@>/*7 S |*M܀\e //! bvYA̕ ?Qe2ƅ>I~X8DY!֨l.fs Jp=Q7̓8aQ`7:A ۔J-q ⋳ nHͯa_@J!|o )V~CIB7rR2%5:$#@4'NA!"VޖTM'+wMqA ?.UsMpD/"2xR6v"t@X% f{9TA\1pSX0}DZ4Yh;$\;*m#+q/29עm,16Ms힁e@ +w M.h<U_5)}牘Eg| +C͌3͍_rS {.V3o$ Gˎ*Ѥw'\[ca# :](+ 6UK-xѣ--/lKZ~ΎAz'jc~c9@:bmxTsYYkդ54 mM vOܴX|Zq)t:eܦ@lE}&ɀ|NE|6ɢE; +L;CVRaQmc} +֑#Nre@sicD]#)Ðy/V5r(G(0W?p:_t|v@u4?2RZc}y鱳zVkP"\EdL\$\7J~( <y@>v 4 +ڱ[6t'&~q*vP  )Qƨ n9⧾=_!Dn7Y /TLdO?uP.LҵGX&3nMUèr<\B + "EX0{ ;Z̻qv46,HxY_]nRSofG/(Pyg^'v7Mp w2MW@э( xAނwҜ*Fa4=iJW4e#!3lʉ9e:WT)r_ŨCib oөZBSNlRX$x/V'LQuv 6j~􉦤t;2A3d\0 +we'lޜ3 tñP5 MNKّZo D_ч?pwqTCʹ#Z<:U@_P+ԗ-T}c+/ I RreRE8wsaϘyP c< YM4-HZ5d;99$܋5xaA3Z7`eÓ ^+[fcdߠdW-V4$uJ!k hƚ @ĮjFh#hķfo3~SO5d|8!x=1^(V>ovjim5ŠQcفnwՊN \Rnz-)8+=[)LLJ%C+[A |`%YGQY;{7/NdW2t` &D[,Z.(КP LV(*G:y/ <םZ4!!:{k9)inGn qrR{Yb|OxkmQAKU,\Q'siujH +o|UHC4&%iT{J2*z:c(r:9h`D::1Ln\3J#0c*:Sؖ&JuW5jZ0jy@D,̟D +^Ɔ2&B'˯ G;z f vS@Q({D  %!v"o7qd$N7uBs!?=l_fCD9h|H ܖ8֮a+jn0n$bORVD" EZ_Cf!Fw؇Z*@J ɇux% + 6Z T\Dj [&;ge1PtRyt~óN +z0qBN +?&D<F'mC(a#_+xOMyÇ& qP@d9+Px'~I_)zl +:8~(߀88?zrnUO +3k>Oq&QC/ 4}q?&ͦ mp߆Pbb= +Bj#Y>&Ign4+MF4pzx#u?1~zh H<#o ܿ!@#_ +%+8x7}88o xAԻ} qN3g5sw !an YHb>/TF O; h+Ds{-(o ȿ_ovqQ} EǠ@/vyi?Fg1}mTJƗJŸxƺ\l=vcHG/(EĢՌ̚˕C_0"FRJyW>F`G=H#ӞB!&r?3˗@ O:d<\ ˅k't!v"4^It{~ ߚӇVM߂k*F9ޙWFUR?j:|d`Σq^>a$TmV.Vh3*LۍU68ܨs!1|je"*Js!N F/śRT JFf@ŤmF\؇?&z)|`Q+=S)\}dDsuGL,}Ǿ߳4o] 6umnZ+hwCT{vŌ~ i5,@EP>g0skR=Mc>]r~ Ɍ4;^n3~4q7ggB%{$P_PK~Ŭ;Iw_혼% +P÷O$xhT X}'$S..!ov^0|mC((iosib }4w_loy9o D4G[Rqz:H׀n5;(+@^HF z=f6[p5cv|羏 iKa4΍.<{/v+ VDG svotP%m +@{é +Ff=|4_ŰdXIIb< 4y7Ә_輴 -fu_昿5.vݟmv1j3ͷ;!3m]>Nxw̥gjAZJިumcź wc.br.w]r._Yvs @~Ia 4{|Mx1̳ɇI?$_~0ukuL^=v3<}'Ik(ϋ)i~s/NDI 16t掍K{nZnﱗ;._&gR5rСeEՆ0Dt$ou|NFQf6/gns8en:Klæa.s ^h\=f_sq`Ժ1~n"M}_Һ/uk YbW6|l9xwPEA +[AOCg{Φ |5p/}Fpuvn1Ļ2Q04 msw F}HϠ$sv@svRUsz  w,=|%Qԫun(AJG Eb"݇;]Ⱥ.sxe.b1웽žۖ5< fo[xϢADhu]ʸ4`&wxqԻ*'؋L[V,V8EDh(9ww`l3lwN}IB ? c64q%?x`%y!<{Qvh#xa~ƾ;i {ХÇ.hax38Y7XQY/0h+>5qO[p}<T887຾&s<Ϣ!}1Y@}ХQ8:zy^n4m完c8>)+ +Ť"( v6,Q1yG8i+Xxu9t'+Hh_ȬzlR,_u !BI7pVݧ5r qs^ƻq1:w_vy3]j1{>%?<OCgf<'q,=cxw,<{G\qwNbM-d)xlsh}>Pd>d< T0,k#oQ1q nG(B:cx&Rh!$Z }B;0q$M1^94ƻ7L8Q`rE?FmtyAOoj,&Φ en@8Ov|n=8Xs3k>~#_7?f͕k&ޚ)@rdK>ԙ7⺻Q(y8Φ q +Х_!ޭw@yȾ;ǰ3pPg"{BsüȻ4(=MouMFGK·( OdT +F8z%S2>t) ~# snrE 0wb=O8ѥa9:|G1Y@~18[|C?wԭ5r'/OPqF4GHW +% č:ShH3}oL ݳ6øi^C !@D"~@q4o6MquwF<{Q&!)k>Vw]Ůl 9t=Å <}E;ӧVׄ߼޿!7c;[)A}( WFKkC(ul[Vc r+ȩ.of4{fp/sh5-b.ӷfn~q7BgVWWЅ][]9al=6$ +]Mx,o}iᾎX Mz!G_FLg7cǰDtRRQ'|~ 7GW"%@u@9tj]~q'VrR}ҧz >|q)7ṏdrB9cf;z7N7a:shu +cp̟Nx9aYa@ߧ5Iy#ހţA~8ޗ{e} +qE3;x?Z=ĝN(O;xfζ!A|Tǿb.4<}?Lة/slbI3ow ^[]3on s>)DQWH2!kpO̡.ndKwKa:9G/qDdKbRI5̙@ :v^M7wر4|q7#anbkɸď4 s@otŋ:>7/)rM6f&nIgE7SjG%y>]~78FYN7bVc3~o)8OakfynKY]#pAO[a=7z'D.qfs-6;ܩ2zn>Ǝ͆Ke ܙEsܘ?: ;yQ)ss5?׆ED?T)+s!JoCXi~͠k|_ + S% q"/>k>f)/vg8 ĕ8R[9'qs$M1ӈ|pB<&el>7{'uB^ ǿ&<-nXח ^ޕdlXb7L\ z>E6Wu_иط8t̟ZX[F,7ԙmmX6t1 օnwauv^86t &-..\ ue蘯߫o/`KCZ19 {9`j1,[-֒k: \ޠf]f]X.Y-eM-MKSKTڥ,mv)ǚZZZ58`۞څ58R;<<WVTUV +Kkg&6U3eVWUR,+)mU6++%V]Uiک6{EJM]QMQeMQiY]eaaYYi䴤ZګUՕVUJ²JYiUT٪*UUV֗V +j*+KnVZiSTZ++9+,*,TUTU\UV6K+wcUeaaQYUeQeQa]ieZTZ)VTVU*JkjYZm*JJ[%UUUJYUYUYUUIYUaYIYUUYUiUUWSWUZ***+)*-*-U֔UVJ{E5VEiWYSU*mUTV +*+EujUTZZRUTZ))*+*)**+**-ՔKJfTSTTRSTZ-,VvJkaJK ++ KJ+պҢfSY-֕v** jJJ+beTX,VVfSYWTڬVfZVkJ+K*^]]elU*VeUYU֕TUV[jY*+NIeZmUՔ֕Vk*5ʒjSlBVNil֔6K*;jYXWRYZVVYlUV KJK{u^QieiSZ٬TUUւUWV{uuU5j]UEieieiYYWUQTYZYZXRXYYXXX),-)mV6JeE%f]Qe,,)UUUVUւT*+KUejRT,+l֔6͢b]QeIeYWSYY-U֔TJ*EucQ]eeXZ-Vv ++Ţ²ʒҒ^aiYa]YaMaU]a]}eTX,-*,,),,U*bi,)6NaiiZVVTRZZ)+픔֕U KJKKu5%e ۖӑ.e]W@a*]K;J|9u| I +`+[Dϵ&Aq+u_̗.i&DyYĥ8B +A$rjj $2F%!9nls^RV@VA JחpxP(b^'v'$HЭ t)4sԗ^zvtӉQ?y5. XA|C*thFAKݧu~ԃRz1Зea/s2bҏJ?%/& 1ap!]9 hb}t,aysu'&mA-R9Um>At1yIQX3F%&D_h #@ W ! //M pnrC+g`t| /S"+GWe)kD:9J&9YFB˺)hR@̫<# 2VK$bNP<ٲ]) +~*̊N /a¨rI +f+Z +^A`gd?*yK*_rMs(;ML!dcB9-FR>`V.VTS@o¦I/K"%,@L- "c/ɍI$7R'~@ܚ(`Rc!#=B-Pơ8 ړP zG3:L5upz. Ѥd- SR6q$2d˜1c+  _ψ\ 8Չw\b yA"'GK2, [YX +!Q98Da#HȟcOPGܒ=䢪K@F69F>A3]3QLIDܒ2U,uT) $nTT^W1$BI %0icGC#ErdyOn{JpjMV]U܂ծߒGI<|ݽѤϮ?~MR̗["mtrYD4S4S,FjL1Ǥ~InPq98D&?R:IHi,`ڢ*yfQPU_H8F)qLrȕ<7??y1sPw0RH2JWNTԓ@'\O܇KxH*V_+`®\ `gPV_EU`M z[n3"X+dI8[!1ŭ)*}pH!~E|PA 8DG$I?#5, I<id!祒@RW=M0[8IčF; ;ȉ-z,3cLIAlɎT+)a18s!@V">p@ECʅ։yvxK˨Ā?_KnI^|DpuIdqI^fX +Tܖ*in"T 1H"8ȧ7J"!I*5$N&_bHcINhS3 {|n>9\mW-$F\=xAfxk)AKSW72PKLM腊h"9 +8Ǯu'5P!w|l@!^s4Po1M2,7lbJL_zwaa'<Rgzhs m\tTؒ5e2Urid^gkg&p1YR&z1qYQiADMV:ODiDē7KRzD| A5&2;J +{Ea3ߍI |H .D}Ky1{&+l!$І%!AûDZ~XhL z*V"{t=T!emrb,^Ѐ'[Ak1>OԎJV)_r)C$M>XŖ+#TE8N'-Yq !EQAq)^{|DCr4k+cI8.ŅMg9QQߖ~(S#tjW-{- NJI +~cJ'lVЙ!m"|rYJw- NN1~QJn'GY`h +U*k\\VJ SL UPk%~""0@58zu}Rl^ݐE;P*bR@Ĝa\+"m FP•]s؄n#(`պJ`ka +iIrbxL H$GUP2,)7Zk$9d5NB*(<ٔH^?6ȸ`8/zhc.rg[ MB2$1\!)(|n@ zJbPV5*o +*6WV+D>EގeBZq=&\]6j5-f0r +!(%j|UEMH~%o2 ASRgU^+,0F Aa1s`y“Y<500%JJc(rj tV/XTdojw19 I;љyK hSL#FYZ9j%+nu==f $0,%f; nR:pSr}ƀExkMbx#._⾛ǮÜ|/,9% plW10m-J:&9+# |0%)COA-{E)0 /\CU3ׂ{G0"#X'!6r^PD;6 SNm +{N ȌΕ"’J_8@u: A)0+.Nx P@LR+}yJ_qP =E J?cx{ 9 +MH!@AiC`R SҦ06j3Fܕ7@b'-m ixh/Ꮒh[`@( 6f-"BPZÔlI9Oݠ7$k,4RZAG)ў䴫Jjމ5#}|xNY6: +KfIWȬvpU1(mA:%"'4]ج%xxT5Plb;"RX62m&?Dܩáκ*ݔ'B w GT:CwT +Fu7"P`hS0!@ =f6 MIxE +8"W=YH3YS՘FΎ[c¹9Ov#u_Ⱥ1|k#]vch÷f w䙀u7pzxue*O "{ۼ vCyV) )4vx#m9?H T8zN2代TDZ;>BD*5Ǭ I%b^:%geΘ#8a9XIN"m J΍2 |Xh>{f0QV12+tEaɴIb*坅cvxX8t4_B9KR:KLŤ?*-b^@#6Х ^ǧ8S*"/}T +AOq ']Z5Ozigx`đ.9c_DٗuefWLz$Q'2yrdX$ \LPÿ4΁* wFۄ<ѥ`w +ߔ:y?Sh4* SCc3JE=dudn=~3_:zFy,[̺cN/A t]ʺ0zj\ L +eU?~46l~=wS;fosHtKx}"'?VPκT qAEãgpSTL1[uDfR5}.%Vs)8lTM߈i}AH ~J]L{UYGTY_ꄤO$H[ǛAG4>:6qsX^b{,ݬt^ l5#DIC}h!@~^2&RR0IÍ> Q[Ofw$_‘s1Ckj!<?z y=Pzt`ĝR?ѥ!9Ի.o ˿4/Hp:(ބ~"Q;4o_FΎsuu^9άn9۞wׅ-,!j|HG<"NB~TȚ zJmڐ9݌ftQQRpMV::OBi@c^ojԶҡ9#Q[AE[H' E%4"ў*`Ҏ`r*Bzo ldWQ1ޣAM>F]<,>.z%1C:< +X z,?}N$dTJ>D|vt8Wj`Ee4 ouwΡ6๛RpH3+Ve5զΪ:wA`EN >H[+7m ϡ;Ațr{l$ P8N9&ٛY'u8TD<Ҿ9SB4F`OqvU5$Bh09zN,Fxc +þ"a_Ѹ x(>|6ڌuZ챗:4.]x7LB){5Y5Y02OO$kaҺA5ʚBݔ:}puBF; cN` =u~s~ +aB ?QYC":O "ZS1Z[h-t|N 4n߆PnFgB/yMR! q'R|?$!(!P$49Ư+CF|*MA *DwI9T0,R*V^aY'<8vF:jS RJkcd-|Bk狜#\Q}{f7Dm ?cѰF׊X?}#N9T:Ɓ$3֥;?}_Sm9BGFc>vwyp*^*$92ulxSg~! PHK&] +|)R3;64@}7"5*ٿa},ÆVʀu÷68{G2m#Tem|?:H3l&JAy'Ζ{ms5wwۧ{1̣E6<~D>-,tQC\ +Xҡy?h嬛P$.neuޚcuudIqл3ÅL070j3yPyA6)}Ns:# +9?}X"9crZ9AŹIRs AJh"D[ѹ IMQy;*&C|s<9H"R1D1~kedT.*!JA;38=6F|r?gԭ̡.oft^=k~Dl;뮯5'945̮[캇m hp++!S-(mwRh+}Q'=֍5utfc<&i ]3BgF<D0A7evWBrp4`8igj:owfm)|[lnaba6zC68))^ǐώ[_=8{ƯBx ܯqzBTZP3(-(5)sօq9tM GboFA?J;5{ Uyy2̾!su9`̈,:aR0z :v.ynKZ + m~ CFmAF7;f/~K{jd#I?u8=z _ȴ, s?9=}~铌+ 8$ه7OyÕ<8U<7J?~&\w| ݧs2{m6q5]' \ef=6c:&#J'$>}PfCHe +M" Iڈ3|O2k㒖j!i7paI#mM`ݽCZK NT(v"KeTϓj^nٽ8bVo;>ֿ"`VRXlkgk"K;`Z'X1JB:X<|\"/SXlѹ|P]Œ$DnZmmuo\Io- M^G?B( /Li"mDow37nF@bZsY)l@I:I5\T9{ŭ;yt #yGτfl(`5 X8:fEZ^G5JD=$}ժ93&P# (] UNGBG>g)栭xWאKP4HptrJu AѮu +ۨ| }+ :EyGP̕FxI4L5v;U~à4=Ɋ0nWm9t -*w,pqp+Jo{0RHu `d=%^"Ex\DJu+u^+JC.th[8bZ IzǾzAG;Snϣ}y8E!QIFbIX}M'_n?oH 5|t>/ƮmmuuNcܻ@KB@EˍS_b߸1nnaGB ΂$Ġq34x +bH +JWVi$pbW&bvs1+_qAc(-JI :R@2crKኇ,"\ +XVݟK@쳏BõGJ&mUuR:?Y;icK/8:w&i;@IkyXW/zq4m#gO@:Qo~JH}0zjż:}l@\$\ t@1. +@+*f r:$>4RiaݠFEu*/!5I<.gl.cDn9. ٻd$EEDN7v>G|9o!i8hۈl|n)ayqXXI= N>2:؄ImO^%M G^-s3lwO_aJ#ŀBCgrF@0Ĝ]LH +z b͠GJ/,4U'kA8кNo`DcdJ4*"maJn>DNװԖ0KW/ug6涔c-d7Qfފaܣ ` FuaѶX;#7CNh֮n`R|90 U~V!&>T qkB=: 9zuc^qLjx;&:B r-(aE@LW/bcp@Y4q&$<kQ6 Q** VVk~%q{#'/boҁnr}r.d)x|sv*BrZZ +-j^g b9F_IAk)p* #B5 {@_t2VFL `Z,aScaBHY;@b{4*> gg$S- XW!a){H䛳<. 0& M®XM &7R?,̝B͚H r\мaƵW2`~!BnŀB@!C5xu Xۺ`=&L)DjBɡ,b2.2,lc_ +.XQUBrvФta"yFv|Nʸv#4񬻮gGP乒NjZ\uF +ӖN=\#x! gq@bW>8Dᚨ\/v4B:[0*]tPLhlD:h4/yg[:BGV7;;/`a] BWR3lr;sCqXrUvyyeyw|}tq(gɀVA/->|i؅r 1 b)a hPnn1`0aP?HB ifP@:Ds4ݪ&[ +|MF0(!gRĮ) tNReHX2Wdwlw[e}XR /p9 E bm x4-j>(?<%6$Z ,N Ye2 s eL}K"W2;Փk4ZB^G"TI܂])4"u(45jUWӏDc rj圏T68\kbrmΡk%@,hi>Hпgs" Ā:fe4IX3/O1'*\U$kdP!LPȭ3neOXPi0Ri9pNX`|GjiQ&eέ ƍ!DO2\1FV$7L)uri>pr +% 5Qs%Pb]8DVc7w3hv##^ԠSoLBXǸ'²0)6| ̟DEƅXQXB̥L-6/mY.lZ=>'l`H+G ۫Vr@ n R;W_Ov[BZSŠ1H\PD=DV}YӘGgmhro(.#3U ιcUЋ@FMQ^Ąշ_`Q*>4Ө& [6߽l;ֹ4 0j_ CY/KXph+VlXP6qA&D^Š[PuN#XM`yȽ9_:ot~ ֲ?8U2S)#$jxu~WD6\+%j WVgݥR;o`XjM] +8XR|>E`Ƭ@ϊʽbʚe ,fHp|R,. e$alMɤ],jtbE8@I>2+r˧L$5h!yr#l0;fl;yAk( +'bTPH3(Wp@%e@j/ c1LQ%MՏTuxʉ1: xX7%_, +Ѕ +$@Õe/򕯻2c޲^B. jaz>% `YŻ@ ,ٓTՙK] XVZ{q:n&1n9RgaϺ9u 0K83H .Qr*:1ᅽх a2oV+bG.K(ŴWn+ SR j +i XTK1 e`wGX\9X`KDC\6|uIՕ &W^@ P|@BYuԡt~N. ǨQ1y\R 2Pɰ^X{94ggTY=6k>saYU[X820 +e$pCQ4]Y֬CUYE <0 +s,H0%GKK#mep*O*rPvR++,/ +BQ9N`fam΍zXBI}t A J*.lI< ҆K;10NDKq \ +z;,e`iܮȵ 4r^G65p K9N++//-#!2\ #B.0Ds/KYl[.exvTj=胴V'X].UrZ#dәf~zO6]2%Ԃ&d$`kP!c2([tr#~ )h/<($隕l벏0r+/RD@U]^X~ +(E܁d@Y +. ! B@-yY-{- +s1@i-Tnu-`ۥNԙfz^zˏ#Na%޼ + +ol,v nS7Ň3@ J+G ڕb.FezNAK(2k:Q +%NoXb[IK=QelI(XȔP#:X$'WMb367Xd"lԱȪd-*٢*@TfuDʅUK()Io &SI=J`MEݫ[@j#ԓ\~>ti GM)G( W<07>25dg$8c2! '\يr "$9ŠX]L#4,S_ZfQK@Q +v~:Ogl:PtyO7"%>qz"" +2 a ?T +Tǹ#2lFfF SCGD e(/)|%,A&ZSn\I MPa N]IxpUl@K2V>DPWpԡht^zN/t.ұE:D9,=iq ҄M&Ulu$!`]e{({ z#S'H5#% +T@B P? Ӈ:}ӕ=]#t#O/rD؞NF,P. +IDd;I)'"U|q ) LF_S37Zds 瘡I'h!ϰ$`G47z|(IN1ǥIKSQe`D9pZ +:NK6kzM51ұX!"$ɾ-|4NI?J"H( 9 d|١&f@F:85}0rNg&\e$.XGS 0c(#-5vPQoCt>g^Kw2=]`Lw0wNovk`׶~Dri30$Z[A\)Rs5rs2rq$ B846bhwhYXEf؆ M22>#C}8P¥3D,a` t' { cMk`0{l+/+[PXy[3l%8 L223P`mf&4aix#UpƌL#~E2;,q,` 'C*P20e\@L)8 P5ނ.--8Nq\zL・ttԿ"Tȼ W@%2RɐH%_F%c64y Acť8-qB!SQ9CfUqJ9ZFre4BO x:P9ؘe:OL2}l.{c:M4t.]{zQ"odR4h]5'Ԙ{O|@E$E(=(V +8q12x ˰a00 k$@4axq& Ep^:ƸG\6 pM/ezM7t_:ti:L/t>sN_RrrtIwnڙqu(} A GApUq<Q(qxV$:)ǽ ?H+ {ܨߠq Q B=h\~y4Iaz9l0gOLq%QJ/ +_9۹rbʮxnGLBjЗ|:RBt^iM8e7-CzXKcu NTV K*Kk65ӦiҢggQiUY3-(Ԕ6[UfʚQeijigQkgY(4*5*,*)+-%+*))V[ʚZ2kҪfUe]YEiδgiڨ3),ZVM- gfE%55]3۪mXڰF677XJUeeVeEevEJREUeάҢԴkgY-, 漻--K ȴdSdPK.l8 458fyf渜 RxH U L b _V#I{-Q9èg0_"iG AoNC_ 8uwRhoREKin1ޏc߇B8Θ}WLZm9/H)mqJ #Aijm~Rh/^~>|Atx+> Pn#5o*tih$Q۪欽9;:CL o#7CS,,k)-owX Zwc͸%)..Ÿ4j& % L q%Qq1~k ^ޅCT :+ _jOd_m 8bYb4nf/&+ 1L V7t6^R T G!~47Hْ\<7[i_ЬbXK/Vw۹]`ź]pa̳{Y/qe5 dp)S?e-qDA/e"F%đ:yEB"jWbZIV"ڛf\"!OJE#ţЬ^OBR):x F0&SDio:MQ"$ B,jdpQ%&o@}8fO"=:alQ"$o&S1J6 0W:>;0MB ZC@~YS\ҾmUe|]A B"Ǒ/0)qD/O "C4j..xjǒy35:vO D7pZiAzwd2@2y;ءi# q}$KqsJIGlova;Z[場bPR =' +NI6!/e"Wy=Xưj~I@1 d|?|yp!@<~l5vp#K` imx\MgEP7M>$5_ȭD +FTeB%{( ,tQKȨj X:^<"fhW-,q'UC\i3")ʅfβi's +tPY48ORDi/:DZn>"V R;Hہ.&:I+Ig ( ]MgۈlD<$JƮuV'WhU3"$)4I'DH!J¿3D`5kS(.Ccf7a:4/SjRExP}P$8bAZP|{F8#yHY#u~sF?CHħXXY:Ļ[ C FDDk aD<n9ێucȺX5Kg/3:ۂ>@@^qd>tMb֍͚P}I/ngۿ!S0B1B;*~Lwh/yZ@^ + 4oZ=o$g@b1*/ y/uC +;&vx3n3LVp MB9Vz{A9W:* +5&&f]`EP7%4Ջۇ/.(%38i ;@k ŽN +o0@~)s3/tta5xs_6zb V.&/^/VPoF4&pIc"JBj'e3zw7x./?u~&o"Ar6cpnL)wj4|g<z왗g108,3V7|3zr3m%KygYG?P"Im +7[F,הX)_fƶnK=2{l>FohY=|P ~C}` F0zOpL"6{vq#er!G<)Dz5̙< sUũ&m$T˅ЁNFAIu\c rA9Ŗ_ă D5Reč(Ɉ,lp%B~JU=}g~XW32 PE,;[l*cgYD%R7Ȳ/xxx:(@tCuhGoC8a옺4Oqne6~qd;{O^.{:~Mp2V5;.nl>Wɨ7P0oYDϯ +,z$̠܅[-W8t31D:o֘4o<\0m-?V͇u?sa/sfMar^pT..m'DqݷP( U +j$"('̃dhZ4.{ K1{i>(,L'ɿHrKt %ē6 $O1Dٗ<2zl׎K@{2]WFy Ã@#_@ZR_a(/th^Tqi3su7}&XU|ksiȽMÍaji DZ\ +:i}2}oI&zH)ؐOP'SŜۄ/NF" 0^mI\iی>})'}qpø:@FX?u(ZIA>9&p}>6abwkZ{Y*mp . +[ g5m]:ms`a *'9ÈT-.mwV KŪM +D:I>G컊i 4q˛۬sWd)xۄ9"gfȡ.gdK[ncnw/ph9nm lo7&#M1AIw.ԟu7akptx][Ů Y=VkRUÎ?{F}S +uW32k({(hT1AG;H*q E!,iOPZWx%30)7(9\CB {AǛT8\l dRq LF"p.#v|"N4ZD]~c|+goe OFϬI$O1^Gqy?(7oXj`@.w8? _ZG EJ&zz[`B }q.v˅[39qQƶ7)u7*E<;WG֔:·(- 9BJ'C{0U&Uś"gv(6aq[w|x~e_I5$̅(P|H uyr"L$*N;k"97߻;Gw&$[場5 '8ciԻP S(sM|j²ۜD;dsw-mwXǻa.rgc6c:&nޙ<k40&n LHvQvqxKhB:spZ=a^QLH@TDQ%(+VNKH诉 QŷY8M_wi0xh#WB+} ih.>q RHv'6f͞lE vWRB\ H'ͤjh025Z'Q>h;"ڵp(4YK[HErwll[\LuƤģ7dT*6_:;ggqcg)"m1# GLk98.!Zf8.p^GϋQg%#\ɳPfצּCD9߉5>]󕳛Nɹ8)49|y\3G#eK`&!ڍm\Øw# h7(@qԫ-퇹̕m$k xUNaVF$}?ֻ>w!4owQ&gȴxjtV J|)ȩފK W`}Ä }B3;,mq{*hĹnRo! )8x淆:PjGHJPŹ(m&zI +xcج- +o Fv}$ +pdM+s,nVJ!'aҶ1lsή9t<Z@:-WS*uWUo0a/qgD:CUzKd׺I3(htpLd܈230yC1Jg $hxZ:4JA&o VH +3@s7DM@DI1) RXKc8RhLyʾZGicڸ=|_ u* #$X /3ɷ#yW5oK]6^%-g7'&?{7^ ֵ9*jûElv8s^TvtmH O2G0Yi?͟1 VTxL?/IoE߿ģi+ =GYQ"( ;{E"OZHF['!;aȺ>}6%N[5_:hR6 8;vC@SZg1[/y3&t:Hc(&c{A@+yCģ_=&} P!o!H?wFch.L؇/yz}p9/bsc;4Xy ӗ&p,Z Lm shǻHlT.od]fPg0)l+#]5òj{ {CMFg"vwȬʛZS˕JJ7.BM~CbnL [Wu[0,tihb[p]MD A>'Ά;6/5V6fɥqth"NB{(S[[-M>;+gi|\VֽKJaTBU2,kúg% )~xAVzKâ'unǾu +n!xk-TIxOc~wz>? +%=|MmSHG|;ze_7ܝu_θ<|](67nnQ|&5: 5| aX;HU@zD]5sy=8{ ę t\?~R e}=o3PgRC;XG z;a-kݯDm7ؖ÷-rz|v9.R/.Z Y:pm""p!s]d&f>uHn[sn"ސVQ,57&f# PN10g#pN5t> ;3F F-[lAej Ib@Ť90{hsc_=&DM!J*7uc޺.te]ٽpn>J5hcrnϹ_X;|l6ah_:?8:#y7໻+wHbRn, X:Q**#} S͸pDm@~aɩ5Αq%*5^g 1~m FX;80 D ,&m +ML+ù-j~l|hC;pL={5O]0ȿ;R<$H4\ 4quPe QYcٜt[UR]V8Ջ[B(m9wf03 XXA>cݽW@ ]3cJ%Ii'Gpv;bpNÕ]%iL&LDJ8Sn8 ʈ +f4\/+"+{Ɋ:g.!as`zg:o F,z*&UzI~xGxraQ+LI3\ `@nԋI({.wm'サc7:ZꅣO.unL`h6"h$IJed0@QnC` @Âv*Ӏ:xrD!b =kYS'A@(p6N0:mwS +ZdbR~ӿ"cz}.~DwO~yoWIEɫ_dN\=DSF=8ꍜX':gzN79S6}uC2+{@z_@Qo|ξB&;L-eEn0%3٤5'>Ӧez&El?+7oKr3N,O?y];M^ÔyM.E=XtGwI9QF|H!LBeH_(7{xYE/ppJ3of叧^?vC’m]&? +Nu|}9} nO.4׼e!:f /竈T}2ir3bo9+ґK2վ#>KԳ,wgɱ[5Bq3 ~Vǹ%NAb1sjRu>zeS<*-v}ӱl6}=gO1HbpQۛ2W>͏;g=3KQ.sVexQpikw).d< ~{I~T㎎<3z2è|H΂]9y kwYų-mn+ԍtsߏ]-y0BϺ4cusϊ}B\/{^/e>Op ;Ę֏f?垼>V߶zWyPu̶,|QUNci6@;Yt)%*EJt_ufAq#Dq;b_s:EY/5禝qx}'̷(w͍h[[2>tP/AξdHeմ\o*u1,?+cvd]6V3N^Uv \X/z~afwcuq浰
  • -,u.O=GXmJn'OhξqX0+7 B1߽ r/L##⹿X[E,}qZttZOÑugW$btDs?Rh|{(k}vvO[Q oX{:w$::R+yqzr[yN'f!kOR{Fwy}|zuFqҼ/$h4MҏD,Wf%_kWhZ9[י7Ž[SP$]m/ԒoW p +/L|)$-}l7t|Rz`I п.-'Y"s \ iק֜ٿ6qWAD@ArZj[.?9IngzI|H:9(w92K|u$Cu 0#J`u\y2|s=pu[FDJ}EH ߚ0V]|`w}f '<.iGR/aM|-<uG x'ZRpV+Z i[RIJHvx͡|:,kͨT:r'ќaVRޡ_Llߛ#2~^Uz|_fλ] "d]~ 6b!V8 F!"TydfykFa4/ihօer+!iS44:*oRy0?ĥDbOwچkC{4HzFC؍<9#>G|)u,DJ0ML 0\`m2wLr(W+{Nߩ _@ÆPX-R~ (h|h&'PxE>&M*6J3:J aφJݪIO/odm}ˎvx#$ѭ\SȫjA^L}#]|p潀6J;pxW1!$_()ށ3{p)oMn3ئ fMCWM΀@X_3{R$/@vyk0[VCC5xԗ dCחжށ'$a_3O#}S[:w(~m*RGz7C7z(5h֭ZE_j!IH"-zrtul\GTXyoju t؋~ڲ7 ~ߟC$:C/ptR^F &>NhÂ2QӀ1mq}tNꡝQ7pK]_ +h)Su0$ OoT KΏZEc? +s\:ǮQQ5 矴 )hj@HΆOC.VgZ1.nd8O׃HePZQ#u +F@ qtg(]CPس=,niYҾɡoRۘ?(oDB6#x͘Sb(uȯװET/xxNEF} 7fmFuBM5<&w}Unu(Z{ոYm1 jwGhoO5 4;T~?!.+&R. +:=J#zρ`Jk؛2 6a;T$R]GuBp˳0Z/W{`1 D|vc8kRM{ sns:wNi5A 1z4J[{kxZ2 +>'dCE9~A/ANcYʽQ9?mǖXIldIc=e~üB(W2d鳚K@ 2نJTkAb"LTA#0*Õ_=C}ie96g@x4~J2)Xzp@i0.-@2n ňxJ݃!ܚ +p #qY0I"Sd.0 +sƲʑ%jFde7սBP\c (sԮQfpxpK ۚy}4:A%/]5d/>$L$^#pU^"A3fܬڗed̲Rr-E~@CPqXpT|,WG@e6n*U&_P+-+2&EJV4-y)YE>ۤݏ\srK#"V/FlY0EYV\79(GPBu63zYY0U>+[s@E)13F2344#82DlqVT PnJM"Bnbmn &z A +%ym.A͆Liyd5LÛXEW9c_T|S:5ڰk&mUIk3#!4U60~h55 Qg>zzWv 7K"V)5&ƭRi? +"еAwҚE0*ڠ0(N_S9䋵B`v%6gm}cY3Q]> +tЙI5R;k>̔FX ԗ cj6Q(a£|(C;E^6 5b28uM)VOoJꇤn-ц" ']<^oBC='A=D +Dd+m,DR?.. YD |$K.d@͝J +KvZo0ugta)E|zYJN&.'f/>KYi3"xY*Vc&&;뿰)ZL7˕® #NeR w`]ʼl8RP1_Br.=fAu!Cti02jGkξκrl◕E O&܍㸄 ݇O~ڑb닪f +1*,mQ=aWؿ8R}RqAJTGmPЄ(99ψYPsݟ#޾%0ui;~1 &"^4O<;CbWZ ]!=#=TؽͲqB5nw +!_DjQ!|HIn4bk ƛ5%sNCnX2a傧H5$G£bA$dān ,,훱K=:8WuKN-Pg&(wTNkI0LW!\ƔP`zF PblW')U1Dcf99Ay0J齫ˑf9pС+k`TGK޾-cL+c +xl[8ZwX2Ʒ8J1Y^&!qn8'Եc2 q?c) {}RFyh5~(Hye u׊ f~/ѥ\CZ/.ב,e[1>wd{RߔEFU{o=v2Te87JmZ!5B7 ,o1B2@$E=N/kLsAn ²;c2s lp,P~&W! x1A]+Cg)k-(Z]2NXUlMW!묨8ͦ||tkgH&}ٝ,Cls }P)z(Ā)˜uk 템Za|\q✴!ZX9"_ +7BlGmQtSwLhϞs@ζ?A' +CZnۺ:^!]L@$NJ#|d + ۫P0-+,/-d iqI\W+!AFo&wCi!a,j ׅJxEpd?ϕR8_NLP|bޤ}v +,̅WПSnMHԙ' {TVu] M&X͋i""Oou\Bg Dl(L(jXDT.+eq +x3:xA30V&jt[n2@N &$b'ZFU!ط/HIz#{гp z<|=.G "мzl3h 1ˌ^_ (Ro13$QFm&%r^ZvE٠njkDCY])\!R};DJ#OM0EDUoXhcDc[XCſAY՛їӸJOiqԂn5h+/LႾƝg$=O{ks|V{C}UT!|b&>뜀Do_JQu%P?.v Q}eXB]C9ZD"q(^f{oEDnNJ16;n + +d6"f"<2N"|`yW+V`ATHC+B~B0TPy( Z-p:%V%g7jP5=(Kz/7j$^A^ ZQIF(3~$#1(CP\ 'G~jqcwATlLR9E,(q:7Pel!d?݊ʋTX+K@S'nGxrp,X#ǢЊOS9\XB9%Wޙ Y[E:vx/wjюm?1UHr< Vɂiog@ڸ*U#I1p$xv\SQyAI5h .xHRŒlI\LSA]f N QJ\f/\^h@I##d0qbYE1@nm4Oπ;%@r `5AeAL:x[M៤h4gd"FU֨rr wRX[u)Hr? + +252585ca4-61e5-4651-867a-fe8ec8c67fc102230bf0-acd5-40be-833f9364b67806 695.2523701efca59-9f29-471e-8eb4-1267ea4afb942da-0174-456b-bac4-46354d7e476485791252537835 фDcgvE 'a +h58Z.L,րh >؀% +qQĝ٫7\tH<#aVd9ZH,񼰼7tM&tكI5(\~ijAa;ft/9s +t hx8C  l0gQ ID /F|z.6:~14e101ed4-fa04-4ca1-8a4b-ded04d9cb438938b9eb2-a314-483e-ab45-1d2e895fe0568.ml10SVGFilter +/ : +/XMLNode1 /Int (xmlnode-nodetypvalu(fnam/2(o;attribute/Arrachildre; ,0%yy10hAI__id)idxxww1feTurbulence2(0.05baseFrequenc2numOctavesnoStitchsTilturbresult1Compositinoperator2(SourceGraphicinin2/Def ;4fractalNois-24BevelShadowxxGaussianBlustdDevib1Offse2(oddxxSpecularLighting5surface10sExponentConstaspecOul-color:whstyl1PointL2(--5xx2zz1arithmetlitPai2k3k3k144122Merg1Nod2-1AI_CoolBreez-5xx1Morphologdilaai1.adius1bnbn-5bnnDisplacementMapRxChannelSelecAyybnnsColorMatribm311nn1anim2NnonaccumuredditlineacalcMtoto5dffill0begalwayrestarfrom1cc82ccccccc8ccc1cccnnnnAI_D_166Eroder66_2(_1750 5RAI_PixelPlayindefinrepeatDspli1remov 1;20 15;200 200; 15 20;1 1 12cnn2;20 20;Diffuse12dyellow15;green;blue;indigo;violet;red;oranDi6elev18azimu1re12l201010121red2xx1-1yy322xx4688AI_Sta510.702xx8yy51-5Woodgraxx4ddoFloodloodblack; opacity:nnsCdd12nn212(131012۞I2H @(>fMӕ<B(& A!  @Cp @ !(1i" C2Z/`FNv3B栳Gǧ.y@޳FJCݷw  ,h ?Ș+bK< cvAsX["QK"ߒy(JԤHZVcAd{m ;qœ7Rb5nPt;NΤ=[L}nFO.G$IpIgVD=gkY]X*MAAs;D=Q- +N )Am;}K'_mt8;x;RcP +FvskڇW<*uԶlT,ܯ7N)@~oR n KAS3Zi  73ěړ;\"^oEWa]B)E,=cG|r-c mۖydRIѼ[XQ +6x&3 NDI&Xkږ;7$evO).4:%1&>ѷ(,$sb>@ʏIQ8Jup {Rp(76vϴ=cLQxc0 Rw47KwvFT9ThMif?S)n˯ yqb<'i@zj6iMDA,bR +J{mJ?/bI'š>fnbkxkMkM:ة{%rj`s_ӗ /2aJ?VԂ2;bzA?mFqp!CC"q"4@u|Dl~Tփjt13t{#0EYej9JÎ<=I-c3$n ZHpɕP#mb3q)TX< +IJv ? z-(ɧBe͎">=9_ bX0DSe+FL-TLgf6ÒR,hJ&NO+Q5LD +|3Dۀx<.bDhLb@  :3ȍ4ˊ 4 h|m @;RE J,PP#H|$Z`IE|G]m"[U,9h +I,gZv%0ARckj&3R(S;s粽()- n\'!@BwFS:T%TU%ًk34{VrA3kMp]~nu;V.J&*lhW{?[CIw-紷 z{Q3e<*^{@蚪-$&QNHٙAm"O$Sjg. ǔp +'Kh8{@pwI&Zlv+.<}C0E:\FDDDDDDDDDD +WFAء7Dm*}~T)R8D' 2 +T ]ؑfۈ%='n(&֚V۸g{|wZ7l{w1FT" y$Ezgq^lW{߫~9nK7ݴŖַ}bs=l-TWncIuMjUr*[-Wa;7qv~|LEon|zmj'~@T7q-wNoejNۭ+Ӻџ~Z7[l_ontt6n??SzSq~^moR64gMkWOyZ_nxݔW.Siۦn+L&3S|}mw}@n9&[1g޿m8%xogzo6ԓtOηٷ}gͮۼm5vH@/J5t.lݶ;_oZNmoÿ{SkoK6o/Q9Pp\q\PPq(4CW{)K:6=ZpY)޵m?m/a7Ęv8q/w{oi{-pu2֙S}t[9N}|Rtr̯ŘVrzs/Wƹ38WM͸_o,otb7X6wkWvoՕvj =2MmhSAi4P)Fi/.(";@h[ogmW=oy{+,q=Wywm׊Vyì +JC4&`xNmޛJC( PP`)Go@8TG~";F86kgZ+z?SϚu[y[L6u՘:_5ߓ񿽽Ӌcs^5zw5ys8[xkbV7k5,= #x}@^-3kn1תwiW{q}@Rrܠh:x"+b鬼(nj7n٘#"*2Y}ch#>Qbn(GF[}s49NSm[mn*4PJCi( 4PJCi( 4PJCi(M? +Yg۫Nkp5ۙ3NԽR<9WK?yznnW&pmLoVU~MkM7WizN&6oS)v= {s^1vQn8|Kf:= lon7zmǵj7m^1vuvQLVε5^^4Wz VG?MC0:y`+mŪ4z, +KȰ# 9!@`stc.V@+o6:*90GiTV{ΗjK(M, +"K,ɘ+;^i鿔JrpRd<0}c7+UWzTPJ(-o Rs6n+;LLfyzϿ8 o<U*X&*D[k_؝oa7@$ELoKba*oc1ѥC*5N-EzG2+c6s ?oκa&}TὺS^TDg̃/,E@J@1 `by`f9z6h`4QE~`W0~~Jǭ<Yz7^ i|žGqNbS6®Xٕ +Jk^cjv*RV˵*(d?#zx5vb712R{n@uF_Ai}~P؎6ڣbb +J` 6Ù;oۭ1k\3߿9XyjxWn붚o|v|l3i1pf-k޹>V9^^+yv_muݿ^w߿ּnl;Ώ9=[[g{^{^Z^o?Q>κn~-\۫oϜclf=3w&4wkη|?fw|5g8sLm3?cͳf~\W1kr7y3{τfn//m36 gм/;c1W׼{y5[s_1^9koo۽9jkݹ]qyۼy|skpf̈́&ϟV۵iY_?ki:ێş֜kk-⼹g8Z=[qڮλpf}kz_[s5ƙԸZkƙkuϙqjg8Ο̮w?cZWZ[{ukZg}sp&Vwn[}?Zmwmk[3眳;?޼߫nr_u^Np|Zom+gBxӺjl3oktWs{-fB>VmfNk[^{pޟ+z?iw[pƟ ͍msոZZ5Vz3s\7ÙfBs_3o&4o Do+߷zչ926:[~늽Znuys{o蛫:7u㼱ukUʃDqe۷kqe*= HTize]i~P}0J0ֱ<-a3OD04G9cj5Z:7g]| T9z"U|~oV!zPEv[RU & GOjnʐ$YҺ>*RJ^W{V:-3ab7+;Ѓ{ugNnBrz0C^X?Mei0YG!im|ıٙfm=kҪj]ێۿK͸ǵk8ZZcZ?{9W-ߝ~Z]Z+wu[[7}6{ZsWoju3޶u?瞹kY\oϫƵbl;m_~~@Ֆ A G3w82P)̴*Lo͊~ ̴ 02|0r +=rM m&9Mb?PV)ǮLZ%IǥJL xR< {u i1P3@ 0WqN̞i+~:o?+@JיMc8,oFygu;kT ̫LsZeq^l##&I)NB4 vjZi-UL(;4i ~٦ +c"Ĥ%lމ2zaF Yh1BbalFHi>- +k,yD^E"4\,Pr(c1BdIPoa@$T">Bc!ϧ`>&̳iM`x(uX ۳Q xNP%hFGdp +cbBpZcÚؘlT>ϢFF^+L(wo0!B`c%8CؠIpu-a`$(͈Ad4#C@s,0pBeT!cYh2-!Ձ`H*!|,Tڕ8 )("0p= LgkyȂA)7p$JlHyqHw)HSpaU %\P" bK Epс) O 7pE.,J `hń\:<A`E{@"4!5, +EKP2#m)]:P,NYѕV8ª..*SQx. +S)9GbPtd = +q/ʥB(V,|E29(XdP.()ol2ab: 'ۘvTA$S0. +҄Cl>ŗ &'ׄNV.wlR`ZIšcu Th +t4KZ!ĪM3i@.Rka%vѸ= AP 5`!1J'@ȀN dAh2.+!2V"h吼LdJ$ER!2pQd\TM"BG"BA:@݋K.w%DMd"Y$< DKPʈx I^ +/bGȾRTfBE""rՊHĩ DVGfELNbT tM"48T( <`#"t-R:EH8Db(lAuL:u +W&WVvb+4+V6Xe&S*+m L{@ZS Rd)&LٷI=d#=)eKBC:.6Lse0M)t@8d(E'pˇ +F攐9,h4ٞd2)U,#0DԳKRوeH +t0əlgW2&l)q|.q*8KF2d`B% UJ= !3ɲֲd If1d,2m˲߰ +p*+ IeF%+2n@uP4X%eYV.$dYgddh^*$" +АHϽX&E(\7!p.,fڈ\iɲ0#ո,wJ + G{@(c7΅#bÒT0lb5q[60.%HC5= j +BRs\T@U  +ϑ>QT Z;@؆t&+q(PP(X8"'KdtlЊ(%g@6\\`zl%PYD>We ,.Y9D$Yv.% +(P\4e+b߂ +lRğ4ŀǒc[dsqc MDay, R%|@)m%H.<HH}Z0-@`:W +eYWh|6>eYFnԓGLu>Ye YdY#Ȳ`C)!X,cq!eYe>@% B!_HP,t`reYk8!ɲA˲ ؛,˲q ˲= +6˲Ç\{@" ,P̣aɲ +ԊU8$Kdub̲IH,'3j蠋0Td(B>Yi+Qy2: fLfZڰF$?ЍVP7@$yךu b@vVa9a6Ru_v",FRlP[ Hˆ;aRKG,UϽgDY*ٍU~)9Rv``ITy例HJ^ddy]<=2&FGo@-2%r)bdը6TQQE] + +endstream endobj 135 0 obj <>stream + +G)qa'z ưS/"wHs E"t&' +tPX^\- f#8u)O! 6o.v +PX,Ԣ! +.vyU +JW)1Q*iH4D=$0&p%#p2H`i ,$\rG7 x +i. }j%E‹&{^!eqpTFʥq$%AF$#ԔL4(c8 çpE\Mi."+uMr MP1`UDEAaՓ*5@u^ 2+)؅.cCJ*= YwYe*= $EbGҐ.ځ,hUAݥ))a0,2KFݥ:lJsy ]`OB2a$J*(gp^dJ! Lhɫct) *&V|H+\DZ+"Ϫ9D' b(҃ADVMJͥS= @ht6ρD$h"1>u EM4D$R\<̇ѴP98)i. EK˜*,!Nɂ = 8(2#!=n8 !CxM$FDj_iQ/bC"upLA8ZX/y1 Gd[HcG4~D*fPTq3IVPld,+ahHeAa YS+ F{@0 +A9YhTH9Ol%NQ- +3^pX. G ax V~=jMh8JES-nT b4,Ykd]@%C`,#HB/zY!C7.bCPFrt| +gCKA)-,Y^ +OƩ , KfK7eSAѢ= P`H/Z^^혬.2.^p5lY[h)c `d/* VäAsA;{!@eCaX*2!(\2G Spi.YIq$s6@qU,tDc *]Hm&Id\JCp)1x&8㌞GB55 -[ KbZp1*DE{@"*@<c4hh&RIGQ&C` ddb*,VZ"sؒ.zЁEGbL*E\hȀlo]Z +zbs#,"ҦEkdeUA1 dU5 ɚbS1Sekd) xv(NOdJSd=mT"W"K2AWdTX +LB'SDLaJk-_z;Wig.b]$ApSK,D{mx(TaZ}`wB )U<(4 AFlt]DTI!hR-qĒ1S1)+pd3^/K󢈼Uv0ҵ7kN+3ڀq.&95w`8)‡Dҵ6Fލrb,U :)y.`bqzƪzXqlU7bUOTr* +"E0J)U*AJXg*)&N + Ej4ЩJ ׇ[4.- +jeHHP2X2 p#D 2h8 04ŅO\dBXYdl\  DHZX_ "% +ɵAqP8Pp`4G'"0!Lh6q, DҴHd bxP0 0`HHN*܈p""x"E,&dxӂD "O&@aփhA!ʌU]$JeKקá p5p"Q«A',>zؗAD4Sũl(&N{@8*F^o;! @Z8T4ީъ0sEBhDH0( RI:ekZ2J@2F@ RFa A1 cBԔ U@ÞiW=xNNpH;Ycng4:'8JpOIn&/7*Y??X_\R{:j|jUx/4ix'^)Յ|e_85ޥHRE̺ʵb +4qnZΤFRX(Lijݒ)SHpbLJ8hM|B8e8F$ ImTrj5Z8I0u}CŅ$TbMX+{kn ȇG vA]нV}Y8 + ?:vGbj1?v] +%&EdKG8uZEnӊZк )ʹ:4SqѾ:6ZGC_ȇ!\)n%CJ- *Z(90{$Me͈ݙ$ +B124$ʎWTrFhf=FFJVE1 +TfMgڳmʨq(U~mVЬx/"< gyW3anlutM]:ÎؽչHB1rRBv.}#Xoى YAk\rX:>tsX}L"9maHOytD4Ϩ ^lsFh/8+vo,s]lrs kWnQ9F2¦ɪl%OLFzQLLmbXW]gPj܃hy hއ1\kЃZ]Z/qn\;_ +Sy;&Pa g^ +]p\ ߥKAay2&]+BVj2)?Fh3#F{2O_/~+K'D+IMۃX,n*4<5I(h&>+#}^3e^ XS|HnecT^jB6M`w׹8J<rK [EfEh'|\lCH!wc{}r³UW#<=ZA!LD=<ԦV +m4mn@vxy1zqHI`csS[ʂo{)B48 4Em~v?S=}d^B +B&ݸ+ 0K(tMQa2F4Zdi}JNQ\N2lfoIm(l5d@ ! * N1J!KC3ƹiP?uz6&Xlܺ\3EgH*.4;P[ZawNo׊v&3;1z CgL繈x^f#nV￴@NAP84*Q5/Uv=r߂]V"qN`Kxzz j'¸I2?4gnjBE쳀CPvr\B町9[p-/YnD_IҲ\ڕHBZꅊ{84|3|׫E={g @FsN;LMBseCNcvX7-pz{U(/j|i\xjxmo㞯NRܻ|df]˪ H`S·H7Oz׺Vm1\MCؑ!we \,[ MΰLzwt0.̕ o؞A,i@LH\ʂ#NC(RlF̉#S5t$yc&T[NjgP}07eيدu$&v,|OMnQ 3p`L3Lksow;͉R*BOi:W?D"tc~AW'|&|9,KZµeKl:8C0|0 H%9DThKl\C+`F9:7gY_fYvΞU,,F"5*>yv.v‹㌝]H;oR4ͯU4g%h؉#}6px5NQGHwމu0H15[N>v i>8VBE(,y>X&V&Ml.k9MBBFCyZ'1&aBy@yU aJXvK4-c'ʾllzԧ ohaTH!Dy% +Yl#R@ў:T.tZF g)1E]jQvW1&yLKžd^]D#~Rgv\g!]HD{ɚ ӈ:fLG5Ո?Jz}ۇMECg8HM +);Ib Bxǣ18Z=`<Ek*?t#3i4{M@n(*+| +D./YW1=F皁IvH<1,@2BBZPشxUjXTPņLRQ<]?y=rkǚ^ZByô2e"V/8ff4| `\m?LÓ(tFsio]K԰bEUVTOjchsd,PH{#S'+ߥ! NtDo= {%f.Ju*#(sHhle!5c%(OCJXpvYCZDjm=ށ~7?9%$)S`>?Kշp F5W𙞍 lN5R10T(+E\lDBl8XΕ.M߳jnK>+"h6Y&7^h_iOϸg: ØdWB[FjPP.^WϾ-WUQAI5, &XFI&A ;!$ Qѕ3(\Zf֢39DJSS8mb`8R(G=C*M;VJWyCvԭ"ͦPֈYCH" ǁLq +9Njv!Kq4P}U*bU>rou܆ߡPSdw&C?oѰ~&% ٥U?^};~GWq!Yv BXc# Qw `̗t2?U]؍JÃHXN2`mCS3Wb!",] PKfhP"]HZ'\gT!^o{&(3y \Z;8*N !.I%,Sͨ;p%ҏ`kŐ=BwfLtjcB$n~xCQhVqT.D{s]G}Y߁33,s<ѺUO/ ϕ{sO_ةu[Df%&)E8l=4Co;L0<<.fHU>J<{$M5t&қa–Vgz-T1 <\EaoѺ3cHr\TAoB +0Dx`_cSӆӒh2ȷ^r]Ĺ{橦:'y#ϱ34a XmT/xP0/g%]ta$i3`8+-L oc!}ngD2`iۈTdʯ ͸k"0һF`~Fh\8]Eа3n)x*`;V@rQ믈eu:ƿjWԮy+Kϣhh`0H抭 aXCB-9fOd1Z3fGXSp,#@QnM][\rTRPڐsQȂ>|@_@ A JWChAY:$crLM)B9|D5krwѵ`2b=џk4m @bMb08q؁Z%H7bgIed>uSYw^TT$|!ڌen'2p_V6i&9NUs̒WxB QA|79E*7c%O +Uʅea&VzqT6֓eћN"ç'e]!DHgI+ʹv'q'"@CvcnXHk!BoaPԡn } %vkKgd`P)YvpM.'FQٌ=tg${-9#W3#8+O O#^6'%ZAT:ZȢ眐Pf +ב) $}dJSw6AI>^󣍂xŝp55 ;.pWwc`؉1H堑gzC/ +7E8WkX_I\kF޽eYnweUnʛJwk|ZJ}Mb=z4roLq*=AHШ;}zH]ǢwiѹG@[nVUr|cR}^ e֥>1;B* xQcۀ`3Vύdp<w,K)3ؔifmn[09r11Kވ\[Շ;z]fGb +G"6`ϧIYco6F`Jkz]Jn,]SHk YB\3AcvcOĊ*ۖ#y A#r + 8~K<ĉti*'-տ499)vDJ?R+3"|n /9I]b9 c,IFB2* l:(tNp(OZb,lscb.W ÿ +5B@+ơE;Ft4Q(^I7QrUꨭR&ͣ|P +!F//įG_p<^:̄7Eܒx3yOK*@r.=-ĮDkߘnNo>NKQb :^9KNy}u dɋu +mE&HiQHVHzBD!3i>S b㊦F+|ߞSg3Yfa+O ? P.gNaIVHVjl>.*Cd5ocՒJ$IJ҅g0+ +"WԂ>[66eZR_&:>)SZV-S7gi:&*h7}ib_4tveF7IYt&2ʄt {¯rXF~fHCӗ6 ws/?#ڗtUM㱳pd1AO[>Af5l/ +-Z$Tȉ\Ð {xrIRƄn> +_m!(KSU3t3Bm)@gьEDKz* <AwTL7縟끤 +P~c@Bl*( Mt&ʘ5JEsլ6Nl;~dkLcC4c5h|pxsZ O#{-vՆ3Xa"/-"XU"("/E"ЈodZW`n14@6۾\)Q-%j\hx,q'P#6ߵw{|rPЍU=[ul&Hj8R +RhyԦkUjoӢm<2pZY0ro5z&=\!8[d)s:޶Q2/ +^?AÁIV;Hn d;ߗrDi_>j͵.8JVnRl9ҡqйݭwN )6#!K s:R}\TF{ ^SC |'j\JŹvxd2k: 2iS{+6~E̳t$6QI T09SQ!ѻCv1!n1 #oߋjQ`[#eE_Alk¨EDhH`wY"M8AYh'7^Y=o!R^iH)sRlK*{RHƧtP#r̕}VL,6 Cj>}v݄yWD ؊qS]7e[i j 7l<`J]bWK+¼9Azm2ײHưO7YLb 0 s9SksKQ`:U?DՇS?  [ 8mǠcc gǯ|5>A֨;Cbp[`|JMG9 T1ru+馈Jyn8mJ: nj?|tFm]1(PyH"^hqDʙq!'47),4 C +Oo /mg/~gFMgrm(?5YNnO6hO py^W h +$bN#Bd鐪@Heb)[i*̞p4Shg?P!n>Hp`bYqĂk+.(ԬO~!mLxeeRb?%bq`e >pse@:_+brI@ \Y0k[) +W!wiB $uiS0a̫p +B1ד5qrstM &"4sKopHV.{d=pdOdT {TR@(lS6ΐnR!Xu"l%=yǐ`@!4 _׺^'< 1pv\J@ŭ?dgs?_wtwoDhP9v[j:r-yH0 -ܲ)LHK"!|!z-wD"&񳞲=\HYC( Ns[ƹl>('C3zayvr!o~䄚?ela~nL4r#P04AɟRDV>c,Ja` 7(LqQ/\v"2dsMcLB+$}ށ4&4Z9v),ǾÙgEH][^^Kh$6j&~ga=j)~vj뷌)1|,#0_dL4kL\̫g q>{NxL\;8Ik~۲̦.:9K :VnSwyZ=FtBm8y9ᒹ +&e˵6ar'=C%v*`9 _LC]5|3.iiy 'ζ(Xa +%4-*i|T#wJĴȵA=I_O.C;z TaV@䍵ά-|4mR+@%/۫Rm JE ϥJ9k# Z\{< p%fPqti|~G`Rd.'{),F0y-yΌPh:Nd2OE9A`.t\@t<-ҽ +wx#v;Mˡɧ|vG$7尼 mc-{:(u۵$2 +PL ۴RV.Pˉi >Pzadx6Y?=}"㗍ӽ8l *4 _}sJ\oqqsTrp(̦44"dl{&~.z.+Q³mX]׺QҊo ԥκ I1SXg~Yw/,_/mK:axՔ~Q[+la@|ZX~zƱbC'PBL k$2PWɨ5x)8f8=Wª71v ꬖi@D1| \@t%_%l8aFWK[De*w8@$2`3niϥΧU +-UeHAήX֩jb|Mƽ.H&ijycB.&%p-FGi*M7ʩ4VmdBS?+JtC\>mZ.ѣ˼󳸼|zrHlh@,tMDD?3Pϯ&'5joW8<zm-r&Sh +&4# 7) +ؙTH̿YOt0ě"0e\mآ7>J!-{7?w'1 +4qԎNadʱ >9WӼ-5:R tf:y Å!W[aF@R*Nv {4pvԈ)f(g̀H&0iBd0Pٴ|L>`,cng{aJJe2eҍ,Z-c l^i$l@g s +Fp*׈֡*FXFvWM~ +#!M" V>b<|f LY#wYǟ= +, sO-ɕ0c\zDV1}: -ezb(2YKkALZN-s]E w$C}.}1x`JLI?0IM"ʴx,J![Fߜ`|̓ɓ5X0ir܆>^l[P񣯞8扳$3MlB}5:~MzͮfPxNqsb-OxwWUCWjM)̊ߩ>1܊r佅::Lm5 W^hk. ;q4.F[SF'ƕH7ֶ1i݋]9CxO2G6I*KN\#@ >'YU4mdn;v $NI`zVdv_y^t8E`9m@6Y;AhaYRf :3ХI +k>lQ5xX.QZ;@qY&5C?zK) ";C߳]|aL g K콮{&λEUQanFFhNz5z4!CuSB|gQ6E#/Afxߵ I$MujTa!+Rc CJy$KTI\RT׻ܱÛ$z$҈+~೤\Nn g/QS\.3St`HyK@$e|wR[LC/fJyњ@EirՁ5fHH|LAIxICҺEpԔ_jXόC*ml"v-!Y,,!7rj(Θu4z*uJ#‚.l$:ětD+5{CzЂV_+T~zIA?e(|kjrȈ)Ç%w Y3]fUz9T% ( c;>1҈~~Yp?̌n (}C#*VWTd +Q)XuMƉO[ 2 )EF'OIxT^ g)F3q2`RHjUvG>~$/EH.FK@>]^hb ~Q2DgM81{B'wYvKA_!;`,ZIoA P93 +)1ź e݈gWtx aXYL|:d1 #=*tQuDn36-/fzL#8marTGW=q +L]E}J` TXE|a`pn5?ǾY(Ñ2+"2{UYȄ0G]9\EbTCHP*PkP +~rI>X̖3NIO h{92 w-[;Z2lM!0^52LF Z&JCPxa`X UX4;37 EJ%{0I"" 2^<5!X.UYZMbqŒijgY, +RmA*VgH)tSZ$_:*aQ%iEID*C.b&yIŪ.I{+lp[2V%V2b }NG mrHB{rY)(5Ց"$闈/0b|%ˎ_hxh@Jzk $]Ŕ4 +Qe Y":t=Зc{>[Z77Xu{!$ 9ƌ):hdyjlNTu̽I?WI9"U9C= sݕ ypM1ԣbJxƿ>lze7/X)7D+i1>U:^dΜ4EjMA9K$c8oCH2ɨŨE8Ü(Nc'qol'2ԑxb} n3wh+9yzlޘ}&vVx*FDZͭ$ݨ* 4DdX 5J2t +0t|EKd.(˩jlZf1&l"y#59.ok >.T'=J$vZ{zjDmOG<^Wap'Z4(Ok!nJFJ\Xq^%V1dTlAdm7,7*&7G~i Uux(R (>ye7E-iA/2k¥`IHhH*\^)@! +}=3O|,]>__x\ +ox}W ^C"!̓"V؜Z/vV~*hʶ4B6yI4g8/RBG9sjUU+~Lq 2nY~ZmΉ[PBYjכikidNbQUAڱI& ǴJ+iTK6mhn S?lտBa& !7i5kp|p-Q!w=;#m7lu_So6][Fp,TCcJYbZ+FuZyBQù=xv+d0QfAtHP VڌX2#)qD1Xb8OB$wUF]8ፚI4}^:gv_+| kŀ Y5J@h? ;̐>]s:e"\$4([trZύx38N"9G,Z40 + ZiwE^"@WR LPĜhh\fN|:%pb!Ab-;!W͜Ӟ,L_̙plRD~pJݎxNiy/4 {8u ПhYJ +Fxu +!4SrCZ8[$@Y!W5Kӳ TX# +1P&KYE~꒡3Vק؄اbrmFIܸ܄ľ_A#0HAYZ=kCXWK*qiIO"?ax&X˓,Y +t<.!EL(~QûЃkYAaD +vrtK2N|ekH0Qыo\uH$g=އP9Е"gQJu1n-W !+/9E┻VsB"޵A +3ʋX3Hz:f^kD 2 @ q v+1hJ4!VGvͻCu3OuʅDv |(:$kK֋A?3<}bXMIss ՇO K 2yg7?noKC m{lKD\1vTeZq;Y3ǟNI^:#Wl˹7OHܼޢ&`NF +("HKo8J-?+PhP'Dq>~0u0uww]c6T!܌. + +Z +8`VГ3lj@M1h{ i4xɨSM.sf-"j6.ŢdW+ + $Kvw}(Nu+V\GTiڞ.A{ +1yא3',ZvMk$z 'l-Ҳ~^CG Wz +6,sdd(B M8eg7,/ re9Y-fʾSIA2j4ܲ9`F(XƓϜѲ!ɪ8)8Cϐû86!cMN0Hd5@@Y'lϩvc.H0 9.<'`&d. +YA=,RF3*&.4Q`<2,*3 (+$bjdVL?lËRRÍpT\-4"Ml^$)N'@^'$=n`W +6ty"mdˈ}?{y~tZ|Ph}~1ot,H&dq@AG*tipXFN00uq^$LÌ *‡DIN+0*KPނbH!aqձ31ZF iKj؇ I+C=`_B(cv> +Ko?jxB8w6pnJ.-iIvJ p,UG +P ׁ[@aSḑ hudWLaLV"#~ZSPO%0b{QkPxT3 ϬP $ӊ!W#Q[ZӘ[ak~غm2Z4 f9`Uʌ=6Bn ai0,D~&k,VQlKB/r1j +3cnW&MK2_|HflDK|GlERw =و{H80#g610d> A'"'"Y6͋mQZqroXXX]OXZ0GU3W@ j뭳KCj8^0 +!?`P;m~Ou(Q^F =0J& &)7< )T F +'e9I _9YOe:M +bmdqL ըp<=16&Ad +z +OgC=ыqkQWCY1[ XN0P7hRWd׻j)!n#3W 7f%yns8u9e"Y$H#FBO>Ot5GȵXvw:8#;kbP[P aU;xT5-RCt+EnO m~[ix&26 7 +a4$c;?p;F`PzǿjʜlPժZb@n (3 VmQM<-CLNq&ݻi@Sdda&<Qέ_qI+J8) ~;*q Jv#> +' Vim.{׶tw[8G(ĻlB!YI"L\%DKx-8l!zH))Ɓq` N>0;xü21AZq v5Cq.tY.,j 3M%P\DB ;_JhK F-,5O5y U3쭝GV4yL9ڦ +"a~Pi qde\7,W^i^>ɞzƓ6j<DQ,r`vJ-5!c[wQTxbw-T9&09Ǝ;A&;GLs>[\&2s80PÂ*SP`Ph[LMqA᜜JN0`Ն{3)z1T=eF9ĭyr%7M[Wx:~"<`y9Oq3ڽ%[idb_#MGY9p;q{l"&򓷅/ +xA;Uu>wOp}Z` /OBZ%Q1|H.0Lx㈴o@HՃ +kjŮքxOfې*'nOd# !b" [ ,+XldOfG)DŽR᭷3 C!9qtv8C`x[%Rl! +&Vb$SPT23Bg 6Y+eRqP58Lĩ((/+ l>4>'2$8ۋbjg( +'D +@0M癕VNC׶! di>&N%BD< 5ٜgM*F@א`tKi\H3:emETCIppT?pQ Rn>ws2Y\u wc,"U^Imfg%,yNiH㍅be1#\fW@+Ch+SXZW:̳퀍1vӤhf}^}dN0']o%>ѹȪY>܏b[0yᑂ#ozFϫHj8b/#)΂;4&vK:˫D,] +Rɶ0 +Ռȇ풱㙛m鬣vZ ٙWa_ Ip"v^0!LXP0b/#H'SV) Sl(Ew*Y!Xr[A: )A&%y0uOQe#jOYiybDGQ `%}Ed^tq@?#6VQ1P_ 9XcNJL5$K>{$"O0=vAB‡!-0pbw'IJA2vJH%j)n!:{>zY I1 mr0pB!^:+lEi 654p_] g +{WJ~*&$2/Hβ茶Tó,?^bH :52ew W"g଩;gR+%o׀dg{F34 N08aJ ;R&.meWEK57\HDirQfTha)pR|GFuM!"%5$;lGdjL54>y͉KjL5$5S`0Z, =m%%F=e +LvSS5d }s)q~^jYc1UL5ebaj8vX*u/1l'ț01^$BplNK Xf1{1'K+vOo407Oc, &UBTKx8j)>j< 7qNkOXbGD<(*Rt7"F7q;vVk$ +}Fe9q+%o:1o: 1KJ3ջ]L=p}'/aǎ" aeyihAN0p5QA'*r, y%snϖ9O㲀iV1'p\*`@z ^$M16a9w_ M*#wUa=p4:N=9ar'p +d39b-5 r9h&AJ'Y`[3gIG+5Ty;D'h䊼mVapAd))&#bbN5ͽ&k|?SBEV^ _7bƲ| ׶iqAZ`-ţiq u6 pt:kVw@i(nق=1U8Qd( `.WDgeMS +\9'5Bos [ 7TL-ab+44dÝWRw^,Qrv3"0l"`}y sqS*!H@%n_bG%V>M݉[ ²v`YS+vN0Vo9qAh +TɄ -RAb ^6 "vzone=_&U@04D!\Sy% ѪNKjNq 'bGgo }um-IZi3pb#r"`eV乒,SM~Zrhgq|BsyŐ9`OŌ}ѵEF ˇT{?zTyRGgǴX_s<)Y Lsl~\?8݉O`2"Z'R^o 9 5`&ÀRwI&:[1HVǢ6uj7 l V\8}>aR E+%GC 6+X1+iCڿNNɹ=2s#D1j+ ïkAG ,)$4.9Khe}|wq$!jHd5 dDs6(hy=3>5$.\D%AJ]݉;QY7I4$~?%O#O섲ڽCQC}`yR>%aYSucKP ԽYR\xbOc6-g} 3q>ϫ1=qs+@%R׷.g+|8q8z:>x> IjD+FO4Yv~ᡬ'8T#/Iߝ80ދ6 Tr'7]x)C Rlk/kMC>oUķ}Ɨ8t?oR%1АSMEA0ʔ(Nž i +DD(4W8AqcpIFS19$ep{2I∕LF:w-v SlQ +4G3ڏS}# ",5`m0YXa `);`2`rrx ~K@m:LQX[ئ8S8;8iőgj;Lxac _A05t*d=Rbw!ZĮyY,d ++!@b!i bB OY;ViiS1G),%-' 08`+LឳҤ9~e %r@R=aR\h zzh<զ+'kL wcy)ȗy7yk8^y"{SZm P"#BylKg$ U9'1 bXX/A\*;E@!PQjd"2Yi݊&"@|m?3ۧo=Ȫcuo`=1Žod?'3)W]yam1"#jX"<^nn/C,x7[(I_}8` +L@#DRO &j0o%=gO< #q;PNukͽ0Pqo.ujBO wK6T[\IsN&o*&37DvY˗TCiѰ>y[ a[[CwBe ~,F?8duz;. wu`WHu []w#M2ɕ;Ќ7'5e~9 kH#[OA 9jA Y {GV(5CqNxf#D ]"53Hkbw4a}5Rm*2BwD>~hF%kVs ٜ(jX_$@(l|r/=u~N*Z;@6O4IqG-6D9y0\ݟH n͎{avd4)8XW&=bXdꆂ Rc5g况Pߎ(m5.u]Rx>Q`cĮs'g2Æ'M +vE6fV5i7y ;0ϫRXiYqޢ%c_(_Ѵ"pGǙp@q191nD2⋥j/JKސaρ$jyI]+a$~|`οs +wt^B>Y35:.DxyLED%b]?..vaadpfS Go"+0||]"uEo&Oa ;VlB=[w, HAԏ~v +Ho !%;åxl @L6٫3){Z*vVWmjjHIIF>N7V[K ^訝U#w\>O]'j{ߝבвhSz={Kq-?T| n/ʹ4O""kD$'n`Q]nw'^+o07 S P9:u?"8B3B8C3KI=˝h&rC;[Xd9Xڰ6kVy*mI%HU*yJ|SژҤD*I7O:H+K), +j~c(bƞP6g%Xw0l.b)_a4f }6 )шbȡ>QCj Iq:Βsn;dHw%^ժ/ߏf?oGч,t#r~wN0PG3 8G.CC& +8YI(m ۛ>}QHf,w P͜&l~GBr_w[͆ f. 3%*9oٱ3i emNB#&4jSR,Su T^"m ջ 0񪥵@^~4$]Q$t5Cy(4_|,N? 7W'hT1Jg1W ܪNJ4UauO!bBVMYC4`NJ ]R,wC dBa)&Y[ڄ[jkX# ƚR'07Df(h[DV-mt݄ $3dg m8~b`PC0M>cG$$B4G`Rɺ&ZS0,8ͯ?u TI:t­*qk !$H"%tshzHhPŅ?rhhs'.{F{E|wt4F[҉-UtuHQђhՎc.cy4!j'0Jzst-qu~h6ę-Ҝس5ĝEN)4 +XMgXzFQ 03LSJ'Y8oaq: 7V4n+\>M9Pv!A=bwfI7PɒYl4b, NDmǾi1칭!r%YSaMB;ʈ0܊Jx /4 ދ*Vcڰ,ZF3*eJVc04 t]ۓΚRv|cDӺl6[}pw\R +P1071jHن(nU +0*n0d[S; 8ʌp*7S6fTWOpIP{6lf<',|9ikT+z[Ģm 'i)8W)P=ڔ˘<]uA/ؙ +AXᰐ`Q 1 !L/sV/N31UlJkqׁiI?zg%Sf[v^a=Twh]UL(QtՍ(&  zbU=604˰Z[r V&Ho +9CaE,Y=R ͼ.a5H.D1%[v g0WLi#$8Jb+vqDyåјQkZ:q:\U@(u,B1ª9/ {̱Pe:=)2j]Qݡ9e+q%<o`.3-dF%8޸$XU#Є-RM=āM1M߭Rpmgþi2ZBb w-=m< ";;rǑGdgU\BLS ]%Dsm,cB' Ul1xIO1n!{(+LXT$u(>"]~ªiWi:jfU ZĪɃآW[#֣ؗ()W]fUAUn@vt05Z>==],`T=sĔ#|rg3*2ڳ&5^ WQy@Z|TPgg gTP3~'RնgF> \H*-Gja $`ۏ~Fݭ*,69ϴh|)Ѧ*SIrSSaBSwMFe>|/|l^>-!꙲E,/䖉sny47aƁ?p[hk9 T=0*H}SfNutE? /tW +:a0T#Y5 2Ǿo֧pO|2t]]qFZ^<ŋ$®gHj Pq;e&3bK%=$b0otu֙M4ھ\?o]f[昋9ywO޻z=$(4T HQ?: o6,SNAd`͹/orZ(YwܯXDs7PFͶ O  tuHJr7="(cZkBb74dzO H4浲Sb:QEs*tȞ?͇H잕p9 Re8ž aPᖹ,feb K6bz 5SU~W-8 G`3<.RIws[Inކh٭0~ڿ{W9q^;W +]H03n"PK%x!DPн$<67 K;-+Q_Vs5V|#xMa]h+kULkaȈBL3 c6WWaմCSX\f7]B+%,NcP4VKՎuCWȇTH)m|3LUlngWZVP®匜(sndn| '^^;1ƗXn4кj Cxʔ1lLJ 'tYA<Ž#{s:lTG8ϡ!*#0 +j =uG[zk>~MHru\xpkU0j, ۚ9 NESN(u?XRwvcf$d`Zcl.VO ,ߟ)vc+GLCJw4+P60GH; 01%fY J7e<)bNЮ! n!lvC%rx \x8y{Jcvnr()./\ L~#4ԅi"+U!ɞR'oYҷTN[^y!Ё V^d}Gx8.iIl}awiZhYTcp|=(􁔩&#*r>_Q'ٚR;-r[ܭ" m;t I4O$> I0:@kGW*[ fC4 +"-7HP|=as-Iv^ lcUBo*5xUM9Q-+(eqxʀ =JƶFI[f)ͭɈJIh&/A;0 Fuv$.idۜɇOn 5%Pfj*ESN"e%gV*9;pmTTBe3>NIG^0VD͢FHWuו] .93M]6̖g3IkھN:߆NmMe& e]}Njf$oZ:KB }]d0`+dߣDAxeJBmߡoL xOThS=H7ML +1Ҝ-e`#E6c^%UC.33{Ȗd'kKtw73ا~+.Ȅ(M0W Iȣ´߶h B; Oe@UIdm1l 4o?IR_5Q(= ztgm{ qv*'LmhˣgLO{KxHO>zW1A`w. Kɹr/ҡ+FΫz_kSdx'mH7BdsY%K>ģ\YI$uͲ "q"16Wk0 0h"D6 3q2,<> QX3-t}h=uWOŗĶ-0? /cLYm.s;ϫI> `]0!`Qi,|S +II +9S'O3~0e |t`:Vo*fPŴSh9AKxZF)6Ni%ˡdNKa28<T ɸ~Zy0 k#?iHC^> 1 +D*hJvdg(rF{]aY B]3)M9M_fvHbk;U\az6BqsVm1a+F ΃:p)ڰK.$8`P }my,δH x}c4"j~^A   OGpi$ qhjȌTknؾm06&Y/CS^rEe1>2z?1OTK<*H^ @>Y"gF\ fM .jbdЦE.Ӹy6o I@^ b߫v-!څn:)P;emOfGŸQYQy H1㻖tw1ˏ. =NIA3}d|Ik^&qR".W%0qw.FE2U?$g㙺[ҖC xY ?Txآ 4[]LI0>O0t +d+8 +"MBސ4V|[H 4xDЉM ¯MGUFS:M "񃪫 +xa]`*hR^+e/Jdn`;ɩ跞-7\9phlom9n!XF߀19@%oB`liFWo@*A^ 9`-b=i07. +9RȡX:xΏ~HV5L7@ora. 58^  ?%CK pi Bo6^`wߴ6̶xJV`ݶO3f vxtu~ C ҌމݛD^! Tp]2 +kۀ uXJFRfCY[D12X1G ([+Č0а]uFa|A Be7 Iݿogx @0 $}[A^q;3^ nz]k36t-2h!`uqU*y 9O28:tH}PZٙv ++4Wuj÷6%TsDB|EpVNCW5ap>-URDXPyl&`glˇlrM_ci/ňZukaG*tntߢ>ȬХae9BQG9ngb]Hx5cIL8n?CPn\."IQqz:1Л-'&S Kj6`u n SKoae`nPDW"4þ:a˻?BSDz([VWRmK7e>1 -|j[XBѝ N X4h +2-bQәp? (89hǦ|0aAH} +M.6Gb̝O;iN^oNG9%.]DW& wo^ <)-R %cat`#V"UPBe)1 !Sj̆^!1t)uyfSd Բ `c2]0/!D!y]@l$Bal!h.K<1K֩MzJQ]ssKq\a-e0 .PoheY +xGaX Mn"57@b'< +{avz^Anl-XDw1?C{$LKdv*⳯q+0goB-aQGu4jv\Gct yͫ&ZՅ5bRF/WI _8}c::'EU($#ڙ}TO07eO"ߴD#HϨc#8;29K'*gqR8jL s컗g ,y|2U(Јđvo(~5Xyś$2b!=h F/c%{4Np혶cCbv+QNlC4kM.yłEnb8J7P%hNe)Τ s`MR%@02cך}HL}A8]TSCrU5,{b'F9jD?JQQd;31 a;|7쾯pn]FX`h kFT +UD';u(D7(zoc)OUc:pa# ԜE;YCV0_q!Xjh!Cw)TI$EzXVhWJ[ +\X\< r Q56dMx2  V|sHH<?=z|OlSZ)jTD1 u.|蕆8ƒiPsYg5B|z{s. /H}EN ȃn2oO8 +C{ +vߗW^@ء+, +R5>K[43Ev1j)2y QA+!C֮b?RLJq76dd|Xih_N8i<R Q +W9'Q ݵmZ4dRKq[ˑ7ޜWy'c7jp\s#ƯK?.oKS?8i;W `ԍ0 2*38Pj.y9ٕ֫>Î9IoD{hJַ=EsBC9nEYΖPЖMlhfgu99JP' 8Gy4^^';7}"׹>s 4o8N\-@B&ΥzW6$}W vԭ>X$"Z^8ВGg|yCysdך U0J"95&v77ATDЊi;\e #=+hq+.{,RG%v8QOPN,Ž6ZPjSfE#Oah=v&DYWvi>u*P@߯HJqzՁtufP+I=p[aaӆ[z} ;kKD"zAW$Z9-bf$gjR KLk϶cO8G{@V߫3,]yRH!d!SQytE¦ϸ B=!=+'t}1p]Q2H{kP]2 $ׇm|6˒F@%!vftDgNm/B"a L.Q>t.[wr}C: +Ut}<؆*Ձ\ֺEzD;I|!ކpEWDT +ygH흶/ya _$խHaj {Vő2eX96͒ +SMNK)UmWe&kwlQ~b 1"mHe5\Bri7̝J&80 :&'=*Vît{}`7aKi7oS`Q*{fֆ {KyHTD%VBbU!r'{uG kg94u`'h!B.2yA:n(sZb&GFߓVm)TɺEf[4**{@3oF7ZBb[08)Φr" +;{%({{ӄհ!*@=J@p)\ĞS &1';pĈK5QK/Ȃ\QD/Q } Ug3Ԇ6[=NXDRzBoì.X#x +D;䦳w+mG!sߟH#J]TQߵsP%EoQɸP7թ Ȟ&GZе%F&N&-podڥ[ @Wu e[jNbg)g:8BcmLAVD8{T7HFЩ4"\Έ?kYSC9j?(MT1ڵ$ɟgCs.7Ⱦ\Se^!Z+wM/$7J%٢HS?|< +mw'8}_J ٤p$PkuD)^oJfR]rR%{_+[xǹ?ږkbV2+nԵtIV+m_%b)Ǫ7;懭=tspS4;ȡ%4k\I@[pQkJ\`|W2M'B+1unV[J(=1G̮ +AE+\wِ- zs5]xkˡ4!׶ywwrlddFIcKSFtpqkyW/mj$ђPavF)/@"?;ɲtHP A9ǖVP;..c[Q +5tnf2bE,Hbnₔ]XjpU痣\kVNed5 I1Ҥ;y@{L +p/&2p&-è0B6;)VKeIrL*ް m)GMФ2ŤuvJ|2F2AqE(4]bz{wF۬ +iZ5j?2)+o9l?זz +P3yɡo%u!UXΜ(emF +ڎM{2׋@Լqakt(Q~SbnD>{\R#hbqNP&~h.R*D3,e8ަ@|f_ 7/9 +9 6I(!FIsҟ tiaFaV[y]`uxx-kV ҷ{H4F :.+ ńAz8'p9icA=" "S OӮb5Ȫ͋n%#=4y[ J @fťIcҥ5vEyŏ7Dx+=sNgjO,'@M0W I{a?R }Eh(/KwJmb+Xd9ܯFMQe t' +WK o8;Ҋ.>bqGQK` ́PZIKk"ԽM4GVMGyknX^"$DzEguB|͝ QB|**»FKN ñ}…5b@[r_X4g v ѻ@"A, B ЬU'Q1S / T[ U!5!6Gw^>ĠV58uI׹{B BpI2Y4L24<;cb^G8|=;A|1xg8_m)5EQvS`&N:0$4~o4o +47xc(ׇ `e95/tgMY`/ T\1:t#RyL8z !+g$ :~4E{hmg#cGV  T(ӜV n4XsQ7 (PF3Mxs6򚢀< C~ΝhWtq +eHy@jʹ/' 99byF}*K5Go%lq"(uΒqіQ.Moe(.6𾧹ЁbsIeڏ*΁Ћ.ԡ<"Ԕ{*L_ 2Nꄤb"u\W+tOW|&"p7 +!8Cɨ96Ayµ$A< L|IX fD0OiA`ۢ,.>[9a\(Aj&x31=Obp3ޞ%`E܋=R5P '>M\knא>77'oJFs20{»Ŏ:*{HX7mغtÉG6$ eYfzUݾu_a2caq/R +u*QHM7{ ⢹>@F +<@h:4gGqQt3ի +]h ;- ox%!mnUXhx=yzdM4dmrp&x웅Y.Pb"]~^Nj hu?` ~ۙ*UqOa`i\91\K+Gv8ABCN>nQ='bUcT]#Tǃ 96xIx0G@ _hLp,`5Wa @6v1tV$7y4x6)ڊjlڹj1ec<(a9)1EﳺopVe>TS -Z4 )Ո+=<\tZ%9B &+*'?U9"TaV yc"zG;;8`0:2EHɣ,7#ZAu*PbIyL< gRH>jJ1r{ jvtGL-n)讈6S;7S[U)٧,{8qXlH3*oBY@ySijrSV-SBk.[kf' 穣DT|;P/KWD\Iiq5Zֈ 8PL<0ZrIiiX@gcX"~KO^L]3. Ee/&af 54<[qȽBP7 ,*z>!oY Tٓ8\#?\8AN#=L0ǫ n +8YpK85HPT#: KJ:KWE7Ό67k.+q" cBe '>,:[XzX`iU_ZTÐ9p) G(_v^1# +^ı4i#,۝ +h|CjIw8~߉ڔ2-KABkRVsgZ&L<ײq%ӦǷ +`>tEI)91Td$^M6':c"^޺bq5B@uڂrG5-2.CNm~^{WGZ}H/ᙿb-<ۚ!4~ +q} :>2P/&ipXlupٜs܏ƏKMxkcmj.#Ӽ{#~.[p{Q9tkڻ-[ .(ߨjvS-2HA<4޹6kV`nXgi@d?KΐY#n_2O60 |2qPy4kf[vUQܓm݌ իl ]~StBe]0Bo@@ c|A^.B)}Ր71>LkcQ'ck-}1pI)}S?KndRꌘ|VD4V.Aa̙P黴r1Am[Aۜ'}]W0dH_puā!WKla v vpVR2K/G_BPGHAɆ֖z^dYa{c=x&'P(͓9g.9 ˒l${5$m8׌E>N?(V')hة*OV}=o1~]J'ƙ=5%Xyj7^r6!u]7p! kGs QXMS n\.[2:4| ڻ]:L~ǙSƖ[Fo(jtJW69utu x!@K4Cszw]gF|ZAz'@ϛ:"|<qOZ! y”qڏR5yA?pͿ >BtaI"*ƪ?biV3 (j\!'']Iq$zj!7 -BA'+;z xC8X**+ R -K.$d8F^=/[pR^f͢7Gte&d\奫die2 NζOuQ^Y-\1Fv:=ŷÙDU-UQ h E;4S^> E'$&j 6`G֐v*QĜ7.>yChrfa7#=nO axMp̗ # ͇dVn5*ROC3}_cvjK8gssp3#2S,jPy뤍h/cWW欆tb:ɥ +~Gr1V`x.)U 04 L5|P,7jTmtqu\NMwu7` rL-D.%?aefh >1u[f@2 YI"x"Iv^GtHrDR-y9"9L),G"|'}?'K&2-K LګJ(j𳣦Y"P{"ph&T= LғS9{I4d/;y^0@IG˂A<9T j&JPP$F,@0V$@p La"/n~vHIow%{uRɨA*I +Be9IAT:`9b Y)""Ĉ &NծGNZ +*s7[QGWIOz̽|ڲ4\!C˓tPu1g#l7QUKN"Rj #ڃf\=@^wbBFWSAqw4-KOz BeAؽLJMePxP"WƻNZz%c97,3LA:ɩW +۝Cs O) Yqki)8a;,JU56{F"Vh +/bÖyRުNHi5##T`n ɦu@6Nʽ ,,7b.5Wm/ +km}h<r:!fx:Cd%~eL)K?wdp +6)? +endstream endobj 130 0 obj [/Indexed/DeviceRGB 255 136 0 R] endobj 136 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX$6Ra!<<'!!!*'!!rrmPX()~> +endstream endobj 127 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]>>/Subtype/Form>>stream +BT +/CS0 cs 0 0 0 scn +/GS0 gs +/T1_0 1 Tf +0 Tc 0 Tw 0 Ts 100 Tz 0 Tr 12 0 0 -12 -103.0967 -61.2783 Tm +[(T)7 (his is an A)11.9 (dobe\256 I)-10 (llustr)5.1 (a)4 (t)5.9 (or\256 F)25.9 (ile tha)4 (t w)4 (as)]TJ +0 -1.2 Td +[(sa)8 (v)10 (ed without PDF C)11 (on)4 (t)6 (en)4 (t)3 (.)]TJ +0 -1.2 Td +[(T)71 (o P)5 (lac)6 (e or open this \037le in other)]TJ +0 -1.2 Td +[(applica)4 (tions)10.9 (, it should be r)10 (e)-28 (-sa)8 (v)10 (ed fr)10 (om)]TJ +0 -1.2 Td +[(A)12 (dobe I)-10.1 (llustr)5 (a)4 (t)6 (or with the ")3 (C)3.1 (r)9.9 (ea)4 (t)6 (e PDF)]TJ +0 -1.2 Td +[(C)11 (ompa)4 (tible F)26 (ile" option tur)-4 (ned on. )41 (T)7 (his)]TJ +0 -1.2 Td +[(option is in the I)-10 (llustr)5 (a)4 (t)6 (or Na)4 (tiv)10 (e F)31 (or)-4 (ma)4.1 (t)]TJ +0 -1.2 Td +[(Options dialog bo)14 (x, which appears when)]TJ +0 -1.2 Td +[(sa)8 (ving an A)12 (dobe I)-10 (llustr)5 (a)4 (t)6.1 (or \037le using the)]TJ +0 -1.2 Td +[(S)-3 (a)8 (v)10 (e A)6 (s c)6.1 (ommand)10 (.)]TJ +ET + +endstream endobj 125 0 obj <> endobj 139 0 obj <> endobj 140 0 obj <> endobj 141 0 obj <>stream +H|TiPYj (FfZPST\EPQD[iQ[i\QUAEYdD@A<QNe`Oubv& gm̗}_^$a8 Uar*-ʐHj2_,c4Z2?-̪/יc߻'luLp?$T#[jo;i~ѢESQ殈 R|5(3:8Fk +;{dl +B-S)Jvrdaj2LTB2$LR*d\"d1 *YXLEMFaS-G+ +(1S,1URmwXlL܋a0dYb\ư1lcL Ƙ/]f-m|XVux-`(F /7>d<&Z)J"g4;xIɘi4i-f)iMX.عMD123BB&KpJoI$Ch &N'S>@BL&# +Qh)~}kCQf[ JX!(t20?{"nZl7{M<ˠ5<`۹`$q<!C[aY,uPn/h`0܅ShNRvyTvܙyxYL4 _y AN$ ^W4 hdU SYCjDǿ6w"ߜI +6{=x[~~Ca&bV:؉`y) IrM/̆U*tW A '^d^>x+(&߸{2ػȉY5J3v^;,'8*)ы@+2I`7 *k^hyV/[#*`n[H ݾ~ӖbK%cOp! b'ANRZ}Gڀ0KwHlx,ٸ@\0[Yt!xfu0$FҭTҸǮ6NnZxĐr :VyxG=w+[7D3YH~e^ `;F7PHw'@C!} tnfviqak}]Ӓ9I#"\赣k8m8sFJmMn[7A n\':E]9d?ahyo;ng6}B˯蚬HyW+49szL.a3J\DdkR~P0EJMLId%>_~euz)eH`ՎW3/ >t3r !{4 |4XV~nk +W?7KoN^||,vM("ǰ&͂ww^(|'7w˾,uG}^e?Ѝ$z) _ nHH`96r64AeZ6ͽ_qR  +W" x̿PrH`/PX,p;<__#Cٯ!neq,ncΘecLv8rp5[H3M6P>Be~Wb+ Pt{hnؽ<+yI[[H -"'jT_}WZW1!1LA8I,p%Ir"?OVtӅOY_o>Lw S&Q +$y]˔;qY&2ğPJe>vd <.a3GfP?b>%xjl@96tTVW%WKyx!ه_ W_a΂XN G2{"J8 `&؉L\qc:3hF%(rO׳ AHlUL4Ȁ$Ȋ-1Zgr_Ɋ_G7`i8:, zr +iuB~!74zHh :S8N +S4npS-Ivީ/&OCy5$zxt4Md7iƻ҇b}i,ptb1 +xyVNth"qG'@[B%h-Y_]7> endobj 137 0 obj [/ICCBased 142 0 R] endobj 142 0 obj <>stream +HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  + 2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 +V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= +x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- +ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 +N')].uJr + wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 +n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! +zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km +endstream endobj 143 0 obj <> endobj xref +0 144 +0000000004 65535 f +0000000016 00000 n +0000000076 00000 n +0000055331 00000 n +0000000005 00000 f +0000000007 00000 f +0000055382 00000 n +0000000008 00000 f +0000000009 00000 f +0000000010 00000 f +0000000011 00000 f +0000000012 00000 f +0000000013 00000 f +0000000014 00000 f +0000000015 00000 f +0000000016 00000 f +0000000017 00000 f +0000000018 00000 f +0000000019 00000 f +0000000020 00000 f +0000000021 00000 f +0000000022 00000 f +0000000023 00000 f +0000000024 00000 f +0000000025 00000 f +0000000026 00000 f +0000000027 00000 f +0000000028 00000 f +0000000029 00000 f +0000000030 00000 f +0000000031 00000 f +0000000032 00000 f +0000000033 00000 f +0000000034 00000 f +0000000035 00000 f +0000000036 00000 f +0000000037 00000 f +0000000038 00000 f +0000000039 00000 f +0000000040 00000 f +0000000041 00000 f +0000000042 00000 f +0000000043 00000 f +0000000044 00000 f +0000000045 00000 f +0000000046 00000 f +0000000047 00000 f +0000000048 00000 f +0000000049 00000 f +0000000050 00000 f +0000000051 00000 f +0000000052 00000 f +0000000053 00000 f +0000000054 00000 f +0000000055 00000 f +0000000056 00000 f +0000000057 00000 f +0000000058 00000 f +0000000059 00000 f +0000000060 00000 f +0000000061 00000 f +0000000062 00000 f +0000000063 00000 f +0000000064 00000 f +0000000065 00000 f +0000000066 00000 f +0000000067 00000 f +0000000068 00000 f +0000000069 00000 f +0000000070 00000 f +0000000071 00000 f +0000000072 00000 f +0000000073 00000 f +0000000074 00000 f +0000000075 00000 f +0000000076 00000 f +0000000077 00000 f +0000000078 00000 f +0000000079 00000 f +0000000080 00000 f +0000000081 00000 f +0000000082 00000 f +0000000083 00000 f +0000000084 00000 f +0000000085 00000 f +0000000086 00000 f +0000000087 00000 f +0000000088 00000 f +0000000089 00000 f +0000000090 00000 f +0000000091 00000 f +0000000092 00000 f +0000000093 00000 f +0000000094 00000 f +0000000095 00000 f +0000000096 00000 f +0000000097 00000 f +0000000098 00000 f +0000000099 00000 f +0000000100 00000 f +0000000101 00000 f +0000000102 00000 f +0000000103 00000 f +0000000104 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000240203 00000 n +0000055738 00000 n +0000239014 00000 n +0000055957 00000 n +0000056198 00000 n +0000238436 00000 n +0000056274 00000 n +0000056475 00000 n +0000058188 00000 n +0000123778 00000 n +0000189368 00000 n +0000238486 00000 n +0000244496 00000 n +0000244382 00000 n +0000240721 00000 n +0000240806 00000 n +0000241190 00000 n +0000244533 00000 n +0000247192 00000 n +trailer +<<7F63C97E8C2566489619F4889DB76B46>]>> +startxref +247413 +%%EOF diff --git a/content/GraphicsIntro/Ch03-Sequence-CreateWrappers.md b/content/GraphicsIntro/Ch03-Sequence-CreateWrappers.md new file mode 100644 index 0000000..3cbcedc --- /dev/null +++ b/content/GraphicsIntro/Ch03-Sequence-CreateWrappers.md @@ -0,0 +1,21 @@ +```mermaid +sequenceDiagram + participant Subscriber as Subscriber + participant SrcWrapper as Rx IObservable Wrapper + participant Scheduler as Scheduler + participant Observable as Observable.Create + participant RcvWrapper as Rx IObserver Wrapper + participant Observer as Observer + Subscriber->>SrcWrapper: Subscribe() + SrcWrapper->>Scheduler: Schedule Subscribe() + SrcWrapper->>Subscriber: IDisposable (subscription) + Subscriber->>Observer: Set subscription IDisposable + Scheduler->>Observable: Subscribe() + Observable->>RcvWrapper: OnNext(1) + RcvWrapper->>Observer: OnNext(1) + Observable->>RcvWrapper: OnNext(2) + RcvWrapper->>Observer: OnNext(2) + Observer->>SrcWrapper: subscription.Dispose() + Observable->>RcvWrapper: OnNext(3) + Observable->>RcvWrapper: OnCompleted() +``` \ No newline at end of file diff --git a/content/GraphicsIntro/Ch03-Sequence-CreateWrappers.svg b/content/GraphicsIntro/Ch03-Sequence-CreateWrappers.svg new file mode 100644 index 0000000..7cc3303 --- /dev/null +++ b/content/GraphicsIntro/Ch03-Sequence-CreateWrappers.svg @@ -0,0 +1 @@ +SubscriberRx IObservable WrapperSchedulerObservable.CreateRx IObserver WrapperObserverSubscribe()Schedule Subscribe()IDisposable (subscription)Set subscription IDisposableSubscribe()OnNext(1)OnNext(1)OnNext(2)OnNext(2)subscription.Dispose()OnNext(3)OnCompleted()SubscriberRx IObservable WrapperSchedulerObservable.CreateRx IObserver WrapperObserver \ No newline at end of file diff --git a/content/GraphicsIntro/Ch06-Transformation-Marbles-Select-Many-Marbles-Delay.svg b/content/GraphicsIntro/Ch06-Transformation-Marbles-Select-Many-Marbles-Delay.svg new file mode 100644 index 0000000..773ebc1 --- /dev/null +++ b/content/GraphicsIntro/Ch06-Transformation-Marbles-Select-Many-Marbles-Delay.svg @@ -0,0 +1,202 @@ + + + + + + + Range(1, 5) + .SelectMany(i => Observable .Repeat((char)(i+64), i) .Delay(TimeSpan.FromMilliseconds(i * 100))) + + + + + + + 1 + + + + 2 + + + + 3 + + + + 4 + + + + 5 + + + + A + + + + A + + + + + + B + + + + + + C + + + + + + D + + + + + + E + + + + B + + + + B + + + + B + + + + C + + + + C + + + + C + + + + C + + + + C + + + + D + + + + D + + + + D + + + + D + + + + D + + + + D + + + + D + + + + E + + + + E + + + + E + + + + E + + + + E + + + + E + + + + E + + + + E + + + + E + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch06-Transformation-Marbles-Select-Many-Marbles.svg b/content/GraphicsIntro/Ch06-Transformation-Marbles-Select-Many-Marbles.svg new file mode 100644 index 0000000..3e7fb41 --- /dev/null +++ b/content/GraphicsIntro/Ch06-Transformation-Marbles-Select-Many-Marbles.svg @@ -0,0 +1,202 @@ + + + + + + + Range(1, 5) + .SelectMany(i => new string((char)(i+64), i).ToObservable()) + + + + + + + 1 + + + + 2 + + + + 3 + + + + 4 + + + + 5 + + + + A + + + + A + + + + + B + + + + + B + + + + B + + + + B + + + + + C + + + + C + + + + + C + + + + C + + + + C + + + + C + + + + + D + + + + D + + + + D + + + + + D + + + + D + + + + D + + + + D + + + + D + + + + + E + + + + E + + + + E + + + + E + + + + + E + + + + E + + + + E + + + + E + + + + E + + + + E + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch06-Transformation-Marbles.ai b/content/GraphicsIntro/Ch06-Transformation-Marbles.ai new file mode 100644 index 0000000..83727bc --- /dev/null +++ b/content/GraphicsIntro/Ch06-Transformation-Marbles.ai @@ -0,0 +1,1760 @@ +%PDF-1.6 % +1 0 obj <>/OCGs[23 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + application/pdf + + + Ch06-Transformation-Marbles + + + Adobe Illustrator 27.6 (Windows) + 2023-06-19T11:04:02+01:00 + 2023-06-19T11:04:02+01:00 + 2023-06-19T11:04:02+01:00 + + + + 176 + 256 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAACwAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A7l+Zenahq0emWukebB5Z 1G0u1uGYEssqrGxEc8aywFoyeoY0O2CwwlkiDRIYTqf5e/nbafowR/mODp4W4/S19OiQFWc0gMYI lDDjRT8S779TUSAtkSBuWTaFoPnSN4r2y8yWmrXls96LppZpnguheTxvBJLHCwjja2hjMaKqdtiv JsTEjmiMxLkbTfzH5R8x33mJNU07WZba2KWcbWLSSiJGt7xJ5J0RTx5vArwlaUcP8RotCGTLLi6t rZBJcSpChNA8jBRXwqaZGUhEWTSut7q2uUMlvKkyA8S8bBhXrSorjGQkLBtVTJKhr3U9NsFV767h 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 2pyiVAdHXdo6mE+GMNxHq//Z + + + + proof:pdf + uuid:65E6390686CF11DBA6E2D887CEACB407 + xmp.did:53d0f07e-3f0a-3147-a46a-ae5b0dc8d9f3 + uuid:391542e6-30f4-4890-8c42-1c5b67c368bc + + uuid:27c6700f-edc2-4596-8c7e-6d5a89794448 + xmp.did:14709273-42b8-4844-95f8-dd7dcca7f08e + uuid:65E6390686CF11DBA6E2D887CEACB407 + proof:pdf + + + + + saved + xmp.iid:14709273-42b8-4844-95f8-dd7dcca7f08e + 2023-05-24T06:57:15+01:00 + Adobe Illustrator 27.5 (Windows) + / + + + saved + xmp.iid:53d0f07e-3f0a-3147-a46a-ae5b0dc8d9f3 + 2023-06-19T08:14:09+01:00 + Adobe Illustrator 27.6 (Windows) + / + + + + Web + Document + AIRobin + 1 + False + False + + 504.807339 + 363.678899 + Pixels + + + + + Consolas + Consolas + Regular + Open Type + Version 7.00 + False + consola.ttf + + + Tahoma + Tahoma + Regular + Open Type + Version 7.01 + False + tahoma.ttf + + + + + + Cyan + Magenta + Yellow + Black + + + + + + Default Swatch Group + 0 + + + + White + RGB + PROCESS + 255 + 255 + 255 + + + Black + RGB + PROCESS + 0 + 0 + 0 + + + code keyword + PROCESS + 100.000000 + RGB + 44 + 50 + 200 + + + RGB Red + RGB + PROCESS + 255 + 0 + 0 + + + RGB Yellow + RGB + PROCESS + 255 + 255 + 0 + + + RGB Green + RGB + PROCESS + 0 + 255 + 0 + + + RGB Cyan + RGB + PROCESS + 0 + 255 + 255 + + + RGB Blue + RGB + PROCESS + 0 + 0 + 255 + + + RGB Magenta + RGB + PROCESS + 255 + 0 + 255 + + + R=193 G=39 B=45 + RGB + PROCESS + 193 + 39 + 45 + + + R=237 G=28 B=36 + RGB + PROCESS + 237 + 28 + 36 + + + R=241 G=90 B=36 + RGB + PROCESS + 241 + 90 + 36 + + + R=247 G=147 B=30 + RGB + PROCESS + 247 + 147 + 30 + + + R=251 G=176 B=59 + RGB + PROCESS + 251 + 176 + 59 + + + R=252 G=238 B=33 + RGB + PROCESS + 252 + 238 + 33 + + + R=217 G=224 B=33 + RGB + PROCESS + 217 + 224 + 33 + + + R=140 G=198 B=63 + RGB + PROCESS + 140 + 198 + 63 + + + R=57 G=181 B=74 + RGB + PROCESS + 57 + 181 + 74 + + + R=0 G=146 B=69 + RGB + PROCESS + 0 + 146 + 69 + + + R=0 G=104 B=55 + RGB + PROCESS + 0 + 104 + 55 + + + R=34 G=181 B=115 + RGB + PROCESS + 34 + 181 + 115 + + + R=0 G=169 B=157 + RGB + PROCESS + 0 + 169 + 157 + + + R=41 G=171 B=226 + RGB + PROCESS + 41 + 171 + 226 + + + R=0 G=113 B=188 + RGB + PROCESS + 0 + 113 + 188 + + + R=46 G=49 B=146 + RGB + PROCESS + 46 + 49 + 146 + + + R=27 G=20 B=100 + RGB + PROCESS + 27 + 20 + 100 + + + R=102 G=45 B=145 + RGB + PROCESS + 102 + 45 + 145 + + + R=147 G=39 B=143 + RGB + PROCESS + 147 + 39 + 143 + + + R=158 G=0 B=93 + RGB + PROCESS + 158 + 0 + 93 + + + R=212 G=20 B=90 + RGB + PROCESS + 212 + 20 + 90 + + + R=237 G=30 B=121 + RGB + PROCESS + 237 + 30 + 121 + + + R=199 G=178 B=153 + RGB + PROCESS + 199 + 178 + 153 + + + R=153 G=134 B=117 + RGB + PROCESS + 153 + 134 + 117 + + + R=115 G=99 B=87 + RGB + PROCESS + 115 + 99 + 87 + + + R=83 G=71 B=65 + RGB + PROCESS + 83 + 71 + 65 + + + R=198 G=156 B=109 + RGB + PROCESS + 198 + 156 + 109 + + + R=166 G=124 B=82 + RGB + PROCESS + 166 + 124 + 82 + + + R=140 G=98 B=57 + RGB + PROCESS + 140 + 98 + 57 + + + R=117 G=76 B=36 + RGB + PROCESS + 117 + 76 + 36 + + + R=96 G=56 B=19 + RGB + PROCESS + 96 + 56 + 19 + + + R=66 G=33 B=11 + RGB + PROCESS + 66 + 33 + 11 + + + + + + Grays + 1 + + + + R=0 G=0 B=0 + RGB + PROCESS + 0 + 0 + 0 + + + R=26 G=26 B=26 + RGB + PROCESS + 26 + 26 + 26 + + + R=51 G=51 B=51 + RGB + PROCESS + 51 + 51 + 51 + + + R=77 G=77 B=77 + RGB + PROCESS + 77 + 77 + 77 + + + R=102 G=102 B=102 + RGB + PROCESS + 102 + 102 + 102 + + + R=128 G=128 B=128 + RGB + PROCESS + 128 + 128 + 128 + + + R=153 G=153 B=153 + RGB + PROCESS + 153 + 153 + 153 + + + R=179 G=179 B=179 + RGB + PROCESS + 179 + 179 + 179 + + + R=204 G=204 B=204 + RGB + PROCESS + 204 + 204 + 204 + + + R=230 G=230 B=230 + RGB + PROCESS + 230 + 230 + 230 + + + R=242 G=242 B=242 + RGB + PROCESS + 242 + 242 + 242 + + + + + + Web Color Group + 1 + + + + R=63 G=169 B=245 + RGB + PROCESS + 63 + 169 + 245 + + + R=122 G=201 B=67 + RGB + PROCESS + 122 + 201 + 67 + + + R=255 G=147 B=30 + RGB + PROCESS + 255 + 147 + 30 + + + R=255 G=29 B=37 + RGB + PROCESS + 255 + 29 + 37 + + + R=255 G=123 B=172 + RGB + PROCESS + 255 + 123 + 172 + + + R=189 G=204 B=212 + RGB + PROCESS + 189 + 204 + 212 + + + + + + + Adobe PDF library 17.00 + 21.0.0 + + + + + + + + + + + + + + + + + + + + + + + + + +endstream endobj 3 0 obj <> endobj 5 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]/Properties<>>>/TrimBox[0.0 0.0 504.807 337.679]/Type/Page/PieceInfo<>>> endobj 25 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]/Properties<>>>/TrimBox[0.0 0.0 504.807 363.679]/Type/Page/PieceInfo<>>> endobj 31 0 obj <>stream +HԗMoF{x;3uhFv{rzEQąvPCJLjIتxlH,ݙ=71Wk^LLu>frmlk'WɥϏ5! ޅdyYU +9z;]E\<tZ~B6:2D hwĽ1~vz:Kf9 ~Nzy\|;;1?l}x\>3p\?Gdv2ʠNߘM΀\BOo׻ۛzVbB\N 6mغD&mG8] !9~⬼y6 ^2gqd\@b6K òW}"LLBT6$擠 XW| fօV[!{!A/B5FC-Xϫ.r2=IMeEN/@."O=+K\"}o^Φ2s[".q:>9DG,oBIY]zu;KrJHIȇlW98+7k6)ϋ?1FJ^$8H!);r=BԯJ/('W!JĐd Mr+g&%s:ò# ]YM?]#g L]zu (r8wkWzdAÓ]-ҋ(0kC^v5˩0+M/:EDB)sjbb+Od< Ů҂9\+(͝_9njQ,vX &[ Wem@/0̥.{+VZe;@ޝ_mGW`ͪ"צb||潇\!$:|2Jl"MVlVGz"⑨cdR*Kxy-jj`w&c|s? +ͫ͞]}_-2ry6 ) 1=fQNy!%klՃVG@6#AOrϬt:#`LG RX +{̵(#'w*ᐷPW]z~m@9&cy[y5P̵L#,bb ƪ.q2 le<q/JΪq2)@C<8SgUVe3iG ѫG\2 u[? +qywrS[#UFFmyY5йi1޹xhjs&cs; +f@MxvmˣͪMέDjbk +endstream endobj 8 0 obj <> endobj 9 0 obj <> endobj 10 0 obj <>stream +%!PS-Adobe-3.0 +%%Creator: Adobe Illustrator(R) 24.0 +%%AI8_CreatorVersion: 27.6.1 +%%For: (Ian Griffiths) () +%%Title: (Ch06-Transformation-Marbles.ai) +%%CreationDate: 6/19/2023 11:04 AM +%%Canvassize: 16383 +%%BoundingBox: 16 -704 480 -15 +%%HiResBoundingBox: 16.134253203335 -703.045871559636 479.752527499077 -15.64892578125 +%%DocumentProcessColors: Cyan Magenta Yellow Black +%AI5_FileFormat 14.0 +%AI12_BuildNumber: 210 +%AI3_ColorUsage: Color +%AI7_ImageSettings: 0 +%%RGBProcessColor: 0 0 0 ([Registration]) +%%+ 0.172549024224281 0.196078434586525 0.7843137383461 (code keyword) +%AI3_Cropmarks: 0 -727.311926605506 504.807339449542 -363.633027522936 +%AI3_TemplateBox: 683.5 -384.5 683.5 -384.5 +%AI3_TileBox: -168.466320512286 -843.072483167736 673.393659959146 -248.052524671642 +%AI3_DocumentPreview: None +%AI5_ArtSize: 14400 14400 +%AI5_RulerUnits: 6 +%AI24_LargeCanvasScale: 1 +%AI9_ColorModel: 1 +%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 +%AI5_TargetResolution: 800 +%AI5_NumLayers: 1 +%AI17_Begin_Content_if_version_gt:24 4 +%AI10_OpenToVie: -116.880733944952 -313.366972477066 2.27083333333333 0 7857.02752293578 8402.8623853211 4318 2607 18 1 0 89 170 0 0 0 1 1 0 1 1 0 0 +%AI17_Alternate_Content +%AI9_OpenToView: -116.880733944952 -313.366972477066 2.27083333333333 4318 2607 18 1 0 89 170 0 0 0 1 1 0 1 1 0 0 +%AI17_End_Versioned_Content +%AI5_OpenViewLayers: 7 +%AI17_Begin_Content_if_version_gt:24 4 +%AI17_Alternate_Content +%AI17_End_Versioned_Content +%%PageOrigin:283 -684 +%AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 +%AI9_Flatten: 1 +%AI12_CMSettings: 00.MS +%%EndComments + +endstream endobj 11 0 obj <>stream +%AI24_ZStandard_Data(/X̊. / MkZp*\F'@L.EvwoV?g0cx񰍧w (E^>h4&. Epxz, "!~("i~a+EHy8G)GH +`|H ǂhH d5laV[j Zbq ;M/H3g VNTwPnigmNB2~R2R.ѾDXxh!gh5 / ȡHJCHRSTu~ c E6!Á }X,+9d])"ڐ3K"QGqER<υ=Wұ"h;v❛{eZ- JWh+*:T0 Q!q<oh#ݠF2G=p4kr ƽch8zJU_6T% igŶY`4b.Y(=T**2SRZj"$q -xQM"q"i8IިHRww, b8`0NGҠt8>x@! +9q@ EH "yHHma<7ˡ i #GEb] ų<EQ{"*U$xV$y4$ıP?aHrPC"15 h0_Ml,rHf-U$tRtJ:,T" "R$ MR\ZUL{=2^:_5TGF@&)#cʪX/T,ҊH"pP D$,<AbD)LU$"q<ȥHSW4ĦHx?lEX|I5W;)[Co d Q:H̸MER Hʊ y(Gc"Y=,-Ei8&C4_\=mَ։jXo +ú5Kz?!djk4XC%ʍ>Tg7":lW-o4T|Գ*ɦ\ O+7ѵHzj^;:ZPA4KڼZ}Փnm=TV.o!,Yp$( +%-YT"áHⱸGH `4 Ee(# 8PBxAi/"a(JHG6D/! ^"y3޸CN /ja["cX8dHaF1q d$C8c`0 0F5q  Gр4@G:ԡHqp4qÏzȇ>AC +! i@5eІ"ц");L/*A."!>  Ex@(.Ex@ə"ƃX<CG>q{أ~x8h8 ñp,wC@9Q4 GX4؆6 l\ְF5ܠ`8 Ƃq22 d0cX0E.pq [qg|u_!X6zi!P6tUSS+)$!F2rT$NJ:Ё \q1а9q|tsX4ErfVF0c 1Ґ<h0ʤH^x]k*4:3E =pA KKIJIDɎbQ Ejmm&"r +b~bC4bgEr!P$ӂEJ3")"`,P$Ll0AALP0B!   ăB"(D" + <,HDPx +D B PA 8 + +L<(@ +EPx(4  bPP$  D "" 6yPu B"Ypphax0@Pu A!21a!`B!!" L(HLxh 1AE!B"aAHD$B!d0A% ! 4`PPش"X! 2]6`xDDL`Pt0,h P$$"P84HHD& (D $"$D ,DL(D 84LH(d !0qppD"<&0884LP LDD4P@ ".a"hР!r " @DLx(lD8A!_"$"6pS$P$LT"  ( +0D h<Ă΋NTigK)V:151u/%J?3dVy-{~-ܝ:k켼|]1<ڥWݎWIsn>O[mx2^䚲<.4B|$ojΥI͋Nfvx/gצ<5fhd㝈cw&oDnC63'oᒞ6hN =:,;^꽅?W37ox{i#e(ڵD7͗K-}nꝟfPk-ui^>vvwE[{GS_ùSÓyϣh孒Tsg4}v\6okyk捕.{)5:of:7iLzڬYo +w, 7xRQ}iZ±ݰ֦ux˥zjEMMyQMgx֭S9﫥q9z46Wnld;xs'G8u~Jµj:UFRT[Hz7jgmKs; /j\U)IBFٹ|kWϝ5L4u.$^e߲VXs=YσZU|&}2 OS1DDݴѭ04ef^fV,ޣGkWg|m.Yki# ZAS:W):o肇YuQ*şs/y=[]w.Or3]2g3 ʩywۡu)Ǫ,5*HX2;?eϳ؄wL)rՂtWw]T\_jR[ i^QSz\٠}Dge4i`Q8):O֝½}ʻ)ri:J8ƴ+f]:u^ϦHG̲1dIԋ ]Ud^U]U[5^:jie6vUCsƶ +󈥣湖ܵ#۝:6fNn)~ kn֭A-KzJ k1ЃZ24^zihƚUï*Yv0j"" @xx<0 ,x9yZ76ࢍ.k=4GQ!':JG7.>hVU EK;&ܤVuo),ow-K-ي n^Cģ]|cW̠#J簎=y a躭ڜWYzl~֊rM +,kϰK9^%wUV6U#2-o]hN˾IM%ѰՏlV4R٤.LnTi3tޜkj{#m0dwt<]jUM#τVK͗OhO=)ᩋ& +׬\imojn3NwŀT]YsTHM-my6YuG+Yά4HqʪNw/]t9Jx[lw31Kf^%]Q>\wB߭fKSixÝw趶6k>y|r}˓wAS<ڜ: heFJRa>QɌFOks")XvwEvfRҖZx8Ȁ:{֤ˮZ͇.1fzlm~˚ڙ!b:3#]׼V UT-re{^z^~4|[ULh7W&ڹZhxG{ךfYXL'>x3<5!yx*a]+t=O{:KK5Wk)OK.}ub͝ nQw6wNZrBnyˌWE^1e撫J楗eRb)O7u' +pVS+m +-6Kcu_aa:URx5~F2"KzJ{Hfk4W]e9TӭáũCZ";ZCҵΝ%T⥿$oV7sjMqsri9DeqU)m! ʿ; }Z]-%RZz.\453,FYyM+z31w}E4Wb9qnƊZ{Tz.5ӆc(wɦ8ihǺC[{Ͳb{hi&έZmKcXIrON4-*E?Oߩ]UL弖hwFcK+}>;}?=>zR"21ڍg][;$׎4o\r}|U[νy»Ά7̳Ќ.MGjsWz_]y hv4SYʺi2BNO:;~%UnZ^کݙcufi[v>,)7W_.{ͺ-]uΨqndmODڱCvdB-G)-YN+uk6zЦ4C#4줯=fXV;ivWfɲ3;;&mU{}c-y\Ɇ2?ҝ*үEs=MU%ReЋLv/5~NW.+i%-*TS\:$u6+ՕjZw-w|7zL4˦Y*}AK]bݸ:8<{wnjlKkt_tR})yw]-(lun_tN2yvYOk9ͧvO\J>=r)KU}Tx0a`@P"<4DLTP"0 P-kVVZjݏ&U~vV-LƪnV2Xixi +_)D[9nVR {B!R-mՙTC$e̼'i1zxiKMnb 5Z!yuіu"-]}ݣ*ڍ((J8D221)'EJ<\vx;iŧӢXXzY9b杕Lo{,mk2 KgV7[ۖm?fx2C{X our] +p_Feq 𕦃J5hz[9CIUiԘ6qU/*GV{d~PbMTwTZkסW[}GU5T;[*bYUtߪ)Y鮩itVx9gEeT9$LX}wn )ґjn)]&*[g4 + +Yw2"2ӫ%ݍtvl׸T~K4,ųC䱕=DZE{ݨIW+Ks.]9W9rTgCucFjs]K̴v{hUl[Rm<%+xMX܂eқ Syd^I[Ihyɸ9RR`Q\j3K8;W4+KZ,X紹C;|⏴|)+ꪧU"K7{9:\3ԕ_&rDSxjZxuK KiTuy7+]ͪn77%rkni +ws~/ +wVZ٭+Zt+,RܹmpibWV/!و[IX\AuMK 7K}G#,J)%ӦԔ~j=4jlb*=yuzKey6Cje31펙ZR̒gWQ;^1W!+\$3=k4뢎Lֲtű4/g7ӋfNk%nJ[yhzLKnW4¢S)MJ6SC&buitZYֲ]^sItC=bXgzv󛦧wkhEܭAXfK3RgJUO^R.Z0KkΈeg:k kgrvZϳŲ_nܪo裋ZVIkzfڎiZMwpV^^skL~U[v?1-eyy;VC${sXvC9w_V]i-XnLI>ަOUthCZ:MZS"nyڽL;T&u _kd,O%wT};T_#=ͣb&>-JU+MϼRШt4Wz4i_Z.jV]UaN:ˆG3٬|f\+SI:Dz)鹃##R[3Vk>Sq -M/TsM3nt,"LfvslEL W3m>BoH}JMR}'/m^B=TCۺ- eGF;}vH>4\aSvw":Q&ݵ\zgY!7_u/Mnј`6 Mӻ;_~B19I:,پ;Pk{/;P2`OBQ +/X;LӞ?='d\LSI&[ժKih}`g- hB\O#T]|W}aH<+U89Y3Kyf1 D1?Ycldp +$ +^F?p?Ax١P!O35jh}8ò_|w "-x^\诉%Xulu܄&'ۓ]2Qj|f 1{pf 2umaxHA0_o9 wh(|ؾJ[KbPq@_To,`I/[m.mS a¢~J:}š\|q;&m1;vJ%/zͫE$M.ܪ(b&N sRj$5x#-Bϵ|ZX%J`5ڬZ&"Gݘ`OFR $)ʮk@uXiȑ-3 ¬PZK`% "<!Q/ ,j?tULVY2%{0)SWP*,GkpN7"ͦ`گ,mO+1¥m`_m +mBT˜𗶇":]!zvm ŴtF 15:YQxk6ga& }4<Ȩδp:twļ',3˪͏6>;P#*91spn=7A6 +<+MbgY_҄@ىhdZ֍/st=/x7^yd}f6D  }}o+SREXCquT%C*p@dlڼ6&}2'*_G)+e1hY*T9jl,,]PB}GR`GXIھvCh<Ga!ծz . (Cr恭PRk:}1Sdzm蜢Bc1b6駧 +.O6nzX;:>)FKG6IMä1Cj>z杒BFioj+"<ڑ EC]lzHrL^f\z? %gL15;u >/WU^+ x+|Epf$Jۇocv"ڀ)"إ!NOVR_?R&)#/bǚШVy{,׶q] l$,KեBMJ}OJ t=|y<*T94EA c10}ү%][1P&p0 +;1#|>yT!URm@Ve S +iBlh@OkiQ{ qDܹ V+mPx v| I*DGTq7熚<"3E"hsވz4O){)v$pIޑ@88R!@l9QaPsl䅙j!|RG7iG#,U/ 5u<]h0?A!20̩W .p oCOT 2= ,M1"7lu8S6RT`B .O,'%Q*3#|$b$6)S@Ļ9v'`nqƑd۟sH:ge_͞¬dA9oʵ&f/̟f08Ԩs 2|pBkTS2BYV?90Tf̟1H2)$≮$k7}9ޱ7?9jAmpi@PL+M9P^@-Nekd淎[39VuFY:R߮wi}#Όۢ{_8CzOq35'9Űf}[8>S$ZύUhDDžV4`Iw~9;aPW"z))ʴi+0sV OIYs7gܨƁ .yGYo99܃ L +g)y73s)p>Gi.^dIk|'=W 2疥Ob:&,XE삦)PHOBz)3Ԝ-$"""Zy +P4;Qڄr1Fs+!=d^CmO@_rN,qu51L :Gxҙ8'nBRFx4dyR6R*?jP_(Q <碝D1PcB!xCe{mÍr;RL`v!FP@a7{X̑mV;n),%5- ~cllHrQd$h*n"HvZea"|PTJ(\/p6E>]%pHDF~rxީvoOOau@#ȡş<ˇ?JX?%1i"ub9Bt$(08#j~µz{*"]ɋ2]+ VDfn`w7%u~]QyB)vDiO0/OFPGG#547y$2n4[:9RA\ $ *rPu&.A b}1I :[Zܻ[4ShI۰CHWO\ +¯kۛ?Uh:faAΠT͟4!J)ΰ9Q’D`)O$omZE1"IDUaޞmp`P a)܀v Ik`2Lx?kMfo\~Mw1jZ+Q C %j0pOiui%( K7mOmd W +t!4Pԩʂ/8 =c-cg0'Ov#>3+VqI֕ui0\vys塡nB]f,^(* jHt6,TlxfԳDh4h+8oj $tQL9qVka40^U-0QTw8y%wAm3{-fYY;NLuM`%w+T*o݆mJ5&.8CEfwbcd6`fPDK.EzM՟sD:ر\/Asj5e4 +FKQ({⥪bQ<>i!ixlNQbS񵹊 V#_P!;dˑ%Edl,]MWj>'}Zyۅs!T&l4C[)p$/Ō|ݙz$(OqGE@"bn͵Q2S_9 O̷42q44haTKJ4'Xk<},ۼ"ek̍4gcJô<%6I}hW-0|*b!(laQA}e/Kt!`WϺ6m}&]J>q&EoJȲRGtHN\Jvw7ZpȤF6>%wgK9N!@„M:JVMԢV.N|Uҿ Z\ZB2O1Ճ xQ3dzB.EՃ trRzxMA܀畊CFzxTEpb=cN +, |SǙ F-HjDbx!x(R˚Ndio /Xs^z88 r|E~4|AH(_7 +F.@Ci)G[yt6tcYƽf&.#Gj53Η:sݒEbmL8qZ88P!5Œy/ U7 oFamͣQIQÖ/Vo!|kXghh;QbvpH^a[YiqFFo@ԾN@9K׎+ה6?Dz i^dDpjD>'>`L%7}8!F8F"ble@Lۅ1P9da@ϣw`4q4xXO P +ÒW+ S; +nPyŕGed%%vioiA06U<j\V%= az'#ѓ4O= ε45 +dǸ6?~=O +IO6G!6(,4Ǯtgڑ^Yס`?za`ɧF&7gKF.1 o5=p1HpoOL0.u +6Pf + Yzl ה~}hUW>Hd(?Ĕ\(%{/|) ɨ)}zvQGʋuAdώn>hx̭\,Рx\E +?bѮBP'6h*۳U a m]76=&S1EֶF!?s Ha{+ Hns4FN5RV i ^^9k0-;{ JgL)g9tw?AF7O_O9=m&bbPεvQJ}yQ3MԷW]ĂxaD9A+):+%S NYRoY= 4cAl("S޸* V+}PPoNeb{:a@b[k.X2L#dS(WwCeIk\2rrig +rx4ː1Sݜq4\xb4jQD<6ؾ]3IEt&&R⺋GYnhpc%k$ jV1e (1M{M&h3j7҇ +k]jI{b,Sбm]Y,1 @ )C.c"b~)! \]dG!L/#k/k r':ĶZ@˰MwФ*o.1biIcPgt%|l> I"pG턬y8 + P7! +zLa%'oi8=1DպbEA~k'QJE!7BhLbTsg*D]sz|eRԶa/DQc |؎0KO<)xuE]+#gAwTՄ^sg-,D(D82A;>t|_Rʓioc 'GzkH,|K*e1e웟~VJv궨b6M~c<4Oq4OELG1C/"qeպvkϷ]Q/XRT1Gsub*i\JT1_[˫h 7&%Ĕf2LJq +NkhBC"L^KA3CLL[u9U/cvFv,{`MɔkUGִ?-%̾ՍLU$KcOtX8'7PBU[x3jaF8U`I&9 SN'nbr2,=!Gu] 79c6jR +`XX$i`O4J:=!23ZX\  >5`-lʣ4C"͔<'ϛj (\I h^gm/qQԕOuŦ׮ɝ:FrF0 cZk\.S[Ý5wo Yr';N`^C2ls[1 2*N5zȇѡ 22ɹ`7+ WRjJ*?W#̏]K@&U*1^]mb+DSs"yB +׆(mB2I+~]p2!R9up{"!-کʜw"#v4w_40P#A$3xdNu~6nQC2bX|`9?@UDez= e"砘 uuh5/4E놼@\dW{_XF]9.]4vT%>^:w%`=A&7&YLJelp×(1h^}Is LL zm%V*us[/3"!V?)~&O?,Cb({yvi`'R:& BM|'DGٽOE[OjH l,Un[47 %8exc*5< ƶS\ڗRmTʿn'RˎTHΡc\9er:S,T0gI +\K]F/{BGk+xcS!y`r&{;ϛB+a;# 3HgGn XsFefϺY/vUWse3Tߧ8 _Dis}Á3i ɇfG4B'D){> 2M˚2Ir#) ֧n6J;^u(Z7FXӒ&źѕ0h_RHKԢ,mGBI>QSp.{'A+Ybڊ$=KK蕤A~洌CȮ oi?@C2RB+@JB)kf=,͠7{Bq%.srNkqzҁׂs"z[82YO#ᱎ2f(b?Gw0#<#a*tˈpaisv'dxv܌c\/) 1xmI1 OLmxd:` MehL/ӐbaPv7qfa3]ghϴ`g9D!AA+eJ٤q7|P}"BӈPf*!"ۚ2]o_#Jac?f9658KH,@| +Y"4:ïɵcڣu42jegi3?;p3S}jd +V1dxQuB]o,Ga1-??֛70u|ܿ߃A>9n+L3˟0G%,rW(q Is~? +Hq7׹97a! '1uHtX(8vѢգdpDD.2ɮʓ^CUfos„_FB2ϐm2JrIT% #߉ug?n.eO/JȠEEI3^##ZԔ^]:`;zpai Xz4CU<Ե "$dsB{o:E .?"I}3}MMq'l$xe!&-Zrݾ)䰔yjrg%l"?3ʗxxr)S + JjS ݠ!e0چx&-\4EUd1t[Dϩ.?ݥ'{bcZwIV;q%Z_{GPzЖR +%c:, |?ζCzB.߰n>zH08y?/:8ԳJ^5k󟬫m + &(XKTQօt]ıASJũЩ7ZiOi*yyaSu9`O+phj!p_ZNHzat?tbZ9x) O <֤27yBM"ޮS!'9;=%,˖a\( Q6Sfr&1"$ɉH +V`2 Hxj\aq'ZAxYP,U>WW,(Co)g+wu;!NfNCv\M,q^AbW@D;3c7 G$p7b_)3"^]`dFG"3hg)Eͤ4dD)R߳eڿS˚kҧ>h_ uFZcQ9tRb&8|U0Iq1` WfS)(HEbhi{l<0)T,Դ7$MXdQB#6jK0ҍtC,RI9SIp)qЄ5(CXmFHV)5h3:i, \_k(=܀*J]2XK~ vk`Ɛi? +"GJI_BZ2\ IJZ%y-/rWߌ4>\;aе_Bn* +m؇eԶ?^!I{tZhC/J*DӨjhޅI + (ȡ(;tUjW#eI͍ FETMI$!՝YEY33D2e3Ɛ0p3$ Ac Up4Tԣο$<*W CGΏ#!7GiFHCB4Y63# Ial!JE7tE./rw,xDKMT$H7?-^E9r(CKs AG#١ÛqiC6d`7 +P+B%Ћ- A# -o G!APKߠ!%"Z{<:>ArV 7։"O \|=3|MvɁbٜwlڣWm?4xA"ıS %2" U_w>'ѥOK)S;6tOgX2T/+\/փT qOu}dVnh( q( 'Ym_,'"?-Yt,S1 x9C+ ^y$PRx=jڪ(2l +_Q 86wK+?M].{[) ѬF煒x5-rB܀*@sJӲBfiυZ!C @B_@[jqSai\ZF2oX1ia&Q{QH]5QؾEYxبYSO +Y/38@.Xp5G<>ɸ+}V;#/ + qK*+S63K:8u +x 6wl8KŸa +g JڬDb:Bn;7]#ZX|lW`7Ua*YPkv+3B:d&VVƄ˃R'0$msy,_;`Ee4b_VJ#AgVZ A!ݗCц"JF::}p*N<9HzfE}C|(WQg .TXaM ?xD1L|s@((ݕ>KP"СbK?,Ix% _RoG~1t(V "jۡ !dE݋CrjΉ +O +Rׇ ,́@:D (y]p Y 0*GZ%b\W(6m@3ˌED٢=hU;k+9jOoJQs7Qp$XĥYФ!M*H, g2ق 'Ae@bM[ 2/ۧ"~ҸЧB)7Fk?@A'ޙO%o +րQ67\q@d#&Rv>| %3:CD LOE=!Ek/U'j*뱃F [05toғ$A$krk Uȕ-!=k_();W* ztB`yb):GcW&asuK8!4iV7)_%wګUC{ V0Pavnx?,XHx^J+mAD:t} +29F!MR0 +=SŲudĺ)C[4f@ -ʪkK +YUݏn XXC10q0Xzxq2sbނ=QI]~20,֡aUzjuWbgD^: Ϣ/b 4~_t0O@_ߍu) p&(x7HsodK& (-Xm’VsHyEVd@A!?}޲G/ Îu<;CCB䛢~ hbhiGr_ҕ9Dg,rsxki`xeogHbW?2$ 6 54tX7J +$a1}&^>F,3|2CD+(N8**_2QVR@K}=A[Ťq m'PO/VV5/\;f;j@ƭp%CźjخSs#6Ͽ34=TQd{W'I")S{ubͦ:YwOx ]ë3#V +Q(3@1iȒ!0!}.=@Uu*JP˛tLZլNM48dc +7]?d(ɒ$ hFB3F̣XFNu>c#V}K)d1 gmwV]Ɠ4 pOG7Hޠ~6:MƌWjz7R f M/I9:z/Ie= *܁a*vZ/M B b I#Wq=DFN=`=Ow2o8{4#r?Cful\&fn&.R pJg.Fy}=FѽwQ"yYH\W@` UԿ +t,;bٺ.\׆b3[65 |ԕ/B1O}98\ DU+q8PbMOs")xwT+;7Xx˶o 5W1z=ro޷[ x&Gn?+b#PFC},(Q F/Rc,IXuBPI QJPn[x Njh/0 + z\vD>Ƚ5>v`Tq$fzz6t$v 7P\,(=0d_T泙~ j~ƀn2X?tiN*a$cÂ/-+/OD~,a(I>Q?VdSϩDb̈@͘h ++A Ju'c_M;uWv"T\ڦ,Jюdcl"n,y\*Yh+Yr+2|3vּ"dpqh _^ MCALJRuSC'}xKBXv8}较-)o thY@SZ rTG]M=4Y0)N~?)<}ŧˉ쵈i?{!9Ho:Q*K +-Q&V -o?V,֧.YV!]Fe!Y`bD9*>g g@gS-@VGfIVhK@qY8Xdgd.=zdT42[;tQ*>L\te| HU<:f?9p0m0gW ;,I傔hcޠ@a89bYGrq8v=Pu,l?n{k*Ќ*L9gLI<;/TX +BD$xaS3ƚY \2TKG؟YgV k;Iq ?35^1EA3g"vĦ>[,NY>+'3rP1ԐƬn2ӷbTQKixr.߃堘} Vqئv[W&6E Nf gjBspD*\JTCKt;͗՗ +-y. UEz90'|A8W&"?1~tęMOoPUMk̛iUJ?we>7ID[!Ӄs»׭;Jmv)}eNIlcgٿ ؊,]nSy[\b"t 6rM&n5|hb6fo},.+3| D-*ʌ7E"O.6+e,$i']*JKIH oՏIwcF;^ Z4@M +:0ەT8%dq%{@:Scam͐p2r Y@Oث87'@XB+Lu!H(*[];-*"JSSˎ05vG`-6/vtyհD8spɮ[*_QXDYES旾ULOPoiQaa){jbۺYx֖ٓX*мfd~w ]tzZ_f8"PcŵrdRdp {U5ɩ8J=3uJ +ˣ lJ6qdnL6c$N2? 1V'i55Ah3ö9F#g^UT&%dZEHVL~Պ^?%|015ch̪ӘOd-tD8S/8;OvAP"ׂFPP¬/^MS:bxV]yqdW4E4*tCzEws~(Kkݒʸ&>˰ Ӧ]-'>dDI>54d }G T+$餜%'ĖUYϦs GòEͺ*\\UFJTXrK,"mڸ[Dz*A"}C214p$4PDek`6ISF[WG2gzV>()GMHn7fOF154=\|D!CUFΞJ$$ >ť畓+k<׺qMTxuJ]<vZ \*x+:dBL"XHhǩW&4j}JZIH נc'0kZ9=E rcgrֺpmBw՟VBK_DtNWt\9t˙c"tlI>?6Ą^d`VuWn L$F3ukȀӫ}Jlw&,_]ON|#j@TG/}/m_bwx{cyt_6 ،Hڐ(yP#9pFJ$[S=I+j+f㥨:jdL1LgRQbpj +g1uN_ +ʻh +ħuW}YAm +La͇hyw-/"LDLʝjc8Pf#HY`5# +Vj{ljأcY +~)!B`aNUdu#SKI>b4&ѩ—Ȝ}Ե Y[5ZU'9oH/DdCc;j'e޷QB40^cqU u. !5 THmA^a05 Qhqbz=5I U : ղb?T~.b^sU-t&dO\(᙭~gǑ=UM)6u>j.mXjoF͞bO^H2q}Vߚ)U7% aK%:׭TFqqlKZ#  ,͊1zM+&¯_%MdtR9n;_+-JVkC(ȔQaג:R0w*a1fCﶮ+~8QĈrĝ%pO,G[Vq,>V,JPy]asE'w[J~y'A|uJ|U{<t ) Y.6쥣j~)ƅ' pF2?`Br dX) G]xY6Onh&!hMeTM?*Sж267j)%b +Be3# lq +1mbz1`Ck%4i`tOE)Ed'3|gmjU0ϪS64۶_Mlsvs1r'jGP+5g:+^!״0H o?o-ޘwn~Gʤ +?k\b?'i+y9 l( *zvIf='17|\_XEVҰh!kp>ا|c]-p!tJsاu-˖m~I(x~ׂA +Z_Z]!͔Ҧ>G$N8%SRk|q[ex!T*8;!E詋8gG}i!ƌ@ &6*u(`Ooys,wÒ37>]ͨSo? οvjJ\kn?U={XbeP5dyřJuZTOBeڮ lo8IFۈ1١jt+0nCę$&,fX恲2+ k yl8_{mka̟L>_j +FA`JKi$ˍ|yG [7sÐL_E;87^"~2c"{҆ MdQna-zX$~na^kNj)_;#3 d25¬?5X +cujjtDp +k3IU$zO9fP꜇ +FunN6记L"ԈƗ_kyP+O,*O 5v1 yUgIC&VO,DV ږ Zc Xx '  Ua-nCq,2|dE7]F&Tu~9i8s(fp̋p "0#4en(G}w~`y~ Yo7,Gw>*M:ӬEYr 03g*zH~|jDɬa}:W1/|݁`[ +#a(Q+۳#Gx+{ l(9kϴ41-#A*l+,OH*¸J"u}g σ% [spR p^p - &9r`;asXoʷAj@>`909F? n>"%gTwqv:g`+QqdJq"u 0sOx׫~TN7L3AכrE!&B+r u}̄HԹʥnCYE3"XŮ nXŏD &7!.)C(az̢F:k E=vior],+T|4rd,@T,m\߷\-KJْkyZ.zZ^GK)I&Z헉iYN%˱}Xx+!#J*{JarUr1fr*yA|JfWB]MH7誺yZ{}kLB˼p \V +f6Q5E|3ςZeD]Em~?GH~˨ܸbQwHԻ(sòC.R?JAtjXj0\/Q̸>U􀆽uN g:Xt^ + 2gPgp\ʎL>&s^jj`g&!/+f;/9 )ҧ +sOi&z)"Ƚ Ei +b$@#tSs7@| +PWb{Pç7 %eAI66lu]JobѳNBTSϛJ9G-Yum◞wʡj.UiZ-G l/dƩ\)!:%YjN-Ar d9At-w?¹#ݥZǡQXbC9 ?(ma'v:GU8%ȃ]6+\_ [R/7![zK/iaayvWqv;L.jIWZ>heqKuPW%\w. R[*/Lߧ?ljc?(i a7Q&uvԘ; +֌/}衾NAc{(Xc؜ֲRRM'niCj=rf?}[q.Мgh\vm.yP*Ž `qn+j$CB& Ä6}ae j{ X5gH;¼\ŰkRAAA<#< Ϸ'$l{gZ<%Y! +pmQc~4W^ǗI0n7u."YYD$mϜ|;ϒѬ(X?i?nP^nUQQDE]-!t0=Ln"?Ff0VEHu\$@~K4G!$c7 #Ft݀1bO4 .P`d=4Ba<)v=xDiT)]Vzwǡ]+ɡXQ@&ӡ,sz2ajzF2MUXn8IM+LjĔ*-ֈThԡ1Bc }5)gv@5A(* KQXLRajgªL )5LC&BMa&챡6u14^EnpAR:+* +5B3:4 Ԍ7eq6FHOPR)لS^tӔܙ=F1vk1wfO`|ETψ. jק".bDF5lȤ8Mm#x׸sw%2U0%hy(b*bD<\^wz[]^[ZWdML#kd&4թjs+~ ȥKWwnmċ&(G~~U]:I6p2&vb_&2>Evj*"Lk2c^vX%Ah,e2*44B2ɰB(: A}珂&2 BN!9{P:lKdb"y$*<.p%i68WPd{DSSQA*<0xX>O-|>Řf"АjSX+6Yl"aܙ;5M} ~Sye^nvZ<&}1X1Sfu˹ՠCmgP2WVб'FJ VQhf$EqƯ (5-9+ +GEkoEDœpQICӒİ)jZkdBYjoԣQY2F6}>mY,H&U_%$UXDd!$3 +6!K+I\Up,F|!nEcY;|ȍoD참H"RT:wEYގ81N?2F\B~4DbrOz*_Z{lvՁ VP_2*_QĸbL'E.Rj}E^)vUSIfcd^DZ'b7ۈ J4 NDl1=ny[mטn_IHRTkl^47Og(ώ"\/ϵ!Т"1Md۵ژ?!b\کb֕d,F.S[ F$ D(u^H)Pll9%.d~(ƯP>FBLf߰B-W^$(AAEqrDe#XvObF30\9UK#q6"QiAjf2`IY\GRI_:Q2o֐QZQC\Z%)i}H &'!%9n#XiHXDr*Q2QR,)JVFXDyr%ר"ghDFB%5%k5EbѴ ++dkI4H ! ᨅBR'ÒB)[zIPWp2ef°U3Yh>, O:W}g6SH"X_&AĿcA҃(vGM__u %;K%ޑ|Xx4cuo,DVAQÈ,RQM!c*(S'sQMJ%C42a3 8jy7&AtiagA>RT:Ĉ&!)p9g&&o^mC f9S+ F\Lj3L3\65 MϏzx@_$"ءGaA6Y…2)C4`sa&wRmGM-A(=gZ1Y)va`@ +3kR@ Uy`gHdJ 84Tl!l4,]cUׯgA\@n`NQ +|ei U7TաPLHe0!<sR +@ qg,qW'zk;ܖA]p-㞀MˮpBYʡlDw?Ƣ 2 [" -.JB[g2 +WoɈeH @nơ]FQ1LA63 Zc2h3 Oӡ+PCM ED:!23ÈgTˆ"02ht*BBv0 x?'Q,4ʍr[)_%4Z}pO(an!a&*oe_U&*3BC!C#t&,.>١4HHHH;Ad" +xPG(EH2 v,E# F㓘e*c:5# c)8@  e"] +͂:jUus)wOOD+FDS>҄O dZT]0>T'4~h>r/0AK C|yRBRжUJHʯB$n  f | qGyAm/q?25 0COA  @xY&Z5U$;BRaĂOjL=ԄJ 3*s1lc;"x|D*G< +hT5=-p:T, t`j`yC {  RRq{ U.W Lp>@jLx)Tc"DžȖAi/D*R^^bL8&zaL)T) c8Jd+C\ y%jUSP:5J p@p@isO RԪJ@0YS\P!'P`+0%AjDˠeh 0uD M 0/yGCS@}S٩n|*U< ]pL}D3ckW!E:5 ˦'íU"E%e# bISd`@ +Cbvx6H AqPPZ 2@&6bT,^3G Q9\ I%Xg݀bRxb ;ě 6!.Ǡ4vV z. SDKU m=*ܓxT{Dn^.hZVBgym 4J))Ҡ?ugFA&6;Q +*q +߿d+ε`߉Ԫ`gB Ypa*¿ +@"s~ȐKc%'njS(E[PJp43/| @}¶L$Ӧ6^NWJ%j 9-W +h0/p^󿨓C G ϳqƽ։gW{k#}pEx:պI\}+G-*rZ:-J0= Ί=ӕMf̊+U(#w%{r:헻MiM y 73(\ AR pi5 -RT&RHD)l 9`q k՚($x>sb\x?DUU (5+ABr)(Ӎ:G'ōP<\  1|!ΰDsi^f^&==Mq&߲nuvץr_zs?Z.#Eg5E )]Q7){g RwP^_hivK ?ߊDIF y9YϽ& ƜQq aEu/m(<ǀcV3"J^4 +?! {5X-QTy<% J 2|?)d"(jҦC̬z)ggTL^sY.k]<6wzgXBm!n!Hav|蘰r ѰfT4Dc7!ct~ JD)bއhID;)Fν5;/hwGpnQᷪ䐇έ߰=p +䥃$XcI^ͣ2)ӮCLԡ˭L=iv'l< +H}MS0xx]Ĭ6OiSXb=WH$u? &|&TYt8Yj7 JZbH6~~5o`(DŽ\ %}N=,|rX`/Mկۮ x 62szhlG!`#-"qFʥn r1kpwe.eGCG*$EXC%M,|#ﻤ"e뇜k{ʒP +z_Q?=MDC136[?c&@@yo楖ܺ>n`JUV7lŃ5)p>ˈ͘yeBR{I?2Sb0dm 7Ns a*BLUyfW(%' k E42KT#: @BSd,9CLC}m0Βh +}SP%cga2S^ +SLbwJ~@lwfeF(^Τ Ԅ'jR0)ܦTCww߰(\݉+X'ph"PE+% ?tߘd7!@_a;pq0\zPHcgrA5y9oaY09XmcM>kex܌Z*jpX$z,J,}·%Yl,tarHMJ P׈GP9*%7tt&85S.-zK$ӁY1y^,7J`YeKm4`yHHh9BPD&hW +XԄ +22P כ9kq6>xT;y!s%IށNG i6<xC 0#nFa\E*9ڜٮxUM,;ŜqnR?8'wBKXM%`vG|-ą7vIbE !>άKV3)#goJyT4W]C|h>̓7?*S%wNu*dXpϐנ6&T)(jP/c^=Wiّ07O).͒i|P_^Er]Sea%GيdТC-=.EY?kDg=Q5 ` L'QA:?YX[Tg#![n<񫧾(zeMAm 1Qb!v{R~ %Mvd5WZ-dk +t2TNa}{ B[ӄ١R|c +;*h5 x$˚-8-y)jLg%4ҏtO2 ,վ٦ +JLj'B39R_ď2RUY9Hz(asʝߧ!kݖC>XH l1*D(ʠv-(٭.1c1D!g43hkR +քQ5p+SELrEN37퐨B[9UVDO}Jz1n,lF|-4td ĢC1lo[jHRF:8=!~tQj!)Ȅ쑜ICW%x#J]5,d8u7B=^МNEQ:)L*770Q.|kKr .(Xr}GA&vR,cNt04q˼/ZO*-z~Ztf50-Gm[Ȼ^FZ<R̕+XT&10 E:Q֑yK}gLIj޿T;WR m߰KRy=ё򁲖"łvC$le:Qb$ف^Q5c/8LPNT:uKdSPB9DHpH ijV fǨdUvϩ/Ft%ZNe,n?å Ȑ!|ϖd XQ*u'ذ* E9岰T| <IeN460v X; "_}39wSȹD :CbYiEN*Ǟj6$:bYr"-BZxEhkdk⡤#jm!6zt\-Jrr;4 w;Z]ZS 6?K]q9: kW1 d(xfNǑ.NN)vjnеjc,UPL+هALuQpLYӮ66N G`%v (P'Gy$Qnuz$ct}\7oj{/Ha~1W-yM`8OJj(-Bz@VDyD>&QI'RuM(+# QڣYl걖 #XB :{aJ#h!Q=;ʁϨҴ#QpA-t87cڕ Oyd=u«J/92x¼}s8vM'ۊټ 5!tO`ӜEiI(>ꤺy5.r.V٢X26NlPlI̱P d=Qpr2 vLA/Gerg:G ޾mZ&0H pi) *F`9 $'WhM^w/l9(b>l ,PYvER m1uL'XP!CA-<}P%ODq:nb􄾛^R͊V=[]C*I⧔P5A)*[zݯ$=[Qj"+j|%PoC uBm7t{E%6Fɀ_AՒ+_ZWk( T +Y@]X!%uF|١9b DNtfSXAQDXқKaLΧj>ɖZaHUGqe^MNtKn֊B!hP> 3!p{~%oU,R2qxBXPk@֐H ~=5 aXY?DxShVPXx# MʃUDJ%`χQ~]#2'HԂEI @=P(+ }BR8lʘuaI"B^9 ~社BC֛pR/ۚ[ʃg^jө)pg,@^:ڽ5]Û31Ct]{CxEs^˾ ) G<#kT&_/1z/NjL"rؼy?;8Kr'oM\rʥ3zI{Q&>3Cz,!3isx 8D4f #у[$~PeL!82e=Cc,NQ?f]d5@/Ѹ ɡs_D@X-p r vA7BkL$DW +|L[ZC̆r 5& b2"-|50$&E4#L[l,E0}naAF gOFtGuD`RGT L K?$Xi`$`4$Q/* uIș\2$%EQ2)׮*oI%XK%B\BI-q%ϒ`"􌉪e AeX2 Kz_k+1&6dyf@3(J6̣n<)R'#%uYEN,.Jy@ (I= +F +3sIpi$KQ4Ld3] j +2dRmG%| +&>IFmIQQ7;HBTz$(!BIx-)4+4OVݷERW +h I/b ǂA,zH}g9 Pcr!-pOZǏ\"jA }$`{p`m=ע٢5<[|T[v䗷 hE E:R\#/ Htb5wB5"p;7F| eyqu{!z?Y O]1utfʹm'f(s1b`$#'0R0(q:.^\DhgcubԪEE3eXDк"x*V QEEFSxQ[ɘjR)RkPdA2{"AN/JAf\08Af$­IgglDHΟA HF"bz/֘@ !i> ux_dJ!^r o ׆5kHGC6>͐\A)'^eF32)m!/XA o͋- )R0cݨBLPH݄X :, 8B}"DH)h86@HmA~,9Q#A,r| Y19\xW>$eёZt,AP !K:}Z`@d@+(O !A2pA! 2@Wȕ h&*Q9?CC@o(!/+ڣaIC ?GIQ}X:u[kຏJ~t}83?ZtyAgBlPQJGE?m>z? $rT0/$|>zra 㱻Y@jc ^C8BX%MA Wv:'7G-.{8 E 5< "e.()(8<4RkauGT Sǩ=KiyX T()q5EvWǔՐtR5+$.5tԠ P1 24 9 oUӠ&bp b!, RPeTHB>u!mY4aHEDc3Ȫ!@ I3|!>y3! 8 kg"茸H9Cː3IdaA%¶f>iFlf̙a&3RbtE&^F&.Beh" H+c2*H)o( GPQ2<#ypk]?adq1h#M=Fwy @#8#oƏ12HH_ +$pr+Ƴ͋@P򢨩e xQTǔ*uqJVdYa{Q0E +b\0\ĥp\'.Bzı>" )0yh +!׫~m-WRv|dYO/[,9o] k8B(e!ȩ,WRȖhD2Z/2hݳhXJ^:,6b-eD$ QJY|"<%/dIobaNZ"q',0b P+m 9`vIc紗TVbb„U#& +NIV*C +f@Yb;LU b)8.6ABU&Gg&?Z& +k8)>N,M1':*C'FEQ3;!*0N@*OyRЪ':cM'PS8?)bLaTPp'(}WAR$J(-''Jyo("Ep +-Ha%Qy2s8iQ^uF(Vu"0()tʐ.L6RP8/J *yAH9PR +N/?IOS'Z/42ßlJ)NqU',`'걝w4(|5|)GI(vrDFL.! +-a%F +9Y XbJU\%9O@D }0+fGJ=&Jdd%%0U*9xCX5~aY^Xig +`2VNb`NJVx%n}\%S(UB=إC7f-h+V\$6 +u$ب.+Acqq$ .\d `!ڥH$$I`r~D"|D eB鎨ޫPRGl9'J,F*,׈|҈3™#HqR`D7\,b UE"&Z!` V9_KDVa6^U<$E>i!B.@ VaC!#_"U X3C^Z)c[b0d-5|nڃdz?~<H +Vу`14[{=b=lҚe8zyXhEf@ 8kβW$F׻~Yo$~zSZҗRRPj\X=@հJYRBG$⹃M#~(45wp$2~@ ;o2m Ml:R;Ml M7'gjPm +xjESیihmE؟)iqi6N5VtPgl>#AEdV%_`s _yf%{A=.ӉNQDTK98w劔Ia%\"\^6L؍@*4/Ս`Ff^0J + m *GlT 7Z!l ;^^VJCaVWi l0Yk*ЪրWJ 21YqG DJhe # EryFU=/ў~%h܎%!5%b'Ѭ7377A ͋ҪR?]JLU $*>!I1[d6BTFҹ| C%ԏRpJ->|+HenDa; Zo,}C$ٴ 5 e*O. +OϢ\mثoS2ؠmP!UeX *-ٙ#Sn5\`v&pdNG&X uo_Ր+z[XЕ`ᑫ+%m ($`pbM$LK& !*|@'RŌsd#@@N G)f]rxFP78]ExO:.WHA 2uҁe"r;rz٬!@viKxT;ǴȨrj {$E!c{8ǜ9 ;+([l"P>i Q{m +& J@F͒6|(>!%cuvzW<#qg0eSw`sH Mف*ow Vyv& ){ГЁbħ?c%PX4M1eˁ`T4:|6gmg2:TЪ T%h@徬#` @Aui`HX.o,Qxfi x]a" hReԹ2p2HG:_ g%Ū)m71Z%s i0^Mua:t<|S(̡^nKL o6mINS@1";Uk(6XDGza߉!B +]S ̋F2˻pYQ  <, '8B.!l5 .w:l+G&D#AYD7 ̰pV 6ˢXq @*ʢ9Q0tNSM+A _wJͷFhsk`rz+ Hyfc6UK430~F8ă{I=~u)XBUmXurWtiz(@F"}~] +oCQ%m}UEۏ\m|k 扈XQ:Ӣ=zZEd8U@>1FiS5(HueR?4ύsiFښxkyȾFv})'c=J&r{). N0ꡌ6*ˢ!yuqA`I[ uJfv؅PW--qRQ +A>ض K-} +cT*aҏ2]t1q:aq3"rۚ>?jz!}%|cv• s~վ0w9ߗ3EI|Ek|\O 1Ϳ]LV+tI8Oj} ̾w?u;<썚og JÂ+huR~(w/MSF + qD[Q|w05ߕ963WOAkb/X PeÛ"yCW`~A)M̎V\>uTʏ78ϔ)) +bQ :k#"J%XTeT ll^~WW#y a縥Fib=s9 BK؈NBW,o=ݓ-q߄('4]/1j&TIЋsS^Xy( |p!.r!Qp}0q ]/W⺎U>n";E=F~㷾}EcUZn;8 j@7d=\#;ewysV{Ȱv +!M]_,Ge[/\k)%Gb"+Ք/S~FuN uNjo7cf7[Q}Qalg3 &~ECoؕzgЬhblչ{e!lu]i [{%g>6x&t1=% +QQ7ىN)|}Fxλ%B*F|V2*L+kY_ +>Ra=d& d x|%r6o⛙>2-]^>) w#P{扞oo5i$p" xb0u*ʆNګ<ZMKyU4O`L!xi +v=MM}jd #}, G_6SO?H:X"џح ~.sʡP=cdW"=ȴaA1  ]+$=7$4Rj-nv"Wn^'!&9-낮ىz T{2oB6t`|idR|$p#Bg> \u `w1*z Q7-^V~aPt&`˯@,h7=cy-} , xV~ZQyBCo:Ջ|L/EpT)#Ieg2/R-4ֹ6FX9!!//CsL ` /-m3v&+{)8gaj*^<.uﶍ|p+kӒT9BnLo48n>nIՖobjNQD{>r)K)md,%ޡ&KTujJ  糭;xx,?s/!]W5c[J߻ l?1btaI7t>޲%|N+WϜw_UewZr'xnZ<}udG3:(U%JbBjfi%"hi +}0)W'.>A)ד{&J +^`p9~g=.#s"gߚ's&=E$C>xI,잖}=ㄋFheYV74#GBvhjX2^pk/ õ8vBM1R}/=%C<9b3&[S0{<< YR墈 kc<_ + +.bޟ+ŤVs9 +\Ory>:W#au[ +_RW;gKS&.xŻ[_Ǝ>H«/sXd_4˺`ދQU:mE/>~&/V]?vQ%뗰ΡAR2^"EK~=͇sZ.˥*t7Qt8k,jwpkLinr ہ-h5'r$!cҡSgYJlg^B /h:XUf]&9^ tiLtȓ fȬFͨ^%ޞ+K,0`՗N1:=&]0XjC9O?@ *A)0 ͈ <.RЅEz!l)豞DO*5 Dũ +sM=.BMm4"P :߱M*z)t;JVZS$M3K(gy}U)~fs 0+{=ZAy^[.Cԁ9j$wo1t:=g1 +а鼟c;x rC!Cbܼ%]>ܕ2ۆ[ xp'Y@ln[sq{j.<:{Dsey] +7gnlf7MWFI 1#LiW2wﬡ"hcU&+|[WGMmF|򃹙 C̟cHEPrp]tLh4\= ʴ-Vx9:X,sPPI~qEkؑj3 VAX̏[TZIHi mX|y06A5 @<*ŗ$LrIj~|*L33ƃKl1[I69jzV=fOŮ f@SNý i#( +RIQIXd|W?J2(M+6pZ@e +n|,O|#gbTiB~2\8FH5w-ت,\LݱmV$ϗ\A^&BZb4)vHvȹLwZ=WZ:%5vݍrޒgg G iF;o!K| +&lƱ=n7,AnwqAfM 'KR*9`OIșa|-݌:rj k*@^)9Żn5Apoȉ/6pޠ?kH5m] +rJs2 o? +rUBirثhu5I}YL1aEiSAt?,.8yZpn8)Ǎ^pn|ݰ^Qqxi|/D3hB#z޵ѳ|)ϻEx_"l7Vա6/.ÈB|c)yuG2 v^*n#M(]A` Sz@I +:A:.\tǀXb~QH_c;#ID8lZu#O"Y^{~JX ^Is:î|y6+UX< +-: jYahXKކ-S-JЅܹb**<ÛB0qɵFGe n3fҐh~ A]%h x[b pECW. +8fMBuT`PTu |3BQ= ;>+A--z~Kfl{H!51/SEڢͅ^,=epyȾJv; lWѳ[$dz}~o>`)sPlT|Ys#5#i'̰0JE[ű<[o:SNMxޚaL3ÞM9\yi_oۡ{}zxwqbx3 +)t"ޠ~њjDҤt}Y.+& =Lgw/ƪJAv+G?H*5]vC2t|Xʔcw _[Y?&suC6qՌ X<u;<-2 Mw~} [|P.Aɂ2۬t7S^rqέQc^QὪ:=pBKw˽l[B:,ma NcmR/3sVIW4ȾQ HB Ep|m/4d++- ɗRK6wp״<-2]=70 .ƙ^\d>ǤL{˃ Łk#`{{ӍXy ܋SXZ uKe YnrmDÝ7n|>O)\QcF QB*ȼ?Zo@ȭ>stream +J$2{6ϦSWǥa\:A &O}Ek.%kHlA + mz2#llghӝL㝦Đ +l +&Pn-F<}&`CKZOUGaZtҴ.,'GPDksU𽳮+@!_2E{ %j@h!k{=K֬iu;64uZb= +|l(^"uڝ׫j|ʔ] c_n_$Lg=AEhk ǀF3rvUbV!q+2ULYˠZ$@SYmЎXZMc\ftOV}o\e}g6ULj\j'\!#n1#}l5ʴnE?He2QGgY-myF5[b0WBH9@:7GBbin!= +@6YY +uUo`fe`m*ŎݝWP_IY +ļ=mJ +Nc8*~f$j:}糨drP[pW. PgwjQ9w~ZHaWi'jq?ir29y]B}:ւ;rogcA{n2\P*B-l^*Ϟw,QK^1yPϺ]ԚV5Nfjԫ @se|ph-Zi7ӻRoNNOw{}Rvb-ci>׾uzGro̐$>i2njEyN}Pa(ׄAou qq6{dijZW2d$N`᜙<%14DS`TJBa:joXSZSK#ϿoYdtimI5\A:-ԥ$ƽ5.$kb+.U' R^ij%UwtRU\\qx3u>ZFzKqt7`zV@abZ5I[']v.hjs^J[O갖 +]Yu}n`nv潔20*:STAiŔVNpbzoI(")r2z9S.Mp_pMypZ'SPh3^V0w 8ܬ!lzSsc QWC \fO__]% +V#Q >ע6lG(# RYjDRfb#- kt-)r UZ<1ZsP!"̪T5yպMZjqDHF"P4Cw1;fNQ.8guGd;59x!aPJ?ТsJtE& +3MZ#"iNmAR%BTmV&O%ZZ0ӬtbY ke`{ AH&VDKݳm9m$ڊWcNYEx8ܞ(xcnW B32@6 3[&~w.=BL nM$p!.j7\&Lq#q!,n@\~qfH. J>_ȷޛ͛JQe08{Z_ԾT葯֍k&šs*~kWY_DX}jpZX eDVo=Nk(%4‡y@bp^V}f"0xC`%Pj 9sXS"8jB8NpT ip?Q.ئ\aoFp;b +9y"aik +)jcs1L;M.Bb^4z`0jʥUMZ1o)Pէ/GYo\_z2s= +%63,ÒaSPvUAa{?b iL `O=q 6P.1nCB/ǐUhߋyW 淮6yMt/u؝EAяϺ.N.j,6C9|cb1SjEಁq)SOū@יc'j33[}A)/}<0V|ǫ!ml إD L /$LDm՘$8]'~o5G&MUU;Za7j<8hkEhE$f@eyxq̊9z6Yx67s 1 +w~FzIU>SVb?sYYvA,%0E sEd,#Zޣv,+ 9<|,0K[M8C RǡWDJZYﭜp4O`- +ZeLo9k\$epovIg@l[A!h{blClLd ̪_~~ +\ƙC:hX9%#泽f>>dvYy.IB-ӓq]ݤJXX{d4"`ϧ^$r冈K' ڇ,ϮQNWߋ3Ŗ's]u9s!u(A53( J l>1bZ^Zs?05Z*c.p]Z3af3ԯ)e5l1L0äghPrV/} +5ؓ@sO=%}^NAKZav3ie^.Ha/y\;pVA,\ +iu94v2<GA " Yб X^`rt!.FiS<>h NV>hb#fN-ʃngL8 ~Զu~B#,ÍO蘹@sX~SzV@qBgISY[P3;EmFJXY;X`݄6(&4=hw(6AB[bABf82$4Д+IrG"rAJ#pV&.m}gǪ6H@,ņN( rІfmCVK@a.PaTn~CL.eX o} 䆾u $z5R EbhN;9hQљ!2t OR̈wx1dEGU +IX{`Gֹ<`]S&zYMQ vJ}.c9C&&+oaBIi|'NS{\ 5)Ko@MhpZ!3-$4s<V#d$V$0@1|9\Mi}v[|;DoH$`Px8:nRvзDo3DľoOyFѠco#y Vˆfr)Ey?NfNTmdy7gChǃK*VB6U?\5 nkYI"~:!pm.K^G`nGMwrCr hn/_EN\`fXk[jkfk۫ Ymt=Libgn6:6 v0Hӻ4Ël?IHp +b. <8N+5/"34fl lڛuISf%NuݤϨ4cbђd#/XI ۿ9 [6Tj;76l>kng^6{[Y6,u3m :ctB,fW410 &5#AouoY;MF^tԓ{6'sKs_D[ t"B/WB~M=#?@ҭ1sc.e7w%jLQј$&;~N$o`oetwhw R}Hx9lw#;YOs?÷y\}I4G<w篨nk݀1{ɑ/dh'?6 ʗPhem +:n% s4ZYj"VQ +xg)F7NAϥ1L VWUirT=IrU݇܁ qR*c{G"x~lexIy`𶴘h'tiw\mv匾ÛEђoi6nl'ețxUyߠ{jmy`Ŵvx\Ba!o0QRO-[7 p7+BpRk" [w2no4{lYA]˸~|4}E=iƐ^./0oeIInwջ0h$8Fɍ{p~3J`(>~m\L-ӑvp xy3; z`ppbE&ؔz8;!&Ov#VGHt GpB;$\}QioK\-O<7ř[6/~aU2>˹6?`fZS]OU)|\Bt琐O:#zoϳF~u aÒ톫ɋ >*c%r1r0h&H-y1;gz9L0GS U}m_g rLsnߐr+z +AGϒ(,e/bW}*!]rJv,I#n<'UhMϊ#Vc(x(IJ}p+FISuH9D*}Z _[3 ZkyzQ_ 'Gh>"NrixFv\h^qsaTJXkj k'9l 1 }1 +9wȝ-)U0MYBrBU>k:|wCTvWe!G$wSʽ9Da3Y3;2x/?-#H)8 5{<1N#d׿0>C.-55sf yr6xh#.<kwz?a>fQ82vIϣ*gaozZ P1 +$FR@6g$֬ L)T_/k$rYzMFWq0 n}@%s( f>\ %{ عf6qGўވrc~‘ͷ/Tǽ P ݷ;t>J?1tBnϛ0 ] +)fR0Mp?H[5P!!?{0o X'/ph$"F!~|(B|E9B-nP1j1ؠ ٶs* OXs.c(j +IJ)B秷eVu :4,rW&Uњ왾;6 cnH_r9Bꏼh|*u{O_{ +y THf'xF>|o}/H~AFױ8!x; &{HD%_Ro͚}?OvӀG>~DO15ioWv52`ܡK K_;_(i̿ ud h/}G7MJDžep!LßZs.6@J3GOo&}pr3 !@!?\_P|| ]A @?`ɓ)00 oS&: Fwd@?gyV@T <I5vce@RMT11h?!8&z8RGiؤHG`0/{,yl΄jϜ))[ȭFiC9Kxۉ+LJjKѢɷN.}a4+ [\wq,r%J ~T-tˍm+T)i*M0c^ޠ!/! .ʩFĪOҗvMQ <:)x]mȐ\>cNZ@;pMqoTH#,FTR9uk- +&>(!Ţ{$$âb)3T"7e M~bJgճ m7z?G4X\&pC%ЍbהּmSk!ʮRXp)x6FXq YQw"7] Pμ(M׼;W՘4^CUnYu߮EpNZ'm*fgoIWG..hhN)dQX6j`#K+f*"%F vj=k6$1'VǙ_#=::=P:KJT~Hj@ܶij+^Gόb  n,T<7Ciiݼy"G7X`fZ٭6t:O8Ջ5! dߍGNJQQ._C=g!dn$ӵXaPMX9,נMYU(rǏ,<`Ƃ + "̰@ڀ/;Lqwh 4,c=YAc9#A$PKzޖ8|Զ$@\l%of`bՄ{C~3EҼ!m@V5r/y&K]/3IELv2myo+6WXĜ8~U9srGb3w8+UUt`wp:8 +"(f(Xqc,R6'PyzlF|滠!Mo!iqCsB,4foG3QS jPt<^V( mtueЊїRJaaMo\k0=ʂcE8'❅NL0WA;4t㜮Zɱ&OtvHOE^khgN w"Ɏ h1*} %TAΐ>{1桤 + % uฦҟx}އE9EF4NЕ0{h9L!-i)DF? +uQI= q!zhJ죁?p<%q=HnK[zh~tp>Ϝ֓rКP% #?bq^pOhΙ{!)%)9C TDE +"##5& ϚBgG^FwY{r]ָbY:lsP[ bZY8yQunM }VN A@u]ΒY/81Ye(6tk}F7d02F!u$Tb*?J+v6 ):*DG +=T"E 4OZh ȾdXT~%Û@z]OT-,u~ܗكkCcHm22~qĵNT>1uo;aijizL|sH drY'PUI_yyu>~ L@%$=R衍`9#BGTdwؑ>q[ydߤk +j¹n3Xm ~W,ַ8bC:A(ȯL+G)eQ>|U..{d8xi&lG*B~u@4z=ΩU;&@6 yCuw6R65;$yz|͡ܧ!EiUF~ ?u} lҮgSLvoC} +ڪ&͠d}5#>={*RDT+,h϶5rFPx>z]I{'1UmVY88}SPHec֪I_B68bwƅ;w{>o0^BވLï%ͅvb]:iU3zm&^su&/+3&HL8]V)L[sPj |cfd XD hT[;>g.34+O$yHâDdreIy~g*EL÷bGIl#}@Ev)Txe>}6Ox 0 !JA>z^qehö>mHߞJUsz9evG?swޟs7thUP6QfsXg2Jnۏ[m6f9|7`:Rإq.Pg#ׇ. ѧ` :WB~^A +G?F@bh7:+m:cߧyīe虿8D}KC"gFș5r2i5̞cF}A ֔;bODvJt + JH) KOTys m[84{#3#ПCfBM;|47n쫛RP$2̺(t]u%UĚkfM!J)@d0xir62L +q4xJߏsyj GDvJ:jdtBQ * +Ee?w{8K`U#Z N(y"NA K{h=8G_Hej޻6 mvwD$Mt9h$H=b?b a9gor6ً6Ȓ+e fykG4*8w?5I`'m +|d+h]$Fw w6Й0vݙp4CU/tG 0xf2 _Lk +0^y1&';z]FPKe@:oq^߀j@:0{m ~IgY32)R(V|'Q|hHd qvL,&8߻^Skz8aAG|<#@ih0_̶0zkܜ{gxOd',B~ k㼍ܯ6t/GraBu3{oFPNa +HO߷!5s}QgxN^'$J^;R߀뺌LbG +=IE절XńvDT7u'y8~?{GR3mu;q^jeEүۈ\ߺd1~kGp6=Dk L:R,< &p_B~(}":ХiwzT FG(VQg/ufܗ:Rwkt,Wkq֥Z@w|&1~mmuH>)Bgh?7ZpQ#}`huMᜯ!|4n]1F5 TL%R_C(cd7pZW"yq^쫑H^y} `>~HWD~jm PP X` NHK|Mᜏce:䟍CwZUB#OQ ԊH?{os^g8} }ǼDgi~Fou(yJos>*'Hi{7iU45s6픪dh@,=|Ϳ:;[ik;"v:ƽw,7^_Ca?37ҩO RXz_2y.jt$: hEc':v+PYg|xS- e;y{o %wj] ^Hz'EkM ݷ99޺nc#g$ļ?'Gġ/rftN Q +>|(#}=^64qr^}OdI=~ucq^ø6?؇2 {N89uO$2?ԇE*F&Dd_2?>'DozeQ!ҩqOI؋@ {ǺA Z 5±GpM_Y(>CXV7&'D+߄*:(y;x$}!ADh7">?0B;lm?~1>NT]Ix8$g<v1xg 8||fl-P@~f"5Qh)1{jtߏ2hKߏXG1|j2 \q4 }QnݚAfP{5rf0>: gY{4m^&00vha]hKA~ @:3͖ad]={pkD}^3sQ;=?< ?(Z"Muw6O,}ϣzsy}FPhD¸f0E;Om>N6s?/t w&|lEens|$N#cmq'j0^yqP={'Lc[~ro ^]sGٵs^θ0tlKWo)l3nNκl͍Se;]C6ء\a˶}m86 +-1tnL9*!݇;_3η8}û5r, O +~ClW8a<:'Y +L߄Nb]N q`?-H7R衽t*Z@k3I5><^jwbeJ`;Hr@>;|~7,?}wݓ{>%?- " < \bgs]Ҹc.i[[1m>Ma}6: ˲q^wokn[ߺ27eM&rmr4"&0yi,ܽͶ}-d.jVƏm_\4nĸ됍Z f &~i4ouopD纱&%SqsGQ eP*WJ`Q7t^h2!p,>'<mi<{F06sHV"=6ݯ)l:{F,U8y͡ܯ 1uj ֕c=4NMy G[rzoBne( +6LQ"M7k [E7 5CF4km*h5~s}1r< o(>4Lh Tq]n뺐_w, x>!}㍳eh3fB2m˙L,lÆnYvej.;u^vIvc*pOgR[u/q].Gsie6/k'$4^8%mK9\❌Coe>5 f:j6X9tpQɲs]~ѻ0zm04-K[\걬Mmdٳ>ș8:{wlq6z"KÞD4B/)q.@ǒL:|aobEIw#i̻quE)hIXTKˊW`q +\F]x{˰CY(Fa\@Xl5slAqe-r_T1rsPI +A%.,bwx*Y1ōo@vdp=!-5`/vKe`X74yŠ,f&uyjzu A#Ef&F'CK@;mQ@d3AHiTˌUDB3#U'!u Lz{l@@Fiɡkˉ01RfB̏LH?x~~2u}B穢Jf-fKxC)RE*=[̍b>"<%e療J['*jAt? 6 +0ӫgG!DJ(\CA(HwI9}D(cM"&9)r&ȤVs1 +2q $ 'Y'Q P7@xKz̏,a +{ciƉX3u+'@R1 R1@%bY +i-@byB`(&erc(e,q6IDXJ(+r#|' +k1y&56o6V-0?:arK.rW.rk¨FJWӕI\i$xC7;X7?B=D~f1t{x-]`,QŭP%FLd~f 98d%JpZ  ()praI"$IJ-a\cXG-sh )qB,IIcF7rwv* u2)[xv;Q2 )AiIi$G$bMIX)]"Al@%>>6ݓ~Hb,ĀJܦSG 0>=dhi"pH B"!0 L" F"Wr6 LQe2 X:m\YqT9%|KbƐ+w+l d9'6'hEB"QEa-q=aasc]m XTe Z\xKtP$w(9 q$FĝE^md~ ;Fy〮gEN ` !)2 ( `ᤸ8Ӑ* B\?2ͰaBEI9cZ$,6,'p)Qj;Mq}nzp\ Psܗ7mƉĮ#gQO O@tJdܿɈ,CBhY]3%)~ْ?3ݐ=ht:NRԐBlMs32kZ$n(%;iHY! 4MO*u@9{ۂ2l/@kФv0-(XS0$X ʖ҆3NW'rK8A&Eer'*\+k:}0xN|3,4h@#_QH۱9 ()oЍ +ZȑO(DdFSTZ-+ ՠpN\uHdvJn&3zCFq',j4"%Ԉ))q Iѽ"dYuf#s4Ky +[eE={J%nG%[<QhpC>3-~l8#T`KJ"X@t:ZfքEq"K"NRn2-a̸7qq9L`Mm*/y + + פE%2n +ZP + rٶm[jwT|~RАPn&VlMU>QDqbe!s&dENT&vN(ab,ZjF,JT%l%}`{B)C& +Pn*Dސ%aA/TI3\YŚ2(ǎİ8 k#G7 4IkwÖ@ѭO\r n# 2`AYc8zh$}KI- Vȥ6h2vZUX86-tf M9`\0]Q JIR!dnލp'l1}d1S$boО BF91+1f[ +Ę wUxt6ؠXd3JY~"w0 3m +~Bw.2r@^tz8s¢;[*9q-VmCYi)'$EpIa+͵RFR@Mdwn}rQRc )Pcf#5^[46'7|‚^QQ,/hn,d(S";"ŦMH`NTTU[$ېh"]X/֤ՃUY]E>gfDEw;wd@-r ƌ4: *ΌMJ<%3IzqUl4Ԡmb̉Cܢ2irFDJ2J?DI +k=`aԀ 00"7N%d `+7d +ઍ@+K(+׭*( Ja`U3$} A(CF` q + ZH2:c:P>'2? +I^%D=D}*,̕ÓΒYkOa3E"ۀ(♁Tz1gLH^QNg&$&zU) Hs~&z(T +_bV&$>}N+}/H!@$?- 0@feACB-FGGL>"AҰ} Uf"ML;1~iwi&٥RD~'ݣgDe4nUE$KXb*i3n 6hSIKA3& + HȞjÓ8ߺTZ`Hi>$jH +LHU/.Sk}$Zh#| PHz +A>? +K6_V/񽐔Mf_ +e<( LکUI/ !|PJ`xGxY1:i@kBQ:vh+ O$k$Uo}8'ۄfW:7H1FX48 KJs]t 0_7<Z_Ff*Z=Afp +NHTŶQfЭs.X5Dۏ *T`v(/9h|Ά*̚N"L›>5^CJk IDZ5<@AȢB'aPEwhe7_Swx_O~b]E;z~G i +ޤx PDzqu@ 3#?'W;Ma cF}-c0>-49lI^I=#;(!RzJ%$eŃvێ وH8IHחT +%aO4*K2|k5bYZc蔓T5aW±2:3P9C` +#bMv!g];&Kd +}@IsଵXpP~)4lC2Q?]AEHԛRPuJSדUyVg,=(yC[׋4 :*kФ3IJ\6He"^̈}i?%2%@`]t׈|x==(2!A~ݧT[ FHSE^B@?֌K{DgacK*h7Hy?.M⮒'vQ"u O*Ff%z}`]٨~BD#O؝Bx6r.3X6kw:GUC6X1|mr.s9`Զ9ydcG ^HҞI;kH$ ( j`v݉5|@|,ouU : H0&i5AQJDG"5Mnu@TPĔk-vO65pƐnG ƥg3""Y"S"&ƛGQF5!, ! )R058Y;c6SbdUΐusz GHgŚU\+PY/Pٯfd <<@v"Aԣ`H\k +EPg-R zOC*~Xcrɕ:A *H`oyij>ݲdt~W/WﺓPvP 3V[־u?@g#0vfsomøg[S"*›f/sm옻5M!g0%G R*3&N'$ L +Y70 O'R?~uѶZ1i7o<6AyBRiU/˰gǾ2dl xܛ}.E:iհgȤ &ûnhwn6fЭ#uzm*]iTov `}.P&#λ~M`O%3)h?;("*{i9 PLk#M? +U ޵?V?P_6/pg _swwfJ߻H޳" o%NpT6`h7&QwAu}1n&wo@#u׏O+feT؁(Rk*cJAYJXo BP)-4 ص9<|]S, kRٟVS&,jISK9ء>,Z38kp'1ϟm!8vw ܧ铫cb8Yo{6rߧu>q46a+~o:wzbV.f56q 4sV z\ MTe*UŮDK +ICC2z$P?Te H?!JAEΌvI0vh_G0 1xpۗ:a\C +<NgАe7Zo_\81vI#˾Ͳ̟y(WGO;~L` 64oGQ´m뾍] JH SVE>Iثh\^RLi +TL'89#}UŎCrUJ2yl540ug3LY#{&/-r*r," 𰕂a9:%f[15 '|7O T<\r mL CG 3nrOe_Ǜ\3h7eyKaڇ,?` t98qvR> SX_rxhS.*/2$Oݺu-[4Z21!5ż~<P,nF'\,,\ YNeL?okeS%v*0a)ez5%1* n Oci1W@ VK񽖠T..BUkM)-A lq4Tž L,hmܭtkB~]!  8eZ<7݌1ylܬVk9l\] 0`ʌP#+=ƹea1yn\'u\rzhr+dyRPunAB%([_Y)1!JR!*n3"#cP\}׏R .%"uNbLC8'xfGy'2VJ^SVZG:QaM~]1^w < +y𝜝,]v('ioK^Wh70([H@U&RRB eu4qnHObl R@VȔ> }"j]=*H +R0,g xöĒrF!dP6꣇bmQ!"{ceG^ʅ-h@ٵ̷a8 xJߪ!T0id.k\,')\#[ul)XIn Imخ=yˮxnBE`Dq*:%*gHU2U"7{@T65Wb?NNfJ {/&ӮU3&.gZ961vjo خy̫}0/vjd Làb``x  … ܊o Iq*WM,ϧv TAR>pHDZA>^ZZL@5vܝDjtdj#lQv@AQ1g*,d +Y#}_n;uZ>ցCp`_x`+4<<bt,>}\52پyes1d~X["HDZ%@3s*% BprRZ^ZV1iDbXX?iKB_ȡPmKᎀ$*:i&BۛȈj-*A1iEualZu9\̺Xyy .+7qcմY<[ 68EXpbTv`Sk Cll, dJ]1nk%dTuh=n>lxNvt +B n-(AE`lg$?rCHy)Q. +5B #S؏B +LyWzD +xF8G[G|gevC[\*z]Z\Ev=ȃG4 C&.l'OE=%ƬG\w֏On.Lt&#ܠNѮ4Pm|d(;{3^ؙ3p37[ڪt ^^dv^Preabac8uo`&6ΒZ.Ma Ӫ:ƥ6K2- JNAt an!EQf2{lC؀0 +!vdz<.mp n\.PKKƜŅ2`u URW2?X]퉌NKSLx.$u`Wg򎻡d @&ƿvRE&Kz*ltc2|zc0ovcCcccbWpKԔ7tCAD [c0NPD 34=p0n0MF4@n_f%cTfR[XxjR8i-؀ +BXUNǒJɤhic cs)ec-\qc11y|w$m|8dt51$,YN3@$pÐu*}E='`WŸcjށLlR%{$x"".}qld[L0T:P/#al1c ,BP6(D_o˘L;.zx`v=s[./zZGG7qoxV@R BĕBF+N cZ@-GH(|T:a\Y ,vZpBD*` Ъ . +ly /C@i;\ m|C 1 Zf`L=.HY{-CTbX 8\)ΧOJ@ P|n?[ĵLJG7pHBxM&7l"- m,Rb1)P߫Ó,*vCҏH ~\%,#޷ڳq4|Y[\@OG :1\6X7xiAx +Y!^QcƯtLn|▿_pEyU@ɢג@4Ĺx,`4Bzh,I6ӐG_-4:ZdxyE#,fk~28t]BbZg+KLXr+cm-bQ,Lg2‘bi S\RlyAOiM| A2BW?G|K)$a&xj[OV/ q;B\饇ȼ9C?;)H@7RJO[Ǭa l\X[N.:8v&9)F'fLd`TTJlXVAʪxkMS pA3) %q FGB**ZDG|w KDRT705q/%&d3\LM6hq5"; +)!J \!-42EԐN4<8Zp.ńm   P4~u2륄,K,3!i{ q0#.TJRbNF<̩[Hj3ĕ^?kMLKU)@Yfha L92H/06~p~m$)1bS%%jPn12 +c% GH9&#xFpsUMmI1qF +H+M8@!M5C\( OK3@?@3ęn8 $(,0k~fII3t ߧ#J_6Rk )M,h +6’=sPo,c .(8qʪQLmR_I4тNJMo">2%s|Džg˲mw02ZxD砑,Bxi'" 7zk$ciEKX2|9; 4 >De(">8_2۰ :.zx"eSĉA|g<4AՁ,\WX8|m]qbqa*^m@*>˥[q/G5 HD +Kp3?< +&/,:Ç`z8SP1eGk§CXX!LL\Iɢ +beU)LR9p2LaƧ*:KRƢ.m5+'%dįpx S7> Y>B|+hsq@FJ? >ùKV煈`b`/Ё3 `fL4 { X`Lđayf"VMعIZ"a ;-|7q|n2|āyxJo0SNP5q5=wO1?|)Ï9\e'q"~o / +1,X*k!>R7' nf1 btC'G7oD!3QN"4f2j_q? + 8A!F|p.S_xON'T-a;D98\pzuiĈGl)bX$H:8|"0yĮQfg<MURbF9r] +P 7;!<~Z s*tu"dd&xIU c'EEr#Éx7p2gLc85,ךjl5o -4 B0ͯVN B<*76ht" !w OD.87^tk|!JIo1"` 3`?p]<_pxosacCx YK6";J67Zhzu@1SL#Ŧ*'cD8X;i<iCMU") +rHIXh8 0|e8. &!R tez`RE!Jqzd'r=>5=TE@_Ѯʾ Q7DIq@%hCyhN_~pĔ꿊I'z{<m ID Ĥg`bkJsZdUdW12iL1qzYW\Qٟ^?ѥ?Vq$Pe?=RTGA{AI"/iĻDZeQ!= }42 k@ ""< x#z*:zMW"~#qt/T#RiIq7`:oc dd'U_HWC8W+&v,OR9*D̔؏:hۉ8 Zd[:@e&U{]L׷hhv =H*G`ձ3&z"KC?)*TD{T{" `wscdEO"1*SȬ@EwG6?)Sg5Cs1ʍ@H7JL}t|bHZt*AP J@~@FDDg s ̾eWE>| PHz#D4ج&@J K䑧d_'EC] +*;B;#3 +-<{Ɛ l0{vUGhZ12?WEûgE<$jS,~(Nyg-&ƒ٫ZP-sL;qvuX1vl1zj Nc' osާ>OW/4JB5$=WPRZH}Of !(}5s!*k`ZgJ'gh,ֵ;=BD'4Q& 4 + LP-63+#h6,B[ĤWF<9Xg-ĮW/6kb,}TȞ`DU-D)і#G1ZS=Y%Y?Ɉ[ KV o^B! @Ji%P_9x]:YKZghv mOĶ0u~,ggpBuzRf`#>  w5p57'$,&p̜[F8vuNXfj,H@}7PArۙ=zg褳ddv lyW[01nfjw<>~)l8}3: +a~3>}J9tU.$'TF +?u'IwU $.m1B΅+жED/ +HUvK*E1DD DN'VɚH(% Rm(WEIK ^#p!:oLXvAvW:=cxR#*{S*n"%O T^\qV(ѹjG_ȬqtFF6Ͻ Sg+ 9BJv'5=G2#A@LHHn"gѸ[3:{ib$ཱི"qJ0z6R'bФ٫^\N &~A SiټZ3P9`<6v5ށWDKem%6> {?.P?ҧmC8ouDAo2ʲlɻe!KA f~vGPYԳuxP-a3Gӎyppz <@=792T?C˂ BL63Zh7Mbd_b!:G5z/XzQ8OFgri?&KtSX/:4 :Ǥ[Ub>8B:zwB]L!!Ġu"dn53&!(k?Lri%n LNVϹLzr5r xYKl[Mc6tvXA7(I>Fg +@B=W*6XqYKD'.^Ĥ±Rh<|_ϭkqv^f* 'VFպh/(!@,("mPE3%O; DwFE&͵#TIO.vL#Gu^ŝ4QG|4jx:M \ g&g:Kږsb^ T4o5'a#zeDw ZHw.i2^c 7VokjSZBSYN2_pJWN=( ܳ4"˯ruIS8BZoH^ F}C;$oiOv, ?S"VV+6k&]3ah>8cx, ?7V +YG`[JdZhCP: 1nö{ם\=%r-s&EK0BgzxvNî`\fNI3˂ŌW,2;% JU/.%1h|x_BJ=5V !*=Q`gnBk:J9gd*6car3]f w˳gcS!5|$&+VUF@6:}DW ]Qve=We!s1} +8!ً: =Lޚ2mr'cq^ $)i-^L!6kE mᣣ!|/se+;xRnՍ͹ȓ3|p7^:'EL)"1}ius]f%s2|mH^(Y( b; +ģO@'ze -gjΌ鳻" o%NĮө}4n +M\ +V=kgX˧ PRk:5aȇ.7Hh]exn$MnX+jbm>IۡB," NK)m`qggɈ_@L}U t +e}R;B.AHhGqQ N>GúcVܭ@;3A 6 8O0uj44Ŭfĸo|5oBM^\("SC%b`MqIB[Z1Ё,y,u ˚ފi]:Y6?ƽܡs`Ĺ&jc^hO9Q)V* 5.$$ fj]54}" &L2w2}k^LGl7o"[)-,<oP-twH3|n[ɾ;c.P0|fଡ@4ej;5yNB=Y7uwm ND~2#ޣkhɾUbSx*-1Y@R#( M[Ik'qz>|L] yz6<{1zj&@f dk|\%ƻbcH}熍4y@FGR[<>g MT KPi&ߝI;b=:οVrFoef0aF>jdr|5<&elMn3qlY̟w)D 홿Ң[!6LU(,Tjݳovm'~w>Į K{ źJx+z$@mAny< vbX~. !m3X[12aH\$B;;x庁5tu&Zٻr|R$&}砐!pF\Gun~mBFعldFȒЬGw,ڙU@~v_ ΣN0qpQ&Od}a5 ZC_藷ήs/tpIjoj:x;2zNBJ+G)υt8_/mrv ۚͶMw-sjmcHPo +KJi >u3i@,4>}v@d=$ōaJѥ\YOX2JW`rJgp7,Q_DH?֋̾$a^ruR+"]mv O6'!d5n/"Fďhp3m&BP;J^Pmhs(sdJn\{}5WӾMjFăD[pr@c}@Ul#}zm"ONvK;A i8sѢɫ P?#sXwg.-1;)BB:+zmEΓ0 Sk#ηΆC2~i=yY2׹1{l\>M),Jk=/F^"Չu3(!w{vZ!;DLb'ϮA;ek k㟿*pG' G8(- o/1`] L ?P]'M#׷8dj#^ 2|m[6w.&l70X']“SU,ʫXDqZA٥L\uk駋iU)*횿֕c`Wni}!)!I-(}b')XK#INRJ#y~?M[MC2 o AB +LN +ONi GF sSkbG;|VR$G>(33{<"8ٷ[+#5B<5(T]AIZ oj^#u{N_Ɏt|`CR_5P'3jKH,)SǞ8GܩKBFze9a6N[GIU} Y\m^8]&Roi,g0yJ0 +y"hZc/ugZAJnhu|=I4ܕBx u#NYsjKRJ>|[2.m&RC6ZKVSPr:SXRJ3"z& .  \179'NoJHD盷tH D4XSn+=,KkA tupVi%o- +=Lk{+":I=+z=Az=)K7;OKFf_: w ITbM OL=&}#hF*Y98 QX +MJk' `@~} CP:KA=oM+3&6ptM J]i!.xDG3Yԣ3LiE\.3A [T*.7ә Фa_`OAZw謉0 혽6[hpv }`Ck:=alr[.{yY.;ׁ&v<Ӡs3d9NZh:}~;:mXX@DPЂFJPx@H8yT*."ev&Dw6/6s&Hy"Bg>Z,\w#G;h}.9424ŵx 1~k9YMo|(GLc3܏B}+ńta+WK(W @ %?%DI?b\R'( nBPa1A TdL(vY?zQ2o[I7<"LÎJV2=,x*h;1~;(Lb8%ԥmuBc5" H$/tfK]WQK`tqD\t?QkNo0vctn:MB7cshy]:>𗱃ifL@[j Ťr@UR%(?UzE%}zwjq90ri4۱l'[a6osh7YGߞ#Ϋmk7dt>`"*_:8\K!fM&@ P0$z^fNZ0)P͓4?9#z}Ȏ@$ 4 cۼS"lPRם]иdpw8 rwocִÄ/΅s@Eˁ ' y:{dT083  $Y+.j&;|5VO.R_Ȥ3IJ]!!a`Q@Jf݀e:AYSbJk8ZŞ Ӫc48_c̠#G q]챬'klfڛ]tB^K'u91wVVѭmvOÚ.l6VeP_%Dۤ*5t~L~;&&ՌL:' y.$\4d_bMYhX2bJ{ )ps^?~ѯ]~$oԇCTpU:*Chcyqn1~n\Dj} "]}h7my&d ?:QOKԥm{,Q)!9م.쫩``Q$"io?P"v*6VK Ƨ3|r5U.EZ7ȉ;i4L@*q>` iTZW K#c.m\8b5?n\ p2/ 4qZDr#XAnVNj^a[aw ̸-m52+ h,\"NC@c2lQLbG=tfP\t)#f V\!t }{+\7>\KPW8@ RLByPK_AĢmò+xR)" !s`R3m,i̿.9X/;He<{0/3фs'\۟JA.M}/tƫH0]EBe Y@p#09"09prd"_\uMey&>eˋoK7rb3q:/)L"`z|Xk:[^X,vr;%%G[L/B sOHLlF^|aaWx<=UrO>VJ|'|<vuCV%mעĪG]/b~:WtgpsriV-'nʈw_z_hVhqyLhΏUy9{:ٙ?\8&/tC;4ghח繺Ijϒ5zs8Ϲ[\ |9&^W/ˇyxTF[{s60zOf7v/i .i=s%];k.5ygƹP]խEUq)k7~r# ';{G~IK iigQ{3\Y:ɟu\> 4pb:k Y7*M6Ut!/AWy0}7=hŬ0Y\ʺu?+۸oģ^b3Cl+ۅߵXW jgǞ;$|Q<'Y^Ad-wcڽ`uf qyOMYYΚu{vwE nM{B.?QHW7/Mo/vdnAj**}_e潲 +YA:Z=^mE:ϩWEs./^C6$۹/^nY}8ԟ;IÇ̋lIaö?g~Obթᄛ_L=rkw݆ZNu8͟x7]WCJof/=#+amȽbHorZ<:kpݙ)R#J6smaj2ّ?m X+m;fաw4*m_nq wq)nAN,!kOrB +%sb9m>ûVwjra_5|Di\ +O{ɹ4",IuPfVRՋQ̋C ۑ\nh/ -Uh43)b6r뻔_`C¯f^JhS, xL>h ~ϻ_nWt6Oĕnz GNݣsG$[ݖ+iqW<8j'if`sx\ɏ)wŦm{Qk[7`Ʀ[cG~e>8 e߁ѽ5PfFi+P`F13`PO<8#ώhC0Ӡ:3$˟cr + cr1>] B'D qg=Z+XۛnD@g_7} [Cp:d_C2n\yŻ}n ڨ9ʨ&4u +FJ,z'F6wMH{퍺4"GgOWeA #}RJH.%k<U;T49Ma4m1صUӋ@58o&\ %iv3AwbjZϵxw3'Dz2ZA,W5lxRn;z.׽Q ޴xB - U7y[ss+ћ֪'3q6ݺ Jq, T) MG ONr~-͍6.!uՙM"0 7y z6%3Ϻ" +VW0*¡`C;fm!#lIhR7@rDs$`/ޞx$wvrZ Fž}LXuN~h}߀A71q]n2yU㯣Bÿv 8ߧftf&FM3% \~H%NyYl@y|&| +`c?X5b&˜;Ӭt}ia>(C\ 11΂S@j޽af9»%2ϏK^ mXE.1|RX/ahB w8MR}3Oi^yVAK?Opf9X"X6W *|3]LvdL_ֳKꀉS)`Zt(jsVPsZ;ޏ)@>*hŁ䨐?,ÜH6KB]-dDtS+c`9MIWAWVܒӶa>z'mM-tCv`&#0N:Ru!6 @Dr$\lr!2ʵߖdц@Gpd*i&FtF I]Mr[j8^swCeYU>Qd؏*O5LJ1?J=^ӿ+QrgfYVPͭ;Zv;Aneh^/%=vsL; A(X | ]-Bv3P+kr'4lIocdD + 10]5H%c y6&lmOd AEv tA-sOEi#t49Ңг&!E3m. +gb|ۊf"T>5 RYsp?z*\2V0/2m4Pq]nwMrm"Xuw''Px hG8P;6lxC Ĩ0ϱ xgP901hF?aY~U] +p?ŋpI]hCBDn-!3?p5ɲE6Is٦, Kv7usWS& +}HַX}(V2mvh@d/'9OJEh'qL\onV=Jî篑XJwSm(a4yDZXs\}ż m=OOչDIFpܟ 5QO5"2j-g:~>f of-ԿdU [V?eG]Ev+43nt:FvH}:1?VlC$pmeK2^1b,sDGێtAq!ZJY]xWQH ]d=9A!O /$SIJ`/0L g޸xpe^Pկ#" +cKX.!wuL4f<֫V79&0 d+OMVV6SI`, 'QT6 rQ^tKȦ’l]Cm;n#:{U 9>z"u($"iKI3uZiPz&PȞ_;bY8?!Y-og,P[-b9LTY +pt~ՑqwN1QG*h􌲤Lc_)i<ţan*m pREM  ד,e\$>w0fd +vQjAXPbh` DxW s:1uHkGR[ah?T"nT5LA! Ҹ5C"q/6XGͮ6D$"~X/z=OYI\ Gbʆ +ҢEQ$!T<LO8 8|()-;,q"{m}V2{sL[) +K"5qI^4aϬLΆ_ - t.V\KDl4) dSx2f?Q!)1od .xmWu<ESO7&H5lW󔿀[)l9SNP`yxZd835~l2$#K> }P>~S:!, + +X202230bf0-acd5-40be-833f9364b67152585ca4-61e5-4651-867a-fe8ec8c67fc1806 695.25237afb942da-0174-456b-bac4-46354d7e476401efca59-9f29-471e-8eb4-1267ea85791252537834 фD!E'">v g0h (Ls,֘h ?xXy\ qUV:$0% r-$]x5X&cxRzqx>ΆY(9a{5$.O b*~Wfj(f :F\L + V.PL)UĕC\W 2W+yxo'5$+Dm(='O Q$'+l܉HĹ5q Q$ Tݎ t8K *ME_b-9@}HK.n*whry$*bmuFsg=fdتotOܤa+G0> q=MA"$E̾QD}cݝV,O302;6w,H!sR]UKƙjD›-Y GF1v=6G4fV;N-{)e.3"ik|Ƴս/0s0wqJx$>k/+\ Xt<Lamw U3BY.Ċ ٨8yuDsBOLwEc 8F3f;gnM.xԒ<[ixYZδg{(^FáH% Y&t܆ŋ5,evMy]QHSR~w؆ishfmM?H?~KKa0eDZc!VٜXMt]C%bWfn FƆ,?O$P;Xʎ`דc.yL(# Ad'࿞XwH,Hyi/]l/$c7$tK nD;iE"m0rH8dժ 8Eўe܈kڀaDžCYSWy%b`_`lNXaJelt=]. XI*GVA@Qg.747¡A"@M03g$-HXImN;gͺWyΰ4_6$f+W)YsVP&lI~/.3}GGy #/u4q :LCMQ^0@چj:D4U5_*rmA!`-AHU JSTD*#ĩN>nrQSB>ޢ)\4mn",9tJG$f!+bʑnTU xSjzæA/[C0S& Ϲnu$ybv/b*y|D6?۞ +L 6ڼj]%0*m'9;:<Sp>6r[,!/i# +b x4wwJ\gzD9D/}^^~h7_Un([{P8~gXrg& XL(zy닫ЏD9 Q:YzTApcwb2j]k)f\N%1LGkRA]Cj76Hٴ1Bojc僟f g5yZe+?8999l,d@͗m"D(FVDT Hل|,Z-?q%̙>19;ᓧ˥] #WNApavQKQk8;!M,#jLvgrJt' +C0EL\FDDDDDDDDDD +W1Vލ>yn1Fh6FRJ)$k4T) +w + `5&))΋V,\fWˋ|jC@OnR.YM4MZ杗9)#G/6rf+9?pxGv0ִ^e<3ݛӺЯ_g;޿3ϯ֎&Y[rכ-Ϋm'[{6gmwX[lyΚL{ZƏl͛ޙtWkWj[»cg7e~g7KomηL?vcu7^M=|;}]^ZRO9;ׯuLu6O+I-/ Gvo3;>ފ/OvN댻{g)Zcz{OmvSntOnji}O/)-δKOީ5ӼZe?=qԦXKq).bZݿn-~wu=TOz8Sݩޚډbw;?ok5~;}{&拷f޵ӜV]=w4kj;+ݾN<ާsnm/~iݚJ/mVcxӼ~Wƚr~=9S|:;ez73}m{t{ֻ^?lٮۺӿzo;:}}l)=mkio7?[_}:s7-պڮjLsݴ{gګ]o7;My2ՙVmokehb)??3ŸΗZY|׆nrmsj{~~sP};n~nNuSO>=9fo٧]7w޻@RuR1pjg}]mmwN浜un{wާnze{^8O8/ť8R\Iqϼd[*޵݁wƏ;wTxmVkz:A{bp7v߳~ݬSw&&ޜ;dyfֹfv&zwswSnjmNZںv_;ڎY10NM5^׿uu^9/gMnvݴکm75~Y,p|'YĿxgYj:;ݶVٟÙh9+μ8nZYěnZͧpqfݬWi8kKq<:ŝ쩞y=k9gcR\Kq).ťR\Kq).ťR\Kq).ťVĬv~5o8z^)v'y*}f4-uh3ex؉ΗXm`VM&v@k 6X4 (wSݸV/}3,g `&WVX \i2rBX T>_‘6Dzb`/or A٨?53v"ǯ4&8&2}隅,kbkVIrTߥ\S\!ʐOKt}5<(XξXlkpFQr7kE-S 0kkz5~phi|Z1;6ĒR,2h,@ p|JPb5h}*1?zseB[`9DObi2 C)N)/Whe)r!GRdhHcm +96;l c }˯6bu?/ȿZUYΪqo];*:ԙ6L)DΖ9ߚ31,CR,Dh*gXjo nY\Uq,V՛ N>pı0KXj%xTj! $MLX30`D2tͲ&V ƚMY5zI6ONV8>9rvմbsOT7Y:kخB =B)p&|s}vk~w6s=W_ gvo+s:7뷛n*b2[ gZ-4ά޷޾y;۪?'4qkX;kޚ׍mq;g\1c߷lo9[k_Kk6[|KcLkr̵9V[oƛksÙ{cBsoow=ア|k}ϷkZ33?cL gj Mku}-6j9Y}άI/t+u]gs9[ޕƘԽ|u޹w׶ڿO-vlu\c~/7Zjv]uW~{c{-/]WkVc^3}ۮcB߮ywu_ָ>Vc87&4/|k=}ϭ~4GU~rJxX42m1ebhKk8Ighx PkUl E*(4{ْ` =&l0HCq [Xd,\۲p0H6|p u8aڞB˜@Fr򸆃cSOb ^ +M +( raQ)3 FICcYM<0((@0r*2QƒGQU4R,!Ҋq e2 +Y.0-D * \Db@H@JlJs>a&rH4Qᡔ!C.0l1ATNBY:n:@;\aY4$ı:v > Ʀظ$XM8 b`2MN7Q!}GcT  u-ec2BBdFAFa¬^ªd0 M8P`zBALDJI8`10`5CX@{!#`{ + +$J> pzI"0@:B 28t8M$: oT,rWđa6 +5 o)*;"3tlSOH.k RѻB[e +!sN(?6Def%19 rl5⁹ø&pv>\ν`}g'J ^'eCa%,: +63"X 38*f/v^vO_/&#aiVmPU U!20íɤ,J,h],\nYp: ad.7qbTсE!&(5B1UDSBW{(#AP\t":S 26ݦ+؝%ua `*^nBBB!p plӣPN°p㳌$KC`i EXR" +bHee"U(bO4T+HST +d"B+ Wl4:mFհtQ,-}޶u +0(hhC(1Rp662mCD0pf=l" %!mL, zK8M?6PBn򷆒nu(nnCY6ΣQ&>m"lZ ٸ#3`"ܦ؁mC +*"-mu۶. +`u@ٶmlV-#b=**Ƕm[ٶ6 6 V=(!8Kѵ<JAtoB-X^[=&m +TtGqs[eocCqC2b< X026-¦FPU92 =lcKKX(@Z>[Cm(Y3C 8m +f{,3M!v)P`Uz},{@x0aRКa:\l@P"U\h +N!F>Xcp p(!'a6&56$ۖbض͠¶mPlۘ0m#eٶٶ;UZm+P@\a^N}AZ@ӣ.jĀǒc_dckqk +N$iy& +T Cp> pؔ V4ޥϋV$!Qܷm&O1mD=y8am۶@m]q!c6Ze6 CٶQ⛃Ƕmɶm7#lFɨ@!m۶W$"pBm[m[U7۶mm?jm[9p<&TsAٶ +мPI0$PKG +1nۂ ։dE d$'km8=F2 &\ )VPD<9m꣝*emB+p,*!ԩS<`R%G  zLq& fh|j c47˚H/S,' g&IY9fM$ä 7XؚsM,s૜M_c;de7hotcCƏM#t}$AdX~#8C!fa>Ҿ8ICCØXATY*ęH¦35bLCCl(ǍLAr6J<% lFF@jlyJωG N`@y +ND 0F XNSIބF+Z1,=\pHc N/f#W#8JĠ*cTFR1Lv`9jFm@8z&dz&*[-=4(R +}V1GHJ@r6B%iՊcaIY*(mj;g49!9QHZZ1JjqEijʙ$m1Jh" @i,(k\p)kEGM}2TQ!؊Z5#BdQᢴYk, +,HiT5 AGHh9k4 cZ%} Rָ?UW_~V VKd`LEa2W KAa@FFį |AgVZ)@b"F{4쁥A/>stream +02ȊFg( DUEt؇WFp쀦"צ)Y$*r\(VFb9Op:,21JfX0!Vk80u$Uq +{A$Ԭ +d = plFX҄C .D(pt)x">QP.H $Va?Fb%:,cDh #ħLNkLA`@ r X<*LxyH<>?FfǦ\AQa DU##,E#)/EE;,)x .<Q8\Xxh6lc@"B +m$^B"+gIltKt!b#e8^dbƑN& !G6A C-Q"@&x`l ;TFAvYxtubl0":'EbL R0 pН88QxA59g{hAńfBy CRKAAR ,B-[,^EUx$$a`P> [ddeU0q&z%3M{6ª?z=HaU@5}FRZ(F`jc !qM)ȑD*HC(6ꁺT$ȑnIFbI5ʕ0U.FLUSI ڨB{.@l";ptHA`8hHUEDR5вr GbX80TTLQnRJ@Q4¥B,t =@4y\!,TxT@ T(ڸ3HPYk8@$tYxn1 uAs G=W`J 'TuA7%RL"r*}IPGۈhՒ p Ѓk84TH hg,Մk8Em28mEIgҪCQ1^ېã*faJ}`'''|$b Ƒ<VU]Hls $lƽKG1 @ ЃǑ,*ѝףN 0H_.Hh뽰 &ӆk8$ +>`L,3ZUcMƋ` <EPd#@.Dʢh.\p&^+Ȣ(x2>XD `υ<Hnn ,l>깆c*$Xp H%Nmf LtI 3%NR"" l\ɸp1Px>5Q18Z#FjP-p5F%(@00h?UFpbsdq ʗ0 A BmӌcLjAI쵡 +F0N=.%A"5' i@MDVX@ "+T@l]2&`"@`\Rje6a,V%䌥Ԕ $h<ς6-)S 醦K3 &@|Zp \7P:[VV'"[v}Ep@`k5M)Еe2Z 2IFJ,#TSp7ݶ@!v0M )D-lv#eϸܞE:m teٶҎ#p@`:ۛ'6"2 +4N*U @|0!.<9c$\$\^v3.dA#Zk8Hބ@Hs(@495#Q[c`pJ5h ؒ,z˗r!Y.Bi,"g, G9?p)N$o,?>ZmF}&Ϝ)8 +ڕԈ#H6>DF\ H,""u* 2qE bI7Ռ%O$N,!& G a{AXHaF.BqJk8$,(xh (8GX셒]J/Y}m" s"'-XZendЉk)(k@xTW&UeTP,T2Jw*! PB5VgS.,Ujd<;W"7}`20H 9h背kd}NJyҒ2։*R +p@\ypK)TyMd|jTV9p:!դ _4xa +$+dݒ~+|௠pm# Q82^ݲ8n(#Ap .EQ&znJ&ѰOD}O+Ñy &6+Rfq7`6MdIIb%mu8 +X:è BPN"ۜJ81;.ܥG z f+U˷2T)% :h %Um$Q +G_ ᔀzeճߒ(ϛr3Ug#[$NFD3T"-2D[$m+)R'j sբ6X# K@%Q%ia@(QAwisѥE^DZGC(!kXADNDKd*G #_!\@{ 0-+X'W}d8C݌8Nrv#̞%OMM-i뭔:אȸi5_'Ъcu$T.JԁI_d>#a)`AzP24K  U4^w@\Cn1%/h R 4>Gf$ksق5>J40A$j. cIE9*ʎ~)ECfDPrؠz4%=p8pM(x'ZI )YY(߆}r^uӇ(Lc>J]=;'sTR]L9PFm8 {:=gTlŸiPU)";4M:EFtSZ!qfɰE:V[[H.U뒡I{%lm"-r͑ͤYé}gAǨP~\"  &e@i=A~аy?ʻ>2JK {%CCGj܁Kx(5hۊX M$[~ʴ1j:)-q'B*8?? )?K FQ.h:k0takɭ#31Y\ dLU{\,y Юn4T9]yf.JvB_+ ՞FE0mf`$L,8N\[WޒCi9Hib ^̝5kTbnR0Șv2҂A0OԥrQ[ hӃn}6G4_ܛo$+n -htm" ++>_X*ցh-s_MY+(yd?tu&ߠvz机}*#O.:S%>A&νH9@5]Z\[sTReK>wH)YmXj?jhY2)*X~wBZ+*[d [Oٔ ,5AɦL{thnsx$ BZ Ԍh1e| @ vG'Mqni&}zl̶x+ǽgVhsP. o~rR\N\-\2 SnBVyރIdP{P;US|b{2}^-z|Av6kxɴz + R&C ND1JJwYSW孽yP? gv8ᤫtM h $ubQWn$1mH_UzAC-B TbG3} ͜DI. -$hTĦrvߓrůa䰏d\4G<,$iY.ԧI;/Nb(mP ;*^i~>祜F!@z6|&D!Z02iiHA.uUk^sB`Q&DL\:G縜Պ7Ȫ]O}̷(L%6y4R5zWVB}U$sx_Mhlte2f $ea.Ԕ:Q&dD X.% ~<];d`k9g`]0.aW? ְBI%'U~>n8Z6GhmїTK\+k5rk#^ldC !))E}UXDdRv V#Pt/3ݿ˝9򳆼nn4O=W]H+lRaa[ˀ euhhY042 2j͚pܻf})aʎ.5/HKPɼN-Eo) #ly{o?6ϱ-`vעu?`6`L#{*m KgRH ,HK­g į2*=װ/ 6ڴEP|c ,aRBFe‘3=AEa^EvZw21eҙUrBg{EI)B)ֱ:"ךGLoO@8wu8|Čaxز~ ٸva>cZWXKg.,ےԮ`!,=,El00e٘ޮX q>2h4RڮEp泮o%J[M jpll\MJnFS:4= W B:H^Y^V3)m F%O{>Q*Z  +Ӯc毗oe J$=r8L_8Jclh鰢`g}-'mK{x{Ƈ`OLmʼn%=RUP( +|z!d >݌Nq1.LLJnDO +[Yd5I4Ύ3Εw#ȳ8!>rwbc3z DկP򻺞6Ur0c}`~ A$|>,_>K.q8xǨ@wtL O6Vaoa%L K9VKCyH8 &z¿_J1OD >Dp I=9ER0SrkաN2wM`q*Vz̈́`܂X~fBXQEͰ6?W DhՁzA)ߓFփ٤;P:T1USOm#n d[93&,O&p-.YsFoSIooPXs5p\M䉵9') C$~B2?pĖog[玍~˝+dY'NQHH~ei&YLAF *j!r3XNxpD('""lFu%֜j ++l&dSK.G)J6pQʣ<05c=21t\+]f35(Ue &4жOnO+3qY&Єhgpm:CNdttĀV@ܱcidq s4mA2WڞSa5(w4҂1F4,TY;1dSKe^QpX y5mͭ3' p^$̒)d94@=:)^K}?ZomK2-SZϓ|j Rq 4s*R.귴CZLcB'Ӎ}`2Ho™x֦ހ3qjyLM4u,c:i}(D6\ӸA*`JͤkT ziwF/Y1PUĭiarԸ`B/414؊ ~rZ zfpDb~cL™`fMK,p4pKFGӪy˩;Ȇ褖'i ԝy.,49S)\ >:M5gm+!]T2xeh%m'ꃣ)TpJ&p $ӽL@LM2yMΪ$,OVjr 1QFUO jFUSFƀ}M#"'Q%V@J{:XnÉh^gȲƨ)h2Yk/hs#âwdD+ 恙Gs`Sha7H)ޣaֺ__+Mg{qڢ ] + DS6)anȝk &B즙B?Tcfhi C(~80P5"=܋C3]Ch.poşk~ _t& @M,9v6bSύ GR%3@jarܳk^娖 A3ՎZ^ +ut@i nnKKw.5I^Tq*-jO͜>f-.!#]ł|G w Jbn¨~-d&!S}DI"*I _ +1 :"4Ee`DDO`>7LdnUtH\756 fB kN9pC䨻PFC&(;'Jg Ɓؙv}׌½KaoR $}'-CGBvNϬibyawN%v(ۡ>k<DWtp!HTҏ@~P0g5HHQ>H{ y{qq=ݥ6tX )}v(f#QkBd*@4n]`be!]hwY!MRE~si>4Q=X4ʯu",Ψ_$'>FUj>8i︤ sX[D3g߂r^!eۘmaQ-S>FÇsF:O^!>53npɴjnV0^9rcbi^'Ye(o+*:g"w>١N̯+?aRHMtiUae_ˋ}fbA-Y¢:&c~KxXbm0A#g+=xJhBZ4ykN_* b0?dJ +}:dFtpWe4C0)qS;?5\@ƁlYYC E"ĎدqXz\q (*4щmk +m{_ lkmǁC$ݝ.(P6\\X$@EeYf G[:*SÓ"U[3 +qm%YpkvhMiZLMa|#bqY"K|Β~*杅&u6fr AE7f&4#%~ j6g,h Z܏uw\DV~GuNHerj N)`5G2.n]f%qua%/{#`Zƃ4vNUf<|~lfJ xmAmՎ:OPJ*Q D)k {^]4ws6vL;%ISVt-!@BRшEҥSH%,${@`+M;﬐x{z[ZL]3DnV`v\ҋ&{?XlL+,"YG_N ; qeGh^.X/}BLQp-泠GCZ6:_n\SSUl6}Dr)IPь(ӑV[=K +S, &D}v4j|Sd2SA}k1 +kӼɬyqT*U0' %mшNE ] bnCb٣ժ6T.X|" HhabI9Fh9쯁FJ[lq=Fzc^H{R1J۷D:NEŊckˌ^7A5GX[n͇U[Nr9ٳ)`{GXC>ܟ;hzlQ&#t÷)eVuXSFWR3 P=AB}FpՀ!U,mbCq߸[( Ht  RՐ%L"7ro*uZ u'{3twQcXELrPvUm~AVg*~uO<>՚G?~B l9j8_D=5bqYi^&eUWc_62k)aYBOO# ;Q2:'뇕{D3O0s +i<``?i(.bORM8Yt9:}\Dw4uPŏPi Q5&?S\+Kq>p'rI{y U . ye!Űpl#e=X jHAow)FD+r]}jH)pf ;ȭ| Zx- AhȾb|+J +{!J-.,F<[ڎR$dMxf _dq7?m^ҵQz Q+&@:x&Lʎ,AP%bu$.ܷA(}|~)A_b~_V6Ȋņg7(N_>.Py xQ_-Y +(yAvwJo"QdSy-%ְz]DGFatf$u?ܑ +BP&`L)d`9q ` e +k| f~قݶ{2UJ +bӹW\%HabP$6PUWʒؗdFX o1 V:d= **I!b%`ly6ils[3lFiK us.爹|f`x,4|-=/0 dzj"j2=S%4 X944nMoԄ|L5jbRpFTh45&e[8cCA@ीd"quCKl2:+.3h <K2|Cl]z>w5&K8'm%֔{Cjo#m!mZ"Wz 'DG΢!M;]ڛyF 5x1_f16%]z L"PP'v#?Kt/C̊oZ&' a8's}oL{;=`˾s:-ThiW 3N56 ^RHLNCRU CpeXݴ1RC<dQle([QFLw&dfv0ɑDmD-Ün2ש$lRk +w#xqsny"ڐbP_f>O`% + t lT);Hc^)-bP/1 +<fG3 kǧ%ZBh?érQ#)dgGRPmXѸ`n'H#Q;ͷyJT'1(Xdh"TE Hl\$e%߶z񃗙>5 %`9te׾ӀKď:Jz24VY +E.|`gݽ8P3,{RIrADX`XF?rexƉŽcʝb$qf 4Rb!™܄5o>0™i?Żlft$ȕF-}qJӑmZiF q! +o=v#O_Ժj*-.9W]not){?P0\XO V<ɹLNPi%.~Hϧ$Ec-4QW9S>s8gL&:}jkVEoEd%eRDVK:ti~+58q}Ђ_5+S~n==([sE|TXqUU0haUzŌ%E'h]O1% cUU}y5iGuXioc["ͫӨɶn +X!rΩ#"Rlq(zh~W((i!t|ߨ8OIS AC€I,mLu?`ti/F 𫐹0U}Ǥ $:9|Y>.u$R_4xd| ꃟDŽ،j5.!&58̆}&]Tvh'HChM8&P0ߊYȪݹtnq3PPz6-iF?W Nt˕+"TQ[@eS ,5 V0\[ )eQ KF[LҝRe$8QV #z*+RePglEr1O Q*nT2t]z)Vן*S yFPBމ'l9-" e&4iPwk>V+VvW^mIA 59* &bxh#NCP`kzt}dH!I+L Ig~Ѡb<ŒN]XX' &5?kJɈ*J!AEHևdY_;*6ZTC0m.oCEH4CEPQ^ +Gxk̥i\zɁ'mB֑eH96&ӖRI)-I허'֧0bEJ6T;v]ڡH5T]qFJUMH&\U:D(1[4ULd$pm"|GC"Ԧ5u6F\1G]pX-91fĆ|V/s$suOrbTт%f |bS̡z93r~q$2M1b~Ͽ>lzeoFߡطS(cŁz˪t#OX9#QWdkx|;MGYbĝ(T$i(0R[5@nnHLP @:_k׼ά`06R͟pJ^Ũ$( yu +gj;ċӜeyhZWR!SR4!S"$/(Q~miBٳ-p*F&Ba*>M͉#8@uE^85'?^7aZ6Q}dLC%Yڦ=xXQM-ܣ#:} 8r `1 c_ʩ6A ւhgW9sC0{~#߀Y=>KbaV]'4jIIJĈeZKn6 N(rXvcڬQWeb%7u-u:|8ፘ?/[ Kځ"6<bmjk(:B: uc~/RFJYX6Փ%{oEs!s(30JV gGAg+I^e/ܫ>dA?@[AJMqӂVVjZT*Ou9$^3 5e^ѵ V?ާs kt &{0\f˓CLip17d k3)kuFYba\mT7[MN9xC&fBk'8ٚnղ3T5=%Cz &W|S:wͅ}QDOM$;tݱ +{ ݀3H@c̳ZӓXf$q]̤=w,6UkIcS88 }Fef +%XGq'e5W0&; 2L'+ب|AygZYMKcE$ @=J!8< ihUBs*|9 +N*K!=W0+Ae~jJ dwqj'x~lx@1w\u;{NLV$D0XJ-Xݎׄ3ɼ -EmG(+ahÇ\ 29x)}Z0AtGx)hEr4tWk<6ڰG.RX @s e\Qز-\ ~Xl1(  `\'|aV~˩ 3X7r9۷kA\?T"6(LLˬ}OҶ4,>+iCsīE\ Q2-r YKV(d;'{(ṅ{?z>5=N=otxp'j6vHs=:)sfQP C?P-Ɠ;f']+J]d} +%fA,;Z{_V*{a~@vĒa7U|b)@{dW?8 b)r}K# +_,kC.oI)iߨZ8$Xuˈg @!r85Pa͕[%sUE"'=l[gWmO?ʝ b2sH =I좱cs+&(av-_8a9ў/8g 6!JZ ^̥eHdr9bɂM3jK!fҾ0?|w!@/{εYܣ>3"ޅdц +ꞥ :VhAٵT(,NG@r1yIoa9+ G\wOINo+e'ne#Z:]|$W^sh r 2dkަ|5Ha" zC7Z pXnV <6PrW +BDΕ=ԙu:E ^a} +D) <)z%S{hFHY@Q΅7͝^7a77y-ĭOszj-*D2׳]Im.8α"bC{I#@fHDLy#LMc,Z/ȩ S ‚,0hJ.oLpY7p37G]_i(dlL*SS-/LJ8Ic3`@ @]   CcynNp5N,4C+G[efyxA΃./ _ Y ++x ]ekCZ*'Q]_]d1;'e b'2A/ >}*A |NaLʮ!Q >HP<# gcXTd/ 0.h$5OĢ UBF:"lau&$T :ojڊpaTҕ@BQJACVqp +3ua%oYLH {B(H'gRCъ+{Y;f+5gOE12R9ѹϘL];MDicϒ@"m# j 0r. ; +WʎRy'+Y# 8AV]~Au[ZiCGus=Tx8rnkѧО7(5JL-VF_=SN/ `-:Q4ؾn{Pe48c<r{ hBlryK#by͇AV @&I8y*ܐR1 q@ɳs )Ue + N` 8[)Jӂ"Vm8~ ƌsl^_Xۺoei/ TvʉL iBXynHN&7hA@kZ/ P,%'P:~չ闊gH jm"]S 2sMak|ZFr02lbNI3y: +F(αjdW \c=i04QȎECcWɨ +jc\+‚펽Lt+}PeKA(&6U4qJ^.R/wF nEʿطsL/ޭBp:oƭ)`6q3(HfiJ#$}f]R^7/jL{ vQ2+řjr^4Ħ$2edmP}uçѺm2ϕځ<i$! cMԴ7b9XĒR9!TaXjnhY>%M,|cxǾR/ 3?ZwA]r #H]}ΏٜG7Gcxr6.;@eoD~]uTJ7ߤ1 _($qΧ'kCܑ6[عTiXNv3vC.;ICv#Cj!5vP%8Ʉ0=z%p^@;%lyHR}x宝!w^&E@]sT +luQ8U*k5V DW.HD,/т˓5\|4&Gä>齀9*SRO x6Ba<ʾ ί=e2eJ ;h^@V!z`NÅ݌ej,P1!fpnBee8 +3$ˬ%vDBi$fKbGkY)1GUTģ ZG.U(F#&6 >DXBWPū39.j2XiE9GaMwLX0t@svԳDa467=<߃*Ejxc!&6kj{@=CL`9 l4zp'xpڃͪj#gY`4 @hij9 Sn.4=5JNJ.-Ӡ < +e!Sh͉t +4"91 +ObF&;2>M(;BX$F2`dHF]~ +{C-n'Φ@t~Gtw9w1\TUQM;l ȱK=lF  .ZQe`*ʵz?Bb؁ $bxqA~7lctb12aK1^QE49iꀦM޶ EdqD0>Ђswm4Sy\?`<()KP7 R> N= 1}YͪRY38I ,f`@G2ZwJv#a7'|8*:L#B}4"B#`K#21kBn' 0v@ٓ`(L2Q ׍F郆Ep9K{.߇[{G2ZZ"A[[˩˙ƾ3l2X,\9L'ad$Ju/ xF\Fp [ۚ][!L.+x%e)D22u,Sa3i(#v4y2*mEL^9v.S2`[!$'6'*kůPzxk47TK}Z @C!eCCZR>ذ>χ4iQF'hh騎VjiiŚtv|nN;봜8n!K?NASiCMUooK{j>s]R(IlRc$RvdrhPvd2  KBw#q@#ۉ(LӞ;ݖjxw-At;m㭊[Zi XÃ(T(UP֮3Z7 jK9}-UAU.\9/nYK5BB˕bP jg)INfxåWNLqc7cBE3X!qζr\^t >O߽‚~=2'S]=5sSKI.F~7|R;d JNE050=,-ZMj47"h55_y D\okT+Hjb2En}*M1x6\]qK`o 9W +#{Vn$5wt) 1Od^v_PP +5ec +[qBف l@ ݭrv# )䴕]~ׯlqJ\M]1syeī}Ċk_f0i! +?diwjŭt H j[@/>Kf\q{?2L#.mS4rU!s2 `]IJ<2jܹHm%rEm䶄=]5Ѷ 5 ὀ g9+}خY]S48ʝԞtYcn* +?̢@'#'e457z4< %n)s΅^  + +-Egv[i+%LjcxXkEPdt@{``;ſb{%S_6jpY2hRc'UL/aJ%濵Yl_O-e{.9g)Zj[&zYh#HOCUH1qY$iômr 0AD[H LY+}v/ad<1wd8ҷ,+[XI8%4΁?_+!݄EzT^s36l_?Yfi/T. %c~`g6؉Moݒ9j">Ww7!8X;$X9ۘ}܀3)YV-Ipnmg1>zŊڙId^@ ?re7'_h~go +%{Br?X YpVUG Z+Jj)ƼE=G.c:~5[„UE_:NT7_Us Z+9Rw%+9ZZejF8!>,Nݎ3<搛Rr2];qRͮyC݇m#f06cwaVa T \[@N`Գ3 ɴbCj.Lx4{.6YEM4J)zhݹ%P3DǣՉjnсW )M2QsԖyf2vR WO!5KzxwHB9v6e +瘈9VD8ccHBdy9Ũ#6:ޘ/*4U3QȋRM'/+ubf>r/ ؘX"ʐl&WsLjoy I$c3< )S&Bj{HB38ƠQ%Tڜ͐H<Ã6=N0vuD>5XZ>F/4u#Z KZnj.m&- Kq@jQs8FJ-i7n/ @ ΑB_7DE b2@(TҾ2QwΨA ^y3kO֠k@sӆ&%k eӯڲgxF8"cwiQy={> ¥IkEb#HۭJԖ?vq7WcRxUrqHZkǮєo;S! +hLU(3Duɮa5dC+i6We"KRDc…ZT ,ƈm41|oaAh.ug2ϑ\YJ)۾e㉹Uہ􂇑Qbp5 vk"3Z u]E N{$5Ϻ`e0fULԍLB'k*\YVLl5"I,f[ &r`zWr.QXhC *we⦓~2LH~42 9NEl+% $fA3A}}Bv*xx0S\FF "anQ^@> Nc1H@r]ڍݮ'_ƃ܃3 k7FjܴO<r+#MBӴh4Qg8>qT~%!p;\aB)@nL\[,͉tנy4X¨<~׾h$5ɉlfrzFSaڅ`^z|eԮl;RH ܏VܹSPo`a>IK ^Z*Hp愳P&R;}W*iA@}oCeB^^f-)j[j)ޑ5dsJ|/kޫZŜBZ}! +EWnv˺~"c7o^|mB&2=Y+_wAѧIy}M +ӃR!gJp6 (DCmM0}-TL6,@_4{"|=EpM,Rr"Od+K)=[kq'~z `0DԎ4u H!qLjl||CZ)EwZ$ݠHmd_<=Vn|JѴNڎhU2lgQò}=4X$hpB?gbko +?t|uKBt!JۜhA938^@n:k _+ڷ]b)3*ln|Hqv@ka;*ܡEj*D]}9ゥ1lF#S + Ul7\**Y#f/ANɘ ΑQ&ġGKܒYec6R_PT*fGMavn$~v{=(?y w856d0~݃z9 b:BQ"7j1_10s +Xܕni48SV2Z;`DFb!Jr\|!yS/o\;!{଱ V:HQ"7 zeN4i$!_Y<_1%k;J(G-h^}PΖvxXN:Lt*ڹ֋%e_lD7xdk73-"f48gʡsi>ڱVSoN4i3qbK{Nloi/ga2kgU/=ո⯩PrzeRV;E&g+EF/$ӧ O 丅A o-%)=﫨A]RHDݥ}.1A1+B>3/-[;6Ks@*W[PI%RN+25^HŘK,$#;˚`7&%9oW l-MŊWh%!-оmsDp縠~G99Ԟ&i9jR{P{=n$d&FO* gs4'*~%VN[ƙ\!S׆ ׯ r>MPn/  >;{]m+=)L\[+0]ؾnvq_ie`N+4D(.)o_ Ζ iD@$i)*!#=2@3) +#St?cWbde~`˱`eXqj;)BWAv ܬ!P3y + 9ʘT%`iTB&ȁ1pDjC Aln )Sedb.f`.Dt&D!r^gtFv-+W*=Vy ;OK%r>);s+"DG, r(׻ CpeٵavU εHqGAHđב L)!j\^@p:`rkPԶ)*B%vS|ץ.lkȄY$|?hŇŋ8e,L+6\3ޝa-R{cza %)/b=E{Pscw^"#FRPx{>7~Ѧ F"gڂxAH8+ynV#@nnv@[LnlM+Xtr|QnD/'ׯ%J)Ec3-)v+$|ub RJsQ @dXi&Љ"*OhSklCI +KLDb!G;=m$*AQ(Jdʟ6[n4.+IW-ń iD\9좺^O4U*o\VzATbvѺ "?\_i{(y k7ECx|/ Ȅh(6U +P.e ֎2xK}"ڕg%̗I_/OL_AУ~N',G *d"%CڨvN-䛆?sye>/]<_wV&R\wLWkDMSȢ6<+9JR(=Ӑ.>;>vTvEL[!;SO/&@xlD\+~< LOmXە< 0B&.)Ux֘C0o yz}}4 vdv{`Q*6b]CRV̥y}hoFt" lK{ҙhp03W*9_um9J݊ⴛp(3[{(VS3/l`{& ?E(tL~<$ۻ%,ҹtg1ǁdGv Ɋy lr7D+[kJV@$޻MuZYJT.1tnI'*@rΦ}fHr|_r(Syʈ|5HT fZF-Y"/nIDF0!+cV*_5MQ14c]i`P ](A|]|3:ƈ*;zM'J)} +@PBZ j{Fwns`/|k#*B'4Hx6~5ю hfƌHT:u.h]bY2Ʈ6//DVƶ2 ].!x57\ IjRww;038]d⒊(b" +M,(vwj/LnU rDn8s9҉tIeiUaiv0s5Z >HZdpHJ8ƮDN$,CĆDM/nc>c.(׏XJ)I(`&+9),O]3с"̶Jh4@miD-e=,P,i4w#@֠Ѻ}JPP9,=®#IA]G Yw/_&|%dDe66Y0Oh6Qev`EbDXR\$ yY WJ% +@1O4Jg)cE +%X)"8bK>{os +6uO1д_8v.:gM#LE6 ^pUiQ6 |NڙOiA"' cCa qĢQb<rTJK'JA0m#,xt^TU]rTpB-S.ZGI CTuV-E57,/;9"̩otgL5LhO Aba/E3:S/pDV>ZKx{ +4 +CJEbPuL׏PFCLQP{7YF:*skSK,E!iuM@Q_%ڴϽL:; +ЎGEv8Bmg EH2sYHX2bU6t,}9p2s}ƞ&Ikc5h|}N,FÊ8VL7[5C:r]|!@f98Mr%8B g}ftH=? !Z#A6,I^Zyaa-A87 K >,ĭ Zڜ#q4< V &R)*) 6e+ +b[tV + l+ + +*kBg5 +4*޸c VCF'jh՘oVӕIc=߳[z =0&2tw(<&z4Bz F[k0cRI?&Ƨ@DР7`V#ݎw>S`*[QAt +e:6>6yK6:?/꣛4B6V3*onEVMWy BAݹɎu7 6ph6ރ!Y;0~A)P#X +v0 TNkT>: \^}1νZ݇(zBc#V+cW9T͊g_ ؓaYǦ4&ج}*`LdթJ#ΘE{]n%|p?2@(psH94E9 8RmQ u*=sfSŐ^%r$ F0:|4H4pd~(6Xj647Bdq)"#b Aes}x&hBj-`ʥ#Dbv<о${ ~?{/g? `3,+SF=q&)EN8 +ǀ{ +=S 8a@8IѸDԎOt;S`*ZIdC @dwpP:} he_ĺlp^CkݻuwdY l8_$X?v'įDoRFm ,<<2&0]eV` :qbRLQ&NWbo=DpBQg{B҇Y?,HDv!0gDt?k=дCP-  `E@S?Wcƌi^WÛ\ |{` ?W"vQ%o@㏆mg"BX[gDmY2vvY0sgE@ .Ɏ-Mb*>/g턌N; MbKȘ VY ف VHtuzkW@ЁD@+j3^)-Ĵ%ҀhOhAt^Ԝߗq' _3.X,x*7XD6u.$ ʀD;X&Y}z,MZ;C< .%0 [Sue&:1*7 "%Ѭ"b| +Ԓ[?B[ +mS`YAep`h@xVEag?ZJ%hÝ/7ې$i,E8O%Bu*׊H{'A6}(JkREuVHB +afԁX)d忚" +Xwfm +54Ӥ- vO0@ NĢwBDBե_ 2[I20pP r3 @N-&y!>YJB_Qi[-hM m/S1>ʒϱ$R6beϥA1Jy<ȴtYSELyE.dDDX#1}ٍƊ(jP 5d̼.m9RmG,JyFBro$bN秣4s(ʍ<=Y`8X2Qiç$q? ?nP 7  1㶫z1AXTyXL2t$H`-)5$C3PH/U6NDUe? 9O182@ĿF` 盦 fgLs= pvOVXw7:1Н1i-?F h>_1|~$qc5^5*M9}>("L?_ q)u| V-\i>*MD7%?8꧕!&| ۧMHOϏi+ƁϧP&TGNX o|f|ySi +b^KqiYßwlg߇}A/T?fX{!Vu,{ aKƜW]I ,󞷼2S?K:h#(44k:Ӱyyj%__]nso"ECQH&hFB zRF ISmP~=XU't ؛{p#r}F#*i파MqdW/ +a4vڦ 2##:M<~?:#t yFL?Ƥ`b};CƊM-:kdcѴFҴR:xN_.;}PMeo2ˢ*4T5AQӾ p uKcC1>AYx +GꃴxMTxXjSY,f(=p<Hh.^Qf:Ax:OѶ#&1x5b+joT7|ke!cҌXO1F>7.5CvOt.y +@HX%Mhߞ&Lӟ&rS"։Hk2@J&HO ߭q _`\d qT31 (MZpˑ@cl02&mnA SցbD6/ER#m5"V}ixVp69yCz_JZ +e#`QK@u0@%k|6ʚ -$wq6K+"GSbQz=+0S &,f6$@@|1 +@[$!ѭB2!B%+LOzАQ?Tpr=m>;:a\ 00qq7xl\B=`0K hƇK0x8k9yZ"Q^\Y9# sDV[5f 0}ش#?IfȬp#r9,W$`YM=2#L!*me@p۳A?30RqD_Th#)i :pa#Qcf)\Z H]toSB?"lF^OS,M<@=GQAGL( 6o4}a.}S"a>kҧH@.A@M+Gfrt*)E^WKbr`!х  iÁ +:tYmq]:d;4ͫSwr)]*{}:dS?l&O'N{c\H=],J,9ݹDZ\߳O^RU ^Y'"4'f ~SPdE>) +]E֠#%ָNPw6} +hp;9j(?uMF_ +̒-ljJd") +Y1?hVGƁ&hP&i4 &RXdJ0cr )k@&WɆ8R*HpVIYGj@~\QNGLQ䘈g(^/iX_fq="•Hq!s#[NY޳y:g Wg^*dE_i+qq"1ѕPT*fkPv ZSP>iV Fϙk#s7II㔰.oeC7~7dOᐚ*ˈ!.xyЯ4ɇ30*:X*BGfC()-(;֖r١G8SeJ&Flń)^cE^ȂhǠ6|/Bԁ\4-O]zu~yĻ_H͛[8I0WG+ .w }$ wTY.7L-ǡ8zݾs:o>ۜZWO)Gѿ[^K{ۇ/If)w4x>&[N4EA#c1}fuTٻ!Z^RG0CO{XE=mpd˛'K9|G(J(dz?''Dõ:BOԀwZg $+sV~=̪(rVT +~bEb^ +Nƒ55hyȱ& + C]Ks _?H2KfZ46bLب>Dg-&!^fͦ'%3- Ҕ:BlZ Wf{+G6 :H=85B,jF\ȥtm+yX+S½u.ڿWL}Ay n˄gh`ME?Wx! +u_Y=7*2򚺹Ⳳ[iH<MC&jN# +xᐝn:HzM*n0_<%#Q:Ah7폤:F8RjnFG;(b8PBn +c套>q +کIB6x YP +v4q.XR&hq%\(U-UjG0罾!ˌtsoq84{cFB>oUq7I&ɡ$JVԆk$3Ua+ekz+d`|Y>p6J}J2ui}"e'mfar9n+KKIEt5)<<\tM6ߎ§g LU8qJB'M +|=+NfJ5ÐYI45识GP@{p}Zsӏ\g?i12}T)+%I:aMDN۽ⅈjeM2<-oSԖn0V6Hοa{3 !A|eX۸~j֌nB/)`8:ɮPf%Un~;oZ±Rv?qdyhpW0enG* 4" 3pTfidroG'Dbm꺣}!w!&0BOHU]6l-Nr? ińjcY%V +ʮ>߅^DKs +E%a;UB8¼*y|0ۂܥo>u1t?,? 툼NK/G6>@w!#J %\{>IdeiXȭ?364C8J9= #>TVpX*X jUR!p7ڑnD.A;t`~W+fqY + +]2ì۬>YX,SMmb;HYa,jRU$"M-*s`3/ݒ4䶊^V B>IƐV׭՚("$pJ0Ϟ!6(/tA\nH<%SW=ZM炶!YcJIe@ٙ!3o7uq +MSIlI Wt^z 2iEcɉu88g7+%a.E1/}zȉ} a/^tv2^f`MM'zF@eߐd? c'0_Us؋B验V. mu͓ +PI6Kw@VU%G W4rIuʄ[j&ֹU{Ma]]83]1hXM; +ϴI\4CwKQH^Ͽp$Cuk@ȳ'7_CiJB`J9yI=EfT;Ʉ8o̒J\q~×TGUj&@ߊjoYR]4 GʱdOT-}&t^&h:lGfYj0"/T݉?_?&s!X +p=~@T@Vxk*Qb[;ifm Wɳz@41I{ +s[c`N<  uA3E)x3*}G|T6Fh6ZPlR,qX 0E`"lHSxy͞+/mX>rT7G:" =ǝԌQ3|4gG6Lv*G2md\o +pyrh/ 1b9JiO!~yNTMhj SMy}]KU#ƤԴQSՋ&%p=D`}6gW>hRqF0Dk|#T**Ieu&edB Jn%[ il-I- iԩ=J˦ +BzAPt3PAdj#zbu|az2CR9@T򭻖"ScZ$Q&Ek +|=}`>t-~lV䙁cGɧeCp,*"s⭺`:PaJ3Q& ڥӡyb48eT mԌ`8o-k+v *"@;CKS֤zTyă?*;>iH {:JSr~-.{G +TfqI2ד l= h^<KG/ !Loq5G5pD}>1Z9.IMB`8g --"YrNU 4ywՅkN]bC" +P6:seLm2|0k=Zd@3O3nŧvsk_ ^r{$:Ϫ*u# %g炟r3Yc|-¯¢ALF)Z!HB[mdVlXmfpMWTԌDn1 ++\@fN()e +].Y7}x1g0JŦˍOA"=yE2( xV")$H5عwX\ɤaF pkE9>;SatWj@Xby$?c>pG]z&ўքs< :$$$o, +X1َrGΧMJ.;beTQIbO:"ukR8߀_ Ԥpwr"'#Y1Qi7Ɩv^\h?NLKT;3tBsqm#K~>?p&G\#ewΕa>䏂0~\[g!^h}mp/HG{Y虫b+X% )_ +X`p>0ςp>FM_cÎ U+]X'ΚQDʰį⤷]۳%o)Xŀ}vI܉4>x+ζ:v2FM%T,ŸawJ姿<0< l{h%˴b'ζ̱E>-nn;f~C$m.=l0^97?2/kv) ='ˤ&Z0 JOiv\4l"7V5v/L +t/P/!G8 +'ۏɁX\c>!pp +t3llV;.7GhcnAn)3lkw$PMt\niy;FJ/7yU-X|4 ՗3md_cd$[ѫշ>Z_:>Տ wUM)ӎa}ZC ` 7%5xXkÈ# >-g=r ,~XM@9q@ZZ z!V}(/囧12I(otR_nQi,Zm.J?˪K@=',a*\+'`!|ÔɎ?ؘuT7?%Yi+}ɾ[>R6}P^jzhN!>9!}o|b=ify ITk5C>_vN(oQ[+y [eəXM9o`w&l #-I2r#JBhL*Gsm[r*X?8L7(d;ZR?"UE^6.ʈG%¯xxUt`౺h{W˱ݽ·$wȗfDj0C(o>لN &>sL4drlϰ ]V@0I೅j: ٗ1i7/KpL' N?|PFDrޚ˘VsF'! NbY#k J8B"]`xre\>Xsyu*Ktwh4 K$D4ș>5L/D_es^Ʈ:IE0\%G2KZhahP2 gG0ǥO +ҏ=#1ff/+2}& ;x&\Ȗ1rݨ3" -`E^g]?Hj*a @,rA#B搏DBSL~;T$ujodR|yhrbrJDwkNs,]@i@P|Q2J5RC Ph`L`:i +"wĄ&1&%8ۼj0 & JEM4^˓ !- +qnPt:%N:as g@&P#Yt@i ..;/#E.8Mh*Q++t2:B |rD&5qR;N" njHuq|`z}4&v6nP<g# C@`{5l:6!)%8f*uu[xZT ouB_?%~^Rg0s N\NX+EqᒇN_006fZ]Nq CqZ )ѐXP zWH6+[ =6b`| ˘±v&`M [`W=I|Ij6*5ph_] `]s%D#MrTӑjRfEg)Lڤzubhӱ$$XRX'kR"ۅ@-y.M>D&Et2E4 +,_zh jQPrm^u!HHTe4Ⱍı649 +pat*Tet]1YεB' h@{L5h*e@Z 讱,MiPhpSqHF8IV IS2PmIh6QǕ2P8hp iF]RUy)ी +">!Ih&U|0|zYd2A<8՜ܐіz:͠%akudTSQ` \ X<]@ >[Qo.J@Q_x + BBrW4Cn- i:գ RY$1v(q:yVI LLZD|p +n& CӤ6[h{MGGAWVm5jJ˨``!Iq3$6=F5\"10CDlaB e L_0JQL$74DCdQ|Eź >1-Mt:=}J&!6V5(;Pm SzBY~$i :&M~I:܇`ݕdV̊@,ei"ۅK.NKIDouY9wI5%n^%La:Ӑ8QI˄O.>۩D[8"RQ`0!~a(m38m ȑQ͋l %$JF$VΉb7cE@cOib,aECаq*SéqmCu);PmJkpf6L@DL8 '!0H +a8GRxYBR\i  rc?N:"AcuRJqT'ũ*Z[+gnZuR8)F7q-Xh)T6&EbCx@&Cjm +ȨcҴ'լkЕQ%&)ՑF,1 +ʳȤI$UV@9DoıXd˴(nm2iP\ qJ2X#yΈ,߰ȨN\ƝA@p>x3^d-`E"%Q`-q2\n768QGL&,2J,,X&4 LW s$Ņ*$P`ytɎLe4%* +H6C(iBjc+Ȃf[JY ,EK`+j˪ty,\4$Ox0Pm/ VP2(!v"q[ +TMdr 4ȶe8yM"|<"Qܫ4M +zv3Nհ9hZDBaaZEjl ,~ 1 S |iR<  +*Hh¸q&O`a8 3 <ׄ@Bh: oU 48ԤS]y@& vh -0Z:ms]4AV.Cw䢅 RH5- _QD8Pa85b`ddչ_Y(@!'AR4@g&jʁ#fB$+QOgCЩFY`'"]a9mye61$NZ U3X>tZtq;`F!ԊڡCGeQtfLNiz9e' hzCH%Aiq#S! "cQᗭ^&*LuTH g <: {2dr]$Vۥ#Q@i /M82]T\%H$dX`i @#fZa3P:9GQ ɉH{ ,CC,aD_b F(m5 U μNf"l:`~bWaD6t,i.)0᠄ ^8pRSl \[Es#4IsX7φg'`$hpR})r#ptC>n~Dy; DVF1hp/90?;pDGCCi26@qw@J`t61fVps=qx-Dq!ƒqCT4`w hWQnLh5+/W +JqfhEy21i _ N>YPj q i 滍4 FdNi \4&kY3YM_4l)b,pK{XCȤe +\ [R1< +NQ8pMBEcRmDl&Mhu^bÀ0$ ¤@#|eRA%sb!3 q.&@7 +C&-00;0s%. 9@_ˋd%&`d6T[0 r/48 +LٸGcK|\T +'Ѱ(*pt&H"a3N(+~`h& Mg.Y䆁s^!\*d* }%T鸮339y4lqnxtk`dJ( Z@Keŵ8Az7qov[}-jG*ai"!Sza?Q>}@\7=\r> ` ߟ7JvY2i߸qmg紸7nRbofϿgxN6ܹ^lmZ\ߚm='RbG7[tu{׌umӬf9[foG2gIql* +5[k7bZq6m۷?mh=gvx>қKeX#:zmzϪ8m'3,vKNwӶ^_pqwWڴKsN/4)۟qNfsNlk~s;?=qstL?7RK'؎VN":>zf_iyRYo|oNimv;SKQ=9NwnI8Q;|@@Ҿ~->~kO|uzgnIkq?>v}՜eϚNyi+x+q<؂q^SXy0 (BC*c;Z羝wv҉ߌ~͝8f) ~1-ʀ羞S3߬<9o[boMKfkW^\f{ߌVY+: u4ЀEc9iIi;`DcڴD 2d9W`Md6 +0!E6QN[z1iOVNLkLmޯ?g47g1I[<]sk8}J;җ[ m~{XzOo2{ҫߴgaztlRvz*fz}4OV1SIayo{8^,yҊ^Yx6ŝQNsz6?u|=bߠwuV]3Dnkiƴ4K0_*t+pߚYkϮߙvg})H{3UTK֚-Yuzs3vyfnvV*N٠uPfQJe`6tv~w^fcŵkF? t>3[i'ȹ~c۷fW<<=Oli?1Aj4wZNg)Xq?4e]@m?k'vK]/k7y;/mZg{3ϟˀKuٽnWc2dN%ޏRvGYt2 oKףg`mkiSY%H'"SVj;y,?)?exGn̰;[z)tۍw=Yk NJ24K;J&ŁPH + BA!1eZÎ`Dʵ֗ ܋ȇ d2#pF+gUΨAϼ?\/ܦkyv׊%iX)ƨ9.>+tdŁ_kh/7(Zq#M{D®6!X&V Q .m̯;ɗ-%h4p1`XL)Ͷ>rV"+5<C`JSRw@Ҩi=mH)>"`` +?:CYw9J\v}2%4>OcR\%JD\g[QD {\Ukmja(-}]hVy2m, e$-S/tJsd_^=[+Rˈ ,V9 [ӌ'+>~o:Si 2^)l蛋c5s${ۚ%Im$DiB/ECz`i14l7\$U A[~XgJIW#Dǖ (eG肕IO&"Jjf)H;{]"@"sҍ,I6mmK:dJcfc hx0Q0R?$Tp(c:.Mi̟سpU*wἉ _$` nbܺt pC{3y1!lɪ~1ecuʚLD+Hδ*'I:ѸP-4؞a 1Z +]%%Ac,ɴF +:A8tPZJUW6-A5(bo* 0Yd m9t$Q$RdIL`/Vb̧lC(HrQOP^:$KD1*ᡲkڶxKm#k{8^1՜|7}N]"jXu:"|oaT^,hY˖-G*J4L}jr*,GZ4;;x4$!rؾHFߔs?3gn])@Q"38ƭ\jnDsbBV厨:Ä37PfQot%zE%f$`R 0HJ~cx3 }N/މ~l%7ƒ}\{A.M:]N܄k9Pɛ$fM26@Q#WOM$_79z(setAHGXrH0+ƽx%\[ rWj*u%s ̈́"$09n\N6i +.U8[dC$;iV.&9*9Y7b0x^ m')=X|w|V!ѣn -BE!lzf۝雷'P)R5VaIFm3Y1keB=)cC2\M3;,,fV!<bKڏ`Db01cW@Z1YcOdҁ^S-yr@+ٱv3Oj<9LIggkYݙ`}s-+/0f:aeTZ$w:W걩f7IPf\aq4&/BT)K`,.>A䒫pAb&rڥs˘Ѭm?sezornwҘBO*l=GQH5'cմvU]]$'+bt˯(uqOww߈`fyқKP]cZcQ^ADpO >pςo'W2$nS՟]hvȭu=bё?hg@W?|Jd@Ӏ{U'!IUZ<p- o/nA)4QO^D zosmhUNd0qh. E~_f#RDTMX#v EVL =$gh ` +L4$Pc#$у +)w mfRJri=U#k{<Ɨ;ˬ45 +9z=I wZSu׍!PT*Kj۳z9/jBPV]P*E)#2Nܟ# +f&UwڮF Pj|sYN$Uw[/cnwmhs̷B8\ZI.wWFp,+'/nޜj lx_xs{Jx^mT9 +۬75ZmTp?̾_.DmDO m20G[}Ϭ4Ow} ׿"mDeXA(.M]p>ޒf1kA +4ՇlM%qv+J1v8=N܁IFCdO!%4>gVC֧J;:Tݲb~퓏$|\%C6ܗOĒlmP27XΗC15չ> VQipWcx?m{ߺW8+Y)g[D(Hʈ +rQPfEóŒxp"ZKiVQY4sΓ$ =92g&ЕiAQ#+D>--W˸c.aY"$%VzJZϾN ԰ q:ިYNM A7Kלye#c&G1A ıQ +T th>6U56cPЗ*HeB3հJC6/)Xc5T+ ZIBIOQ\!4sw" 48"R|eieGPm١GZQ#t޼7LQv`_2qZ)7~ŐIj8kJdJV]*>YO^,jQ")(6 BLqѱxc,6Ș0&]ަuz0+ +س^9}n*WSّjs=DL:.P?*HH$1AZIs$NLM"2o\ؖ zV[!\>`Bn2(C-K.S޹'@eoX.(!r"zUWmg:J(K cL- +EKf@[>X/sm=cr_%^S!.JQ>F%y[񓦧 ]4 j^yxP A^y;} E&f>WQ_vb.;qE(}hLT`r|PE{ᴴP(l- ؃WX2JU7^Ix~R6HK"h=f Dk_(+,ly}Qc0V& 4,"@ΉTqBMUǾ1XUGn`A6T#%GN9:!<&8t6P-\IDsLi`8eys X,v/f䋴e7ʬ[>cƃ gGŗf-f"U ONkmnGF6@4+,i4?^pE +BW[uGbp>Ҹ5>*Oy9g>9mtHn[D:FǽD.vCGFR;lj8Pڋ*kU\L–{ p[FBDlU}@IYw4t.H әN Qhj6H>dWƩx,փ412yE b7\-K_:0߅EBfe~*PZ6~'EY^!Ⱥ'azr82 Q^pZ=SL[bYx[+ao^Q5`E;掿H!I(uF;YoJtl>_BgHzS;)Ж(mѫ;–rvҹ =H[#_p`+3u' .v N瞕lh7vjvy&K#*^R{-G(`uCC:w4k8Gi`&9}d6.lvh}k᯹ܴvy:nxKX(w@Of]]TSUcU!rl@%UմCV#D ~pˠ'Urtr7$zA:ǔE~V}+tAxi4^+9+qĿw6qjs# >$e0Šzk>ݝaAf 0U.EyV88ć1HQy{]]?*5 ofH\S83< :i$4ޥ)ڃP'zxhjܙ/nt(3$_<@7h(59Xu(J(8W>L=*(SRP (TBR@bA_B|eiAa` VZ ! q6ΧR"|Cntk#d؃D4`o&Li:g2^r}$ =:Ii8]/B,:+뵎tA^M(~X"8QOJ^Φ 8NMKijloE$Py>Po&Q; e PqL!/$,[& JB/$?ĢO tڀƜtV+7&V_Ky~Jw(|`[8Udbf(')HJ2lrvB@]w^P&V2PJ& +& +zͼ@MJ[[ >[$#K \v{ֺZ|m,t?}Wqɠ$ |җ$/oƼW`)MlS؉zbLMݭivjcXT{Cҁinq:tۤtR WUa/!ws٥0b 5Ad8duwey]* *.2_tIՐyGX69%uuTnfk;oW2 Y$BwbÜj+X@0Kۢ +Lhtc5RyӺ*h+VZ}vUt#'&,+fW&gP9MKт.bWjuZW3fJ֚Um۲4\2/ o8 i2ڀ6/n N?VǭơY7TU{5g۶+ƀ͛;,KX{5r*V1]TuhuR5D,~U>^*h'"kCWRꎉ%T4H<JggnwWGlwͰ8wMV.7xG^UU׌O C{TW\BhTx=A +endstream endobj 14 0 obj <>stream +w{yC3)#_l!ȘJy#Dm|yݺgo;5|rsgXYl8鎤5OճVWLKhV +oHaD2sN==f &D䒊 kKy!d`!ȴMhL<ƙe?^1()$G:5Y-m3i'& "ؐ#=(&O +i} {5ױqoWz%yCҭa[I䪨$cY0LwJp.ޓpAu2I6 V_6 ɆܨyY̮)xpO*w˘*mm˷WVY-D逭ާVsB%i&aڵu6ļTʼl }UrmgV|vg +0 0``A@4  -se[|jTS,)W'=<}zU$~ Z)8zAO?\ylHfd24@!Lӎm=ngj4M9q%r]BKh@8oĶyX&۩VEG%W FSZYR2E{˽̌ޱqc8 쟞G Hb)7o %ɬ޶U%[ӻϠh.J^l3,.UJY@Jߡқ|Hָ$&UڊS2u x/o7WuvɭHd$ٌC6]1ivipl4*Q!P@蹈( +ѓZr& "k,֡uJXLCA$8ыե남L=(1_:DR3޾t +5 LT E}+D + cb`{w󹞐Ziɬr%nJ|#ħMbd¶!Y#YU +bL Mqk?1MZ(<#MF6Q1WV^Y*M~L23_ք`KN?n65v H$vdJ%iIm` ;2APXXa+|5 +yH'¢ſ^P٩uJRe}pS S>Bٖ'h>*c/0U 9+8ݷjH =:1D|0VZ on (RFH>tHGQA_ Ij0RLf/X< X"qcdLDLM$W5yw#IToU_\a =!FR9Ҁ0ԍt6W9M+P>ǚ*E*Dtٙ&2?s[Jp+}.oEzSO@GԐu,3 }D_??;Ƌqlʰ'' +˜fdIlQ<Z JiQ \ME-%D$fCo ?hhhcȔn W+u'_K^T9{ fAmCE-lneCP i}4êVѭӫ~l(/)(3x2$RSްK_~ +k[]$r={-*|A7\]T+9FI$V݅H(L<߱̔Eөl6HyHEN(UEKE ©3#)vTm^:uYo^$/zE;NS k)Uo J]_cIB:]DxeyZH1OX +]\>9IY&ҬCKV;D Z$^E|C5W:鋩WMGFE[1NWL(ST'R xF125a ؄;MqQK{Q?XM=//`ەPxvdG4CLj RV  gyRb$td$/-E +HK,J%Ĉ`՘an3Nc^*~q#RtgwH;p5&BϚf \wBʧEǀxH85pcMQ*fl+,prj $lKb!нQHB2㌚4GK_bތ%,2hGTֳkh< %Ʊ$`{9P%"=sdZ6Jq7a՞A(@^UUS,U"Ms^̓P-KK*u&c%hM0h /y3|W9Ev7?F%NԻQAE !͜\l7%DjQrQZT2UJd썟hf 3ߓZ1tsFHg\*V;Evh$| 8":&V~j~T +;ih߈]D oҒQ}{kxD5;_z'Y `6g8LɁB֢<%(##ߘcX q<]sDm*{+$Drog'J"0[B* a0=졤9O3hqCHO:J x,yZyk|9} |6π +endstream endobj 23 0 obj <> endobj 32 0 obj [/View/Design] endobj 33 0 obj <>>> endobj 29 0 obj <> endobj 30 0 obj <> endobj 35 0 obj <> endobj 36 0 obj <>stream +H|wXWϙ a# =QIhT j]W"1]j!$h":0L9{!&"g&5ѱ}]=ׅGFDiq(7֯# +%R#.->>DyDngљs1˞بgGK+ :w111} 0 G8>8GHDa2X! H^¹r$a +HE1 tLl\|,@P^A&9X\XeXXU:?]+F>F>/Xu>w:tȰe6,4,2,6r K ˌ-"V6vŸӸXlm,F`6E؊m/;`NB1vb>^~SdE&:0BdRp_+qp_ Σ +{\u@%~p 3*(xGx'SjQN(}{ܜ{{ +,Qⶶd(U {M" @Bw׫O.>D0 Gq Ǒl@rq2:Sc8! +Ox`mM)6զ-6nvw73TΏ6l3fofYlfYnVfYm֘fYo6flbL,w5q&$DdMI5ifI7[60;.1{>0M9d#`4G1sdls\sҜ2Miw=voAᆹnFC=bΘ$" HA*Ұ؊m & ; ;+ë«ClY4Kh-EPZJh9VjZCki 6fFKqO HIL)JiԘP:5vA;i=q[O(AjNtʤt# "E>|)C 040`G"(b((R(2(r( +J*6 ōvcX7N7,ʦCtNiʣ3tft.ED +]ktnMEݥ{O=zD =gbbfŚ#p$-;瀋pQ.ŹR\pY.WJ\pUչZ\&\q}n 7&ܔ7Q܂[*:j&j6.>!1) - +ϡ-G;eWɿ z^7Sk&k:^ozބ?Ox >@/|fcu S|uN)_|_ w~ A_OAY47~o`(~0 (Ec4`,a<&`"&a2`*ncFxmx]x}xCxcxf"0s00 K˰<9 oo>Eo_~~ ``0(98Tp: x_|q_ا/K2/ ++*k:oThM|S澅o[־η-޷h;Ǝx;Nd;Nt;δneg9vgv]d%v]ƣx4<ȓxr8Tyhųus7Mq}Uut`n*_ukuuu\wCI>٧Tׂ4 5Xjƨ j\P-UJZUUJTjک~|STGqNs꒺n[|o8U^ ӽ馻nvlc~O +)RA'%O@?.?hG"J U)-奲TR_:LVFH[i'/J{yEgH$t]y_z'|jW[Ր>W H~w)cUL2SY"+dl}X$IIWOt>d=yfaT-dI2]uy쮻.aG#`?#(?O/ApUs.p=bQQ `cmt1:DTP$E %DD> :}@v`u`m`}`c`s`k`[`G [rI /r^.%"wλ~7~kw=~/cX?λ,RNK(T$դԐ,!RKjKԑ0 N3IuҜH'p2 +'Y8 Dg3ٙLu9ӝL'˙v7ݛOܟ__{Ͻ>pw}p_X6h\{&c\c 5L~S4LaS5LqS”4LiSƔ5LyST4LeST5Anj&؄Z 5uL 7&5Loils^B\pQ.ŹR\pY.WJ\pUj\kpMŵ9psGr]Q\pCnč9pSn͹Vܚp[nwNܙpWݹ^ܛp_1qܟ@ă |1>'$TJQi*Ce +T*QeBU)QuA5)BզPCaNIuEQ}j@ 5hjBM5ԒZQkjCmԑ:QgB]uԓzQoC}P,Q@i xJDJ!4Q2pJ4A#)2(FhCci 4&dBSiM4hͦ94|Z@ i-%rZAٴrh{ZZGimMާ-m#v>i2=-3T?bUjUJTIjdTF*]eL5JVcX5NWD5IMVST5MMW3Lf鶺n;莺论{螺辺ѱ:N@=H:A'$=DtNuNKtЙzLɒY2[\Ei}9/K:@#tq:A'3&͌0#M0fmƘfz~6 NkbZ{} {c4T똕fmn{w2OY(uʻg׳Lw{=Yc,yO߽ޟ/o{^y7[,Pp yp<#G^C~Y"Ke,-+*Y-kdCD!F; EQ Q%QJFE9GTN$CQ QUjmszf/w9pPc#a)<\-9!'%;`j:C8"(G4D##ZEF4AS4Cs@KBkA[kڡ=:X]^^-O}1Ecb# HD`(!)TaF)>td dlhXxLv +1 1S1 13Y9yXEX%XeXJ`Vc bc6bf-[5+zlXKd n~_yeWZ*,<ЂߐF;lOy#|m؎O)v3.>|_s*(8N$N4,+\%|opWp-;#~ TTGO~BťC\qߍ **[qh2FfQ0.D2!%.Q +hbq&fHn7hԓsrNΙ׷nխu_uYZY[77%Rm=B|mdlvUXKLPP ~  ThQKjFu@jjlE]2 +>!H4B ZZ ڢڣ^EGtŸшNtAWtCwĢzz#}H 7HA*F`$ҐT{820 +0c0㐉񘀉ɘ0030:٘700 XoY8 q 4=]G1X}'Id}>UO}!K ˱o/X +oc-.6=l&l؂؆a>\'؅؃ç؇g8(90*B}XYzD)Vjw+$sy|T>&O'Si|V.r|C)ߒKRqoV,JuFTR3|?}5%Xn:f:n:a:IvJ(3itegJ=ҀQ}j@ J3҂@;&SN3I(SjJͨ9)M뿭U֙23ߞt 6pN_ цj4%kõ-UҴt-CfX,U1d:8tpri(Y ^WMZ;w]z_Q}@-iPWF)Vzh=i&R}LuOkZA`͔s<&z ώz.+8 /G (?SER=3~:2lT>|Sib~oH;RR:mt}9t1 +U1F1`M:fO )O5 + +Om^m}Mh1GKxq dhGm=ހ @7OOg1g Ulv>{Oe`_l4BMsz‘~-BΚ։ߕ%~ 9~QlGǙ sYK(l8LVk +VRlvױMvnṻA{W:G_ƽ΂3 n/U=khЄSr TP,\Fo&dBPHΪ0՘ɍ"Ry<$@eQ[, ժګt3#t&+m c;y\/d5{R$x14nG(!0=x!>,Spr %Lb8,DMچmx.E}z +t'|-Ud CB*}1ky.)ٮmҦK$@.t)Of Emw.t#[4CE Klf{F +м*tGF5 #)NQѪHF%>xh=xB)/V;FQj) P [PHCb* 8'n ט!K  U)Sm?;@e4P•Bc8J (r깋ᄿSe%SS(ޠ7!p MTZ2bHFDMćj+qryk9No.wk)pe]OH.|A|g+ 7 +RA.yy3NnjNWgD""$ qҤ:Ti +%ACb/]LzD_!s3*.By1_f۞IG5`8nBMЀE"狂w +4m# [pHzK)8s N8HTa."EHWװ&heCGNJ>kGwF""&bK?txEd('`^ ^7jL'U]]Y头:*-Ϝ`؃ 2g>~(>!OJ/& `~T]3k }Q_ERLH GX"Y碪V'(&V|.<%Wɾ_b&O(| jPLX,(b iS{6hNO7wiV}0PNUV$6: SℽݙWT?^Gճ7>- +K;=ے-^8ȴ /[&L˼qؒӾ-MMud9.)/TJykceQAS֗[tD2Kt0isZKOÆ q፞EiW +գu;Z&ӗIKx[0 8`=݉&cTpg~ھ6{K;KG{ryG[^?v~=ZSCW{]|d YRйD]*3A~`XD)~|bv.,su .WW/47ezrR zTN&;֍HfHzjܐѐij𵧟ưVd-AFȑ0Mє\N&aI6nlHÕҌL/F0{V0V\U4['3<5gOOJO݄֑퇯XGto3UJ3KCM8 ckB>! j ƪs]KLW$]M]g;'׎c;%qˎ1ɈyM$g h@(UAclA!yU֭P鐘֎1F!w}CR绯weSbZ:EI*XI7ęXK v<K_urk\~3׽neR +39YrU6+f+\*9[ī;GzYԈ߆貁% dNB=KAXOpN%8sN۔0%PLfZIxMN.Wy~og\T|9Kֳiה5[8:]$ȎL7kj\jpkbDϖ'm5_WҔupvx.1sNe߭驪8oʢœ4NCyA( L>,b dPZʑd }hX0b|^IX2^z ~BJGDQEՇo &1 5V=gQLޞwւ +_" +/{}mNaRݸ1"5uCaG~h!{zke;'Wm\u޲ٕW~~>wb}gO#Jɟ ' +C4;3lD +܉ QUM mﶡBB0T9:: 9 9'vih0DhHo) +*!-ևi󡬘1ʔKG'!O_ftm{jg4Ous1G_7`;5ocV&?zd\/qR.eukD401Y1e0Y`$ +03$8ȑ~ M¾RA!679URm)|tW5/Jq a5o"7_qs_"lb-b)"A]{GMpQ@Tk݂;!ʍ@;&DZT| Sw pLF!|"~ + #>l;[ېP SO@=BsB>7.rr#_z`=/, }}uxm+a?נ tlnfcMltNlus s>p^|=v]Ѹԥ'gb0͛4)CN?v`+(9i@=}Ghts\W1qpyw~⸎b = Z_ɮ품͟s[ yҵ-P܋?+ǿ+{tOC(iхR F926}jT݃Q21Lyr[0,ϵ?=ΫO0Ky8t'"joKScln8\ [HӔ~4as5T77VLT>]vi 4)gJA{LÙRukڵ1Jpi +qb,Zmc&8s&_.UB`\x_mb)ƹno;)UCD:uTF5GbTQ_3'5QOkݢA]5)^)5]ƠSKzP f][`koZ NM^rmS~5Y4uȱO(ZsS^{v5}eg+KNP?|o_i |򃠜>wO>`mΚg)MѼߣ?7n{71خ95;m?+:zwwtO!G<a~4z g~F·D*aR+.M- ^# Ϥ&gԽAyV/w<CmI g}YbiDn_t41.bKo;6+ȯ 2ŗ鼭7eב 6 C@=Qy_2hZ)^J^|?[#>O-VC4G\ľ8g\dirًсGoQ͟'}?]˥T^= js/sܳbn*Bh"d#+?T(#9^HHV!<]tl1|ہ -+v_ӏ#sOp̰srtr̟r{wvWGW=F]G(v/swH7Dl}Ğ}2А 9U)Lz5: +Lw:U>/7i?&O&<-* >/B5FH>|UDTV?}WE/q;-'}V̾FvPn B45:|=Ω?۶=1oy.?ֲ}^v{ԶfvZ~;Q1 l"b`3-Z+;^6VfOusN=?d#Fi^*8bAi0*R%~WL.{>l,U{[s^>@^UU?w{+VˊĿXb) c ?kKlȊ,i2aCVC+c",L*XR \y}}vԛι{ q ;;\AYOƥ*$U_nFo)e6_vl~} Gd9H_7E"+[fk; WzAFcWW)tWoL9Y4+xL"ÚP.LEy3{*|~Ysmy7v4==eGGo%<`nܞG? Rb=Ζ ⽏N;r樔G{soGw^2:8O?pʽwx]dl9\}_H'I <1zMlvk-K-?&K' >r,O:gR*U"7ݳ9. ,H=Yvf~=\|h!\.d[KUWz]D"X{4f3s{56u:g|؀W=:-|b|*²]92GOn"Ƚےnt OSpƛˍ%Os2f~o>O3pg8X8 K6LڬL͆R~V\To;qb.$LsǓYRL]OU^$5 Jt=UHӁmXf+2/Jgbo噖hKEQI{~[_=E:^Kћ Z0S͚^+:ro>7K_ƚQ;U|Wy@H^eU>W}:NT8~p>T}i7)'89AYGM>KdVQ < '"F&:1t]g_ .Z2ͱUt +So|rI`sAObS021jFfF_&9~g.P?'bûu .w)MַC߉/cqEs=""ke4RvM4F /Fw~.Avsn!m q+h_PO8?0P',+:|HYPKXk ZoєᕶWU?~ ")߇+1!Pxgwe˜tr=ywcכks9{RRCyn,cڄKu9Rm«x3c55|ϭ@Yj 6X?3jsTR `V<+,i;:;#j7-fgʨ3 +dY2' o2s9jRA_x,7v# Ra ^O帎~S̟eaw$h%Tv3 2#eDFyd/05YS$H^9u[u2w?^tw,GwYQ pԚ~Bۧs}+L_k=/mՙ5/)Z/ åo^}r7Duȁfrft1Q96D͝晴KR QW>O2.i|.z'\c.\8:תoUk(o'%"9{-狔$*{J+EQ7>KvkNzk +䵩1 + [y<;;ț}˲yKeQ_8Zĥ*o^ +9_I$j6d~}o(oQ4Wqtץ2(#ǯ$Ξ^UK@/Yzi6{3%:jߗ9Dm\J`6h$:oVGiqj'G"EֲZ /mSp'st뤂;3\ ^m[1+ɬWȜ3 ?de leA9hff)&C8ϥvpAf*euOJkr o0/Կ]Z*!r˥A͗%{l4^bW>+ mN7Oۮ~$r+qHK]]zfy Zyw1n3'髹4)sci>uD.!'}寚^ ڧg?.Ab(2A&?I{[V]?${~Yn(rh q<BDpǟߟ| +5m"jQIiFM*+$Wd Κf[vZؕiicK{萻=%;[9ݩ |Y|{x'BL.Ioң*/G.+afkEHΏNՋi7777Z683~AyWi:К+znf +mUZ[*R24oQM ̉[7c# V ~3Ǧ,oiKsQkán4`cobEhqه~NmqzJ?4UB޹c6I3Zs Vޏ}-`| {RMGur[oq_cob +sf=l~̈́߫l D/UP k!&` oy8QD3E;HC,~n8% P'Hpt$DӁwzP:kL`OJ$gPḋgp>kyS f,!H=YCՑ3-B͚&ʮ-F3O`-{MfvTU*ϕ~`39<ܣ[ޜ?y$Z1HO=GZc"bbrL>O"=`4a8}}x$~3DXӽ'%㡧[z1hK$۫Z$[i{STPO^{&=)>܅$cyM͗G㱶Q +əReh'7SW4yՋ~H236n?> +{Ո6wxyd[f'v(痐a7kwy:_c#~{{~uNRt֋eݖw0 9BL2!,,s|s?k)nC8 +cܯuєoKﶝzIdNw?%ȷQ{l@0澽K7S4Z$ҮR%FI%Z[Ss^eR-~* 2zO1|-g˦zKͥ|<2@dv 5%}?f_?w$w#XK-]w8| ;HDM=y-nv >G/8L > 4{"^%/ ~y(;DDʽY/Ǜgn/skylY)׳e7yއ={E֧(0JͶqq{uGʘ +|ƥpB[Ǹq67 s$% +J?]z?B|JBt5q;j 5{(T8FjkKj8[[iWwA?+1=9}Q=iu:Iv ZpȹAK_^ھTOBsr/r\sOv! KS¹}&1{9?V}_}Nr#w߳Ƒ #X,+%OXЇ97Cyu͏$Hs< Z16u.,t罍R:yWaTJn̷Q*^;5-[G:^{=*c3BK–Pd"7EQw9G롗q}Y7 WzLkk6B8+<,r\N٩SfI:,q| {ȕ";'ڧ#d3.MJv[/:"GYQ)dQK ð'Q8v&BN>G?.dk,$7WXwaɄq"O#eގy +漇e츼 㒑O}a +G;F 2 0ؠiKWhC M-g4u;!qd0 iH^v;%aQR*Q`wO tk9= Q?}adOI-v/o5fyq<(CB-3+FEE'$s *=&1AEFq }f93t,7.(4vKCHS/ ؎c KKh 㖁("Qzv@y ؠWk\9'(: +ϗiw'/ы^a9Vj!7 -T^?9YVU^عvq E EߤMݤMZ$Lu_uZ[ucۆ3ߠ=y\ß<6?GGJ&rbO)j\t{)4)bF ]-a]r#}^]h5}3m9wS'692dbpX2ԖT7SyrZ-ܥzyZʟWoW+ՆTvxcCoh<^7XR=IB;XZx޾n=a*%] -I'e2̍FܰKHyvxmc@6%og~ +endstream endobj 34 0 obj <> endobj 37 0 obj <>stream +HUiXTG=U]q n "FB\\AnYDA]D"(q5j'̘ĕ5nq\&A EL&3߭Ww|5:082z19R!͒a,Np%ifzo51#*`S5HS/*:ۤX/3r49gI/-#gvڐ#d&YP~˰jt'թ֌Z#0+szN/X ln>+;%+h:J@pR&Q(oHpvz΅ ;3iDVUjѣG,)s d=j[or^ғ=za2;z1 ɓy,hRa ,)9.w6\Oar6>X/6HG?-$?V>\gOx"Ϗi,Sj#Ê,6Mbl"K{gǷ,MeX.̦]C x18d" Sqa6bl f/~ɘ'qTyb{uty%b!EXJBZB+{9P|J2NQbx%W9RQ('J꩚T?լ!jK&jjSm4cq1X`\ci&ejajcRLL]L&)ŗz=H+Vi[~vX;ӎk)KvIi713[ITdsfyy;;U55jk wL}znA"R Yi1-sҧFiJ݀:QW*+ +ˁ:P V{j5ARsuFԑƅƒFM%֦ -djwׯuVg$uX#sLD +UjԬ|wst.yNoKNO odpfe92w"{AwݾY^+3*?n8Vt>eݱUڮgضٶKf`h+7fIgUܫ]q=+E_ ,s[GHK |I??+,tѸ,r)u=zέ~--}j3 5;."G[`(mal=_ccΆp?ݝ̽I8.#kܽ{=Z~=d[n2c4oY'Q+)9vdW+> ܻ|֋d+$ ͷ|'K<6:X`!#6pdqX>%Oֱe6r1lzP&{w(BԄ:E+5TT|/K&}nJVtvtƻ]v!+BOF>Fw8p|ppqR*?b8!3"pC/,"p#5F[Dwx1q cqq#H:An!wH]L# tu0ǘ1H'f +3 P0<3=^cx9!y1b +3 j/"2Eb%&ΫuE!fE~bCiKh>yS.-<|(r$D>uR( +(HN]" +MQu)T,b) XFab9(VP(JEbX#֊7?؆N nu4@h@b# hLQ#ަWVA:r]?fAn{sޗni;DЋJJW)a4c#c v/'+Vp{Z_*V7;Zw ܑ6r'ĝi3w܍DpwA[h+m{qo}h'ݴ^Gxƃ5ᷜ8N 4A R JP.iimnl~[~l BJ"-f-eK2-ꍺ<4lE[V!jP[ְ5m-[i*Vkmhƶmj1qao[cmk={>#ؕr]W֕}*B0 b8#P#4xU x/a$^(b*FC;WU[x`<&`"LJS|7b$ jwbSJ*LPVF9('ܔQ*H0bTJUJBU)jP-qp-u.pa ZXal 043`&̂0< `!,Ű2X+`%հ6i8gDV;`'ݰ~pA8Jpq8'Dy D"դ2ROKs)' 4R_KC(&+}}@9#RCjIS)*UTfRL*H#)ť4lƤv%F1jҘ&ƙ&jʦ 1ULnjڦkj2&i2|.2.eu]6WewLlq9]Qs]qǕpi:rA'gΥת:Lp L%i 5uZ[h]OmcF:^mNf:YmSS-uZgg:K:[ms _|.PWX%.LuvUIWkg]]tvuMAFn^E{V۴n~CNQwO[{gݧ~a:XzP!~i~gY~y~_E~_e~_WUTP#jLMԚP[jG_Iyc/ot1Gy< Os޽g4%~5C^R!ڪ1T +Y$hL7\ ԦKupESnuĥ彜Db~_|{sι? <]M'qqZg qF 1T b&>el !RϋbX ObP,E*6Nlnx[tl!eSqAQqQT.2B6ⶸ%J Q!DcEl,_MS. -%fb$+# YP1QLs1O6Y"mr!w/.[{>lm+e2TɐKJO%Uy_TҔ^JoyH)P +ؖ-QJ<;lưQ,fcXrYǼ, +X!&l̦lflH'IޚiVg9ތuyQ^yU~-irB;, Y*d)+-y/󖼕Z[œ<'-l2mdxOVc0?98|B\q"W*\kp-b\0 ?Ǎ|(~M|n-XV܆@܁;K܅q¿~<?cxOAxOi ^GiOc.>>}@S4qj6ͮ ދ}x_ޏN.Jxs>w❹o`831 Ga6ƿzMvU7RUe*媦ԲQZ6Zhc +)zWqW +Bm^nii%Rm +6gjmV[`[ՑtvwXJS94K.9n+695.5K}pwɗ`ߦ65jRw}cZ%?Rr jZP{4O WVA7*ShI=hJSfii + F@$3`$dh yf(BsaCx@H63<+q`k*fI0:2fG'HdB4BgTϖgM| ~s`u|!,>%V@s,d9s<2I *ugmiMSTTJ T>0R-Eji̬O(SY<U`&0yMLs-,ߓٙjE+a2_N$m (EYF]cy/|̶ kysH'֓dVa_J=mBv{`/UI;a7pҏy(pPNQO.e +uˠn]CC:f"SnqN$Ē=#+F +o(+9} 0!MSe`W9hF.I@*ޣJ͉30}@~}|^i=Sz$'u^»]ݝ17^_Wor.mQjr7~12aX ԯtOh(ݦLG7EŅa54CugD1wV'n}"E=.y\*,Hzu;H$E +ZWC3Hy{u6@2Q<(#~G$.vЍ D-6du9\F6rdɀ0EJHfۡۛ@ |ل]ӆ|wDg{u'h]xP |OH1(Ah4dfaNefEgIƴ.;9v4!PIZ{5=us_`^\9PpN MނÓm-;b||ն2)BP>ay4Tr?eHD5-`jG]0d*쵐+Jz2L560d HeYE3 +uixieKn+'H +}_Τ \VKPʲ8 + {G%a% +@va؁ RIӘbř |Xv /N=y@]&maPcAۏ?9 21OkrR)۝QIJ'. B{ @m +}>f۸TlP$1]^H4>%"ܫ@VI-°Z +EV0 |Φzopm./J$V4IqieVkšEiB^oCh$uLgh'IeY̭O|3k$~;|hy+ (@j:)5X؍TF 4/Oe>$0֖hY&Ж`W4 mSŃ:g;F-/js +{b|ryivW~ꊊfwW_#k^΃= i41ppoHk* cu1 ͆~"=8lՁʻ6ljzo-wv[ݼUVX %dS]rWB>COq4}W?~ۃ*|Jxtmen,Ez^q[gfޱ[A׽Ic>sTjUU|a/̘N̫?Qbj7?9*untx>Q$bxfSFkG% Vh,v;NJK$DNOɍDf%[zAk,&0EzQdDz] ajPFSyzrԟ8M?O=+wjE _F DA? ,Le|GCsOg޼{O0bdZd+ +rhT^=)~lQጾ@ y?@e/ukڶWǿw5XI~ V!Ѳ !JBA;Zr;o#N!9XvjṓgFܼ } D0([˹v~c14$BCNX`ZlbޣoljK_Opvyeuvxz5eG7N{,CP/;6r,DU: j+ZB=~# ԯ0;E6wk6݊agnޟFyiȖ$Rb2oBm -ʂ #[]EԵ[>3KngOfj n{Z뵒v-ɒezY2O !4<38А6M)L vcm +%@.0LKg#:Em$zv,=w-{aϥH_aO$grM9o[\Ѿ2x$K#R-\ @5{-‚ `Ip*,!p 6W^EI@[3v(`RO|!^ sch4`gIuX)7 Eoj&30x'3W3!8 P.r y&n۩`zz3BW9\u!!sABA?PXёkh9%MMOǾm޵{.st:ߚݫ-7d. 8mtnU[ImlIp]u҂!-;x!0DvBH-,,hPf'XDXn( *c3D DBDkԚQquovƵ)Z)VF,OMoo99!ƫݼq%y/aK֭MZpdפV'QY1X.2k< Y.0&L:uzG\1cG:M5Qedb``N"-@[Gc2dkj2 a}Tai;{xaڶ  oCe+prwņˡE+rT YٰuKMb"&F,ŐJm>*^ց{M2U@J`Pz;]O WA|yW5݁(;~w{c{qrwc4"R>AԵhc;Xj}Z8ę1m|T ujᨓIˆm"w$ٻ&g?RZ0,qU181TBzV8 a3׆OW->[c%+7WWlq`^ᒊ%H}ՒƯzP\]Q0 X[ʨ+sY9+bykj"zӏ/rL;#sD_FReғsfT9#O=Yqr ;ku7=t1F=l:  *7ZRR`i{}Ί#YtU.lᦙt;;C2-m0q.7S׈1#BTX7b{F`N@bIxáZGHD8&2 K"""__O?y>2b r@8H PR$Ji9j5j&дh2 A0w6q.e2FlZhN4*nQ4rc{d/D!M?~TЯv6No-P>RN-WCm} Ѕ&6D^^{!/J~(}wZ^*Cs +uR&3Ws 7NFV: +JX +%5Qk +o$&ptA:'0srJY,TgH˾߁\s᧶P-?zu[FnB31@P*DQ*ۃ¾sVfDK堬sf: V=BZ8Ӏ)նD=a9=!1uJv|6JId\|F9/޽?gqƂŕ mS*^yԾz~|aX_;SG}\iL7:&V{߽=ٸ^O*DŘV`YĀNԵ܃h "L,Jo^mşV5 j} ptHB7]di_N=-_W|ۑ)1R\QB d0=w7Fyv@⨩Jf;aDnAMJ<$, H!<,!8)`SGFZ2Bò0scTQ,Z vAd_HA֕"G7$%J=݆d +R%o^[i,@vu]WYoph)yݠ4#@Pr`G'.~SO-R.@'m5:"@4Y֤` 2N :>M }{YYU{*J[L,F lA2Epa1](H̆8 cSdXIQ4n2}o}+w9sU#_Kػ/ŷv ^ϥ[q:Mѭ(E>,K9;4_%l`ssV0E$:QwQEص7R{3'K[? aK]ng%S,1fͽ5G(n!sZi.NѾKoiFGR~~q>~PA͟GkRy)Y{x^q +~Ʒz`QҪǥЊ?׍Cۙ@<@]ԕzL=H~wC@,Lۇx5;Bh> rE|yoVe7u+Nc Ϲrm"fίeIƚ$wM{&S +5&q8NG 6mhߡ!Ci)-lao Fw}:Yl}&7A1*𹍴%h{9mk'=c٦8[(#}v6LClۺʞקt=Q?Cސt͡yCNK@.5H_N_(Y⼻{X{67=9,--ECNĮz_Tg ZA~6ΡvęM[LÝ$-iWzسab@#^}q~>@G~7ݞx.U M<+}ܴG? 0NLRc5pO<d^ф1ho%|b=e +iCXiS>?d\^u5^GxF4EE_CC_<{5Ğ"WI[7:=':xXMxWh $jz%~'6Ke6OqkӣYCzzՏ(i +%2X};Hz7]%x[d;px4¿x#go!WDoɿzv{R@7ė?%RPa:./ۈ0?J>S0$}?F<E_{,Q{FAB\C@݁zIg4>/%N^|\ <%55D;} cXdߍx4bWI8$gq?ZSybٗyZڶT[ A ۧ +дE=3t"=Kxu tn/keu/[~d߃6c_KaAPհ:IM]냟 wo r5|9^(~( E_}MrOo=w2ύÿZG[88F" Ԛ)~x6-hFY?_, C"\!Lߋj[sm7ysRQö"{FѶ86/]&8$xFFkIjhA$5x5Etm}}+CL/2-N`E|;2W<D?%yQn&7}pz"O6h|7C{l'WG*daPI>k|>3xn|]~Fy+I=NnMc-׀["{Y?39wtAl(I(_C)W;up IMquIri-j&r5" VN~_wo=rN鼄`Cj/jiQ,el\J&_i~իU_LP>NPU@>28?g"-P<5)C]_ûh\K[3Z H5if$GsC}G{ܙc=*Wۄ~MdG=.j/rq}3q_޹?N_i2nQE?v;.5FuY_V@}[W5:y*iZ)~?o)?UKMHJL՛:`fV5KF[iHUI۪dV-S֪͎jTVR>QjC['PUiǟ ;\ѩ ):hp upB\Y,Ep6˥mE Jlr}͙3tf;|D̢*Q2Q/߱?(8/ySlq_\VbPQ˯:gg~Vj ,~/%5 |hpW|>y +|e|FP|~Jh4<*PҸրRSjϿI\֪f+Z^*N%z~jcjPޞjUkkM-o}"v?_oGo6ۉE??m! +MTc +V3jmW+(0."Suat4g7շװ}V3g߽[okڀw63gΜ9s̙3gΜ9s̙3gΜ9s̙3gΜ9s̙3gܯ"kTHQ2%U/VEGޏ4mgA;}Gp7u5.Uϩ|(XF_Q*ܭ|];~4-" vNI&yxTbܔo|ɕG|rםIY|8KgB>=cGx1{qt +gM{nkWޒW=e]ɽS\>w+9w.%љvy\X;v8]y[N*XtwazZhҐ؞Cĺ<Χ8,nGZzÝmqeX +n|TWn#ϙ |ٓh?KO>Q&{e07G7/,F~n},jedhm!^aKv[y|PbDH/Jb +r$K+Lb.K$ޒxS %I&+/K$PbDD %I̕#s%xFi$%I<*L%fHL&1UDbD %J(((H%\y9#%Yi#$)%I<$1TA!%I K /OD=%zHKtIHDKt,I"RD$KDHK] [6Ҵ{|6϶nxKx[SN0>.i|,1~#Q̏ď\56oe]1{ +hъbFkne qB4[ӚeϴfӭiT#" fI^'NZh`MG'$Ujogkc*+A$xkw{5jǔkyz@` +-G#B<82Ԍ߲J[F- GZėk~zp_Pz{2P&kz6Up\Epכ1YpIpQpAp^I'f v`'߁o7`;l_鍣K|6M`#|>'`=ZPր>XV\odc`X +tx,[Mx,W+ex, @(/` s`6x<O',x< +><f 0LSP&I`"x0c@(A7 \HY dtR)`8C  vf`HA?A"3z <AwbA7`1 . + t#HtzLv ^IWmAn+^{@k +-E݁ 4MA4 APuAP@P jꁽ@ P TUЀSzwwwwwwwwwwwwwwwwwwwwwwwwww''3 7YĐIMnFdHU!lZ۴'};  ^(7^ԞnSRmI><7LEqp7pmrr qhA3pЈ8h) 1C+c 5QapqQJc+&؇ +coL;۰[~a6b#d)rb^P=E)`5Va%V`9(znRKy K^o_oKR|rxbO'ۭeݢ(|kIzVDP]t#5civ+0qYE0{2 DL뮴EkM#ZbGuky6a{KD(RWoS:bE8Ver@l rl^ݫUnH^R%Aa^ sBF3:d] JgpwЁv- \G*2.".-hFqgp 888z!ZԠGq!G**P؃؅؁؆- ?ʰXXXXXOP!>x.DDžXw68u +:#0slLdccb +&c&" 11HGR1:F58@ +4$c8^+x/abcb_7 +OW/Fw˫?`.R| +endstream endobj 28 0 obj <> endobj 27 0 obj [/ICCBased 38 0 R] endobj 38 0 obj <>stream +HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  + 2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 +V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= +x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- +ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 +N')].uJr + wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 +n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! +zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km +endstream endobj 26 0 obj <>stream +HWMoFWQF~ZVܠ1^tqTqa2%Z2b`I$| ߼0Ws.Uq1j~tnOw +Q>*PHA-calyM[qYn*.uqQUsfSp-I0ZU>b[]=Paz}o1W?֫קb->-.ީ}zu٫2,%a\)et?/9:E{!8(83r B懧UZ\˺4 +]vwf[ +!t u۽ hwGL4i{W]\ث+wr+}&ƗZSxHq9dwF~}O#X-wɱmA'^)S\~\u%#9 u54ok"AW=k xc:ۣDO{ntt-H1 +fz74/4zc8~kSR,fq{U̦L2=S#' +Zם2?FT7jEVWu8{^a%FcA LQ=#oFs&2JSANo?q50HDi\ X4~-b"c%ʷvIh:$o !~g bqeЂխVUxô1YyluU#:UZV-Y''Xyѽ;ToO2x `f8* ^|#"BHvJ [Q@$Όoq5(LOiT i03$o44X[/p@o1U!~gG8=c +#o E>7XwQưE. %baOEFq=eĩ).FVqkj_Dq tGUOq0{ k:ZG> 9LڷIT!oMȶ (UqOZ{`ǽRqOUqO7MNF@ܓc> endobj xref +0 40 +0000000004 65535 f +0000000016 00000 n +0000000147 00000 n +0000053770 00000 n +0000000000 00000 f +0000053828 00000 n +0000000000 00000 f +0000000000 00000 f +0000056172 00000 n +0000056244 00000 n +0000056461 00000 n +0000058210 00000 n +0000123799 00000 n +0000189388 00000 n +0000254977 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000259171 00000 n +0000305666 00000 n +0000054271 00000 n +0000304274 00000 n +0000301590 00000 n +0000301477 00000 n +0000259358 00000 n +0000259772 00000 n +0000054715 00000 n +0000259242 00000 n +0000259273 00000 n +0000282057 00000 n +0000259985 00000 n +0000260232 00000 n +0000282307 00000 n +0000301625 00000 n +0000305691 00000 n +trailer +<<3791B17F253B3A4581A42622424D3014>]>> +startxref +305919 +%%EOF diff --git a/content/GraphicsIntro/Ch08-Partitioning-Marbles-Buffer-Marbles.svg b/content/GraphicsIntro/Ch08-Partitioning-Marbles-Buffer-Marbles.svg new file mode 100644 index 0000000..855480e --- /dev/null +++ b/content/GraphicsIntro/Ch08-Partitioning-Marbles-Buffer-Marbles.svg @@ -0,0 +1,91 @@ + + + + + + + Range(1, 6) + .Buffer(4, 1) + + + + + 1 + + + + 2 + + + + 3 + + + + 4 + + + + 5 + + + + 6 + + + + + 1,2,3,4 + 2,3,4,5 + + 3,4,5,6 + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch08-Partitioning-Marbles-Status-Changes.svg b/content/GraphicsIntro/Ch08-Partitioning-Marbles-Status-Changes.svg new file mode 100644 index 0000000..bb0865c --- /dev/null +++ b/content/GraphicsIntro/Ch08-Partitioning-Marbles-Status-Changes.svg @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + receiverHost.Messages + shipMessages.OfType<IAisMessageType1to3>() .DistinctUntilChanged(m => m.NavigationStatus).Skip(1) + perShipObservables + shipStatusChanges + + + AMoored + + + + AMoored + + + + AMoored + + + + AMoored + + + + AMoored + + + + AMoored + + + + BAtAnchor + + + + BUnderwayUsingEngine + + + + BUnderwayUsingEngine + + + + BUnderwayUsingEngine + + + + BUnderwayUsingEngine + + + + BAtAnchor + + + + BAtAnchor + + + + BAtAnchor + + + + CUnderwaySailing + + + + CUnderwaySailing + + + + CUnderwaySailing + + + + CUnderwaySailing + + + + CUnderwaySailing + + + + CUnderwaySailing + + + + CUnderwaySailing + + + + CAtAnchor + + + + CAtAnchor + + + + CAtAnchor + + + + CAtAnchor + + + + CUnderwaySailing + + + + + + + + + + + + + + BUnderwayUsingEngine + + + + BUnderwayUsingEngine + + + + + + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch08-Partitioning-Marbles.ai b/content/GraphicsIntro/Ch08-Partitioning-Marbles.ai new file mode 100644 index 0000000..3671bf3 --- /dev/null +++ b/content/GraphicsIntro/Ch08-Partitioning-Marbles.ai @@ -0,0 +1,1722 @@ +%PDF-1.6 % +1 0 obj <> endobj 2 0 obj <>stream + + + + + application/vnd.adobe.illustrator + + + Ch08-Partitioning-Marbles + + + Adobe Illustrator 28.0 (Windows) + 2023-11-02T14:40:45+01:00 + 2023-12-06T10:32:13Z + 2023-12-06T10:32:13Z + + + + 256 + 140 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAjAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A69rcWn+YfN3rweYNZ0kR 2kMMsNhcxxQNHK1wIp5IyGdObN8L9fg+IALvX4gunBj2hjM+HfnV9LSDQNLltNcisr7z1rOp3igX KTXBjWHgLyORJBEGqIisQikqTsxNI1IzLOmkBbIa7GZcPnV9LZXG9s3nuzvpNcvnntbBDc2FY1tZ 4lEsX1v0QaqrPLybj3C1FAM1X8oY+Lh351fS3MpMX/M7y2mtfosly3Ev6wKEcFYIZePLn6dTTlT5 bb5k+NG6c8dm5TDi25XXWnSfmf5aTzA2iFnM8cXrySDiVEPqGP1etTHyUjl9wI3zMGnkRfxYR0OQ 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 4yFiK7qss8v+ezpF7MsmleZdSmvJDAiPZl0jUandgSvNz9IErOvTqirxruFVTpPzk09o5ZD5a8wq IYklI/R7EszQtO0aAP8AEyKtCR8PIgAk4qidS/NGCztYbiDQtU1BX+siQWluZODWsqRdduayepzV l24gnrtiqy3/ADZ0+coF0jUV5Hi3OELxYTSwsNz0LQN6Z/bPw9dsVZ1irsVdirsVdirsVdirsVdi rsVdirsVdirsVdirsVdirsVdirsVQl1qMFteQwzyxxRyxyvykYKS0bRgAVI/nOKt/pbSv+W2D/ka n9cVd+ltK/5bYP8Akan9cVd+ltK/5bYP+Rqf1xV36W0r/ltg/wCRqf1xV36W0r/ltg/5Gp/XFXfp bSv+W2D/AJGp/XFXfpbSv+W2D/kan9cVd+ltK/5bYP8Akan9cVd+ltK/5bYP+Rqf1xV36W0r/ltg /wCRqf1xV36W0r/ltg/5Gp/XFXfpbSv+W2D/AJGp/XFXfpbSv+W2D/kan9cVd+ltK/5bYP8Akan9 cVd+ltK/5bYP+Rqf1xV36W0r/ltg/wCRqf1xV36W0r/ltg/5Gp/XFXfpbSv+W2D/AJGp/XFXfpbS v+W2D/kan9cVd+ltK/5bYP8Akan9cVd+ltK/5bYP+Rqf1xVRuNd0uACRrqAwj+9f1Uqo/mpXp4+G KphirsVdirsVdirsVdirsVdiqk0Ba7jnrtHHJHx8ebIa/RwxVVxVpXRiwVgShowB6GgND9BxVvFX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FVOaBZuIcngpqU24t4cvliqpirsVdirsV dirsVdirsVdirsVdiqWwJfm8vzDNEieutFeJnP8AcRdxIn6sVRHp6r/y0Qf8iH/6rYq709V/5aIP +RD/APVbFXenqv8Ay0Qf8iH/AOq2Ku9PVf8Alog/5EP/ANVsVd6eq/8ALRB/yIf/AKrYq709V/5a IP8AkQ//AFWxV3p6r/y0Qf8AIh/+q2KtGPVAKm5gAHU+g/8A1WxVi/5hWnmWXQ5hZvDNIIZ/QUws IluSn7h5hylrGrV5bUHXMnTEAm+dbODr4yMRV1e9dy7yHaa9HpgMrQwBoofUURMYmuAp9Z4Rzjoh NKHvh1UokiufVHZ8JiJu6va2T+nqv/LRB/yIf/qtmK57vT1X/log/wCRD/8AVbFXenqv/LRB/wAi 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 + + + + default + uuid:65E6390686CF11DBA6E2D887CEACB407 + xmp.did:6d601e58-17dd-9b47-826d-deaa351f2a23 + uuid:b670219e-4901-4d82-866d-303594220b9b + + xmp.iid:d5e0e1a1-24df-8f4c-9817-d3a41a08c4f3 + xmp.did:d5e0e1a1-24df-8f4c-9817-d3a41a08c4f3 + uuid:65E6390686CF11DBA6E2D887CEACB407 + proof:pdf + + + + + saved + xmp.iid:14709273-42b8-4844-95f8-dd7dcca7f08e + 2023-05-24T06:57:15+01:00 + Adobe Illustrator 27.5 (Windows) + / + + + saved + xmp.iid:6d601e58-17dd-9b47-826d-deaa351f2a23 + 2023-11-02T14:40:39Z + Adobe Illustrator 28.0 (Windows) + / + + + + Web + Document + Adobe Illustrator + 1 + False + False + + 767.715596 + 562.009174 + Pixels + + + + + Tahoma + Tahoma + Regular + Open Type + Version 7.01 + False + tahoma.ttf + + + Consolas + Consolas + Regular + Open Type + Version 7.00 + False + consola.ttf + + + + + + Cyan + Magenta + Yellow + Black + + + + + + Default Swatch Group + 0 + + + + White + RGB + PROCESS + 255 + 255 + 255 + + + Black + RGB + PROCESS + 0 + 0 + 0 + + + code keyword + PROCESS + 100.000000 + RGB + 44 + 50 + 200 + + + RGB Red + RGB + PROCESS + 255 + 0 + 0 + + + RGB Yellow + RGB + PROCESS + 255 + 255 + 0 + + + RGB Green + RGB + PROCESS + 0 + 255 + 0 + + + RGB Cyan + RGB + PROCESS + 0 + 255 + 255 + + + RGB Blue + RGB + PROCESS + 0 + 0 + 255 + + + RGB Magenta + RGB + PROCESS + 255 + 0 + 255 + + + R=193 G=39 B=45 + RGB + PROCESS + 193 + 39 + 45 + + + R=237 G=28 B=36 + RGB + PROCESS + 237 + 28 + 36 + + + R=241 G=90 B=36 + RGB + PROCESS + 241 + 90 + 36 + + + R=247 G=147 B=30 + RGB + PROCESS + 247 + 147 + 30 + + + R=251 G=176 B=59 + RGB + PROCESS + 251 + 176 + 59 + + + R=252 G=238 B=33 + RGB + PROCESS + 252 + 238 + 33 + + + R=217 G=224 B=33 + RGB + PROCESS + 217 + 224 + 33 + + + R=140 G=198 B=63 + RGB + PROCESS + 140 + 198 + 63 + + + R=57 G=181 B=74 + RGB + PROCESS + 57 + 181 + 74 + + + R=0 G=146 B=69 + RGB + PROCESS + 0 + 146 + 69 + + + R=0 G=104 B=55 + RGB + PROCESS + 0 + 104 + 55 + + + R=34 G=181 B=115 + RGB + PROCESS + 34 + 181 + 115 + + + R=0 G=169 B=157 + RGB + PROCESS + 0 + 169 + 157 + + + R=41 G=171 B=226 + RGB + PROCESS + 41 + 171 + 226 + + + R=0 G=113 B=188 + RGB + PROCESS + 0 + 113 + 188 + + + R=46 G=49 B=146 + RGB + PROCESS + 46 + 49 + 146 + + + R=27 G=20 B=100 + RGB + PROCESS + 27 + 20 + 100 + + + R=102 G=45 B=145 + RGB + PROCESS + 102 + 45 + 145 + + + R=147 G=39 B=143 + RGB + PROCESS + 147 + 39 + 143 + + + R=158 G=0 B=93 + RGB + PROCESS + 158 + 0 + 93 + + + R=212 G=20 B=90 + RGB + PROCESS + 212 + 20 + 90 + + + R=237 G=30 B=121 + RGB + PROCESS + 237 + 30 + 121 + + + R=199 G=178 B=153 + RGB + PROCESS + 199 + 178 + 153 + + + R=153 G=134 B=117 + RGB + PROCESS + 153 + 134 + 117 + + + R=115 G=99 B=87 + RGB + PROCESS + 115 + 99 + 87 + + + R=83 G=71 B=65 + RGB + PROCESS + 83 + 71 + 65 + + + R=198 G=156 B=109 + RGB + PROCESS + 198 + 156 + 109 + + + R=166 G=124 B=82 + RGB + PROCESS + 166 + 124 + 82 + + + R=140 G=98 B=57 + RGB + PROCESS + 140 + 98 + 57 + + + R=117 G=76 B=36 + RGB + PROCESS + 117 + 76 + 36 + + + R=96 G=56 B=19 + RGB + PROCESS + 96 + 56 + 19 + + + R=66 G=33 B=11 + RGB + PROCESS + 66 + 33 + 11 + + + + + + Grays + 1 + + + + R=0 G=0 B=0 + RGB + PROCESS + 0 + 0 + 0 + + + R=26 G=26 B=26 + RGB + PROCESS + 26 + 26 + 26 + + + R=51 G=51 B=51 + RGB + PROCESS + 51 + 51 + 51 + + + R=77 G=77 B=77 + RGB + PROCESS + 77 + 77 + 77 + + + R=102 G=102 B=102 + RGB + PROCESS + 102 + 102 + 102 + + + R=128 G=128 B=128 + RGB + PROCESS + 128 + 128 + 128 + + + R=153 G=153 B=153 + RGB + PROCESS + 153 + 153 + 153 + + + R=179 G=179 B=179 + RGB + PROCESS + 179 + 179 + 179 + + + R=204 G=204 B=204 + RGB + PROCESS + 204 + 204 + 204 + + + R=230 G=230 B=230 + RGB + PROCESS + 230 + 230 + 230 + + + R=242 G=242 B=242 + RGB + PROCESS + 242 + 242 + 242 + + + + + + Web Color Group + 1 + + + + R=63 G=169 B=245 + RGB + PROCESS + 63 + 169 + 245 + + + R=122 G=201 B=67 + RGB + PROCESS + 122 + 201 + 67 + + + R=255 G=147 B=30 + RGB + PROCESS + 255 + 147 + 30 + + + R=255 G=29 B=37 + RGB + PROCESS + 255 + 29 + 37 + + + R=255 G=123 B=172 + RGB + PROCESS + 255 + 123 + 172 + + + R=189 G=204 B=212 + RGB + PROCESS + 189 + 204 + 212 + + + + + + + Adobe PDF library 17.00 + 21.0.0 + + + + + + + + + + + + + + + + + + + + + + + + + +endstream endobj 3 0 obj <> endobj 6 0 obj <>>>/Thumb 108 0 R/TrimBox[0.0 0.0 767.716 562.009]/Type/Page/PieceInfo<>>> endobj 106 0 obj <>stream +H= +A >6&I/V6 Un#l2 <+0z4b.HJ ~yIP(yeX0˶s. ?%نu܆IxDSW?4&LKsf4gos0gI] +endstream endobj 108 0 obj <>stream +8;Z]!>n2B-%"l+j"a:t#:#orVdthg2^F)_l;Td'')F2H0htqmUTL,P[G9ntg!F=rm +E;,>XVENX`G-:!HD!ab:M;a]U.dnVsGgbYFbalDUE:31:Nd*UFB\#5Ts%bUT_-h': +*HeOnSX1X]o$3WsSS=3ETX>58$A;:EM.UU>pCh.W'b%MThGIW+U%>RshFk\tOA(QE +bCOi>)!0Z(jGK;@NTDn;#@RB7jqiQ5f`hG%BRf%~> +endstream endobj 109 0 obj <> endobj 111 0 obj <> endobj 112 0 obj <>stream +%!PS-Adobe-3.0 +%%Creator: Adobe Illustrator(R) 24.0 +%%AI8_CreatorVersion: 28.0.0 +%%For: (Ian Griffiths) () +%%Title: (Ch08-Partitioning-Marbles.ai) +%%CreationDate: 12/6/2023 10:32 AM +%%Canvassize: 16383 +%%BoundingBox: 16 -549 996 -15 +%%HiResBoundingBox: 16.134253203335 -548.66015625 995.961513591814 -15.6484375 +%%DocumentProcessColors: Cyan Magenta Yellow Black +%AI5_FileFormat 14.0 +%AI12_BuildNumber: 88 +%AI3_ColorUsage: Color +%AI7_ImageSettings: 0 +%%RGBProcessColor: 0 0 0 ([Registration]) +%%+ 0.172549024224281 0.196078434586525 0.7843137383461 (code keyword) +%AI3_Cropmarks: 247.366972477064 -562.009174311928 1015.08256880734 0 +%AI3_TemplateBox: 683.5 -384.5 683.5 -384.5 +%AI3_TileBox: 210.354780405145 -578.604593259479 1052.21476087658 16.4153652366149 +%AI3_DocumentPreview: None +%AI5_ArtSize: 14400 14400 +%AI5_RulerUnits: 6 +%AI24_LargeCanvasScale: 1 +%AI9_ColorModel: 1 +%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 +%AI5_TargetResolution: 800 +%AI5_NumLayers: 1 +%AI17_Begin_Content_if_version_gt:24 4 +%AI10_OpenToVie: -88.0366972477059 141.972477064218 1.51388888888889 0 8118.8256880734 8088.77064220184 4318 2607 18 1 0 89 170 0 0 0 1 1 0 1 1 0 0 +%AI17_Alternate_Content +%AI9_OpenToView: -88.0366972477059 141.972477064218 1.51388888888889 4318 2607 18 1 0 89 170 0 0 0 1 1 0 1 1 0 0 +%AI17_End_Versioned_Content +%AI5_OpenViewLayers: 7 +%AI17_Begin_Content_if_version_gt:24 4 +%AI17_Alternate_Content +%AI17_End_Versioned_Content +%%PageOrigin:283 -684 +%AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 +%AI9_Flatten: 1 +%AI12_CMSettings: 00.MS +%%EndComments + +endstream endobj 113 0 obj <>stream +%AI24_ZStandard_Data(/Xh^ / MkZp*\F'@L.EvwoV?g0cx񰍧 A T43Rz6{'!UCEe{)RGh+ax(YF d\-y$-5EU]GmW0ֱ]a \dcX2Їɂa"iQ.єtmH_SꙥUA")AX(!X0sa-+)6o'޹Z:Ґdxμ"C% ӻBǃ6 j P$S$ "InłA  Fh@up8>`< D!0 HɣP$WS$h, Ŧ!=#U$NEb]xVhXEU$Hp4dcH7 T$rPC"15 h0_Ml,rHHcW0 [Khk)PHV.~}ϲi 'h:Q$dR$D1ŊDaEH"5܀0 F2Q b< \ Z(ǚcPE9)HkWV E7j~#ˡHt7Hx4KW$"*KEpP$D(""p/,S.-yא +t["qY)TEb]QxE3,H R$S$N`"YA.Erh< 6EdG.a+⫨HQOzH X:Ա8Ebem*Z8HlERV$^C8ai)HX<5 )==+EXv Q$bTC x, a!uʮ6ڕZ"YO'yP1ݶu{;|9tI7P77TtgiV~POyfNÝZ 3i1|4YvIz;ta݇ H0 "7CN>\(/Hep)k*x BrP^{0A RCv\.Hs0<ǣE^PYv8S^bT(Hma k1:_^oqk9Bc!bq lAEB鬴|.t#!Ij-5=EMU]. uC!J뭵U$2},d~~qwqiE-lq \Bb`, ⱀ, 1(1d(cg, F`<5ܨ5xh<H::X8x8 zQ{>X8bH!$!0 0P$P$eCP""9%P$Ç"Hȃ\ERԅH`4H"9S$x0}C>a{z@p4 "a8u#89ƣh4І6k\֨Ԁ0 X0X"QF2cfp, bb.na ?x/+:d2v]\[Z/-DІ.zjj%$$HF.rҲI A: <.&"!85qޝ]bkÞH Xf RR bZ /vkMEFxfH5b48vi)I)I"QY@0_,jH_R$xV.SA C q(^HV6`tZЂHIyfR$P$ʙ 0EA@P&x4@X8%%D&$6 A TPA B 4шL4LDD <4`(0yXpxC""hXd qA2`X@ pa!DD 04dBDD‚% Xx@$G! "J%@Dᡁa!‚A`! P D>1 +4q`5hCC @A 6 2!‚XB + +xP&a $,xćd &(( +&@(D 4p"xP`"ҠРQP$HA! p  +XPD P@bA +8h00A8@A4@!(P@㡠H +c!bX, +dPwa!BDd"ba" +$$@xPP$  "  +.$(D&&0,D"L(D@4DD4 с   $&8hH"<4D(<@D0,HPD(ΩδC sݮ-\eԭEo'*oz(oY7x_wZ>c"nzX2X]P3g\3+M]ѣ+Y3>j6qkV鍬s͜}閾jm{x$"}ΪB›gzuWZo7>Pt֟j6t{M]s/Z:%\y7S>Y]}WUOiԮf^T;DTv_𻕃wALץ Kknz}xx]jl="^TI֗Y.ypIP"a^po:6&^s?ZJeKͬsT-e栩UΫL 7t,(t-ޮ;n.YfTvN<;кcUS$A,u2Yl;URj[Mxѻ.M*/5k)έ˄4/()m=Vzl>BWz׳v2ĴDhsk(g(* 1QsF*b>eK8R]ſr-kb]nȐѪb,K*V&Q:en9K)bh9r#-ɗ%njϻSK5UKSo]([hArg3JYV htۡtz[']#*1-x;m|vxSbd[˝ڂ9ߝRj%mbR}my{娹Ii͛Zq䣖V,E^Ss)oe)ekc[6R{)=T>#ZPqZZپt<8 + (,0"  "x AA8P$W5PxyTg+?uAQ8i+9-\oW8jzLY[2eՖ짷E3ݨn'- b\*l iKM׿JTѲ҂,+kvkF)-Ve[̶Tުս1Y12f9"#RNf)בfs.kޤ,fY^m$e{V嶚rxlsMΪ8q֊&yJ~n_EzCW/ULo'N]\Ew5ICBrJ#۪حp{;*ҬU5z,nѮ*zw;e̫A{>˾*EYJT7;ܚ]x{UN'>VPtX;U+ٕ]y^{Gs+gmUVLk;֑[2[[JcQP$ Gp*r١("Y4 [hĞsHYy6g0p,XF:FF68Gq~,GݙFhzHGi<ΆguulOjvV{\DYQ]x/#2iyxIc{SWlq1+Bee&gz;u<1͎.U)r9w(iiR^ӟ4 V)Rvx%9Y5*/hu0Ӝ/SMz!jUeU7z*jkEȰ/&i*fi[׃R\˱ڲ,evkG6R*lVciXtUl~kHYGyC ZPj2q制Z #R}VykNeQޟJFWM6Wu2W|j7"u1x#4ʓ'qS/&tUmQWc[{{Vu5Wm{M]ؖ]WQrT*#Zr׎lwZ(9-Vw:xw ڐ:Iخ&wǻdR-VZ Z7[ 6xWcnQ{O.)=L,]|cW̠#J簎=y a躭ڜWYzl~֊rM +,kϰK9^%wUV6U#2-o]hN˾IM%ѰՏlVrTOlRVItލ*x.›smU~oD͹ܵUmҚ*ip|gcX>%qQݹpj+k6v-&h9ә)NYiU^嵹 1GX9_6u 2U&&bYqRZޫK4kUli*muWbcVV7f-:7W\oy2SZ>pGSghwwXI!'*ixm.T22`qnw.=V^R PG]y/՚tUãn{.1fzlm~˚ڙ!b:3#]׼V UT-re{^z^~4|[ULh7W&ڹZhxG{ךfYXL'>x3<5!yx*a]+t=O{:KK5Wk)OK.}ub͝ nQw6wNZrBnyˌWE^1e撫J楗eRb)O7u' +pVS+m +-6Kcu_aa:URx5~F2"KzJ{S)dZU"U dR#qU"ڲVҢNe1ťC^Y7mR]EyGMrwQYw7o-MOtIlzJj%ލSϳ{/vKLWn)՗RKmwgukY1YVI)ӋgH~|j̥#.TG00  %CCD +%3 ղjlQZʭQkRwhgu/´hiO*3.޹%1V/WB>Sf%!- +R!ܢ*V +OK?OR{3GJ,V-,6/bZ'OhQm[w]Lͭ,)UVQnmGGyUI%%6H9e5.Ry~R[I+>MϽRK5dژxc oЅ_fX^:2ڶl94ϕڋַexRxk6*[4Ƕ~W$E1J5gOSƴCd/*GV{d~PbMTwTZkסW[}GU5T;[*bYUtߪ)Y鮩itVx9gEeT9$LX}wn )ґjn)]&*[g4 + +Yw2"2ӫ%ݍtvl\eƥXq`)"$>-ڳUFHhZ^zs*ιi:+"K4ZՎwVT^b}foFrfcRjq(YkTJ,ޔ']lo8Xv8DTΛx8&JJGTMƭ$^~4X?Wee-L>Nz6&+69mP#-},zykʊi p͜SW~;ʱM᪭zxkՅ/-,/EyS1\g,v7V=<Лxgxʭ+T+Yj +xEnE;w-.MyVe94q+ k9ڢZb)aZb£}Ew6CxzڔOFRl[bW=No 1fHyF:1ӶRkYY*JxG+*Cudfg-fv]1Z8֛S>fz)4{65Mr+MIzju|FXt*%ӲS)fJx[D,.P=~R+KZV\ѫ\{n=nhGLc~n ͺ>UvliFv_޼ɫSEkfiP Ұ4Qgq|y̳]bюXyXMâ[~{}tѿ]vԪ6iPL1-Wj˫~αՖz)ݯjN:v`6/vx*rDvrVՕ Ue|YuݷcU߻U3&lxjC>Uҥfk4iM~9jk2HSUFdd.|1m>KBIx_Ixm|y9]N4īߝtϗ UgʳY;QY͸zWT%Eu:Rs7FFg$}Ϳ[қ|_Lf"ݲYE3L}6fH}޴:d. YN^4VT{VuIWYwy& WڵkM5L^6zjLWQY_ln[J4gku:[tetuHz[T袪Iy3/-}{zF7xzVd8h?+j]felR9uZ*TVjIjf'=:|Gk-e6/ɬ4+/:J%]lGZz4;w.˺Z岠JCpT9;Z+U'- +x+o,Yt:RZ1WCu3'&Ovy*R|]kҜU ϴtnuV[W]ݮgIuS(շZYeVѐE-󐕲\̵̤گ6[jd,&omWeXKʳmSWvzy*G}8̟M^-]F6eM;f,y͹i3k lFT># fK}[y,*^)y3=;$够sY,YuCXJq:wE+{gMz7JdJT:kܕT4"+󯜝t-褛%Rtsizs +펮wg|/Է,7pim5y/zg+|iB,ګ)֍(}EJ5ƢkخŲ]= vJS\ݩ,fJM]Mi1+J6T4LOc )P30@<A>B`@AGzN!d+uf¡- J-.޸]&@1v|JhzmPpn@߻@JcrJWuhF![JBGK++MH8;_Bkc(QjnF0a;{kH]j}ezMMEY%7 .Buݠyͬe@z`JVIf +ssE=(B&*e:hx)wPj={ppN~o!`=_RC'c_W߆{B7U `Nn6i]vSShVk"EļtjKK+ ʔZyuzm?/:8qgk9& +!d:ur{O;d`|V-n[BMpkBTDӗ ؊s^'ם d۬8Vػru&mfA$%}km2o4dN@tL O _և @UxaDO[+T1FM?:^I ڏK \ ʷGI jvmҏO%-@˩3l6-, w{D>T绬aGDC!au2ڜ/V㌇5ߞ(J>MFg>*{Ҵ0YBp+%5-P=Jr}As;/G͆~Ř f/0c( U|` +V I qȜ4O&6\k(4rC2>oSĆ] ,9@*D1To@dD[e2uHUY_K̯HS1 + 0fOlieVaE^ݰ'\lM CU湵70Uܰtg-ήcw@3Hr4{CڂmNq p6tz~̚JLhL4/'܈fE'Gd}O/++򺫺[r% z1X&%D8".Hʢʤs}e{u\pT3S1v{8@q_Qק1ln3-v~fU^4IaEV[7e-Y3L$̫,&!$Rs%h^uܐKp-QIxR&*m +첀cV`H r=! C%Y^5;%TpT BG-xq(57υOEX6d-]4OW ,L;gU.h!;D݂]=Xy5\#cfF#dbJ}%SB!vmOb3%8tn}XI8Q'P[ӔلBg])%eޅ&;1{QR{gBS *>BHLm_5c%5}V'rJ^gJQ?h@ )X#9T&?*GHhs^${m>A O X5yxoh +D,"g "c;|qn >E^]jW87prR r(}N,O3N%D$N@#RS CzDʿrߙ(ǥ`(Qh7P%s )@ Q9E`6쩖`C!FqIv2DF4dE\_mCBwAkCP f^qf`7}窖PlSʛ`on$`$FDVU߁( }4@e$t$+">)9M^f)BB'sR\'j9>_LtgME_vԳA=pYC0I-e̵MCAm4n s< rq:)a"3vx7yOn>Fl\LbQ$ȩP&3@Tp) h>C~WO2ZD*pmmlXz[CitC!9:sQ'E7 |ծ!O@=l-$>O=ZĸƉ4SC(-k8wC (65tzc]ݵ4;y{M =PApIEZ+2j?!1D*L'j~hh>*J#ۃhE2-}f9Qoj[s[&HYW'tSoA*ٿ05oyG?dU *'tF]$Yw 残Mg"+`m *}zV@Ą^,uD!sLWm(2LMN]y̪_2T;ee0orSmRQmaz!*L'?\߸a9MEMN!Gx2\.`E5:ݱRzVfLA*?RѷϓĨ5מ + Ԗ6ƒgF2z%3-p 3ŸKCx6Qjoj&^([0` a!8.&Ae19ފnl7tX(*'H`xzGHN(6T44q8"T NFPeXǗœ3\"&Pw(4%8$cI +qbjDpž4PI0^'TXK6c3╒CPh"y +')KU# pTD2Q_tŗ/XړfvH?(]IּCA=w񋜓 [FwG` %nV<)Mdl 0o 1֞0/F\Iv2f eR= *H6OEm3C]Ql¯WR0IwE(94P4̨xӀ-5@S  Wo L^\Uڞ~gSO"t\~Hʆ1qK/Zex{)N#}36yNs)\K匇@6SjEǯQ~IwH!TBQH:E!9J +5_{ʫu@+E>Bu0Sl}̫J\ق%F׆m5EDum"eW }( c=;mdvsS;vL$Q|s&qxN W2p {9N5K'ҿS]#A鋈p]Ў>EPQ빙? ;!у.Wk{x{|bTTbS"G(B= <;nnx@n%%%-("[p)|r9\oȄ,̛S@n?+{5+z3eM{2ѳN d*\")/Vr㷚.hHF;`U+һRZ2 x\5rĉqbyak&A3Ns6[ҋ8p4N"k gGUT)}}+e_5.q }O1&]cI;J| J +V ]e7#nV'Gz|bζ1P;\ +fk^-yLA=G8A 0W @zBHRe}׻̯a^Yʼ?c)oo89AԶ`˂*O-Y&VCVq$:KbUYצz>7щaXkM\ lv;¾c D`\/h RjQs jLY'~:8R^1P6Lv쌉.&4T}z}PZz2C@H_ + ֹbJ.b)DmWbrMDuvP,b=̃BD*yto;b-C<Y +INqhJx~m"h;CdhY+imt!Oo +LfkUvBrϦ䟧?t>3/{*&pZnn ܁O-Q{zz w?$]= E̴㨛/VFvДBn.;G +U͡X M7&.Sm.PJAm#yD\Ldi)PKNXA4TϚ@?vmshfiY2(FN5C%6A%S\CL0 +\"_kR旔d) Г‚;UH$d +G%0dCs7S}Yэq[T&?[`kzQO9D,PM%w9dD"o#@gK4mVI1SOBov)_"`չfץf< su'y'0pb)|rSMV0*0MD0Z3\lbkaOHRwc99ю/I+QގB1i4dgGbе( ;;HH:",մ7"w<ٜ9tfɗӛ`.lpw0ry*{UK[*s6B2J=>Z9'd낂lxݜ16'fP7Y$)I(iaOڲq5U!Be! ,{裲iVyjhېA+ԥ#+Ez)~0c[͓7O9 -f.kB:Y61Q[}hycM,GGrX+a$T׸xyrQZ(g@7 vd+ +R[npC)1PrNgOhԓI%UeeȟoGhU~I +S9Gcs_P&WW}9(V#@W +F͹p L\)\~|$ʯ!AT u(="~vYJ>iU)-viyLJ]&ɛ +C28  !+Ō/ 2Ty5[!VhaK]P;l@ u.wK υЎXŊ\ @YƟ  +3~ﱕ]8Uvj7va_Ó}|+௏j<#(:x41 D2! +GI4WL& +˸7f[OLP"Zv, AF*+`n&( +1AgE%OԢX$kɂ>E|P@yF,,0h Kkm其ϥL +HfEE&LaFwZXP oXgT!DpsɈy-:FҴ@=sv/ջ]4k-/מdV`+TDT4Q/bk\ ~L$s#vhR^> gļEo{"&obb CµhL;]ínj(%<%.2w'<h۔-s4)e- nvq5709CyD#/S?9q3~M\ݿ~4 cf2yV)5kx'6 rرaZCpxX?L>!>^4Ckگi8  lD2>?;iKo?,po<5&b(PJu+@(b7.=GaoQ-;8Sn ܄(V>ꛘ߿yQ@G4< ′W5 vvyɕ_iӼr0G4GCn=G0qS3ShmL\ ՙ*D$PEQ@v><LjB#f)_'7 PQzhd~!`]2j}*K- +L__z1# +oMV|Y\h鿔R:DZBuIAa)qZrTq&2N A~pPXiJY1py @ǞR'I1(ip!@lUHc%O^Nÿs3jR:H9{BXټ<9 BCj#(\VJ~ӈ)6tp1xJ[;":xKϑjYj!?jEݓxig>,O١ (A6YeJV}mdU_~fɇ^?b$Z"p*4),>T ܌puZd)h +w@S\Q0r_+-"'d2cL)J'@c{.QXчd|5Gfw;l`O%W}@v=xd;'<@.W:|0TYeym ٹ7I2G<g&0fW&!b[8Ehk1A? (@rmBI;*$,(J,TRx)(ETT$c!$4 cޮ9+ʋ-k(I$M=Jwt }ES8%vb< Ryf"Šu8NFx`~27S0KwhS'Ţ!s`/R6 ̐:$OwJzU?)c"_ǧ Eyc^I{+?*"D; xW{R҇|XeN  3i$|Wg k,92xO{> =sduq)) +B[~yr<9X`6vkK9O!E22RK߾h?rdQm^І5}ׯT{ʹQT.)]TUA(1nC\fyfA Qg c 8tbᲙukz}c:FxLI_f󰝷Jer +]õ[0LI/QZXhj(X#-B6l(Ѷu>N9=ð +ʺ XG*Ë!5T@f$BVY4{Lh $ +«9DI#dDZ&K%2knM"%,%CSKjS) KA)|'e(%ۋA FXpTYa +[qQqTz`i1F~,pi9( *(b+ +˄ lD%Z}O#1t\ݽ)NI"Ŕ(0ǀ{;o/𹋽[x(ETF@^AfL0H.G[] Aj:|B pV~*j&ƽ [etBpylogj0ۈ<*ٲ+z&g ~PDŽQaRs̅R]=OMЋh$zT$p +Ѱ 7.Q> B}dUeːB.H5hґl_GΑ +P[[(_ $,A5:XT;@;1%7Hw(őo^iY0?:ރ_d\D 9*Xf]V +W;%H,MII_(U'ᘊ< +'iSn`H 2޵9PE0WzInMz`?u;M8BaH&uțâ]*jtbLtY>rZ58we.KbFvnSf; +6%s}Lx9flЊTntpsC ,*[R2 +O}?yӦ3)oY W8×Ln~9 Ɋiڰܨs}9Y߰!2{lcT.O/8@.X4<2IN|ڙPj #tc_r.r,ԇ@R9J@>,&6A+>40|f#YR( kA@,ge%SWZ=GUӿpu9ߊRԲWN Ehn^lKէT#*TI)T^qϏ |s{4R^!C7ßE$h[u/p9-RՆZ!٦F( 0=Q h½r LCU=Wl1\XZarDL|3EM(:.ϒJNM hiQ˥GUDJ Gָp/^~r>Bf$PpB;G%lJ@"+N&Hmn>.OQKLǜ@*2s. PΜ)jLc@U!^$,lY?_{Nςo,r~K/(p KM`C4-V!b b=jOK̀@5]E3ˋ 3@MYv\h\VWO_ڮ$M. FL_A+MJ= qVrŒ +/]Cb+-aWq3iS=s~vǠN8wL&(Mlam= 'u}~0p)²~S5RnE}dzLۺ&ȴ*ԡrs\?*T#pQW#D\Wuq{xN" L\ M`3#%x'p +ꀭ.K19=x%0~][E1k$6U( +vnc!LUySU +f>L,|Q(ަ +G;T;\Ԃ0m|Fخz: sXr:# +G7`PKW;N`D# .›;daAg%LT݇Bu/ʶ8 +ȅ6M[m@wtOh*L}(G TB񠫘*jc@Q "KzA hsln'5gP_fq+r᫿Pz[~m[t`}2Xp8|`Qf Q/{?i:a +ϬDAd\#.88}hJ$ĉW %f`] ;̆~")*o,**`Mn>у |L(rto,jJ_ȩ ~|C.I!ˆaP݃G"{Ʃ$@v⦵{U!C3\mePߍn陏+3v׆P_;uwh[aPTR?<$(ڣE30:[-qCIP,boL.M ~"הe'e97O[9VK]|HydwHA7,GIU6pe'gm `t|#F x%i!!$x>Ц[lp/#վ+W/O^Bs-q:kjc{C+j4%@hٜq;/ϜNҁOVU'oOԻiU1hTO-0J|o!槇o|o職&7?w"g|A(W+WL@e4` 2 >z Qvz+XH$#e׻`Pnza}yO<MsAږsX \c+1}ح#eg};EO5řU\k\Iwgu$pbOH9)Ur+CDZܪ<ǟY)Y!Ž;Uu?ŸF?yAO +,oK[A]ޚk|dž8e-:<ٟLh)1 [,NY"+?ⳲjlKN\2[&ܼh@"J[A|E_4i;h$"R&j::g;gʘ2KDA+*VŝQK%?Y SI}HhL}l\=9DAǼ4$|)ye|0Pkv|MfBUR^F}WBu5&ɵ6jC$ {)4k'ytb{QgKLu:zߩH\ 5ߑ-ᴇ[RzcprSN|i"?ZK |߽\\Ѩ3h78%O8-a#cY\!j1=,^bnO__-#:UޔWy<+FIݓKZ"|,/d:l,HʹGԅ f W$蛓qJNh^ S #c[m-,aUtDN+է 87JBjĦ+T$̦śAR"6ΩZd|E0:O]K5lM`~2 8/?!Zyw!ttsShv=Lo5 +lb*,JTjL_Z嚯y3N Ȋ7}u1<=Om祱?ԊVU;ݥ79o#z+V.uOv0^jBHȴͦ""' ?|dz<-2O.(a:/PQ_.1443Hh@\86t/'dրoz" j9(v\TdXD>b_[ʥ\?Qp1M;QhYwC2s1+,V6AiIe['/2Uo%Gt1Av Kea?+q!tN(/XgP;LҲeYa:Č/&qL7PSӄ*i` !4fcнUCC2 DUWtO`lx+q%Z Ɏ+3•_<<[% a?6j<N!@9KL xKs٣'|ZbBHB(4Z{ $UBQ<63p~S^'Ë1/!=GiUfFX\}ѿc[ńȀ^J*JWD,NNPE損::Ή;f'+jOϵ8_ |V[qEfeKχWB#&Kf^¿T̡T7܀P܉?cm4.H"".n}{ud@?*^I?4LX& .N4?q= {]09 ?0<>ȑ̀ =Vlb9n| j" +.S:u澴Þژ!7y)yubv{s*45K/&^ +$f5DbN,clD` +ChzVC6PCE58Q@R/տޢ'AZ9?׽V֜=|@Gg9Kqs;g KԮJF̬SAT-/oq*EkiX:~k/y +!`Z~Y^ތC(Vm裡͜6h2Րd:C${ՅJ.l&00eEehubmGj,+3kog$ YZ;YHFo.S-I8f'^'[q&nq.&i x\M͐H6Ŀ)ҏ6+k'PHw\\M Z3ҥN5;HuP {CjMR-{ƛ ,@S|%%3doM)Cpi91Q>fX,JDk2*¢ĞP(N(j ܄~M̏|j:(=@d1xN#U:#w?)uyw%=ǜvXC$ldk1^"ů!41閠'io( + J5#Z}czd8~l`JHVUU{kݣW 둹QT"Sݦ^cE8  \(맒/b+j PTBS$;<񕥼$!^pTf V_[%8?j֕CT.&]JS:kdUf~F Jv*aE6y{ly @U>e7dB]uuݫT`m}OYPbEWF_ uO>vC ]}`O1V#gM=`2 >i*Za5eb%!ԢVza?J眔 Gb)pN;A^Svpa\(n#T4"xK$KɊy@f(kn(ўڹJEH8tqi!gGjlvE PKʛNjjµb#( 8\ⵞ> MlpaUHP8u[ٯVTjWMr}I '#C=o8cʣ t*y:wN$MP@Sq9c@8Iau#p2(FPX鱨=)l +c A?QݠeOw# 6Eɜez&%EXt[tFd<&aYo;X[A33 +y`})ww;9M`*'FB И D3. +ry6s[VZTvvKTw5WktJ1D(uƕC]B;Ӑ1 w>зsr`p(o9%/X9DŽB,/7[l@J2ICk}%8SBof +c+ohjg ~W]鸪IWc~g1| 3zM3M\0!oywh":?i :<'lU]In X=Qf`28>J +U(yv0҂ +aLiZFkHo,FJJE`A8zJ< %Pp:bҁdKR"< Lˋ@] zX+("?R۾u zR񂚀ZO4$~ =r&qF)gAq&*Ї49sɯ@G + ݚBT :@2zq.? ` ρ%X̛$gz΂Q2 `?< +T;/࠹?AÂ4o(L gp򨴿 JfFs ukp2ُp'߻bLu|^`奈I ( DlS\*BǷTETOp"ҟl +REvW)Գ@-nOrNisM_ӊ݅(ӸHDG-q9] ɺy +!OJ'HPbO! _X.>{O~`9,nr\,b=V N{Am + C< nXTzZ`Am2y "_ƺ9]I[l?|K%dH7yzĿ t*oR*?)![z~.C7&n {k1Ir ]gRU*纥@<|:#-̰6xS~C`(i0TY ~_ćdhĽvI+)ٵ2#^FdAl@Q$&adsua*<+Rrg _(~pcFpSpTCMI &. n(K7Z$x=l74ې5 G @#{ 8Ե(<\{cn +~lIiRPѺ޷ mfȢM.\m?V_o5^G*;u7hðRY\erZ_ଁLli $吮2dʗyenriWY…+ +~.u\^ו^ ץ!h=ut-ikziK{zF.0.ἥA=/$%` UJbMT`~3+B^w]ŇPCݨE-úO)F {*u3}h[~g[޿#,XètojiSwE Ey@1@2a, E*с<5 +vz-I]0u|U;/~v?6c!m ]s +,7]soB~\r!k*M#5 |_ye˽a)){x-q-/@rJ%&:}ݗm \ A)jN:-?pGQJ1X?G'?eGW׍e71 0dwdWDqt[[tN sur%1 Xa`H>yr?l4TuS)|SQ;zh/lm']{s*+*i_A6=seUXRCҌ-ևy ˚7 +ȴ.>ٶW{Fl=w.hi3/E8+\ܤ\ܨ +Щԙ&yg9Wj1;0O>ԫwzc -0c5D) Y,sڈim~3by\}śIY}hK_gRS蝀F+5-ƍz#aiui/ +2IiZ..Q 8$5J rʱ:4x) #Xޖ] 9V*8)GP沁Ujz璪|q;d"*S2=38M=^IT hL| py z{_" E?bn%A#[d|y0{j.G:H1Q Pl= 3;=pieRwG5zk+xt)Q{$Ml@`S;f(/ ce;O}?q9~A s_T"~գG=T\հSO||`8bإo;/(=_u^.Ju2!@"!ujDfMKNr+r݈dEF@`sJFPIʻ57Z[y6QI y|̧?U݉L< +X'J ů_JK+w&s,G1ԺJ"ZpIB=N v'qFJRI""q1EM#ݜAyJ<΋$\2D"C_&3v(<\62Pџz٧/YQ+,Fb:q{eqit kDD( +1heiRӁR +'*$$b !!KjXEP( + DFDɱB[!8^i@1#s1I CD+Kx!߲5"`+DO$XOD}t&0QH)}F*5}s)6S4^P8(XP3 LR,fJن!whcS~ ;T⏮vĽESAatn_Ӆ:6 A8d*d{DSSQ~͌,},C;Xb2Qh3F$=UH`3դV|uv%m {jBQQhF`VĖ^H*61^$T#FNȡ.FiTKUeu"eq:P5%&!bNjh?أ +UOLi%2]TUTCU(Jt#t[jngULŬ2V:&!q>ahDZ*{^UG A14m~ryv= u!ST ]өn8k< AGy<§>bg3hH) C M^slVMUxU0ּx/7;-dT +ŃR,Ols;X^F;O37+׍p1R&4R6g¾3/-R0Єrl^a6]_f4JtPE0}6T~D }KH~* +24dǟɒ*(:cF`/={UCxsuf4"ʏx5G^Bi%af!atlA '&$2:f "&Ր}{R5uy7Pdb?Qy"ui63 gdf,#YEäoÖfWcFz,_Bw +NN6l4ST3#')^ADT#,F۝H""F $Aha*Su*b\+jՔٴ6N.VQ5YGtuk +Iv^$4vFI񖟬%L#D+~r>SsCOf͘t̙IaBݬX11LgH1ٸ) N7!/"ڻ%kF/0%;@25B _$p/""F$R!;y5}bpsAϠreѯFyc;u=NLD3#)H6~ՔO$GhYaW8*Z{+&愋J$!FBf iGDviTLn~zV56SÚBLv<+\$𑔱4JcoFgA2ieL6B<*!rZ,&j$ !h-LQ 1XZIjƷc1 q7+5!Dn|#bEʇҹ+Ҏ81N?2F\B~4du'J='E1[.*ju UF4ԗʗELQĸbL'E.Rj}E^)vUSIfcB(/I6r'ț+081ŲYjj[ˑնoQ}I)*EExSq)Ѩh\;"-*ӴKf\]+]Þ"˵*m]iiMFb2i$HH! KR O[юr ƖS~Aah + ye4$,"Y9E(Y()%+#KJ<kYk3b"#IB"hQIK25J`Fz$pB!)ٓCaIt-$g+823aتy}4rh'+Mc>3G$N,/ ݱ Aɦ/Y ̈́%q>,sf 1:YErP^eWg#TĈ16yh+^Z[K+|+".FhE)d6ƣ:C-brnw"/trJ΍xe/2/ +K'ɦ$ 9Ņ诉׾f OQlZ58"SX#VI.7Kf J2L2GNCrt̂FHN4Կ5Hm" +t;s$\d /ll5iU)F"fX&lku>&@_$"ءGaA6Y…2)C4`sa&wRmGM-A(=gZ1Y)va`@ +3kR@ Uy`gHdJ 84Tl!l4,]cUׯgA\@n`NQ +|ei U7TաPLHe0!<sRHV KeAsƁbe j[um2 NeɱcN(K9-2^X[zTe.JB;g2 +WoɈeH @nơ]FQ1LA63 Zc2h3 Oӡ+PCM ED:!23ÈgTˆ"02ht*BBv0 x?'Q,4ʍr[)_%4Z}pO(an!a&*oeLCɡʌra ˠev!  N J:Ҹ : eQDxkƒ$qt +NDX +&P(HEj  DE/ d)3t$tU{\ +ӆ: +"DS>҄O dZT=#I(0Ƌ'1{Ah$9T'-y.+P*/Y&tGyN rN_(# _ r 1ˀXP@wB 7Ѫ*М7':cGh_ +3S3 21 2?bX0 C𔩇PY 3*s1lc;"x|D*G<hT5--p:T, t`0PPbˬO$]@(:Iw0OpXUe+RcK#aό?.E 6vNDx Rᕊ +rTe©4 ( cnN2'HHQ"]D(⪤fȃ(.QGD +!)]&PJe`0' +AU  `#!#)ClO$P`+0%AjpR"e*@ANή W{F%h}bho?;U _O彊'bxz q*"$t^Fdd +CLvdPU,2cZB4Zł&Jhbp_GgdӅkzuVE\Q_ kڜ ĒkdgmFi'qҐ:xS܋OUe̾n~^:G"s%~ ;9o>˟Ͽ ~:{"?qUV'zEB `@ +Cbx|4~B aqPPZ  R.?5?*FenItgQl}M(ط`@ׯwiCVз=K~7Pf5n(Ą@V^ QW@0{qGBnT04x>"l/,р)nMrS.`mz9!2l71xnGn?HЉ{w C-*mqtvh9Ц!q°h@@iD3BC{$c@@j_? Rsɟ0WWGL wc-Kxo;opioqp|}BzAdzSU2<1$VNb*l5uђ-ia>YPze|F"޳HG+ NN$&9ç xo1Dӧ0 f+6|k;:q5ʨw}@L(]Ђ7٥&Od +JMZuk;1<~jnd{Prp<3@S{z6UlϙXR-Rk@)7#%w~27<ت)/WRj}0:˭@$z9b17֗ s^+COP a eDN\1R@\#N:Sex+2+iX>6 # :4%JeK80eKuT6a{ip]lzx."=ɔ.zpBG)wсŘ uzLa`ȴZ!b*/8J+7H0Q7FR)g.}0"v27.#B9%:*-C9>M䃑Ğd[a{JU(V?hD@xcX3mR#[9t9%~.8`n3So]5~S@M 7t t1wڒY HF"\!*}uͿeڈ71Ի#ɚP^ ܊1"0wwGSa\-`L!Sn)A{ +[kr*ɆE(YbL@<pJuq&.i~B -@\/)su7^jGBmpq -)c +#,do4XS-~ hգZ*(McN8ty*¨_:DLn9 ~$_Kެyk.UU>qIʞf>qʂ0{|qCb&U޼a;ɴWo5ǐ ?u& +RhŜMa}ΰm?4O fy+}' .2M'<bE{*D.Ǝs[&<(d|ObU͈ ?d["K6 +7uJ|:q*L.SXxiQ ]Yh39F<[5A) iS] ;M6,ʋmn#:r{JPlX2ieO_c|,ZDu.$A|f Z1t09@i7QDy<:d`Y^E*iXacȕ4PC?Efː,Q2X>M8'kxdu3t/J}-P#\K")w@0"(Z"4Uz%e<=Tː(MXZxE誜_8,9&aTҎ^ќ$6stOͰpbnb1m];bvK/~mçQ}]]t.K1pƢ8OitmF oGsMWLŠ溢DC@ca!S\sa<'i5w}L +MJAs 1sp OA n!R)~h0ǒ_&c`W3 mdk +::1T\mv\bi!YQ@^=hShV,e/aYJɑ nb'xu4ߥQda?>Y gNL˺m#(i%fBo 3eJ>>hN"E~LWw냎6r[DA +m&BxD6-2n1 "989*xϞ.JKlR])!dju;M|!1sY1ɟ(U*!9]Z%c?Z90145PE"UBQCnKL\}$ iN&lur>LHQ2j,Z8!B9 n/pPҼ37WV¨ Ɗr8(-9}rfI;By4cGȚ [-,xHiw"hYk4ҽrWz`:֧0h ţ\I3r[ye|YY s qmmm^9!%N4V"ybg6S#嵤,0߼u.VXC26No2IH InCXGfE/(^8ǘ{5 bWv\$Gvo\6-1  +D8 HAv%+Y.$w]Cva=sV jo߆W0[NR o1!LGp 8"O 3:pb UVzIUP5vx΄%/ҼFpAZ^t߷$&Bze=Hz$mP0:vo JJ܈sE'?l:FM}j hNJn_{@T'DsS +6_JRki'ءsjD 0!fREZx!䳤7i£O[#L+Yj6ZUL@r.e GX U? +%:'ܰ+e2.x;V7=$6gDWb8/4NJWWR1=5/e(E04Oa]g<)NBCphlS VV)| |˞6@9@@a.Z]b!A)quQ߅ -,`@p`;1_"o2mr sьea`ڭsþΛdSGh7JbtAZuVo\ e.yj1]d$u+_h[8zӭT4O@f[s|O:(ejCy?At|B@ǚhD۳)vo[%]}@6nkѫ rF}eʷE +\xj}.> PD.|hPbHgӏcЄK~W ׬TЄc|G$5L|~q;8fs '!xEĶJxKd"_0-a=r ]ji/!~4L!1A[T&K,τ_Xb4qJX&t%5!&Jr\f@J Z;!=*)b=הOpYJT5Rb@`'w +%(y'R|GH1!S.IN5fٜ)ax.IBKBM;$O>O $P:1I/i=T]JG_ JN +}a{ DWU$zD+B$v Q˰XaH Dc! )O"Y $v3Ȝ60܃Z=[byu[%P-B#Bb ++)υ8Rcpvv(1]P1oX^0LPΏн@?4E5j$1r.EuhgƲ ȹ{0#0bA0s:^d{\Qbl"ʦEC>,3-]x ]dQES~d)bdjRPRcPh+cq2P{"L_F.MDhq%V!œH!0gyD?c9#4h*"2 r!B~PZ!4D=ipCqn)5 CՈC] 7#YzkH!2^C6j(4٘nl!҆9 6D~!u!oBz vyQm܀FOQ7;6`RKb7}C]qpJBdG2MK8BCq6X HA2Ca¤qAD81$rj+2HsATo>]Q#! _}HbCdCWCxC'f\|@nIX;XM !69 c + +!{@=R'=w \΃V)!G?tjy K?x'Av2EOmx|#APA<\A4DG3D- C;JAxyh;@^,Ӆch@DQB&J7됣P;V&>;6/2$-A uCcyC[@~j + SltLگ`\tbu%|蠛Ԡe 19s *UqOl՗SGcRK}~jV96g HAt> "_C% q4Z]8( %ê$=D}8"pa,0681 . d)",Fhl[a6{#ވ 2 rȺ7*b ׍tcq\D8qcQnk4 LȊFj()Mpkc)ކX:q\(2h@RQDhf@C(i  { 0V ԲF<,aHZDc Р! +BR}1D3>y3t! U@ΐ"~t9I3`%np!$ =("63,%)OfP@1C3jEtx&.|ы0gN#W2)C]PHu2J5&C5A0Fȏ wW#Ctd0@c1$7GnϏL'cdh}1UH#9UHV"=$DW {"K1H~' +I% Ĉ$< cD #aI0\$& SPB&p%NJlcI ,Qu5FD[ CC$( 0i/ܢDo3A Zh/,D7~wy(*Q_f^8J|ʠܼ >K^J,/lK|5rD%p.5.P% jB)Ʌ*%Dž?q!WP.Awqr%,%|'6 HeK+a-LZ:s%T.,DJ-V)-ȑ{(,E'f3Ӳ(,_BX<@9Ʊ@d"OXHJKpX YB,XHKKypF.Q]BK4]a0)W)B9[1Oc"ȦO+F&je"+”fJYAU~LV*Ҭ ­6'6BUD&dG"&}MM5y}|:F Չqv(T݉0BDƧ<ы'(t +O Ma>dLMS`0Rp)MPF")0PTNx.N +b(lA +D(&8IQ֢(cm=(R& +E>Ht(L )H̙n +]"pRʁ"NKP{)#.͘H}>qASʦ̖;qJ:aqQ)D"›aq&! kAX0<|B C0 D ) )bAU,l,X8Xd&#Hɂ0ћ˒"fܭf9? see~( -"m<Ѣ}H--j}IU>:Neŵ@-l,-/N"`h\!(KvSoVocz0ip!6p"z0x`<8u@Nw|]4mJ`@]DK;H /d‹q񂗸nf ֡>z!7֋ԁreϭ}/ҡM}/=ANϡP̯_[/j)HY8_ KDb:0l"&鎃8>88>^88i s0$,p`vah_a|m + oѱbf bT1İ6x~biD TX[0mp3!0jaYYYXoI5[%)dY DFԐ͑d"-&SD)ixr2&# fFÒR&IedVf I3mAiq2m fg}d!3̌hFj`bͨ q)) n2&gI[錡`, pA{FƐ3 +3'Il4}CBC11wD8&dќtZà3r{0`(\fe`4͚_NGVw /0d{Kyb^pD5U.<ºtmL\@0U.Ա5 hp!-8k)KOίh IBVZ5Oeyp&x`=Rb 겪͑^qfW6:7o=WUpߩaqVrVr#Gܼ +& &)A~ n̥›QdjOSM}c)J̈́7R'p= +jj86 Go-zڃpd4tG@n 8 ©  `=m9cC4.טbFjt } 3 u_ãFoT$}17{=1}hoo}0if0oOV(bvq{ePH2{#}((>z3V,|#IU[B}|Wf{ +ă[5Ԕkf{ * Rz2ۃ( ,; ^(&]mw'W{ u&$D3؜yӨ=h)n8 sa"DϮ%{ t[MQDb "Q 3}~}A@2ޙXڠJ4(A` ٙ!!/ji\֮R/DNiT1Ե`BpA,}Ly|M?E!`-9GPs=琂C; &pM HnLU @v=-&I.T2 bg5wt`,e0%˨0WHm $5R04n?H`1(\;S*נ8!T_ Y(B>">A 0܎.MNx +.c697ݎך%Dm<➈{ Utkt 7NmGt Gr~».6K3e: Y@f ++R+4,댬52|-#;`Yh <`c}㘈w>T`&2oF0(譳p_ ):SrPzT]W`hX#"i նwb@tn@ݓ, ٩>tXYI7[W]E1 JTӗ57 $!QI\}4Ha&V? N`&p47s^mw͸7ZVh>K`fQDD%ɀ^lI@WNx~B`;uaj`>9jF1ZOtq1N2bvz\Y +ǰTq`u; @M*~@ڄbup`FS~5bLza5(3gǣ#ɥ0:2L +L#6**Fy3WD;u `c p\Ѭa@@C}]*0Y21l-N*~IFc6 ;` 52'pt3VOY`2#p~)`h}qUyD P2V[40xLl9Pdj)m ~H]?aJC<7/ Re 3t~M)!G^̳::J +.c )Dɷ1Qxqs‹}IGYy}^k +;UML:f&&-qZ^ktR-.篭,xGx${(]Ӥ}hgi,)I犼`VF2I|9 eXX`׬40'ᆺա +G/RAvI, SWIb_?y69@u2+*\p`&uߌ8tDFqé^"8E3jK2#wY+V|vR*z"g ZanpYYW Lynu>3YXD3Y!+)% QNdϬLj+veUVUԬźpU&m0Mo;21z$..t;=sA+lV#PJ?e2r2_D!ȹ#4Gl+3Ct?_^3P7? \-vg?1Y:Ā$c7#g^:5e-~[j٪}<<9PE<;4FA`m\(ǣDN<_:T]>{_-!`_k~\=g&$raƑVw+%ȯC/ 70 h!RS+]-njrg|~Hld.torX^&E{?+"7)7̈h#ާ-*8~* 3Rg0״ td\:!b c1Ũ}EAQ*7fKVk_iNA,>h_K,ŭžw'go9s"FnwDsՆDY_Q xկ0[#ny QdvJfjxEn)IP,Ja'm+{鿴Ҥ/}1J@@)K@gp'qgA?SƅO}>g3z/&8os|& x9-t>q!ⰭÛa|ɜI6 +G2OjΟ W%5ۊM'w",Hc')|Ĥ[Hń;KDbT|BĦ&e|-XAKQ훐lްrwɩoOhĆW Fͧ)(}n5 -i>,~Zݕ>'?,n^Y oʟml\̥@7ˎZp;sz`S㼛I4O;VC(C׆'x(jwIR\7;,`gKUii95}oBeW|6?+R{-">Ȉ/8連-CYa(÷ ZKR^ Q@v|+p F|Bz0湆keU> +sDi}E:{z3~C#y}UC㻦A &:a _#!߽eaw_[sWK%IҬ{SYSӕaiK1iŗ\yuN# eo7XjNԛ-mPv( ۞!`lLuf$&Ģ/D]˙=!zFڳwżpi XFZqo6{hjm6"_n&Oi%ZUәzoVjwlf {uEQm>kNXZJE_W#4=w^g +G`%mW^(n=n|pFj'`wϠuXO@OH"HԔ~/3^/V_-@̺PΗ.O=H0~NzR3=F4j5˟%*XǧJW٨E{@Ȧwj 3Kl-uavSk(}I^NCHO(ME.%=X FZ"5$IC=䷶ =̠?ȡK"hBoȖDz)F\A\Mryx"' +^$3%44J.pqӡEW{y3&9-kݏx~2Ͻݼl8 Ѡ|iħ9ͻ$#gPl^ޖz'1VB Pђ_%YRUbg;W4 7RTzɼ,0dz>{/tͪg}HlYtٴ{wr Z_UVF،^@׺_|1y!--^:9l] lY)~ _i!N[o~ZN&()GjM:^vB@= j÷֘l7<+>**WX](dˍC*|Wb[n0J!D<"*! >7$8_pabԱ"NG9RTd|VLMBowGDIi 0wģ$LFh~l?BȾ,jBr)ť9ɦ@fW܉ty;(ke.RO@CT?u\X{azO _4qyQsDyoRn$鲐xW0v [K!{x03 u~Uǃou/lo@DcmYݻݰvjt\ʱQRyF)}2| rX<}L&ſm{|'Յm/M{ތVXo'}i 74}tn_in's`ΰ*W2[=_{flvw([kGjSvjVL$^-BAqT +}\ո I|e™kP 7U'7[}kۗ==;qBG\˱zXV4MoGk!;Z7MLYV)Ʊ d;w-;D&v8#)=N+K5Ng/{=hpY(u}_劉 e~Awu"Z::u`ztDOeK5}KP +/4ieZYH*qYg[7VnG Iʾ尒9|uh +u:&=Dmc jOOi`^L /,MXudԓ0zU4k8BZf.+5 hd>99^d@*aɇ2r\Oo\-اO׷[{jŵhX`M/"d5xDGwiK?\pIvx{7q?Or/B~lLW%U)t i!)8^vPO] }*;2:c(rrR8_sj,̙Do==Ôc􎍏G sA*?ٲSB%^K{f]M9CSziL` ͕G {F+n%# Ś%{`s0+m0kos6C$tGm;[znН6b!Wk;U{Lx:Ypf5@WTe:Tzk(?k6z'*@HqHQq3RC#&dWt *ĒQt,9gTAGw&|f7αFC0ʕl>{>g]oK|B5n}>fl +Sќ|UDp0WzS +;:0g GQwy_C̳> ro+Ϗl~_W񗧝( UUqx*\>C0Gc_8v{I t;e5uh~<6(YΏzPc.?^ +RN@^s OK;BO4az!IFȝ &].wLj$Q2rn} &r s?^I rQmV%?@G& &`+N߽BԈ(~R#QV+[,@AS(AK{ $- +rn[AT^XhLX7! 7E;!o˨iƻNrϹaNl-'m4W =;'&pw /Ί9Q9qHy8M +{\LY2q{Æ#fE?}X^VxZ0p<>UxGA۸3{mO x D#:g)GGu@Fb-sJT8AlEqzB'W^ÈQ|w}:,g4,H&7[/LJ(#a)X2zT:qvNtpoX(L,ħ_ KǐGM3fAU ɬC|xy~U!pW nԿq>ǒVy:{㉰+*-˖NFp +38bwea!|33 ufO+w\ +`R~µ|%|h4L"rznkfOaRT.5\ ֹk0+/MVdMy%yRp+HIp|l[6@qAD2Lv !f.q 8eD0|;{p7CWV4tIv- +V1t/4_G%킑*M %7#U߹R k5f9`>CUVh&|a럺6LW%T7InmwAqYUBL}_v^o5l?囉w|>PI[ô}I ,~FyNS}QIH=ٚ}4n<(;B,J{z4¦,X[9HH=\4oOL 0:"!3‰y/ۤxank*N&MPW2z7jE&]2ǩ'+]v\ `ڽdA&wn^~1符ux6WT3 G2v~wenE$tO=F4nxZ^XNDD5Ҷ>3=E'[tl!z)XY&F +(c_S~ļ/[AԔ_{};O_ðQTLr#D65ARA[uh -^<;MON9_m/5'&@뮹 ##6u,%@56S1}.g!و e ΔegU܍= ŇKHuZc"a6z#Դ&/܏{Dky7*P5@ߍL(Au UHYAj!k&64Kb 큁7(^:άVDԫZiBegWk1œn_WS3 7Z͠Dǀsk\!-9TڦU}Gqւ[T-y)G/TV[1<#TcQFu~L@TkSӸxҍ کɾ3eM]Sg"SUdO#)f`OL&npH+[F2NG]WU65lnZM7Ϧ"Q[I PNJZl P@MF4r-B k+1qH`6+BMQRA]=u +i/;cb +,)C9)GoOo@iF$1Vb:s +P;O5OjPu'To J5 l.Zy)l原^1%VKo=g5Uv4vP̉@- +>K a aZPӫnp`v b- +tz {^4ڠ4B@&5)+C23L`OsqGdΓo9B˥W&aF0RޜX t.Ů-N>:MGYI/*hN(n`AE6p-2,)3No( >7kSv Cqz6/mɳijUb3s)N`!Luk)_d:za!hI\>L ڸǭץξ4ou4]ƹxZ+Nǥ}YZ4cd 0lI@B3Hox9 R^:Eݼtf~un_-t&^HCgMALZs+i+l,]I:ФdH ֓9FԢzÇ?%.J-Rf*]ǝY>O!-7_4};U꽔7Eu 81eE+PbZ +IQc6#2tYJS,XIM8VjC,/qV+IDbfg +W+-09W[uAuRΘbOP5k9 5WUfwC#uhQF{k [0QURHi˵@K~J%,ǚ:c^:2sx<8,C5jTE܆-d'TMU*Ud׬:Uv^U 9VlguZմMWe%tѶ/IUc}e +^]y? 5ܱbIVpWcٻ{fՓ;U:~˨Z߱7VDȶ:֯[UFK~JWn-moMږDk+DKzWn-MSPMR:>stream +y^pbomk8 GuoMĎ['Ḙ>)UtFk#cނzs&m{oQ.%J+MD>%\aE 4pM-5qcgwxڜaSF XBH4\l. `TDŽxxr'vtᵢKל;T?];DP911UˮܸՂn@}s!jҝ⻣ :AT*#_8Q5 ZV*J/]ؔWV]3?bRAsUѽ,d5ToІSB;K0BNIBq8?-I/V1Bf]@$PK5ijy3K/|eY1($k'y:o 좦pKx=-~ Xd3"X+'XR }JBQXVG@w;3?sT<;2¥U%_BN)>!DxbykoPLZb9Qᠩ*:,:nhw5<́&7Y%$;b{Fk7`MFb5etNu"щ +ZU (_5Y7#}P/ +;ȑ|fi2Vp"3n-Ŕ1a$ok,qqRmGmyHrƎXt jtnAs.ՅƨznDm{^vJZݠ;x]z9Lf ./%;"j=,ě@jq[4sB gcѫ̱ U7~.姦 Tׁ1m2+%M,cqxąiU[,ҨDT~5A96Û7ývUrL6SPp<]2]88礰K7 +W0_îfT ('S#ueS`'P%<8NPܘtQ۸nwԓ3l*ϝxm= JɘI(usNw?amt}h6W~ᑄi񽓪;\q0'ca)KCr}Ox!M8ZUcl9 |`R^oW7 HXDx&RgtAKcգ5}IFP k}c(_틝e M}q4:~11Gw^҇ Fl-7b]RC';-Ê.851`A{c{/>`#b2gl0JHXdUMN4 Q*"WsƎHȸi1*[)!KkWӆ5?Wa}_[+Y 7`~2u=7&;t:Q|fCaol 4hkC wagő:ڬ7\stĉh0Xp%j] :j\JzְZdiСڱp5LKƌAKl9187vc︆q@Xt"W=sC|s͞wtzLn٤AsV~\.0˥AX%80VAEf M~ǯ~GT"GԐllcI(I$9sN)nE1d,"L_]?˼&YUЬ)l,K9Lf>87>כ0\-qGcf0n2^9g +dϙNF, \ΉQq? ѧF Zw3iP. &4KϚժu1 c*qʟxv ~&\5+~-R)SY(8p<8 4Z.~+c(8WNanN68Ub]{C~RrۄVRlw4\V˹ {z`U +3pÂ6.mkf~'] DSXvuQY`y} cAC9 Ƚ@b07~aҖ!V} hAk`6g4!jZsŃΌM8O0u%vB Oh=0SfW, Sp@{BtDS9)焮P3;mJXY=&ۄ*(&jqt(v/kJB鱱jVFBcgY,ڸԔIhC"Y1{r$GhAI㖱(64|$Xע q\jeC˛a^w^! 0[+@trH4:. U}}ݺr]my=J.2[49$dN><:{ԁv4r% w/eDzC閔h#i>դ% 6G( Z!c1BӘr:T)y?9E4-GNfzq j{@8F]p:#Jލ!>L~)eaPA9eSTC`Vs筮zEk^&uwOYKFP1p1uXO" F (Z]Nѵ:m\u6#cX_ Ms;䜸v &!as b[\<{Vl[NcK]{£خ_G6ws5Hv^1]嵟B l :h`Vfb+g +B3F|{ӨacܸLg~ڸY y}=*7*Q̖߶N&m3yOЛmms܆$i&X}dc΂Hn@a\i`"0?)ỳֺAv۵`I +d:+/ͨ4NRKKv[  DKVq`5Dt)nmd(‰m `bOr]kޛ.Yջ{C):BQ0 +H3TynF0v}c̼y})Eyun/{&vtzԷe};4 PH:m};M("jO|TOM=,=@ml1iw9#vuj Ċx{}؂$vUO!Ivp)7)v3;ޑHmmZ蠸u2@L[]5fA?dM0cR,-޾jˆrHmh: 6 D؜t{=")R1fOӃts'|hGg] nY&vFbfp9vv&H[@uW&:LDެA}i +5zE}\-08;^k{գrڦ\pvn٧Vi">\ctohטm&mhh +~fJxQr} IÈbtKvQiEh6:uݤUpX}V`f>"bf[1yk$k8Vq^3ۓ+rmP~kFb&n s|h7kVyabϠ\-twi z{{[Ʒ0,mҨb$Кw{6,z#mdfwn{çLV)#uwK"%UgĻDġj$Jv"}h1N~p7]OilZ;?!rQ9"ҷ7\\/xN9yOj0gty?07>nST( + )i'jo{7% @>T<*o vHY~7{V[K{PeD/B٨l?-/2@N^z+lBRc2\VE2io`/Kҡ5.|% r>ތ +<"/hf'=۟9N45L/!>wi6i$EKW$n$jp36\Žn:F8hAOC}*M66@ abG [<8<_rAy86>d$zӱ4- .RqSw>.J!=9O X#QײaysjrD|~$rfכ-9QЬ\74Q]d+CwO?\݅(ΏJ@i.f!c+ݔSs %љvͶ<$BBMx$:81d?ʁ6YYk~%WSk +1%> C~ $3cjpKC|)]剜e5Wġĩ$px`b O؛Iii,Ȧ&PnZ҈;̳S6I5 Oh􁆔D9fzeo٭C& +c]/W .%]QVwΫ_@)cQb|VKg/?}xz@E^kC' jސw'f GK(]immd<[}Uc—R9fm uRhDPn!Y{<ğ/t=1!T)~6WPt?'GA_xjQXͮP^ߖ{@A9(~x~A*ZǗMڐ1]t̯ߑ7fG@'*I rnBRV-ׯ\'3$0GvAt4Oqo;_)IS%//iҪĈ?۶Ƒ'L7՝Y~JՃp >?: ׊o$l7sǐd?դrۏdt +9 D_4?r]")Z/ }l2cɇ0%`C"#B"_VSvǞPL+in9sc N/&^({t8ad-R*5(o L @b78] ,sɀNeS!j<u Te *@_*^ `39D$hK6-Ɨs. *p=Q4A>aQg`7AT.Cg6Ѷ:FOr@* /;)gV ~BIB3r*6:ѧ{JNC'N\C~VbKvDF<ၺP"W]h*ȕD+5eRjmlstxD%1S >r )".Zc,wVXJ;ѻ뚲)xSt񢔟62^GZ𲗲л{pmS7# (H +9jk &v[٢y\h k믿SVO/1-6U3B Ôʐix ͉~:QÇ(6{aW99K#Uؤ)OJnۻ~TZn ծGԶZ +F֕)V+TVh tT i I?lﷶIÂkhFz#>Omk{U9_1ܦiS$:,&FUHF +3n1L<-TGN% H±S!{[KJx9O:'_RІӧmոP:e83sqh@nj-{t[Q>@bb7iFggID*=31-T+!i}hЋ=z}đ 6:4ز&TV9Fe}vҘ?zՔsx N{ B4м&lNm |PޢĤ50H8.MLP + X蟩 {]T\p*hnI4Bc@nwxT$y֜Y0ZYezaEl' 1*g w ;0.;W%'DdW~gp3jNDsS.\Fc^Xq/ si$i5^ + XBL,e׵Ɲ{SB{5`eB)#>\KEcӾ)bIgwL5/c!H$D8<޷r:3 {׃~1 ;1aE\TUr l2=xj&H%-D%KTX_U[7_)oUّʔkp"yc^'vwJpv2_@ٍj xAނo*W9=_iCo#RG- kPil5+l6Y]WS"KHG)E0ҮݦX.!үN%cKs_V'QF!*[N妤tIdrZEdP:l0w!m9; xh`fZ1'ԝȐֵ08ĿTfiÝa qRU@)s'xh%yTY'/Ǫ 갸HsQ1erS>Бm4r(- .ITÓg43?U Sr.0āU;TN NFnuE5ʪ\{K}@gB#r'/Lg7(V(P{=][ V{ ¨ +Tih%U;XEKvNxipǛ"#`ʱM"ꢘ0Ch854Zi8'σ1(L5 +Mʺؔz)H7 +SAO&v(۸V@@$im [TF E4H_~I@4xy + +Hv5vs3:% +:n⃫rb}%, 9\fOKrNѣj@&\IoQ2ki`dU:*r}|~Ez#9!GDzXfZ-Z[@ySYMoΚܷMfL<|!㴈[dufEÓ#^VcVoPvYiw}0$R&hzMګ kNHq, hEGs/Vlgbeq= on(=-\OhM2 +V tA~5Ƹ[F( bW@^VлjRY7  M.InZz-ݩԔ &jxaɭtH6a},ȕ(?bV^y贎7+ɿi|Jlh ?P4d*DW4u։]wv&]) 0Hc=ӻ-Hǵӈt}wF%aP@ϑm2Gސ8𐳾cy +~0wч>M©$SAԪ30d_|3۵&Ks<r,p+Bd0yxrnP9' ŒGCb$0QM+ +v>QKذ$N`!yJP lKmv$6 b 2Ce AKg +_[Ǖ~D{l^fY4D9\ ?/ q-2NWaIग़?E |Bd8ǐ,/z+LUktpm8#&;xW5Lk:eT>*;"~]G8 g#?b9^pO?s~q\zKRdG p̲[ +l"$#xyXLZT +Kn)-a]b}CQ~\KZ?<:)%ĥZ4~A:m(Zw=!"Agg/Cn^˜S_̸qV&Ѝ1pk5nLZWQ.oI0kqt8Qf`~u'[Ѹ 3< +F`=b7z$6{Qy܃qs8 ]#w5p 6m7d3{"iP}?sB,] 7!q˶ ~M`ܿ=Gq{+&Bafq=#V>~7S)_*66sm؍!`} p-. VTgC@gϟxzt J@z%i1|kvLZmSg7~ ~W0{gK^ TJ@G B99{3}ǒPXX9<3}o7V;ۀpA܆P?}wԊz«(Jz;Jh:>h2m~oJMR-$k+uq] cĚ!QOGxLpq>}F,sb!}1x}~bs y;v3 ӗֵ!O k QRU3zD߳wC2<|ϭI7?}wi3su&7`&3wxzD @~A/eBN$}cp*NB >|^R^ѦQY3`hWɰknżo)L_ +)~ +Fɇ}?OqsޞF<$_cx鶂 NJ[PGikilU0*$OqͻЕu_hrO`[8+ĕJf/vû68Ȓ+ Yg講R"MoB=K`|3u{;9u5VšI-$cahR2el3zg++3$m,tN +GDjhDQ0G"`:DZ,}!$Z3Ŀ8_Sq~{r"JBi0_-̋,=|g9DP$ n)@HŢz!Y#}.l\]k=Nhg2}o5OqC@~6rv#ib<!3nѮ̮ [QgIbģJ1iKCߏcXwۈ"AH( g_帬J oDzF@LVGgwyjM(B;KMh Ew؝4`h/R*"o(J;A ;I4ow!(yGGІxCJKH?/uB>:uyK{pQ1^zHO?g\sP&Ϥ^.) [Ϣާ1ynv EB<<e8 u >#VryU2k4 bp12.Ф`MJj,ˀ39 op¼oI4x y ? +W b1Ë,sC完~#_ aQ4n7ܝG;_Sfؑ/zgܧqvcԻ0fAĻc96Fsn1=gM~ ᛿8?vإ/qet^=fл^\ cf.XV-^7`1d:E$}K;D3ŭbX[@>"‘[5VBt6 +%JžI[QZfZgj<5zhS1ycЬh2,FۄC8odHY;8( +ONi LL5yE3~o6#ߝţ$>PH49 MCy$ >4I0g!Ha'>x!_nC(elC>~'{I/Oh87$y4څg3XYK 2:yo,v/7̥۽CՏJ+!f6&*5,ay'&y$pi|OcC~2k̞ ×W}#ch4%.wƶ[=g646Lg /w܂:W1:|lej:(+zίIöKwSs0܍]3˹0{X:u˹0~e]l^6I%#/Фq5Z0&"w󼎢7`JƗN8 wJOI43q\YwJ1|n=|6N!^=f:uh'-,Aǘt5D9dP}r?jT>Cos?{ Pei<{$$8?|͗ԭ1ym -krx}D@'/orH>/{͍.8%a>7v~P y;6.myԻjHV4.k;^Ժ@EIr@nUy(AH:G0Y;u8dGsgڼߚtmo8᜗k]Ⱥ/yfYw V1l{+ms ~g-VS:x6~Kԭ/pf]L}qCwyz) o%? a V(}o 6|ƾ{˹/xd5 WFa-eX [WQ6z7c:[fSipM3v8;'MzǾ&LM9V4ov G}KCs<^ZgHg3d}7/f7a;GЮa͟_QC_9Izpn{<{>g|?8&}8_'gzP D9hmzȾ.iP% ~I8F0IAԻms^F8$1|j4#_%#?IUm5vs6߹c0qm>y!2k6Ŷo~ncfD;g!ظfuqhAk}~J?6m`ţD8?|^GQIĻH$z=<+9tv#]|ǰoo[0웽mY=z_] +~I?÷u+Ҁjp܍#7⹻QG`/2}ou [XwX%P"=eHw 5qF9w'gV +-4p76 ?|*mshqv>|wh2a#W L㯀oB=>}8M_s768Ϣޗ[.mevOM"@G14=z X2ZLdDJR( >.p۽۰GjH`= x$H }#nBYKhB9N17ۆP Q +&UDţoZE:w/yyr=\}<͌v#qt?(YDSwl<5:|q +l=߳{|ͳg$yq!;y^7"3iovΡq@}eSpFŸd1y<ivbMP )e +F?)xu +P%,&m&o(7ET:D'mDo5] +!L?t96 g"5LdHF>ho +鼎 K8haރq +N"'đ4x {r#L0tj0%DC}I?\r ou7 ܭhD}"Ϳ( 79<Ex&A}Q&!~ Ɖ$ ǾsTȇ|8/w*Mč6s5BgV4]m#~68U5|#4%?Ka2/4o7LZ TJ~$o%Aaf=yY䳇.=f9l^@8&Ζkicrz%~3k bVo[6WƯ2zkAɑ.mRg6߈Gϣi@:&p)B~9x"} :VBTE +̝V+"o7"z"7a; TR@N/g8$K?²Qy+hLХ={>(39uym%܉?&D߇<dهMyFn<7w^ SfD< C}HĥJ<~$q'Di#q&C\)(37ğ>O^M#ɇ3lwϢG Fo >{x !OxQDDZ4oǑ/=7_F8 Xu&̆ }l> 6tu|LZ]~>{LgߌlƺiJ>ϣor7L_B.uޮ Y̳inu ZMz7E"Fuc5/v̡X]˻0whtLߚBš8` Yݷ_]= Cvoqwv砇ceW.6r%v7߳ou :b/49(?gq~q1]CgߌjúhY~oJEJS*oJG`5sߨs?*u +G_\_Pө!ntŝXɹoJMIb14}=߄> >{9Pdcy7  :0N_3m<]=g 8vf0߄|Ρ)svx ޚcouhR0:= <(T*"s(yf!^9A}F'mx~RY@{_fﭖs{ƾ\}@:PizP?>(Rc>; ؗ6rxO]$هۈ Yeù.ux׆gl:r k9㊹3 ]3k& cj=}[K#f}*n7m-j~kI^2\}s(W2iYsk۽X ͍Sil9oŬ;m/8`%;yX8uۗ] )DoWV|rFgD[ 4>]#8w<&87PjDßG1 ضu˹7vu$|h!r,Tï4 +y8 K?iw:MI`dsWuL 0YZ↏Jj +@ a$]V +0{LF-A"ZdR/PKU4{Tn",RCaOI!B9[55 ZpAjGJ769V/w@)C O+ C8<(DB$VGR9K/CӃ\p:QDݨ<Bo +SQ^4ӋO?A]KG svu@I +pW91dGddR0 J@41>:d0^qhRLgWR Uf&A)˭a6n: +9,")a)B Rpȋ?#5&ԘKcOɌI$7(R"LDrrɟf)l$m0EmQG<(iyHE#^R&9JWƟ(;e})J+^F'B*XI F'Q%Q|P B 8UҏI">(ܠ#OI뤟FF\Z +D2HRIi&o-uhH#I[zs=^_vN|sQx؂Tؙ1erdw`ؕ0 9 dv+@n~[8xԌD!^BY_DQ_<;%FWeTb{pׯ%n$/p>"8$2$/r3,tW|d*TnKT47e*qd$sD%aKT$HG'/w\1krN$'}4r)" +hhTɞek"Oc+gR>_7n6+TK#KDbn 35%K)a툋 %nJBE4˜  + c꺓uq L(U>fn9J (j7]h&IY[րQ1]qYw]AJ3Kx9@6.Ed*YlX2Wq*4 D3D8U,sL) yˬ̨ KE"X +j'z`"Jɛ)fB\a"F` %Ţb Ƥ>w$N_["㾥OZxdhCNxɒՂ"l-I?&`d=rNQ= RCo69|1I/h- ݵ kv'LjG%SpW+^Q/9ĊW!&͊zb˕["[\RNȖ|ߐϢШנȸuFdz=>!eC|BtxLx|W^䱤OI¦٨oKXx?n)SR:WX@Eƽ]'H$\B1HW+xxXP6Tv>9,}\eي'[b'Z͘(%7,cp0Uj4xbH*EBĵ@. WLo.+)t*5ؒT?dWy\xec=:Zt )a6/Qn"(Vn `[ci1z) bΰ@.6#9_ k|@p97 =Bkk`U1(m7WRzU+B"lonj2A!Vc.C.53Pt9wm5D}"I&$nD?Qq))*/nv#h9HQoZ_ҁ[J@JcJN<&1<ɋ/AqMPcW@aN>ztY86FwJE> ƏI觏 \H \Pt\Gm GBs!ȩH 29#.qH`Ң^ =ܸ Xb'>`RpУl8\t/\eb#bRJ5j +L W=, SJؑR:kuPH?O V3`8ȧ=Ah-UBapHpZ!A&لWѺgRQr\v+,Tqɿ܁N;e활|Z:2mQ1nIF$[LTs9DG#"+"' J9O혤>&i;ʉL ?3)a{:&+B"INxIo39adS^X0JRʝ}~ZWxrJSpBJcळzl||^I$>ukpM+uӇ; şu!PbϲIQ^C SVk.'ҹ[ H'պ9Kp%rA9'BYG(JG(JWh"jsY)_OTo(X7,oDB,t/ (1iz"r#o[If gȪ}kA=#hD_Rr@9S("كw)6e=nwdFJqsaI/:{ZWRgԠAE `V@0W^JP C7qUȜp K +^ >=܆O$ sדUBRZ3ģϞV-&2Rr)FO +H04 F@W@Q" }p :!gS@ +<0CGqIU~bMIbtJ P<"Ө:u<{ sq +J=H2o3luc.se[D;r cƕlD gjpdaźpA)h iA>}U2,+7֍KҨ϶VqtgL0 SLi'6G F +>,lx N4z=O3W(MM: FĢdZ M$O1n±Y;@,nE:vXc% )JPLbBҖJ1i/ tPG`|RAG`CD)IN>* 8ׄxn.DbRTZl` +<0H/ˍ:2+&߀n<CX B,e.R_ @|_p͆;mqR0; +oJ<{x͟) C)ڱ" :x2yp SV̙/rf Y=#vߌ<-f1K闠dt.re]=5.&`MòZ*L?Tl@@;멝ci7o$:^< +] +>{`x+(hg]PN ĢO38)S*&[D:"j3>{cZvT6*&oĴ $cOi?.&Ӫa_ЬDH,/uB'iq`͠ģwahmĸf,G/=n֍N +HJIOP"Zcड>4l J?/T }?jPZ$5SIi]%#V`>pJƋ43lwvqKKJ8lP:7&z(*Eq]dKT)$F~KɧWKcfyQg/@H9A5WM +KOCi߼(= :0NRPA7t` N8zos?~rr/g::BgVmϻaBWiY5>g$P#QlP?[*FddE6mȊnF 3H(d)8w+}dA K4`JJ1ziv`XRj[МN(᭠ŢUf~hO0iG0Yor!Y62Itj+ҨQˁ De#֮PVRMġR}n,˾z'F2U*%P"k>F[MPg:+}csn7;|lMt)8N^jSHBJgz"TX ›6Păsw9=|6v(dz+]:<6?א|+H緒A{t >Ϣҧ/I?zHP(sRĜiM,ē:^?*D"eai_؜`XP!o#O |8b!gmIyP R's#@ [NwaV~h\<~I>Fomƺc-eKi{&jϿzڿ͈Ƚ,'0}iݠLFeMA unJ>|8:ɿ1i'0dўAI?@9?0Giu|S٨! ')-z:>kTDLcj?oC(@#n3!< & sbsuxO(k֕!lqvIupecz FBi*vSD{)l+Hh/HxWɰt`pB;SW#xwY))12 ADj>E^>=be"6zq}1zlh؋UkOξMʃg*@|KܙyeǾ)΍6r#n]aC`aqe[q#GP6r*}ò6jmuwIg@HO@FLg6Y;QHBJcZ} +"Rk +}ɨ@!. BC}`QykмrMGi:Fo::$pN8ByRDz5ę< s(VX'z˹E[O[gm蜟>,?~^Iu\g 1@9Ü$ i4|-A܅$ΦP!9bYOJx?y@ĵ22m*Em#Hot9_ܟكa`j73:f/͞k5pGqݝu@Bf-dv6u 4K{] KN;)4> Ě:@o:MKmKv4E.pdu˙}3}n"Ϳ΍2wkP{9 nFg43nK;36-g6}00l=ypZ]Ybcg/vg=wpJp8a{G! +*sw Y +њ9B8E&#N7pzx#Vs%p̝=*I`ًÛHS\}]'D_کcj&=iWҹǒoR~DbC]%&!K}@5Ѧd& +)k+, +nUYn]G"Rz ZjS(2ZKHR:X]6 iSo~i({irlifJ>/AҏKy/wlۚ:F\wܩuh8\5<е@^*~i,#۪VNlֻc^0wlݦvVv<.r)(=2-TqB [EsB6ks!M"kzlvǨێtAbx$(|90'^ˇh7jŤVdkrt΢xW' NJ(] +Ţm$Jhψ5;`ʸA͸n-VY_ʶض:λYH-$p:M!QQ!1KmH3u8\TS00(8,g( "NG>fLg uBvԋIf, ]$q-tIX''UD?*~(7Q%acإF`<ccCyH{plw\/kB^AJjбZ1oɺL%ۅb0ibJaU!c؅0ܴ&.XՎOFhRF,ZD:iT \aTB>?w|E- !r0WvjcmL~뵔Jzkihb湥l;ch9o#uJ X0h5XWǺ: kCpBz4au?Q} +AU7ǝ1SJS;.N&gBy jAƚfcG;x>@6*6BSJvܫbp6 +eh7;D &E5e{:Z?:/@yWe +N#i#p}Ciq3֚Ji-eNRIᚧc-nuwޑ̣L;z&6cFC1da48.xv:ʭ՘ذ̨͠WR]&&1&!V͙4YoEQjrj?8:?89K0mƻFGXʥ7 @k%{T^@v%T`F0 _v ]O,;Jb4*La2۱"QOV4q ο%mˡkp-h!VYe3{ XQxۃ8րGB) # ))cU6 Vb-3\AZaU&vmmGZHr8 R<r{y, +O2K.(o>9Bv}@:q1|i4vlksh%Z:W +/z^nLb6ƍtv C?bЕp&!)cXyCPUbԄ=@ގ7 J#k2\A CDlWRlNjeUdйjؕ] +Wpx+o#k%S=Vi S:p!"t%!] + $};g6k!2VݵL h-[7Z쥯 `O.(vBvuWw ˁL0 r 1((nC^s/5ix1ԫscR1qTT`N0@kA-*f"8~+:4J3'a=)X@gRQ9oZs+9t3Eȗp!K{FյS:VRhQb<|]OϦX1J +ZKS1Lpac,?@0 +e`Z`b9 jc +@BȨݣQ1]Y88#mO + ;FNG:&ߜq٥`i7 Liv- şmBH}5a2eaHlDZXxgv0  2t+ +z[п֥/X6aL!WHUe8te!fRwʮ&[ 3KpRƕԆ$gu%>;"ϕl?^aJ:`IH +ONg( ȿ9G1v8[Np=2@!i/%3ن# +ȏ3Hz ֽ\])-k Z- B@yiR l(l'6ܬaAf e K@y[!UcR6zrw60bVDHt ţa8#% Dz Dt~`d gBS"g:]F g#A[1~;ۺ:ZЍ1|it XXaC ٘KX Ò/rc̫,̫뫌C9K. 60X=|iK.Ka(N) K@r{$tK ˇAR.e`M#87  /| V5bPCn2‡A Q8#"v%HYf@üub.C— ;`3oZw/ /[z }SVϹ]P-gl+7L׽hQA($҆ggav:m*0l+/ W\.cXyY\U\@5p:N: HJ!+@)p(iAi%{#"πFLImJ|E[bɥuX3ok]lkw].W`AN\}!F@X>{i $0+#aM",ŐHǘX%ǂP`5y|B1ؔM Pe$,2.Ēb.}d:mAxYmr1e8aCZ9J^$ZtJpfP:߹zRط+܂Қ*Aʨ"H$?J<:kC{CqQjp^B7jЍ"&RxGFh7pgزe۱ε}m!TrpE|Y]ҵVFC[Le 2! :/݂`'tj3FByˤǝsF`x u᭏Ah弟J$Q{۽"Z)Qc:S.9};<2$U6h/6Z6t-Pǒc%F,:l|3f5r|V%WS,c`1cD2{tetI(# chJ&GoVb!UCGeüt+"~ WOߘM"]AQ._^>%4` +x$Fy%;go01+&f ZCV=B +VFE/)R|9Htf*i~K;| UNՁg +./gQ.T \E /~|ݕ PrA'V ջ .* +e^g ԠϞ\\ +xJ:{q5q˹>H x́n(Y<ݐDQlpPUu EW!8,F/쥍.L1,| X ;F'p@\m _B'_5r`jP!|AY$p$%DVР{)`WAE)##kYcJ(vMa7hc/d ͢6 ;R"%jm Й9c{gYQk@2]B(5@/r_nZAVSHÐHŢ\i(?QXhe$b=l[NV>1bg\*6̪uMw0M8F]㚗j )ĸ΀*M%"j} PG?;tYݝ CWϪ‘Q($tRfe0 *2`e`)]Q edD—A8-?:\Zk+Wy +WUǖk@Z_ie|e4dT*:q˜3C'ntnSJ;= l@0\P*WqaH:Y6 \9p'Z$sMWce!`,tK+vo|@e:񨡬,YvZ_yym a0 jt .xYbr0((R4A:yrXS&4Ӌd|:OX,4!%[JD + {l3eHA|Q%ML=hDM%I׬d[}[9X?x1"@ S@.>%'kU8tY(`jtjɰjqkt PPN[-np0<8lS.%u4ҋ\~\q +ce`,݈UPx3\f c po ft)>JP:$Š\1|8j)^c {Y +-+KC UŅ#ln,YANkU7-"$hT* ق*ǘTfS\U :̠Rݮ.%Թ:!%a;LZ"n(h!޽ ]ۤX`yr__9 ח]~W+CH^!)(lBTE d253;n,^ݼ6^8AE_*T19^R 2k 2DuQ7=fǧ ksGPp,=\+}Z( 3|%8 /_+4[#/=Ebv#OW>@|Vȅ[<@#̰Ӻ"SD! (Рlc bRC +^`Uu QK0=] sllNW.[рI9Ċ$F˜<}q*LAʹDD4M#r!^ PUY*XPUT^ueEU笥#Tz zUtԫ|TQ1R=H=MѰ}-/u:A=Ʌĸt8B>:)H( W2V} ZCXWZ4Zho052Fde% CM_膕d*ߨPTRIej^'3? +)ѮTsAu7*F u +BH]BQ@tԵDD];dԱPP4-(u +MD}SJZ nz֧t.t dO PFB +A@tH"Q> juۘ"цgeEEV%kqV9U92$W.$X.@M u LB}Hw5u0FݞJ9URk*^MDPC?Nm`~?joJ,>BA |hɿ!O,}$S<#%D a8V(X0P!!VZTwb`MeR7Z]zOPWәn~:O?`3ԙhztӕd~~`F/9GP@a IR=a025j8"jXPE/(-kEy}qHm+a -(0YžMu-TJ\HoR[pb MOS:gJ]BQ! lE#ԏ?]sz֧3u!-!zPeI$O`&@l6$њ "C&L8++q%EoRg˭ -CN:a^EeTEou-QP n>C؜J.?ŧa}zKO'`7,pP6brQH"5 &!MJ 8mm9JɦNr0 \d=V6L[QH-iV}+Ak`zԑGC!Z@B=''R=B] Qw1D1At..=FұcWr0:L?>X`,_O. u,11 $|`6|$RXB Z +X.#_]s@CQQaJC}~u8+}@B(.aȨ7!P5D} uj^f:3TFiՁ&a`00 m,Qy#c30e LE641!aT~a<sPYBOzEuQԹXQ b:QpDԭQ)`:Oߡ525]l5PUnbҰG&60;JpqSgNq/L`2Ҙ pKZDoư(~hS(@ƧCu%n==tq O_:Ӹh@*py~ +ybrT +*yGR'6^M8d&4Q"s$GJP .a4TԫnzOt)@O$Գ|^3ԝfx:L2OOJ1ig9he d7L/:F-8\`H=\E q~u 8㳰FL$t9.5MMXWҍ*#ARЩvh\:M8t^kMvź !Lm+^(qNQAB 8eGI M41j6X<胑s84I]*8%q< -::_\L1eKt~uC]+ #K[A m&5 +H pD Aė;$15fh)CD'XG-26\hidC/.!b T3`;[]n:O_sc_yn\ي]Vr/$a,.9\`uߐIl53' L3flf,җaeK 8Rr)(bHIXПfL/lwii=]tcz-}tF,zOtF*2*$S !K_+.9DtF0lAŌ2PG20 wbsVm$X +V6|e,۠!zڕTg3ԁ:N.yZd:N3`{wlw{キiM7tlw2ݦӋ:T#VA9ǤƤM%|0/"A/B(DR5&Xlj0a$,3XXm ۀYX&* ;41mh^.ڸl,1X<~?TOoM0ݥ.kM4ұtNaK/w O+pΌCE1KOٖ?< 2 pUBsijп& LI)?v,AZIF%K0.<^`@A˃ETL1d9<; nxd +~8(R|IWΕPvŀs릀> +.eVԁ\O8]NoM@/C)h2,XKcu NTV K*Kk65ӦiҢggQiUY3-(Ԕ6[UfʚQeijigQkgY(4*5*,*)+-%+*))V[ʚZR3C.řYQIMmiUUeԪ΢ִVYQgZXVQٳ4mU-6]3۪mXڰF677XJUeeVeEevEJREUeάҢԴkgY-, 漻--K ȴdSdPK.l8 458fyf渜 RxH U L b _V#I{-Q9èg0_"iG AoNC_ 8uwRhoREKin1ޏc߇B8Θ}WLZm9/H)mqJ #Aijm~Rh/^~>|Atx+> Pn#5o*tih$Q۪欽9;:CL o#7CS,,k)-owX Zwc͸%)..Ÿ4j& % L q%Qq1~k ^ޅCT :+ _jOd_m 8bYb4nf/&+ 1L V7t6^R T G!~47Hْ\<7[i_ЬbXK/Vw۹]`ź]pa̳{Y/qe5 dp)S?e-qDA/e"F%đ:yEB"jWbZIV"ڛf\"!OJE#ţЬ^OBR):x F0&SDio:MQ"$ B,jdpQ%&o@}8fO"=:alQ"$o&S1J6 0W:>;0MB ZC@~YS\ҾmUe|]A B"Ǒ/0)qD/O "C4j..xjǒy35:vO D7pZiAzwd2@2y;ءi# q}$KqsJIGlova;Z[場bPR =' +NI6!/e"Wy=Xưj~I@1 d|?|yp!@<~l5vp#K` imx\MgEP7M>$5_ȭD +FTeB%{( ,tQKȨj X:^<"fhW-,q'UC\i3")ʅfβi's +tPY48ORDi/:DZn>"V R;Hہ.&:I+Ig ( ]MgۈlD<$JƮuV'WhU3"$)4I'DH!J¿3D`5kS(.Ccf7a:4/SjRExP}P$8bAZP|{F8#yHY#u~sF?CHħXXY:Ļ[ C FDDk aD<n9ێucȺX5Kg/3:ۂ>@@^qd>tMb֍͚P}I/ngۿ!S0B1B;*~Lwh/yZ@^ + 4oZ=o$g@b1*/ y/uC +;&vx3n3LVp MB9Vz{A9W:* +5&&f]`EP7%4Ջۇ/.(%38i ;@k ŽN +o0@~)s3/tta5xs_6zb V.&/^/VPoF4&pIc"JBj'e3zw7x./?u~&o"Ar6cpnL)wj4|g<z왗g108,3V7|3zr3m%KygYG?P"Im +7[F,הX)_fƶnK=2{l>FohY=|P ~C}` F0zOpL"6{vq#er!G<)Dz5̙< sUũ&m$T˅ЁNFAIu\c rA9Ŗ_ă D5Reč(Ɉ,lp%B~JU=}g~XW32 PE,;[l*cgYD%R7Ȳ/xxx:(@tCuhGoC8a옺4Oqne6~qd;{O^.{:~Mp2V5;.nl>Wɨ7P0oYDϯ +,z$̠܅[-W8t31D:o֘4o<\0m-?V͇u?sa/sfMar^pT..m'DqݷP( U +j$"('̃dhZ4.{ K1{i>(,L'ɿHrKt %ē6 $O1Dٗ<2zl׎K@{2]WFy Ã@#_@ZR_a(/th^Tqi3su7}&XU|ksiȽMÍaji DZ\ +:i}2}oI&zH)ؐOP'SŜۄ/NF" 0^mI\iی>})'}qpø:@FX?u(ZIA>9&p}>6abwkZ{Y*mp . +[ g5m]:ms`a *'9ÈT-.mwV KŪM +D:I>G컊i 4q˛۬sWd)xۄ9"gfȡ.gdK[ncnw/ph9nm lo7&#M1AIw.ԟu7akptx][Ů Y=VkRUÎ?{F}S +uW32k({(hT1AG;H*q E!,iOPZWx%30)7(9\CB {AǛT8\l dRq LF"p.#v|"N4ZD]~c|+goe OFϬI$O1^Gqy?(7oXj`@.w8? _ZG EJ&zz[`B }q.v˅[39qQƶ7)u7*E<;WG֔:·(- 9BJ'C{0U&Uś"gv(6aq[w|x~e_I5$̅(P|H uyr"L$*N;k"97߻;Gw&$[場5 '8ciԻP S(sM|j²ۜD;dsw-mwXǻa.rgc6c:&nޙ<k40&n LHvQvqxKhB:spZ=a^QLH@TDQ%(+VNKH诉 QŷY8M_wi0xh#WB+} ih.>q RHv'6f͞lE vWRB\ H'ͤjh025Z'Q>h;"ڵp(4YK[HErwll[\LuƤģ7dT*6_:;ggqcg)"m1# GLk98.!Zf8.p^GϋQg%#\ɳPfצּCD9߉5>]󕳛Nɹ8)49|y\3G#eK`&!ڍm\Øw# h7(@qԫ-퇹̕m$k xUNaVF$}?ֻ>w!4owQ&gȴxjtV J|)ȩފK W`}Ä }B3;,mq{*hĹnRo! )8x淆:PjGHJPŹ(m&zI +xcج- +o Fv}$ +pdM+s,nVJ!'aҶ1lsή9t<Z@:-WS*uWUo0a/qgD:CUzKd׺I3(htpLd܈230yC1Jg $hxZ:4JA&o VH +3@s7DM@DI1) RXKc8RhLyʾZGicڸ=|_ u* #$X /3ɷ#yW5oK]6^%-g7'&?{7^ ֵ9*jûElv8s^TvtmH O2G0Yi?͟1 VTxL?/IoE߿ģi+ =GYQ"( ;{E"OZHF['!;aȺ>}6%N[5_:hR6 8;vC@SZg1[/y3&t:Hc(&c{A@+yCģ_=&} P!o!H?wFch.L؇/yz}p9/bsc;4Xy ӗ&p,Z Lm shǻHlT.od]fPg0)l+#]5òj{ {CMFg"vwȬʛZS˕JJ7.BM~CbnL [Wu[0,tihb[p]MD A>'Ά;6/5V6fɥqth"NB{(S[[-M>;+gi|\VֽKJaTBU2,kúg% )~xAVzKâ'unǾu +n!xk-TIxOc~wz>? +%=|MmSHG|;ze_7ܝu_θ<|](67nnQ|&5: 5| aX;HU@zD]5sy=8{ ę t\?~R e}=o3PgRC;XG z;a-kݯDm7ؖ÷-rz|v9.R/.Z Y:pm""p!s]d&f>uHn[sn"ސVQ,57&f# PN10g#pN5t> ;3F F-[lAej Ib@Ť90{hsc_=&DM!J*7uc޺.te]ٽpn>J5hcrnϹ_X;|l6ah_:?8:#y7໻+wHbRn, X:Q**#} S͸pDm@~aɩ5Αq%*5^g 1~m FX;80 D ,&m +ML+ù-j~l|hC;pL={5O]0ȿ;R<$H4\ 4quPe QYcٜt[UR]V8Ջ[B(m9wf03 XXA>cݽW@ ]3cJ%Ii'Gpv;bpNÕ]%iL&LDJ8Sn8 ʈ +f4\/+"+{Ɋ:g.!as`zg:o F,z*&UzI~xGxraQ+LI3\ `@nԋI({.wm'サc7:ZꅣO.unL`h6b|k$IJed0@QnC` @Âv*р:xrD!bDVSg@@)pN$/:yw +4/(,Rf(ENig'\ھ 坓XċtW?Ȝp|6'~ 3VQ=q9!cy 9ހlNY_{<Vɖ X8 (F5DJJB&"8˒l}D~Vٚx ViS??ſeM7E᥏[36tfOI֝&AaƼuu.ERT#wIQDU|H!L?e_0o{x_p]x^%տdJ/\S/-) *Z]#Q\2sw;|w;:Zy,esk??&7II959 ~'~գFg}v[jV73=86xk{Y~L 6PY{27X7^Qʶ֝3"Z]W .=wLaQ gRM)q (Vݬk5 !Z#JY?7|ODE*xv@ED{B,3\6) +{}Im܋.Svg]ĞmqoWYٯRYtag-H`I5o#g,1$8bye1=Mye {kp7sT57U2;cfK)`3m~rn}ڨ'{pRL=:+74r*Y9jSu}|~r[nO {!wmze?ҥ{Dc" Qɒ[o%MGg4Ywο"y)OGQ?BTm%ɸώd!żުBs'?3ʱ*vja˺qtbM]{a}|ӫig. ~ʝ֋VaЕ, ޤHveVћkđeKu~ 8o5UoHѶ-vA?Q81ħ;_!WB~Jm~CV7-GܖOr%2u-z}jo|mkqNǭJD$fҵRm%cbrOq8 ODaA< ^ 'Zw9;I2W8> 3V?a#7WG%Ed;OKH7^ U5~gd܉%XJ%EzqDNjRd+$JVXnC`91Srq[|!3Eo5;{sKTϾNJy˻JPN8 2GY0Z,$?oAL@t]0::$e$JWo3> i9 iF6#pf]]6m_ߘ6E5ҩX׏8fA\J=xSE.ÿ́05?h=Vbw\=>2m3}{'>V++'N :v<>%Vhi{l/xӐB9sٷ8D^d+sH3b3O` B;@"j1rq@ KДP2tĻl1Q~^7I(9NP4 =+%26wUxwMyQv% AǢTYOŹEe.hc")\j${O@A(VgZҁ0.կnpJ7HePQ1- 6A#a>tk]E@د5,n)Y +Ioؘ?(oD3kXb +(7Tz.xmz +qJ{,XLEy@>;hBi_|aΡN[g&0SZ+eHWFL>_%5sZ7 "ezko V|ڦ~2Ou&0 0B_c@faؒT1B}|R:XⰡ򻠩\szWRa)*ɀ̴2[W@XV*ȭ xw<,5*H]7f~үPC d +=1PL;el F LC㪵!*dl/$7S= }ĦyAbL:Y ݴ'rmv YMu0ט~2>$JY0}Y&Ͷf~ Nzx 6,x Iu,YXE,=\ÍUe6R02)\IVJ%ξ"P2J&߶+"HayM%$mi$f㞦%5s0&SZyy`iPzs1]%B}ةǀn;+가6ha +|&CF/+ =A +gzx( 6S\ԭz>6b|#Z<~^Y-YrG8>Qި`-Mŀ&ԉ'%am6&B&!иEb]P6Sm(r煃n#́+Qݗy쨹-_poX dw з}^qqJ+oXLLJXhoNhʊV_oqLN JNQTeR y@~SZ/Rl--Mc՝ +g ;݄@'}GC$V R/q 4,WY\x^3&vkA)nkņwx//Ea_+!}7%LJ u{m׌&pL&[W1h.rP,Ҏ bT0ҞL]ǻb77i \E.UrPQC1&k DHkΠ8k7_ה))ն؞ VDOdC3 +g?y*,2]\0>25lhx8]NyM69@ , +}%@=+< M o7.8̙kjI*}z"UR?$wk!m@"at078m gu9-eo\!`'_hc^!t pq$L!f^&9Mߏ4p%c,hl >RRx\ʷãz֕?[N+ !o%_:s2!dڳ$aaWZ RqiI+yZt?-M,Pw'uk['eԆ"{IyyۛȞO ;Z C&Kq#2uJ0k[ c ~Q8[exjok7G%`>D8ߛUVpeY810 +J IaMn306QT՟PV,hCxfMR2[Gk#t^vuBY9l}c7*d[G ä]kL E[ge"v{ +m%~"3]ʃiPMeitT-N~e lhmA?ȴ)?QuCuWy=PY} $̀T&kk*q0h{}PxMo(Fp5`SHy1h`r~/EC,.Fـ,/PשּBq;*{ 5oBRFU{ =vTT%8!UmdӭC\;7.ouBfA',Eb=NkT悄 >#3 @"yR~L6 p# D@G0ܜ)+6oP0 Nlȥ-UXQ1F|~|tϰRw,"lM5P]J5xגl9x9I!IoCG6n:my&'ZӇ|Ķ:X,WHG9c*S0LW4/"@ ҈w~@r肌qL(Є[  J-RE,2Alx8ť3 sH8 {y#ݬ=u\E~zN~+J غ/{[,(Uj2>k]ڈuD=:]v: Sxp2G$kN@g>qY\Dǂ8CSoDQ7$1/imP/f 2Ɏ'|cן~/*WC+zBtw<LZ +p`: +#VgnF$ [ jD\^E^ YQah(~ +`eH71_'K./kW^OٱrYPR.uf䡸i~ԓ?U+@SOݎ&jv[x﫹f*)}P^3zL٠Ku hETȑJ6 X~,A!KHE Ei)ڧ4sTrC>rJ{'2Ls-&wǎ‰0E'b3|{ =Imq2;]Q|JASz=Y9c>u7#~v 肌KvA40RdYN/I1e] t[Cm,n1"QK%l_"uP.s7QK%K p}N:uL:4Ge`<1Ut"{7Nnc70gK7I1j!2Ho&HOR 2S#ީikL͑WB)\:y Õ9-C + +252585ca4-61e5-4651-867a-fe8ec8c67fc102230bf0-acd5-40be-833f9364b67806 695.2523701efca59-9f29-471e-8eb4-1267ea4(afb942da-0174-456b-bac4-46354d7e47685791252537834 D!FG0 g0h (Ls,) +? ܀ +Aث`5tH<049ZH$,-ݟf~kPփ w: kVrLg(d5V(p 8+`N4grZjK_c&N(,g`Rtƚ&ffV; 5G<քرښЋJ`V8Be=b4㞻°RFNp%Xh`"8kcj!9q9"ڴOlC@Vc6M_TtoӜP {%=FDRqzۺ9.=s)ӉZ̙hAohWSQ + ItaLǞt;ـ3N"g5'nLw2iuwnk8!.@=jmW(@B4J(m/Cy T0}6ĠFO#0|֒ؾӗ@ |h4=!0XG0VcTje8G?O 8I<.tFs۽2+,Yyn9Y9|1d k:وgD΁pl[=sKrSƦXm$+G;" PɂDTm mP9/=FeDmbNjWHU[) ]zRׅCDyltvp"NIZmԑ:ehQWV4vZGp^3w҄M|''a+Ifn _ +' aʰjv;nƝMd"&m72b9V+~yV<{yܖ ]P?:Ξ@޼G*&,z} 2O(hx#Q~5ŧBI +Qi :*Hnj7m?kw,"wGl[E䰚*W$Nn֐&XƧ9L҂re>T rԦ7]CC>F{3-{OB]kT[]䎒,DϮ\E"޲|l:t'63L;u _]E;vK<3Y \^4=*9zgnO{ -4B)oEBIKMj^'KKtluM+ޥ.Cqc#Hg5:BÅ_R&2Ÿ"ݧqp18:aőWΛ`vZcfxAn6{Z]|65]Fa3` c〭,ji=.OTeeud`n Z!>^ֈOIg7|vNŖt9j,F޻dJ "Mͫr:)r M;Rd^01ew>/:E봃XWPQ,5٥= 4|E(8gDd&hrUDZԄd(hnZ9jث%F;zlڝinlِ`\of#{NkMӿpɝnqۆ I\D]br-HuD`U2 `eGZiEsČZzgS"ϧ3xEYF}v'u:LLķbm 2 }Rl21]Ithq@`BwIIvџf8'٘7 ׆Id!~P=_TDs=q,ښ_IR35a=O{A2#J+kYLQRR`Tv4yS( +L:~̯ l4X=FuӒGFvC-0n1SD)4n;δo +9iCmuuó&d) \|H`QT5u#$0 J%؂D=+8*eq}P6>?-ZQA_9PALޛ! tK[ h~Qɛ;z&FhS:X …CH2Ρ.u@#(ݜ֞u622|C0E:\FDDDDDDDDDD +WFAء41n j}~T)R +>05 0 +T cqokM+m\3ӽy; +u;}m +#c)quqS"~9rn꿵߿Wksvݖn~gi-oŖʹ睭m8.ּ[Mwj_nq6e֚){47ίoh@^M\9~ZOH@7sٻniLtiorZ7wuۯ_cיb˔mK7MWo[_ۿgjv^}_b;mp>oRn3ڟM? y|[k5jm<_Z_S<[ik8gͽSmk}y? -\R;o;S{vNIZݿn-vu=v'ToM{O\1~ʷ͝ڟok;s_;i=o~[o3iN.ȋӞ;w5ޕm_Z'SOS9s6鶘ӗ?nMy(4ogntOuLmL_kWܦw宾|ykO{7mv۶Fޛ|j[}NmڻMoOoZlbns?9inWkg3ߦޫ<^-~qn+δmS~^&KLRxprM-cNmϼq>@K66f'߶m'鞜oo7SϾm]ݷywkv$ ®T8Kwmkms6gv߻9ۼ>4o8u:I^8pINI8= yȝzkPU +cUEmx{xMB[ݼB-Onm{(ngƴqu{q\6;{N{-pXgNݖm){@:}@JYK3cZ[-s-Ky_s:4oHg\57~)~Ӿa;[x÷n)ί\9vW+4pRyFD)m('vNI9i'E/QORqz[l=m9[W2whP@ 4h:swwvxlg;I;i@Y̘]1tN= L+g Ց+Go@(.a$ +@H{mg̱{Zܭ1޻-3כn]{n4oSW߻X=y;7=W^^7Ys7:ۊ号){[o|Sn1Vs)7s뱥uf-[Zu363j}ﵟ5__wv8qʟ9)ξ5y[m^wn= |Sq}@Prܜhsz*B*O5)0SQN:*;cuLƽ2?l3kLQGϽSoۼhֿLߛ}ߛߛޜ޶Z[1mu6wjo7gεg۬tmvb[9֯6]߻MMmSoo9oSo/to~{ͬ6 MަzۜV1omu_kw@yeNM= {um\iY۟i6vjMkt䥭U]g;2F/V=+YmVM~g6:@upf}@VoN3/vU1zni}@XY6+pxY۶=޺N{wYڹ3znN[ڧލw^o> Y?  B,c7X~-\wk-奄XQ]XOZD90I;i<:멞y=k2 OI;i'vNI;i'vNI;i'vuνfW9TO+Νs> vq_m g/vkIgn1z;}_ySz5vymn6uboұ7gSoo@|ӼͷmMvv\+vil7iݰ&yugsk}׺W~8M(^m4U6|MSa 4ၨ+2ƈ2ު;1@%vH<@7(ud7ϸ4` $ڸ*?yT<b3"B v9`7 X; uSZy<ů <3}(~,o<S+ <kNIn<a鍰o%%z}ȱ+6.R>_v-oqzFjm`b{~z>*ϩと.{u`9#2DO-2̃;~Jp`bD)L'*9k_Ml>QAPi,1T`eT/k1ѕCߜ5jvrWvH~fmodu02rv&bՕ3K^`SR؉.|=X](W`E +"+;^iK2`)>+Tᇢ|<}c7 rpBNՕUI'aNZWmB8)p&|s}vk~w6s=W gvo+s:7뷛n(2 gZmb3Y뵽o}s˿w3UNhjiʱwޫW[]}u5ێ~wθb~uϿ3Vֿoެs^>;/zm~{@P_13[myoyy g ͽ5ݹƚ3+]?߮id8SLhXkn9߬5Uc9s7pfeJmq*LZ[yu8w]o M+[m]+|wmoVg5r}㪵V[nmUXg}׻7r~^۱o55۷:&4yޘWZ?yW[\oju3gBr{k>ẔzW3f|3pfո\mz5ꚷ|/yk++mqڿ7>u7^m}}y;+}rQ=@#QT/3+e[>q3+R~b_Ky>9Bfi6o1$¨H7 / +N9v%|@<d`Fh=8GvW+M # 213V \0b + f(6`;(Hg'Sg=1Rg3uZRSف3:qX96LwVR< :+W&ƋB1beiT6NL]31-e&-a3ק< ۬`TaL;1sVFO5豺9 d*> CS)ϧ`>̳iQdx(%1xHbja(KSGM8ЎXR## q48c5p^p!pZfEذ,Twݍ+C#\/BQL +wo0!Fw1su %FJ4 ζ%L`L= {$48h2Pfl]4732 L9d*Mce6:*CZ%%@Q򀀁Buex"Hl8O&3< PB , o#>61RgU I:B48t8laSFߠT.GvYĮhm*}kQ'CO&mp@zaN]pȕrZʑMsYdrp9PP# <l= Dq\q8t%6rnTSn!ZPa-4: k!X:4;QZ_<%~T;ѷ*c*QDڻQX}@U +kJJ%Cu9$"l((u`2 + !sKFc!dcP2D0` p2@dZ +DM4]p, +`Ŷum# +bM\ Efl{hmsE…z]>w#u L.\.  Gr\dCDJ #FF:X1eU7<Tdrd*I, .2>WF +4<Ѣ'](pᅄO.\LhãdvT$!BU2qBa\T!= +%5*ە<A%))r@ +cPQReb +#(eD8}(%P tה,!hH$8BFQ$h0J*ID͘o#( EeM('\d5_&ЃI2ѤaB&B| +/L"O` <\ܩ+i?52b)m*zh4Ӫr4AlJk19TL42J@p F A7P` (@@ d +h`܅t2Xf^Yh8eչ HfBJZP=  C2<ؕ:I3$dpDHFIqQpP5-HH `ABv/.Ipm$"i%d`4E$Zh` 鰐"Vt,*rLH`Dj(%"#)BZ"(C  ՉtX +@ "a0yFQlh KDEL$IdāI"NE:(RYĬ-ԉ++;q*U2*䈍 |ȶDF= I@Y#6ITT D۠2%!;imb[C&DL[:Jl`|䢓y + 8CHH M)&L{@T $  )l<4$1 Lr%b7(%76CUd E3ƖMhL\2NуL4}*@BՀ LL e" 2&leYmY +`7 +]rdYeP,+YGC%dT"Ye%CBuOa +Oq,"+dW$Yшߠ: +mMhe?K+6"w1m"YV@FwY^7wEbFi\PhH%\Rt0ƖU`|vy㵵TNp1m =K( \>JPxn,dDAkצ5Qi@ϢC2pq64'TP!N>c`Q(%g@:`\`zlAHYD>We Y62 dY9Y,#E$YvD% +(PX4e+bЬAkq6)P'6ŀǎc[dcqc m`0@}2($[4#bS0[]y@`Z.Xu@#rQ-=˲ҡl}~˲ '|,.Y]dY+Ȳ첡!T,[q!eYe>M % Bp/AdYVȘPYeISx$˲-27Ye@e/zleY҇\{@" ,dge +q  #Kdb2IF+'3j/0Xd'C>Yi*1y2: fL_jEִ/ ;XmF5el0^J06t&Ѧ$?nBQ$ +VN :/"4~- +XJ4&L)%re(BRX O2>Xp)P1tI~+qzdl] Ef+#gan1q7XN3&0X+?7Cq1q$%&yno5no@Gk 0IVGHAyЍ!"7(J}%yة®4:?kFoH QX++ Џ݈A`a5v +?p`,x1+CO%RbG PZXK + zƐ)1GPpQ7/qbiUS/ڰ/J/ Jh4o>Ùxǩnnz+=J|`- ,I:Jv(F` +ֽEQ"G Y(JX#lᅴVT~(q=7Ay,F˯D\Q՘J~SYZEUc*FXހ*FeҫX\yHW+HJx>D1}eb$GS1e( uXSFVKs# ZWR^;0^~RK F5U^<0kjLb$-a0:`LSu-0 +#a-JUcQbaGQ5+Eu&'ARHHa^X^HIX($, %aS\"0XQDv +HS%r`0_EMdFGh3-iJ/Ɏ7(? +endstream endobj 115 0 obj <>stream +=LVR_j&&,!!!1m +c]WEHj8HDTXBS膦Ph +i,_)l,N=u^=u^NW2URٚD , 0$n``k<$Xjp^B (\W^0w>CI8`oG{@F E!PB"*= >Up,yBMBeւ8Z`iOX6 +~:U bQf q @%%4Jd,( C c[E:-3D.򹀺PtpN YY.$UFH3%Rυ],&Ϋ = G$X>/qKI@LB7Df|= 'X(́8, t/VtDHB99(b"z*5|D<H> Y& $pQD|M cR`ˆc(MP4@ +vA (Lb!k,Wȭ!bJD6_Ae>d.d|qvU`j>5lYǥd%*,Bud&KFdPP2|._aYA{@F JCBڄ4Ԏ+0l@`0\(ܔ2#1yNe &< \(,lC:0TiF4pX|EYË)CÛPxAKP#џ >U{ Q;eőQ7Fʑ%ja(*&*d2pAwk@;2/(#J/2Ʉ^.p B"FT\&*p5#S>$O-YoXƊ5 a/&1 + l*-Elq!pDu}2" "ql1LTua"Ѵ\8ū4t EEк|( );U-p`sp[S!Do`("+mtB4*T|4:(ҥvnl4BenYX$8ʊK}6EISA3p}ltUcpFEtF@ap6:<ј- qPpREH6kԉو#>QDq!A$zIT Pc)x0#4'JPfRD* 61J0@= ( A>fENbXq$9>  T72bGԁE{@**x"֐T8Bah aqZX{@0>D1aL%2FJr@qCIXbHQ{@HNßE؈Ɏw@tDVpxB1vظHHu9YQ1ļB +1a }iΒ`X:J,4ȱD(0MM V{@Zwa1C( +c S]bBudr@]NL)KѺd0UV ],c 2Зf"i}ZЌ6v2, =+h(F{@@ņ BS ^ φ WSV2U:]%㫍Jf)K7e"KDZRQ XJf"V^^혨,22~nrHch)kX&X %V{@*L,u\KVvIܥ P ꄡå%:2CUQYmjXdDZ?T:Ye+re>Ha0E +AyalHMH'xj9.RтeI>+.{Q!|jE +CR-a!J,Ql l%C8lp0hc1(23ebT;[]2A̒F%k}K8T2ƒA`,p+YKCd, @at\ + ݌2"9`H&0P 7ɽFCƢ7Ƣb AK rXDU5dc1QUS1R"E RiK`ћcǙ.`+ xe(vv0ExB2`&;Ty/GTMFo)Q<' +q"*DY;!!hP-.0%1S1 >!A z^WE}qLTऻ2"e0qR%͡= C]H<| lM0Ap[HX]{YWaZ*(HT]At@# Sre^bkAhU'jSOR2Q:`D&TR P:SIA@G1aD]p}وpeҪHSD=IOc% #iHt56!fp ALhF00|Z#Ke*Bu. +_pKhp .EJ"c6,Uq@*82tG)"@!bm(8 +d51ҴFQA$@ #!HHV%+D1(E $D F$UH&&dx "' 6M% -(A!JTa$JeKO\l@#J` $7-ֶ8d$"FΧMd_q\L =e8Sh5΃hi /H7bhE,O`c19DԅRو4  ,x-h>X,tz]+(eP4CA"K +$@9IJc ER$0( ʤRE:eiZ0L>2"H$) (d!B6fS= 'mlC)KB pR09rDc ԡc&pkYҖ^|zJG N/:st%eZآs=ђB&p4j!򚠧\zy%6F -`\i蘁mvQa 1H΀+_#Xkz]e_ (X&D-)m騶TnjL<AB..\ +vsbeB0GNhϓԅAF7IbDnM Hp–qcH7"RypθŽvșB'ܸꊞe6Mqz cK?Nmc쓏R,Ah&!f \b+eB?j͗=?DAz,m >i{^ +FlLC\0e$$s 7z`ZwY:[6&Ãڄ +3lʂ=h{7Fh_1 m^(0(\";[<%c%TWo BgeN3U{Y ,k:F(Jt,Fd %ÏP|xXmtÎȼ=3@[lV ̨Kf:@fWi,xQiDItm.CS5Uߚj.uZ g_24jl>#NE[V2\(ljғPcuфYT6VV)P{[礼hzs/U,fsGsOYyJlt4Vb>vv TQr|6%)Dq}z=MctY`'*t55?DuC5^4lƚe1H6Xqoe04pKo +1i4k-^qL`Qp\KZUN 4<f@qy_e(KGҽ'ҘvM| /'JrT 0ZwZ1|J@d%#Lv.ˠ5":]˯_CIv0@P?# .z ,dEه +EjKՠHOo3 +VZ#@%*qT{ג@K#h0 +wffcp<ReՔMrGݭF}+]ѢDĩ 5ȭo3S7}s(+#׻}a$cOk _kϹSTh[NC%1hXsGtjV9enKNJK>6se ؖ7 !$B]z^B!mU=I QT--%ŢK&H%fpv]&9 L.KCM6^}TA|J_qNoT +;_$4,G`"?Iq~+l0: jũpo::=xx*p (_1-N6jw4!jhr\ +c p ^3Jx^+L8I}V+BK9,ȩ# +@I辂E~6׾M"3AXUS3MӾ'ec;@/x6K4N(^K Sd&Q6D8T(ǟ})' ؐD;UE`zbC +YƜY'A(0Rxu+Nj3nf ѹvXLT +5˳Uc6"<do{i69kr;Xt ]0nD` W z!Zip8W| `t A^!~8ߜbc;zs@/6TIOBːޡ=ق8WZ1!X$4jkڏ@E KȾ%*f6aea.$>S}_2x(q٬Jƞ% za@tM"Y1$)LT0vA!cPvEt).rR[cm$w SnQ0 nxa2Q8 UzkOe/IZ4ўg?c~ҚǺğ&nEVIX^/ꪶRƗGex&_)AV7h Ox2yʷo4sB2 Y]oQqC3sQ"Uphy(Z2:wIFW>'z0i&Qom-ƽ-;Of9S?l7 IwaC^QlBh&rAŊ].Wju$jAƹO^U1}$f0p +s02i/DpDMüNG +<tn6E5(kZx(V<sdWLG{fumL$VRL'L5 ̕FI# Qq;y= +{!iTP/h'VH'x6PW 0nzGVVĶ U D4Mp>&嚯|܂m$ cBr'+Hh|A9alx=?&6d{v(͢ـéD2YK7ff~49Z/E#Jr,S;YIBӷ5.ThY(tEF-Uu`Ҫ(L̇Rx0ų+^Fי#Q{f7v@yf{m&dw(*.ĶZTaKi1#B&0NG,2ȢlDϥJLof ny5^&-ڱ,kp8@?TKd%.z*H'goI4Yx Y]yGz],2}1ubilA@L4&6 UF~QkR0(#Člɿ {(偤z6r>DGO1^ ƛ_L}%n +m \F + .ETDr{ua `@ /)$w[r>9|9k@ +܏]AE5}wy>c="UG5,h^52ɲ|$$wZ>Nݥ8AW&5 6&b8SHk#tGy_&Jt[T:L/W @q{뇊i-,*|d%z^h=ѠK(CT.x#qla*HKN@ + -J3-O*VOV`n.MfxC͛#hW.F.} +/p|\)v"IGx)QZfb*VkZ4G簬ȩ@BR>~hҷc$%n\+k{>[4M[ǂ8 p7`epgFI 5 WyݘHѕ'j^ )B"l Pm \1/٘P3nMf䖢R'Æh9;=*t>Y* 1YR,%-'8V=^3! }k @K+6%`?wvXMBQo0aI[1" G/)A7C_|&037R+O{_^fH(#ps 1 +Bqpr+U zWf2@ ^^;WZ4Z9&?i5ʂi0JĦbR\CqN)&(qrnr,@x>L +WA'770u4 C?ەlSK\Ŕ)9|,8%)ↁ~S&ݒ_M'Tp!1Cc N<1z8!ޕԈܤ]*@Љɮbb}]zйMUI5f;[RWsO]vY/3[ /z 0;7@ o2f6p ŨDOy[t'F)ҝQxձz)cvp +xRQ6RX@zw,ߎg㏡ɬyGs@7ְ-1*ؖ@q be/ 9NJegZ$9sB$D̵XAXJ0Y}l[YV +v13ET,$v6a$$@t?7Ev6-|ԱDq.W5_l˪TC`YRy7GM@Np/C|c6\!Hvgtd}eme3)FDڗZ👣 )0Ƃ4@P(=>Q30贐EJ#Hp pZ3dX bȠ2 # <5ؤ[~J~؊"7ȇD'@!6K)L uU:QwaN"W~k̪6ѫȱ.7or,e&Q]*$z^zzí}ɢ9cD'$"F +ӍׂX7w@cb6a|$3f&jVF]2]#-&@јZ[4s0XsStAhJY.D"R +{Y;{c˽ۋπ4_|E^BXUu#=BJ+ }&!6Gqa'[H}A 8!Qu%#BR.Ln:kD u'JP3[hiy-|Eل~02D$&FCwĊ]tM@ yJGl~м%'Y'Df: 1s, 9Du)۰ԄDqjkB f#Ï$.eп`E4Qj2K[kC +O@FTP1!T'JJc9$.y)1@4x#e}`>澈FSI2= +eR!f<ǷX&4pTz LVK/)n=-Wm.zcݰCyHcRmO@.eG!YWjx ccL4EI9AIw‚?z5`NGmcս8N`P._ˣ#Vht#"&a6NUJܽYiv%xPD& שxV$H=d2YВ${PAK>3PG@NHZv@ QNPB,Gҥpa1sIE1o΍McL- kOi=tz׌2=M7˲%Mօt$\\^|0V9 A"D1bwSI*Ds\<(8vۤ9i3фo9a0Pth2ԩVwSނ@sU{@rx*w46~Fv$f:<`Ol-0S~2"r#~K0U#bgbLũO +y)c&%jP/IfqHy s"j I2_qgÎr#W&Xc5I@e33|z隖jĢХ!0G'b߱.|H_9*B] {cC GY좱OOѶU~r +J -̬[!zH>x٤UDcQ㨊qL2"2"7 +L+?=,$ U7D5y;dU~@>!YG/qkdV|ÅKVĂ]F +u'vm-"˰}(*Q_&DMj:=h*9H,#5h%uU~&ڴk~+x>%HS #%7+.'VjtK3/|[.1w@k RɮT `~*~Ma8A\ wvU30kX:x461?!6=MA哉|Gǔ!&S,u d1- +vM5!#Y#0з0`N~GB'bCFwf Yº4"gvEHR gT@$|kd#.պ#8V(.1Ϳ< +?wxӸ_9*EB>GyiPXuh>}Yx{u'͠jzչkkwܴC1"}5?Yn1Z&4|K\ܪu];E_UԄU?aICoxwieQB.A mNHގ쿗b j5쒲~cS9&jw~g1ėIV:q0tuR흾A]yBڋByUE1KI x;~."`"`]g +llCD'Hþrg WGKt%B|L0] Q#އ}*n&$D +M'e)h0sKk[ +dsbi<;1+''yVao("Usb26yFL7M-e xB27ъ5Vpbr9@Z@ĜȅVag%E 0Z >(z 7|߰`| U& pצ vRHw}‚LQSK_c< "wX=j|,cpN# +Aޫ +M)%JSvΰs/e+)!l擉G ݝ ieS͹uۺwT&yE(%!ɦuG(Z]Ki[ +->bXGi-U㋗k5Ic]ȓ "tLi/~eoA1}A an5@18;BUtn7@lJ a/q#09.eg:F'B6A}| ~4FDZP`pĹT@VCaڀ%uLJ0|T֥O$DU,!uvk$V^##P8=VrOy`=7tf׾H%ʅ8}ڣe_P33xdt`aMrFv$u:xؾחu8FgG8S~p#01zgd.|n&[R+?;3L%jOt.UƛippĀdkqBފ._JR<ӸZ|amQlBL[s ʦW荶`BSS" ^d h霎rNʒ`&bfBClZa| +m@~pwx~S +ԶJQ@bi0'\i-Z)$X!/$I/V<.oH%=UՍKӴ#;v''нt7ʳ=)H9ťCb:6 GávsI;6܁&zЛ6 +Z"7z=7jmY8/D!_ LZVE.-_DYH#1Ȏ 7IX::oGƴEKBaC. 'C0ƺ?ŔҌ-w~Tm1xZGG et!yj Oݓ4EJx% uf՝F82"[i&Oj1#£! P#>ϰLO-9< +m.1϶a՟v^}<H؞r#sۂoxDt)I^<ޅE#. 61r aR_lȝ޺rm"|>\L1I]PsBGTB *{R`xt*z͆) Mg*2.+9yh>dM]>k OA-#~_D}mm|< +PPAb9܋lI4Ό\*oJPlI9ޡG&/ ۈ9N/BMCU6D0H4D6@d`M0Koa R1-(Gi`L r+dC!z|ѡw"kGرQSXhFh(\P'cqWE`ݕ0?Yᅉ14_qU!sK_lgCPktEH0R^ZD-Mr +_ 2j Gm`h `H1dŔӏ-q 6Zl>'Dl$|tx.v_.\ޘҨ 00RMWb2 7 #I[y!富 O- aLNS[w-IA`VΠgP3Or_⑸FAn*F Hbn᭾ܔ )sy5&A{t%$o&~DdGDu5 WB+RPL~!cSm"GYcfc}⦰U(%dLQCK }֍:(7\"k *oU@u4Kvا+L&( wkr/[3^olI\@ ާ?q}G%Cy=  eѴO)T0B+" $8R(+"7}7n5$9Q}|U gݥ[3xx,cgzBNJ\ -K[l̽)z=w8@0(`V!dvk?AiWN L EjH}5>h]\{ GSngm Vz+1XQgZ8 | 7CqI5dYM"$!% ~6C6* oyH^Aƫ& I]dֵ c;0JԶoNQ:c?DhrHX4{?ZT?%Z7Q#}Y 4ĨZ⢿tBnHs\E/ =CQƦݷ'g!h@\2+gp^ sAVeLF+ MHC3MiQ!f'9C86Rϔ$/k޹~ $ +Q׬| ɗM\ؠTVYxD*&H $065c^D3#h黦J}mpCR8-=h4'Z/XhوizKOQ:2uS ߵ.;8fPr=s˄K`X1>E8cR^^0Dd0|4M$ijoWq|A>lx<A ܩ }kz}?yYI?`+*Y1Ur1r &wY:l5j)&̢HZǮh/$%݋`,yVE єZ#Fku"' +p^z= +r6n0DYA +{Ӡ`]h-+iI/ip`SbAg]ח2|˜ިeCKU Z0v!PTPXADL=Aư :g#~3A/赱N.!1 +BsU..ɧxkjՑ5aXj[_e_8:I +hT3zmTmE(¨@M0FXE.i pD.4xJŠwG_4HI-y^ttiS[Kxch A,"3R%2jaE9 ]#x| bәҩ&Ю헓W +3ChtXH +y<"zihQ&]Xfx>)oL ÔqN$$'??04dpzv׌wP O.޹2e^{pBBLlt,2iPn|rI Kb B zנ,>/*k6O&2 wze32>G_JY Xh%̐55Rzkd/$YTTř@YV[1^&FRYF)션0  +a],QQ7ot0KYjBU?E][{A{oqj?Kbrܿ6_i!nJ +Hy +P/oO :S!::Dkl5b J,- is#DKr+70Xp͡-q.$<F(o6/?7x0篍0%e5%fڵ7!&*ͼ S:Bӑ FʾK8WļOa)2ePx$A,qւȨkCȘeBE Q /Xq^Ѐ( w* snp+.;0VUq!+N.-dx [d$ ]-ED[Ѡb" Q$/K2 5Ҝa]zӣ<$G)Jwc8:GX19p( 48coe wBv^~EWk;Δ^F=9a$;MJcȎ[I&U]xWNO4LҾOg@2bJt*%B톆movJَ;j䎳HEibhx8MYRC`h ZqH @GeS ]"YIqζ3+r{؄R q$j$z4,\^A[/wn +C8WK!RE ?!~X$&_ICϴgtu<]r!Kdhd{QZiw!j}z!:+_rpSĴ{qѣe3μ)>:Lx7'4C( ECyfNJa s@b;\`D\LHyJF ՕeD2XmP^c9tf28,!Bl'X&yXP\Ip?PR<\eՈVx3]k⫕_*njFs偞CKmiPdSO7h"N0=#D"!`IFu +[ELHQme/L^PRZ3slD8 f֑|h#Q @ǣ7*Wg{+x[ 9`ir+/3ڌ[Q9qOQmCAiZMht1P@|(Ua"N0qMR 0f* L ``Q%_Y"@>idl341у5OՎ0C*іڴLEN0Q߀mFۀF)6&}q3}D +JKyꤝs hӱfby@I෡@mX۔ h2?bntq; Nba '.yiݏN+>.υwm,!bBmJSFY'&@΋kr:1N0lx~:*&u2*TDk?x'>@O(U_ځ~(r޵3eҌ|r9'p8 h`0::`ל`pLqۇ.f +6"6!9ظmN0p.+Mn +;5¸( p5T'폎ڠh ZN7qb) =ZDda%B4$:'ph#kZU(:z /M'o6dvqCOn``OZU`q3h`V! \čr8(5n7E! n7ɭc֑e#9jc@s˥\M킘D>ì0Zy R'jHI9Shf>iU} B1 1D]5$Z r\k+B-t`ݟ†$im)khk ++$VrZ{<:uogS|itr؉[xp@x?H[D ,p(wY% +&tVuepl̄@9?,FJzcF'&~ +]sdE'&Dh b뿈GqM;kkܸɥqhafj@?y*UI얡DAS łnQm{g:NL v\D#vs|gY,v 267vGg<ꭊ^">+ )Z2Fk VͭYhrͮ|Zb(f($8$z=C.*:z! p,-82߃6chK,J˜BC,7)&S +k`3ru6908FO rb9R<זr9}tOCx\ނ& +JX?=#gyR`L +d:e71,f2*f`5jH?]X&7klLmt+e0 +!z@! n $D" ^^}Cmq0 8I}S0tdJ4UћذAd@Lћy&&#Y&cQ* +n9vn789DJڷqd!f~7t ӯԋ dhGU (=H6XPI``{C %aQZ +Rl a7hqk`h$Fƴ j/aD˘Gln?4 8 @;nkw_@7v,N[(%LP E:@( KQmШ<-EKUTo!7/!#^F2˿ *#HK8IB`%Bn5=OT0vt*| ʪZJmuY\NH*yRJpI@ )4H`v1{!_=BiwFmwf.[STBj+]L)^H3vҸf]rWujC?J +Wm>P| )BBb3537iۃRF]3.lF!rM !f I +3u"Z)$.Ƀ\u w!LP1qÀ  :J C iF_?'.Eg +do흪cŤ?3y<?& \cBIJڱܐJ$p| <0jSj^ymlruf vDTpK` Dsz L5K㬡P9{a$v ?L9WuQYڢ&Ͳ-6!)s,P"&!ah.ĻpAs,R+5 + ɼj 8p&:w+`Q-AJKmLSw!ɢDz:*Iգ83̴#hI˪2Y2+q~ޕX=l-d ԕz1vR'A\sfrj Iɕ_CFtDIt %qTcS<η"0ڸ[35K55PѠ?kڄNv* T@j,rY\߭6wi ![IԖӜfi1 `~®ް v ua3S$4~Gz$|W  >35Rw,I6uҘIsQTr >dctՁx]h_:ƺlKoKÎXώJ:6PO +>IbIdPkJ1%WmO*Wm|kr b`ׄy`wbz\)~T@"Ѡ;/}=pzF9kEO|NÑ{F"WX!kڪ1Z6"1'a+G,dYVぷ#j3kԬ%v.R'u@J9k2G֤)S%…wj(ZnΤޤ! +'VY' +RS },asK2R7r14JBqZ1J<|/AZP$GrX|ϳh$ܥC3Q|!0j)La 31x{ܔښ{QuLݏ'(ROZG`bd" Y:NʁqDbe>cRˆ*Бr7#|7Aw,wQx Qf,hq]-EY.O_Fl1S.QʄeA zmR4`s⑌Ȃb@9`kj9.2%b.u+,~8@}()Ƚ 0:M4k{J~0'|.Oy8q]J7D 觙2/x3[HK|ؠgq5p:&w`j\@ф5N%m;?־Slpisg LY4K4LY R:I]r)-͈,(ڕg;!ogF ]۱b.> +n"[2EʂI׭Σq ՝aVmLyo Jd=G/D?'Y/Z!۠3rN0@ 8»J?FFO9 +UW5L+0Thf ۠EN|./x  :4.O5C- N`a"W$*R!Qt[۠GKp,Za^1',.at ߭w! /)z|< .'4n,WNpQ%Fa[RFҖi&j J DTz5fJ(ì8hYUEuOy*-MQLF*M&c%@~ ۨqNkj(|"xN''3D(]rQ4d|+?/ɌPyqDr[HVLqs>n)n3ЉHuLBj.7mR4bN\0;%QB\bF븹5RT@33Nu}~Er =&`X5GIm57qg[|LJ!rJiʤ70ȣ&}  ^75cuP4NL)lszg vN0`L (X4_m@4(,J`*pr*WC.F fP[Hٰ'):Rm $acj&Z@+^#hN(&Y3QQ/ތX*z?qMѓI(ў!R2 +)&C\CF7"kxs[9T%}@c[ ʦ\>˸@_]ʯxb9v}2'#\qAir cl]EKo]9L#edGy3 xt-vg?I[h(Aa>px#,?eb˰v'cQ@V8Ri)SHKypSL9kyю"i3xØr0zv9#73<#>I?=V_1ńfn~pQUEKx?~9quq}"L}^Gi4L9KRxT`Uԡ6"$ O"RoVU%ʂ +~D&.mB++֫c_:Efe4B>qVBꈞ9$*B~G4zpΰ 2YJ :bg(h;Hx/LU2c kYeAksc/'9t}|лYloY 1tƲ9&dXV/.wpKj/$ K*N\ L3Ev1XE ƄgXcn80}WH%*;ZO?o+6d{܀nrt'L$/Hl^XWN-[gH޳;kpj?b`și4hNJ(rߌѡde*(H`s_jǟon0xLۏʂVςl=!q,1JDDqFIӂ낍8ИfMaQIUSL) yDmx}@@R?I|)Bnϐ<5*Qj6山nL: z\%aA? -⅍c5p`e 8$9&]:SvQ^zeujGBj.{%{ Rs!;k&:&;^X4wQƝ{px o_?jD䚑rֱ2B)OtLF~xLx) 7@+2QLi{LT6D]Ąl'YfaPcpE>'8XFѳB#`<uN0](9i&MPq U28 Y;ؚڂCfz}&jnq(ᭀr?'߶u-NF>W1xkGYJK6K\{6`(':xqfȵ Pk\wTJmjtX"ɍĀy_i&Hj? 5n2Œ tš,'-*If`kF`k{B|j<'yof )W DҨyh6яD]](U@(K'@@IqP7?KíKꢩf}pC,_Y:o?XLq|U>Z"  +2&N74&÷KomӜ> Rrȝe,cY^= +É\ҘEb섻m,|XbPeN\MjrQsɼH.9oir2`An1_)wFOѝƀ:z]̺t;~5YqplG1H.E#`Ո;5T{w_F$\lVGPQ5D;#w~%C\z,@9 2h-|p>` +}6k4(*uFMr"{듽CKam/jk$7'd#juDs2"ny"D +9LhuPu5/4E卜qTeT[mY[a/B5"5Dq~A"j'4EZw99aKp7Sݸ@I&b5T:rJ\y^F%sb%Y(LC꒨h@0lt +'G?*[,w~@Dh#`4̂j +eg⮑5ZXz`_c6(v93D*/%a$c9831e255BJgh]~w׽c/M;Zk_Qsk,$*q 3N0($+g.?8r;D<կj^뭊¯+p o DQBf+X Do)@3Ķm5A(q@N0`qHPi46xq%89:O:B,o NGn[LM^i:{T_%OnN'q-;v9k,-*]ɣo-^N8>ߺ+,&,n1D[& 0kL:&33@c ++ԔڄYꅃ#|rn߻X.`ok Ik2ќ^"M8YB6L&RW=)p+Z)ڍTIJlwS@pAcc![P 9tQL%]ƌѝvYa)Φu#%ӬMل|ENl7UKj;P5h9m&ԧ0EN`/}';Cop*V[ip:/Ѿ`'t) *|Oqj70{ ޏ8kJrly>]L.`AY_2^#FW1w`U%<]\Q1Qm,w>~;M-=q,lU[/#F0@r0.&AvN-:De= zrQyt5`?+PN\ IvbgP:ڈ3DUC_@}!j6SYZOct, D߾W;.E6a H(&iT5i\N0YP +St"yy3-w5qkŜ;i:CGPQG!٠)sͬ8XR0 okHnVFdwjk;&i +Mj]1 %:VSHb>-|}y&~EO DWUX.E82g7WgB41O65I+1ٵ 3`FZ{6 [<:Nѝq  +{I/^%c/f(}58FjL޿CmU1zaSdða+;"wx]pl3k#l1E +K>kWGn</w4Z"e=72q>\a {v't8A`GA.o6uq8rkilw>]Јy\niv!_C}P/dSmĈ&& kK05.KN|)yP/kWGY믡ڈUg;DTJ\t %N"j_㙵fq\9n3Qg+ 0\('tb䍐.R_ߩ11v-rA`yc1} "ὮF&h=!/dF3pr6݉j:"+ބ І"r4VLɲ4nnzH`.(i6x2/̓%G)Ĝ5wC,4mb0vNR;vƌڀ:6y8mI.T,Ƨ&*vBszw})7:'˾xwpZ\סĉK\3ru /12dbGݪY5N\i,;BETJm^,:51qVݸg j GM`8m&iK-v]~ۿ<N0Hwk#yEЅ!BFdC&9sPy(Jb %P ^w=h70 7I) 'U,:Dr翋bny|Wi֒!=;V_G /OAYOq+F#W1-??A WKXuVӂ'5~D]x{%;E @gԒ/oR,Y3QoaX''앲9F񑯳(u) *֜` 5{ؼThNb`+Z%%P$w#3Ŀ66ǭh/ⴅ6L-vN4Ѓϟ;FEo`yA_fqH=q,&w~rI[hZڱ8,=^Q/B DL.Kvʏycڱ,Qq` ׃ [)ln08t6Se+֜`хKd!Z;@MVTǔRcFdfHdZmI4 PHT*,&(P C1@ (tѩ;L$2pˆx bzՏx"'OUQ\8X +'A3 c(W܏szv\PLi:綹}Ko״?Fb- +/\x5p[⅕R"\tV4j6+[s#]d4e-3 "x_̫А$S$4?IS&/Ĵ~%7  2Y0h䧘!ޞC䷜Z0IOx&.ϩ*bxT`⌍s{xumO +\ֆ)EVR嵩K9(m +BB^d?Aaa-XW^ 1לS^w{< ȁe٦~\Z=z]5EqL _Nû>QX]e}f&[(fe?1"--T>KRm%,6JT6[Zʯ3Sϗ`1+'@vcv`ٳm7.g# dM,Ba+CmC:VngBdރPLPcTف(JsEEj By׋vѦapvLQkQ6V'kb}*D_#ia!N$UՎT2}(=P,?i:&;nХ=U8]bs*}Ok)ATc=Qc{/9|)|d {K"O|B%2#;cLf%#MI+ aoDWNe׍/4HL.d1XK݃ɠ+D",dD'%wAl_wt[_sn fE:0>ΞN(MCrh +Ej. A +xM;Ueb>}>Ki?ga_\X"iS 8$l$= xupH0֧QI=)U局 YaN;=sqnNN}-/9 uy8sQ*`_1]뎢J3$x3x˂z\%:q{^rNvAiXi +^}iH#0T8XNp߅Ηg-Onp dQz=ט\ۈ*kQ)LGd J!e۾*|W+1J!KUy8X$ޜ`+~i9 +|g< + 3iFcjC~#3`=c6ƎES]İw{du?r~ӢPR3[ҬҼe ~G?;E^]zmzZ1l&)q-'9VY,22 Z](4 up7:@.[X&U|803ԝ0a.Y5Š'la1P/2 k?/^#8}?"Ʈ`/0s5b%7B7le.Y +?mP7>.g5f'BTP`ez蟳7(!:SC?Ê$099M' +;֚Re&Mq~/("o^pQ[y0JEƒCDE\:2b#Q0% +M#T:&+DteG*2uTK=8RXw' R&atQw%i \M7׃dѡ p]>XE3M4cg*&SG}("ub +FkbD s",|!ޒ?Gs0^!]LH&y̳ vqGչ4vr5_.yԼλpWSЫ.^߲\{Ss}NR{\5_o&Ϥ1z8K+=l_mMB ˞Gj>f; ))]lمԟ7ƭOF"o^XgiKpS}0 +SEMZVW_">S] r]2pB0l5Z+cQ a䱙H #NP$8xE'~  8)73MQvŞ$J~GE&x%a|9#F[|fMcc& !4:cnon/&Wty.J g`ySMx.QZr`]=6B#$gK`+JnuC].O]>}u5,?N2TI(dMJK]vvo;zy4SA!~V @6i~"|Ŕ"$C@YaeÇHf=E |2[ǵ\.`ɯRg7spJ]_%94ʶCF|H? V/C,iLG_ܱ4[zG؋&4w3MX]J#7cK'0)} DNqX9!'&))239@׸qjnb7<7?\'˝Ep-|RrL?z УO|ys>GO~Dz$9c:oTpd ƶΛ - ,E,ߙ,0{^Xr+CYޠ|pqCFU=eN*i0ins ؠzufz׬N5O!UOW +c+1\BܖzVzc31/t>{ z.@h!@QJ!@7E1ˠ(&J8_,O0:Yܳ=~WQ|]L #jre$x1~#㮯3Z:E ?`LN*:XR/OSıaA.} JR +2%m'T\r(_aud W"ϙs\\;z^i{x^Ơ\++`-%\NIFꁻJz1SL8QSZ0R9JU^ȰZKI +OUd6+<#?PRo\@B pu vnʍu};i*_qchqίA%qomP p32M "%#4g !N \I[=)濺E7@v>/=ClfFBhfNVYVeK/ZKL0wc0MGgh%t(r/V@-h'V?Ӛ Sl@F]n{ɍ4Lwt7 ,rcN>M.g{Bjbi&cIC\cAAm_rSYZՕ*`Z_ mRt*hk3 U2*=#$JR2o2WArqՖwnA-ܠ7V7UK|PoU9b|=)mS*0.:%&bu+u֙:ߔ]g<+U=-zRCDf4zݿt!q\ ΀$HwqmB&@. +#+9=td؋q:3H`vTLpl;8։;=2vFK+C+tYm1urnr?|Oa⴬%X¦oi.lD*eiwƍ"$.!P:]e|=B^|kr(\HB+5C +繴\,kZY;h +ZB\!3{T]r4]=Tk+Ma7is+v!ҵ0 }lBLfZWFi ?Bwq`ȥ8hJgrt\TIB ++)KWX2K`֓jc + &ݛ|>"]u8țUKed]䚀TL j}3l Yق fhX>X'&Xj 3n^FČ^Ѹi !6Z`V>R?׹?W>V??ԾCGeK +~л~TN=ZRx.DA%M&]ԤU|{$[~Ϯmc"WYSzu1Oԧ Xa*0ֳ %څo-fs1+B%_"tB&s>k+h9@_JU+'S1QײCuFk!Ҏq&_GDMZJ챲!JݐZd*gPciik]CvmȲa%p~6O|Yv]V JA7#,|~棒f|8_Z 7t +JKaC䆶 JCTK2+;[ s8 }^sēEi+eŌK(En:nd؜e#]44mj\>UbC=vd:[Q$l +4dR[·"j|8ٝV:jm׆:% +H9`r| L.l6`n6yv1AIC]D|ﰸ$HɇLLŮy~`O.|¨VuR@~0ؕ hH$mP>^g2Kfڥ p +,bto]aQTí=[XM+Mۍ!µP*]$mWg6yj's$ҖfD읲J'l/BOTґʍ (2I}FԔ'k+gf ARO:";D(<0cnvI0Mn) +;?uYhTZX A+N(#Sd! LQxLX*k.! %d1b6DfLRdž`ȡembv)c#[*$@[T X'b35&e6O4RZ^/ُ֤§S-);˕F {63W ɋp+E8 +4rL$'5$`ǃ0y3taD09?]3΅Ch8 Hi"΍OJA,O>XTApH77U " L'@o(0+|K( նRڡpϟlݵUbJ9C@0a{4m%1UioNwKwf!h ÅgVl MUk-RsetAe[z0X)^F"hJ{R,4] ZYl;tEUOk P +Eh)HFf#NJz`qX5y0KkjQr.c[vAG\NYOC[~y[,|zQ@ M{YND-čQEPEkJ~pW2 +3<~e07y'3 )2L"25y5LS7gƣ f$|ʩZNPc @Lxb.Y9aIvYC%aCCc1za$c u& +,WJS:􋼸D]4hO^1#?'ݐ/Fy/@F\ƺ8? ́M:bzneH1+xKZI>ZSnz,*fɉ}G 8 wL4@_cw +/|] 6^)}}gƛys$_>ߨeǹ6v[-!Q {DmIl;Zbt)h(2׹ϲpGLeqr([{9!Z& ҄z W`aj&$; X{cgdAq1p$T˿ *bh%;K$t"[=R#+ޣ2_t1/tU/$dF:¨%)gcm]}$29~Z+8G +֜Nvpx`)D\,.>>A֕+e^OlAg|\bVZ/Sn$JX)n؄@CA0ˑ˚g>i%٤ps+d=F\u Fizȇ^L$~ YBXL=?9w7:4xҔ='ea 7q:eBٶnU$՛މddj",ӱоOu.;wL>bYU23GQZltcQ2~u0-{cjB:reH$: $geI2Hda,C_L.A:CI005+n[De&$MHhjq Xޢpb:O`o2efڳ><7[gyg $37mt}U-$={[ {ѹo~eWcXĹ8*`[É8X&+!g3y wƙQjo}=Ŕq0r?V&=W03jO׍ƥ`"haKYl1 +9kag^p Jc*KU>"Uc|\1,oH`^KH7_3\un5}'ҿ{h\k2aQmJ9"J$i \Je5+5AN}HB2ډDV\械u_GR>UbOSӛg$o `D2ݻt)S\MzR't7bvZ-6W.Qa任^JD)oZHgb":qZ)BHY <ђ}F jaz`MHt +τJ7E?ōso(iRk>w?qyY<)90Mq%xhRVZ;qHΪa[?Cvi08V |е5hk>cΖތS rP[n7&t-Vbii2ݨY=tXnSfb98]xs8 \{Ha 5bܹ]$-OJWhJáRnBj`حe +ѸA0A|uA|X!"nWrܯ*gjo!/!6lUX%{Hw&_إJ\ !zI6s)x$1!g#7`Cӵe_MnNҤ!=]+eMtͳT*^8T@RKH``W" n̶Էv{\Q#xɛ|r$b=yp͕Xcpҕ0g"x-XЍ& NߐL ;yJ[:Ľ,!:<1 TV󀢗4"J}ɻ[0I#^jnQSUHzwIi=Us&] 9J2,S>M+Y>Ӭ&Wױ*eoU'Zsŏx0~Lnn "9= +7h"kk# + 7͸'&_ +JmA/+Vjvz0Ur W-@:Si5*5c!\%0f4ɺ]5$ xQB)P'!wm\~*ܡTӄ4K JEM-bld"‰pǥmI#*yImsJMs:>Pp/lv,еgDX+'o6ۊ#!1j>r\UJYe;8rBFV&\(ys4\V% )p3>~ܦĹA W W)~NBEWG16܁ԠGpJ+H |\ +() RY5Y{? ^g+8Lidw0A_MqڈVDѲ2Nq;nFijF!җ gUf.| Ys@U0E\9MCm9?TSf; pPCU)+&SH D1h~XWTpL,%$6AI&t}gD{{D]ʯ WYWڭFrlsm4⤊䑈Aמ U3ϠnӏWL*~Ͷx*yOG& *#4sg/g^iinws؉zCj`@@='|C.Mы68ƉNi{;橜') YDTx`)$ceVdJ;y#A |׹.6qhF'7QRY +8pyDot#:f߅X [ =Wk%'k`F˂~h4L@ +uumȡ䔄,M9(qq&=\(G ia$,)p^?K{IJlgbcK +YL!K +&0;[dIVx0PV +=[ ^ۑa40EYk &({#%R7]Bi6P<+X*@Oڎ,@{ $MziTu=O6O|%+vU` +^eٚfᖤD!) R (2n\uBH:#=/i"Z˼"kvGEY.1dFc 5 ͯ;0`,C+¤; +[sdCLV$/dW#oe `aQLzoCd?5Aj9+I85Co$-f9C7t4tJT͆'^K`:u\LB\KlŤ2팴Zr&9:[\wcNy?˜PWJt/HYm%H`@@|vΣ5rj:+h~^IQ5ꠙ\Gjvf5VGQ59'NNRjT4k!0jpG>wX$pˆBZ`@g3t,BB;آ" (qN%?V7AҚ-re`2BmRGMQ^4/$AE}&qXũTiz܁ +;4lHpιTʐ0)-kG-aNbrA Ȏu?:c-mжWsr5QLh|dYq.,LF@3֠Jtfɍn^+LĠ3hJ0G${#ޕ%φS +_(x_E /N{j%6H>au&|n9ߦ`y)R6851 TY9[$>GuqJIHt(?Cd\4y@s`<@5Flp1i %vy܉`)w{y<;3[$p(h`C91OMb1aJѽ:]jG)^PŨ |K]Mg.gY+4ǢXY(3ˇ,Y_ /ceFh-ԤiP{s`,0dmn 'fN }hB[4 ^beJQފhL<jʬcM8Z + wWĩ,<#RšY,!:q7E7Qh[tv_p@akLFO͹Nap9чzǞY+cCL^~op%z#s"qNϤvЧ4!J@!e:,,/.yJ(㔑㘤ZG2 TO/t_Ѹ/.bx3Fg8!ϖX ~:FIY >le~9#WFq^d>LU7/K~Q)5[ mynD|omu$US9 ⏓  YAl=&OCNARⅈ|Mfʼnxh3Uky +죄+}-A~L;P ?uctfEMXw䮡1#,+OFL%L dm+Sc~+DT#Ɠ%Yo 0yƱ?bQ>5ɓ|R>/9` |'#U&G HeDBɖO3/u\]ݓLP3F՛tmP/&\dzB &.0Ӝ͝De|zsձ0՜"Mk8 |+ED=%8 +uH$ɶ *dA9&:+$6,hc+%WD1 j#ՒVƀ}d|xǫ1'[Q~mHQ`2)܈I1ATZ\ ˎ~ +U A^߭$G'(JZȃ#]mk8Ά5lQP_)ܞWF0)仾ce6$75[^BJ2>3~%˴z}s1VAATkSN{v);&cla->NKW']@4V*+8(xsQ羵IıHgyc 3v.r[X6q7reA_j>Z Ëf^j'/!\|>X(h.VD3jfv@!rUgN3j^H%@y-\]gT%< V(#}"3.D)-[)bN|FM9%f/&# +èN f /A)8dg#k2G$1 kcԏA/0-xkupN] GeVQ>WN|?chd:Zd(dwW$?[cuCVJCl=[_ȼ3+r,+ +\@=)2|'cϮkهݑpP"ä-)i`!Y{_HAmfOtUKS{$ +ǧQHTA,}O>r9u8+9B1kBNB% 7dCMbVȀ1b^aUỸ,VJmKp33rd{2 { .G>qhn_q Xx)Ex +v -"f(֩+6qje. rE9D#0(Ph_vȹ|-Up=OX3 !﫢s:EkWdƍXduE#Y3mvdlƨu- m_OS爱&UgS\-rmDYHcڠ$+o?f] +73s3e /ڻo'+A Y)i8= /F8i2r ^ LTL]65SBhDՄY*y=s.{¯}j"n D]o(q;Mg Z +u^mU6 _Z**fS,B誼 JT1GRk`r )6ު>mMnaɋ}YqqdPq l\ArbϨh;B +$gGl_2KEkUUcv3~o `Tadj꘱`Bs$i1ʧbsQi|ǡy1RV2? +Ä٭i)b ɇK^~b#k5; l:;Vd`]Obnr +* +]+ƽo +Bi][ϣ(4 Lfa[^+ V`\+.Ő +,%dwA4ZKX.JE~t Ib|;e #XgFk$BC8 5^I㒿4^\& K8}{>fWZe I +m>aT"[N E?Ɋ5=dtjle,2\P7@s-rҋ{1d'-^(0<$lYcM s>"T}ܣ +qbI!=<&+ +CQwTul8 W:E-~J/[R&?:-5^\ah^K, ֨tU$xc@*olYěur-zzn IbjvݟVThi}{U$Uu/aPRz- +3 +MތT74I'fFO-3Spj -H9`1 Qjc-#Є$Bى#xU95mdLC&!sl->*,K!=v@x G#Jux; ڂ?C?ֽ NOvBg +h" M>#qӷʇ4J@O ,ZHB-Ι@^l>c1&H* s@\ҘUQnMR?a$`Є 4L-2#%<\#!(kXR`-@1F-f<^)[a?8lU/34eu I$6cvIᙤU!krQHN2'FW92y*XxWX0H#TNbui0S`; +[ |fMH3Mt)HM:\GߋG]Ah.2cj9YXL` ' 65 !5@sJbɤ|x7j B hjf@;2%#xC3b1< A>^*# [r?Q8L`14N, sb1uD+A<=BgwZL33<򞇫e\ѐc1 d'9ZDf08:5+v@x.y^cubUl9P|< pPk%jҺ:W S&0҆2ގ ىzfZɦ9`C5ZZK6TaM9}v] +qrXXQ34"p(T?N +QW!Q]KZbJ•eO@&u BNHfT:NMg8 %"kK1C@1JBM TH]f0`_Bo \bQ7;-.sPzn4Q3lBΌ uܳ'ݝ\Uxڱ2J7EGHZPGi-`r,. !}o0Ļ oM>_@B%@6lƨKk 4GbSјl6Ε@ dnA"5v@%D. +—pl \]BGu<&{Χ)Mؐ]A ɉX) *b:&^r%~WЊ29*/T3] 9AW9`Uw`@f ˢ^R1䒆hv]Ƌӫ6" ,Z&`:>_҆[eT<\4CxdFRYdJ&+BM|G|zJІUɠ 3e^KH9=Jq&DIIN/]XR)!վwP:J7Ӓ-2b[r=NNآ1ۦswCc~w&sҳI7?D^fģd| j~%I8k-.˜}e#&ZdYnMbwR`%Jt_-#?8rX$E.b h7Ecr$tߞ}.f,5R1ιwkX~(}R*%vtеgn-\ŝ7.5NHjϤ8]Ԯ]D1E})}Qmݳ&G%?ٳ?{.XYkcc,آ^#wvU[D좈x@D1&IoMj/Ar?M&RX.doq.-G,ݸSTu_ZTUv9ђYߣ~[dGhs?abA$ELfycRs,߫^ãntKE5x^V`q*Fjuv;W<*ZzU*u@Y]z7&AŜ3,#8o[M +cIO1crw;E.'_,!2H"u$ jܢ:o#Ijr` 88k]D~H/ï.G *2ԧ&?_(WTo@k +B^3A(x@x r,*j2 j>ȣ}.,IGE[%q#OrԎY>HOr8=R$/1.$&Iy ;?"K'KtRsY'1&Ɍ,sKq]c'={Z܏qR9TK%rƣ&3&K"Yr&*5w_'(YR$GLI-{G(cO? fyqKM;.3)8];eΘt27[ZxZgг^f@HY`):ZPe~GrTe&B+xP ]b=8zKd9j8IMjlR-^&M".&w-O/,KGqAϻ +BFhX, +]r p3PwmP25XC# +R^. +7XBG)P ~X^(!S'IuE䰭"i)cFv+ZK/u'@Ô/R@P(>@Hq+mosQhP0J8ڧ[™Hf`>VXpA '٢M-!0j>гY֨%0dH|4TQ>*܈mLynDZ( +M~a"⬥ R" U9d,XW8!^Ј2rX( W\L,PUlDON$<"2CB"CQd>)S0 Z8H&$"XP(QŒ, @Gc#dHԹ-SrUhX%"ѹdßŋemEҋDpubwn(uĵg<1B=IX7q;!4i|L"VW`܋ZGjh]M_D4 T*)ly p.e|x{+JW)4&8$'imldZ^;@𲢳w֗9a52#>[N ACr#=C~<N/zB'wl M6mH#(&+'\ֶ3Ce fGuӋh?$q=Wǻsm|[PRF[dXI2P`Fs_k>.{k1~[BSͦ}t'2uLKz1;1.P@Je/Ver|u1`YYdMeӃ17ZLؠ4 )wކׅiLv+[P=]~UO`}4:ZVI8qD Mp1_XIRob3`qsY;>+]1Vj>R{LDS06*%cш{j_OAHV=#"r,fr8H́vSpu/ +B1.D˞ŷs ,{r>( K{锅T{ gH-f|=LtPeNְΖRZwKʓ$LWhJ|˻p@UjJ>᠒Y[k[n+`U+Kؤ+[.6D؂xҊk;R"pzh}bL;VW"63JvxQD:Em)/ !*#J?.㕮gfn%]d +1d''FFfמDxIK}9~zvEcp';*Y龲`"mnDIz'Cach=*mE\)jQOu$I,ɏ>HFƺs&!ioss ;kA?=sRHDes DolGcQ3,ܒֱeE{ڧoFQ+UU@-Y$G̛Wqób9Ch8"r0T\W0Uz-jTI0T +{> Z +h(L%mV)ii`AXeu5A VVI^%:/VقUsI! +l` aU)X}g.gv-XaՆ$BI,XeLxσR4KO} a ;B[;XJ7)^[Y3\B<]Ϭ*P,Ϳ.ySxߑ^ ]WiMv`vo[4(K'F Ҧ=2Nvϩ40W|ABg@8G5 #svNn(xwQ5&c!Oc2̺9EYDVIf^84XE:I2xb"ں?8TdxvFSik=CǟB\M6EL+;z@ HL*02O15ZP/\I*'=@P(wm#/wѳ+Kv0MNF4 `˔h-l:𬞠YO.z0o$2DC^%7]%3yPd'kvk&utu}V$ ZaʎϴYeF1I*aU#Sq3"!GրUPu،0b"*8Uk]19 ƌ!WUч<'{"ZsCs8rఋ|7Jqs[dJ1'a/p#+";xTDWaPӛ]7dKL,YIAaƕIf;Fg5="$|(MƗK @JTBan+2M 6;!.Y9E + +7mNduah{׌sWcYߺ0Y x@+?(MjcJ&pШt>]蔟6BS_ @7QQզ:??AK0Te(-imXBj(y5MI'Og* +Z-%>y8nO06Zb*2HeR@9 `b 9˗$#c=G /mERq, +yFLjnjcw?(⌃I;j7,D; <ϋu97YG 4lXTrV*A_ۘ(\>$NǗ3}ŦH}~S"x׼櫕kc4FsIƸ_iYa&UMra NU \H ZsJhˍ9ZT,j巗FgH9ՔTK*p2[;R=k@hojO&+ƙf .ѪqXQ @J +Xɐn"As]}d"u,,P,ѠѧՔ15?xvs;!f]j!9Y:2/˳/="_7DVGg^2PXa5}"h|Eo@)KZ -:X/[c30q -q Gظn?UJD+b"JIGIJZWI:Jpj6޲ޗ׍Ew"p88yhqjBsxx 7㮃KCVwZ{NTw~VI2; D"O8$U.y\ZFE_.o\™nOJM1$si]H8?e=)F=a=*Q06 jf踿e}%Bk l]ɥNL ,sJ3zW uSob ;7mǕSs-Tyv>G*D6ϔM*cP>7*8S[&>.Vϣ\(m[x%߿yOBK"L] t\<|vI x'94u^#_ҝ nU!']<ċ,uX'̺`uVncZ"7IRD7XaX5oh$99w7t.* >}0 \ٓcoOLMv-v=a8$Dob.2 0"n(3 QʼQE!R3gGi6Sw)Z&nڅG= *ЬJ,i`A +oI:M:j?\ w3JQP1髧c`ۓ*!?v %@H7Qq} 8*ʜР7UMІk>nj"L>F A.飺&n钏 (π5Pf始-QYK`-JF/J伪`zw(PpӴcWh> ;y͙?m4ETi'N,ozb3.g(h\r+_$flD&?#hG]yݛO-R$ u]ڨ>PoC鋊ٖ9g6odԓ9TS'b椞ͅՅtGmnFn k3]p.]bnZKrwx7ЫsڌCk6݅zR'AvB,Ih3P5n&YX|hHzO `,0V@FBb>}g>JC*rKڱ)0$Xai+3MF2د& w:)3,;u辠*٧U۔ŭ,Cw(9yAPn"i?/[˜P?h^#LCo>d~:7?I d)nkYmm Хo@fW:'|@[uOX%06 +| n5aM3?X|J8;{}i8eM[hu +QJĚV()Dt7 V"&1])ynC2 9 +'dD&(rF66* s}߲ctNQ7ڡuŊ)iJ; kGu܉bu*Zyۗ5bı#Sb!3&3,]I+O&f[Gvz b,LJ:F +m@CmbAo[}[G:~Ҳ}d6y}$$$~`Xc_f*TTr+{ڳ/"ڥ =5~JCdaR+|O;Xl5J*X*Y%T(u'qQq%{*MUꭼq!dT\ UK5 4x,5]3T^ڬw'cAt&qD@ubidjF+SaH5F@lfz'N^7#3(VJubjkԈLKg !U)7D8컎vB| Yr2PnJk]&2z4Ea+ " Jn"z9AYʥdbnB2nY@!5֎L[ybjŷcO.0UguX2.KxcdyYfJs_s=oܜ`96c&Kn_P[b2ϰdlJ U`h%ܨa@DoIJn~'0e*Kn8Z-I0S%w(sTҕ0Pn%wޱ,r']Au4 uّz_7} + +pJ +endstream endobj 116 0 obj <>stream +وU+X=r#(qVQZmL&<ٱ:swα0֔#PEvA*ő׆Cdiq,VЖ BA96Ώef2ܜ Wd\”00YBsmJFDŽ]D!p,Jϒb"vZr'By-WR NۢjzWdoY0buG! xexx?#OL727v~U_l%RPe61hi|l-g۸l,>T01ؘޢK6fT=9p*ٸI5+2OcXD߷(yi/a1l1}@6AAP}>emYX~^FȈC;w]lH5;zTGd,\icƬ&C%bj޴5cr`6rNpZWz4V,&T!8$ؠo8xs-9pOc5|-Y̬옑oҰZLvimMz +7)(㔀PX4*jM^9`Ps,tl۷ j1.0Niu6F)0l KE-&ۮcՕ>WR˚Qܬ [™׭AicARք_MjXXu36.ql^@skʚ$B92N흍H9fcUZ N`c*[pbc亠65|}#%xs +._As!|qIk&L?ewYzgx`W8z !c&\ŷUTuZ#m晦[))VV.Cb㴓'bb SSZ*P4і챙ѩŧb4ʟ ZQ*U)uYEeÇUƄTIq*#ᝀqZɴalKfYWpjVž*\I(YHSxkVDg<&pb9|aLxw;$-}2VL dTA$[/z +»XddO hJؐUB +/MRY EeW=ؙS1,V +̡DJ&)^^~/&&U1E>Fנ/*֨=Uq{*'Sh+B$B.ȲX6\5̯/`=fmxvU1bWu^+s#JƋ}lifB^(i8Ew3T_ؐaږ)wWJt)f1&PJC +ѕY48\In\gU͟wEnsY[1=6br msdIMtXn LK]b~*۾).`6Pl9 V󛽮Bd!u#~4#@NdLm&#cQ$Gv]fi=)n^AqX=)olK(-=zV_jlќk2ZSմΜP&c sv ۦ#ŵәAIA{d4&-J?Lx_Z6\Fܘ%jxg_3ܩ~Z+EdmB(1S[ +c;R fj W"4ck8?rތcNmG{:0ӰbԌ?"ؼ"{-iˠX2xA@MAk -$}|qYD5@}@ؕwon%--mpqqDpXAcz@aewuiyya.Β?Gu@lN!ռv9sU/_'뭺=ǐ^Pp2vYQ~/2zPWa6=#(gKTp wb"DG2j6~!e +Xg2S4^u~O|#䯨8Ku>"o mz!:8+řzqR)֒\ѩ ;)@Mb;fwUSR! %]\4ޓ;sP1B|1cbT ֻͩLuI*e*zƟ8e8N\ ύ"P~ %THl.p RFCp، ֒-3T^o2TUoKn@’D!EEY?S][s\w6[8F[)KAm=l0G*4N2*jl|TUMo dQ&X=&uvrph%͛6 =7š$]lpvEuMنؠXL>SO|H0Yz6pQ%!6Yt@`?ڌ j͘l(qh3*7 +&K~Hj3t`x[šUYZ; 6e/z \ kQœCt]s8e!=+3:] ȅ%Ľ;$W&wzAYc%!<;jN"l@@Ë (gYB Ed2T| z7=kY%2[J^o*.+MD;Æ :*!7hU|{aKH1Q5 Vm/5Z")fm6 +t*R +Sz6r׭^Æ~VKō3C +[tI̫KE0wͻt%-zuR^) hL!S>_襒,PTҹ*)rVjEZ/⩁J☚6^*T3lV ? ^jR_ 2NQ{7B2"Tj(a2:ւ2bAЯĥKŐ;Y̿4BA&`=Q$gІcgs$s.L-'YǏFdq{O22ӡ4O6Nk2>]nh-f؉0Y'UZLk28!.W@8Le%XW1b@](sH"砆UZe20Fj VTImj0 ̭87РgU #; fv{D;CxYYMej{ȑΑG3'A)!py`̊uqI1Glܓ`I}"{-tzZ5,"V_UjvzYe$q[CoV*ʩiG`x&\׻'ڡ*_VbKqY*QJD r띅r)[E9ECUpEG**(mPC59`X~'f,ҖvW'h|m7 . AO[\W8ý3G+xf|P8!=8Uq,OС‡"tM=`"xjn8\1U(!+#L:1@!g1cO0ԭ*w962npf3:Ѓw rLփH?/q%%w\Uw*ב;eR==Ēiʌ~Xˇ=lY@4ont]j.ʣCB=Ko &Z2iAA[Qܒ 6'ϪU)"FyK<@p +z4xbSmw rb#3_"|ilAu[eٙ/aVz쥀39pz)8:f2MYDDq3?iyΨ1ЩFQȐG3vbEKlwJʴS1-wW3.'*"88*eq7U.@P7eC{\RId>16w-bh7/L:u|gavpߕ=_;mnC뷈.z6۱n? +q1xQ\K-ԅ$͙d{my(nOF<(L\ ``%9&MQEӊ,vBzf@j/O/7sOe/iUu ,Ne'^(xeߍA1%vƪ‾dlm +RShk \ļjs1١K<[Ȇ0vR/elx!#q"~8UoER(ۡN6R#;%~H#?TLǘ!+bM106Z2,9~Rv-7ewv )Ň( +%lx:ʄ!0KP/3my7d #K*p@DK2$>xB0([*su, 6[9-LˎW D+\۫Ŧ׮v\w&LM=`Q~Ň͖wY2Dϸbb@q?>P"XLK1hܴa`P +׵[喰k}*V%%aa46CÖCyĘzHBĶB<{䠶5Ĕ&–wQ~Hm[sEښ1^w|ezץhV{je]Z՛bEw{{ݬĿ*<R(vcl9C0a1Iѩ6;]Uˢh}W"oG I2LxSB~'pR+:ʥܱf.Өn>< =uVQ]e=)8 .3N\ pxF(.'SukEdbVNXlZi`C(q$C|r?'@ݴʾ;0mj5gqVcU +cU 6u$4G.H0(L0(oR M+Je2q^_\uw[Z8rc0:Ig@B[f^11;'n yK0(O+`_ ]f&,t|Su{;\c,q/ߔ,\3!$SCr=ug" ׻^[*'.lf&fb4)d,48)IhT̞l3M-Lpg?EkP[ñBYffl—a S}g#Se7SsFN*b|:PEqf5kB(!sK*IJc0V|hU: =;VkVxKqsZt<#7$Zi;4f4rϬQ|zAeӷpͳ?Hf]4\}iSän4.3]w% JeD +zp04RTIg P\эO>s +@aX@ cQ ( ,@`,(`P0@ ovW\EBY.,{خ) dT˶vcVTNNR=5uyAArLdQhx!F;@T%r4zum]Ŝy׍yFь֯̾7ې*nYh~O{ /X5/ճ37^]>qT8od<,5>^ŵ,>5.KFVh, 7?l˧7kqVг +z>4ޟ K  VU:q=h*SfSU-CxFt#wlrRe٤R=#7T>,-:aiiը;- 22RCR 05K@Aȁ BP&f!b!B !"Q"\qbI 2m ?OKp&RӤN[ʂyQ1$Ƴ ( տ=⃹FLlkT4Ejy N#d`"9KvMYqɨ.7#˾'dSW*ZXSV\Țm%xwXqWd;f6#8<V7ҒBe<7&| FM!ǀ2ok̭uuΟ'[Vٍp(@`h| Z* +k-#3 ݼN.n7? Y~~ھM(3 r߷V5'TPT,oH^X2{s쨊F SU"+ʻs<1.c_սL#^P__=63DfV.}^Ҧܧ܃iA=Aم& vF/_~Tĉ[96[tZbAs$jNs8"@ֈŠnt=|+Y8 Q\k%HN'^\8Sd%S)HX6葨!m!1Ӻh:AWJ 2JK H[wYW(GL@](1z(\tO=] xXLM58ghT2Dr׶ZK"1:҆F!](II}43$iKJqhzᝥ,y'%M%"A&PAk3SC2l0H:?ӲJOV=M"dTG64 8?k24F;t[)AC~x/|*e efٯq`ئyHE1nה44…MfD -š%wl% h:-G!QXYxī8|͎GT!h8lkߦ <"%Ȁwjy3<_$!c"?tꅌ3aN,2DcMXt"  N>*uch*T{ +~ŻV7OIʻH0G0,Ƶ`zghxMĄh?ʣwVroy]T?Y@ +-u.WD6 #V6R f?GXQ++=&]kp';fv4!!u_nd?C{p2WfI3n4 jwłi+lz7B@bn27sXK|.EɎDP߱KxH)YkgE3b,WL$Ho +)qy̱qYrVmL^xT Iђ< 8~+ːBA^GAu6aÈTM|JD}P' +Ih'$2\FQaִmgy",٪<6\=llװdCfrBu͒J ++ݗUe!aQ Lճ3*<t?Sa罍 +endstream endobj 110 0 obj [/Indexed/DeviceRGB 255 117 0 R] endobj 117 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> +endstream endobj 107 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]>>/Subtype/Form>>stream +BT +/CS0 cs 0 0 0 scn +/GS0 gs +/T1_0 1 Tf +0 Tc 0 Tw 0 Ts 100 Tz 0 Tr 12 0 0 -12 -103.0967 -61.2783 Tm +[(T)7 (his is an A)11.9 (dobe\256 I)-10 (llustr)5.1 (a)4 (t)5.9 (or\256 F)25.9 (ile tha)4 (t w)4 (as)]TJ +0 -1.2 Td +[(sa)8 (v)10 (ed without PDF C)11 (on)4 (t)6 (en)4 (t)3 (.)]TJ +0 -1.2 Td +[(T)71 (o P)5 (lac)6 (e or open this \037le in other)]TJ +0 -1.2 Td +[(applica)4 (tions)10.9 (, it should be r)10 (e)-28 (-sa)8 (v)10 (ed fr)10 (om)]TJ +0 -1.2 Td +[(A)12 (dobe I)-10.1 (llustr)5 (a)4 (t)6 (or with the ")3 (C)3.1 (r)9.9 (ea)4 (t)6 (e PDF)]TJ +0 -1.2 Td +[(C)11 (ompa)4 (tible F)26 (ile" option tur)-4 (ned on. )41 (T)7 (his)]TJ +0 -1.2 Td +[(option is in the I)-10 (llustr)5 (a)4 (t)6 (or Na)4 (tiv)10 (e F)31 (or)-4 (ma)4.1 (t)]TJ +0 -1.2 Td +[(Options dialog bo)14 (x, which appears when)]TJ +0 -1.2 Td +[(sa)8 (ving an A)12 (dobe I)-10 (llustr)5 (a)4 (t)6.1 (or \037le using the)]TJ +0 -1.2 Td +[(S)-3 (a)8 (v)10 (e A)6 (s c)6.1 (ommand)10 (.)]TJ +ET + +endstream endobj 105 0 obj <> endobj 120 0 obj <> endobj 121 0 obj <> endobj 122 0 obj <>stream +H|TiPYj (FfaPQ@TX@PQD[N9ElqEiUDYdD@A<QNu`˓ql5gm̗}_^$`8ܼMUr*m0ʘ/q~)T-F?A4ez՗S,1M&"R-[lokK'~ѢEvl"_)>VT"T*yRa'[&PTJR1nQ,X-SG)U2  +YJPBe,8\&`|ƒw IhURmpÑJ2Bypaaasqþư&Rs36b$vOb/ Ž`9XցQ…E<4]io:&Z%J$;T;xǬl<ʼݔ &~ &"ՔOH&!ݥ+[-ioW$$L=d(TGV8[^t7Wӹ6E֤Oi`sx4qՂ:HYvbJx3X@+K*/GVp穇,/mJ{QHh"nQ4Ds$NmV͹fl@Gh yb|C_qA> Zf 8|ֆ#Qf|')a(¶ 9%u׺gCk♁>Qל/~.oonia[KtfgvpC xGKR8q984-#}>xf5wL/:Pڇ~')Ww<4wswS0H; WAg~@쭠F, S}WlzZ[kêދQ)ɛmp>էv)z|_DaNd#If]|˯yCOc haBLG,U0  <a^pDuAĢ\` 3a.G +uCQ6H䉂┋̫s7ڤc|Vx80k'sCtFNizcpT,G%&7za88B^Օ= ,y>ǭ-*ڥ+Z޴J(b{Dy}s1若.m;{T)]sm +J*cOp&j''W4 aY1 W-ltY_S+wX'dyk\T[UTTސ]|<&K-is1Syx=G=u+5K7aYI3~ `j2.A>a,(!k(3 g5wM˾$ؾjǮڶ8VVڞcx[u7k n\%rLެ 0ټ7s3sTG]W@鰤MZjojMAijn ' 2rJ5)? ^2Aǧ$$'F#> +/?z׏nuiZ'vvӎWs4t 9pr@b4Y .cΓYFsit#)?CX`_? 'Na!Bi4z 1Պ]01w&E0$d줛HOVk ١eH:8>m/ >$,_lE]}i̮kj:BXpP.BeVwBzz񞓌w iGRYgy"x0膉>a'ҲNe7r0*uwPٰ08`Ŝ!a(6R2Ca (~ %o]2+|wCaeEŗR2MqKaj6Iw 8RY+(7!lPg5(c9eW׵0˽]9tmŹ최Wro; BVz M gic5H3=yG[ά7/UGt4 fosaluS&aeT,A?, a[E:6EEt +qNJttB3l&ܷ\{f$&;slpl+"4+B?y%8 2"fD8}ptX2UEUS $B;,Cnh t'sO>.At79Hڡq~s1huM55'WWCZ*WQ\1 +endstream endobj 119 0 obj <> endobj 118 0 obj [/ICCBased 123 0 R] endobj 123 0 obj <>stream +HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  + 2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 +V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= +x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- +ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 +N')].uJr + wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 +n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! +zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km +endstream endobj 124 0 obj <> endobj xref +0 125 +0000000004 65535 f +0000000016 00000 n +0000000076 00000 n +0000049446 00000 n +0000000005 00000 f +0000000007 00000 f +0000049497 00000 n +0000000008 00000 f +0000000009 00000 f +0000000010 00000 f +0000000011 00000 f +0000000012 00000 f +0000000013 00000 f +0000000014 00000 f +0000000015 00000 f +0000000016 00000 f +0000000017 00000 f +0000000018 00000 f +0000000019 00000 f +0000000020 00000 f +0000000021 00000 f +0000000022 00000 f +0000000023 00000 f +0000000024 00000 f +0000000025 00000 f +0000000026 00000 f +0000000027 00000 f +0000000028 00000 f +0000000029 00000 f +0000000030 00000 f +0000000031 00000 f +0000000032 00000 f +0000000033 00000 f +0000000034 00000 f +0000000035 00000 f +0000000036 00000 f +0000000037 00000 f +0000000038 00000 f +0000000039 00000 f +0000000040 00000 f +0000000041 00000 f +0000000042 00000 f +0000000043 00000 f +0000000044 00000 f +0000000045 00000 f +0000000046 00000 f +0000000047 00000 f +0000000048 00000 f +0000000049 00000 f +0000000050 00000 f +0000000051 00000 f +0000000052 00000 f +0000000053 00000 f +0000000054 00000 f +0000000055 00000 f +0000000056 00000 f +0000000057 00000 f +0000000058 00000 f +0000000059 00000 f +0000000060 00000 f +0000000061 00000 f +0000000062 00000 f +0000000063 00000 f +0000000064 00000 f +0000000065 00000 f +0000000066 00000 f +0000000067 00000 f +0000000068 00000 f +0000000069 00000 f +0000000070 00000 f +0000000071 00000 f +0000000072 00000 f +0000000073 00000 f +0000000074 00000 f +0000000075 00000 f +0000000076 00000 f +0000000077 00000 f +0000000078 00000 f +0000000079 00000 f +0000000080 00000 f +0000000081 00000 f +0000000082 00000 f +0000000083 00000 f +0000000084 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000262496 00000 n +0000049853 00000 n +0000261307 00000 n +0000050064 00000 n +0000050447 00000 n +0000260742 00000 n +0000050523 00000 n +0000050747 00000 n +0000052481 00000 n +0000118071 00000 n +0000183661 00000 n +0000249251 00000 n +0000260792 00000 n +0000266782 00000 n +0000266668 00000 n +0000263014 00000 n +0000263099 00000 n +0000263483 00000 n +0000266819 00000 n +0000269469 00000 n +trailer +<]>> +startxref +269690 +%%EOF diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Amb-Marbles.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Amb-Marbles.svg new file mode 100644 index 0000000..331e518 --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Amb-Marbles.svg @@ -0,0 +1,107 @@ + + + + + + + + + 2 + + + + 3 + + + + 4 + + + + s1: + s2: + s3: + + + + + 1 + + + + + 2 + + + + 3 + + + + 4 + + + + + + 1 + + + + 8 + + + + 7 + + + + 6 + + + + + + + 99 + + + + 88 + + + r: + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Amb-Marbles2.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Amb-Marbles2.svg new file mode 100644 index 0000000..edca8d5 --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Amb-Marbles2.svg @@ -0,0 +1,99 @@ + + + + + + + + s1: + s2: + s3: + + + 8 + + + + 7 + + + + 6 + + + + + + + 99 + + + + 88 + + + + + + + 99 + + + + 88 + + + r: + + + + 2 + + + + 3 + + + + 4 + + + + + + 1 + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Amb-Marbles3.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Amb-Marbles3.svg new file mode 100644 index 0000000..fb1a9be --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Amb-Marbles3.svg @@ -0,0 +1,71 @@ + + + + + + s1: + s2: + s3: + r: + + + + + + + + + 1 + + + + + + + + + 2 + + + + + 3 + + + + 3 + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Artboard 26.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Artboard 26.svg new file mode 100644 index 0000000..2f923e7 --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Artboard 26.svg @@ -0,0 +1,12 @@ + + + + + + Switch results: + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-CombineLatest-Marbles.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-CombineLatest-Marbles.svg new file mode 100644 index 0000000..4f3da5d --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-CombineLatest-Marbles.svg @@ -0,0 +1,88 @@ + + + + + + + + + s1: + s2: + CombineLatest: + + + + 1 + + + + 2 + + + + 3 + + + + 1,a + + + + 2,a + + + + + + a + + + + b + + + + 2,b + + + + c + + + + 2,c + + + + 3,c + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Hot-Marbles-SourceAndSub.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Hot-Marbles-SourceAndSub.svg new file mode 100644 index 0000000..0c92c45 --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Hot-Marbles-SourceAndSub.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + D + + + + E + + + + C + + + + + cold: + Events availablefrom hot: + Concat subscriptionto hot: + cold.Concat(hot): + + + + + 0 + + + + 1 + + + + 2 + + + + 1 + + + + 2 + + + + 0 + + + + + + + D + + + + E + + + + A + + + + B + + + + C + + + + D + + + + E + + + + C + + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Hot-Marbles-SubOnly.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Hot-Marbles-SubOnly.svg new file mode 100644 index 0000000..682cb96 --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Hot-Marbles-SubOnly.svg @@ -0,0 +1,95 @@ + + + + + + + + + cold: + hot: + cold.Concat(hot): + + + + + 0 + + + + 1 + + + + 2 + + + + 1 + + + + 2 + + + + 0 + + + + + + + D + + + + E + + + + C + + + + D + + + + E + + + + C + + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Hot-Marbles.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Hot-Marbles.svg new file mode 100644 index 0000000..e888091 --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Hot-Marbles.svg @@ -0,0 +1,103 @@ + + + + + + + + + cold: + hot: + cold.Concat(hot): + + + + + 0 + + + + 1 + + + + 2 + + + + 1 + + + + 2 + + + + 0 + + + + + + + D + + + + E + + + + A + + + + B + + + + C + + + + D + + + + E + + + + C + + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Marbles-Cold-Twice.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Marbles-Cold-Twice.svg new file mode 100644 index 0000000..d423eb0 --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Marbles-Cold-Twice.svg @@ -0,0 +1,107 @@ + + + + + + + + + Concat subscriptionto cold: + Concat subscriptionto cold: + cold.Concat(cold): + + + + + 0 + + + + 1 + + + + 2 + + + + + + + 0 + + + + 1 + + + + 2 + + + + 0 + + + + 1 + + + + 2 + + + + 1 + + + + 2 + + + + 0 + + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Marbles-Three.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Marbles-Three.svg new file mode 100644 index 0000000..2ee31d2 --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Marbles-Three.svg @@ -0,0 +1,79 @@ + + + + + + + + + + s1: + s2: + s3: + + + + + 1 + + + + 1 + + + + + + + + + 2 + + + + 2 + + + r: + + + 3 + + + + 3 + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Marbles.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Marbles.svg new file mode 100644 index 0000000..739beb7 --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Concat-Marbles.svg @@ -0,0 +1,111 @@ + + + + + + + + + s1: + s2: + c: + + + + + 0 + + + + 1 + + + + 2 + + + + 0 + + + + 1 + + + + 2 + + + + + + + 8 + + + + 9 + + + + 5 + + + + 6 + + + + 7 + + + + 8 + + + + 5 + + + + 6 + + + + 7 + + + + + 9 + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-GroupJoin-Marbles.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-GroupJoin-Marbles.svg new file mode 100644 index 0000000..e7e92e9 --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-GroupJoin-Marbles.svg @@ -0,0 +1,164 @@ + + + + + + + + + left: + right: + Right observablepassed to selectorfor 0: + + + Right observablepassed to selectorfor 1: + + + Right observablepassed to selectorfor 2: + + + Right observablepassed to selectorfor 3: + + + Right observablepassed to selectorfor 4: + + + Right observablepassed to selectorfor 5: + + + + 1 + + + + 0 + + + + 2 + + + + 3 + + + + 4 + + + + 5 + + + leftdurations: + + + + rightdurations: + + + + + + + + + + + + + + A + + + + A + + + + B + + + + B + + + + B + + + + B + + + + C + + + + C + + + + C + + + + C + + + + C + + + + C + + + + + + A + + + + B + + + + C + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Join-Marbles1.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Join-Marbles1.svg new file mode 100644 index 0000000..27bce8e --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Join-Marbles1.svg @@ -0,0 +1,153 @@ + + + + + + + + + left: + right: + Join: + + + + 1 + + + + 0 + + + + 2 + + + + 3 + + + + 4 + + + + 5 + + + leftdurations: + + + + rightdurations: + + + + + + + + + + + + + + 0,A + + + + 1,A + + + + 0,B + + + + 1,B + + + + 2,B + + + + 3,B + + + + 0,C + + + + 1,C + + + + 2,C + + + + 3,C + + + + 4,C + + + + 5,C + + + + + + A + + + + B + + + + C + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Join-Marbles2.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Join-Marbles2.svg new file mode 100644 index 0000000..6e44dc2 --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Join-Marbles2.svg @@ -0,0 +1,118 @@ + + + + + + + + + left: + right: + Join: + + + + 1 + + + + 0 + + + + 2 + + + + 3 + + + + 4 + + + + 5 + + + leftdurations: + + + + + rightdurations: + + + + + + + + + + + + + + + + + + 1,A + + + + 3,B + + + + 5,C + + + + + + A + + + + B + + + + C + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Merge-Marbles-Multi.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Merge-Marbles-Multi.svg new file mode 100644 index 0000000..687ded8 --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Merge-Marbles-Multi.svg @@ -0,0 +1,79 @@ + + + + + + s1: + s2: + s3: + r: + + + + + + + + + 1 + + + + 1 + + + + + + + + + 2 + + + + 2 + + + + + 3 + + + + 3 + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Merge-Marbles.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Merge-Marbles.svg new file mode 100644 index 0000000..47105ec --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Merge-Marbles.svg @@ -0,0 +1,95 @@ + + + + + + + + + s1: + s2: + c: + + + + + 1 + + + + 1 + + + + 1 + + + + 1 + + + + 1 + + + + 1 + + + + + + + 2 + + + + 2 + + + + 2 + + + + 2 + + + + 2 + + + + 2 + + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Switch-Marbles-Bad-Merge.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Switch-Marbles-Bad-Merge.svg new file mode 100644 index 0000000..19c3d82 --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Switch-Marbles-Bad-Merge.svg @@ -0,0 +1,125 @@ + + + + + + searchValues: + results (I): + results (In): + results (Int): + results (Intr): + Merged results: + + + + + + + + In + + + + Int + + + + Intr + + + + I + + + + + + + + Self + + + + Self + + + + + + + Start + + + + Start + + + + + + + Into + + + + Into + + + + + 42 + + + + 42 + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Switch-Marbles.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Switch-Marbles.svg new file mode 100644 index 0000000..0fc5f6b --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Switch-Marbles.svg @@ -0,0 +1,113 @@ + + + + + + searchValues: + results (I): + results (In): + results (Int): + results (Intr): + Switch results: + + + + + + + + In + + + + Int + + + + Intr + + + + I + + + + + + + + Self + + + + + + + Start + + + + Start + + + + + + + Into + + + + + 42 + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Zip-Marbles.svg b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Zip-Marbles.svg new file mode 100644 index 0000000..c620768 --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles-Zip-Marbles.svg @@ -0,0 +1,95 @@ + + + + + + + + + s1: + s2: + c: + + + + + 0 + + + + 1 + + + + 2 + + + + 0,a + + + + 1,b + + + + + + + a + + + + b + + + + c + + + + d + + + + e + + + + f + + + + + 2,c + + \ No newline at end of file diff --git a/content/GraphicsIntro/Ch09-CombiningSequences-Marbles.ai b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles.ai new file mode 100644 index 0000000..d1aa6f7 --- /dev/null +++ b/content/GraphicsIntro/Ch09-CombiningSequences-Marbles.ai @@ -0,0 +1,2381 @@ +%PDF-1.6 % +1 0 obj <> endobj 2 0 obj <>stream + + + + + application/vnd.adobe.illustrator + + + Ch09-CombiningSequences-Marbles + + + Adobe Illustrator 28.0 (Windows) + 2023-11-02T15:23:12Z + 2023-12-14T11:08:40Z + 2023-12-14T11:08:40Z + + + + 84 + 256 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAABUAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A79d+VdK1OVprybUrGRYb mAQveuCYpGQySDjLKOI4rSp28MBFtWTEJcyh7n1fKl9I2k6dfa2dS5vdk3Kv6T20JdEX1WHHmD/t kgZHk3YMEd/VX2r087+YmnhjHlO7pLJHGW9e2qvO3M5JBYbDjx/XQ7Y8R7nIOGH877GQeX9TuNU0 Sy1G5tHsZrqJZXtJCrPHyFaEqWHv1yQLROIBobphhYuxV2KuxV2KuxV2KsSXQPJHmVy62Mn+gJNZ 8JLee0CiWQmQKsqRc1Z4+RpVX2J5DicBiC1ZMMZ80Dq3k/8ALe31O3tb62aO5vhOY3LzFKMgST1Z KlV5gU+M/FTjuPhyJiGzFlGE1HYy+PJBDQvykDwTBGLLcrFFRrmqvHGYVk2PIRhDtN9mm3LjtgqL P+UdvqHOunNnHl7TtN07RLKy0xPT0+GFRbIeyEch7Dr0Gw6DbJgUs5GRsphhYoLUtYstOMIuvV/f twj9GCaf4qgb+ij8ftftfPscVRc00UMLzSsEijUvI56BVFST8higmkmg85+W5rexnW74rqBAhR0d HQkdJkZQ0ND8J9QCjUXqcFhh40dt+fJFaR5g0nV/X/R83rfV24y1VkIPJlBo4UkNwJVujChFQcQb ZRmJckxwsnYqxPTvN2lWMMyXt/qd+6vO5nuNLuY+CxScSg9C0iSg5DhXdx8S8hvirWsa9Y3t3ZW9 ncXlpd3cN1HZ3SWUrFX4xtypLFWlO6j6cBQMkYyAItKbbUhFdWVxNrGpPbRs1hNb/o1R6t7y/wB6 D+4JDfCdqcvbfIgoOoxkGo16kJba3Ovl3RZR5h1VfW1BYBMNLAaVebKIiggIUGn7NadKbUAvZzjA cRFfw96e6fqVw3neez/Sd9NEFiIs57T0YFBhlNRL6aciStf68dpA7tMojwwa7038w6hFp8tncTXt 5bwqzc7a0tGuxPuvwuI4Z5Fp24kbE+FVk0rV856E1ytspuzMyeqALG948K8alvR4ipO1T+rFSh7H zd5flsNHmhtbmKDUOI0+P6nMvp1jJFV4fB8NRtgtqGSO3mkx8w2155usXsbzULK1mS6gls49PcLN LbyHk7vJESKM+1N+tQK1yN7uRhIlCRA5FBLrtx/hXUrr/EWqM8OozRC7bTuLRhbkJ6XAwr8I+yGJ G2AnYuRGA44iuney761N/h31vrNxz9bj9Z9I/WKfWeNfR4V5U248Pbj2yxxeqV6T+Z3le4nvrbUd a0O0ubFpC8Nvq0FyyxRsQ0kwKwmLivHlUbMSK7VKhNtZ1PVbPUbRoI7U6X6M82oXNxMYvTSMKQwP Ftt9/wCHXAWJ4uIAcksh8z+YJpbW3iXSZbq6c3MUKXpLNpwIHroOFWG4+P8A4XBZY1l7hz+z9abW PmbRZdLsL2XUbXheslvFKsyGOS5bYxRsDRm5A0Aw23GB7kYNW0t9RfTluom1CIK0lqGBkQOrMpZR uKqpO/8AHDaOE1fRIvPGueYNLjtjos+jpPIspa31i4e29Thw4mJ0rsvL4/hPVfpUKOheZZbyTSFm 8z6HeS3iqZLaxSv1ljA0hFo5u5O6+oPhf4FO37QVRS3fn1Zwn6NsTbhoAHNw4fgV/fVUJQEN4Vp2 Ddhu1SM72Ap0XmebT7m6XzTLY6VEZI007/SAfUEjui1LhNzxH9Bjfe2Y4zldj+xGQ+cfKkzWqxat ayPev6VoiyoWkcELxVQa9SMbDYcchdjknFBSlNuv09cLB5Tol1p9nfzyRXnkrRJUlnlS4sgkrv6c 3C5dlE1oVfixWRjXi5oaj7SrLI9X8u3Igs9e1XS9Uu7lp0tXgQRxemOLcCrTXIDheNW5CvYDpgNc keJwyG9HosN/+WielOkmmq6sbOCQqjANWn0rX9vp774NlOqvbi5y+1iNtd6d/hrRZZNS8terLfok rG2PpsCxqq8ZBRun2gFpTfpWHQcnPN8cvq+lkOlvZf48kjhu9JlZFjIgtIeF0tYJalmDMKHatO3H p3mObRK/DHPmfcnXnHyuvmGzitnttMuUXkHXVbM3iANSvBVkhpUDfffbwyTQ1bWXnaC4ijS70qPS 44URbeOzuFkV1JB4t9Z4cAoAA4f0xVYtp5/NwCb+wFtygPEwSepxC/vuRD8SS3SlK+K4N2qQnexF IfUdB84XkR53WlyzC9ilQz2kjqLaJywFPU+3Rmp8/tYCC34JGJPF51SHt/K/mqNNEWWTSitjcNLd rFasjBGlDfuH5UBIHxfCN6dabgROzfPLE8VcW/n97NMm4yVGK+dhHJptpwf1TKPULKQCnAlvSG7C tRx7dcVQ+n3ch1eaHUrSztDBJ6OkukivI/KJXlCgqjA8aVoBt49cF7sYiRJ22HVNxZWY6QRj94Zv sL/enrJ0+179cKeELV0+wWGOFbaJYYX9SGMIoVHqW5qKUDVJNRjTLiK/6pa/WDc+in1g0rNxHM8Q QKt12DGnzxWzyVcUOxV2KuxV2KuxVK9AktvqzCKC0txNLNJGtjKJopFRwpk5BIhyrTkKbHapxVRu bDynca5bz3K20ms28pltubKZlk9Lh8K1r9j+vXAatlCZogfFM797iOxuXt+P1hInaHmaJzCkryJ6 CvXE8mKhoU17Po9nLfPHJePEpuJIl4IXpuQnJ+PuvI0O1TkcUjKIJ+xUdk1dirsVdirsVdirsVSa +1TyiBDPe3VmUDPBA8jxlObMgdBU8eXIrXv+OKDIDmgPqH5bSauT6WmNqcJF0R+6LqWUxh6dBUf1 wULWMgCQOZ5qq61+X0qgJc6bIHaWIKpharQkiRaDuOJ2x2LEZInqm0EOkaHpUVvH6On6ZZRpFEGY RxRoKKo5Mae2+IAHJmtvPMGh2Tsl3fwW7pIkTLJIqkPIFKihPcOMNsZSA5qH+LfLH1Nr39K2v1VG KNN6ycQytwI6/wA2C0eJGrvZXsdf0TULmW1sb6C6uIa+rHC6uVoEJrxJ/wB+L9+G0iQPJFS3lpDN DBLPHHPcVEETsqvIV3bgpNWpXemLJVxVQuL6ytnRLidIWkDMgkYLUJQNSvhyGKoD/Fvlj6x9X/St r6/rfV+HqpX1fT9Xj1/k3/DrgtjxxurSTWZZ9SsXWx1bSWvCt2tpdyRerbpNG8QAdfWPxo2z/F/s cSGQjEn1CwgNS1m9a1ivtO8w6Ml9ZsIdcumtnJdERuaxUlYr+8o3Qig67ZUcsQCb5c2QEbJpkNnf +SLppYrWayc2rssoUoODzLVhXbdlbt2ycZRJ26NQxxHRML6+j/RBvI7mC1jdUdLi7FYVDkU5jnH1 rQfF1ybNjfmCa8TVzO2raXaaNZXJm1O2vYDLMQLWP0zGwlTiwY1rTptv0yEyALPJNRI3FoZNQtoN TkjvtX0d/K00SXNtYRQlJAESRpXdi8icSxQqKV2294HJEczzYmEOGqZNY33lt54prGS3ae9LBGiK 82PEu1QN+kXcZZEgix1URA5K99exwahp8D3lrbtctIkdvPT152VOXG3+NN1UEt8LbeGSSj8VYz5w vtRt7zSrfTL+0sdQvZGhtxeI0glAkikkjTiy0YxI3j/EQnMCh3s4cN+rknf6I0z6x9Z+rR+v6nre pTf1OPHl93479clQafDjd1ugNQ03zB6dt+jb+JJ42b6xcXMKuzo8iMVAjEaj4FI+754lZiRGxpy6 Zrv6QuC9zZNpk0Y/0b6qamYn43f958VQB3+jBS1Li57IOHSvPCHk2p2POSSdpylqyF0YFbep5n4k UICfb9rpiAWIjOxZT66tGltPq8EzWlOHCSJYyVVGB4hZFdKEDj06dKHfJNqT6l5e1i71eK5h1iS1 00yO17pqw27x3CNAsQR2ljdqAqTsf6YCGcSKNjduHy9qq+YZNRudXku9OKJHb6ZLDbhYhwdZKOka u3qMyk1NNh4YK3QSK5bp6IYgqKEULF/dAAUWg4/D4bGmSYoXULW8mAktbloZI45QkfFCju6UQuWV nHBt/hYe9cVXenqv/LRB/wAiH/6rYqgb3T9bm1HTbhZrNobaVmuOdufU4NGV/dOZG4sTTISBJFKn GTVCRavpUwjMN5DIs1BCyyKQ5JZaIQaMaow28MUGQHNELPA0rwrIrTRgGSMMCyhulR1FcVsXSy1v LS6RpLWZJ41biXjYMvIAGlRt3xUEHcNXF9ZW7qk88cUkgZo43YBmEY5OVU7nioqaYpXT3dtA0SzS LG0zenEGNOTHoBjaDIDn1VcUuxV2KuxV2KuxVhesNoxubWbVbvSDeiJX06e5jpE0n78gpWXZQn2t z4+GAsSI2L72O2+saS1lp96mp+Wjq979WTUJmhIjkgflx9MGWgJ/ap0+7IX82+OLH4kgLsD4/FOP LGq2UPmYaTo2oaS2kTKJ/qVmjC4MhSQM28jqB+6HboFp1wg7sIQh4QMe/wCDKta5y8bFLi1R7uG4 RbWcVllJjIHp/Gmy9X+E7eGTYoHRvLsjKJtetbGW+tbuSbT5baH0uEdSIq7t8QU+PgftYAO9lljA kV9vemtzrWl2uoW+n3FwkV3dmltExoXNGai19ozgMwDR6sUbkldirsVdirsVUJbCylkWSWCOR1AC s6hiAKgUr0+0cUUFFNE0VFRUsLZVjKNGBDGApjrwK7bFeRp4YKDLiNk9Svh0nS4JVmgs4YZUFFeO NUYA8tqqBt8Z+/GkDYUOTV/HqTAmzkiQiKQAPGWf1Cv7sq3IKAG6gqa4VSmC08+D0/X1DTzS7dpO FvIK2prwQVkPxDv/AMS8Y7rl3I4PjaIutN1mTVtPulks3ghZvrRa3YS8eDBfTcyPx3Y/fglEkgil TnJq7FXYq7FXYqk/17zXUf7ibXckE/XW2p3/AN5++Kt6nf6/BqdrBY2UFxZzMizTSzGJlqJC3FeL cqcFP37YCwkSCKCRxeYPzJms7gxeX9Oe+hWVfq/6S+ETBj6SOywtT93xJ23r+z0yMSeKq2bMlD6T bJrKbWZIYGu7SCCR1Uzok7ScGI+JQfSUNQ/LJsQp61ceYIIYzotja38xakiXd09mqrtuGjt7ssfb iPnilbFd+YDFctPYQQvHGzW/p3DTc5AKqpBiioD88VSi817znbtyGk2P1eO9WCeWS+9Phbso4ybx HcuyinWh+zkSSjGefH6e5U0rUvzBmu7VdS0OwtbZ7Znu5I795WS5EhAjUfVxyUpvy9+1NzDeO/NF m/JkUDXDKfXRI2rsEcuKfMqmFkqYq7FXnNxZ+W62jHUPKqlLpWjZ7df7wQTgCL/Sh+94liDv8Afb eoVZ9Nb2V9HC8ipcQqRLETR0NVIDdwfhbFWHRafPoGucNDt9H0uHUL17zzBzqs88UjFIpVoYlU+n E38/x02A5HAWUIx3vmyuw17RtQu7mzsb2G5ubPiLqKJwzJzFVrTxwCQJpixz8yLbRJ7K1Gq3eg2s Y9fg3mGBZ4iPRPP0w09tsqVMgqeSVBoDXJKu8kx6Rb32px2F7oUzzSBriHR4limDrGnH6yRPPzbg aiqrRSo6CpVRvmvyxoGp2Uv6S0y0vrZ5Vub4XSilIEqr8uLsOJjToMUEWhtN8y3Gm2ur3nmvUtKt tPtp3ksZbZ+CR2QUcRKXd+TL3YBanooFMiTXNnIDamUxSxyxJLEweORQyOpqCrCoIPvhBtiuwq7F WCtqNpclEi13V42jkdmcaRv+7DQlCXsCq1eQMvduO1V5VVZVb6tp0aRwy3YWT0klBuAIHZHLBSUY R0+wduIxVJLv8wfJEk1xYWmv2K6vJI9gg9VWZLlBRVcKa0V5V+k++C0wHFddEN+XvmCyvo74XOua Zq2qS3AdptPT0eULKFhB5PIz14sVNdlIXenJm48RA59WuF0mnnAgW0ROp32m8RI3Kwskvneij9hr a8+JK8kAXc9mAIws0FpN/b219cepq+o3Qi5TSRT6eLeBUEbEoJltIB+wXNZK8iOzKMVZC+p6PIjR vd27I4KspkShB2IO+KsT896+sAs1g8xaTp9rdzxQyxX49YSqokeRU4yIB8IDNXqqkVXrhEQdiLYZ DQ+LM7ee1mSttIkka/D+7YMBTt8OBmq4q7FXYq0EQOXCjmQAWpuQKkCvtU4qo/ULHnz+rxc/U9fl wWvq04+pWn2uO1euKhu3s7O25/VoI4fUYvJ6aKnJiaktQCp3xVWxVp0R0ZHUMjAhlIqCDsQQcVbx VTmtrecIJ4klEbCRA6huLr0YV6EeOKqmKuxV2KsYu/NDWVjJPLqOmyfuA0V0XaOAymSVAVRfXZgO IDAP2PTATSYwlLaIspYvnmX6nZwfpjRTqSTpBft9YPB2Cc3VVG0dR1bkwHQVOR4mPhZuEGt+vuV7 f8wdL/Sk09zrOmJoy2izRpHKXl5cyGapCk12+Hhtsd648YbPAy8XL0/pZlHIkiLJGweNwGR1NQQd wQRk2DeKuxV2KuxV2KuxV2KpNqOk6ldW1zEjWRea2SNRcW5mhMyF2Vmj9RfhDMppU4lbI5c0sk8u eZms7B0k0hdVW5iuNSnaxLq5pxlZDzQ+px2D0Un/ACcjTHiycIHErweV7gazKLiDTH8vvZLbC0js xHL6glMhFeTj0+9PHt3x4WfiT4r4jX6WSgAAACgHQZJDsVdirsVdirsVdirsVUrS6iurdLiEOI3r x9RHibYkbpIFYdO4xVLrrzTodq8ST3IV5Z2two+Iq6MVJfjXgtV+023TASwnkEavqutvM+hXNzc2 8d4gktWCyF6op5CtY3aiyDsSpNDtjaxmCSBzC+18xaNdapNplvciS7hRXZQDxYOCfgcjg5UD4gpP HauNqJgkjqExws3Yq7FXYq7FXYq7FUhtPM+mRQxxSS3105DN9YfT7ochWu5jt0QfaAG2/wB+KpJe aoltqWm3kup6jHZ3N1cW0dlHYtIhmmlcxsXMJK8Qvep8O+RPNJyRjGiNyeaV+Z/OFnHfm1tdX1mw urO5KXfpaWZROxtWdV/uF50UggDr8hUCRbNNRMrHLv2T7yV5gs9QMVql5f3V9FawSXU11Z+gsiyo WjLOIl7VKnnQ9i2+GJa8hHiEAMvySHYq7FXYq7FXYq7FUFpOt6LrNoLzSL+21K0JoLi0mjnjJHbn GWXFVSPUtOdFdLiPhJIYUPIANJX7I9z28cFsBkierm1LT1ErNcxqsDBJiWACMegYnpja+JHffkuk v7GMzCS4jU24BnBYDgG+zy8K42kziL35K6srKGUgqRUEbgg4WSC0/XNF1KW5h07ULa9ls5GgvI7e aOVoZUPFo5AhJRlIoVbfFVRtS09VmZriNVt3Ec7FgAjGmzE9OuC2PHHv5LnvrJJJY3nRXhT1JVLC qr4nG1M49/Jw1CxLBRcRljH6w+If3X8/+r742vHHlapDNFNEssTiSNxVHU1BHsRhSCCLC/FLHfLk vnxEt4NfstML0kN3e2E00SV5fuhHbSJKfsfbrNseleyqlL5B0dUVbSOGDjcy3ahraB1V53eR6fAr btJ3Y4KYeHHuS2/8iT0u/TtbG5S4mjnkAghjkkkQIA5qlAV4fzdB70xpTjienNLNS0K5jW7F5pMX CeIG7kW1hZTGlaVeNCF40rsajrjSnHHfbmy3ybKZNPlqsaBJOKrFGkagBR2QKMLMBT06Xz9Fezpf 2Wlz2kl5J6FxbzTW7x2X+6/UjaO49WYHrRkU9fh6Yq3e+RtFnmvJ44IUmv7mK7uecELq8kYjWrHi JN1j7OMFMDjieYU7vyralr6RtF0y++s2ywsAn1d5AnM+karMKfH8LVH4DGkmA7lQ6R5bS4QTeXxH ygeESfVklX06qDGfR9WnKvfrTGkDHHuR+gxeX4rKKPRkgjt1jTikIUMEp8PMfar/AK2+FkAAKCZ4 pYd5G8weV5o5Le01CcXlzcXQjstT1GO8uXEM7B2hUXFz+6DPRaHZeNR0xVkEmkTvbXEA1G5iWcyk NF6SsnrMzfA3AsCvLY1xKYmjalp2iXNtpUFkdTuz6cQjMjNE8gotPtvG7EjxJJ98AGzKUrldLNG0 Gex0i3sl1S9kEcfESTtHJKK77vIkjkiv7THECgs5cRuqVvLulfovTFsxe3F+sbPSa6cySfaNV5N8 RAP8xP3UxApck+I3VMf0DzD5WTzBq0TahPb3jXv1VINR1KOWKWVoUcpZQfWJuICpXjwVq8tuuFgy c2E3rTyJeTRiZw/BBFRaIqbc0c/s1xVBaNoNzp1l9W/Sl5NSSR+c7RyueblvtSI7d69flQbYAKZ5 J8Ruqa0bQJ7CCdBqt9MJriWb/SGjkZS7k0UujkL3AFB4AdMQKWc+LpSroWjnTWvR9fub4zztKwun 5+mzgNxSo2WhGy/COwGIFLOfFW1UmuFggNItdRhtU/Sj2s18FUSS2cDW8ZIG/FHknYCpNByxVD6i vmSLSr+Szmhe9VLh7KMwlyW+IwqazRKf2RvT54JXWyoXRbrzVceWLa+v/TttSe29WW2mteDrJxqA 6LcsobxHP7sjEnh35qlvkPX/ADNrvki31e6ljTUHWYOkti9u3OKR0FYTcniDxBHxbjvkcUyY2qL/ AC28w6j5g8pWuqajyF1K8qSK9s1o4McjJ8UTPIRUCta7jHDIyjZVOdPtdSSSZ9Qe1mJkkNsbeBom WNpCUDl5JeTBOPIigLb0HTLVXmLVvWnKXEKxMwMCvEzkLwUGpEifthjiqF0m380x2Srqd7aTXfJ+ Tw28irxLHgN5evGldsjAEDfmqno9t5xjjuRq9/YzyNcStam3tZUVbct+6Vg0x+ML9rGIPVVfQ4PM cS3I1u6tbpmmc2htYXhCwn7AcO8nxf51OMQeqpnkldirsVcQGBVhUHYg9CMVS7QvLuiaDpaaVo9l FZadGXZLWJaIDIxZ9vcnfAIgBUj/ACu8t3/lzylFpV7aw2UkU9wyW9u0boEeUlWrFDbr8f2vs133 NcrxRMRRVluWq7FXYq7FXYq7FUm8o3+pahosV5fzpcSyluMiWF3peykje1vXknTp+0d+uKt6t/iR dWsn054zpwjlF5CYg7l6r6ZDNLEKddv14GBErFcmPPrfm22t7f61cKrx3brcyNYkK8SlwI1b1gpI oOg37HuXdiIzob72oSeaPMRW44X8SlrpHtq2YPG1HHmjfv8A7R+Km30+DupjPffr9iY6P5l1GbVp Yrm5SW2uJUWzjW24FEKgMGb1m35V8f4YsoiVmzsy/CzdirsVdirsVdirGPLejeXpLg6hD5W/Q13a tLFBLcQWySkGRldozDJKQshQPXbkCp+SqfXNzPFPEkds86Orl2j4jiVKgAl2Qb8j3xVL49b1N9Zn sW0S5W2hhSaO9MluVdnLLwC+psRxPfBe7MxHDd79yDu2+s601lN5Zme2miE8uqBrdSZEYKIyVkDV 4qP2um1KY3uvCOG737kLFpvo+aLWKHR72O0ZDOb0ywGCJ4yFEZXk7nl1+1Xw6Gje6iI4bvfuZfhY OxV2KuxV2KuxV5T5Y88eR9ORJk876pe2A+tXMsepW8zL/egSepNPaiWJI2NI05qONacgKhV6Fp/m ny7qNuLi01CGSIsyEluBV0CsVZX4lW4yK1GFSpB6HFVRde8vtdyW66ham6RFeSMSx8wjFgpO/SoO DiF0q0695e+urbnUbb64IyyxesnL0yRU8a+K48QulXjXNEa/is11CBryRGaO2WVCzLUAniDU79Me IXSo/CrsVdirsVdirsVeEeQdS1CHSIxbeZuczW8htotV1vTLppSkvFQZEgvSPq9QjsjU+L4qtTiq 9f8ALTXE9itxdNBO6s6W93BMk4ljbiWLGOK3jBEilKKp2UGtSQFVK3OvJrNxJNY2SwOCltc+syzS fvHIUgRsB+7RW2/hlQieMmhy+KrmbWzryH6hY+kLc/vzcN69eX2aelXjv4eO+JB47ocviq5v0ufM Fsz2NmLf0H53PrMZ1IYCiqY1qPj+jfcV3TH1g0OXxVOctV2KuxV2KuxV2KvDvJPk7X30Vo7XRNCu Int2WWHUrXVo/VLuWiVv0iZJFHHl6vwuSaHsAVXrHlXTbrT7GaO6sLGwnkm5uNOqIpf3aKJHVlUq 4C+n1b4VBrvxVVM7qytroRi4jWVYn5qrAMvLiV3B9mOKsbbSox5pln/w5BJaxwiKO/T0xI2wanpP xUgFiOVa5Vw+u66c1VhpsP8Aie0u08uxxpHAyfpEmISRsWBAEaErxHjy5b7DY4kesGunNWSZarsV dirsVdirsVfOfkLypYTaKDZ+VV1tI7aVTLaa5aPInrS8wsMlpFaopuac2NU4lRTxxV7d5LsvqelP B+jJ9JpMT9UnufrS7onxQtzfih7rRfj5GhryZVPsVUby0S6g9F2ZV5xvVCVb924cUKkEfZxVj3pQ DzhCq22pM0ETILkXLNZ0PBzzR5ORO++1OnjlJ/vBz5fBWT5crsVdirsVdirsVf/Z + + + + default + uuid:65E6390686CF11DBA6E2D887CEACB407 + xmp.did:c765f3ac-1881-344b-850f-cb39f546655d + uuid:0f73cf5b-5807-487b-83e4-9feb27d0eeaa + + uuid:152d443f-61e9-4330-bb51-f38a8e360e29 + xmp.did:f18479d1-4e7f-4340-b533-076a69bfb2ae + uuid:65E6390686CF11DBA6E2D887CEACB407 + default + + + + + saved + xmp.iid:14709273-42b8-4844-95f8-dd7dcca7f08e + 2023-05-24T06:57:15+01:00 + Adobe Illustrator 27.5 (Windows) + / + + + saved + xmp.iid:c765f3ac-1881-344b-850f-cb39f546655d + 2023-11-02T15:23:12Z + Adobe Illustrator 28.0 (Windows) + / + + + + Web + Document + Adobe Illustrator + 1 + False + False + + 457.733945 + 133.375724 + Pixels + + + + + MyriadPro-Regular + Myriad Pro + Regular + Open Type + Version 2.115;PS 2.000;hotconv 1.0.81;makeotf.lib2.5.63406 + False + 6851 + + + Tahoma + Tahoma + Regular + Open Type + Version 7.01 + False + tahoma.ttf + + + Consolas + Consolas + Regular + Open Type + Version 7.00 + False + consola.ttf + + + + + + Cyan + Magenta + Yellow + Black + + + + + + Default Swatch Group + 0 + + + + White + RGB + PROCESS + 255 + 255 + 255 + + + Black + RGB + PROCESS + 0 + 0 + 0 + + + code keyword + PROCESS + 100.000000 + RGB + 44 + 50 + 200 + + + RGB Red + RGB + PROCESS + 255 + 0 + 0 + + + RGB Yellow + RGB + PROCESS + 255 + 255 + 0 + + + RGB Green + RGB + PROCESS + 0 + 255 + 0 + + + RGB Cyan + RGB + PROCESS + 0 + 255 + 255 + + + RGB Blue + RGB + PROCESS + 0 + 0 + 255 + + + RGB Magenta + RGB + PROCESS + 255 + 0 + 255 + + + R=193 G=39 B=45 + RGB + PROCESS + 193 + 39 + 45 + + + R=237 G=28 B=36 + RGB + PROCESS + 237 + 28 + 36 + + + R=241 G=90 B=36 + RGB + PROCESS + 241 + 90 + 36 + + + R=247 G=147 B=30 + RGB + PROCESS + 247 + 147 + 30 + + + R=251 G=176 B=59 + RGB + PROCESS + 251 + 176 + 59 + + + R=252 G=238 B=33 + RGB + PROCESS + 252 + 238 + 33 + + + R=217 G=224 B=33 + RGB + PROCESS + 217 + 224 + 33 + + + R=140 G=198 B=63 + RGB + PROCESS + 140 + 198 + 63 + + + R=57 G=181 B=74 + RGB + PROCESS + 57 + 181 + 74 + + + R=0 G=146 B=69 + RGB + PROCESS + 0 + 146 + 69 + + + R=0 G=104 B=55 + RGB + PROCESS + 0 + 104 + 55 + + + R=34 G=181 B=115 + RGB + PROCESS + 34 + 181 + 115 + + + R=0 G=169 B=157 + RGB + PROCESS + 0 + 169 + 157 + + + R=41 G=171 B=226 + RGB + PROCESS + 41 + 171 + 226 + + + R=0 G=113 B=188 + RGB + PROCESS + 0 + 113 + 188 + + + R=46 G=49 B=146 + RGB + PROCESS + 46 + 49 + 146 + + + R=27 G=20 B=100 + RGB + PROCESS + 27 + 20 + 100 + + + R=102 G=45 B=145 + RGB + PROCESS + 102 + 45 + 145 + + + R=147 G=39 B=143 + RGB + PROCESS + 147 + 39 + 143 + + + R=158 G=0 B=93 + RGB + PROCESS + 158 + 0 + 93 + + + R=212 G=20 B=90 + RGB + PROCESS + 212 + 20 + 90 + + + R=237 G=30 B=121 + RGB + PROCESS + 237 + 30 + 121 + + + R=199 G=178 B=153 + RGB + PROCESS + 199 + 178 + 153 + + + R=153 G=134 B=117 + RGB + PROCESS + 153 + 134 + 117 + + + R=115 G=99 B=87 + RGB + PROCESS + 115 + 99 + 87 + + + R=83 G=71 B=65 + RGB + PROCESS + 83 + 71 + 65 + + + R=198 G=156 B=109 + RGB + PROCESS + 198 + 156 + 109 + + + R=166 G=124 B=82 + RGB + PROCESS + 166 + 124 + 82 + + + R=140 G=98 B=57 + RGB + PROCESS + 140 + 98 + 57 + + + R=117 G=76 B=36 + RGB + PROCESS + 117 + 76 + 36 + + + R=96 G=56 B=19 + RGB + PROCESS + 96 + 56 + 19 + + + R=66 G=33 B=11 + RGB + PROCESS + 66 + 33 + 11 + + + + + + Grays + 1 + + + + R=0 G=0 B=0 + RGB + PROCESS + 0 + 0 + 0 + + + R=26 G=26 B=26 + RGB + PROCESS + 26 + 26 + 26 + + + R=51 G=51 B=51 + RGB + PROCESS + 51 + 51 + 51 + + + R=77 G=77 B=77 + RGB + PROCESS + 77 + 77 + 77 + + + R=102 G=102 B=102 + RGB + PROCESS + 102 + 102 + 102 + + + R=128 G=128 B=128 + RGB + PROCESS + 128 + 128 + 128 + + + R=153 G=153 B=153 + RGB + PROCESS + 153 + 153 + 153 + + + R=179 G=179 B=179 + RGB + PROCESS + 179 + 179 + 179 + + + R=204 G=204 B=204 + RGB + PROCESS + 204 + 204 + 204 + + + R=230 G=230 B=230 + RGB + PROCESS + 230 + 230 + 230 + + + R=242 G=242 B=242 + RGB + PROCESS + 242 + 242 + 242 + + + + + + Web Color Group + 1 + + + + R=63 G=169 B=245 + RGB + PROCESS + 63 + 169 + 245 + + + R=122 G=201 B=67 + RGB + PROCESS + 122 + 201 + 67 + + + R=255 G=147 B=30 + RGB + PROCESS + 255 + 147 + 30 + + + R=255 G=29 B=37 + RGB + PROCESS + 255 + 29 + 37 + + + R=255 G=123 B=172 + RGB + PROCESS + 255 + 123 + 172 + + + R=189 G=204 B=212 + RGB + PROCESS + 189 + 204 + 212 + + + + + + Marble Colours + 1 + + + + R=255 G=139 B=139 + RGB + PROCESS + 255 + 139 + 139 + + + R=240 G=255 B=139 + RGB + PROCESS + 240 + 255 + 139 + + + R=139 G=255 B=188 + RGB + PROCESS + 139 + 255 + 188 + + + R=139 G=226 B=255 + RGB + PROCESS + 139 + 226 + 255 + + + R=255 G=139 B=232 + RGB + PROCESS + 255 + 139 + 232 + + + R=255 G=221 B=221 + RGB + PROCESS + 255 + 221 + 221 + + + R=255 G=165 B=50 + RGB + PROCESS + 255 + 165 + 50 + + + R=67 G=255 B=62 + RGB + PROCESS + 67 + 255 + 62 + + + + + + + Adobe PDF library 17.00 + 21.0.0 + + + + + + + + + + + + + + + + + + + + + + + + + +endstream endobj 3 0 obj <> endobj 6 0 obj <>>>/Thumb 283 0 R/TrimBox[0.0 0.0 457.734 133.376]/Type/Page/PieceInfo<>>> endobj 281 0 obj <>stream +H= +A >6NL/V6 Un#l2 <+V@) >stream +8;Xp,SKbk"'AuhTR>&.e]M;(tZD!!!$!rr<$!s8N0$ZO)#i~> +endstream endobj 284 0 obj <> endobj 286 0 obj <> endobj 287 0 obj <>stream +%!PS-Adobe-3.0 +%%Creator: Adobe Illustrator(R) 24.0 +%%AI8_CreatorVersion: 28.1.0 +%%For: (Ian Griffiths) () +%%Title: (Ch09-CombiningSequences-Marbles.ai) +%%CreationDate: 12/14/2023 11:08 AM +%%Canvassize: 16383 +%%BoundingBox: 20 -2804 937 -5 +%%HiResBoundingBox: 20.8046875 -2803.5439453125 936.988157129443 -5.6937054275395 +%%DocumentProcessColors: Cyan Magenta Yellow Black +%AI5_FileFormat 14.0 +%AI12_BuildNumber: 141 +%AI3_ColorUsage: Color +%AI7_ImageSettings: 0 +%%RGBProcessColor: 0 0 0 ([Registration]) +%%+ 0.172549024224281 0.196078434586525 0.7843137383461 (code keyword) +%AI3_Cropmarks: 477.733944954131 -423.027100099343 935.467889908259 -289.651376146789 +%AI3_TemplateBox: 683.5 -384.5 683.5 -384.5 +%AI3_TileBox: 285.730927194138 -653.939244226582 1127.59090766557 -58.9192857304879 +%AI3_DocumentPreview: None +%AI5_ArtSize: 14400 14400 +%AI5_RulerUnits: 6 +%AI24_LargeCanvasScale: 1 +%AI9_ColorModel: 1 +%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 +%AI5_TargetResolution: 800 +%AI5_NumLayers: 1 +%AI17_Begin_Content_if_version_gt:24 4 +%AI10_OpenToVie: 91.6330275229375 -103.752293577983 2.01851851851852 0 8123.77981651376 8228.55963302752 4318 2607 18 1 0 5209 170 0 0 0 1 1 0 1 1 0 0 +%AI17_Alternate_Content +%AI9_OpenToView: 91.6330275229375 -103.752293577983 2.01851851851852 4318 2607 18 1 0 5209 170 0 0 0 1 1 0 1 1 0 0 +%AI17_End_Versioned_Content +%AI5_OpenViewLayers: 7 +%AI17_Begin_Content_if_version_gt:24 4 +%AI17_Alternate_Content +%AI17_End_Versioned_Content +%%PageOrigin:283 -684 +%AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 +%AI9_Flatten: 1 +%AI12_CMSettings: 00.MS +%%EndComments + +endstream endobj 288 0 obj <>stream +%AI24_ZStandard_Data(/Xd޹ŋ /P&mIcNm9PY,SjVŸҗn ; _EXBv-pcAp`CV$ qG{ޮDSE!}MgVE㊤chcǫì얎tUFމCUD-[&BKCU*Z:JFF,LBbAH7=AXsp\5 / GpУgU*YHc=+=Βs1sBR2SRZj"$q -xQM"q"i8IިHRww, b8`0NGҠt8>x@! +9q@ EH ""GcX-6 n"t*s삞V^YAۣ*cW"#`(U$m#݀,P4[{A ERԀ(X|5ȅ""]EH7l/jAB%""E"HR\ZUL{=2^:_5TGF@J(DTB5e}/_$+- _ +W/Ɋv:V$D(lX `$ c-"y9{Z_=a U$ +q"a8doveUPtc:zAws,}dGctE|,"T$7 EA!"a2%w 0Kwɸ +"IU$eP$3޸CN /ja["cX8dHaF1q d$C8c`0 0F5q  Gр4@G:ԡHqp4qÏzȇ>AC +! i@5eІ"ц");L/*A."!>  Ex@(.Ex@ə"ƃX<CG>q{أ~x8h8 ñp,wC@9Q4 GX4؆6 l\ְF5ܠ`8 Ƃq22 d0cX0E.pq [qg|u_!X6zi!P6tUSS+)$!F2rT$NJ:Ё \q1а9q|tsX4ErfVF0c 1Ґ<h0ʤH^x]k*4:3E =pA KKIJIDɎbQ Ejmm&"r +b~bC4bgEr!P$ӂEJ3")"`ΩδC sݮ-\eԭEo'*oz(oY7x_wZv^XiS,Ilv.ߚsg,M ɦWt巬gyj8z mI1qQ7mtk=L,M|l.W.,u5u5VjoFֹfξtK_6MoygU!Mq3 yKnfb(JT:[tx`ؽ&}Qa*^'POSl^ۏ^^5릝zAtu.ӹ-sCv)ݬp6*f4KUjWp~/JQH}tR57>d8Y'zu2Kim6UsYnMdkqoRKGQf}6uUI콎xAr[Mb9xN䒉/$ڦIxIN3[cѠR2Ͷc=i}N!]GjfRqCөRrKҺ-fZLo6$WM8z<[|q./St;{wS<)tZNmqiKW2<)Ju9yZ76ࢍ.k=4GQ!­aml\D4=kMUCT,[,]vM{\<ZNqsy^̫§{iļu\vQ-Վu¡ۛ#-c( N\&1meab͹MtUụ:F^4ٟdwS?3ڋWZwjȪTѡ_$`TyOLwΐzGWEuyDm^GV>f4W_Yex iVzUݽp\}9!ɳ pVw7ܼ4_˯ee3Uzs]Ewwd[}wʪqf2\]{,pK?u_۩{.#T;TCWR%ڝ9Yg6e3ʢ}~7۬YZ5_Y*WnKtkO;mnǮlOfK.4Rjy4˚Ғ:"-][Llsw mJ34BSqNڣheuzXfwYj,;JiYV':֒g=l(s-R.Z4g>S)dZU"U dR#qU"ڲVҢNe1ťC^Y7mR]EyGMrwQYw7o-MOtIlT^-՗һquqy|VCn龞-RjLN[z-Q:X?ܪ8鲝4ez1|?VɯsOR]|zRE && ,0a`@P"<4DL\P"0 P-kV&Zjݏ&U~vV-LƪnV2Xixi +_)D[9nVR {B!R-mՙTC$e̼'i1zxiKMnb 5Z!yuіu"-]}]nQmFFyxW%D[Rocx{HXV"Mwg.eUSiTK,M,u 1QJ=6 +]5o3+Ûͭm˶C3\ַexRxk6*[4Ƕ~W$E1J5gOSƴCt|V9#^+۽l*Zռ;aͭRϪzWVOoOJwMM3%;L9+*Y&fǂ軛tfHԈUvCN2Qj>VxUvӐ!^}/n\Ke*5.U=_ww`)"$>-nԉ֫9✫V9!ҺDUho9J.%flFl*g6-E6NJMudnA2M qjCD弉cx4GZ>DLYD֔u*ԽzsJ/G9V)<\Uo-4:.fJz O5t9j;+TY-)}L4uZ1+glĭ$}栺jjhśkK K|ٔaiSjr?j6Kmϊ]ٞC:2Sq -M/{Ti&-e[d>nԷijGMYCRߐQEmKHujhu[tuWGk~U/] +nФkL\Dec[ t+m^.55aΖe_TOsVuJWF-N7\^jN5o.ٙw<#j*"_igD{g:kEӼՎg٪+*^sj릵UvCۭ&-J;Nz:u w`9Zl5ƹtV<%J؎hv\ueA_ٕڇvѳ˳ᨩrrw0WZO2[VVޘY+t\l;d?I~cQpgNL+U֤9ihꎭɷ:<һ]]ϒLHg+Qo'R=ғˤ!+Je[!+eIk_姥mfd׽Y:LB`B0B *2CIW@Hf +c:m;LWt+{Gf_7G;f?=vC 씿jGIľz +6mޯ%9Dhdѳ*OA7摦35^؏ lNJBp<Ņܶ~/Wklpε.8$\ԙ^XynDϙKj"g f*mFE5h8 5u;PJ y'7YEWmބɬxiQn㍆1hIz!9t/h~#*:)L_gTRrgUtVϊ& zBFe5e:>`uZb^ܳ|˾Rm-}X ; E@8s+oT#;}fu&C +vj<dIpzU|7/VO)v +Af癇Lb)=? UB qB?QD` 1"M[P"l=Ha9R=}u5. Syj;k%úpRKbN&ox0ȴ`NēQ@V$#%8~6|ސ@'d )A5>B<΁Dc`B9m2n!hs1&~FS~Ug@a7d{Pfu0A9h⏋p?[ݧ09 քBd Үӥq +Lt^%jzYЂn:OT:t!* H#{0KWHk{h{]2=A#b^FOrYE#]ԐӤ;T+ ܳ|=AO>WzM᳛4]`{XO`AsI N +Ko.zGZT咎銍_p0E7aݧ +f.42ӕbhϑ шVB&OtCLIy=C8l3j\p!?qg:ކ&*2AlzZ<\O'LzqԺoQ(xdkg)]G?t@PЬ]DVAqԠrrOǖ))L3xJ uF 4'D&MvD}b"&*؆o#" +iuCאv8"LA-).xQkhf…o ]H@+Ju1Ϧ#.*b6Wlh,np tDӋEr7QoC#L"gP\k{0ܸU>)NAaq.*^Em]3&_oGS=0fUپ5j#=|WYc&(dgd= YRӍ mŀ!'kk_0Jcg3X2"襽qYy"eDc3oiVۚMJ\yDQ@( e G&`[ߓfGi7ui)oiv.Cink7Xo~]{oم:"4Zթ5T.*cj/v?DS!]M9_\ǺVa(LYaPK,YSKʬ35NC!޲SS,Bv.2+I VԜ]Ckb7&z=Ȍ)1+2yW*7\ Ӂ7jkZ0y9%8!M.=L1(1 dN^'Ar2Agէ~z)fXxB**ݭ#%r+3{6xa8/x@ 褛ݱaD wމkyfYPo.fу-uU27UɁ'Ԇd光"`m3gB)h*3mȔXefOx CXOn`+En|ͽ|6!* +T\ !IdlsAp a ^UIlnq%rֶWpSZ,vj7{\'U lF ]`ܓMDAWYK̬Z&OF" b"FS0}(O,occL4Ts=RN*QOE1g2& aFz;~ni<+#y w*MJ=0]%%276~?⢟,1IFUT& $]mwlbcz,sܦx\ÙFJ*ń0.4]_!9!quB7bबtjZK@7v։17m6/Ʋn 8qEV^S[ %=$CWZ 1>'"?~Aja-n j7&Ax7вx(y7% +!lt|=B֩S:;~]d$# a"QXB2|-**,krXy(|rmCBCTհU [DOk^7HfclQW(H_a.>ejGLJ?Na /HWV5pVi^.r)2\sȬEFp(/9Ϣ+h!a{H`Cñ1* }1d=]@P`7&dU Rq@,wiPQ?zس脒t&!7+֪ÃCY:!q4Ɓ:ܩǏ.s] 8mV3Y>zca r«K BQHu0qEjrLWOHo(YD=cߙą)H[$@ ˭s8fRq9/qJ@rgёz)JX-)MJ]:wĶyJ GohMܳ9P2(#x~!Iv=)pU)Y.M 2\'m"^OC}?Pjm-/ k9tЄ:b/8u0o3j:ԫAHB(|rae4Pи k,41\DZ +H1h_  +`ysQjuﭵƾ0ˆ#kH}O"G~ )(?X % n,|a:Ars;HT_тN8*:F!]BAZY')g|DN0]ڮ< 8[Z~90,ps')H{ٳ*vf52~Ɨ9m DOXz^4$]& JNoNZ= +>Ynl +/ufaGOk}xYoݴ-(A =h$nԒ߭jN]䌨l.iy2/U*j_fbbm@,fc"X` +ej.Yk!jC3sP +&9W;дpC0>DV^Fnuql~0AD֤/)R!'#zk\$gp%;J{2':+R$caR;ѭ- +E&oGIqpOhL!S.e9\o1@#h1hFnkVFcS1:GopRпD`cs 2[A9J<<[ru'}'Habev(E|J r!lKO aFdQMRtjĞHZ89=ܠWx;=0oEaR kQ(#u),GE +)mZEđJA6E$[)H{lSě#SQD]P.#޲l/$9֤22!8=vzi +MݷoR "  JBOD4U+KǍybaX~ZJNl+ҟqdfѶys<)Y8A=[y:@@pŹ ڄ6mn 6)\ʟg9y`$K Ih|yf{A&nn9 >mLv5 &]2p +$J-|!c(|wG''49J)3m2/G8'UE&VɼB: qO2 ++ږҾQ#1Ec ʧ߹L\z.d>Wu$gJ䀏RXTۇ:t2"CY+U4և'Eda&˜Ls=ª4hJR eT `3EiSH̀1,a_T|cP3y8m6;jP݈vsQdh,jElYu^PQdO1^_KѮQ::]%;/>> +4UJC;赝Fqkv,&D{Q95wh* y%Fs? 4 +EeK fol)I5o?LX?Mb6(s xLʱ2O'zF9 =]dA EoN)( U!9i;PT*8 )LyRLD iThЊn +G)@}ec7 Y{^Qozix<>o0j]d02CMLk<֪c,9Zq`g$Ȱ(V+"h2lΒ\Vn_--k$T434vi0x^! +!D} +~ࢫQGkɉ5w qL]?hW.K%2_ eע:s)"!ɺUlT-l,{Ey)ٛgʇ`}W= \tWvx}b^YMs,dJ9$Ec^1^z +0RNٟS"E(dTp@1L"eʕ@0`ʲ>.Sf!g2T>;t5$d4؁y8hèI0׆}ٝ)FT +ښ;%]`ٿ*FGqldVk^6+jFai= dy4f+kUu0r+r Wn^9̙6~MΙ!pK5 ,B脾7\gpt5MT^K: Z樢]W(BL٤RvÀJkKc ]I.M&tַm޷Hw:yX24oYS!yJĉiE42HW؊h}DK}CW2=5R-X+ͮi49A"ZFDc ΂DVڛAhz oyZܞ4(BŁ@;[{:t)TR99%9̭?PM\2;AG%ޚ?\J"{IQDD0#MUR +ՍDN h}ԹR;`/6F]ac#rћyT0hJ;`W鳭?YenQAaPDRu@E#⏔+ϋORpYr> +fok)A7˘xzɫ䀜A4C$ ѓmL^ap&a6S7Kgy)n `4 -mkJĶQ{YZi!Q#&n^{w߄Ur=ij[O,tbaB~ +\`T kXrHϠ@̊R~3KI.ߦ4"pP,* +O}{Suw=_#,d}M:G@O}Q5  JH4ՑJ|9O@OvEvΝ93)8@31VƹCXyQuS(@sASuKXq,5z)Y$h1F*=p8R ӬuNrUat|ڙUq\=HɑD&v Z9-_tEӷUy-8|1W.J4l8RXa4]T1ِuG^JɺEvMypVLB^qO:=ݢzW1{0CbR=1 ̦qY)@ps;mDL5 +mECI9-䭇h?sgm@bO.^(0T΄3.u-e͒Aĝ§7݊5`B}B-(2GHqA% VYf yk  |sQf"P5Gok3&F(YF23Kt#B0ERTBXd" 1$`=H $38?: Hlzj#at48884L}JPm4a5ZvDuejf5a26 mv\_4M.]8E+"Ƣ ZBRǨL!E1#~X>~G$r9Xn#D*!0mPNvh ir@6|s eBsh6)"9؄%IGE-=u?TOkK ?<\Eȉ4[ Cd}<7Q(M%~a'R)𩾰@pCwO:d}޹`Z w!cy=_VMjJRT03q5S_k<Ļb9|\jX6f5FHz*޵PUCF̱qV2Ux!WgL QV'U&e@Q؋V'TlVj< @&}YH?e֜G?ρ?9 PGAo9 LsmoK@2 RҮ 9b]j\>%]͟Yj0&?BF Ku0h]q!}ِ/ۡBQ)&#}] +ctyb٤wVH&譳KRl] =Z[wʫ`qHbRX)@ģ^uԣUt DZBa2\ưePG΋ˁB()5 ~VaP%HԊ5UHłӁGiފq1{)jd5u +QnSH dV +pCJ3dj[lKT 1^ }S;9h/ꌟ)sõ֏L6#jL!!CfHb21"[FWhtgae:\u2\ 4HL5R~ŋ* I6"3cHp@i^0Q(b7mZA%D1x:h2IUwX:3I[} +A21lzȬ6y#(ccUm6.!$9EI'd4tezō u2%{_ .%Fjy0i ~xk+P-t0&|Z̓l:dUy ?뒥8I]յfόJނg U#? Ї/(jq@=aj+Q%(ar+ +gY{6$V/GsNylg?})r” p$Oag+vD!0wd9dSYݞ] cc<_5%RVCgF%K`Z`qu~vJ:DH );[9,(|+Qz(kvm3ѹL5|T?TJ$m.mKĊw/?\ @c9uUHb3D"T=h wƞJLxmH2mCy/;$zAQG;fR]^&ABt| +3:H4:Ί?g>/-j;,}UA!=D㷺yb+ 2RZ3)1;z?-j +='[@[$"o3Qk*dGd716KzV.0lJ#%$> Q(VzdU\4*^VA'.0Ԫ>&U$ +TFBdȔ?UsxTcχ7~e*Ď;.5hZmJ:\U@**d'bZBu2J*mpme +~h +j y\XAEmX(Lw=B +J R ).9rLJ&"k|JP~(nșm2m(SJa`+ |K׼%p #nhJCP +]J'jLϜtJ)_Ct( t"¥9J@QѶmh_jW(cpX.b rap(flBxT(37cV 4b)'/{[lAor9pr1L50uvl!W´J?+ =pEum`"U`~uHxݐ~y.kl+*10}.تz@rh?㋰OO85žf6dʰ{7h93+) wG/."!r |0Z[ nam9V#2^m{c.ێ2ޣ~uyL +BO7ygߛBoe_c +NfۙpR;^8(3:~}V +:f@_慏7߲]O 3=0` +{jnk q+ !.4 YK=r̰ĝt9-!NK=0hf+9Q܉Gߒ50elHb?] \V% 9*lG~G*l +sp;X֟?Cl|B>+Q7}|Q8=Z ZrN;ʘTA@:+BMcp%k;@SGH!@8H&B?jS59JO5ܝTT|rqF1q4 f&IIUjIʪR3z∭42/"& 0Rs"i +Onj?ꯑzJx2Iw3ZE)79F_@/ Xt;>bGHy2$cƭDNy01h.(ĶIãkԿu'gp*@w"+7o`n+x?)c*_rfiCjyNbrf!0,L>Y +@Ŷ ɐK!y¡mN +XUGp+IĄi/(w 4E{ ͈p#o& Q @yM/JA^9ih\dhͶ)r.e`{vf]cԪy+RA,o4z/wl Wg깊}q.Vus;yV rKq\b5ަgZw\+9^j(1:AG`1cN~606di&I(@<7VPflTX܅{jQ$$% +(9:(^m^7:8m܀)=9Ņ2 2\^"F91]Gms +fMxgIݏ>w[_dl$'Kxk_q+r.R:٩7bc W%7+!}L;tWp ;F 42q΁m)`j{97_jYZ7 +h[#cwpB +ۘ j `71Cp0\P$vҿSJ&=2MV,v:nU.d=GܰM696 g~4+v\_/X Wt ߥ+ewʲZ5>QAW:vp޲/v4 ǚ[ϸK8(0R.DL]&s~ Cu`rQ睼yr_4 ^_ 'd@`$o~bOltv^z$,z_`=PLJ"3gSs^?g $ +:<5כs׸5JX#}9ADTSPƼG`LIh2`_2Lь[{fxu_Pgy'$s s{]3S1/3ֽx&' |NsA$.H2ꉟe.wH*t3 u{VCɋƆWiwۂ$ߋ^˯'z`qY)b2ayhpy.$oIܰ޹j|22K/5 :I|)%3MxDO}yA`.YVF#t/i4`8[WR-+c| +j|g潀/:.ld(3VXY8p, b)֝|3AjS5-KYmݙb( VS+Dke5iC"H"1-6J(uao7;!5?Q A䥼_IǁEʁ8煞܁v֎&.0Qյ 6+n)} ԗ+92fTdEIm2&hg$6EYn[9o+Zi_M{uFp@P1[v' =s%F 2vl}?yᕋ8"\UB&^$jy4Zp.Ƥ\P^ xˈ`l:| 4jFfB'r6'EZ|\kCTηUL^B BĶk~$[YC#;/ZSX=%mm7;I"2,;R`y/)I2'}ˈzJN9;0H|ߌ`K' tZhAX'^,I]9KcW|=H.7zH24s?B4:"`lzU`s0No7`nLG_b״j?[!l]v;`Բ3:W/w~< (p*àHU![`=mѿd($R]Hw!|۸ի?+WY WR`2-τVXSEkY^XEVd/"=۩Y:1'c-Y}܁d@@y FsBFՆ+A"z3uм^;83)̩0*,a586a3O_\#(N5[.Lײt@ʣXElj<0Fl&8RH`*NaE= b"?w^=gS bn%i7RC;p#|-Zy /R1 r%MUXQ(L.C'©ftC[>=w oxX*te!{{Uvm:>hT?|& ,spfZ !DJ wxGL7;v!R1#IbZ.|"Bt 9x>*AvW}y5OR(U/ˋQBzcٕ5ȖQuDǞ\*%l}/AfbUG7`c[f5~89k)}]?nO{;qa.+=)a i- +!+.k]WjB^ث:BsCXf <-4:3z@-9J,QW@f t̅1:{Ā *o-9B*;Ol}l7Zj{ڬ7BAƽg*,>lUg8Wѯ-I¸n-E,o>6Ri?M +ԐnCP Kk=fT*wLmaPrDFQl-_/J?*/nK#cn@lk3/‰ ]+gn) \ɸ%kFTͭH5hqv:& + .HѺp;Y9ڸ3MB{8CD|Z8/8QArtMZh ]A9`=7ۍo4 +L8pކz+ p@\{#>N-QGDښ:HGu ``")\qͤk ^37p +Mz}GJّ%4uR )% /Fg_4FT8[W<ʵ)*P"ȇ?C5WIM Wjܻe-b0&HueyA@g[̸vġG%8*'.hA@ d0:\2,i>mq)"'mjY7~Af=UPE%*lz;j305\2\5ڪ)F9AG7Aט''"Λ^KGCj8k?Ƙ=l۵w W5tZVܫt4fJZ1}Z#ZN͌NbW6dm2$??/r^9G:;ʮ<ίX<(A܁,`s{XM@$Prʹݫ^/Х3Wi`;c4s EL6~&j[wY?gw/QeWv]th߿f5o:k"=ԡ¸vre?sܶ/[t-FL˃hI221.du,?%~XfX+q0pJ-acU& 8rF%*U^ S-qp%|H>&!' +,h0`2_s[D Y2Y ΄.p8;u*Xw.&b-JNYU&}4yC0"(EID(J ɿ>hCѽ@w!ןD+[ЋmZS +g&:I@qpEHuZU90D^>&]u5[CVReѨJMAilF+ ZSt~.x X$QpCsYYr>%?w7U]mDqtXP%?:cxĞ$QϽFȺko+q*1VTF%t8ҙDyOݾJ~/SȖm1ceX D'$$+Y3 qsGP͒ոۇȴE ȃEvH رM0EARc˥iOjeJwL-=&ÀK s7XZt?t'=VO!%Ш~ z, 6D&lezpbzl*gbPr"о9'i?aE#-q _A uˇ]خbWg +N|WjO+Į]Ѥ5nZ;8MPl»\Bjf7]^ĀZpVrSɦ#=!2L4C5uS?+{fvO2̓4&(N1Mbd ^F2Y9&BHKL4`$v%߭$_%TS1J2Krwz$)4+ [ AH>B#Bڛk+ȇ8y# +OwTičf/keH| mZ-?H68TQZAm=]oz%84Lpxz<]kQPCQkjMXO\KIch4# A$BSp&'nZLgvSOײPĸx0i߳O0-GX>?3U#+E?{ER=Bss\jÝ81΅n +Ն {O_ +J+l  gB.㦱.~۸Uԣf~V,rhf}FQ󙐮1ydsJ/Q}yp!+HJIlSJC긌 Y ~MS8 !؛o_XC̘t؛#0UŲ:܈icF`| ,x`\,0TD~[Žo<]^9chy^ mt㫬"LWѝq %;vB$ts>iEVpx2#\u?}8? FMmQLϖ/ ST8r*ό(s+=OAvձع0w(̾Tͼ[piamU D 3pt2edXf^VȠ!v?wh򂆸N2bʐs%TIe zKH|ZAtSr"d Id.SO=0V ++Lf0:F.BԜJn+:RT;$I@ww87+ѣ ҕH4mC$-伛;@h:#KyWT\\a($xiހ-M,~<͘{J hDqH +>Dž? /c'W4뭊ґk뗙Uv7,R j7[MҭD9X+22OYUXOV>~dݲ-Z;]-ՈuZdK!27"V|NGmXܼ'[pIB=;Rd<*}z٧/YQ+,Fb:q{eqit kDD( +1heiRӁR +'*$$b !!KjXEP( + DFDɱB[!8^i@1#s1I Cƚ3{ +;Ua '.9c/6$/cjh1fvMxuBuyna"sDR)МjX)}u}OSrgNBحq(YSmU !V<ʠ-*CFKR )%1wfO`|ETψ. jק".bDF5lȤ8Mm#x׸sw%2U0%N}CXB>:C/ǂ$"`Z0lU)UKPLJ3ț_"ZDP]Zǡ>k|ՀȨh"PCЄ D*# WM&R!!ȁ:(B$Rqh:RVnN~Ec1M =dLw\ dNb ,Pk*/VT-{@HQD( k"%QMI]|%YeoMfD0_T "E','".u&0QH80Ufk +jSrm hp +QfZ^ݥY&fMCySǜzmnƫw]H{ %V_fi$uݾV yum:99!BAb#QKp I"iCK*NH8!YKHN%DC؉*JQ~ب$8H +22Ҳ czE;FPiq; +C:B\NA:fMAF r(?V)y-S}9rl$x/7ND%m~̬2BZ&i@dt* +ҝm`X䛎T*#TsL)"O=BҒrI”72Rh +R/f0j*2.ZقDyl&fT=xa"| Z|1D!զ$ 5V4mxϳ% +*ƝC\775њWf̃JaxW +前^Z8籃!~aS4sy+u1Z ++_6Oҏ'B,VHjg^LsU(NTSE+$nNJ.nO#es&{y,</M+gLLU6aeF!HA +M'xh( QgCGLD^ CCx ,,ꬭ3{kLT`/={UCxsuf4"ʏx5G^Bi%af!atlA '&$2:f "&Ր}{R5uy7Pdb?Qy"ui63 gdf,#YEäoÖv1b}]=/!;LU6)fc E"RԃTIOANq#Q +U):T.\jpjllMV',M5ڤ\;/cc^/F59*Px:1HeD(`dF&#*:lO0.ԜǓY3&$s&tkظ+dF7+V b6"d8s m6nqm: !/:Ѭ(Q_n6zII/-"]bS|)7]$ +!3e[[ :x+c~Umu0 ۑqb`Phf$EqƯ (5-9+ +GEkoEDœpQICӒİ)jZkdBYjoԣQY2F6}>mY,H&U_%$UXDd!$3 +6!K+I\Up,F|!nEcY;|ȍoD참H"RT:wEY45 &cݾ y_\f՝H+[o.DjubءVXeTOC}ɈʗELQĸbL'E.Rj}E^)vUSIfcB(/I6r'ț+081ŲYjj[Kvd}m[""c(z_cRj}%!JQ!y0?z<;jry4*Z<*C4uE^2ZWm1_ ToJKk2j# M#AB +YXR\yw:/vK(6s2? ӇTPGWYK #!&zoXom+p Ԣ89^2\\,R'1]՜*j[̥đ8Өpj3PE$g!rC9HJGkJ&}D]M[CFiE ݏwsi!Mȃ$不,c%!a)D GI(Ya_WɕP^nZ I +ԔEӎJ*\:8!QJ[$M4#$ +IɞK +e$Cl%9C5_, VdC0<\iCiFx>"tbPIM6}jH|]n&l/KxG^Ga5dȎ)-h;*:)"FT\|G\uZZ[yAv4B/Jo&4թjs+~ ȥKWwnmċ&(G~~U]:I6%ydEvj*"Lk2c^vX%Ah,e2*44B2ɰB(: A}珂&2 BN!9{P:lKdb"y$*<.p%i68WPd{DSSQDXx͸\jj\@(" ADFT=FHASJiC9*,gQnJr/(e:D s 9X^3!Uy+`LUfCć GLX\e},C i %wD$ BAJ#ˠs/رЈׄ#I2d1MxQh#*A_v+6 RfHIV-ϥ =m>S MMHf?2kQ0>T'4~h>r/0AK C|yRBRжUJHʯB$n  f | S&,c\CD =12 ݳPMjd +4 !< x})L0\VADb' 5SjBe6̈KtafjfƲB#d3q(QSxRy.ЁŖ8C ,C}a/>a#PwB!ԁTH?EdՄ92zX1P0sӇgV"[6vNDx Rᕊ +rTe©4 ( cnN2'HHQ"]D(⪤fȃ(.QGD +!)]&PJep 8O>=>3HQ*APd!VG4"BO!rA)FRz@ #,) R / Ѐ 0uD M 0/yGCS@}SS@Tޫx8*g>"BbJ7ujAMO[P1DJdG8AQŒ(?E/d0ZAs^xeX,h/*v1-gsSH4bpbUO5j"v1T#8T#+j3Eͩ M,Lv|fVz- 379ŽN/:ATQ^f!['կs(2Wΰ3K3৳W-ǏWeq|!GBcGaDoɪZbϔ=KhEB `@ +Cbz|4|B aqPPZ  d$[,UQNH ۔+p!/#IJj pfNn40:p"-XPVm&GIÎkZy1B8\A9#g&ކw%Ca ٷz7~s1(@x^`&2 U=1ԿRnvYuׁH!`>;j1NNmU|;FlE}gULwSjpen3fa #QXg+xFvY CۧUUۇbCG15n/O j Ԑ)HIDNqLR. + X3! +2`x^ Pë %NQbNbQטr +ރ-0ʻ䀪^3r0BST=FM!$BhLxvf*63QWz$M?2c[t J SI6_n>rcѼ/rggcb"XMV܂ ~2gzfERx +i\w G:[xuڂH,QaBF/K-Xҝa!!یV)=Saرz~'N.#Y,u*)nq$ކ4yS=s^O1PmT;lMl'sT汪nڱvcU?, CSf=edCk$9XO*w gvn:@0e?+>cm'W-CHA j9DSh:8;1qz[.:qBtOYl-=9÷Xǧk)4&MPo&,/'&2:t1 1У&t!{ᳩ(6@FbȌŒh!w1ĥp9NzXLa" cG bm˗8?ggMWJUN&s쥎ĔVjNUn;ˈ9ʵֈM~pM8 Aj8~W"iEb{L, +bi@>唃]󇮙o, Ne9W>WMD;b`L>=1#4N|Pt}.| &x-6CI10G +FZ6 ee1$B!'WNas;)=zcJ2Juˈo[*!eC.Twap)֥TJK{`*C䜱Pw_O)uѸa#~8bPO+1+),|AUOZ_^lh]&Gr TrLli¯I%i1keV:?ǒK&`B>~gŖ\/y}ɾ}\ҿ)HAFxg +8η4m) +*]Z%c?9h1Dib5E׍!L]LN]&djS;9u$ `(Q|bY BnnCsA:^ugb8P5Q"ZS(-9BeCAN;B,Ѱ<4uډgA`)X?mRk#iA$,b:'!{y^/uEax#v,U._@ꏺ3r3U+eW^-F WpjeY.XFH\MЭZzkN ej7Y47*IOhPf~z%~t'[3ۚ5S&D ݇I%|'h@"ߣfD;ZS0Vx;YP׹#1JRUnybibjO5 H}DGP +( x]$V,|G0q'.+ JrosEԍĹl<6hUufu\#|S@twwZA?#(\^3 INA3V\Z -z\F)E7 %1?s).HVbWef>$֏%\+ cxFBC Ru)W75(廒_-t8#B ]gd.d UrQ4MI5J|.@UgZb!ϹDV涪l^ʎfąS?4"E`,v#絤D;VBiee3o9[%`:XGfE͐_8QayT2Q:G7N_ +AF!0<Hg %-[ /C!VsV t5WÊW*# R 71LGp mma 8"O5:tb 7Uvug.ҼGdAZnD_t$˨/!e$DzaB{6XPwF% +E`;,GҎ Uݚ|}Ә?€.iI :@xz(ܫ|"bZ~㜒\P 7vH:}}@/L菒Ȳ>'^"B"LZPs|k5ue%>PJRh՜ C&H}ʳs7zF!hePd v4v{F%8?޹UMa,R0BΓͱ"'TXlg u$<5 "aX܂F#7Z|i^X(x M +%`͇Llx!CD1)BK'Y|%nM Qԇhd1U0$' z{kgh\3ZJdh>z _ yCzk3:I<% "Eԉ9$}8H +`yiBEj8y-+7+"in@oEЋ]"q/&^_"!Xd9$f!V,'$NI}xna qnϸY8AOy>hC+i=~d,VE\_ X#gHC)rA1GJ[smySB`P+J~||F0 ;V>$? HTA|< wpcc0j)W?)# rPMyД:`H{`::@=IKV, \s*^!A [ĕ:t$Fz.u +$%1 ccaAg\*Ec /_ ܟUPc_&ljCrDШQtk4W1 +:= 8+, :1pVkIyp<ˁjv-o?Nso7b@ !\12XLܘe d& " +m"D@H[ $ɊH&mp܁h>/KA! /I+,}1ȩ_lCz(g;aNmJQA8Xe#d#wl}h 4%P} #ȵP{1݂kL_k,5(D ֈAn;UV$1 p]j\QcA~4קMӸ{(!Ӡe0diTKhOpB# .p k4ROɢQ C"p!x] @hwݐFoIg=>>Čg" G!5737eyl3 D $("͌9"dI*U"u`]`EoRH,#j0xe#Ъ2fʠ{ ep;#NidPF n$9ҵ.K9p(ܑý$œ*I]DE]\H.r!irB䛸.JKT`ݢ%X ٯznu r$w٢%lJ\ CgX[&uZ蹒J +{o% F ׃=S[ sW6fKhY$QԯDdX vX?zhMXҒ|8,"քXB&Kn%~h[~'1@]2%L_R0Q &BV]cVyB#óЇeAO3QVȶX+ L +XV!4x3/6YPk +I}B2'0 +?ЉUUu9;*IPxbSk)\Փ,:EMqO} )7"W$p)H&(+ŖR%' Jl'E +KIP@ œDAQpD(¬E(1 +H6pQ|& +ЏpwHu( )2ݙ(b(BW)@/'D"~B1W}b>CS2ɦȜ+q +]u^VO E`US^&C7QDʍ61X&JU:Y T Lv.Jd":87&vY^ r%L\&F*1{خrK P%XroK@WM`>AJW\@)!dA-ʀWّšZ!AI N)ʍ!,^?ٰ,)3e# +S+!YGx`&% VpJN.(!%XYxS2JʛXI.\S`JJKT'A}o8ĀCv!%pV$&X JR~Q:|gA@ثdSGrD +(Ⱦ*'\#&*҈?gĜMFP+F*q`aQ5\D`," t`=2E@J)lV)D_/k;"?BD @Dʇ !f_9Dn q^ 2Cd*H*2B^iU>*) UGbXT*VtvboOVf%^+AD"DQnJbbu%WDӑJ@ 12@%!+lBX'!h9a(b,Q z` qlyq(’R2 b,N`&D(*$IX7!8%AI`kgLhX]w̅F eIp AfsB`R\\ZJ +!堈 ɰIE `N /â;!g* ? "2z +"RX *e3 DDx, ExS@meA@YBV(ڜaϢ 7phrz`RaN-ЫE`q-ʇpli@R=\9ݢz\oyU=Dfz1CWp>eP=@yrx408L %]Ewݺ]}˅ſ]vdDŽ%>\e xdKv ϰUEc }/"a~e9Ե^`3?fHN0`H/Q&:`; q0BOqq0kcr%^ +RޅᇿFl}7<8 .@a70Q`1dmL0،RPuO?cf6ٰ̀Sc` T5T}SLp@2RL!sd5 '2+ H$ٴ0LZFIa)(ciX3 Ĕ2鈆'AEh2n2v]&"ghm31ßIn fpG3[k&2͈Ip`&Ig w; dg@x 3yP.h `MnS1Ecak4h#`8 48-I 0kœtLW|i z 7 jDռ.x ,KfR&_y`\.u r^~ B[~-Ă 懍 ͹n$55 +D: n* lFm. 5mzno׫6Qg{mgY9yӂv|>MZ!mYA; ʧ۳*lB`(BI. +oF#,R cBuOaϑS7y LDW +:j•I(Kp4 +p! +D,Bt +N@'8&>'ȉq Nq 8OY1"91lB (!9*g}$p[f&|oLZ/c#Kqt-Y 1%$Ihw9KBO# F¤A"s*cG9=9qwx# 96t|> +HR aGE6:["@KtO'! +#>:iS]lծIHuB9fܰsWiv*k'G'BoGwDN4>@o;Gy xPz ﲦdN+X@>Ol ;цxO3C_;0|2G<H!Ws07;s ыƒra24P7\p@jH_4{|/&GU42^12? |u%\]n  x ʏR;ZL eNTI ^R%@$THc jJ(3uA2ȪZ8MnvZ3u3MwH^$QW)E?ϗɦ] 8. UO[ţ؁95"#D(b%B ${8sR 0d~m E!/B)%  ?SVBbΟ(5A`} n4Hr,X@ yz.ID lzU>;Ц~tꮱg*:Ib:e m:1>&r?RzF).2PlvƲ9sv#^ׄ`K2蛊w""l`R{Q^na>7; i3[#,!A y @Y^dYkk)1܀ߺ1P䓕D 8IfKQp_@Ao9aOQGz;\m2ȿ!5&4pbw]ߕEakO짚};> Y@;0jpE?sz2@ΪF,є.Kƛ@$J&X̄-c寤 @عaۨ 7(e%:x,|`G܅0D1H}iGV<#9II$wOv,@:|t1JZB垲Sfɷ\Z7xAbV}hV 2NyQ!,f]|+ S(#X?Q.MۼǁJX81upl|6\}& E#"Pl 3ɕ\xȢT3'g~ h + +'q;8WZSD,nE}*wce `t8]d ]L1P`'(`9Z]feτh`{*dJ>C8ԃ c).Ɉ/k +?z*^Obp8<:R^]: ~1:Ak?/M>GycJR~pKZ%3a7!yw.VVeBE`eZh`_pb)l}$tgpdL@K*+JQ4]޲'mwPvTק!]us_&1п`" />ܽ6IX\P~HJX J Nx}yя_޷JUmtgoA(DP^??L?%\ކק+wC_u|#Oٌ l&eҢ>.M.O\'pQrCD̶o޳@9-kqLp͜]YiGqf4%Sƛͯ,FcU/R$!v|$BJb7i Dzm7#7S\M.*P,-Q5ߍ7!arQHtH|*@9nC.5̳(uEA}Vƫ|4 +|lyX5n=ɛKvo!;bghSWqyL(ZJm_SƮm}`P`C + /ڕqo]wY78~-%lsrs4`#;|W0ɥ"eN'-![Eȑ?pҞ[ jhDX9-l,A :V`C +\$Yas )/f]=&|)f0 4t}`[3*w}@[W  oCSÒ{\7FZ˂Vy"v.!,qX4 g/=!Ʀ?b" rkS^_aYK~tc:\o*lAZcY*4c{f!LCk CC414WJy5كn YHcle!lL +>mNJU}3ƞ6ѕVu1oG$+DQ +7ai|=F<xػi*HaxdֺL#|>¬_>`>h+]k_.{%K^^>ga̺y.O}5Ą,F=_gzF-hh%.X_ŧWx%Ϧ{Wm %i-va]OKSkA^{'(E'8[!-p"uEXHiI"ѳD[gᆾr]^_̡G%a4?`dD4rAec\1JyF +^? 3%4*$.w޸P "Whܜ-K]z,? ڨP0WScyʼOڿ}>t+BJ.Wz.d@ޣQV+һux9-Ia7;s~+Y~~W/> +Z5=i}9ꈑQ?0t_* #zFL!|G/"tIP + M$7Ȫ-KBɸwiQdi7} DjE:2l +vѠ/IO_{Xjÿ31!r63M:(jR(,Aє7ry8`v,TW@k)=r͌8*vU!'W9~2ʈbQ0sgՃhg7.cs"_/5y$y%C89،&쪭xU +v(ryvnM%$!y]R. ĉ: + +.v(Vu7Z\'w\upuu +u %?>QV^+ieZLh1sY[nR+e_XV:h +u}\Ecju'`_hs/MXu,=ljST4 re+Ύ4tf3ͮ>J9RdTDz /0Hz2ɇBlhYPgÁ}L9ZXb#͎t"rv.̚>2Ӄ|S6.#?ȥYɳe W:/6h)ɗW9n4@>5H4җ6b1KrW!?:Zk)n*)'H.EA`aIt4zۨY-l%!%o׫Un]ؔij=EJl穧j˘yl`?+?.Q\f+~?ׇY~PY&N'j/਀(\t1.l|{\۩kld "5#mh)@/b[t?YzT:Rm@G"Jl&&{ ]P%V" 9YB::@(7>Itu2PLofu Wp_̾f݄0=$|ށXЭWR`˞ӇRTK8CҁQ9X fƝ뷘t5/37Lt)жqΝg|9^A9GN0 1|Δ. CGpN1lCsd\.<*p*&R,Ċl=X qcnLҜ:zȸ2Q9sY$3rf6Ms#P(/3P"Hc &vlG6r E֌$U5wl.'H\zDUHZcLNfT{_4퀗y [屢WQmsLP| j@?PO^\#SCpQ u +K$@gg!S\H:w208zIN9o/pU9"QڠT|J ˬC` y:r%NFGQ"`n2N sܭ"ߞBF(r7rfE;KljȎQ99cV>8(YGUY98>_E@"K@~bI瀟@W\7]`߱Dѻ O;qy~|AU8qpU+}ȏ`,*f4?7rIqv̏\;pZk8]P.D~fDoO" oOb +y2//0JD\/;IM OLjlu4(L O9d'So\ͿI6(V9Nt<( &ģ(N߽Җ(F +X}|P+N+y+QJ$-@tH9l /S\_&Մ,/Ahk\Zx8n*|poxih^LxLji Dvh⳴`F-?e)~"@=a''/È-?Ž>%:Ijx/8~3  +ڏL.$MPa/R\XiJ=*U ;{_:9V,^&O/A-.C#.&3ѳVȭ*eVc!A8uO`O+v/w;_M8ɤUD튚 :_ jUa +|Ke->JXЅ0Vs>UĞ*܈HF*堭0㋑Fe3p$#\>tZ_/F^;]1x8AȂ#,+0{'ED2O 8|ݍ"aK菦NM)Rx Dͤ5}(!$Qu/}cơw}!<1*x;}.K4@{2`qڢmb탍=5`Bsx W3uZGBqC@߱Ud͔֫{S*lxzy)zF40&Vx?h6@=B7Yě[[Sْ45p\?1<Q9ԻVvqMuf5emi%C;vϰϏ<'5,6S/܊EҨ .^+Ct/'p=#z2Jf +wBdύpx5:](`QF[k;\iP|)wks ܜi#/g筰!  ΔA<+oy2:|ý|w%KfXK/ W&2ȕn| p~@QtMmLUxox1|ƢL.,8m4ugZrlr;HvVy 0hm6}P LLJ6FmTyJD}7ei5VAn-/2Hm"鑶(-j=1ڽSj\[+aCk[:joF%UcG9-CN#FTNڽr5viSʃ$ܩ1mTMbL*R*Y'mK{JR\/?ryNgS)H8H5:_xhɫo<`òzJʰi,-e32"hoJ-ےy*׊iNO2$9-d&#q,r==*U { +IHGSci{I58_`}6}ލ +} P?KPfmb>zWD2z}NZvW<#oGʩk*eaMXY{;1L:X-_HKU+N׳Lqj=F)t}ϣFVtt 0ynzuh:NZ\0c>\UKZdE'O25"MkG?;Hu,ЮDŽvj'(?[q:'o<+Ԕ$=due&RK:aYډgpeKk*ӘQtsuxFmfmyIW@QoJ|4 p\Vh!f$BXox9 R^:Eռtf~unR-t^HA~gmJA3"؆ttE6qVRZJ48*z (uA-&m3|RһҬbcX +ˬrk3 'fnv~K'i0.1-@1_LQh5+)ib*yn&n$̙FTiQה*)@8C{gz wyb:JH=;+".Heu-TT{]BK*p$*ZThFb zTRmCs(n?K+?]!";GB2O!uG0Տ0 + U%<`VNȫ1v/VY#61ypVFՋےxJ:jטN_ֲx4r`lWubEҁYXn$+tܧѕxYzYUNiO;);[րV̘o|UH:Վ[%ں)oWװvt9e~oחnׄ^ -,Lf:%yW{_.3 aB%H 3vZ]upň3rHC+`ZFyT:ܙX TQz,&[y"F6VƑ.GȮøh˽LDOxs8KPʊEHY}sCZ3Q#{Q.#E2UIgZ<4cC#hi*w6dUyiŻvLzPsl?Sv 6k֊X >{1zmhz1ᴽυP[oշ8gm6n$J3#_u{E굱y4Թm hR1!7IE .w$7[$ f_~xڶ6[ær5 4 W$gD%*lvC4mQ*t' ѵwE1wt;EuR~kV g-i~2 vB]P[ +endstream endobj 289 0 obj <>stream +J+.QWP3\~s i ) ?MU¨0C9]t2D}ت!87XlApǣ62UA5J$K@'q;;4Mo\SdpDY\YXj}0 G1sشA6xO^ǒ<1K2 +$uxbCjԃv3qpKkM#:Fv ᧎ymrV{?quEeH,M=\ XGtC':̾hyPoցԢo3 #9v3EY3^Y|sDX[j&8hW9>:*pxԷ'76 Kz*AE%S9\"r7nfT{;rfʱLUd*+G<8Vxp륝 ++vΙ/aU3&c۩GϺ3|#X<+8Fs`!Q8]t6zn3KPʳ Xm{*AM'SnO'c?t(6t}8o]`kEkmcHIic#<=A6xkk~K25.P@4Vr#eIl[ɨ~o4E4hz-GOǶ7n:4ȶ1X,>&־X*7k1/Y u.QpJ6 +E>6a,zA͚3"*Iz^Vj1l#0~,.Ia1UxĎc$9DpF"q$8x[4OǸ2c>*:rbaƆ*2\35&>ן DcfL~8gyI'X\ a%dlv~qEr=?0~3E{;)Ag XF8_ R kt"=Iw gXqݟ: %Y]+Ooc crS&*^Ћ1}) ѰD;vEa w" C˜EDJ3M!=˜YY &ƙA]|1b;VxwO$Rkesʿ\̟2<b1$s.007zc@A'e{3 +ݽ)Xa4%j/|=ݣV/ms[G,$SVŮ ([Qqw//.yIfQ^ϻb c8[@|CN 3,g}=b "W.re㗄[y VRڋW RM: CEuaLE%9Hјj<\m(s@8kX"hl8o¹/?T1P{יʼnܛol[[|.ؑ-ߘ i/ |/+U_ mo`wU3o4 7bAv5~B@241.WcqoY@ͦHjwfP)-ȳ 4ޘ-?t8ǖ}\mX_cmuε6$y1Qds Y q`wb!K/q=[`Y!-1ZȅKrg1|RwшRAXGf2;o2i]82^åNRڝ5c%W[9 `|Lא3Dʲt i̷۹d}h jm1 ۆ:f@^a2v/sF3ǫ&@fP,GmPlDRy!f ή %h %ƹ#u`baQ9_sh=#Y3".Z,<a[ ZۣV\d^}eg9!2tϷzϭ!|ܶAC Mt +#A$SR j=?+a *d!p^p׳fg_Dkv ?ϰ(NmعZp!Ň[,ҍtWpB^G?)UJMQ>6 ](軜a-pAFJ)\ ;&ȏ ZdFHxMaAJKTeA䵅fTFt"_}`%ܴAsN;,WJKXbꃆtfs ȻA#1ɵ8ȉM΃Vg% Dݺ^=='tS@W}D,Tw.pI:롩9zx6JjYР3݄F/ + Z4[txa:e>xɒнXsYY  34_M[Do{p0М=AI汪z$LA.r(ZpA,T zrЭi9Kщ@r__'}k.NDCmuKы-0N{U;:oTPMʂtd5+IBM#J) jڮ'{LiL+| y˦3ޏOi: =]F:\yy ~9Z':c~Ew/BV?Sya,qi(~U5Ug8t9p|K7}rtb'p֠:BE*^وQmm'҂ ΆS/G&7^7ABJ1 R\{`# P[̰/@ؑWkks]F|c#[0(]v:GG +McwE׾r.G栬5;&ٞ +GHuv&mt`v1n,Y-UmhaNbymPOlkF +7rBHԤmMޓ4Mv$T!P&kM5fmRg-}u8P_KFиn?qx]v/wY!Q} <P*v85qʄt !( ݞ|2/X@ޚs8KCzf=to`zD68XQvzqz8 +(MuFU|Ul.oq8e^qMmvtE}]P>]NM(Fo`BDi 9S\-)K@wh-m4:-!gD.$4k <n ?+z $|&n"|]ߎh<iX +ƾ:7Ax(%{ ۲y"o 3x[%5(5i׼ ^[.mP># 9݆m sn#F=t|=I O א?C \Hy<بmܶ؈>i-B.@OʕHj:͚է1mp{F\5?ͥƥ6 [z7WOq_X`meJn#M6Yr ڮ¯ki /&g;/!Aݝv+q)$>8+wD4)X# I90:l7aipY79_͂\]U<$2X\~l߰,16\6hw>nFg1dif&)nv9pm45lo< 1lزcϠm6rw4z{{[÷dR m:NbTPw;6,z#df$v|{fn7~Tq{sLE:n&䖓2'ys'@97o<ۢ+JBw~nA@rΰaש arfb.jEtU7Rl,hfz&{{0Sz|wj4pݒwRC-nAz|oΟݙnv~Qke:ˈo\w0(b/ESUw0pĤ%㡻p䷇#wwJi&A|w!tVN{ֽTqiMNy`%N[(qv:=t9y%Їb&^UH3]Q YV\xxTzu>{~>RR`xO,llZ/mPW9΅y\p4;y-|#ހ- |邼ȷS<1!oZDž:6O?-̉k)5AAަ^ry-, -wyc uމ.v3jKU + ڝ"'Dy%9Bo¦Z;JUB{Z&aljw4-'=:ba &r|HCdJ[dۉ 2P}sD}/ +xSfC BDk(\wE4F͆'ҡASp?z5 wd,`t d^Qs]hS:s)o͑9)7#yrOz|mċS뮄<~އ@JS* Bf 3GBui9&w+ 4 xV&PA9[RQ Choz\ ǗjX}oP1S0JOG됧 7gz|ZZo,Rg&^׵ -pT0s6|=z2}urAKQA/Y%{Gj?q2`1Dn{5Tۋ2wˏ܏B0Ceިa_Y}w8]%r轟'6dON}'{ڿ3mOt|8Eh])52GBB)FçWM4WVx n^t\-꒖ڬk]ȐC3qfL^1(Sy\w )frQռ2qm9GN ,|^{G/,Mlv>MM*HJGwMSFNP^w̒yi֜3՟Fb>Gǭ_{z:9g}<0^btucƚ=R}ˆƳڞo?4S}Gp >=L{o!#5vP GaA(ʛV6ޕٗ[@(5{ X_HK+$VZN"a"P~S9xml?uq'<9d< +Oua5BdRqR +Q|<Ҫ !V=%|vwՙL:ם_63W#D?H`M߇[BG87~~: [/vFI+Q3U*&K.8h!a#l1~a h~1|h/W#w`h' lbЏk߇?0o%$^1x1U/;O0a.lc_R* Sr&h/K_೺|dI+L%Pe/!I^tǟ,! +| (^xYϴt0uHQ(uk=D]l0"܉8e]ۦҧ|İ6Wt D0X˞%/UL ӻZ %rUZ"b?!8vbRl+r[R-J2\ )sUZ."ԭ2JuNncs%*Lnf}2пq11jPڹ-הчŋؖ;6Bw##MIzsCʋc*qψ&E8UFv˱`Zmp,K(,fmEz?kT#茖Oѡ2#40RiH`角DA]=BH ˼*I'y"⒤ +<6)8u BXبXkƭ#@!$ 42QhhIgJ%0tH +ZǁFSep=W>:PQMNK`(~U $Xe4,WZH\όbI "(.ul)l zG,&$ZMIV,؜j04(x}APsySzNGo%X;^щ_!)6F'.BLa8S*KS&5ڣX!&i8$.&rJ< 9E TC?^;Lʸ[/2J,x?Y!!5\dvQ߁V-9@doa'֒8ݻGf.0t̹0]7c2"ƥh7Rxp~7E4OԄXRU0i:SY\(2]k[k!U ]1'hõ#rV+gNv*Bj?naXҏ잒X)F૕P+gיw!JPR^7M-"6xQ{lM *.*&CDNpcIU3A*Y *K\̝EzںLxʎd\ǀ@bB?R[hsng +IWIYL+{tt(R=m]NӛfYRONk9(5Xp_^Fl ïm·z0eQ4XDaߏMoQei`аume DRDŽĬoΚDi;,͋eLi lYE3U/bfC.op WUYi<4w:$0"h[^cyɑFqX hmq9fQoS'qx rw蚊@b[f<5ij>$ +[BQb.ف&^Vwn0=M.-)8*[)LZJK+3[UIhC|`3ڝYػ(Y{7-9:rlN_s.S)D'щbrA@QhhU*ieY!_hS!tݱHLj[St'hzw|Qf)GonsAK%g,dh"J \d: +O 9̖ +>* >`3NL_ǍV=?DNad͠Ll^4_+4"#(_/T>2w l6b ڜk<?%=Zs@Z P7b&P ὎^fYBEf_eSj[GhOO˜-/$iKPڔTP}z /4E<8'#3?2[iPBgg@a_oĖq"Q L/IZI$"XUl9p;(=3pZKU{@*1 _G{gI5mLweGY¯"JN$,.4 P6^oХJ]]'0EL5z1!z5[JmS Ӵ~bU ;_˞־11\}%aiHlLA>ծh]5u*3Յ-l̳DgM>;Ip#?b9^pO?s~q\zKRdG p̲[ +x" $#.!Xz(}?B\gaR-? 6M +-;xxZGV~wɳX7/eK/vf8|+FX85WW7 Oxg](A$Mwȵ8b:(30?:Hĭh\[OH IN}֍NچPG߿W0R1P C؇MX%Hd{ +Z H'5dsVN?zS7HAi?YgجtpPqq&>ܪɟg|Z#=M^AdF<=}0smM8λh0wiW țťiF "c0-(xG㻿CAF߿JVpnpxqnܽwuxg +3!S3O#9tG1>Y>}g19i߼NFomKڐ'V(*‹Y\AFjY;X}`>֤zN >|ͻ9:H0 i +;w/d)x/HhSѨxdXT5s7b7_IT/Ik>ܧ89oO#/1t['ՍL- `46*vF]ʺ/we9'0-tJbF]rMdIntMx&7S0:=yܝucǺAE[sФ 0wl4S)wBU2m63ÕFv6N:k'g"5 +@4"{NO0vZMHw>e_-ęFyF߯)Ə8=x9%!4Z/Ey3ÁrJ\(7} |O$I/xh?zv˄׺4qg@E&9|\\C߻|Na}=ۆPQ +!>d|pA<d0vi6v ĕ46o#.s Yi=z7u&2}kvP&WbK@>{x5q'acj'q8!@ J?BGV4p1gKY CwV@hGfW-3|H$R_@KFL!1msw uP$P +r\KN^=~{ l&[;<& ?}wi%ӦD^yԻ_f[]4 +T m)7E$7߻{ǐYy# hCh!NHz +:!y@:o=yҨ/=B$'ğƿ39v~(gR/qx^ϭgQ<7p"Ai!bb~`2Q:w+K*o5sp ]hR`% EeKJ7uNa7$Hb|gk<B+Q +E!^rWFȑ/t^z0(yBfs]rΣ)|_X 8c;{DZWA~~3 1hӇVy\m#~9uΞ񃳁&pu|~`{a엸2:F/͞s]n~q]/.qK{KӖry/@\2oť@D"~V1,- BPO՚i+P:hIb߀Ť-A(c^B-3n5 =)x[٘vh +L4B_Fmtmq!7JT2$Vn'&UAg<̃"7IGJ(M$ą&!ʿ<$F`\G~$XI`/!2|lΡc3792 2xk>Ʈ}E?\=$̗D |i4]~y< ^BWA̳%<7oRތJG%3u|{~i⿊ay_ɰB 2j[J Q$a~ץ;9uw Ʈ]\=,\.v/ $Dh8\cg;y^Gn 0%K` t;ww'F$EE;G azTi>H>sgnú_IXcL:V2(o9H5 RHMd!7ṟ=\R4}=HKa꘼{5_9[gy O" ޓȗ7u9P$ S=F_0B;cm< s6f7ll>cn ۽<Fm+[氖2z+èu np]wM1-G4pMtinݓ&c}vd Y&{ch7;ύv#uq A9tF/~3QPg߀>Mcc웰@#HhWˈlO/?Ө/ā$=N={~rQ}=3Mk>_3}o=y~R6=PdMxk4p_?$}~M#w$ 69cwe +I`hۈ>5/BßA I줪Hj;9@z1Yy yɼߐl5b7y1~m33wאhlmi`Һ8t4Q ]>O D h aC6^v}"{J>W$P$J[ADswf̑u]\c7{}÷-kx޶cLEh.?Ф[ЕqinM8w U#Oy:Yq(2Qs8O#g;3Hy+~AnmkiJ>@6J98b;CyP$"F0&=}wx4K/]%IUfp"n^`V =}&Lk< Zv<:(& 0y$M1Gpqou}M8 yEC8cx"Kߣqu x9vG}qy I@40+sW@7~>/9ÛLUKDgQy2;\'&Bof Σ}D,i]-fq}SW"%{)IEQ}8mXΣmcpVBsOWѾ‘Y7x!ikجX4zɿ'|wpmC((*o7"zûOk~Yy<|2?y@ĩ`X +F8Jb~xФ.9U7ປFA">_ه߄tɇU"AhU >(?Dc9 Ymrx_ؗ;&Fޚ3]rƮ̶}pǪ\Azq~0Syz&P%?i 0}j3<v,Ch3X6/M Mg4p&2{p6 n9=g|GVЙ5o~n1-^+VMy5Sh f6|~3ouwQ4qv M8WyKC[>}wacglʿD*" +Nywi7PzT=߀*Q) m'ÏŗH?3Q%baYWɨp J4~d|RA=TG:<6 hazTqKsubÁ&<#bqw ;xf/[k|O"_ +ơ>$R%?i" Ƒ8y!JuO TC&g@gQ#mq7{=AC6{ : y:v>O yǽCovNc}SH4%Q7cvg:o׆P,4_:PƦc"W}S]̺1zic_j,[Ů];4:ovi ovάۯ 8;sȱ2{l+H}BWfyY7ߺ\w}B38ή!ov5a 4 , ?S7"~)7No9H{:#/DJT/s(7N U7&O1}Shos|ypƽ(19b;<oر7?4)?F s*9{7Ƕ5?Vb/yn 9af4u˹صY4r˜bX`o~_],^ŮێEV͋F7L+ori3|oޭ{Oήw{i(5IϣgllźܛBQ]>4Z9} @W͟C]h6 S6/ths,Ac_nwy(Ȗ:^N=0j3zu𙽸ocinGܪגqi4pu1|n4 _ܭSx,Fy_ȗeKs7o˖woswdW{& vW.1}kK]^qn~vܘ5la6taKw ǰtv,gD:s>n9ra*tϡmƾy.on7 kYa[63`+ֿ.p[ vsch=bn.qhAŰq삗s_pb^-f[aX\ Űe7GF߄p1c.tf[|lVL>z29ԫ: oJ,}Ż&D+qNsO Hb:xM82|n6N{@Mx[[/+#VcŠoչ4|l륯l+3Vqo?qd[/?÷nXnv3Sbr~mb4 €a`-p4X1l La[\X;\ f/m#֍\1_5nK[wۿWW^н0-hc`-fssbXZ%ˡ6tzAC'KZ-903d]ZʚZjKYR544kp%54= jp2K٥P58wjyxf[RY-+,% ++Ll)mgbMiYVWRڬ*9mVVJK K +[uUjSUJ+5uE5E5Eeuee²ʢ²ӒjiWUTWZ XYTY+ +˪J+eŒ’VQeWUWYTZUrX_ZrXXTTTRXUrXZ*+),YAJkNQi䬰TTRUYTTVSUYVXrUUZ,܍UEeUEEuŪʢjQiRZUTSYUZWYRZڪ*6fiUڪ,*-+mTUUV++eUeUeUU%eUe%eUUeU͒VU]M]UiTYSRVTY)+ՔV[š^eMUiUQYXZ),ՕJVQiiIUQiXTTSZTZ,)**+V*RMQQIMQiZ)*)--+,,)VJJJK+NeTXWک,,,))6+ +RaeTXYKNe]MQiUVjMieIeګVZeUR٪*ҲʺjTY-U6^eeSW)lVҺjMeUYYRZmvJZjY)mUVՔ6͚fIeSUZmV6 J*K*+ҢjaiQiiVRWUVZ+*,vJ+ҲʢZТҢҢjTZY(,,T6+**K+K K +++ VV;%ʒfUQU]iRTڬ+U6euE55eʲʲªZReeRTYXYmV*e%ͺšfYTY+,l6j*+ʚZIQen,KNQQaeXTXVYRWXZR+,-+++)+lU֔ +KE5%b]eZY,T6Ų0jaiSXZZjJ;%ue’Rie]MIieTWZ++*WJ K*fZXXZ-*bYiv3@/ CP +NOH[R>Ki/ "NrDݧu~k\ ++TNE zьԗO/>ub/-Υ_8(&)]dĤ~J^LbJY28(Bs YÔ0b'(=NM*H![Rs*|hc|4sg&JL2 2L-)[`' G0$8*O AyBNu +ڨjoiz-mwU876K][A0 +p{#f*iB?IP\&i1c53D6c `l,B|`[W)yظpRJV0KDQ$0pq)RcD +2.8JB!{pfrV;` HMrŶeUұPqBA!-eUCV^`e`he#@ЂH_G9 lrru6L1eHC%S<78rZ!lq |3_y\$1M0 )_EJXZa(DƤ_~InN$5QDY; $C$IG({l[ˡL#C9p,' (oA&CC]@1 fuj\I[Yɖ[1mI99$9 d1c&"V(^A"@p$=\qĀ&D N0?dXBrp! .F:9?]Ǟ$@%{\EUԁlr|ff&%+e5YRIܨB%WiacH53R3K"` Fh Ȱ՚+,];%9%'f:y({ŁI1]I,&V/E8(0hiX)& +K!/&Ԙ;Rc/ I?%3&ܠ3rpHL2M%!uҶYEU*"q,RxJ+ybWo\~b`%*e>x `'ODDU=(V]GaŹ0jϠ +܂kg#e )hE|W6Kq.Bb[S- [ST +A'%VI?"B$ rpH>%~FjXqjy(2 f#CK%z'ap9#$ew@vxoI[w{}9aEa RYbgƘɃ߁bWRJ0cp+{C٭E|lQ3x e}E}*_9 Q]ݒ㒼Ͱ`]QP-UP9Dҗc&#DqOoEV/CR5 Tj*"IL"q,ɑ:ǒ!x(rCPP%{YiqMVfCH 9%KB"W [#w $*l/=T1 EHg U7\1`KR]EqE`>jq8h%Ӂ6ټDջ!w(W[e%Unl# 륀9! VDx|1XPPx "BÛP Lk'@d Wi:jUh؀P{$Eu.d,xe+dQ$vڿ]++ơm))l ;INJʏnʎ[ 5j4-eu9@bڬ o^79@PIQKFx.xɪ%br1DA)8!=2)v$3).'vG")rKV\)% { z6HaXJvݤĽu (*M;ךlG\3'/n}7Aq]a9`_XrZ;IK(Jgجb`Z)uLsWF9,,3`JR.?&>Z6S`j3p!I7pAY?i_p5 ͹x"^4"%䌸!W^Kz + OX$^ +ċDǏr㶣&<`dLIV=;@6jsdrMetHa8+Hz(~2h_7L)bGJZ>6?GfƯAy#?50[̀#%T +!YciFYd^EIGюq٭D~REą&rB8gr_kȴFŸ'ho1QnZE:Hȋ6+0=Jsp=<Cf_]҆*h3~Ma +)]!J)mf~+osTOZ,*(&$_R &VѶaY'(x;P;6@@imZJE$$)5ؒT5sAIgoHX:8iݭ'6S*=AiW#kG'To%ltS)*)6֍Z{YcफbPt JD~%O7h/9YK1J'4jx#؀ŤwZEjaY?Jold L8~(SC-uUɻ)O + +~#t,T*7,8oBE"/H?Q'1Ѧ`3CxN e-‘Hs}z"l ɛA +ț-quEû/zdfC1i#165ss BGƅSСucG&vцo3"n> >AUD@D">d 4}#P5"ѷy:/Fg- t +Gd:P6V’ZKCV"%>ƛ F&mƈD4F`+!$ՍLzJŤoJEă,pKɽ/oPr#{pB:r?@']OV +EJiΨ>{[DHyҧj">B+ ǃwm_ Fr3:yÍ:or2vpxƟM+U +Ž&U5'=)/@ DbO7$y ?&p%.);*D0V<"jX6-폹̕mqpy4W>%鋫mq͆Ksnϯ_p±Jf-h]@UɰlXX7.bXK?ۂvZM}1Gps/L1D$@~i3e*?i38}"<`\Hw7"fcdW26$="i!.49<ŸU ; fqh ̿ bs0t*Ai3I T +I[*ŤģGAmKA%O qqTD$_:% 68_~7 NNkJQiβy+#]sǾb'H$:Ki4U}5 yK"()u5r.h2Uzjf{2P2zl7LZ2gFș/tf`}3X6?u z/%_ϻȕuaԸ@5 :jţ0h$Sm>z vVͺ߄羑蠗z(t)xDO᭠ģuAy;F8>GģOlQc눨ͤjh\Kb +j=Sp2:S٨TЏA(>ǧ*N9FC!," IIƑ>Ƿ7i}t +m暱*X>:Y7:( (jF&=AhЈC(P%y컋.O@BkmX\DLm&uZG)/ݽ.m.)aQCt<(Sj uveB}dLb/Q`h}-!$^-͚IG# c*'_B7y(,> a|:c;<{';"~KACa`D"Nt "\C: JI1.k +"7#R"jjk("`dǿ?H>/Vu2k*5!7ͻ)u4&'vƤD# +D{%~Me0Dt@Dc:z[!R0ݏ[iܾ  J_,<,̥B@N!~ICPYC8":?Hios_[W&Uě9ÕU:r% `XM!T E]%òNЁyq +N%{1-Vۈj ʿdOcG! )jm9x(|[XH)'6&  +F孥C~Y7I>ۧ]5fȒ89wg 9 +Iboan`g4̡X#}$mS` e/u~o F[(3 2&$`m'DIrڵqIK6&!s'uJGߥ2|quIX5/vm1_0t+)5pKJ0vv`v%\!{,fJ>.UHt),6d UPbrTm +IVaR>m0‚VUuD-o))ZPP KN%6"$EZhf:%+*RƏ-Ǧݟ揍lƏ "x/@h,h4,wrǶ ou7̝Zs;ۯ ]à ]‹_XQ22jĦa;sm +hWH`[j7[ ++#NeW([4'4j&/<+"Fafw +  O$7O§ I  s|vVLj%Aq ,G,:wu؞tފќP,FnQú 毌wیflb1Elm\[E:@B5(&4SZE%=sN1rV.$qotP'`- hG1a~EBu~REğNSxU{6ֽ-]m Ƴ-q ?6?9D1Ǧyп&d+#͑_ZҺ]X;/3& +VR1]( MkUQ[@m&i"EJ@uΐeF%D/A#)~a\ .bIX"s`766yt^K &Vm[ʶ3an36b:[Сu. = ^u{s+h6 .tAV7էTu3 =| |s0t;dbq~8kpv+dAvhi:~QnrXn#d9zP9aǽi)+f +W:l`Xv!vê|SL`RD[QP\SV' l/( +uP4b6" 7仙7# h{aaR6 $qy*h=V|<Ǣ$#p$>/th7͟۷ {>:×Fc̶6:1o] ^݁VB%sU/vhc1o@m0#!] gAobٸri=5nb^¾b6t] OA.րF:]XHq EXMm?nPrފ:ΗϚY361" i +WTsPh`W]2G ""z'yA;#uwywFͷ4LImt \>uðlü8t, ,}ulV$޶'sے&ݣ\qϻ'/ 0b@!N39H# HsBb΀.&\EuNCfP f%qΓ5 h]HRF0V2%Czi07"K'kXrjKRڥJJkR^@?{ޗ3{fs[(3\oEQ0]@Vr}^ʺ0h[⑛bl' kWwu7 +B)X* +r85RQc!C:1[a8&5wMEE]!VT"b+A1[a{Cd8yC( Twy(+5WI_17H@7Q9CX>|ygntϹ_];C9au--5/3l +U18= /: +PFV0-Ӱα 0f$Ĭ =E3^k ڽqhY] +vPt&a\I&W)sQNd XfMe9wf.h0n+@`0`|!Cb@o{q`m]rea&}5!PUCIY +Y \a6/Pp*h!9;hR0<#;d'e\YJmNBx]WR#(\IE-H( 4tVsh'O.KP2:<@m8jK8tm`u)bI:x&lnUOвй*t: 0ZY֑l|bj|jVxZ bPvAX dbP8v{*jkQ.ʈ:po #f@iKZM' @]< + 8 1+]pM PKDFF@~M!-H?.:(~Ehp6"u藼 }#e×FwU+)6tНԡŀ8,*<Ƽ2;H8d+_lU× +Br +1Ұ4(WABt|$!Y4sP( BˀnU->t&#|3R)bWm:[')2$,|+ +Pz2л;|\UW)8eբqzͶb}Ä ?`r&(" qM^؏BGF̑4qV7". XXi$Q@MNW4>U+6i+\*MQga5vf9Ŷvе p 44Gk$PG b@rPa 2R$R qUr,U V'R 52H(U&AHV U72',(SR‚ +A)]SV@YJÜX8nzdnm,0f#5쀴( +sօCuL'\.Cn+d:94vN8vmVpݚ䨹(1f.BL"\;4tU/Zj)@7&c\aY MIP>AUOJ"`"B,Y(ji!QчAjն,S6A0UAReAw7j|a󝫎'e}-H!bPz.(DCiᾬtị647ۑߪܱ*`E t&ݨA/b/(wiTvw-^\ zB/\D̗|e,]ke8 T6,( Bb-(H v:Lyn0 h^/DQL:x +?gPGkٟ*VO5:+"]5+3URطmS,CZYzn&ns`C + +q,)nkk>Vbˢ0cVS gHM^rL1e23F$M @ >)@[@2 0dRhqf.^5tԍ\v:Kw"bw `}t$udSB Ai4|\syܑq}6 bb5`ؓJ1*(``a+8 X2 ˗@g&GCPL^Xji{ +Y<qBUY]1o/ +e!tbn0ZۀAz0٬]Pu@ I e*m̥,c|=W7Z G)᳎T0gȺx%؋X I$( H]e_Rd~b^„07X鎕`#mdz~xP%t"UC*AeGXHB@7@^Ba? ~Tԙrz:N?r=~˭a?2\lv6VJB,J h9#+R>6wX+#%bZt+7 Xk54 Y,ɥ2 #,e,VF"ơk.F$^j+/ v >M !`CɬjP:?]tӄcŨa-)Y>U.@2 6/&(=D2]Up{1Ӆ`zЋO?zH0?==F}e dAHAji$Ƀ'"PJ,ODIT="G +\%lB +[EY7P&\^YuZ?Iz̀!`YEGǨK.Ճ#(N ˩aPײN‘Qgԓ\xzMLK1## re(cLg ܠ5Ou ;[YH&S@.!cDVV0J؞ӑ\|֧t"l ve#($r~[s +"i2ҤЈo*&D08lm$(E#llee((Hr)ft:~ਗAI|:$ԣxz.#u'NyzO:ctN:-;vu / ,ElP 3> m@I.fGR u%e(?15 4u.!4秡N0_Q2׬ ,=rzUCԇ@]ec;3@Al$YY_808`q ƈB9B/<63sx^oTġhCFA9%WT L@H5u.vs9u GD*[u)nF^#SeV[&. {dr~} a3gN_@>8poщ& /))E` b:O d|: PWF؝sN: %!a: 'G ok砐o*a!WN?wT!prjhtIfOLC%2kpN{dͭ % *VOj[AE&gk~NO d~O X@B=ˇ;)U9CiFt[9.C=zZy~F@X^9΁I֑ixrcS8X[ 5'1opb&dQQnPCAOP5 u4] ӍzO8=E#nmzL.Ņ +rk)ic!=L1!bZ!ĴBxixc3|#F`6~|m,C0E!TNWu> + zSE]C]:P'drC-29\hs$QUgXN#=> k$MBRTݤ%j~)ݨ2 0"t8-j'ƥtoN5lwiXd^u +_$CX{t2MfEC>9'3Ie2L]R)fQ1`i;~AȨ7}M3/ťt.0]t;M7];ԵR k[?"f[C- . IL9D|CS\cf b14;Ntu,"3lÅ&ƈLF>@k(K"0Hu0LL۱50=6ƕe(_ke,`< -@H&XljK (06P3qpb04Y8cf?N"}Y R!(w[ 2. &ziazKovuO8.=gMY_hd^+ ZdHg/܎K1Ua aűRCDgT8!) uĨ!p*8Gj% LrUNK_je#o9WƲ rx]Iu_z {v{{K1t~aLv.mM=CA7`e)4|LjL\'> ""ܞB+[s`u qARMetu:M4t2ԛ!RSwRx:堬 +(6L%XKcu NTV K*Kk65ӦiҢggQiUY3-(Ԕ6[UfʚQeijigQkgY(4*5*,*)+-%+*))V[ʚZR3C.řYQIMmiUUeԪ΢ִVYQgZXVQٳ4mU-6]3۪mXڰF677XJUeeVeEevEJREUeάҢԴkgY-, 漻--K ȴdSdPK.l8 458fyf渜 RxH U L b _V#I{-Q9èg0_"iG AoNC_ 8uwRhoREKin1ޏc߇B8Θ}WLZm9/H)mqJ #Aijm~Rh/^~>|Atx+> Pn#5o*tih$Q۪欽9;:CL o#7CS,,k)-owX Zwc͸%)..Ÿ4j& % L q%Qq1~k ^ޅCT :+ _jOd_m 8bYb4nf/&+ 1L V7t6^R T G!~47Hْ\<7[i_ЬbXK/Vw۹]`ź]pa̳{Y/qe5 dp)S?e-qDA/e"F%đ:yEB"jWbZIV"ڛf\"!OJE#ţЬ^OBR):x F0&SDio:MQ"$ B,jdpQ%&o@}8fO"=:alQ"$o&S1J6 0W:>;0MB ZC@~YS\ҾmUe|]A B"Ǒ/0)qD/O "C4j..xjǒy35:vO D7pZiAzwd2@2y;ءi# q}$KqsJIGlova;Z[場bPR =' +NI6!/e"Wy=Xưj~I@1 d|?|yp!@<~l5vp#K` imx\MgEP7M>$5_ȭD +FTeB%{( ,tQKȨj X:^<"fhW-,q'UC\i3")ʅfβi's +tPY48ORDi/:DZn>"V R;Hہ.&:I+Ig ( ]MgۈlD<$JƮuV'WhU3"$)4I'DH!J¿3D`5kS(.Ccf7a:4/SjRExP}P$8bAZP|{F8#yHY#u~sF?CHħXXY:Ļ[ C FDDk aD<n9ێucȺX5Kg/3:ۂ>@@^qd>tMb֍͚P}I/ngۿ!S0B1B;*~Lwh/yZ@^ + 4oZ=o$g@b1*/ y/uC +;&vx3n3LVp MB9Vz{A9W:* +5&&f]`EP7%4Ջۇ/.(%38i ;@k ŽN +o0@~)s3/tta5xs_6zb V.&/^/VPoF4&pIc"JBj'e3zw7x./?u~&o"Ar6cpnL)wj4|g<z왗g108,3V7|3zr3m%KygYG?P"Im +7[F,הX)_fƶnK=2{l>FohY=|P ~C}` F0zOpL"6{vq#er!G<)Dz5̙< sUũ&m$T˅ЁNFAIu\c rA9Ŗ_ă D5Reč(Ɉ,lp%B~JU=}g~XW32 PE,;[l*cgYD%R7Ȳ/xxx:(@tCuhGoC8a옺4Oqne6~qd;{O^.{:~Mp2V5;.nl>Wɨ7P0oYDϯ +,z$̠܅[-W8t31D:o֘4o<\0m-?V͇u?sa/sfMar^pT..m'DqݷP( U +j$"('̃dhZ4.{ K1{i>(,L'ɿHrKt %ē6 $O1Dٗ<2zl׎K@{2]WFy Ã@#_@ZR_a(/th^Tqi3su7}&XU|ksiȽMÍaji DZ\ +:i}2}oI&zH)ؐOP'SŜۄ/NF" 0^mI\iی>})'}qpø:@FX?u(ZIA>9&p}>6abwkZ{Y*mp . +[ g5m]:ms`a *'9ÈT-.mwV KŪM +D:I>G컊i 4q˛۬sWd)xۄ9"gfȡ.gdK[ncnw/ph9nm lo7&#M1AIw.ԟu7akptx][Ů Y=VkRUÎ?{F}S +uW32k({(hT1AG;H*q E!,iOPZWx%30)7(9\CB {AǛT8\l dRq LF"p.#v|"N4ZD]~c|+goe OFϬI$O1^Gqy?(7oXj`@.w8? _ZG EJ&zz[`B }q.v˅[39qQƶ7)u7*E<;WG֔:·(- 9BJ'C{0U&Uś"gv(6aq[w|x~e_I5$̅(P|H uyr"L$*N;k"97߻;Gw&$[場5 '8ciԻP S(sM|j²ۜD;dsw-mwXǻa.rgc6c:&nޙ<k40&n LHvQvqxKhB:spZ=a^QLH@TDQ%(+VNKH诉 QŷY8M_wi0xh#WB+} ih.>q RHv'6f͞lE vWRB\ H'ͤjh025Z'Q>h;"ڵp(4YK[HErwll[\LuƤģ7dT*6_:;ggqcg)"m1# GLk98.!Zf8.p^GϋQg%#\ɳPfצּCD9߉5>]󕳛Nɹ8)49|y\3G#eK`&!ڍm\Øw# h7(@qԫ-퇹̕m$k xUNaVF$}?ֻ>w!4owQ&gȴxjtV J|)ȩފK W`}Ä }B3;,mq{*hĹnRo! )8x淆:PjGHJPŹ(m&zI +xcج- +o Fv}$ +pdM+s,nVJ!'aҶ1lsή9t<Z@:-WS*uWUo0a/qgD:CUzKd׺I3(htpLd܈230yC1Jg $hxZ:4JA&o VH +3@s7DM@DI1) RXKc8RhLyʾZGicڸ=|_ u* #$X /3ɷ#yW5oK]6^%-g7'&?{7^ ֵ9*jûElv8s^TvtmH O2G0Yi?͟1 VTxL?/IoE߿ģi+ =GYQ"( ;{E"OZHF['!;aȺ>}6%N[5_:hR6 8;vC@SZg1[/y3&t:Hc(&c{A@+yCģ_=&} P!o!H?wFch.L؇/yz}p9/bsc;4Xy ӗ&p,Z Lm shǻHlT.od]fPg0)l+#]5òj{ {CMFg"vwȬʛZS˕JJ7.BM~CbnL [Wu[0,tihb[p]MD A>'Ά;6/5V6fɥqth"NB{(S[[-M>;+gi|\VֽKJaTBU2,kúg% )~xAVzKâ'unǾu +n!xk-TIxOc~wz>? +%=|MmSHG|;ze_7ܝu_θ<|](67nnQ|&5: 5| aX;HU@zD]5sy=8{ ę t\?~R e}=o3PgRC;XG z;a-kݯDm7ؖ÷-rz|v9.R/.Z Y:pm""p!s]d&f>uHn[sn"ސVQ,57&f# PN10g#pN5t> ;3F F-[lAej Ib@Ť90{hsc_=&DM!J*7uc޺.te]ٽpn>J5hcrnϹ_X;|l6ah_:?8:#y7໻+wHbRn, X:Q**#} S͸pDm@~aɩ5Αq%*5^g 1~m FX;80 D ,&m +ML+ù-j~l|hC;pL={5O]0ȿ;R<$H4\ 4quPe QYcٜt[UR]V8Ջ[B(m9wf03 XXA>cݽW@ ]3cJ%Ii'Gpv;bpNÕ]%iL&LDJ8Sn8 ʈ +f4\/+"+{Ɋ:g.!as`zg:o F,z*&UzI~xGxraQ+LI3\ `@nԋI({.wm'サc7:ZꅣO.unL`h6b|D2kLDIJed0@ImC` @Âx*o͂nyZ}y۶?+$/|5f\f_6/9WQzwq`f~9('C,)y{_s'q'ˉ;,='yxz%UAZT9+\>< <|pBOxǤ玎KIaWF 08qς.` mYٿA|:?k7yr{}m?/Q*H?kEm|u- `c5W;p(&ognh mxSgm})k5Q!|*[YE &H59r"(!mT=B dV_k;^6Bbb3VӜ߸L;WCNonAj,ɩۘ^? 7kpʾ}{K2EP W܍N~V'j3)· g%Ig06VJGN}X?h1|qL=}~4"v9ps~SJk{Y#ܞީ@?zD"{ ' !>n\K4Zuӈ|(=c?B+J_Z'ciyk}Q-?IC]Jrykr3;cd`M@yj/ПgoqOr:SG13G3yh'#9ZISs۲uUK%UC{]t-z/R +[;8Ew/iiхT'N6Ht=[eOkFaAoKFZ \swy;Ib:f 34W0ߔa#D6Dd뺴KH[3}8 U5~ǻ?e\ C%*(Et^hx?D>p2e+]$VVXɶ5NBD9Sˣr=U_y5gȽw/YsDW9}:EݖwHǚ/%܆ɉZ6gy+4;`O@M#mbfVϼz2 s(? kP0R6GP*o~&HY̆ɟcFѷRfW}׏앑c"#n'OwNyOobp +(*iE!ZrUX_ U l;amcvE7T{X!J[Z;l=~@bj+/  +I^qQ +oTQ,DzO řj*#Gu//H.jGр(Aʛ99f [صiD5+e~_ dg f+c,u6~81ڭyaA:Q\e`t \B- +U<7Clׇ]MUH@p?kqr]Q3,K&X(!x";l4@r^f&ڶͦ"pw1s]`sR >*5NXRk`6KA9qB ѼPW$64OYd{?AسۗXQtk^o$'$M C +fqG-+q¢&b)邫v. r~+8b'{4C3F扡V-չ ё A&qjG(o3$JmrN^)7µ9ukJ BJg`u1^#VXk£CYoX[jQ9J +Ӊ|V;Bk![>e#Oʪ wˤ1@?;d@wN ^*),R: Ux F9+VNOC=nCr'Xmexz4O9D*i!S/D#-"|-VdnW5]o"Jv[Bq7أO#G@A0٢a`ރ*`]{ڶKŠ.c +} "\C Ɇ]8nu:iƝZ!]5 +K?>8L#%^H6Cg.b-Z8<cɘtu6Ts" ?DYC g!>*-2@ Ԫ2^ I IN?YsNK;9a`YOilemʏs'~mYǀG-da۱q,.Pޜ` +tCub'A^V{gAm )Gzkp ̢V7| ¶$CT>t.:}ރZd"y-}l JZUe 8J4cǷ۽C64lO aCޡ13B?D)mR [ʝp[ݺ@l(0_'y&;< +VHIar&j@Q҇:]aH1: npb< Sהÿ)~E}N[+9`LE0]yLF6R +fؠ\/Y_Zؕƪ=@cO?P8պ9= 7`֛R`/h?W^ .<6t2)˥kۇ +N.Pܯt\Bzs}߿WPdb:!?,9M40V%tF_?8 +e.et9x:G +[E5r{hE~Tpok`aJ +S"$/P5ڟr0kH!6g"kǽ|z3Y> a.OUS 9o9rG,# z%aEJxlDopɃ Bhp-/.eߕfBgǞ5YfrE{VD;90d͓Uf_W +Mre)(}𯪕e,H#< }` [ДEy*Ċ%r 2,do3"`B {+w1! +RώR6@"{?J?ƕnσIX-ޫ(4hD[.N:`bLuLՑea,Qv\^ C: WWߠemY>)5osuE'ϵ7Dc\c +{}CV@/0أ|j5f~P</y\Ik c~RKR<ׅ֋Ԇy P*P ;L=5oyAQUޭ=uȈ fYn[ɶ@λt>N +e=91R!Bޘz!?8Sc"$Q7V׳UPA#!&˽f;S5gCР6gAoPVuQֶN + \ّvDN8vRmR2d*3L E*aHq;u銃1*7A8auM":FG 2sM S|0f?1Y0oL}=pUUO5%;dR&rJ9eAnHMJӉ,A.:ݮuz1bۖ"}`G,8 ~GP Gm\"iV@$w WeY4˃ML`FxYŀkiN8! gݓs&v[Ȣ.+%$x: +xWeސ7+])UNPbԓe~y46ڥ͈p!T,g^ )Ry a,|4:%jUkVWU%%6R)ǭKNjK4DOLM + +[vFl)WyBدu18m BI4+I4P.} ѕ0S<޵`0L+" +到8q e:4W2C=itspnǠ| +plv4s%bmLv·Iݿ]QԨb@J"yϮwD2Q߫;QX* i,q}u4No ܢ)}t8*jQ*F3Тd!3WI'!+\d4}>IU!D +7݂VyQ5FL!d#QOHY鐭ě-|vdQm;aFW%y䊥)0#dFi1Δ0ԫ9|3#(;lS6d]@Fp),1| {;۽R5: (fA2g.k4 t[щ/\ [EсS?+=y?'~ C 8n ɜ'PbN&V_K!܍_fƦ񹣓u y!IFfB +sqhPXA6Q@Lתh5Ĩ}pZ@0x{J>EXn(I >fUPč`YI3T-1yYT +dh|X"̘"D8y4Թ"V?i3l`чɱH Uؾч%s UK%U]q*Os#e)X |,'" Tܧ1R,a&WXpFP!Hs2WC!uS拧o!h%vII$>Q/NЯߖLtw(*,<#0QNԭp#XRRQĽZ֌*.&_ږ2rرDqWKQg@JƠABy-W08H[AYkO@@<B=&J49`[}pl8 J:z//ZVA6W8#eSpS{D#%%֥yrFp8 q +A3kJ3 d['[o]q*\0$fEs=OTٙzg_5JbM(.<]9G"}kDn䆪ك1!uR&prޒ/VGyht%a`*.J* SBu(IIp֧a>)Q('`KPc]0xI >`|7c??SoȦE ^#`ه3P-xrم4fo-d:=t_#D +wض#%/ O[HBhTKS8u-*h!D$A1^;/Z;t1A=a%<7@SR"{NK{PU"e=`6F46b4`diy2`NC CH3Wm-Bk-090.u/' 6XVkL[N,5-]˖I zMU&`'"R&&Y2"M١-`o?9ye,A6Xa;0iv 춒[&vÄ*"\;G-)nj" qrhء!#B?e@@Ͽc#rGi?G@Vo`_Q98+2mhUݏ(p!Z",~)u:гᢌa5wB˻#'?${"^{Pi&HrF\ ܦZ^N|ftS&kFՊmHAgwE(sT%Q"Gݷwc$M_ VgQW W3\Hs޲>sR:axrDzIh[!1%%tIW0P%qr\j&{fOž`q'ʹH&b%dq`[tTiA`}}腝̈$.Mvtʥs ް|qq^ 'eG* +8[nRbE\PH.54-JE@Ejs(((#ߒ6wa`O۬K3#J))e""oux5xՙt&rH; m:6~?% 9)uZN5N ']Ea}]awϗz#@lUZjaGך:+оHsKgDJ̈́,&e>dJ5n+sz׮Nh]]s-*()}:mwHㄵmH]SJ986~%!sn RM*%ϯ$Mv贰:)U3eatnGw%i9{3JYs?*e6d'U$ᔐ㣰{, 4do'a|WJ9"W\X[K㷬ٟ-Adg!ҟ6Wyn>w禛:ڐZ/"uMg} }͈$̸fi:ֽ& fIm!>GW\mͰ{d?R~({fQ%I:)$MH$$u&RgҙHԙD$ΤKL:PџEzSx"7 O+EcW;GF*DZuwk$I$MD"I!IbFo*Zhi;H$i%p>uRV)"pǏH$1"H9CYc|RaͰW%[%/"(yc uj4!}Uewl6[eO-F}2As7dWdTFwt&̦3L:Τ3ѓGFCHLvT=FY5>Izc_YC u@Fg/08;M+>\)T)I3L S .Bga8IgL"EBUIewu!UZI(8D0, l"*3dO]زRѺǶ1RʕJ-|=)WfI=Bma*iM[mYR~Jlm?R +%sJ(ve8)uϱRX=Yau?HB=2MKD萝csӶRQZ -g=mW iF4R!2CesOiW/AZZmϽ[jN)"ԮMHVr*G$L;\ƱetBh&6;OQwcXܴQ"4ߋH$E$IeF8p"GepFܷAc{lO?>;vR~ӦM_F$_JJ;cEnvHB>G/̼(a?aOE ;7w[ss=7_B>͒>ײ`8'}>7ss>Z!OkO쵝)Xc߱mƏu& +vF7[rQHg'v> i;OM+$4?٤q[v<] ٨ ʮrr)޴Q$Tv&8"MK3T}$H||dؓHH~q?GBk]~ht/r;j+ޯzD;"sa6l&p?Ոs-tUpg2UU$"PiΤ3~[D0PKK|%n݅]L:Τ3L:Τ3L:Τ3L:Τ3L:Τ3L:Τ3GcǚH,Ie;*%;DUIHI~!WsE$QFOfH#J6?7;"#mEK0>?|V0sպ|nʗ0N6J_JF-P5";ݯP>ݟBPh?n#3d~?_bˆܳ_D$8o\e63߳m̴#ݪ 0m</0U1r>>P<6I ; +95ڸǵ<5ڀk:^ݑ(i8,,RnvBky٠\2nf8ܳz.9cURx8p"$!2+vFigw4^8nZ.NI%ǯNgo퐻S:c}KeWoK#9٧Ww<)wrm7?q){snd>ceRSm*ٕXRRv3rGyɸRw=8ޱ{s22q%ׇm%dݳ*cd\pe3ћ;V%錒Z9֮\N}~GvonN]![vJk)G=vZ-K9۷lQί|)F2YtGIG%dpsӏ\[33hKw;yJܗcrnl,Ǟ1^8nFv,siS(mZqkڗ +}8;'-#dwJ!O:??{ONȎ󩔗2HӮZt;eSF*SgޕZjR Z)IeGZl[Ok<_ڏqFzɸ9tӦ862_2:ʶB3J_8{k>OemKMڲ͕'XBZgK-<#;,^8c[v1I[~8~ΖNޒ{x8{ۗ Jw}ur^Omu]c¨Vն{r#+Sz]l:hKv;uo9FI wg?we8$D>#K3R"P($" vIsUtfqaFRTDN0㲻2;ζղjc̵]cwCŀ4*$><0jewEiDDFиHˮ& +7ۉB>#. ¨ˇB& Z?-e~y%mRB2uE" Mq7Tdݍk}\lд{}ֶ3d !p Gen@4TBYKkdiOg>'GJgoW+Ȳ#}2(dsF)Ǯ򔲽ʳ:|TNڐi;effԻmNBQw_#dSm]:pwde~znn*oZv 8z]t|*meP"6@n$GVBJ .FPaH"u]ru\RN +caYɸAjUυilҰs(."q,Y76ҦL5m*DYUM PmHX\s]`WKDY~S+Q"v츝Ž:sJD Ym63D$m6Nrh:SIڴ*t.0S"JD]%4]]mDžu86i@QXu] 5aT6hDFX6Y7ܔan"\Dv?YtMDeYE$ғ`\&=IF'!U~[ #a)H*{w1:o:0O{ +%/?~քh2uHۖGn_J9Kh%=):%d )Ezdwy +'~ImUA#%KmِmCw1"^vD"YK#S-ZߔZZh!u3ݰB~]zs3S؝MpHFFR(;ZGѴCZk#v- 06_jU9glI5=SX?]vwH )96R7C (!CжRz.B,ВҡrUõDxXU9]~^QUy0 v?s#8kZ$6ͺ,iO!\ʜBtUc p}T@!DZfQ,@&OY,dewVmL@QSh<$ c +Uڮ 8Fq@Q\mqE0 +8tQZsD[A 3(`!gѠ&(*w,qUE6h& ^PL$2yF.PFL$Pe}U.^k(J{aۅQPY?e }@MAD0DAsڞ lhP>D0=;ւD" ?2 ~2aƒ1pU!5YQYD 7,LrqC* L +/06I/CU[v=קSSi̓l4\6e\6u8kmvaŅ0 ׶iky&FUJV94u5U7s% +aUͅШ\ +6;H3~m8vx8p2O_ifUˑM6вio8 mڈh/+a&kBv`CgmZʦ 6}P:Ԛj[yT O]VT]6LFaM8UQ\3uP\dw.XIFM8EX^+] g/0qw,L>>6B( +ܧ>&vMFm?ѥǵ{)pc}\Dr8V}*vi ¨q7u5_]KKAWp7@W8,k@)SeMڮiƲSm(i66qת@XPMma. }0XwÕ\qUݶ¨DʚBHSi.I^+=Za%SP[5j]R*+tz=N|C+h"5'A([ʵImY#ڟw9#e;U^}k7JJ_VHM^@'?7>L/PS]R:})MiJϛ\Gv~_o~7e5禄^%zh3}.AQ[_?]G*Wti~&_۟wA&]P?yTg[o l`4=N4:}N"(4^TJχuKL0>لy ]U[aXu7C6\fip"ֈCضa{MF3*& |]aҙUщ~D(^i$rd4&gߵ򔗌3r[)jtV2ԭs}iS:ٻe^m~t>OKfjWӧmKi(˧r:LKor[?jZl nd\N[%l֝ΎH;VSdMk8Ǧmtl2Yi|s#P\Gd _2Z[Z,2:Oٝ28#wm[-;iSRKR:+6ʎcieSϦ[+ҫe]m[]G.=~?ޗ ;wi}NVOeuV+%qVRJ㬕NYiW *( +*B$d6,3Qp !㙍ImhI=˜NP& tyKP! ,s>e<(@`j·3H4 Ր 2*@ 7S=L K/0\ؐFLT`TDy8Md]^u, 8?HDB8vADLD& Oj +**6 7aRaĄ  bD$"lL\AQ# +D$"`|L(.%4H0 IDGP8vB4`8v"D"#RY&ɀ31QyQ $zAvpfcm|J O% q 8 F.T:"`Q9THx`ȕ 6졍„ha40`FA + GH@-kZ6c'ÄeF10d:6llH {H01"12(q)2V!c#<2#HzX!Ha` ȉJ0•FCfÛeLPAbL +:6VeJ?x9@iaQP|A+^/DX`{SlDiM4 +|Xci` p2tXP@{Q(0T<A0-@3 L &&3D$Deabzq Ʀ0p9@a!$"yx& +a< AU$"iX@XT % (# H0IDaÀW .4 0k:' & +À A1 I ~0rG ,3e>(N"נL*]l-xHp2>H"*L̊iYv@CgI`eĂ2Q Lthg$W <A`*z6#$@6H]ja4\d\ `TL`P4P36"D$ 6&/%5`'S%Bע +$"86dB{SP6:`N*H,7mLZ0-܏JV@DH# Y +@[ S;܂a 886aBDdsĂ%Ka@İ0H|xX1DCf Zcd2C A5%0L"@9,`\,X9Qр@9U@DeT 4eDh0Ƕv(,d,Ӥdn\ LD 4,00d |#’x P%}lX +L$.Pm)1Й̓ܐZ,P@ce*hb#iaZ`46˂̀+,]&|:c> *@$ 2*l* +D$.No,0 ͩPŅ D 7p6@Y&e@Y2`dapAaAa8h*FJe*V*TP\T@-@%> *4mȸp lO"(%DDS&\,($`H>stream +ba [ @p^0mÆhD$j03;",00LI '*dx (&Yf^ ;2 +9Q [< +*4L>u)dp`,H63A07qduM` kqmu>jQQ7Ӡ0iq<8.lLŶ]؆qp<8 +$hF +CTɮdžBQ$l(4(UYuVaӦ4nDSûЈ}2*K+9aj#gMuɁP#Q%Mna&Ms>^nu0'Q>0wMȍW 8&MZ] 2>jۮ&w ASƹV!C #ܡڴKj.Ohq)tlUYȢ0eMxy)`{~.>č `0!,4@uPhx_`0 + +0m`q(h|\V}*: + L_ ǦU .(`ҩeRD0( dBA͂xm^2L:#8 ǑFQ1 2H!fl(|h4EC$~޺;4/kcsU;t͒6mirDpAIo8 ѺjLfh|,BaPO{i(c{$R`/0 :/[eUPQQX+ȉ#4(CҬ8z͚Y9ȰT9I^ҍvA5es#=syR:-R:*~gnqW`iMMFh=@"\a屧hΞ 6M!g+KCl +@h&n =DkeјC\- \#`ND 7 F8uǔEx+QH:JL.]f "t'fǔ,I#I (h|32Ke"S.|r+HQ/3~\| +zyo?srUV'U4 7Ps5T<3,fŞ@|碷$1-gIqmN{99dG.YpOqcbNQR(>e i LNIZRfp fWMvU'81 }DG$4xi.Qt 8'm5Fuu *|>/l^*32ȡ@V|Q>]\HUV [B%|%3⾬@w?L\YY *NʙgY#/ZXk><#06"*+%umT<s7+Kr/i)b:*1Dk#T1s7U(RS67*AB%5&8m+"_P1]&0]~d@SP(PMX2y̹s8;tQ5`ԾI sWT +-"ctz(D3}!2ҚE F~|n}ASࠎ +:*XJR]J % +lUP2^i=݊vM00\oohtlys~;L`Y僫5>6 ~r ]ڎ!~1 |j9 ?l Yv9 1oFrf}wxy_("ZdXs4aN Ƴyڄ=x-鎂_rqj.[L$h3F(,ii  ϷoX(Ȇ؜ R KHЅƈz P0 @VjљnSWPnť3 V^Cm'A:҄l9q(˕o3`{nq4~ ` +N~3.] 6UDPP +K!ll)H=w!ݚEFFB 1u651m }5$VO!#aco돿 +;hhW"4)2)y6]Tn5-q@6U_S©H$&tWʢZ= *@AOnԩKZ+0=}g +/MֆrGV K e'ș +ک \pRq{/3hGZ'b$bafC8$#ihJ\Br.+Mq + P5 6C[|RhnCR[~#x< .Q ZQxfMJLt"x'i: S僒oSS%_T:µ Fkq*pPz "Qesƕ=yRqw-RzB#+->dҼ.0fpOH0$<^O$̱li|DUZFʌٮWKhP9RA fü u@U+M-UC겳:HUlqB.jk &80®6Ng6M +:qhi 6`Yen@5, +% +}}8@ԉѪE=y[ָ}0]3@c Q0ޮFrLL!'&tJMxgٗzt.Q+թK0X pC>`%vISRR)+)PhP1Āls d{/3vz?A{Fv{[xׂpɊfmP23csm1/` =|SEjbwsm&@,̧Kwdj ,2[ @Ri(Å-Խ#|I7(w-{8r!#n_y#1PELPHu?ދuwV E%yZi&B6puJ%sc$қ(D$K+OHN:DB%~{y MGB0)Iњu?xNeCDBL?:l.SӰRnvMFlZˮ[T*IA'On FjϏNp%e3~}Ks$]WgNۃ9f^T +4Y7 /pEQNpʘ2ҊGAH\lDGΟעԔ"pSe1(XL{zq*a߹_(tIU=UԋO5:Itf\4=YmS%RFp8$[$> s R8Bf]bU"AʉkP396 Ґ I ]~`Ck@~C#U=c}Ф|ԸToJ";HО'zcjЅ:ޱN# ėVvEC\{$uSín`*o˗WJk +κ9+24eրIy(?RD6*05\I'<1Tf;ș3G-֝݋gT=hiƘ1~tz h[fO4`:6cw#;0e94 ,ŮqxvgA6iu:.Nb{J*G1Py6,M?C*oAN@1TA)<A w>v6v1oN%ji8'D"QiO|0v&:Eκ4 ^(ӧwI3 ) =j N +ᆰՒe8q.D+ /sca??A8ś$5𚵈4h. v)߽j$2ײYz-' [|̸hV?8J5+爖@pq*QiF' ͌rA>|¿hXނZFsŬ|tӥ@:2+P jn 9꽒=PMC}WQE QF@U!x6Ꞡb0Ս[܅pE`f(c-1Eh+wL%ۻA ܆NyCxP7A< bk1$LE=pDVwbn<\-y߶MQN_Uђք>QeIڧ/sћ"7a,f>l܈NP>˽-FtG.+u&#+ bmF.~1hKݜZ;UƭstQI7ä́ +A}| >1ʑ?my3^f52|uU`t + r19ZġUUTzW2e׏TfW2M\xtzG/Lв1hVuթ߬_o3B) /}휁U0 +](?QKPkP5 ي~*D$Z.ݍWŜl0M$'8wU31+*Cל Nmn`\8[p)lIFXX>c*6wOq29)q/ +ѥ#~?ڋYXp4rty@`x-"PʺnC̯:&9"Fh%oM! &6JRWneaԥZnItr՜kؠ\j{^SE_<1īOK۰ F;C~B-`R[h3 74@.}5 +Ĺ%,=Xp྄͡3MyPVT҈GJƧlB?LXPЏ19.x6V(ީdLb{x=7<ۄFIHxF2cgq6N ?=al L e<QJD )Yu޳gj1L@PfMQc,Hф 9CgYC`!ŦgUB5{Jbv&L~?h^b/4'M^wmѓnI66Hn"Is XC2GfQlaK7hxl"`B-2"Q#v|B.nH yQVNIliKꓠ! ;SiHGRN + sije:)۰(ԸGَp-ްj?*޲<3'D +rz鰰}u}H2n,aȈ\P"_]G :)̆E/1'Df0E;3v۞Z37cG SLIM7BTٷ8 DDXTlҦ"n|YY-PI85+$Mc0m+ߡZЋP#FWF]XۤU!Wc cR!L2gۨD[/0p=(oGܘBseW#sI_O ]Ce[:OIcBI4S.dD# +c=8Q2ZME_4iү-KMjh*psR/P He?C0~䇪(󲟋if+z#6ݦhI+H닷2%Hݥ 8XWLs> &m]dhy䡠T&<`0X\M X/˰KItqĤ i'5w΋k^{P2V8c"PnH]"^P&(8u&GrA]ЀK  $}iq7~OZxykU3eQf&ª.:E[Q?1 uU#燔Q[uEG-\qHVGô5%>Dg7CUDQѲap]~=-oZ?4!ΊI*u[>AjoY#6P,O*ȁuLl{NjuiGezN#&G mxζm#$N}ۍp(s &7-0䉔w4soV YjmZ 0cT{Ä(ok7~IOP``4@MdAo 乻Q B4S]ӌ0zwI"ΆUF@nDzQoBdP@Y{F/Z:REɍ  +L9EXn 긚7MQyH8,hN}el <'W7y:&p:1y%. rap2WIWW1[8gmֽ _Ǒ=rC犕q [QiP#`UED0h!Q\F$VT=R%EBe:os1bk[ kwS?XJw/}BLQ^_ÎpeAC-W.?c[u]l`ϥF)PTgBĔJ:}WU7e*!ldzAB!xs3qXn[s˶Ikjjٔ}G +x7F"b:{][%_}9j85QZ|xnQ4N|5g +'6 ,Qe&H%Чvn}a: PFbSCTxX~+&}W'S݌l.♈r^hK^Ufi)J8_8UDE7Wt ^uLU~s|T͛~VUkXDj~5vPOf*qvIuzLJukFS +y=/*Q _Pj?Wz@#8~Aғ;_ȭW[|XǬ-=?Y2PiRscpQXj11)>MTEB]33m/t=F;lcA.Vǎ_vqn/Yp;[ 譮׭*CTﰲe m zЉ恳̖d$ _) sض\~є.Ⱥrnɼ$!4n(E# o$8 '&$π&4L!pQ 5ۊ +LCaJH<_DfYD̃Zdb%\t $|R<ҾA7FF'Q C 0-[&%%UV `G!{. . Klm%A]f)7o_~n̯shOuaQ6eHFukHM\KKe +- $hK=܎V8%\Sšɢg$h*uᶕ` 0-܉wJ3lf>>hPS^)n1az٬5L% +o ?ZCMw{XP̹R@ ɄQj-X)Cq22M{\F^)4aɊܜ8QW--&F=cښ lj߉Z,z~ACSCvM[H&T"W + d#lk|j(oA$ucF4|W~.]1Q%*5ci!nchB&e #vP +`O%a[W"a}cЭoZ夕s|Tc<~/ư>HuˢH6 ,gUc4hX_x;F!SW<~SF.%KJZo˒p Yhl`ASQU3;5>mT3ഴdnDQpH0R`|f@Ex38!5 K!כcKt.*&~حjdCDWU&B6*ei;cSz"Nݺc ji9 lFb Y *> 1jdԻus wxH)$F#xI!*ztIX\f&uINfz'`˕fIbp1cF+DJh00 71[ >?܈\`t~zg:{\▕U58BN̥)ciE+H*R⺛\ɦا.˜߽ B"3|G +o$?<]+Bk\;#Đ?d,iǩO܋rZ1zv/W{c}V `ټm7 !7o6.幋en&BJ~fJ6L !SyĂb]3ٞm;07[ -0M8pKACBiW1W.-2㬲`(p@ęsDVr}&!`boWd+Qޥq|3tj[MU\(~w/>g]Vvrj̅|p x^۴&]'Tܴ{߲B2:U1Of-(ُ+)dpd%$Dσʀ9zXHW,:O +MgCS +HJ5V FD5Yվe]u@.6a &}sAw-;O^$i@6 zѧv7Y(M;\y`M (=㷷=2kP\ڝ۴ly/g ׍XAݫвjZ_aPx[x Z8!&҂2I0jd8$$W"X򘰇cP{Vvb\ 6~cZϴĥcRɱ@&cJ-q43."?Pٗ*7c;`i]H׭Gl*v +5HFi)]-6ªH̑4 MmH*~A[ǠUCBK(] ۧqCD]KO%Ǩ&.N7? )d&S@<J@;)jTVH +<֖U+֞nz0/6t8'|[ QfN"#ҪD@eEϦ-+0vWG)n:sОt$h/ԎxOQȻϯ ]jN{zR [?ҲT56yWREqڹ@!\ĥpKOJ(y2RE,zAM|vh*m{z(yE1:vXҽϫh'hfSNO!>jVBbDGH- пfc!Vb&w P3H4n%f2Gus$BYoWx fyQ;{VPo&D,` Njkb$Ul%44X6Q +w`điٿ&L⭾aڝ=&\_[Κ..A#X5_4y1cxa6*PD3 hhVjQljD"44 s}sf-Er5q92Vh~%l2xsab3/ e[`/CC>X@cޑіO  zbrPc;Ĉь + ,>#6D:E)*`6X̟v򓔛l]2FFZ_:M0#Y6@@%ԟhd94*֋M~``{y}8-/w|]Ux@Ga?OkDu-ET?.`%᯶@^o ͦoHDq1Lf +s#- 3erg{Jb73 :~D7A+OȽdV356Yp^I >J E.jAJw>it̥`+ ;-s )?p!'v uMYC+7|\"kx-G@n/ B_JyQ4 'PJKQHM Ӝa]v"wnñqsgD)L^`l x/V%5J@ؕ&;M.5$<4$I T_Lr4*-rs6? LHNz5G rc}Y* _|yǓ;NFdKf"(>ԬGC*7n e2sЛ\ ef*.Iq]gnPܑzwFoD]PD :VH19&<\1:X"8Iٷ<w&cGd8ut6ǚ6g68fo\0btB҉YWE+D 8^KtO.+MdX 02*GJ%xW˭Neqoꗧ3 CIq b2&%bt JjA[rfMƉ1'e7ECW˹pi$}nPN;E(RW: Yb:wegWA%*!{3XL .vVPGoXєX$LNGmʘ,\:ѳOTY=QjFܻ,%@wsGWʅ'YJpAP%-.9F]y0Z}D[p5X~~P(;KHMk|$ ޣ`Wto/{[^X &ű#W ՇŲP)W;V#7^V ŝUAo v|44Pw?w"Z-HyN*ݔEzraQEh +,.[ϺKJ UOV9h*Y8m#.fm$T*9LwUXps9Rih^'OCJE&]Mi~Jbϊ"[J]\f0Z d fK7UƗ$B3+c0Aj*B4{B] +ng=z\0&pI\'=ֲǐ ) 2 m qUAp('G&EӈdA) j5&AfL$h} ..Cq ޺9<;YdI!M+ P[`Iђ qJ;:Qis7An]A3fM@[zpҟ4Β6Jܪ pMĭM|RlȺ1k Ȥ&p48R:Ld +)&D F$ֶWV֤J%nF3&ȑcH)S#aXB3$pe 9M!}|X_eg-*^!U:ØvMѕc/BbFqH@d갠k1)0tcED6.g#9zE _+[m ;EtƤG0٘@){z:x wYK^?Ekڪc@ޠ>CXGN8PH?~0,.? V!<@p^̼z)݁Dt 6jDG&ƒ3.c# ӝ^lo+3tTI:GZ3$ +tԂr&e@:߃)*VGR +t{]oݲֲJ>N(2ֺ|lcr:9 :{'}0ͫ +6GTW +}8P@E럠5:N|p4 t h4nt2A*XMl@&hDsv4Lk"k1}ew;A2I/-)XS)k?G (C)ylO."4@Ƴ䆑l@;YB;^#' 8\}o#E)0ʤ/%?uy? w%MpQuѴ [Ptkyk#!T`B$yn,ъӠt1qj[)n6>vng{B|ӠUXg%rȈ*=b CG l\P$h2dUmtnC7@Y/1AT(uȐA\jO" ƅ0# rhhb rSr)Є%Oزg/yٷo X"y(w 9m1] +ɗ +@@Y{_,P "id(G +DBA#rt0ʯקudY(CpgkE,>_?EXdʳz7MMhr7S-qPFdIw d^hwZ3NAiɚx&bd1 C ND['kHTB+ !,(1}*&>H#@-^@U _4 `~Hgȿ i | N5M!yʤtׁd~ƒ +x5 ź1dtXqo(Zaq(ABK2z|^Ȅʛ{'dE>Bdb}hd(?nrrs3xBn=DDB{ӠdL4h&܅6Ic8A84|q'{DB׋^ q'naY2Yd"ۙEe":"ᚘ5b Y;b4 +A;  3JV"Wm:B« BIcM#TuBOU%[:r鴬~feR/x!):0yK ƌ9$cCi4EM#)igjmش'5?^%<#N eHp[ڕFD`y\$X{72$Aش'tqYtva}^_lJvW?nn:%Տ0m`J{>i8_'s%ÕЦL z}l͈nte kox&fMk?6 >6#Vw_Bi}Ss:c1Y1cHlqtN Z"*iyYFq~<`~xJB/MOT&k7NWfI 2~B:nz$-:I7w`+ a} (% }.ep/Y11o3߅~g;&U"t7ݸIL|aoStX9[z<JnX^NO9ݧT9R:i=hiZG{Ypn~Iscn0X|ψ +_%SJKnUQ)Li<`<)>CV'bqy֛|+S:o/U7MhJ&f8&zv#(!{BP#qN5 Ϛ"8W"i;`CJ%ߨeGYUq+8xQ:XRCn_qIz+P~2)O|i 'C*Aғ +K$QiC6::k*Wh Z6G؍n'0:pΰٛK(.C1I^mS oTB&VӍ8t<wگ +ݶKRA 5~\(\260s3Jxf|x$#2pD|4%V;UŢ;9d8ܘ76L=X=A*SW_uj-i|Lt3FwD$oK#[Ζ6Jܬqi@wD@ilHzcYB[VY ^u{t/tlF\y;6ci] -fJ~B| +ux7e2[yX\j6ĜV^e<8f[[+?p3az#S$! *ߴ45᜾Dj:S7Hlh7mn{J86~_S kIG//n"nu&Xdx6vwu4aYMWHwwo15⯐GͶ "qN[ 񗃢Ï=)mݴRz]V+/QY#zGJcL4P"W_\J;KFnSaJ;%2?%U/&lf뛚U@oivڟ`$90Jss(>V tg|ǐ%< dQ7gu[ҷv07hUY2IJ"݂ZF&u|q3Ö#flir}rH7WibMHܖ-7EASyʒ^eKzi5O=G0>T~d $wL*NɐLlbq~8Z^/ US+P %W̋#޴a]|;Pe$denZu/׈sO'a򮘬ͫ^ٻt \i㒇;%`[e5}k@yn|'_K+]H\bs>AQ R ߚ`Jga/$R~H7Fu-0m} yxAUwlYiFwd!qJZcL\6J^guQ>ݓz.~5B?OI^DhpW՟}<}fJ?n`ܘٻ5]qwd)'P˷ p2KV3qg%'Y.LWAQEuՂ7mW}<#^uD NiA,ze_n~ Vn$BɹB&nT8M#VQ +D³s!;#vnf 4ն•*Ɣw cڧ4| |xUl þWk@ͫ<}06^`z) +УQaWkٚ7H: EQ1Y G.ait%i"V:xy8I[76V#sCrm8O8̂xPt7gt"ݬz&X049 b3v3Du3H[M[ڼNkba~J?V$*0Ƿc(nRLX"#~EW>F?kOq#oͫڕEM]?%73#KDlDl Rr֍nC͔I^-=k%-\q MUXy! + J{B'@5oaD,UR/O"?4TX Ri f 7Q(] UCQ^QM{ ſGhYEMNq>͙oX +R)=Z3?yyl]F<bya^ꯅά›#cCi+*hb 6a%ks.k-Ζ.tNXaUg8%:yOjt/O=p&>˗Mhm._9|%T7K@մ),-arK=PnrHͮ+R+ g79s)}hhepT˫Luf+ с.[Uӓ k[03PMfPXØ6_=W]kV,2cJ6^WTJ38"8sմ׌&,bڃ3b1[e'>A~Gg#v/Bc7Y]Hzu 0lj2*S!W; YW|<9R+F߾Dz E%0ʚcDp?cr!aXFghB2DmEJ>%?B ڞ+[v٫^'ƓHenH^(>_[ZWmc8){Gʒ>Z6V {eGni,ݠؗVP7T҆qiAY%\(Qs|< +L*Xp'Yi+n[dy X{]^yD}u#6 ܰ _ix7Zi#kbq&POHg`!?nv`6ZM:#ݠZ f>y҅4ãˀTRx-c# nP2p&."7\PϺ҅z+y*BjmJw@"!bk2qʠ]| _aQ[l~0׫gͮX!~ɶ<;R{ U_V$3.'x~Ig6A5pvVUblHimawը'򰘬`o0V(%9PۨJS9`|n.V$J۵wގN)kw联̈́S%`G"`fs&p96Xs ͕Sz[u XSOXdhqS5@gއueqR?WhlT;1(VFMJX?̝HgwiwLe^ k5:@ a5hw +Q<J7(VBY#`Dݺ>m/QiBF6c +Ơd86Hp|LՓ͵I?Ks`;K:ܠdxp+^W-=.n⸖>S/; wYU' `Z a^/K!ap34A7[oԙ7ճNѼ:AbmNSش[(Y +/~cāAܤ!D+3P49 <~7\W; 4hp +xom|b)갲[Rs&/1_Djsnh2l)܂`jp&OIS(ѧQnI?t0! g Yi?2U$/༎毤1$V]HYO#ѺH)ܰﶴ-qZŽJ72s[3VÕ:ʖo,8svD]["ܱi6܆ⶐKl6g#vF XOg܍x?´ V'*DQC]Y(n~9#3)\``{f,Wkd)ןY+i& ÷Pz*}rPV>ldXFn7YNUF_I3z Dⶄ$-~A{G+}6 3t |MA Vg5  q|tw'ؽP#].bU4]@]-bmO^ެ*GJ׈#jlJj+\Uհ%mC*sX0ݒD&=!J}T?͠n8gҒ$p M3 cH:)bSxO,xUX&JҏZ~BR|| W}nO*P#M/ ,W UiAMNi9^~dW曹6Lo ~VV 6׈ZM- xӯ?\IO%҇Vj![X| )'4>*{%y6m&Ho 4ӽG⎰; $a ݔ8K: @ T7k +j AwYqŸ)'YA(A@]iCNDTbeDwSS(0s iIiX)B' Eg/wIa&rIݛGZ +팵sY|z]LNĪGgݵD Mip\CQ^"QiNc^6y7~qPf JR7zb QJO]4n&N3oC5mJu)̨JbNN,:A#&o!]eX 3⯟c^O6I!ȶ;|Pʔd뀃-N[}J[`Y R';fN9x a]u,:3~(l!pt#ilڿ*KxMA^/lz]m<]U8*8 +ش _J^VPBke~P*]B$͘\V>d*4 "%Gt;x@Je|aQ:A^wpWiMΰ]%t~k^`;7ksF)u--[%-YX^҆ 2^fV ?X, Prks̊߂M-ѧ_t+a&>.t +Zl^42^2g$V}Ijڌjs&@0jD݂w3vm35?e-{v]$*8w^;W8(ڋDn6̸p1$9l`jdX{QJj7NԶJخOwqgvqbvd:!R> :"SCp cZHFܣ^hy,*.4 F}:|p9q6*YZE3nM9O + X 1o)({W²[-hkƐtų%DwJ? +sׇn{ [mՓqx \QHxkT 0 ⫻t*hk(L=/Z(H)ig[:DV-#`u^|O5>3HZI7+> PW2 +%BJej:[Z3 ȼw t^ӊ!Jf fa{2^; o(v {8qSݸPYC%WcmvL=$o9˄/ Th*6ݔ2dIw/mlO1t$> 6: +W3 Ā bNDtqv7%nվn˳Aq$@WQJCR(>L/SEpj\ԁ OX5DHk۵Dlknvh*UƧ!fN,I.3< !׬#UmXHfޥJÕZr6=};Hf^1)Z6qY~3@ϭL4)Mli6kb;H~A p> +Iy$~ؼdT-="\?gӖ@ 49&)M*>} oSUTќ?rxÖ\4]5 [z< ?NWoPoF&Lb,4ˁAݑ +ε3 Ur큜ؔ_?k5%!< IO i +$dqI[Rˈcކ( 1mSEF͐n&2~\="g}N#nNg`[HxlZ^߶<}1c^4k&jV"g_]ڲ`c@:)ڴx͂FOHZ}n7m&B҆ /;ə4D%7ial6;TD*b8 Tj60}KN2tG@ AGRi*f/MkdUJ%<4@Hwc 2SZ_}0~ZGOxT`1crvʝu_3>Es1/`* XJ.)ps zio5iۈIW(n9>^wpʍyY|  \e_H @ЀXe9矰W.U)Jg0mz}fIbOlJ{Yܪket4K&*I)6=IgSgF;DҞqiFegM&cʩ!1XGzهpi2*i4 +lJ鑈A0tK"8-Mbe`Bx[t k^/y8z{|)I7LؔJQ$Meo*>:4m[^3歋3$∭h*%".iTXPL`f7$yrF^p q)-L!%~ܼd*>#XtVǴ'E[NA z!N#ݸm?6@9U/*:w^/B&+mՒjyS-ܹ^'DxDlM n9zR!0 )/x22߁.n^2t!UJgƈHqɭKWE>. $ 1$+j,9…!++rȃ{j7HmZG戵eUg#`ѭrPV4n`紇jrHkVN@ir`~3DR@A=T!X3O _#N +R&Rt/ˈJ?ƏKdӖL ^ 6֩eRX([BV$<$XAjRV@./EP$wSSDjmiŰ~ʝVco7דҔg(Տɋ҆/+$f,f ?@pҏV/ytJ_MAvg* o[%s;pxUbe}E_B 0A 6XItSf +7*̿.4(S( ݐKIqg?; Μ&*WՑ׈{Nj +J'G m%݆ {Smi^WD9)Rt" {WAAt]6mBIW[-=t.)@XЮ>׹8;(4L?nV^Vp)}lNŗ OQ7gD7%\cF ;F&x}p?M-$.(,:쿈YuT;h]uLJ]0$Bو;bYy]g*^%Y-n%} )@*/a_1ab7'!KxuE0E*T Sh޸g5'$7~޴Ǯ1 >tX{ .%aWEA%X5)5_.R9(5ee1zٔgoAvjai@7!ݜ7In6ܣB,t`J86CeK D`~z0nYT&=@2X#!^pgv2+wIy_BM??JO$x(UT6Xtj{0DrOS@"0 RJ)PmC f4BVM0<8L&* (q@  `,q Krej WeŤ-SHӗh 1NU]@p*s3ƚI첗PIlG.Vjsdät1q)%>2qza +?*+bI|Ma3 d'tA*7~P;YN^@dH.v ik@5m"Xk +%-O73O3ZDщɚSw xvQgڟ]ɉ {ŀ g +7vPv?{vf>~heg~<.x"=9ć&|\&D7xP{( qK?YCùYI[-%C.tg9J^N!Qwwl(!]@MPB%\1g1l.ZjZ@LwVaB?Crh "!#)VyŁ Cq[ظWSث SDqW \3E:4\NUls|+d +oȈSy.!bZ>R {9gq0XU%xOn@-){<9YO 9YYW }4]Ց +\f!a6x0E/N \X*"+JI[H;gzw2oKkߎ6W\'&/;'b]Gh9 Cf3_ gDнTh @jtiQ_R2&(qBKp,:l¶O@lL+5x͖ w +Kf Vc\RP|16fAn |VPuB/&KUzvX"4ب׊o  +\JmvpŠցL2T3J6H&nQȦ^|;7 w*fͻƥ]73q:'kZPa~?+²`0J;3RmJ w\مz 0{tDA".M| QSãS ψk u_tw;8h) ̓B;ףbR&`0NUw0x <``>"WߍBu`5a7wǹdr=^= هRɅd7c=Z~K"r,b4jfYѼÙS#r8؊z-% +3$LtZW#tz;@J| 8B-_[͈ ct$9),N#b#lu#ƽQGXkΖ`>y9PN" Ae9}֕ +{?3'Fo=)eR7ܚ r HsDzB'-3J}/ +O@.7\,K'E)nAP"?'7?yK/i@?fR^mhQy ̀$zM}X53Il<]6e }E*[*\5&.;q,J1cpHc)7|SxlgT_Bx]MO<0mfY9!uT#$Ps2(/`U[j!˚rߤHjQ;0 IvIq\ѓ)v BJIqiyi84*N}]PjIkiQ _ѾN[38홑̐uso<U*555$zgw{#|#⽬ih)O{ +}x2ُCnpۛ+{SK k/yQ̷7YX\lpv Ȫφnւwje +Z+šf]>?dk&h'0wPRus IAZpJ ρUL8E +͚q8NԠ +1V +D7uBqP$ .M4eIygU$ɔy 9Sy58_Sn{ 1- ZXghNԻBc%CQ8ϊXZX9 W7\t:}ZTG_Ί_a#,a+A)bzjڠXũj|kxa04@|TC)&=7\mXs]KsXucQo^yhpܙ1 +J XنS1z5#f)|ϼtX!vMv#ĉa>E^":e(@Jbcs-f<2F'*܃7 Jw@AE޴1yˆAv`f~@G)- 4dBk>Ћcz=I%&sF o:fA#>y`~f#g0AuCK1%N1~_쁨U͈oJ{y%2(j5/O݋<n@Ms 3RYE֔g7HƟm0O=|m] ͓$$4ᦉwӜ$ȎB\H]ڳќ#bDdrzc>:gY +$G88M8Y&1Mٺ9n]xpRrP%V,tr&ͱ-p݃B +{GE.J X>X'x& +p]:e+̆Z:u^TBƂuNE,үE +D`Q!Wb4VQLm/I ix (IT) +/1Z)5ɢnc_{Wn~h-|JbWەFZW/3!#svoaGQE)NE@w21{1N)HX_XꐣWlW(x"k+ڌC?䴱~z(YOZkwE2:- +\&`b !E36Z8KߙsA84 Dy[ uZA!d\@!{r-9_ZqͫNIfPM%^͐P6!vӽ!QA&M~>et"*EAm]bevG.'or:ugA,dtVSSоVCy|us͂&T|\|4U I#{ظa4RIp9k(>uU/g^r_HbI,-ͣt$l1VYdVdFq\H"ttdvIF }&F,is2 qIl3o(Z4/T9o"5x_c=/Vѧ?/AM|)K ҝ9^b"/(D@F * +K0<&Dg9.6 ~ӆ4m2I€h~'yq$1auG{2F6DxK-M}J `n +Љ'.q 7esst`璐(P6.ʰ߆V4*:>fok[o{y|RQeFMoH;ҊVzO7LXJ}JLLIM7bV/>+ٖ;{ |i ?#**% 4Tr(I%5 P8(P%B0?8KhmHv(vd&'"^1[8i(u3;?j8Q&U#u*)JyѬvmaֱ\h嶲AK$6īy[u)VjB!0Ce".6x_>*pKЀQݕ}Wq +be4n^=DXˠ+)fsmf%fX.Y!)w]SҐJ'f`&j9|QsżX#Z,)@{,u#5,)EKBwS/)׫Q=YRLg ‰hPg#s<*4w0Bfns]C*դ0p佲Ы온-ZH>`~uɚmda;y?-\H,hT~ /WԄ^ X9,%u#Ƶ.MVMc~px(#.8pk|`mh BjVe7M$aIP^q)g {`/th +jEI0sY-e/x1$_-F5j9k幐j9I \+fED +ܜ[z%U`3H;G'/m-CjNf5_8!ɯ1rYRLʹ$Mry zHZ`%;qI=YX-ۖ $Ť Fw[J(4)K蓘ZG&x1)'yCwo.Zv+9Єw!6Qp iyCtCRwLTgdJ9TBx^HH +#xًLix]wr57#JgԶO kFJcln% E׳8'"m|) +ǂO1rRivzI>i" +Öw*6y1\dyLhsE(F݉fTكQV8&5-*3y]La QFKct!*BDŽӎNFz??MqX-l|]oנFWE~P"@ UXEAPSVS;ڭ56*ϡȱ\M`y2q یDᄨVmU eWy2O>H g![MM\cgPlc:C`l5ǰd,۹R; q$ 6*#KD%bPqn0_w,젏XG!*JaBJE-QǎZI&D%!Ɗ*z`n1B࢞s~@^Z5$? q(CT枛0!AL؟A!u'btvdŘClU nMa>HgCa:%Ck1[="ĩHxƳbGeWB7,`oX 9( C t+.0͌GeGP"=~yk[.%]]0eio*[]od*T->-=9C)y.pD۱#HdEI4bnNZivT(jnހe$DuW(d9m* +-ϙH?-ɓ#vs5lRMƙdQ [_kdyVQ|էh m:?HVp>[@Ic- 7"8T#A;PJZQ}DCMl(SqԫU)a44xY!вG(juRid811 >[C,y>@l-uQ[<4wG‰qJ xdChAmY c9? Y8Mn`9Y"-5J3Xʘ&gGoBLt3xO%(#ċ`̨+kmg&k``naAĺ89MȻe:!=dF0o [l3>9 01)0u28[!3vM|乂WL)QVD4w?H)3s!683pૃU*G6bTH&\6dN&mTS F&^c 4 )'׵#i~ 6ZK +J$9-NV`X^EYDH|YnxP{Cρ>;60"kv"He̟r#jqoѹ?Ig*QQ5ÜMp)KcB%ad[WXMqjRJeJ*v՛\V7A2n?5M:?<eJ*`:ikYO5X\z"= %HB%jC`@a\Dpv}5{T=/33) \V# 8:F[}*/5bpZv0٫Es1cX A%Nxq.{!y_^/$5հDuq8̽NV+0'Oص~@ sXT' T `C\HQ 38eR˃Q?!j_n.rF\Gm}.ŃTnFŝ\Vq7ȗCe3 #kb>lص[6zWXF +8*vb!32ʷf-eS+ܱ~|,^+ !JJX;Fm F8ťvo,0%ZdhsE@u.dA Ƈǝklh=rd ll_}IwH)9Q]fdPEl~/D`*3cv}(hm}:+ej%B-NmrC[jjIw +8K=V'ou\ >[,,xr7S0ڵ7Ŏioz6\$[j0҆dR:[FWUy\;gU";{O#FteAuЂw3Hp| .]iZe"5pM-Fbj*o&K'0"蟣ms,!wgBEuC`ɠ[.c pbwa~M~ 026Fqlk>gtP.c4Vwq)<vɏζ&E1r41B@?8[пK " 7\\!So0&{}Gh-<߯F"aťA}l1'R|zZyW҃N8oە> d"7mM\ۄ=pwB/_m>&eZMC q,LXؼJL +.2Ϛ7, F2fE>T1^Ĵ_ fq.duCb@-$?C WRps'c$qVV^p"dȂO fjKh8?88R`:QW_$g9C:84]b=y<>%ܬ]: ao 8{O]Me "8ը R x LzpV/FʇW9jz85X:tHNz@:b#^$nG?FwG)"+9(: +M}ck?377bh 'VBB G)*no>^7)lGBi}Qц#j(98D4.~J_kL%6u]`-Iyʤ!c%&]}$6,١7~=àӬ:|pEW+)# K: RqHmʗmu+ڈLh1_My,/\&vy1O;vThKJ* [Ic)qwzq9OSeqA,샯)O+xQ}k $1Q)X$g^'ƚQsҰ"CpC1d-"ɟfC؅/?=*lZSϮ+8JȫHSTVlky26S4>(_݇ j$3tدV Z`@\%> 13O4E9!l{T;f.eOYLq.7=m>\/(??ql[E?Mh+\ݺ_lAe_jj[f*S '!Q?Gݕ(w;Yw}Hk#n>n`*.- \rpŕWʋ_ ڏr׮-TjK^`'BEv0#dDhl1SYkqv$fz dK~ Iފ}}yggSN?%Q8Qz ubk>{Ւ]4qV#3;;Aq+$޲R.DCjE8!}@ +oU]U@-4Ʀ /xW^uh0K3KxۘK+j88@}Bn܊y C05VsNVIT! # Ǩz}dx 86l0C8ۦReɁEj@NA= КAT)w|!G{) KFA4J,N_Llj +BD8E 6䵥1OMfi $@k||wʩ"p$fjme4S8]5eŧӲ,-&P2)ULޜGPx +ZBCxSڏ%Htb6D2_)ڡ[)k>bN`yEc,NKIУSd%!q0d C\S!NvdX| HѴc,7k:0h_<~o)2VH5PøE~ bFq$p`!CFoU7B}|RZs3z^oh483@ƶQ =SFw +r^/#OnVڥ l&vS6 Ijq-~Hp 9NN3VG>l:!f@X"0|-NҮ2V 66PxW_6 ݱj!,!CEÖNO6wJe q0ҿտ]d"c T~Ƌ\0a8ݒ#JNn(yUw-~ n~SN p R#a]lU R6P$&> 5ҒtIiȌ#iMUũ3V^-7B4KN*oTRS%}ļ4f|2b 49U;NCJ0%$A&=TZ(*7ea@M_&XQii,f;k#iuVhWl<^Y0+\$h58qY|XnrQ1schv-myN( n79#=2٦Vo0"(%1^"C>"Hpq{$x7&iD_K1xk+a/YƯ,jڅ ,۳>Y62Aus1ژ3zAfhp1*o J(]hPN:4%x꺩"@FUڌѓC_= O]W/r0hb4~jQb;(zr&XM1_=s{!EBT%W=.`(Q.P$nt.`Ze<$VPn%n"C1g6Dּ[2bK[v()Nsr'yG9Q t앀4A:;7f2-oYA@4\JO֯hS ƌX3HxdC +\Y¸>Rb[5^߲MÞ +tŌOܦt54aQ(72~+)FLy0AF, U`'*G`C;4G6H>Ǵ!CPA XtgJ܇Hb~0-nê ,8g7xۆt*iA(Atiܔ1 +Mh ~S9ݯDwj$] ƃ- `ɼڠY6¼7O݋ 8ݰCw 9h<:vW&2qͯf5P6v xY6+k4Tіw8;iu[ᖃk3Y}h'~):!L.]>#[!"; +P\#S]#0Z<{<;6AK:E8?fx +ܿ?-a2 Y]63ARmr!~iarTMQ- t<}s 2Ix; [Cܳh`)LJUd.\7xzD4IBiV!zHibТ.t:H{Hk&{2P=M89`^ҟ-˓ Uid8`ZC53!ih7wʈŷ›G9K?p6mK)rٻ- e^"Pp?GSN}t;%.Ulڞ%n^*E/J3zV?>EaSg2)K]*qA:O(m(5 ΎFA>+մ)# _6X*U0!f1jeg d k1vo =Jp‘Is@eH_dրL@G=boa6wF'.&HIU"0_/_u,mL_)p o61. J4 %P%7/KKqƷO_/%EiP%dt/4: n߅KX JW*,IiCbե}G֫kK&݈ϭ+d0 \$rby= ;/ M J_= I7R-"Wԣ8Y\_Z61g]m⒳ſ!{=,t.XMRzQ\ .Y{W,s3K)wbՇvyp )6 Ӗd#c1wX[V~gruLyUvvfE e4 nr]&sg+9uV(JҖT6EbHJƼ6eXssjˋl-? x .X̼&w3O=I!n]Z^2'[;d +A+ +XtM\҅b/$V8đj3B%a2noΏciqq%Gm "CJ?l'd*OE5>s\3a'[pևC δ<́pne 6*jmABͲH?|4}h-tgh-$RBpSt40]H2%BzIy%m q@ +b@ R7'#tbz})]jDT~l@ކo :)gEVN8~~)]ؼP-yS$iI-G.9ḆPpb}z#'<#.Q_fiBb욈0^2,ҋ:;QEK;Ɉ[`tEVbe-m1=LZ\oOGv6R4> n4nS5~C5mS5kdeWh ~7J61p3FJFVtx~gI˃́WB lCi[9|ȶqxړɎV#Qi1G4CV1"Rj 6'"+'@s8I9)>3t䌁hdeCqKDAPio +݇-+=Q B܋+nj#lTrr R򆷡4ώ3 k`DEc*c>] +ݓObCiTazn~PEMtRAwn"~&d&Ct#a0xt7:DUUVbm?SbުxrƒoqCjs 2wJ=:dHG)-3x1L]Wb>qVY Bp}F\Bi]u9prѲ{{]TM["eA7CyJ2S [-+bu"ڔ8('"+&d~Ĭ2SY: +7yh^F[I8G&3!otJ%V]X:lq1?6&%$gtxf@>^%O|҆5"i.-!Ho$f2^W GOI)uߖE9i5bHV}烄͗zhr?N]o>T4 OT}@cq +0)QL90y0yflH` H{S>׬qx Fz%SDFWP1tM/_/ӌΫ_&"+{0t[Bu@ ;P}f+ߵ/.\LT¾7z !'J{0Ұ;9<,^wH$ .Ԉ{ݩ-)Ϭ ӆU-;񎉠YMT {[2Ui$y?j^M({u2Öəi8ũI *b`;y7Dae1WN_5w9r1?OT[B%gҍFwDtӖ8R1֪W\2¥nq$P `:㏞i4MBUj\t1&k[Y~>bWD~ ]8o<g'UD]Pf_eE0mzM?6o /~yph}nT 4cRk$3nnU5;ݏ.:"3gbmZ*_D0H5;E ?Jk|%Pb|(i-SS=Zu#2Y1?=³e=$XШNT4'$F;Espɿjq^kNS[0ڊ\"G}i즭sJ W͞+@T}V7AY3$ZR 2F (TTΰqW$tH72Yi~NwK:SF1Q1x6ͬi?B"먚6bs(&?&pM '&}[nM?lgKPCէJ^ӯK*҆Tm|*.+\ɇ#ihW+Ew^*xm2. ./ +@8< cH)e! )RQ.ZHR("aH +P Aˑ +4N\/#.K٠{_PKS'US o:D0;<#\XrHvTKԙ!mMe=$4-YuKl&[@ n H3;ɳGcc\- w{Thy'^s0(R?cA&9y^fy<+ICeQ +p+? ؊dnhSCByqI7Iw = $P+p +AY.a5";۸Z Ydk պM1+n,j5)|"R}Z&o*UYPўnއ%pvOR#Z, #PmNvaN8 ]!Oռ#Iv- +aWɱѥ2ϐ,~T%ӞD#r܅޲d)@25 Br~sKʍ+r`ztg@ms]a>5[BݣTo%C +Pj@jN;3x N}ndvc9mxNh)w(%tw52 ǻ% vrAqhhm]ilX<$2O(JG#azn";nM(||E=FG|[Iÿ<- ׼Fߢ軴jmk(;ahWʍVo4PfB[ 喃@U2S3 +",8硿!I$d0?2"$?{ 1R'@/T7 վҬ$PIM0v2x=-ȍdp&{AV*_fC1ypD+)l2T0K=ܫmTP{0= D(|+-B;N'.&ۃڨ^UZA< A:E5nmH_6#AP}5fSS8W/$J^FZ=#0O(6pm}1.~:يm|K>w[6Dz%JV?ZY)P;٠0W>`A>베Uߌۃ Q̌SVtvOrk%e8 U`A{辋w҇ݴąT\ +@q_#+ ĨpQ<c0t$3ѯ;iV$ k@UBdiPE78i6 rBl xī U +TJۍ*@;S^(7ێgЯZ8*Q",J╴2fTѳFlRM&@hX0H}(-x>ܿsAb F&Aͬ. +%xg]?[U-d^oA#.%/Fg:*-%KA8O#⤗qp+%zpot'p(Hcu^d<ڔtCUH'Yߘϗ޴vGZՃܕǿV)n+pN41V];+IG O^'-L]KnpPD]i9yBvV=t$n#77 6,p!Vv#иGB#w])2P)Ρ4\9O3-qtJ)T^|ȰRduqhR Y b;C.+vNEՄX׻cUwd\x%K U4^ב){"큽mDE󥷱Q"z\ObTMXRɇZQTA{_i2D=`s^aAkUr|}F&2 <~) "Y>11o2Gזx5bTLҧgE~IhLy@ deXHR6ESI*jo3܍!f!B񁌏6_2&m,ӣ#(ȈjO {-QYjz~bB#SًaEOƻ%H˰ɔc]yP!'Ԏ<·hǹ(! D [e ,]BgG`!෰Dt\X&AޖG]3",#\l ֑;H~p!3CZ>hA*` q=2l#{M0AҲGeRLY<!Ҹ9?a׿F_+ȳl &폱Ƽ%w\2 Gy=t JctUs#Da8^kÔܽFho[,k0NB) P݉$`*ߊ4a_y`c=MlXкC9!FHaN*[ +H G]j%(6 }]i[ײm; ƎcMd1 5 : +߆~EH;zsR7x뚲PAjӽ*4;jvNXZ$Bjxpb|EVP)kVkuW|b"tmz=gn^>a2&!*t)l[^V|}:ΜP ] rqus`,:ctF_vPpt爊Vs1T[c Mǝkn׶f!, GĤx +"g]W!'$guVFnds&)Cef\ZtXAm&YsA`U~/+OHMDb +mm~qR DlNL> +/+Pi&H/j|R&T8J&OT^zZ"tbd +7e+M?v2JT6x6;O'3U5Mi۲T6I*l%Sc6^@+z[-e1{2_5ӅddTEQCJ6ãAwbu˩d.ԥA396$K{_9gVc9'f" rJ䞼-Qg}l}ۚsLGοLe ~at'M4,l_iBgbW:R-\.7~g@"*yh ?^ =x5SEWtzͫP_/3pbIVdz Ĥocf'(Cjt>Ѵ/ICRt m8j +yr ֫RUPκUoʵ1_1QTTa0j*Ì,8|mT a=Ԩã KiA +tkpd # +ƇrE>]WɁ*!'S;) [!ǢY䲃Ը)JDR +;D:EK{O2juPĆXO5-y?B?瞱%Q-} :k!{YpRUkVgnl /D~wm_M oxЮp{Rb$CIN;anxd܀c𞩫_n;RLg~D6IÝ$WVBfAnpϘϲŮq#qgJ ZЦYioԨacحr:8z>Ydd#4-:kim GC{[eU 3ps%WsI{ 㣶uqq!rxwV7(gUrTN9:~/sI9?GG=Ŋ4XOu"rH-<tJSa#'b\ ' T*oTȣ\9VLh%!* [ "yXz*rbͼwtA+rWK|"4nDca+r/!oYæ`:jSTU)T Ty9h䱖Z"_Szգ+CGD[?a7LVy}?P$m^:=kmٰLCiaUa'> {o<, Y&Ua +`x9r*Mxe]Rb^\b2+~\XOQLIQADž5;.w\ kO-LλE5&aaae}Z }w`* A# }j--W]7E/c.+k,.FwzNh#]Ԗ˗eZb[;c:&k<]( ^tfxAxmY~6Re @ކF_Z5쩡!jN#"P{a偮Ն@WNA+&!QI(lD~-/Qgגh*RMh) 3[g1qP$>ψ>`Ѭ얐r;fAGc3Aaoer[NzGE4oF +t ~EXbgPc ;/0Dypc71ڊHm7MW1cKM \>8|ZwUQ+j>Lq71YP8r BAKJ>)Πհ7s C֔'aMљA.Ff,asK+%xx*XTaȫ^%{.VWD3 ?54!Ia ),hv-xZaOh @zj AU + 8L-֢!NӜ3%x qn14o&>stream +LRcVh%W޴B[Eu!,B[dh}G82۬djoW-cx'fƥ .b[:r]dwDv~^7sTo|`ܡ^.1kaxlkchR+c4[=-:8,[18VIp]r-kToz|mʼV`ǻof9NR~|(SDHX ef^R&~["9ʾEOr^9,jY&v_lyyb5@ᩫ!`x(:3mvDDq=ȿcM-ľ 2s0DjC8J20`p0-a('+eW7΂[ mMqRiDu +AԾpJvH-.Vl-(a\:9(x^qcXY'Ǐ&S.38|Y~]9 ؘ~;"R Fő8r|QqDj >u<Bvjg_mqz)uaΤJsO`oNjsj9صQ#209Njwڰ DTr4o؍\]qHO-%Yx%QЍMaDJG+ ,8Վt{q r7c@$Nzp$lk(c­f Fy8BpLBD2( +]r +ކ(w#Pz87 xÑ@TOQwXt:5=j֭rH(%eBD F_ Mʍ %rp90!U;7 +|lkbzBS̝fH 85hk!E6m_:J Uy 5LΛceMT2Af5RYp5Rp;'`@$ٮ/XX0R۷)-ZZ&:'XWyWC5LC5>^iS[+‰M8ɹQ1v1/bY~~Φ+@C0&OP\~!E]y,_*z\zZ2PW"Q\؅c=s]el\!ϰ) l[C+^%R f'/KX 02|Е[`O$_X>weV "܊%fWH0mW\$$ D 3 `>ӕC +:4wAW ++qEWrݦ`š/&*C.d+sEZ2Ár"ӕ켺G+/ݣ*Ǡof𮫯DdQ.AW1kYiEQޫ$`&+uW 0YL;IkK> LnՖj+ +ㇸ6]NOz j2_+5%̾V,"=qP0b4^K^}ſٻZB`ͿսL)=F+U`z.bɃoX˂8&`?Y>l} 4]4,TƹZ2>RX +G|IjY~R2\W_S[?,bpNd +4m\L-"\ +#]J][x ~ 6#M>ގR]=B*J'=JAhs[Z3Ir]=2i)$pA ޲ Mb,#hbβTGli RX vqCF0x{":Q$ , f2̈́‚ D/r*2T dLisS%1I@сU+7{rۃlH½IpɾbBl"/1bOAX ]34/a#5)` zHr82tHs@hWw?|Dk;Rcw2fӟᑅM̯ /Y`Ar_nܦf~1@8+*N:) ~*RY ip~0=GI8+ $h=r+3C x'L5rG0yskggva HDnM\ @&:ɆWsXX3"ӆtU5WWvvn֚]iq%uhWj'j< Hikh"[{eM;k)*szW_<.(#Vgu~)%7 !銗{iY]a׫ꂙ@t+^J$}dݏ^=@[T视jW[ +87*]}WSWAK=Q3EBo֕(0k7Y/;v?uLf-55 +Z%v %nVB)$ޫ&mK N8MOIA龝'Y0>͌+m r7]I%krA[Hj}?j"Ѫ*!x*ϒn^͑mUWŦ9ͮy<=XR0MP>UJʁ}js3D5ФǏ*j@ #ÄUÊWח^%^_QMtcσ|1MV3B_5xx{.jW<@Lu,־N}\{qgvHԠF^<< UBC$U -bZ>:s9D;M`uutHn 627O,"t?3F|Ub"R>_$.<Վm^wJ~+W?Te欕X!y%'^(- x`a@joE3\tĒoRPief)*IpܪVm) +5x+j話ê~F1I!2Z`Vt buȪF)NY_+C EW}(.> ,cϾ~1՞ X+%,CV +8`W@V[}9 +ࡪn񐙾bd5*'P +z-5T (wG^jZ"1_ +H"?ɵr!VdO L,M½]q>|YF;xYlX!Ojj}tKSžVx"[A9.|6\ZV[tʵB\ѥ=ZVАZ3)J L_Waj5PEWD_}e8Ђ\+0V*͇+dUX^Tr!YT\QcNhInFj9ɪ,Ն>تҖ +uK"`(`>l;oN~ӽ =Uu3 `?* UE W]1j" jcxXST]SSSms)|]4ՑR<+_f =$"A!߅X#$? 7qw^fk 2N1z` f:\8%1v_S^9Ԉa_@Gt!xP]j_p}rurGܔ>OX޺`1QXZ6N3ןBO[ֺa,_F#`o@GHA + UTH0J0+\.-5Xz=J5?ti5чWZcP +RZmܕxf 'ŭ$@mETڞ9Qj 87ɽ"nͶr3X4RYQە Q8?7&WR+`&AqAJAZ9!2^z4#VYgT͸9Q| R$p?ut4RP1(t 㪐D \4kWj"7 +ȾJ(8+dRY!e+FG  ~f:# <_ ӄZ(41p1vLvX\qaw<魠@=ek;yt@ZuGU7pR';ceyB<'՟P܋]obSRS) Pr1wbuUKJ$fcfw91ZA` 9Xz80hJfcM)Nŗ]ґb0/?)&U*zx2Յ5_o`i2I*!џz?[jv +ZiyIvn%A|2>)R AnɜR+=*k7|5p:k:F܇E8d?6b]b" .X%qF0bG>-VO Vn(l2bErdg _zTUBɵ/bjtՄs3ڱ/Rgv٩Өכx hsܢ2e5)r萐"`ɺגU s.X@(+7P%Y}TRs8ci*(8]#B1ZqqgLb׫tj1r%D + ^wq`92m@zG7`1ѸLaI!u2]edk (+x R^RW(etQ,z{pncJLY@8dO_`+E)+jhsW& 0V +t+yU|nk<ѕPv;1{Om^AĠ+U*47* +W%g9rE蕵c;\WtbF#*Jо^U +Yz;[oʊUMJ֕qn It&7W({ B)Z̆؟/:3E+Kj*Onm>rwX2e"=t]՛5]/5M8}Z0한jM%=F֑%zJY=˕q8&֦' "`n]"<6HbjZRqr䡥pc8k>2KD. KeȅACd$][JRy˯3]p΋ Q-s=ȖYpM>UN a7#BѸZ#sLZABzn~wO}/bd`Id@{:tdm$qAZ__Oa +e,7?'i߃e‹PB:l"jp? + +U9Iˁm.[;9. RO4f$e0ϐ p8aEC3ksbv dThS:\"h&r5d.6y쏭FCIm6e 3}̟y!?y `n \}m KjH9eo  Rd7 jy,bwH)t+j8EQ5/ >8ۈ}^F$rKzw"ψ2X=> Ut!-72zDQ=MY[H3XݹAmiln-2ۀ"IN߱rM!ţҹI=3027k⇵V{O.rr cnB"A*4dFU"t j׈.'-?Dbو hSѳhoceM ǿjj3 "3t32ee6U׈1CdrcX`$ +L7vKH65BL\)?UP$2 fDp(Ӯ7Ë1`eDZ ݥk6O2,xɧ;)x.X_7!7[k@ўH AѢE +Ըj&vmwNh~jE]^wH +$5)9q'Uo:cN'OJ P|uIQ\Io^xm$EO"Mxz]rx]WMO__9@`E_/vs6X=#$-b_FLv:&q;`@'Mc2m@:YzJNw36U!89J-N[_"0%0XP@pɜ\gRMihy _$%i ڛG„dn#7Y88}|ŀȰ$$0ɰ*}-cP~(z?7a%W,87mzz)Bu`bssmR:]kIJ32FSiBS{ux.D:.LXb? \rypțܽ~*w j}hG#żjnPs 0 A= +ږ'쟗`6kf- Љ4{+flQZž"Fwph>{7؈~PrBM{$LUMkq5spSG$V͔X9hG&F$Q٘1@Pz྘YZKk[#6H8Ui*c7LSX?[[AZVZFcٻ!FH:>.% ^έJ?cD`Nh%-m#?IV HWL>MђӐ qt qGdnf-Qf鰲A()#"CTF02Öv|\RAYY8|?q#@#}F̔XqX![U +uE8_G3yz'XqOÀS }etcuB$B#,IUE@;n%7SęZFWmۄOOoU2RiwyIٲV$`?@\^_)UA 0K14Bkoʻ|&#NL #%>^08F(Y1)OKĠd9%ӂh#X^Ct 62Ǒ.=aIVp6mà +DtG)9W]mg mCPÑpc2p %@ 4j `ݏ D\ n\R(@Rr1bcGD&ja.tbI1#9*~Th+JU * +#**S͝o%rx |V89rk ~ 3 q;>SK&%dQ;2"3 +HB(qVBiit%5m"/J@ɧ 6#@a\'w~3#O` R'Yͯ LJ[;1}x $TzH{Yl S[o]o, @@B֖6!C +LD,.ʭy,epG?"ꉅXt7b3*g[u5Ѐ &ۆt)m!XYTN"s+"ʍ?#N4/EG@ڑ8Ǵ3S2paڸһq#B bD`IIxBǵw d!7<$:B4FYYC !F;SkI7˗#?,䱼`Fܼkh2hbJۇU&1ʯ2 +T(9Rp6$D%+mIhB) I$P4BiJ?^Hyr46$]u ¦[i4W'WAM(yM *kL%XG$!e-mG]rJ*Ui5H61_ihi-i;ǯjp ^RZ;R!mb~H9o +$&)r>|O%V9MC̀_Hw-h]!L!WӣT!E=ZKG ӎ"liՓmUgSJ܈t +H-&!|dB; h +7<Ua b2t[ )F{!/a}BĻ94ſb-X&TtShkZ +  szצS7@M;d%B q)R}ot78)c$<:T¸E +2A lF?vZKǗ?E +T>L@OHo:2tg14Md d0=yKߠLJI/ i$j"Cfp*euje9R#Y[Ld;&dTbLUSf[3*mJQ?Yg95U`0A+Fh_ͨ;'yl@<2zs2Y_U }QaNtY%~8[Z<֍WP6!}/`+T[1%l ɘroZiiom!@Yrhǭʒ{|Z(i +'EV),՚RvO$+MD܃yc!gdKxXԌbgvVqDbQȁ}C|A4p^k5/k6Z(3t;KgIw{Q2/%e*.+Yʢ ؽ9ݏs7] e{FO#+?[YӧLHn5Rkor; +hKC pXF),k2ܮ_%}g`~UN20,͓N +,\dKR2g"|) |H"C XN_̦F +=2|һR(%%Su^7wnv;)^sĸdIsH)E#} n>j#7F0)uWR`ޱi4~{hղ#5i4JePʋ9ZE rbĢ){C,y>[$;&-yVm;%1ekbjQ =n<n\J՝=WUZ]A;O0*~ +ٸI$WAX hFKŸއh(DjLl(С P+ +/Xh NFHKK)._K* `O< @ xuyPP[5f{qOJ|t6hL +5ue4Ϊo$oz쫘t_SsMͷmߔ(q*ǀet]pq7eBg*^_W >WcP \Chpgla +;Aa-)>ԢpX=Y'FXJ:L#Xjo$fu&ٯ*C@=/Bi E„ +cj1Hw.>c3"ʔ׫&c0ק ܘ Ɋ3@CF mX;TuReC/ľV9ɰ.x'Ú^2ך}n.n9;SuX2BlԡG4E?zV& Dm TJh봊> +o++Ԝ\'EER +7+u&'<ׁUZ(c5f9 9Cns6Q5l158Xgbŭʙ\"= I.7!LU6C}+,OY}d[n՚:AW0XDOZ^!n!2§ +h4 RHdBdjdu~Qs6}s<71o .ʁ}݊]/ 5d} +76,Ul%T4,%# NEOi(Q\& ߔm"/f'ǯ/UAEJF+̆oHcY6s[ 5mQi'ݟqpPc#ȟS՚F1 6{͈Ya[$ ]C'spMg)6xm%8C1C"z}n`w*iKdirjyҫ@IlIcs3Wkf4= M"9t>f`CO&1bͯ{cƱl(^M?cσP[4)Ŧ653Wȭ#l}p_K@ R1GtOIRxji;L6ex0P^`xg%ݢ,>š>Wy(v38AX6rqIyK78֭k@2e/L`G߉񄱋-:Qge.=.8Km;z3` +93K0$Mo5T/yH z\#XzBY*]m LjdK/)[lL6-BJV&[2tƚFeM0qؚ_,LեB4zALaKQ{1ÆQ0kK90yqJѰ|O:pȝQvT +6TDY\߃qھF ^_Z5&g6m[Sb@'#-Jh= >Cy i@F@cN,%X.[\gD*E(y=A#H.=8DŽc`ҳ6v 8pB ǾȆ8M|z*$ $=Ocد ^ 1mQD4GTKExm>[&9]$j `?UG_{5gu `h e*a^sqOA[#BJBpRrTG% 4KԤҡIxQ_ܛjaK&bY_J(MhΊM'KLN,+p]C{ PkˀC@M> (o%p'Vz^34FʱJɄa 5K5 " f +-B(9nBFP9qBࠌ.}ޮ&,7e]XVYؙBp Y)Rԫ!""6!aE`zMg- NHe A:ESਉ0ac@ [&CƧɮ<6:mAwIVׅ蘉z4>Y<]J޴P#ruRnGRG2o h`~&Y؏_cKhCh:% +oO3Q1R^^@7~EXqviVh%@ڶFI"WC!4W ,?zR/H O ‚M53~WxNs| klr1"h +#$,Zٮ +kvR͓m7z@%}@ZA}Ϣ#'tU/JU$'̀7"U @ʊ5G$ꢑ֪3n$ ؔYE%J/]aqkK., [ FqU`wqCBܮbEz**m|j W|'\*4*-ՑV,@C m\/y +-HE,=*X<Z#"o5dӍ쒖Yp|!+4zudY;&VBX ۲_@y71N]"cY PõU3@ڢjYYָa7W7y!>t=ݪzlcj;!ċ _|]˳xe>՗M1Jŵv+âZ,^V=fE0! /όB(mwAX`s6HV +.e/apyK3P>Տ{l`K5Ԕ X l*lwѯ6Fɡs&r;zcU,\sFeev$NVh´]g+Z* \sL˘U@ek:pü Z @px͞kU1m 蝟ۏbNeVe:]4z! B:(1vCT;vHB*cEM qa4 hi%l o@H%\\6:8QXhf0ow)GAϊ hCǧ-ȃ+_:JL8E+rmԽKkKۙUȕm U C'~h3TIL͕$\!pv[ m>g"בbRqVf@RVIEH_HaoG4b9t/C ݃nF{PΡ*=HNp2HWX%ݮx0wUI0&gEWҫQZԈu~"%j]_VJ0&Dc9k} tY<6p[QN~(%:+~Ԙ@]W!UbS C&F4/B)>u1C %zEM ks*d؉TڠΣ[LtGqث_P] +͢hSrzw>M.iHjPpCGRXK@#'wXϼu[G#>U)XzŠr#rW=s"gp>:k,#ȽZF#7K3spWa9N_{LD^'`}FmlDD2U&Wr ` + ^j9tDžʘ >kfCeJgddƉ!~,Ilʪrk6$1ҿoz,ԅ9:BOAR ކ!VW4SNxԍ-P:Xk`> k{U)b,>QȚ7ݺ-{SCrᣈmCvfZ#(7ucț T[31Ys%C6|Ex3Pwh4: !uO,6H̥WyonºMK +QqZ6Q;(Yh(n?k?Pc~CِqD*2mR] iJ,©jV"<ÞTV#Ud Q?'F)NFT1U)~_I&Z-mԁ@AYkR,Qd6p{,qZzʪ,(7[ct<Ǟϩ@Z/&ɶ_Ά988*K7~*^o.B5qKLzML%@9Aw7}S"`$ Ozʫ՝;Pmۑu_%H4R#f%6~,$E"\d68ۙž3…1פ++0yMhPdMxҸ9|]z$}4.%P4^C'u>XIf>]~{[(Ǟ,/`~"')țCQy6Y`t_ﰄ7 Lqm ZCmX^o̱H.Ś9f{J9fFX_B3P$C& Ǟ! ;᳂B870V *_{ BSHR펎 'h,ߔt*]P,Kv! K˚:PSZ`YX 5쬼RI^2H6Y**vV(!ʈ=Z-z%'"=̻3}PY܃5{qsʋ9\`[1ZDLlFe'"CC U'AOz"\81޻<3BB!v)@SA/LhLB\V<޺}uRP#GVVj{u?e 9 P"wp@d~@o ,lP/&6Mɚ@MJ֖!{ 7McD?Xnsk! K0@]VH!=^֋7 ЭcIZFV;rrn5c>q]Vug2Gsh-pwZ 2(KkpZo:70z:vok;s K/WIF(J-N!wr+piArXiD! pdpCCui7N-z7 aaʪ&<֒kBÇ&1FWDn kwߩ3<4~dSy4<( aLMҪ'P;K>GV nMQS3Uޫ[+$~Lse?l1;DM]<" R ?;>5{?Am==ISڇꋕ"gEex@H'qj{vW |P'[ !N`z 2iM<|ˊ#_4bg>'cנ r=   %+{/z^d:f<3 cP ~kT@"nQpH$R=M8}g&9!R \ )1KBNge̳y<׆i@爋RXǯb[!p!UGLI=I [HWtjAyW!ɮ +yv e-1bGʥ]g +Si8\7 +hڃDzPRxfY+:]a墉fs%ky)=k ՖEkNzX1d)^EMO ' Aa.g *.=1O.ozr?]1 +}#5yS fBzxՎ_r.(Om( +f44@Ց:nF-sp5JKs1oXJX/M$did)"kHtC F8"aZLH?j \ǥxã̤8FCmYJB|g=t6Y(TuB\ޝE2pۥgW~Č16ªziGO4 !s_`I[)v>술?ٙ "XA^ ǍO(EO=@8zaiy?yCNy |X|#K9)r$rAȔ05xO٥2ֆ2nv.iB@ЁbbVƄ +~<$H\zDib,*<"['W1v Kh ݉ @@% VRc/0:k^ |0ł#%V&ʮQx[gepi`ģa_M9)wkǕ%TMj>a +@)ܓN{pl w!=&KT((ҘuRt,s0þqSu'dqwisplWN6㧰NO-B_5Ml6,x+qP#tBIŃ1OУ3 Lr+'XmFLF1"P3NoXyfMJl:TiE{9ol;S:׃z4Yit@!ظA]y4.F\tp)ȿQY){ą#ʉЭNufD+f'xvuc_[bQs,jGR)~9uXHF%(d ?qOGxba}yЍT{S|0 RvR9șn'׀u1TF<ב.0CE&) +Q8lGPQfkk_, c>n}| 5S{>bٞca?ZNF⓵>b7zH3MML)mP!֒o2f߉pC &dm%dh 5lPCK݉֠&:$/cA2l0qf/ݹ(oȂ޸O Po5|; +;sy˙.1LL_i9{s#]Xû?c\b۽XH|m08$qԢD77np%UE7b4"C2h +RoNvI˚GLЮZ]53E4rlFTj T[{qjfxܿNF7U 4w^!V#LdDQJ%ڎW8eA+_MPW>-I:/N +\E{OH͖jMiپ-)$QoNXL;G?i~)EM3(4o%$!r@o22y䪍̣b/R b[=<*K p =FOж/TV"o+%)r|[P| chO6B|CU+$R->#Pe3P" ,ŷ8sQcZR[~vªd89%ǥȿ1qjAu{Yl62g$9IV _IWa2ү_E: +)$-AdfZQň'&LWxMm5EF,0Lv@$O +/2ꐃu '2ARԈ_YsPBf5N!6+pOUn-z0hʢ 0޵[)o*6sMpfا:H/erB7p-}TP @z7z :9"!{Ш3}[|v"Bh4#X`?5+q<o62ѬxfOWJ4(JVO󿻢Ӹp{;^95W"kRE,Ic[F)w)[V+AḊ +M~XPU^<8i j+]W÷hCA$wih~`Y5.l?Rq X~H4caڅt 'x6 FWi:|+JXZ;~ :+YzQ/yVe+VǠSF*'[7ozf|RZf z-&ɴ `ZzJJOcP3<56cw*\ +pTĔ׷Q»A9ue΍P7HaOYv3uMQYB@.a'΍R^aw&yݢ 7 ҉32T0Pƙ6-#$*K^ =j=QXu'0"G1gǂxi;Ezӟ3#e 5N =wS¯NCc(M6MF7[\sf,SqJ8Msndmp(Nrt.[A|^"m;XJa_afth SNIs^& ,hr +^|/Ag0A7쟌c#J[2~ a ՁIJw${]syJU<[ims9pN m!~6^#N_ _"c1S+Ɔ :.cSor-܃\!y&&ٍX JTYY7풤2h6 SIaURtB&T)$]2s +8Du((.2'TVvH1TJdX~,Ī + U=!Xb>bqn tbS{F@ፔ%7!"+ 1 c ׁFDo#^\ +g3r"+&^V&[CY@'}桫 @3#EC`cĉ<~PV~&[[vvnIV∂&6Uu&A Lhǯ3>d×Md;8طxǷ݄KvO[N0c n#>Rea* +w;;팵YVC=.ﱙGW]3xV^2,/+rx _41T +I$ܾHs&)/b Z+qSL4f$92DL?W}1Ӛk."߼Y }v2#B@\1~ p<"C*KȲ-m>N;F!Y9$"ʛۆt 7DYCN۪OȽ- ):& ڶH^XXK7)e72\(>#8(xLJ $) n^4^/XMm +*a+ ^L-̓4 7' 7òUP!UoF嗳c~M%/9Z6 +~ʇiU8Ly͜&F\BRJe2itx[ڤHUڨ߆zɶBFgHY2Ud{'Sz(Ui"I52h]ƒNp5( U})@X+,@Qa[ۜvմ 4yb۬n(Lw? % O(%VATѥVY=3[ܾF#)7&}gg#$6O,Tk`Y$@* XtdenJ1C$qM' c[PȀ4i](UX1\7E'**Zxx=#9˚LKa DV^GFP[ l)k Q JO"%+6^eae6c;VJHn8݌b$ OXm4wx} ;mg$}ͰW{^/QyF],Php*5Aȴ+ ?󖱤Έ=m(-Pa,>j:w^|d 2C g5@1$;8 ? tT#mEq$;y}t&.ˑ3+ ܄[J4~⳴ڌ +ΰW{jYߐr/J@tA4Lwn,9t<8,6pXݢ-OE8ĪLJѽPte@c1` 0 +[A1N5F>/l^;xIKH͘&{JޥAѤ'6X +J+s{]{pB8;Z"j V-0-nfIyLyCvM/ПlZWרR#LrJ4a'+kXPdE?F2UAm^ agԙD K˟Jz7W,i30R&7 Ʌ1cVE:Ee.O2-kt;q;>U*<÷hpkɭJ;.ETt\,~Ha@Jញdp|~PG8 t_V2!=!݆Gdzb 2UbpjͪW:ix?JU)}N3YB0m o!͎޴+æ₮ +Ÿq6-|5xB$ xԈ2xɀZlq 2&mu +-99](a~F\RЗL܅CJQHYϨJ>Q$xq(ܬ+!s.>\O%$C74DrP|)-5D:0JŖZhans &, *0o^0PP5 ĔյH4 +p7T3݆iHս*R> ,ɐ`_l(fBkVMv`2`j"_sRL+lCK3X|Խϕ5jа2pcx}?Tc'KD6X{LG ('#:VE?5lVij Ee5ns,۟| GV 8X}iPc6DE X}!+ds=3~¹x@@ǜ!Jl4dbW@@kf oVO(i ԋxDzn2.dbF~/0/#C䄻^e]lݛaXPQO/iil"eMUM1] (IlJ`gښP6_ v90;,O z?Q'*`( 9LwƕA_E PE¢ +1b@]H7z`>+iOʙC \匜ʡ!#蕑 Sv0}TMtf: D!6Θ& +FĒ]k0ޘKZ1f6X[Zs,wK._&qp FR>S&mzϧپ:VKz#LՈ :+$C r,Y5Kri> k #Eb ]ڑRS:"S?3X..qq,M0(Xe$&1k:gB)*#4U %TuI`§KzOb +(>]uIx=]Ҝ1"OtYTN+$rg@qU~GV9P0.0Y&gvnN7ͧ5*_ʫ>¡)g5CxRcaǦJk)+[%2Wۊ 8+";ì 5}rhkT;̢$-YSRYPHI%䷼՘im%? p6+m |-#62\Tj~i">kC&dК۬Ebh[,(SzdI2kcg'@zO1Ԕ!q.lL5ظ 3_}UXҊ}aO,$ 5Qe zf]}m퀵Qkպ +߇| Ԩuiwk`Z$1Q˄Xk"\4Hpp}Wа+Nצ|2%9\숈xmQK_ 180e d(Jfˢ0 V@_SNM8wwM|['&$Q΃Ȑ2&1OLp.iQ74,=84L [ݬ 8oVFR_ +ʉ% ɍ^VS+;؞[C1cɨQֳg>EOi؇V]~&r G !Qݯ0Hi +Gq?#q49F_YwKy<u6s ',Vʤ Аy*KXK)b8LΛ ++ ̈́nLB8g]L}LB7'O!Jw=DM Ifnbx0 $T!w4\wVo۴3D$=ЦlKan^X% LRI/Q_K,;3Ck mY< 0.f&{Pő, ?ل嵛Du/0|T|lH nԑ>l3VZlT@{oZ'ͥC47TނVNlP\P;!HӖfS`hy9a T1WA]o&s; |I:'9TCy9\L;P|NF:,˜q/`| !E3OdF;W-O ckVVC񼂏cdzИ<{DwzЎH8rf:o[$ڠ™. +rfpH/TNu |_!/rt)L$'cCC+ +"iXu~72 C +Fd/u*uVLey|;Ҵ`sPH\+NutDy$|XBO%1q |m<##ӏ2Vuo=[_5G7 lcsvTBةcӨ]iDnRmiqXbq"n,%9?:$WT ^F6GK fE}8d%)c@DEIW?k/4dEL tC/ i2v2Yj(%ɹϽa' +Uw4'(]|S +u NAocM_46_?g=^ +Rek?c 4P{ L~榈h^p>W7y^Vpr|o3]dd` K9Mz K`ωØxA#4{$tY1jF`,^bY@ M;Y\^ w$cweƒl5WÊ׎".ѣJ@庮TqDY]HFqUeCk6^/*4YѐTBulڅ͢yڣcFdl GsFc{ A,"u@1Fd{&6QHpNآ|YۄnmF:i4OUdᬖ ;n-,-ǒn θ!@Fh0Xԭ"K9DG~#}z~ހ50dc(G2(aL?4yV {gjf2:mlH0j`r:2U@5]csbEDcC%kl%\9FO"klE4F.ܣౕ\cC)\4t(zVY,kԒ'Ov˥ᢂAF +DNj +n +x)WrᎯ˽Gd+ %rMX~;E+,K~Ă2e1+]N ~+5,8\z+=J4&eWMiFJBb{BlEӪËic@h81h+g-~L9}-8ߺwXFpnܣ(1OuөEh2ӈvx!@7z-8nn\Lqey@`PC i 4_+ _ 2 0IUFM|lg8 bF`d*n'?6L0SŅ)-_eNg&+AOnucv,+x#z›(g*uA[6AsHh{.&a"hCd^L 47-+i7"22S6Er4Bۂ*P=?+$YPJg!i"Q)t刟"B$2l,4q}q=AR,êULH }E.\s}1f8qNʗ¬&M/h^ˬ/ x& jW +1 O3#&ڠp&4fs\I^@>FF{^AE& 1w @` W$D)V^T=c,({"{đRU䗸{YL]0,V 9iC{%GE'Seu&և +dk-5Ҋs\Fgt$?Q e4솻 'X_+^7 9j7m+sjV]ޜ[`i]Z +hv7tIr{Ώ~Ow؜"Ѝp% "UWM)~Q6 2_SU-K @<,ocSE?pzwQt : +2f>arhf&X9^.is îہV*NyLBf=釹*3LK*5ޭoPC-o}O/COzX,v~v= DY`07x׉QmEs=)Y;~{r-We>XrޜWGګ&P "i@5,/S#uҾ*`̢̍}I^sQI%ё_^(]E\&]Ֆv6CԌ5]aWGH.d}SeL6.Zw@&M3FhM_>HJ i18n#UL#WF{ Kf*[ + =?J\⣉ޅceX)~ ֬D]I/ +vۋa/?%]^5 :7`Yi>P!m!]w5LBʊ2uα0Km;~A|i/~=]/N38LZae٫&GiU6^\km<=X̀ OO ' ++| u?hьIJN_PR +XSlhՏV $n)5\Ubx{4iFIpNB`.]G ,ʆL)r0.'EHPg%h5 +YQ8= zz &])bu2 j݌gE RIn؈ҳBwgox7v !ۣdAxPckEk?MKE9]G-x Zu"pXjr#EdAXVala[AY=к]`_ +D- +} LQ=vZs%VV^th zfo2!;G9^ L@XQ?6L%gf̀B#.3@-a~ H ?1fV!KM=,+Q֭PA.F#<JsPsF1%ZҤC"׶K}ьV&?LKE'؉NBU+-<+89>DAOLPn#s]I&PWV#PnSIp&~Ieʘ\qsxSibm\RQik< 41UUgi.> +3ص9R} (f$'ALM~?%J裆Z0`昨u:d#+BwId 9r 'M>{TSKXf'Jh9k$ZzlE]+L݆F4tՊ$ "GuĖn  +)6" QS2%/ +[z$T MWRhY)6nM$(N +\&e(4{5[f~-X.F?:>#4_ޑþ`VpĹdT ,&*`cB?S\1JƵCc"e;ifNf'i1c.ȀTLԁ'(Hn,!68;lF!Bi#^]g̽~Tt + ӎhK{uMI( ۖ:7O~ +h`hŶ-ⶼqtm` h8Z쀰$Bx>!j3m|uL^-p0z}|%D +H"<>!wOV揖{֧xj +h_5\ +O|CພӭBUz^8iLG<.J,~Y#ٱlktu8jfS{/6ȝM9޸!wp>ޫ !%: Eu'%L62q!<[AF2곕9pP6Zj +D=`a;eܤJjd c`!;$F~W"R!eLmUE Q:QgbvD^uⱿ~ +U?mPuud7@4SK\aS;D'RyH99Њ~ +4^rIϑr)rrSE` *\[C||{\[ztkhc26p4{'F޼n>3[ wBFA*Fa˩=*'e݀Ds9 N0c'z͸xօІK]8A">u]x@)kioׁO 8id ˟̊f r.4 mlޮa5AC\+'"a +^S\y!4݋C 7WΉzZF"ct4OdRӄP@<PY?[>Yn'Q  Y$HTD"LĬHܚJ;I$bV Qٳ"<[,P;, &œ֌999f 50t>]5и<;[2iEl'aCsE:F5  &G'+"BrvpXFj2A Ml}a-tf8 ͡vjo H8!}CpKGofR9l/qrylEDQK#&zG- ﱵg18<~"OD[Έ`7VK~5%>$>um'D#"6>ѳ[1O;癹 짙'N!E 3d .F/\ Z{{fyhgr +>>@~V P I9‡uqel(m2v1x)ׂ0`#3upJ2xn3GP{H"B` Jo, U!GF?+0%pJ,mJrr1ɨ 0Fˍc~ҷ\1^mL}޽Y2+!@YS6ȓ&D?]A%俽 Kp2|Bl`S?@;SĮG5L&:T6CIoφG@&l3& ydDӝ::ċpS*P)oI'>1pģq;L㜄w8:aքX& „xrDpbBC%@aOtm*u$BIE!! $KRx9x@qDP2qREMqB&#J.6ϧ*g")%zƦFGs cѝ.XҀ^$d"1zJxT1DDtɴy ALhD0πwXBc( ^ $KZ5"d2V('8ȧ"'dmc%T+M+hxhzĦYZH` (n# dNzYuIмl{mtF"%ijЇO^*"c`8p 0 !4kf)ʼI:Ũ9XY&NqSj2R[0M2Vvj('bӁ #,S*%4.P*]Hcb BDNaEH;5lq3GTZf4"lɓ3Oi:4o78" y89'7; ;_^1+Ӄ taÄX3v9L8Le"]-Uc(ͻheje 'Zmٺ5~cVe[Ħ6iTWJ@TeBZ%(æ֕HE*[OD*z%2o@9Tf`u 800<d68JJ +k +tY7t=~J:?ygeX=R2lIxd׀iV` i2S2!e6/"4NPH!Ɖ*![ vR!!`@[" (Ra(S̢Ib DPby6.)xDbA5GR) 6'&bB +;|_+D߄!Ar8&``bkCa#sZ/OstZ@B4Fq4rA#AE๔8.<' Fd(kIz@1!:  ZPhD氨MG$^=p $DSaP*QT6RuYv09fа! l$xDHBFzDODܸXh&@B "8I9jbծ V(b$m$Wc(3yYSӹP` +ͅwb04D">٫ ?xш@c' 4 trӛT08 R `( ZX!,Q 'B aNqzsP*&%Y("86ab e D0 +J*RW"rv'D LC.ئ1Z+PDŽ)u`uJOgs!`x.%tjL@.RqhTŇ@^X P)H`(:CH2-Usl>.D&3@@ ÄaJڇB"<&MA ݉ԙ 6L: +md2:=M"^\W'D<HC aQAL. ErX`X̉IJ%# ht` &TkJѻ\8;\MpM~1Y]:^gyؿ&.{z_oF9'}9.R/S_%cRie،mNllNhg|ƢlWm'u6S^XdN%FiowwssVtd4;Ϯ?6O%DɵK6mm#6=zp5o6"zIYFTR]0:95c.iQ8>|JEJZtVn:W%&|IwJl2RR6c_Nrz!qՋ2F-=J2ϸڔ +c׺܍w|W^EfiPdIx,x]si%"҅Xx^),b`i5usƕYʝR7"wԩ܈I^oQbuf~Xev*i݊miڴr12s*}iԛ'SrsK-)c7WNt3Wv33GsJ^,e>_YJ9]Ĩ3mcǺO#/Ur48{;Hsi%H^_wi\'=`=qu6ol~cν%ݕXkcTRۧ{측V Ϊ=~guZ'­^]i˿+nfn6ݦA(zV,Z1tR:Y9iT8Ko}*[7ƨ#Rr׹Sҗ=k䮑V#_ʻy®!/e]V +}A&qѯv] m]> }qZ}9cOg*#]w]NIuKwؒF;7]6}+cLg,%݋=8ƭdhit:V*ٗrj\SlW\,zG^ic-Y5}==&nk=l 7]vʍvPʺ?έv{ Q(0z]v-}g.rrsNBns +k5n Uo~]W#ιbYVɳvO3nNO]5 n;vQ廻ϝ/+iܲkGLvӯO{.ߛ۶Qu[!{P3NJ*=bL+LpJވskz֕rq8 v58jiP)ʦ(i+btrZ4s/jPna'=0<" 60 H`($P C%S28iڤh-Ꙫ$ZKCMYu!WjuA-0hR`Kz(kjz +:Ab_VK:1.79X(=̂QCؽ 0a{Uિ^**E( ͂!{K֚Q$bJRTz%@H!'m׃bZRM s\Kp GgDܳ =6 + +;oHzh>-V0J/j*!;B;muY$EEN}?lek{iqW:5Zylf %%>HC³C"ŔTwY.x3M/5H$÷$Kl&lBS@C%w/%,Jn}h[߹qcu#:{$&yI).`{X XZ޻OF[5T֮~T!gmR``)BAqjH@ X˩Zsۆ 2$}4?ÉܽK /۬L{KaԖ`0J9$ Xπq%+ `@|65AܓH//~KI]l` 2kv>KnRa"/4'WІ̤sV:sTT,MXfF@6hХ"U![j,h1t cng:͙kRuB) T h8=;kqj'4:Yyeq!aG78L:u HDxҁu@ (~ UZɡ}b a6bwȜ +l2<A"Ҧ£<{@5`r6NK7a4j^ { +~:< ӞЏ_'ѵ&Tڜ)4%WD,dN?Q2loJ M:hgߐ 'MԛIo1i&lr +Gesڶi*I;g.c6 +6n)hKطfxuD碙I-q=-l2l u'X@O{ k]s T[4I,IR- SLdx`&KCm.1L?W.pѺ4`RSqƉCOiA&[[誁صmӎYyN>R1,+-=S"5K4&7 IWSS nZ+= UMR%a.ABŎ(=P=rє ㊥s{U[5iI,IRJP- #^5W$ƮAu}&uLrv~ +[R:I3#zw(/UK𬴩ǹn< eU.9D$QI$r[,q=U\ja]iϚ6ml5oުWNF;`yiqϸ;M焞oi3$@tI!utkrXh[gm;M3+nNLYV)”Д0ì#rLEA}&@AF*X<rqmLE(pdLP+Qs;*Ɂ蕟Rz[Nk ɤ#ݸ @U +rCbQ4BP-I=XN@E CR F1$ALd8y U9Z3? ʽ]++^qHXc~W"lkIL`lD̍V٪ފ@HK g*W1Ϣg} Sj_]W|^42e>j Q)mlJ +EB}N`Vz$N~j,{`<<118kʍ'TDeNgH`UC<ɈqUVt7; Ⅼ't8t]@~Z9dS ~ +$x{+bmv'mg]KeSmM$XiJjsƪh#=R Vd6wڒł7P8j/ex-ű6_s^qȲ@`_iIޢ_Q2NkLnʬm9 +J9!$a˾\+B<2ܸΟ94kZ,R[&nKBFJwc\Эv4]K3sswZ նY2JrZ +<v>u!XcY/6{(g'GvGONP)Q0êp>qI+{ ;f9W@x׵/XLB1i(yͅk P-AcOY%q>4y2% +9 ] +AZ4xQnו~=ã!R+lיk~p8Niywjr[FܤѸSB xw!FHE&_2BCɋ${9cE6*h"ʲh:fJ5]GowK}?UtZ՚#ˆdp b+L08&moQVCU3arfJx_ + 'A++=xIQFD;չdxD2 e*~* ㍍#輧A+.4CTdf/OlEqg P _ L{wix%2iO?:8Ųwyt9v>Bi|KoPϊٌfM][3aB?rű>_nʁŸBcCTmNKqF3c:JzӴw)Z4xf,r#|(0}vH.{;tJHD,+&fzފ30h' +""~?܁v\ISDaJ+}mvP3ImgPnΞ!,L0hω5ݟn 7Oa S[ yAB^PF|vQ 웉иY=!Q) dӴ:%R} ÈŖA(DzKpNLkIv\,pT,#!^ ƃ-\P>|^4«1'ȭhmF»K"a0w0hp8(es Bq|U㮑rT0@" $YuF~%@> ]iK +IU>gMF>{ϾͮSVggo<'|7S!9Ϙ_FfސA>c^wjn>+4|v<}. 1g?VWv58@7:oUM+$huo癯S*j'PBUذݨ̇Ʋ:`gH*Yo|PYCĉ (WI`'AeXBXMI-#Ӆ8`<1 GxO UP%i+Og#Wz4Mj+sY̸nơ#C +xG}ڞ'| [c>s*y/9v9YT/uD>X,",twGU|/2@ :͇\EW @o `QKztRp;L-YJ!6FR25 B(Ȇ|{ϡ_C:x'=#iF&%yN7Mb|BԧBUV | +v:q B&g3e"c8,b9 Ybq$Oj'$}BK}%-AxPؗ4;wp!5[u!4XD[?J'5IJ>WEHrS N\CZHZ'HE +CV,~htNs4#<I:7E7Kuo=s6qAso} ,@wm'+cXs!Dyh΋nj 8TI<@ſh5 Ւ7 IJfv^Bg1jV[&/S lOʿI% \N<$zKU-vvs P|u r;BuxE"pk>훁=j~}b`Y}.vܵglo7G3y똙ȋ傚.؍-HR՛}nS iNVOg墦B ``$O6H.$ 7!$y #_<5@LsJceߨ&-(#`dS%(={:mhX|PWPm/~7yN}9xѭq,`}jE/d)-?"@$9y`şMp4:5" rRŁA`WROnN@|.6xN0E=ix&fi 2NC_A~cÌ sʜq4$ZЀNB,7>"- ʴx&}jA +λ@%r xZzC3,C0#]DߊhAGK.cM(_Uk,]$nsXMaCP7 rg;hL^9 +@}~}/⏭ +dB&+noFM0OKuo dͰ7$RHc loEpfdծOP])tENx7rba񨷘H 2Lxt}w>8Pn@:҄v%p?RqZE4`&8KƬ}ᭀr֚~ȅJm٥H|lG P@9+Hĉ@N6O Pnv0-(n +֪ۘq1Fa/v-p%ba1kJ̘~Il q~@٧l!Z4w.v*QW`=I?SRP!_9w4ƕ,YjM)֟Wp60\;"\G d` jpIR,R޳?U_T|J(A5ksC5oMoPL&fz s;ĮKTg(>dմ? QHtIXHsp#CDfa3*K ,^ZokӇ9ʽ|vXVl׼.h4:Eb#a& ڳmt4Yوe14$ ƿRsu!&[Se)Yn$B Yڦln.+wbӫg-Du b~2~Q\s5!jq 㦽Ou?ퟳP+*82f%FhȾwf j{簉}Y$'FZsII)D8`!p-B -_@ˍ</> +[{܀68DydLw)xd-2 ByS(GSvDMyEc/ʫr5`&H5;p|)M}(Hw&B; !d8#WY oI;5"{A~3^iD?pw9@kjD7}3+)ARWd+U>BRgFÍ#y%ϒ?,Qgh E_r˕uJYխMO~,7 !@QM.՗D.^iZU5"2V^뭺{?rȦhwo<6߾uRһ.S& +aMOmJWM$2n~rFe=OTM}&ߣ[ >+L]"p۬@:( kh*zś^Kf,E- ]%I4.Ʉ}gIYkSLk^okpP"H(pi;Mj9^!) CajJh\L[-ՐdgFȗg/ÔCF^"1LW*/afVφJ 2U'xHn0R]0=~obS$MnCMP*_!eE ,`YM8H. +I$(HE+q' YS$T8 GktHY .xAF%iŭLFR(Dy3CMw +X x+%rL[ XH0:w"5x8/Qln-#+U .HYXCH1EK5 ?UŬU|319o:dH{msJ|WLP}vsd4舘! sSaq)!h1˱JaIoPս +͍+5EPSf]i{ƒ]@R.- uYqOx*\ }t=+>r+䢎5G6۝xRƲ7'ast[d=wrA cM/6s(I[F ΅)R~tE[ +r2p +˼䁗"5Xn iW5kEȂn +! +nl.H +敢w]ĽG) ȕ2UTye2.9dRg9;>2-~cFG13Ɛ#M<K&T"d56Fx/k q8mArqQ4:(qp`][oqB)BAߩdrf5I됍1GJ(Y^T_`yIyʁH JYMlG9&C sD9N?5OP]5' 4-}k~ "eTNTqvϩ%{§hdo.f_ +Z'u(Dy CWQ\zK؅mm*:cJ8\a D1 g ނ9Ow p@d1aqKV Bk5ӀxyLcKS(~oblml׻f$ ̠NߍbK%lEMYf1hi#3X eUp_3KU8 kgHM|8H3J> Y=mz|b/Vb{J, ˜L$yOBQ%qB1;zֶns }땕UĞDLux-=kPퟸI& (1x#^ʛm#Qlł~`Րr%̨^6wS͙îm–$VjkZi y +9.{ +OaF0 ; ^q >9x%3`?K46bAg O+3 Px $aCI=RΙ=j4Aj-s=ҡ @}lKw j{fDm5W~Z存3bBexm/vKǓWQ"EQ,Ȋt0qـtAua]a%TqƓ +i7:w=aj4d@i@lSZH Ve>qM"7"[[?rP1с&I[L_i4,A +vK~; H:­mԞ ݯ,qk O'w[nNj: HEnE) +B^q8} o6@lŸ0?ٖ]a ;q dɓ!0@QP9i/Z%>^$ 1Vz,EA 2 mMbriנ>c\I M^c}"$0p)]{.[&?0wm:X$1Kb.TrZSM )0MtlbX 07s07:j; +1~YW$&d3 t~ܞYb#B2$A1ۑ/*iRIm#baH@٤"¿JH/XW1{دfѾKdl@+&۬a"1>0k{BbJa~XCb]IB&T϶3d*r) ݳktK WXIbW1Š+PR’39a6(`leRgőY!1[':u6bL +9 t2a s.+acCR +z2J1#2愭렵~A?*&6w-%X|dnw٪qVvtن& (\ FՀRm㹻ҷ@ 9$ WӰ^&xʉXTemXfayEχnaU@ h VJ]495,Vio̐^'#ȬҰJ0X#^5lZeTqQ^%dYx_ϴN܁\#|~뺷$-z_ +g@ Ĩ+*~q '{Ub"9)X`)p#1vU9M"[{i^?1ɘ[[,*Jϛ&A"v+Z+=1IṰvq`D>-H/4 hSH7dQUj d[G@А]XM@_ȿ0i9+Tuƪ5Ib)5' +.UV/뵌pL +OaH. lf{ +`b;}ٖlQ7@UQbXc*^$0+JX~E;8/'ib D +E0mfHܴY%xza_FL$b&˄ɲ,E`@ #QFYo0[h91PyG4%8Dah(F|`)az櫶Uu?1kV5v6S9NMyNqP^ש;%4<Ħ x \2 HT,]+C*3K>ׁzeX +NbP r =AG,Jؐi:5UVTMuaA@A b?<&ph7TyL٥[<<DJkjO|v7AqČf@yHڎyc4=Kln.~&%" =qFPA.W4\x  R@Rf+!((=0<2T7W0\\ +3 l : :\`p Cq1C吂 +^21C0 ,5XaeQ +;#x i #[x Qp`4\TIë 'm[$(Nn;w,In!(VDB on/R+m)\lg 5E^L +KlUMPk +HnLs9?2G0|BPZl9\뮕i$uv-$8f $Rb`d!RW͒єοɨ\DL k^O"&5;6`Iw!WRz}Y b4=9(EڅPQYB >20J`t6•bHh1S%1=!U5$;9ݷZ*IhhCol:`άX$̙yİD d#8uWdFqC|G +W(*lim 5&̃((+#BA`~<+66T WnUEk*Hy"9U c"d1;|#o$whّԽj NIl9:1 Є1S2N/q! ;XQO.tĒf.G"9!T#!Ta#KQ/4N =7Si"XPDva-ACy +E9:8vD_ː9g8s.JhGQ::blLNUw{t4}hyWɜ[x1x ,xb2@Bo80jE£7.@2` F * BtI|3OR88IXsóabif0cv;#쭸 +1(+9l(NbO~MҘޒ3/sl釐ن,XBd C14l>ffdfwx5N| PSh$q`wb/DAD#&S\-peUV!7KD\Hr~>ۉmN- ]"~W$W3,J<4P`>_Sט̟Dw"H2jɌ%#F x+bBMPk>UV3$jr9ܐ95  (.QzHsnf'3MBco&%gL0Fs(-mU\z;/qLzY +_4AםoA@fFY%Y =Y Δ< Qp(#(4nH&zF`0Q$A3l@| rQDF)a?-CzəH> kX˚hJ FK(^SG~!A{IJֹc )%^w?Rdh5$Sr̆p҈9(X%RߋWiSwĎDtHOhj/쫌Δf 4v4۱ iiih6# >C]d?9vf\r7uC16;:ԗDTxSEM 9DX e!=3i 83g=c+7e'Iêܬ.F~Ix8un(KlX)B!P}<A)iʜ"~ȄdrCϑ*ƈxC[]+Q0n*AÐTW+_N yfYCs3啥_Qx4b_f3(EQ|DA(Zd-"4C- +1!@ E!SCE,gfh!-L(ebX(-4lp5@9ݮCUF@!%+ֵ9ހӈDP9 M M]T'7{hj$Ѯ՛KbO:q`*:ns4 P4fP@E)0 "lJ$)v8fhO :*ϼZF:r#7EMSR̺Gi.4TP5$p4/;+aё\7^k9+B^Qklq.M>='(}Cp  I{J#d.Q\q5#3Ұr:3-UxkN)1AT@[=u9b"A]% =$?ǩnHunK4A+†'AB ,yd4*2ӏXC!^mOWuX41ȧC!P^+5†J )V%\*%<Q7V6pe0ٚa;U +j>cVPڎKS üi 9]9H5wXzژJn1=%U4!Uy1 Ç!N!GiX'R>V ~V0JK0<ԌBfpi'ljNƚ33+ܶҫQ[J-ͩWsW7uyҮܞ8Pn#'>'}k3Y0.4Ytb>b::Lb\BE3`ƁJ@Ic/e 5-ɄR%U8R5ozs|,(YUu +Q?齄oikaIAL4 O|瑨zZI*>CB* b e?"U'M#~wnvXElW}C13>cR-8nHOAJw ԫJ H?="ۆk%OiPu= 50S#NXAVlhYdS&1؋_-2;ܢ4sB9IdiZ͵J܋W_g=BkLU,p}ī\JL@U$J,^Ts# L>l).x/! ܐhe >EW"gZt QW l5c&J/^D.ݎ%7+TR4Icn/-I 37H8 +/\K G2Y?VʇN.vk$G՜-3(n^S7ˮ9t2wE[:x܄,'S UX>1" +]y{YUL +b\oۅvY$=Q2x+<ƒ5Z殨OOҊFH#f +#`X$ /yJK; bHa?YS53AM/R|LwR3і4BNZ0aU +|ir8jۑO<H:JQһYBz 1:LN~SCi$ 4]O>dﴀ ;Ԭv^FYh(W[T)äAFbmUqNol`Yθ6 :"Y)o0 m%Z`3rjh]И&hk3s*}['9XޚG)z|8>Cf>})OubhG,:8پ4u%]dayk9`D񳪒H@a"J_zF ӣum4) k7nBڛ~=7gkA /ђ8:m}$Vzz5o&둬N8VOzՐP=Tx(O04$6JM 6*N'/ ;ǖm$GAq#mEHކi=n5(S`A l* \IZ|ZEݡ//:F}F 9­yl+-$q=ݫ:CRRxJ"7ڭcf%v,+]5zOYU،YeŒiPnǻ 0_L=$5P=or[W]S79E&aSwϜr=ޕ Nh`IQ>0L"0w" $L/x!#HQd"HDevQ7Pzc9x?3B(M H Fv\,ʼnbGV99.:@f߽MFvӅ?=ŸS?62[r<3 nN⹟'@-kV2>v)cy-khB,F?,~hmzلft,JVsUv8Ig1{**“r5rN +fd؛%0NW`2G]2uy@2ƣپ ՑEyH7e0^$u8tDR)NEUhM:{4х0꿳̇RXI`!^∏ߡP6 ^y%Dċdv1k~szCg:|J1nc!}dS%+bdR,I-AS9%0 iSg% +R QthG*VRYv]xWV@iI&3*,NvI!lѪʧBUO=ݗP٤A{d:|PBNrdN1rF_Rѣvuc;+$^o޼ǝ5K+"p*ӭD&㎇~*gom$%z~i[PhMr@o0+h3 Y7G5,y+BJP\laWM$ L60ɚk|Zf@:30hW:*Qs#RDFoE}5nIo/1XIPCIELR-h Z'vD;ЋϻFrwssQ qCzDNztPN]ekc==`iBnd=z'UnQ0z~͊LzGrѴT#*ͷƫUOgfΫ|2^@Ua'R{1&\v&FZh:r(Qi +U?c."etX#0gʹ W$); 8 Qjf6&za>4yiwF#;&n,iXB?[@9H(<:&v{;Th;5JQH>_t=)N*HVuG̴QVup Q^hM!uA*E[@Ly(|+ %ʡl*$̒Qm-e +J#!S)$ԂyN[ S +ہw#?m*")a8zε> N[>-=ZFC9 w\6%K(?q=lhu`C%v,j%ѐY=ï{ +r#  mrǨz@>zZoӋp @i^nJ EoMj'vbHmBLM0Q״arj~ץyҡPI+qG(: ޞƖ'\ʫ^3@aѽM Oh&q^Cp`PA@?Igl?Y&K\ {I&l 4P)_ }E^6a|q5ףpҪt E2xa&LJj6HtARbqQ*3xw8xçLtzu7 ?Mϻwv}S`U6")~Z7"76C!$qTBZGlR; +(^8n-]NTZC@I{ + DFɄ1K + ۭ;.dICy ihh-ac`P荤aS"T{< >8 ͪW[D:O`X`>Pj~ HY[y XIB$?v@zźv>DaPLc۳$'ȊY$ >UAp4QYAL$3I̺y<$)Aj 'Co 5%:&SC OY0%ׄ,QᢍF#}VSdiߚF_sIOLNY& +C~wf $ sPmx ɶI5l ̑nzfmwl:瞾S{f]N%m+4۰$Ƙlt Oxj6r_iijA$ݶߗPt.atAN*HG>sRb(RJV07Ǟ,yI]S9"2y:MR$@!k {Z(zodHKB)?oY|}z[sI]$k˓; +,A tߥ< 6cЪ$vS%;%V]ZT:n˯dtDonuboj*ih'RS}"rt/$0ƋDD%D_p?axs>Rkdt}UtJ^-"n +вF }~ "5TFFf$:,D6C蝫6W+o"~XE5Q3F!%4_?)Ѯў J -Dz ЇЩo8~ o GERS;zC!*^/=OV\3yyEK-3DbEjCp +ne$1 &'{!8 Q,M:XA`r{^h9"hЉs-WcDX%37r8)q #@;ۉm>Ҩo5ȝhtg)v!|M#hN 7i}fYs,CDK뫥̈́3l4 hFTax6Mñ/ #pU܀>gĴ0i@ˈwB@qxbj/`V^X=zrdtn):cJKtSNoe.:JaU8U$4C(:U,X}A霝N"B- ۺ2-dGc`@X.=IĽ6zǘa)Bvѡݬ1G.`r El Ǣz EK =ey/<I 5%B$ѳ{J`zMQGOOԥ@64uJ=jǢŒPΥt&]B|Dw 3 /(i8,ӤŠUcm>ΤRˬDb,Q/6>v L̦C\å{ IѰf?R .C >@% M~k>lYy%Vzv7^ AΛ FB[5%FB f&jiˌ/,>stream +[0 j !WIp'"$TNNAp+$W}d-ڬ}Cֵ{T)i }t2vyT3beMaY<GΡG6 Bn Hi۫<*]PFHE"yIH%J7C]jKyz25 Jj[ ύ5H%5a0!}ixsƜtVm,JjOA Š\%zX:B* IG˃fA(r0A՛aUJVЃL)8QZXYe_LNE[͚uA~<ʂ-I׉j-DSHy :3Ea@PF[)!tdн@y=deӈ*Zt"ڐ5V1X^!'48:  a4|k<@$ٙ#fq) jz$ru`OA3Ee"|:ׁ(a^;?`q +ԉď @8Ó S +?2(l&܀7?yxG]侵_8Y)qu/rTI=mh^r=ײ-m n/ڔ^d@. b?I ԩO̵RTL>P^o~zLHdوOKGP͘?}ra)EܘiLWZdW%=q$.-賔 cWdE=/`3*m,t%)UKg$1;1e٪ _\>CHeʢ-OڻG; WFwit^>9>oŭH +b߬-C[ǻeÐGG#<0%o7+RI!,6PC'^m5~Tہٿ:_#|zE6\Cq^Ќ{H!O /n>=W.nm2,g4щD+"b5 +V[Вoz~z!Kyv^I%7SUzH)5}hG>FvgUQ458K4R0kr҅B~jQȀr`Q,%KpSLp9AI7 +mYw Q)@a6Û$fLjOH&TkfN}{ɍ=֯:OJa6'\r5ڡg!(檚.{DDMfTנU}DH?ٞ1)f\j-׼F>Bm NX7ı]?-jHz-hdnBwX5@*'EDa +#{$@(V,\"QeQ| 5Bk&%M*ʟNwGc+P0pG)†@MşDVQ + +c>dRȌ2)g1ɯVF7+ +.x xG@3$&NR8.tk~BT딆јݯ75ڤ=2JI%bՙD8txu泬XאU_BNsEs5C*{IT.TGy!dqCUY>_NGL;g8*BHB; A=Õa;3*܎gM+JBy2pa»XlXYF5˴>vS\=0WFbQ 0^4iLDG~VrYYxZVSyF@d@QmBBH9ӡC"Ev`OS*;GyE$J9 G(<ԼϚDuHZ*DL?\oP0L`#Q(EUxhdF6ETJ!z ((N Cd%MI[vhҵk+4P?x̴0(91y;LN'r 2ASJ"7<8YQ4*ky+~s$P*GZKE4ϝg'5M4.ﰔT {#2, #q!m|EL@҈IT6h4e'cf$`RQvx `yuMr&Pw/|bY>5$H7Uλ ] M?^chuL T"bYtT+e|tORApD&ؐE@@Žp1\G (^ +QCp7U&c8]~Ch##^2k^C*u1䘳هZ   *# ʕc 8q8BѠ +5C(A0D*Ij +jSB#<Ȝ@ '[n  d6Eq"6:*WtˢW}0(S>(B < +b`0ĥ`.ZAE=ŔǼ!'*$0RL) c(44 +Y%PS J@T>^Rac@2>APL`C=$׺~٩@eIؤOMb4˼iM-MEesTjʆX_s\'0sSl#΀(pjBBi9[,ɬxb7gZZy@kEMNt"(sR +E!`M`h%}¦qHψ\$Vct!g%T~/B!Lª@ơ> +ZLˠ1I/o(gp| rDjEg(g !Eu7݆"6wV٭0hx YAf_~4+y>#M!Nz, ʕk1Cp^kgnʣvIfQ> +{xa,Ԩ3k!sg̸٧g'fL |aN;[ƳoPfZS˼ 7KHTɤKS͖DWW$H"ܱ4H0T5>#r`^5_M (D>E +b^@jWdVءJcB(+\zQ0%s +J<(;,?Ԯٰҳ5qlk6 _Lk0ORSQK/"+(NW0;/ݠAV/\<یGF-$DAglV9t4H B{ +Gi$38=* zSЙ̳ЦA"W\`y6*I@( 9II6i6.ƬBVC5v)ǧCl*@mRCڀ}`1&0::{- [Y.ΖY"Q!?:{SN)Yuq!+̍K O/ oB%$0z8|fVY]3%7p0OK\<O$x@ +T갊x}@vኙ[려Ua ^Ԑ:U|E W #Q>~̫H"Dmt1fxb~Rk[h!Bd,H*)FBRA+elSL?喗&(ʉ6ÄX[ TRk]P|tͽ]UH| 2d%EtG6ܚTtgJr%],@ +Gt 6;zh2LhkO#V;K!Z.V٪1& +VV8A +Y/KoK=bԇ8&nV-ؘg8wAry/$@ƦD!^:Cq:it5K +pw]7ctvӽm1ӛR?cI[@"I ߄t*TgkrL EY7PPz812O/p@"-co[v}.ij\ɡ2 8 +,rJ'I4Y:[$ /{X d#J 잍mlT"Պ\xtF}6"BMeWtMFm؀2=ZAxV x +PֵBmSmdANׯ^5"i#I]O6n1D~@zQ2hBW {;mX5uit63 a8Z96)1>;im1HJH(CpoO7\}}a/TBs\vlF#u! ! ҆:)lI%WGÀcQ6^(,H?S3r#B{ܪeN=3EX4ICLh4'g+.{Qgj|0~npΘKퟲJ7,d 2#4RssT">Jb:#NH[ 6;e-u+)'>knJ:Y|P3#y*Xəʡx2#OM"hAVn>{©.wTMZÈd1ZlDgzꎿ$9HFZxJ|/60Y +IKLe63wY\0Ԭ1CLN=#i) Xnto=huM,,na ,3upO +y= $񉾱6vv) +E5dD=It$lQ17F5SR,9!A.5:SWꗒ}A\mWuBd,b7fA\:Geّ>ܩhM:N U;ATRGbVjoE5{ʩ]~[mG *=—Sb" U<Pv}ÕOX9Ӛ)!jHNBO%\ٽJN{G O%`W,]Z4&(p 9H +z7Ix@lXIzi·Q +#bѰ.Jw$t:-z8LH[tF+9d.@#]+9;"[BfBI>Rb&zcxMr4Il2o4ڇt[$l de=SV P^aO.poշs~DLImV'a\F$,OVE1eRx{t  _7]N╢dsLw` ­L@k"2sl&b A5{P +Y,!nEBoEݰFj@0]7Gz("q2@ƚuQZ +dvzfӍC24CWUS+%FnI ׻KUa}cu$֬sbh +B|r_/~/@>$_V&Lc9hg+Izӛ` e}-E?Y^ LUDūe9h$gԊ%12.Ud5XKT6;"uU\QfJKۂ;lb@f 7Hgʞjc(V 'nfӗ_lǯޮ[%YUd~cFm~~\a.-k9UJp]l '\{ R5/př C9js:D2NFhoVm'ZyO הּò=S.<+f-И&9aήZkD)L* >Ѓk cPGI_Żwve|B7J.UBPvJkW ѓYM/bΝ)V d\;PWe2D^r +{TB{`b0* xnĢ7HZ$TrqJt.1j+'Z42U۽9>ҺN6]hL) xrh%\;L<"m;Jt6f'jQ8h|1},ڔhJLU˿Yn N=J +lD+YH}2#Ib#VRRuxꈰS*<됫AXvڍN 6O]CM B'N^%p]jtݰǺ.c?1j#e6SJ:Dt+g iVJQ-yVy{ mMA`NR:^ 'zV6NF1Bs<#+geGz_ ͗&U;d{avR@9W氤 Ff}WF^lAXG,j01BR +1牴K #)'[C琾K }f>- ?mK./_{ެhvht[Cy0 ,9CT]#r 1BC̃JB9găx$dxL +dw>KaSnRx ebCU*![\^I)L)MYQ =m@~5} +I=ƞ4~ Ez[$FAz`Fn{T`D(N3@Xue? ҂Lz`R+ q+_ PoKaL ߳7)$c !i%ƍܱAJ&^ON`ܔ#ֳeme +$ÔDѯa'ç۱z)(e T#GI }iO0rzD#FYV<^!κTw u>TP $l󶫗I4'BQOaӪrLl#E + (nN?WIz̎x,$HP` T(w +lW@ʒ?IM/#4ž*zv4VbEXMI&mmKYǚB#LoS1[]>Sѓ4kc~Uڗ +rOq)_3 \q>h47 ݋9R\h?6hAJgL$JфWI}=Lz\,p"˝v25I8xH7>\#u2;R3_ӚnR7m`ŒH0)FD5)jx<L5fUpVh9@qU\Qwբ|ZoD#pVg%΂0TŧL#E'Q:9,m&;Gs/ԭb"ј${2Z)11qGw]QRu|vu~UYvY)E&%Y +Zn~?J : TEhhy2蔴 -s zjx喁Jw R92vv (c@eB u)@TYkq0 )6Xm]=`IR6yJPe(iehwyv׻X᏷ER]w.V3v cxP|AM`d !"5L1M%*5*k. BWӮ78{ +IaziJ Xp22(1cz;d`p!X&(% WgP!䓕a*ayG(١eꂠ 2%;xPaxy>$ cx~FVR +u$ƐsCftk7E<}mβ7M7=B#pEJmGe-j-"٢PTV[PPU$ fuC Hj]JМ+K"ҽ0 Rfɲ<vJ/fu;*U +&ҡb)b cy'şAq4!yBzvwHYhՁ{]` 2G8 6Ut6+:ҿVXBVW5jYiN{ץ}ĺ(R +ȃ "dk9 9 cգ`֚k=ҝd>HQV02u/P՚v5z).v)*v35 qO thգp'zA֢Hgj.RKe){*6u^+Dq4ܛooԽUd_}-+TP18QGf2]ZӎguqdJ">P-1\I,+'P]=& 4R%—X۽'pqXt)F@)(DDQDtw +E)6jIHBT +S6$Z_3劻$J< פ_U%aX&tUo'RɌ‡.j~Nb/<p9J2hI ZSYbyѦX;%+PJ']wwIFW7}#,bӂSGF]v #Bٱn9iivU;&H2f%ST=sەAVi2OmXFg"Jo(SYI<UGLPMF*/MX-\0puUQUe 钥EҪ"#=U}sm3+I7Lmʲ۪rbTO&MZՙ}}i umXj+Me;4{ :C\,p)HOF64,g06Uc'X!8 +Z +ޢU,*iU+RM*C;E~ @@9aL"E u5QkDGHc^>fF̊X'yTWBBZr^55-KD^W$eIi +{#!?Rl@QLn϶vyykBC)%0 +# r"5iU`tng \@Q˨v9VDN(!-/=OS|/RQrڑBe sA(QJ R5n˘00`cdIVPԪzm.mn7Vs#8o`j@yCTsj׀D'4kUPgct>N"9ȩ +R-]K$L˹.D[%՚M5p/:-(-cizz-b2NLdiYuM)W:35C {F@'!g=P*,Ś3"V +BCkiE 8"Cu ƬZ4;eZVf\SOW%B̳,\]p]j݂~,Cc :C5 S,+*s^Ֆt\XC<2Z4o~ETюy(%d0{>+&F%cH93a#SAgP)^\"QBde8K_Ć[OgdZ`@MʽggsX8\Z^|Qlj9 mSM+9afe=?,I[ŀb)S}>as-+Gk݌3#H靓+Y*iVdFWQKN L]:+bMwZߞJ7vcD@a۲+F?x8uڙh3$ D-[1X;u`|JCK-I| W8dd4YYf˱a3/ʣ=bf5,?u :VVA.\=$*NP띜 +gǗO$y}#Y+`pQ1M.Vf$M%0v)Q)=PT&2v8X6-+wK:|v ́Eυj_:=h +\8A,{"@*/| +wA**H-XV1t+fCMHEz{\BxkQerseȌn>DJV(pegņI>Xc +xo񧅋xƬC\FmbOQ{*L3Q NLKH/*O$D̬Вg!H<`KpupH|!`ID Dfu*Uc$tY +%uJUo6A@;DGR})I'MHq_PWNP)eǰU٨AP3"yҁ]F5xq^! h_^?yt6 trC! +F62ڳ3ǭ2V4b<=|=N{%I .Ag_B)]jUbx\38;oOKkh'JDž0&9V+/V+ =OW!WHe+TTt9|{FZyB-{oϹ` s$eϪ~F"#2Lߘ#gįfOeCoR+n3@+1$tdydqdQ}cӱN mgdf>*RzY0uw+߀D?=j: $R +k:)Yh#IS Fpʸ$Q颁@h{>j&>u։j->b61C߼nu%LuZ)Tѐɨ]{#!A +VWmRNC 勾H ]j{P Hë(&26&/$ cHګԕviЕ @k5&C1 +kA#8Bb2ڢ[[o>i%_P#HNI^4(ҷNHb$L4¼k_-cmp&)Hɑ@!w PE,)|R[q?||edilCBN= IĆ!(KNϷ[ycM1N8`Qc6eh +D 7oĤR8J<_ض|Ԗޑɠ@Щ*`3p@WJZ(#RlKe9:}@+A04K&A@6lx8k'[p (YN t_8!y2T*X؇8T2r +7X,A7}5sB +ڼxIM&HW̎ػ]/#Prf(1|s!71tAMJv|9\pn W$pЛ'*!*vyKԪ&#}2=QV 38&&K ?f.ܿt׶ViKTCMH̒oiWIRt,}ESWƶf$ׯvxCtүKj'-ꈵ_`Ø9(것Cy>Bz0?;dR,ҋx=i}"!F-;LoH!~P?bDf=6 u.z2azƕMYTb8[bQ[Lá" 7K '27"¯XoW7wvW^lY2?\b!50)DsK-}Ǎ-oLHE +!%E.-9KԌ3ޭGn +so!ҢJ"[c{|^0GVfJ`F5@>ftW{pL"h.svǦZ|S-wXp +kO?YWLف`@OdA1*TM\мgO_lJI`@j5RoI75d! +ЁE ]v0N _ˡ7 _NMD^S虚E:D+_eQB7 7~a$.}>$[33roY kFeN|L#>fTH@䡪@=^|"[{jPHGW WLms. oWv,j#ꗟ, L+w!&h2UÒr9xG 鄠;k;d!r:@ m=f5ۃĦk$8oPO SW瘝(#`zI-lF P?Ȑd ?HGHR[wӄȹmhQS̎5OE(K3뗘fB֭cUVARQx'HfӮHAm y>13߾JRC3F9 )nGOg:Ws&Ԝ`=J@Lף 3pzDHBk':ntd&]#[S39X7I/Yl"Y_De2c˘I.]r eB2#ZѲ^dpd2IgV[R>7-q~\܋*4b=C5k2AnzWX^kY"gV50jVx?t$'zCהǜ÷W)_ϢKt;H*)1a,ZОV[-oK$oS=Z;loaQc~ z qi5.I lmiŃ5&9@E/Q҂ȆkS &n f^iMX3kμ$ / Eb+o5$ {vgU kÜ"i/eC"/# =ʿV`0_B YEh;Y W:QW`!Ϋt4Fxd܆_aH촯k$M[2̻!ʵzK\:?Dq;tbuҪd/'us]/f1rƎ(vlD~yvwzfԑ+vܡ%$tyImT$\9e >âdI֛C񚓽k2g1sm Tw6I+m8d 4ցSYиh3"<V#f/ܜI|CҫH d]'e3F&kG؋jmgwYY B֏(n#[ +gЙ1 0!V<)R*yCsZ +4")JO{%(0 2X]Hм^ќ֐#7ʰMpTUü`NEDԙp%I*1+%& +lT.}/$<řTScK*lgqi0br$/ORaiDBȥ&bޔJ\{"Յ1*7?fP{<=bio'zaMA >mGbcʗ!\q|+V8b$ȍ9٨?D*Q +vnWʽ05IլN^{DF ):u) 4 +yz;TrXc"+xE&8".4-F4 v>ځ'G1e&RyCm&auOm +ɟ .T6QB8c"Ddbcs 1Xx 'tRPyU32{T󓬮PL+5L2mZk;{l#tbӂ2^aS g,4 +5[_ B,P7kf.D#G#ݕ Pm: 0o1d^9BKׅ*&>:<T,"<{}~g/8:}X[~Ӳx&I`4EͶ){ +ٲ:5_,jEEQb[aP0&6g:X+?dі:bG,IY!$|j8PkqÆofK Hɦ-J3˹e#3F 1hR".Ǻ24K/JdN91.n>N: ?9 M3p0x +]aWcZȺZ )k`;mL-'_П=BA*J!n4ߛAmvݶ/ cXu}y{ǶlYPK,l8Ũ7P",}:vmiv]>ͻǩytyt$wUF"*f!lPbHP5/\*:UB x%}?VCLq +|>f7U)6S|0>^:Mq-50ecD Ai." ,X q x P`HBӀh  +,`0@,@q X`^&(vr~fT7 u{k<u!KЦ5r$qϩQhWzUCqaC@KM+nl!_{c,5LS|&vf?/(bsJ8IPpnVFl;+R;PnBRV50$Kw~Ew۪fpm԰?>Mki>s#GXb{mh6hD<ԖLfRI1cwPXf>[DH$I0TQzA0 2I<6a b!!B 2JFK@go7o#]k!WQ(*RB|Im&},RFJSSIB 6pѹO[6`!\\xf4CC("I,:.F3ZgAZ"( &!5DY.MߣВѩy;h!i#I IbPbdPfoG2ou@+ + ΒPZ_=C0ϳ*&գX%R.3mhDjW40谽2/ B4ER%8 7GˆnQejx!XP}(69[m3vQ7݁ׯh_Y ~lV X]5Pw aueށU.+BЫH!m#N$XLLm܊\][W{u2H%dw nF]r N7pA`[%M2a> 4(PKwu\iH!쎇JlOkO\}0(98ilI("z৷+IMb:2W=EgCq {f[ CGL(rAlJO&8UD^&c`3_Uap4M\2(MKR6L9FOpdFES6T5u0iX: '(}wgڎHüe 6:&el*r~ P^y{J,ݵj"ʜJShC$ .02$0*sA2lk0YLnS!D`=c!r «zyB | 1a2~Z)AmB_XJ*nU٣@4.} % MX{>ꂔh _#h]$#7&Q{SQ"C~WaJΩ:nJLȲ댶m){nwZucmbלj7bӵq aF v_P+ l*p*aD Sװu]RS6pj3WDiYI- `6=ig"mۼ\8 JBULN遡%EoB Ԗ5d&`0GpoZ%~I>mOMj +\Q +`Wz`VYIR/L0 Oi$Ml[!N7擬DnɵshH亝6/"ہ N@8ꔙ4 e+I~"n#JPM&7z,A!U0Ȧnj9Kncta9tGjO">stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX$6Ra!<<'!!!*'!!rrmPX()~> +endstream endobj 282 0 obj <>/Font<>/ProcSet[/PDF/Text]>>/Subtype/Form>>stream +BT +0 0 0 rg +/GS0 gs +/T1_0 1 Tf +0 Tc 0 Tw 0 Ts 100 Tz 0 Tr 12 0 0 -12 -103.0967 -61.2783 Tm +[(T)7 (his is an A)11.9 (dobe\256 I)-10 (llustr)5.1 (a)4 (t)5.9 (or\256 F)25.9 (ile tha)4 (t w)4 (as)]TJ +0 -1.2 Td +[(sa)8 (v)10 (ed without PDF C)11 (on)4 (t)6 (en)4 (t)3 (.)]TJ +0 -1.2 Td +[(T)71 (o P)5 (lac)6 (e or open this \037le in other)]TJ +0 -1.2 Td +[(applica)4 (tions)10.9 (, it should be r)10 (e)-28 (-sa)8 (v)10 (ed fr)10 (om)]TJ +0 -1.2 Td +[(A)12 (dobe I)-10.1 (llustr)5 (a)4 (t)6 (or with the ")3 (C)3.1 (r)9.9 (ea)4 (t)6 (e PDF)]TJ +0 -1.2 Td +[(C)11 (ompa)4 (tible F)26 (ile" option tur)-4 (ned on. )41 (T)7 (his)]TJ +0 -1.2 Td +[(option is in the I)-10 (llustr)5 (a)4 (t)6 (or Na)4 (tiv)10 (e F)31 (or)-4 (ma)4.1 (t)]TJ +0 -1.2 Td +[(Options dialog bo)14 (x, which appears when)]TJ +0 -1.2 Td +[(sa)8 (ving an A)12 (dobe I)-10 (llustr)5 (a)4 (t)6.1 (or \037le using the)]TJ +0 -1.2 Td +[(S)-3 (a)8 (v)10 (e A)6 (s c)6.1 (ommand)10 (.)]TJ +ET + +endstream endobj 280 0 obj <> endobj 295 0 obj <> endobj 296 0 obj <> endobj 297 0 obj <>stream +H|TkPYۖLw8àAEW 0 1JpE t,2""(/Ded">שٙx0֭:u=~9߽upLd8>aɒu+|YWPhb] +RhINjh&Z/㪿Sg8.}s{X~Z'bGmƌ#v|2&D%ܫթviѡ1BRFEG rJ譛T%U:J#WɰpF4 +jB)f?Th%F:aS+WD+ (1#,1q:MJ:}Y*Rpaasİ8a_ctl6-V@;G0I[`\kZ1Ml" I8rF~'qlَ -m|먦Ѣk5\i">e(`8 tF>p\ oI$G7h$||#m5Pk~wl -!M2; Bcу^iI㍋bq[F[JHkPFa4>Zٽ3$/҃ :?7KAMw!Xh"aCHyP 臰Q,}}5fDhhxYR7zCg^ q`?ۭ+qE,݆O$a0 u*L/Oѯfp]c}k_162Z6q2o裧*2frhIhuxdzfKnxwx::(F/yyпyyH( ^x+npQ"lFzŢz+ &@z/ yxJRh:>5F+;{&(x'v̓`މW!' +4A0hC,,0Ƃ0sG0C8"Cz#)bW@dp 0ʄ;*$)paI ʂͲ{q ;gB ݙ)~:3,вoc|4]- zEԞU z6ǵM\B[d+4I,fK:1ގ.mFɽVW˖of]acvX`!𑅠 )5Bi@WZ@Rjϙ.W2 9^;-\+.S|<8K5Lڰڲ+K Rj8Z|L(&G0'ہwGw.8f%bBBUe+چ%-jG0nt9?GMd~k0_+d}S`̓ƪRD"w5l + arpWjۀADi)ᰁ $^(3[xF `z2?=h{562_o[&լJ2fc qq5&h% b%~O|EY駺!O:RU-,.9ʕK"X + +SkJ:Yμ.A>Q}}BAܣl]NM]"ЇCQ^ʽpYʡ>|ZqCkk"ok|yvld4u4ݻ5³Y[\X60jݔI7:!|nٲ9> -~ ~VQMQ"BL# 1-?=N~ci*,->͊rwND£aڛe_Iqqۺ>J2WFB_/d 7?F$i@N}qd9Y?} 2cj~-sѯz) +gE]+n> endobj 298 0 obj <> endobj xref +0 299 +0000000004 65535 f +0000000016 00000 n +0000000076 00000 n +0000051403 00000 n +0000000005 00000 f +0000000007 00000 f +0000051454 00000 n +0000000008 00000 f +0000000009 00000 f +0000000010 00000 f +0000000011 00000 f +0000000012 00000 f +0000000013 00000 f +0000000014 00000 f +0000000015 00000 f +0000000016 00000 f +0000000017 00000 f +0000000018 00000 f +0000000019 00000 f +0000000020 00000 f +0000000021 00000 f +0000000022 00000 f +0000000023 00000 f +0000000024 00000 f +0000000025 00000 f +0000000026 00000 f +0000000027 00000 f +0000000028 00000 f +0000000029 00000 f +0000000030 00000 f +0000000031 00000 f +0000000032 00000 f +0000000033 00000 f +0000000034 00000 f +0000000035 00000 f +0000000036 00000 f +0000000037 00000 f +0000000038 00000 f +0000000039 00000 f +0000000040 00000 f +0000000041 00000 f +0000000042 00000 f +0000000043 00000 f +0000000044 00000 f +0000000045 00000 f +0000000046 00000 f +0000000047 00000 f +0000000048 00000 f +0000000049 00000 f +0000000050 00000 f +0000000051 00000 f +0000000052 00000 f +0000000053 00000 f +0000000054 00000 f +0000000055 00000 f +0000000056 00000 f +0000000057 00000 f +0000000058 00000 f +0000000059 00000 f +0000000060 00000 f +0000000061 00000 f +0000000062 00000 f +0000000063 00000 f +0000000064 00000 f +0000000065 00000 f +0000000066 00000 f +0000000067 00000 f +0000000068 00000 f +0000000069 00000 f +0000000070 00000 f +0000000071 00000 f +0000000072 00000 f +0000000073 00000 f +0000000074 00000 f +0000000075 00000 f +0000000076 00000 f +0000000077 00000 f +0000000078 00000 f +0000000079 00000 f +0000000080 00000 f +0000000081 00000 f +0000000082 00000 f +0000000083 00000 f +0000000084 00000 f +0000000085 00000 f +0000000086 00000 f +0000000087 00000 f +0000000088 00000 f +0000000089 00000 f +0000000090 00000 f +0000000091 00000 f +0000000092 00000 f +0000000093 00000 f +0000000094 00000 f +0000000095 00000 f +0000000096 00000 f +0000000097 00000 f +0000000098 00000 f +0000000099 00000 f +0000000100 00000 f +0000000101 00000 f +0000000102 00000 f +0000000103 00000 f +0000000104 00000 f +0000000105 00000 f +0000000106 00000 f +0000000107 00000 f +0000000108 00000 f +0000000109 00000 f +0000000110 00000 f +0000000111 00000 f +0000000112 00000 f +0000000113 00000 f +0000000114 00000 f +0000000115 00000 f +0000000116 00000 f +0000000117 00000 f +0000000118 00000 f +0000000119 00000 f +0000000120 00000 f +0000000121 00000 f +0000000122 00000 f +0000000123 00000 f +0000000124 00000 f +0000000125 00000 f +0000000126 00000 f +0000000127 00000 f +0000000128 00000 f +0000000129 00000 f +0000000130 00000 f +0000000131 00000 f +0000000132 00000 f +0000000133 00000 f +0000000134 00000 f +0000000135 00000 f +0000000136 00000 f +0000000137 00000 f +0000000138 00000 f +0000000139 00000 f +0000000140 00000 f +0000000141 00000 f +0000000142 00000 f +0000000143 00000 f +0000000144 00000 f +0000000145 00000 f +0000000146 00000 f +0000000147 00000 f +0000000148 00000 f +0000000149 00000 f +0000000150 00000 f +0000000151 00000 f +0000000152 00000 f +0000000153 00000 f +0000000154 00000 f +0000000155 00000 f +0000000156 00000 f +0000000157 00000 f +0000000158 00000 f +0000000159 00000 f +0000000160 00000 f +0000000161 00000 f +0000000162 00000 f +0000000163 00000 f +0000000164 00000 f +0000000165 00000 f +0000000166 00000 f +0000000167 00000 f +0000000168 00000 f +0000000169 00000 f +0000000170 00000 f +0000000171 00000 f +0000000172 00000 f +0000000173 00000 f +0000000174 00000 f +0000000175 00000 f +0000000176 00000 f +0000000177 00000 f +0000000178 00000 f +0000000179 00000 f +0000000180 00000 f +0000000181 00000 f +0000000182 00000 f +0000000183 00000 f +0000000184 00000 f +0000000185 00000 f +0000000186 00000 f +0000000187 00000 f +0000000188 00000 f +0000000189 00000 f +0000000190 00000 f +0000000191 00000 f +0000000192 00000 f +0000000193 00000 f +0000000194 00000 f +0000000195 00000 f +0000000196 00000 f +0000000197 00000 f +0000000198 00000 f +0000000199 00000 f +0000000200 00000 f +0000000201 00000 f +0000000202 00000 f +0000000203 00000 f +0000000204 00000 f +0000000205 00000 f +0000000206 00000 f +0000000207 00000 f +0000000208 00000 f +0000000209 00000 f +0000000210 00000 f +0000000211 00000 f +0000000212 00000 f +0000000213 00000 f +0000000214 00000 f +0000000215 00000 f +0000000216 00000 f +0000000217 00000 f +0000000218 00000 f +0000000219 00000 f +0000000220 00000 f +0000000221 00000 f +0000000222 00000 f +0000000223 00000 f +0000000224 00000 f +0000000225 00000 f +0000000226 00000 f +0000000227 00000 f +0000000228 00000 f +0000000229 00000 f +0000000230 00000 f +0000000231 00000 f +0000000232 00000 f +0000000233 00000 f +0000000234 00000 f +0000000235 00000 f +0000000236 00000 f +0000000237 00000 f +0000000238 00000 f +0000000239 00000 f +0000000240 00000 f +0000000241 00000 f +0000000242 00000 f +0000000243 00000 f +0000000244 00000 f +0000000245 00000 f +0000000246 00000 f +0000000247 00000 f +0000000248 00000 f +0000000249 00000 f +0000000250 00000 f +0000000251 00000 f +0000000252 00000 f +0000000253 00000 f +0000000254 00000 f +0000000255 00000 f +0000000256 00000 f +0000000257 00000 f +0000000258 00000 f +0000000259 00000 f +0000000260 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000346393 00000 n +0000051812 00000 n +0000345241 00000 n +0000052032 00000 n +0000052237 00000 n +0000344663 00000 n +0000052313 00000 n +0000052560 00000 n +0000054327 00000 n +0000119917 00000 n +0000185507 00000 n +0000251097 00000 n +0000316687 00000 n +0000344713 00000 n +0000350574 00000 n +0000346911 00000 n +0000346996 00000 n +0000347380 00000 n +0000350688 00000 n +trailer +<<91219DA85D4E5240A4F5B4B5BE94347C>]>> +startxref +350909 +%%EOF diff --git a/content/GraphicsIntro/Input-And-Output.svg b/content/GraphicsIntro/Input-And-Output.svg new file mode 100644 index 0000000..19560f3 --- /dev/null +++ b/content/GraphicsIntro/Input-And-Output.svg @@ -0,0 +1,99 @@ + + + + + + + + source + source.Quiescent(TimeSpan.FromSeconds(2), Scheduler.Default) + + + 1 + + + + 2 + + + + [1, 2] + + + + [3,4,5] + + + + [6] + + + + 3 + + + + 4 + + + + 5 + + + + 6 + + + + + + \ No newline at end of file diff --git a/content/GraphicsIntro/Marbles-Swatch.ai b/content/GraphicsIntro/Marbles-Swatch.ai new file mode 100644 index 0000000..8488659 --- /dev/null +++ b/content/GraphicsIntro/Marbles-Swatch.ai @@ -0,0 +1,1254 @@ +%PDF-1.6 % +1 0 obj <> endobj 2 0 obj <>stream + + + + + application/vnd.adobe.illustrator + + + Marbles-Swatch + + + Adobe Illustrator 28.0 (Windows) + 2023-11-02T15:23:12+01:00 + 2023-11-02T15:23:12Z + 2023-11-02T15:23:12Z + + + + 172 + 256 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAACsAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9E+cvNU3lyztLiLS7nVD dXAtitsrMIi0bsskvBZHEfJApYKaVr2xVT0XzfNqehXmpyabPYyWlvFP6FwrryaWyiuyql0jrwMv ptQfaU9DsASwyS4Yk9wYhP5zdPPcGgM9ydSksjepdh6QqjSMvARV40/c/F8O3w13auY3HLm83+cz mPi8W11SJ84efZrPQb/V5BNHa6TbNNcQW7ei8kkcfN+EjFCV3om+/Xc0zb6bHE4zOQtzc+rnOUYw PDxAfam+leYrtIzbeq1wsyo1rOayFQ0qRsS/xVFJQylj7eAzG7WrBjM4t/ZupnkuMuYQtl5vgl8w 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 kX3mNILwXNxGxasfGRo6MEUkqfTpt12rvmxwEcfk6zRzvNLwhUeH8fahP0drw83LereoPKw04xPp 5ANbkyVEg2+z6fUk50nCeL+jTicY4Kr18XN6vopuTpFkbrl9YMEfqF68yeI3ev7X83vmknXEa5PV Yr4RfOkbkWbsVdirsVdirsVdirsVdirsVQDaDpLXYujbL6gqSlP3ZYkHmU+yXFPtUrg4RzavAhxc XCOJXvdOsr1Al1CsoXdCdmUnurCjL9BycZEcmU8cZCpC11rZ2lrD6FtCkUXUooABJ6k+JPcnASTz TGIiKApTh0vT4ZRLHAqsv2O6p48FPwpXvxAymOCETYiAWSnqOh6TqLI17apM6bLIRRwP5eS0bj7d MmYg823HmnD6SQrrY2K2n1NbeIWnHj9WCL6fHw4U40yQ25MOI3d7qFhoek2EjSWlqkUrChkAq3H+ UMakL7DbJSmTzLKeac/qJK288v6JeXIubqyiln25Oyj46Cg59npTblWmY2TTY5nilEE+5MM84igS AiriytLiAQTwpJCKcUIFBToV8COxGXU0TiJCiLW2en2VmrC2iWMvTm+5dqdOTmrNSu1TiAAiGOMB URSHOgaQb1bz6sglFTQD4C5IPqFPs8xTZqVyzxJVV7I8GHFxULTDINjsVdirsVdirsVdirsVdirs VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsV dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVd irsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdi rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirs VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsV dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVd 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 cvi675h+MPE4ujqfzY/MeJ0v9j//2Q== + + + + default + uuid:65E6390686CF11DBA6E2D887CEACB407 + xmp.did:c765f3ac-1881-344b-850f-cb39f546655d + uuid:0d7d919d-286b-456e-9401-a12695f8c978 + + uuid:152d443f-61e9-4330-bb51-f38a8e360e29 + xmp.did:f18479d1-4e7f-4340-b533-076a69bfb2ae + uuid:65E6390686CF11DBA6E2D887CEACB407 + default + + + + + saved + xmp.iid:14709273-42b8-4844-95f8-dd7dcca7f08e + 2023-05-24T06:57:15+01:00 + Adobe Illustrator 27.5 (Windows) + / + + + saved + xmp.iid:c765f3ac-1881-344b-850f-cb39f546655d + 2023-11-02T15:23:12Z + Adobe Illustrator 28.0 (Windows) + / + + + + Web + Swatches + Adobe Illustrator + 1 + False + False + + 227.366972 + 191.018349 + Pixels + + + + + Default Swatch Group + 0 + + + + White + RGB + PROCESS + 255 + 255 + 255 + + + Black + RGB + PROCESS + 0 + 0 + 0 + + + code keyword + PROCESS + 100.000000 + RGB + 44 + 50 + 200 + + + RGB Red + RGB + PROCESS + 255 + 0 + 0 + + + RGB Yellow + RGB + PROCESS + 255 + 255 + 0 + + + RGB Green + RGB + PROCESS + 0 + 255 + 0 + + + RGB Cyan + RGB + PROCESS + 0 + 255 + 255 + + + RGB Blue + RGB + PROCESS + 0 + 0 + 255 + + + RGB Magenta + RGB + PROCESS + 255 + 0 + 255 + + + R=193 G=39 B=45 + RGB + PROCESS + 193 + 39 + 45 + + + R=237 G=28 B=36 + RGB + PROCESS + 237 + 28 + 36 + + + R=241 G=90 B=36 + RGB + PROCESS + 241 + 90 + 36 + + + R=247 G=147 B=30 + RGB + PROCESS + 247 + 147 + 30 + + + R=251 G=176 B=59 + RGB + PROCESS + 251 + 176 + 59 + + + R=252 G=238 B=33 + RGB + PROCESS + 252 + 238 + 33 + + + R=217 G=224 B=33 + RGB + PROCESS + 217 + 224 + 33 + + + R=140 G=198 B=63 + RGB + PROCESS + 140 + 198 + 63 + + + R=57 G=181 B=74 + RGB + PROCESS + 57 + 181 + 74 + + + R=0 G=146 B=69 + RGB + PROCESS + 0 + 146 + 69 + + + R=0 G=104 B=55 + RGB + PROCESS + 0 + 104 + 55 + + + R=34 G=181 B=115 + RGB + PROCESS + 34 + 181 + 115 + + + R=0 G=169 B=157 + RGB + PROCESS + 0 + 169 + 157 + + + R=41 G=171 B=226 + RGB + PROCESS + 41 + 171 + 226 + + + R=0 G=113 B=188 + RGB + PROCESS + 0 + 113 + 188 + + + R=46 G=49 B=146 + RGB + PROCESS + 46 + 49 + 146 + + + R=27 G=20 B=100 + RGB + PROCESS + 27 + 20 + 100 + + + R=102 G=45 B=145 + RGB + PROCESS + 102 + 45 + 145 + + + R=147 G=39 B=143 + RGB + PROCESS + 147 + 39 + 143 + + + R=158 G=0 B=93 + RGB + PROCESS + 158 + 0 + 93 + + + R=212 G=20 B=90 + RGB + PROCESS + 212 + 20 + 90 + + + R=237 G=30 B=121 + RGB + PROCESS + 237 + 30 + 121 + + + R=199 G=178 B=153 + RGB + PROCESS + 199 + 178 + 153 + + + R=153 G=134 B=117 + RGB + PROCESS + 153 + 134 + 117 + + + R=115 G=99 B=87 + RGB + PROCESS + 115 + 99 + 87 + + + R=83 G=71 B=65 + RGB + PROCESS + 83 + 71 + 65 + + + R=198 G=156 B=109 + RGB + PROCESS + 198 + 156 + 109 + + + R=166 G=124 B=82 + RGB + PROCESS + 166 + 124 + 82 + + + R=140 G=98 B=57 + RGB + PROCESS + 140 + 98 + 57 + + + R=117 G=76 B=36 + RGB + PROCESS + 117 + 76 + 36 + + + R=96 G=56 B=19 + RGB + PROCESS + 96 + 56 + 19 + + + R=66 G=33 B=11 + RGB + PROCESS + 66 + 33 + 11 + + + + + + Grays + 1 + + + + R=0 G=0 B=0 + RGB + PROCESS + 0 + 0 + 0 + + + R=26 G=26 B=26 + RGB + PROCESS + 26 + 26 + 26 + + + R=51 G=51 B=51 + RGB + PROCESS + 51 + 51 + 51 + + + R=77 G=77 B=77 + RGB + PROCESS + 77 + 77 + 77 + + + R=102 G=102 B=102 + RGB + PROCESS + 102 + 102 + 102 + + + R=128 G=128 B=128 + RGB + PROCESS + 128 + 128 + 128 + + + R=153 G=153 B=153 + RGB + PROCESS + 153 + 153 + 153 + + + R=179 G=179 B=179 + RGB + PROCESS + 179 + 179 + 179 + + + R=204 G=204 B=204 + RGB + PROCESS + 204 + 204 + 204 + + + R=230 G=230 B=230 + RGB + PROCESS + 230 + 230 + 230 + + + R=242 G=242 B=242 + RGB + PROCESS + 242 + 242 + 242 + + + + + + Web Color Group + 1 + + + + R=63 G=169 B=245 + RGB + PROCESS + 63 + 169 + 245 + + + R=122 G=201 B=67 + RGB + PROCESS + 122 + 201 + 67 + + + R=255 G=147 B=30 + RGB + PROCESS + 255 + 147 + 30 + + + R=255 G=29 B=37 + RGB + PROCESS + 255 + 29 + 37 + + + R=255 G=123 B=172 + RGB + PROCESS + 255 + 123 + 172 + + + R=189 G=204 B=212 + RGB + PROCESS + 189 + 204 + 212 + + + + + + Marble Colours + 1 + + + + R=255 G=139 B=139 + RGB + PROCESS + 255 + 139 + 139 + + + R=240 G=255 B=139 + RGB + PROCESS + 240 + 255 + 139 + + + R=139 G=255 B=188 + RGB + PROCESS + 139 + 255 + 188 + + + R=139 G=226 B=255 + RGB + PROCESS + 139 + 226 + 255 + + + R=255 G=139 B=232 + RGB + PROCESS + 255 + 139 + 232 + + + R=255 G=221 B=221 + RGB + PROCESS + 255 + 221 + 221 + + + R=255 G=165 B=50 + RGB + PROCESS + 255 + 165 + 50 + + + R=67 G=255 B=62 + RGB + PROCESS + 67 + 255 + 62 + + + + + + + Adobe PDF library 17.00 + 21.0.0 + + + + + + + + + + + + + + + + + + + + + + + + + +endstream endobj 3 0 obj <> endobj 6 0 obj <>>>/Thumb 9 0 R/TrimBox[0.0 0.0 504.807 191.018]/Type/Page/PieceInfo<>>> endobj 7 0 obj <>stream +H; +A >6&dzYO v>QDV+ +2;1߰3*J)մ4jٜ4 (Ƥzf2g$9+|B\ +endstream endobj 9 0 obj <>stream +8;Xp,SI0lJ)o/B03gr02Cr^W%2VYEF85mQPSDHc@)oHhULL!E6`YGp=G!,Jk_ra$! +fRA66D'&bm'2h(1$V/%[#(Q^SqgFl\~> +endstream endobj 10 0 obj <> endobj 12 0 obj <> endobj 13 0 obj <>stream +%!PS-Adobe-3.0 +%%Creator: Adobe Illustrator(R) 24.0 +%%AI8_CreatorVersion: 28.0.0 +%%For: (Ian Griffiths) () +%%Title: (Ch09-CombiningSequences-Marbles.ai) +%%CreationDate: 11/2/2023 3:23 PM +%%Canvassize: 16383 +%%BoundingBox: 0 0 0 0 +%%HiResBoundingBox: 0 0 0 0 +%%DocumentProcessColors: +%AI5_FileFormat 14.0 +%AI12_BuildNumber: 88 +%AI3_ColorUsage: Color +%AI7_ImageSettings: 0 +%%RGBProcessColor: 0 0 0 ([Registration]) +%%+ 0.172549024224281 0.196078434586525 0.7843137383461 (code keyword) +%AI3_Cropmarks: 0 -191.018348623856 504.807339449542 0 +%AI3_TemplateBox: 683.5 -384.5 683.5 -384.5 +%AI3_TileBox: -168.466320512286 -393.109180415444 673.393659959146 201.91077808065 +%AI3_DocumentPreview: None +%AI5_ArtSize: 14400 14400 +%AI5_RulerUnits: 6 +%AI24_LargeCanvasScale: 1 +%AI9_ColorModel: 1 +%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 +%AI5_TargetResolution: 800 +%AI5_NumLayers: 2 +%AI17_Begin_Content_if_version_gt:24 4 +%AI10_OpenToVie: -229.394495412842 132.724770642202 2.27083333333333 0 7744.51376146789 7956.77064220184 4318 2607 18 1 0 89 170 0 0 0 1 1 0 1 1 0 0 +%AI17_Alternate_Content +%AI9_OpenToView: -229.394495412842 132.724770642202 2.27083333333333 4318 2607 18 1 0 89 170 0 0 0 1 1 0 1 1 0 0 +%AI17_End_Versioned_Content +%AI5_OpenViewLayers: 73 +%AI17_Begin_Content_if_version_gt:24 4 +%AI17_Alternate_Content +%AI17_End_Versioned_Content +%%PageOrigin:283 -684 +%AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 +%AI9_Flatten: 1 +%AI12_CMSettings: 00.MS +%%EndComments + +endstream endobj 14 0 obj <>stream +%AI24_ZStandard_Data(/X 4 +&6@=X}?;`Z[ȕݽ4 %EE9 v&"$B띬΄\ڋ0~;)uNhw;;7_[իɎN#͘;W5F;YG+^اʂ̑Co$Ѥ|Ss-|256xa3+4+1QIh82tÚfC|!ձ4-{b1i'>6(G_)ghٍtѐM ڣf:hh͸uDC1qKsj1yщ? +_Ӂ&Tr6؇5UK}5.U7ӑ株bQxoX*W6Ra:KmO2K.AɛJ1kLEs{U$ԽJ\4Kq#D{1I<|sxUr:7f7zlwO:|ځw+I'oiOo8Hmx,˩JoV'tõwSu\d^8ܶ|';J֍sSdS$$"Q9mjէb@?0 +H 6h Tx*``*h[]<'VD\i`Υp.=MT>VGqndI'd%P&svH“FTӸ7(tj^? םʧC_gs4҃ CKTdF< +);DwFp`" DH0 `„FXH\0T@၄ *DB6@(0.̗f~ή={{^^![l/Pqhh`hXT @`_fѠXP( _N gN:ؠC9XcbPX"2 fU"1 2ٸ\Zu`4"qf@$JH,rl7LC! bH0dxE$H0taH`2X@!ܹf팙SK7L"D15D`@$. l H T"pDajD0BC - +*`zCH,O4D #ݰRO} dHd@E!]9WDE$\DЀH0,(G$ˌF$"` {ZbY! B+# )( / 944(fA`2#)cTF&|-;2RhU8K٘Uiy.üe a֋?WD=˲'6dwvG:_kSu0WYOfӅ1e (]^@$;|Z@$X(J 5"Q eĔbPDb D R( %T  xд莈x3d 32|߀ĤOxxE"PZ(̨\S, p I `Y(M-BÐk-iT[23 H$ DB*\w2D =%Q͉Vv1B@aHh`X ,|R(ljRП1[fKfPeƌ2Wٝ5~v4Bh'3NsagExa(eN#3zâW}ʆB_iumL6 FaFtw$_f7~KU9 ,@$,p`1p`1ã 4"hPTB"hX4,, D"QHZDst. Xc_^"u 8 +++D!uh"Y$"Q DBDD`@$HCBD$HaaH4L8L^XtDL.*L: CUUUaarQ P]a`,C\D$ly49 ]P- +D )Ȋ + ,0,0,LHL" Tb@$+X(TEm#:D"j(CUa r ;ԁ fp]¨ G E cdPzbL.:mr|Fo^0ࡒ H:)hA @ @S-UVKQZ(j(n +[؂1X9"1cs1. 6N6DS\HlH>n/ w(0)P+bAhP8(Yt-ja["_(‚aѰpXB@ `!b0c@D"Π`X0,0004080LC@ ` ׀ 8ppX8,8084Tv HDTvW,!G$ʡnX@$D"0"aSa:bpH!^hX4( lkkj7aApd@c0b B‚b p P @ .̂âaX,/rHE-jA.f`P,( +T()Q8 _^{Db ɐ HE>31/-+)Il@\@ p pS::_o[ڙYP P ˪՚r Z("2 9Aae"k0 :ء:D*2Y<̈ءmhCH,,WQEQD:4,,f1 +DQb67WA$HPX8`T"b@$ *0$@ 0x@a `B T`A *H@B@X8z\*x L ` &l ƒ 8>!L;. @pPq"H3U@A H q + D8D^:WvtUe~*t2kc+ɫ2`RI&ƒ H 8(\8KeQvƌ 6``@a +Ta0zEIHJfJ{eFee t@„@b`HP @ @` 8q &X8p@ + d5@a"q&d0aa Ў Tx aBDx @D@x%`H !4`O0.ADB "|(,`HxЀ (LpI $Lp PX`"1R@(LB LA  +*<@a 1B ( ` +ZOrplw3 U2Tbѷwriv+p¬dǜv5V1wfi(i#FucQN$4z==XUs2"!'j`B@Wcw$4`$@x4B0$L@B$p D D`pp02 +0h P0*Hh=\%^Oz|OSiK%X I-fYtؑ=S/`'YÍObď=~X?&>38/_hIR +aUG&&3Cr]*Y:kňRxKKKĺ[(M `ya_7n?; +gDxrJLWY:SpJ\Yaw_ʪ?My?|렌I?(%,_M Y{ZRe#=;7+JJ|.Ndʤg4>(Cף/}^Ûx(:v~XͲS%exOr0$e9,vV#eJ$uc#~(c/},kלMʝA񇅓A_Qev5.R}ʖ׫Ϯ&MӨ&oNh$ffvDA;t_e%Ҕ\fI#XSWOw”?Vݭ>㺛 J+.^!x6:%^g2Ksn2R[=ߺJQ:lĦee'Ͽ>^OW;Ow;B,:tDzU^{p''ب-'6R.L!i#ғw;+']HX"Sm'䯽F<4Vee=TdZ;CxG;++PJFGz/쩔T^"c0Nh7yFv;f9uyv|8v/Iz@rt4e9wP<$Wb}O82˻R강w9kjL:|ӆ/.TԉZe]iYLIL< pMyȸ?s«CJz](֧<9pY5'G!ه֪ -MWX=WntUZWg8Iu<k`mZr-'ϒ}l)v50+ԢKV5 #sL(X'seˑf%E+bs<SDgULRԿ{eU,yOAJ7~)1BwB/˖)DkӴ-lɛ}hU]#7Vgr溔{[̽k|DUT;#ڦGijt'yC2*VŬND\lL JSY,;3bd㝏#IqV,-46(;x;I4f.m;حWm~ckWO?D?x#K;fSN{iH:DuЙٰP-SI^Pv`o"wRa`;R +NZ/{2qT;E'r9H2@$+p&Y_8e7~lGhcou.{a2{fnEfvmtW;I5:0=#{9"cTR2ƴBבwN:nuwb?if-ֱE9M+n֫,Wf٘P* Jog,2|Ȼa'XvgVyvux_D(5M̆4i/Z M.8L^<,B{i<*Q Zi8&glZUS**hsT'FeόeECfgxU4Z;;5ʛPOdgnNDURYJRFd2=Bl*QΗ"swcvKG( + e4Jx< {òX:y&S=QCΚ؎9z 2ߣTw _㪱N:XQ/jlVYNÐrl5"bqiSU:INNTNּ:"\Z*'l'auHdf-/IjdT]7{4jFQb(,}c_aGJ#D2:3%[m~U$A;H¢QJEʥdW.UpxW҇MUS9guAZǺliCDUt]|PhD)+-фxO2ʼ݊N{Ej/FmJX .}eJo9GcqMqZ"K+ Y<31gd#zF2[tҨ,X_7s2¼^"2zo<ª$FiFbdbf2*o%dʕ ҊGvFKլ dW#3%M'f{Wt:IGѿ4^9y`OMstt yTGzݤ#FbY/&qՓs4eS_B8N5wb>UeH(f2]JU7SN^>z+d?cidZ؜:L?<9& g^'^YwHu9M37o75WMc,C2r|c˾љY6.f,Z_i] 5 K3c'iꏗ2bIzI.tkx~ʆ +w) BY=<ëX ~N$M¢,"$'IuSĪtgp˃z$O蜴 dJqr9R>X(tܞ0,wq3gR k$sݓuZ2eT**`')hl*;n})zҡUULOWԳ+0Ku>ZzdEht%F}ZR_QT*n}kOsؓcd"2~7uH124Lw7wYlJGW4t%9egʒ`ɜsUq&݋kdcIkdy"ˌ!ٱKڸIO9mI%I'͸ES%IWHS #ӎtߴ<1S|y(CgtU܊9oi6%)X#ꦙ;)DK"Lf2I3q JrS8VѲK|k:({>G:t\])K9ҌVKqbq,w&q3Jsex Y.7Оןh':/(WH7v"_u lU.x#oΌ7bd'WrnS;1UcַYM5Cqj}iO%9 f(42kRw&ZSL+38&Lՙx{RkE$}.gt2޲Pj/cHdDB9*3k&cҧ%̬FeUU͌, Ḍ#62TBNHF&K'g̑9#ilG +VXlU7ߦQTN))駪h^EEU,{1Fcn%F4;C}J\*׳єh~r*]&^3Zf$3$hIȦBokGyzk>y{v9P -{FÜ2ۘΎ~>2/N)IYKgc ߜm~s3\;Ncf.Kt3-J<4LC;) fMԋMlTEIdo:bDgU˗j%5-Yz;\GXy)_](O{;fdU1ܩ,X'zUUgU|c;M5A7,33_L91JNo*(_VيJTTxUT=홡YOx#g^j[TO[)ox2M?LI[e}5K&&hhdhfa +ޭ L KjČd. -IU;LbQ膮P4 Ú+aG/zjVW/? iZa5v_K4Zodr›˜VNNwxzhv&#w…Dykot7yIwj?ӓQa&ewTº0H쐎먇u>A,3ɍ֛DD&)-I2UTl*fJs!".$ԃ29.g]C9RJ!Mʍ51̸Jn9r_+qQ~E%cɺ3 "dВdJ9r$g#MNPn)F|yWK$iҪǔ2Kٔ{RV6'eccfgff5GbvdU,cYXYlb3&VaJlV.fg笜=>rjҲԙLNƔVQzEҤ,7ILlr˒:#:rP<7[(\K v^X`ySNk.S<=(x6-#Zu&3< +\I:JKW!J dI%ǫȱQL2GDkUjRPsqQe%dabe#ZYn7/HIb]Vʩ\39(gP7WUؾ7L4`w3fkbh<+NUӌlZ6Zޠn|S,Zs)zl JR φfxig$^'z-a]b l;7O̬OY,!~j#^++ɎWEt}kULϲ%Uf̫SW4SLyu|GsI"Wؼ5/Ћ +ǥ 1C(r٧9G#KQNQf_;V6zJ&;&eUD<{뱹׏/s氤_C* ]ݕ>EvծLv?^l:'#}F>dssseƜ͎LGcsq(%KZ}5i1m6%njSidtt4RJT6xVh#I,͕b> y#;H464&T$kMMK\|ۤa5"!QlNFx^vewjlWu5SRM!hZ}W6W2ȭKhr|@i{ޒƭg{ Mίpx "j>L1AȒKyK9bKaQ24qlrjT6i̢LR٤TљqĮ>7;e% -IhysB;^GgFZ"ΙU?ѨUnfu7ڤȗ s\g39q9)#$"x4{> +'+*:TH(Ĩ>J;2kFh ]o|vSw%*!Fmjus+FuRΣmcW*;XJ,!A<_~zF ZyUHeqw4M'FU4R)V}V{VDWVZ83D)4Ea]T6'ՐeNK$ϱGu+NvʛwD*YٺUGh:5mN$TߺQljDo.KI)>I4t+ShXS,먐;;ёdLz涼YD9gCɈ;%([Z +cho-8bmGS0IDV॥!A3,BmЃVӖXT` RhW2'VX,M[6:#+ۉhzGq޻v^Mƨ9$]wBuI}u1q] c%ީ]x`;X/k"Pߍ D}«PmSh:DJIUEo~R U3Oz(j>ȷݫMOjԢno|9lY n^K ? 90Dfw!ʊ|b*v[X[ 2oG4H5 `l3]T.g+M.%T`@>D8̥j9K(0'&OwHzfcԃؓV^U\0kw”+0YbVa +{ \BZ5kQ7aV›فval !^bnB2X0:h{s6j*cbŃ^쯩,%xO 4SX&! +KDxywbM1#g[/ЊP1F'5C?n\;\*0^oա6g,b^Xkf:K褢G`Tcnކ]̽*y~Q*%$eȂx2NIf/f1>_&jDJEaYu\1P[XWNܯ{pcC1.6Y /sFXbYPy:0VzӷFUt΃IP/ Ӧ UlVPRWW7+xLKGq.]ӏTj'n7857]8.8+)Kg p<̟;Uᨬzqbv +;8>lg?۸?Wnc)LXaXױ',đ8wRKΰm kAma^qd)8/)Y!GKNZNq jr1VcspvqQ, $.Ow}n ^x1?u Z{m1٠;:cYbvzc]ӄ2 ّ}=˜î$YOgK{˜i=b[íoB: azrዥ'4$\v|ُ#j%r5O؇lbȼ!asqKŤ_;oWA!3i}_ 0ӅrY`ՋEp={{sGA`_̏j/n58/@>GbȣbL`v5 F%vЮ8P@w+N1hŮNkVb/냣_ceJc<0~-$XLYUZQDz1 MW1eqWSX0DͺIE2%F͌VJP4&,iC7o<ہq"HKb|7dO:=+eXεoofq02 |cgXc@o!M~0VxQq^%&Nb|#Qs+m~!Qvj< xKCl)nGWn&S5&`fc 4cq X6϶w}m3X:׺Ts1q"YpVh?] n`wAfX%*Bw\^cdLG=jƮ-ɷQcyJ%3;LvЛ}3JtDjɾq/u2/Wd~-4eG+K8M-˧rБ2C +%~wt=&M>x縗RDA1mc!Yy) TgF\{پKҜe5Շ_(@V +N ޼ anp/\h$Y&A3DžEhhÞ ~93,T@g}ڣ]Sό|[ٜJClSy?K4h)ABH4 +NϞ0B[lY!p R٨\lkl[33SҰ;>x`LC]n5Yҁ4cx5@سEOoO&)P73Pн  +zq3yŲ^ +v)(*@ӂv"m[M=4!(ʂ6/-X"iA'piΉ.vBq-)G&F7e 6[iSMꬡLfm3uA#a3Zp-Ct̨35 OaS~Rε0j$!?~B3Jj^:?Y6@3 ^mwD̠ PI< 7^Шݥ҉)@סݗ -u %_ƪ;E1œѮ:9EQU> 3"BJhvc$1~5ib$GYlڶ6 +Sm'Ze_<t/]mEnσ^(TҚϲ1JCzbwO?p 6U YCm +MP0:m-Sż/Xݼ-I*ms݃ AU= +BraN8bo}6 "?6C؄:&橗kb}&e =m!ڿx{Je[&4 qjxKx 3"-mro&LQ&mHdXXB8:: ?wxbROXv03mPoҠH 8 0Wr Cah:Itlt{{HwMypins9UPOG29cf)v͂KzۧZhX^VG@QS&:ùpfgۥ6O^m{=oL=wGɍ.i7QJ vP{iV/LHv0Zl+pI08SN+vrrl*9>/^5ۿͶ) Խ$)bq~`+(wK }^Eam>)df;4W$vpnhwHΣa`f=ږ;[}inGjKCq|9Tcxd1!w;j,z xD1Av{ #}2#7(['%^$͜}ϞЍ#1B+n/{@ + NqG,I\61,={ˋ;/G^nms|6nhLzw[~Yv=Rl^;`cWCqߚ,6a/׾w|[eA{wK#9ڻ[x(Eƻ[) ĩ*ݝ%hS GG4n'7 BeZqwg{P==-V}GD<%MRFo2+Hxq?Vw7ӴݢF + tnnS!Lyy~56A-}H~u*=M! +ͷx `Jy93;7D{vzgE("bK;[$TO7o-o uJȻe܅yuT +*{ț>p*A2o:2mSqU} ,FzqcP]Oe0.ԁQ9ߞlcqE?DƼ;%9 +*wJ"ЕY&M{ou]3NO:P~:R/-TKNC$2"/gN<{ 1A𤎄˻Bz$pJ6vO;}L=ַ 4Y%0@;HL<  [7g$"]N#?r|`VvJi<](qV.*d/ OOw&jck=oFMI𦏝K-ɽD}&(Kmr̄%3j9||T.wY/ ?]xNϓ\73lo4sPnH-ۖsl3u.;ڴCŋ#]:;W^ƺ-;Y[|WE׆VX2?bG(W|D_һ0a淮_co9S;i`oz8ib @ʦRG7)qKtP $frszΑHdK3=pG]/vLd/Ci$ +v…}ƕIwΧ5{5nLGxpWۣF?k܀/:hF@YӾ# VZ(~ߤ\UIAA6O+*'t:>Y n0ə;B @G>JmܢG{:_4fgIvϾhy2F2/4ьclq:8k>;oS tIaZ+aF~IHՙ,T?D7Ӏ.Lo[=Dwm?M.,~R]&"wF3w j'gcgc.2pLOП?E1"LwONHAagW; F ?ô\Ģ"bnJq$:~0/a0]R!N8e\Ħ @h +!4iXx `Y;VInؒ-50lj{l`ZEEn_vFۦ?/q +&h:DFf@/{ZTleՒ4rRmȡ܆?;Q5B3/Ėhѹ*&A $k.= '^VR6Xҁ+5p?P4x!1ŽPkgE[׬)sFxShm3Y'zB{p c?1"{Qtѓlז³uQBܵ!^рKͣɬ-VUOzi O1P&G ݗ#7&)V*l-ހdE69EN2b@F~pBO\dJ[Mj)?U..l1Ɲ13,T +މo1U1p0wtI14y`ҐYCY]6~Bg"hma+g|v.@VEUFKTg,RG>Si`o*V̼ڻ")W` {y^V.^0U"B L Ẍ́T(WJ}+'UW*Ͷvrv BTfؘ mwI@nPq.h_AK~I U/= t7MA=zj&MWbXW m*5"ҧlVZ' #X~uZ9(լı;"ir:8DޙPr(r25Ŗlɵp|f@G9pzQ?"9z eІ9D͎S&F9UU`?L6 Au;0H=N\©X՟#ͻEՑNGTd +H&z԰O':b Y ҅tXg4%i4:{WS/"' Hsq=K̂Iۧ,jmDXJ"ֱaQB\uOIV^ OLw~STDd<ʭ9 A&IO8NrJ#0*jHLh+u @48Q\wВD`bW1*I8 ]db8A-T` Nlms=Y b l CHJ7A,`S>z1!BSsW'`G$ى7yCܻގ]n0fIP8~"y_n}):9@Qx=w!ħbUl.pY3a|fuLu[T=.|#;ӱ_ƃP:`#]wM+nR Q@LǎoS@HŒDc!ELJEW$!+Sw,iu>c?wF?&biqժHΞB<$]v1G"Me@z]|`VzXiakmL;)BiXj*i0 + `@}*E0a9EɤOр셄,x$STr>'ݾ7KUưsJruM)ҹd>Wķq @B1' +FnN>^}cM\j: +W [ (C5GoOr[yD[HC7IpNh.RLO +0ͽOh,>2UC -t^~l:nhm1q8νRG´ u)cG XFMղI1*Z̙њ3/y =\38.^Xަ_r3"UXUQ_GSYzsVsa{.Th;;TH< +BLBZ݆>%&*e1/{>޽ V:`~T⻣#j `ܭou| +E)VǍ-r<'h('X4\&{`5k1KEf@'XN~iW~ +ByyӦuY SiԱg#>? ?ԔOZ~GPgSlDHzCw\򠡎dS(C %60v${ZN%b1.nsBOeοXGа2)Z"RoߖeX,`rv>R +r #c~Y4jex,qs|WHWUIlN9 Ī=^D?2Qְg + +M!btJ{G/&cC8 |A3XW;1(H侭 Kq!nUe+dygsTZ0iElR~\k|e(4hBܟGn*<̩¦fy.L!^- αWf~Ͻq$mǝlYsz;[ +`a[/ gއjzmԿ,g(bVAo o8=h 8Nq>7!D:k5`dzz4{$!TIJA#ࡹkgܽuK>8E뉘}ςބJ7zlYJ]eYYiP. V6˜XAa?O2aqG +x $e[0Z\#z9.`?XGq8\ep='tn,#M_i U^x%'YFl!ەz]>>mxdѨsOLuaŰrlNjحwkq885Z;zng)IgOG,|n݉k1O{kO lA}\" 31@XդOj@[7NRJ2%|O( . ֓lrr-k9> $*<A<>"d= v[$ %KRʄ,LzZؿ0F;)ٱFу#X'I>%TW`G bFGzҲ2ADq*r*uwGoCAO]Mk%U-:G] 1`WjӟyI9:YQ[Hdၝ^[i#8F$رN4A*OG%){QM! ]-Lg"QVi!,%r(^z}EUݿbU< ʚEBiߖ`fEĔK^d{'@$\8\78K}iY!IUŸRr]RUS7y%L\x‰B. +ZFeR +Âz'Tah}i:%q,@#x1Eu~YT` H1&\ + H?.COL6Ǎ#$ FLE=d_ĉtZgyr~@BKSh:]=o&w6CBK]Ynmn k+qNM)iUStsS <؝]7`r2OzD$TҸ%(p$*s2yb")'sE祸'szB<_=Y084ydb,]\;.|R 3g?9ܼK @0:X/V煉B̖jW-*p* s-i۴~t%$} +k}e !xB(䌽M]FcZaIsú9HQ/1\1F!O>Q腁 ?@mO.ii]&Z.LB&\tKhhW/8=ĬcVcߜUDZu= DG@RWKIVu&ib-;pSܤsa,y 'bPBnZbmMɁ=YFJSHwB<І t[[k " aslw䢇ΌIw`lV^I"_meAu^`uQ{ I~] hg4V=Q!W#Vö޶l.Ho|樫'DÎEjpdBRLn̜o *^z )eW@ Q?Q(MEGsʔD.#Wy!Mv8eQ% s.WLP: 65̑5n&D nwIv1:p%3rkp:ϕdUQR|/*%N{<1;cX>VƓvQ,8h-`m@cW9"xY%gri + ,;-(JGh5ѡbKfs&.ǑJ-$k : 1RԏbK={["kwH߀ʎXi3h~IDm$1 +C@%PF%!mh_VOk63>fj;Q+1)ct&)uD<#I%'`iޗcR2ʨyM^ ?fE);JidD7Ø$p9ύQbjiB&c9fənX{!4YH̛=#S==n6wS +(\-.S&ZJPI ayȐہ A4\61u 4Ԉ֣̪DLʒYAIQN=&x89 rm!,mU`?+'NHO@S+rCE q9$u9IcJs#~ +Ao ʸ ; eͬmY #gI`I% vo<$>fAݩ?4DU!;]`D5;$)?V5WWHr꒧P1.]m__DmcY\帰 :pl_p?!Ϝ &gTԈB*D   ܐcD44DȲ)<'! eKtמ4K2LZy^zɯ_TA XD}~[ ݺ?BݷDokMqNħ,:PTDSbjy1r懼X{v1D#`bᴍC hLH ny-4_ ;y_bhVK)Szl.Im !L?CTM ۲CMe{Lg{f"pr@P[ۑ,Nb]T0Je + K H9p~݃I|pLk".3` o..Lu+/*ۥme#XA$w>[cr2U +?;LچXYPI(VikaO^MUȽ@L\Y6`Sc(oA\ߑ"dJ~?m+͎ ?&Yڬf -f'uNcAgplХ|Φ"qBnl>" %DX]|`^4|z܈lMQjtGb{t +< ęL7BXw=/#*#VYs7Ym2A7mjh*^\$Ctꊭ%-ys-ĥ!e^ႼN$f-Y qSPY[Md7 +Mr iн4`? VOT 63ӯwdzǨt"ˏ2P +2\?0e/GhE<( ~;C;.Rᗳ +G-8= ԓ5mb]95 +];o_4*am_[Rl deYv3ɜ8M ,C{+1 &1ě)/Z*Mo3?["),RR7:Lߓm?VXrtMِ +9?aQ1<_d4X)G4pF\p!ׄړM6jXu!(m Ӵ2 AUssw`l1K)6A(h}7e.SFRԧ{ +Bb`>٣&e蜤 5kLM5o.=s7IFl2S;4IOBN=뜧z|yBkFcwޜd/2xwU;%-1`*inf@ +a'G6>g~ar*VQ>2,GE}RȌBnit,e7rI׌q1 ˝bI"s%rmS'8^ؕI%92jo:6yղ ]!zD@^ت=5#ߏQPݥj- ~_Xz-=8Ec[9K@lG@+rXŌ(F7#jb)%n{NwBпޡ1K]ƟgKgR_9$3GscVMhJƫх62+e*ULArvi.JM3CX +v@}h4Kƞ||T4ȷl\ Hp4kA*)"m fޮ׼P-lxyֱX\6DH=?=M@HdXxVTq 5 21_'aZa,s N,>5bl "OM~&oN}@n(S9!^d$U`{ȍRNZĐbP-2@k*96,]8 n4 uk!G1I^ +SKf@I ĞhNuXcJ+spE6e[,V h]NfL!$'\hN3>5'Đj2f` mtPv"@|廛:"*ώ<C3Ϊ,Nq9]r& : )7rqρKiRi:bYܥfTQj~9\Li#KMzn% Q܄X`[:; )I.0y* >EF[w>$l%0m$NwS<Zn` ̔GfH&)HXL[Gg?n%G% kl Lxd#˃h=ԧĎUkEBPf #=#k% eЭA"-PBQ\9ۻ54hBVH-$:.+VnQ  Gk%1Xx-ZUwBY,C;K% u;$iпGHH~}QPWAt!:ȷ?)Xj(V$J,ɭ odw!HO+!$H95 ̀C ѻc 5GGl|'Sxԝ=vt4@GFA7Q浑 kkD#eC'3"`anP7"{aF̘l,8%HWq.h;HW!=`H$9D!jba!D(ozzv>P?+69LEqQu-$jS!}ZKT)$BX)wfN kfl(Yu,5ؐ%|M=O?b!WD Y,rU;bZW%9D&6@))o@NGn@R$m@˯yTN,O(><>F8,V:'ΚM1tR{$>xEj2Bz(ΎC4 2Jk'zYmX wZg$12럫CSBTuO%̮Ȁ nbC@^gėBBBM:V(DJgZ`P?jQioGR@gdb2 0:,TY~u@$Nœۧ%bDeP3vqLN'x ?!!}X@/ e.~tQD@F z$ɝ?H.>a%O8t&gI&٩"B,1SA@E*FBun +  ێ.bxcѥrM}8 +U3=W LsʎyAL~ߵ(E3Uo@f2%1ad ePލu(mɤ taEłZvNŤ#Yl` zoIC9XmE*%,PPNѐRW;P[:d%LKoHmEgL*Ҋ(* (}5_d`=ڪlԘ'})r,CT3Xq)x1, @&qsx4]GIv3S!UiL\S9r >m _"5.כh!+rWl垔 |VE"{J'J'uHm%t$펉TWnSפ4YQr!grmɄϥdށIc2^: -M&W<%Z4JqO;#*ңP7'eҊF OݗiWS`fZrhp "78kEL鷔\D;I@~ +D +gT ?X yQvH_plNJ-p) ]c,\MD`#Kw?X:v;;Pg%K`j=tXmN{"Sku~!Kw +i,̚QK `,D5y|)1ͺLۡvC<1 +EH*D%.F)XXU#&lhͻP%Gsɻ]/+E|Sn/VA.*_ ~ л7q^6z\vݭ ̓$}P1Wyj @W(>y8A]tL 5XMqX62S"yj B2  Tx^aV'y@ +F*fB.ņ.1Jf[bm- ړB#6ӺrpUwB2_@U qT yǃjbADW_؞z7j"ȁQ kv2̭rra^]KEN?Hǁ;mrlR~I"Stꔹ8?0)03w +sr{nd!?y]Yn%?RPV~nıA:9h́pK@9ADܛPʏd \cŒ00 ̵PU;~k#-]d;s56  I%ȣPhLeucnѠ[&+7s{aa[z:uLXA˘:8L |fD<1 LHaj>"R*xNπg M!Lxa*m2 0?1q1>[|)ʌj%?Ѽ]h$A2]ƚ5ҌNu|Qq. +! O?>I^fT3x;uE3hl* hZppt Gf\OBk䉯cd+x18^d q"?jxL<3Tl##'e/ Qa^6vzi?–\SBH%;6o*/N"Pm OQ*PRyYzP9-m1']IBÏϛZ ' aM(y'/#l!\ #-Z|UK-ûxY=/v;_֦t) ~ $WI0F r4Ca XAvI@1#3KE/C2dqF0 *$h՞wnMg ֍wV_GɄ1oQ[tċNվ=F(Պⵙ#aD~G xdJjz̲`fnA#`X% ,~p5j20iYQ˿AT٪/$D}5m&YO$0DfQh,Lڇl:5fOTh^$<ܗorZJeR9L( 7R*D@܄w2@m7$O +1)2R\&VD42Z)5.U!3O,"l/MWRpCM.8TtN(&匎PERnE-~2ͩ1R! bd@*GbVIW˂P, GLy*d Ҫn*N{ҩ$dxt~Ӂ))\RVF" OPr +Qnx֭4!$O1X43v->I~ BF򃏿9ǖ$AUJYA"0Qr9Ma;\婃B$&Np- |EʪN21SA-@A]@[3VxZ*Q6oG"eD銃BɃ 3i`HXe;8vY!b$i^YbiT2q|1t݀{az<ȓA-er+ݮ9mjiBi9WcU{y-4e0y|-B^Y=zډr•AR͚"F6@@58)j#py"R@9`_8RɱD vCm޷X,-遉0DYrѮ|df/B>r'݌)VoU M^bf-k6eugH&dO^dDmmO[,}XXL4;?H)uUND6 5?WưVP P:.UY12VFW{ut3ʵapSրuӆ_3Na̪9N\[2I(Luf 6 ûP]]Uo@PhXbA +05+\NO\R-fKeVi-գd2iL4T?$Œ<,+9xe*P|arF"T + VJ_-3 g\*?U7[P^Դv{2]2,n_\P7qSnKf!̔<) JD [C%V{D0*xochc幁ENBRA`Y( n@+r'RD95` [p,_ 8 -sN.qLty%6gETt[AXrB9py8 gK>&a$^P;>' +`qƱwЬai @gN]jҩzSx7+Z"Vp83'+(@ߑvP0ɗ(/x1Sq JUrRҿR\9Cv4GgQeY ?[, %(#_9PmZ:kdLR,%N]},jZ-! ţ&d\5.ޯWbjUT 4ji )t[pWNwx.xm,)nT +=Y%'$ 64\,;@^j}XOKFJG0!Xlډۇ ɓdĕ:F;a6O?6yTd|Q+kD_­ЧxyWSNou%5Â+x>r5qwq8F|?GDPI;撕0VC8&6L@(8rciؑ!9j' )̟Y ϳlV\{S1ͬ+KudW/bf/$nE+D Kk/oGٓQ2+rEZ)}RWb0Z,"K2U +$zBɚ5+9К)}umZ) +fИi= A_8qL$If?R";n `Q]kMWMwrǡ׽BNs7luH-Ұ7`=9@hI6=&#tԹ&^9=nyXJ˘bF;sݐ0-D enXBIR]uXXt +XmkĘ8QAP>qP/bFj3qlF@I{3K h% Bx^+?L5J2/opцV&_Vho(>/ ?<(* +k +>(LTlF{?l5)64=@4EjftTW9b;'$# Tg% u"G  Yl 6FC(:'[/+a+4 q6{  awigww +M!^' +k\']&:JU>GE7(Ӓ=O3 #cwYd8!R~`K͚%czʱz=r} Ѫd`. (Ó!3n1",|un-A!Bf\+|fElwo'g8 i֪Htwꕅ q_[7 ދF~%ٻR'ޓ !~,̿DMlFY(n^ŊhzNoS` < +rK[߈Co?wd=)*7oψPkЗ1iJ^<= F98thbQV1D'viëZ&Pfᆯ|䧑&^hH#n4P,G.e;s5ٕ +9:I:hh'bDgI";wNqHd"2M(.'g0|7IYIEr'343=+_zYL.ɫ|bVKuvפ4d&V*٩klni$sk2#jƖl;f={rFlF%]lY_Չl8r-[|folɮ|lEҍdJg4x.񖘡cC]墲\9u=KΌRST>fc㛛#9;kj%>n3;Vlj:h=]]|b+[hlj1aYD!%3Lvet@ɿ_̯k<:߷6WIec_~VнɼE%YUZB Isue!5Eg_t^֡Nhc٩-{|CTuyhĞO#E|IS뗕QjoZ{W|'SSOx=ԮR$Uꭌe-ȒrS\fbe~fޏ7?Y- 6ue9>i_Cg\++_RuT!|XWK;$#ldbKjl٬6g ~WGw`\=!:!9>PKFڲmj/ eSSB-&;t4ǤCN*T[]5OĺӦ E-Je_2#Ɗ#YjC7X%>hWt^r)Ω/wV]Hf:!{B.$܆pJԛ//r=6^=EvEwz7DlQ(%G٘(d-%CFnή+:PjceO':-Fsdޟr Аث͌e1<Ʀlu5ٿSr7ȑ<OV> s*vĹI4)U Bc Ȁ H0 R"e@Ń5&h0P,8&xA#H$HX"H0HX"BX0@` GbahP@!@T@X@< @ $8@@¢ Dxh@4 + FAa & hh($($mz*3g7GkZJrq'(RIUOi($"UƊʵS<+RJ=ڍ˺klFФL !tq}"eN|fXNE(ǩX7WjDV]=mf)ZUUcrj]3ug9ӬV_b#gTUu^RmDUYu0H \L$|&C)c(&0 LH-kqTBz 0X+aCvlHH0F]9ֹQJ+7 !6*(e_b_ df2Sp瞮s#fho oD8ֱ\h 231MH`!G[O@Β=Q-W')?" ZE"/Ug-e!2.l.=prb$o&e.d-9)vMbf"Q%&)6KP֎!l5ĵ  z& on|$UEgJDZCNl$媀 \ ɓH0wy?Fqe(WZ%d|[E? #T0!>(oO!?}ÇQu$E?mvD1}:EM3=%u,ԒXL~̫`0Wp&&zcSz }su"f宅)dflPvV"v3uЯL:@jzV~ G۹V?8lzM? 7^'3]'=i_5lxa9Ȣ +R.?6ztn Ydn:|]ϷoIc`(%H>p-tZr,.TwֳC&Zrz#ypUㆂhs#/P R_kn%6+OBqc@WH#(4a: YAyvFEam]Ґn9ΈUh$dtjB7Ilƌ 6!B.]ՊHb#`:TM=,)fP]QGrihdڒS/#qnGލX8FDexX jYYsq 2wSzQS8z_X%+JNTBB0gD%KOxLP>V ΔܒK y +j\[2dTZm~CDJpqjBGŦ1& +m# ?L N$I REχiPl'[^ 627"Nﴠ$2^d3 [ 2uN!7ZX3-AG"`'aWI/o\]tJ".7mx:`[3JH# w3>ù%QOJXg7ttȼ VgY=~W8]^:+hAP>L,D xu.!ķ# !NӰқSS +./+7;hBWgLH-qE& _ ^r;dD @kL7d!DOByRl!z0 PD@AZSgEÄI"M aRmE0EId%ug2B~TF#∪_G&$0HIڣ(4%Ik$< SaKVSX B% >'Q$GTa"~HBrPGċf| =kkE|+IVDIXsHa!ي~BX8daŷ1$AڜWcA<`ZI# #7ve=ka` 6a C?hTS]u eLA&K R)΁3GaaAGݐaV9~&Afr| H C . $t8JqK@^Gxy@#p5 h[@DpHy{  o .^NoS ;@p^7QʼnPtcznԽ}q F` B@b.Dž +md;ļmD2 @@cʃ# + 4_W zm쁰b!mPCaɷIOmm=Fl4& +ul?Ad _`)mLhpZݜ5FB@Zb^A atu} FI> 9Hh KM$bgbxw[BOhQ!Ht.H)F C\3DШj  n!>CCR0>ψG;C"d"d!g$ oyl3/ܚ!4͌ܒ"k2"?f\/E@/b22#+d222It24rȯ#JFk]Թr$q# #PcsG\=>^ݼ5ɋx4 SU@]ԩAvb/0=QSB0O.8T{s.b[p\ ą)\p#bt +h8A+Rw|@f-h/lt%qE\VI驅˕xOTq%|+-A ޳ JYe ꚅYp- V_ d?9YƱ(l,/dĂ(-HebI,D %| +ٖ煵Ld[Kta(W\85&5lEN+^F&3/`hA@4lʊ%g`w0YUYVk~6ڄ{TaM5j8Io=N4KETG?!tkT ~DElvr* +0Oh|Y<ū8EMz\)*D(SLQ)P<.EŹRJZJJ(/'Ŵ M')) e/R( LPyG!DQgEzQ|2c*(?J(>l$$%)gR)(x"S(dZ({)"OSp<|B\_MٕL┬:!(O͌w¥ى 8`nBWp-!2=îp +c\(1\Ax s02H ^ҥzR߁uIz.⾳2.wɓvh /dMx9/\ס2䣙n^a~YEߋth/l8/K Tz Q^0qk'd8WK% +ErT;0DRq^0F!Nq8ā a8/,ah'T! ሿ0 CgFqÛ-1z O2mC'T*y6]Lhc,3bʌ!0jL!@db&[CǔȀLW2IVØȈBQ2ɼlnD0cMؒ8'# 8` T 4 Ϡ" і.&g26û3ΙXdfeޖaX3J+è6c#e'gIt(2ۙ3Gx N ׌hIhjCYL hđEaMaF3{  JV0TYC4]X 4Vs u/ fa^(Q}x_ՠO`tssAc9V.ptƅR .ꚤ>?e_F[0ׂ,dJ !F7ZMpPXDpƞ,w6cmM|oB6]^K=n?WƎcXܼjYALM"y&UuAJ6_6B +yۨ`!T!4†S`o00,g&Z5g 8Ga"hǫ(Td4 +2uPA0Ͳl}1؜>8X3u`oヌb|Ln7B|A:\Ey03gRڃI69Y3,R[;N Bgǃv 'VAу~lqq-`@I-}}m (%F\{=jB\E=wY 6qNO]=YQ==JJE.bG28z,}zQ*2$y!ssݶ%uWG5M#@*.;x7Zt@3h?Gڢ`Z& >F/Jha6֡zߟ T9X_TyD\ +Ѷ%*9sJ(30O/GҺ8"\e[ؐ`(E4MpFfsaJ08p8e@ib g :2j , (:rx)A;f eP/ReI,$Hp +Ba$U{wpuC옩P{F 5"(F"(CK^H3<rVA EC[QׁT4UQ@(_ @B=Ē*IGpu!9:),bCQE-6Pv MQVNPT 0 }@􁧓>Cm}[Ie=ܮL~ \#Obxle;SpX;V1D5^:9b19phHűU Di) ~4P|6EE6''Mt 0f*q\^XBC͹u4HHhH +37K3y,HG3`VV}M@K?2e1b1' #"0IaxK] pB/_h˿m/ ]ʝ +.R?.@d4'pqE&Ry'y;=ƨ+mdNCLGE9 n&{*@LK6e +)S,6J4;)`%5L"P;֘xNw뼲LW-{٤{a@(n<*H y@pI &ax`B$$4k˳( rtՃ* Z,AO.),<P׶kxl翼]9UT u-ykL>e&S ȺSD5<=SlNg׀&Ta'}P{dsIr>a7G6 A`] x, wf##{߿!>`քFpd0@hc^vU`$:R–8},c6vvE@AkTk(KWϜ"* XĆ]h@c^U7MU-JJzNyL6Z&`(p$-%^'m#K6|  F΢H PdUp~2R|"H4S~wF;PZm͊}Dt)Պ&@ovK /Slݘ,ઠ%ér 1@<`ȮMhUʬ^ᒀ@Lx6u})8LNܛ-uT8T3@$3n `0(%q<>Ka^w`A@/HO/3xfx.Д1a"[ 3|ŰQ; ˠ G$ţ'&ܓX^6ƠE̵Sl#8 B֓-Vp('ƈD&!8䳘yPCחJYeϮFPZg_r@TcӕroX ~RP pɠBA$޻(Uv?G$SoT(1a .MG6Zt$ \w#(LR,#ڹaly!C`txҴw>ں밄A'y3lȥg 9%ߩjk?cP_{Iit'(.,?eqm7r .0]_'V\ը^Z=@&{+B\8jbqWo<(fv4= ,1~rǁ%O!FAS GsVζZݱhv7,fb-0D[ +O9P54Y|.pǹH+ؙjQVz @.G g$Tx:%Õb,v(qVKI(RPpm%dJK1Je<>B)jH@_ʕz]O:>Xw)D)|2_w9颈7un@|2m柾(5$wm=bXȀ`ΧU L~$Iˢ+|L Hbu wſʧ.M84O8oF(g + +Sj~>q,5_ &*QMͿ&d6o(j>I`5(bf]yz!5H .4Npw7NM PRD򆃅7hvTqHlHS+qM͟T51׵\Ae +l|_)Y\%qm{ʸ#f8;[)3'3k\79F)k~JKKNZ/oϑ8DLNr%4EcZ +_6pBK>p~+@ +|Bˆ5(4UTk"=/2Dq +x{S'|0W]jHl$`j8\=ewQCS_@#;Y04en1q@m +ㅗG*}GSoo7tkse !ESe6,΀2 id"ѵ!:*iowpc\Y[q116{˵e6 +>PMѕR)ƾc:vHF13VKר+ +EbV^3%zx;-Bz +(FYhut3g WYn:bOYOJ氾=AVy]}_C%`jd(G,pQYťKSbMF6{bS[c{Sɉ _c|zB`!T`MIdhvXU10c!A.bi9F8 (3D>? F -pKD^$fѿDOu +go }޺?ϡqDτ~Ⱦ}挸>|,{ZLfGH_E < +1y$4 D|@vKEW }96.y}O&9- +Z'j +1|$9={Ipɧg~p}%\b2/[qc>6#i4*xamtވ W XZxE-ÀԎ pk@lO@(_F/* `8m3/,{P4/CmF^CD^$|DFH;ZMvֳ&WFI  z/^.e&xشP:I/~;#v[-i\q3 ;w^)~3/ '^A]EkfVCDE| xx֬5owt+]]xDd;New*<]YKnxgo&!|5T~QWMKassj0cQsVNLT"w!ggI(%b0hiZݜP>6)FE} /ng6Qߟm]Pa},fB)O..M4 ʝHs^qdL;5hֻt/}@} : rHt;Eͱ_D;BPɍt+ndyFw0i)=+]mFbqvux ]X`ZIȈFq =v}п%: 5ˆy g>as $ `_~y,DOtnL_RAWUmĽqދQ5_^Lo(v +oU͋H|"A xN3{F{;)}c_k-c\' Z Ѧ+_5uOnC2\GOR09!s9_Aّu#ux"\SV'ęhi +N 5Vvfˊ򧷵&㉣+Gk.kS{Tdo؛HŰC0HtZ:K8Z2h`Oد[r +vryi>Ms,`?YGow*wF$Unx;`4Ro"\:$%_K15e~+Rgu``bYb'K9QKsP +4ieZD(𓜊-Zz +$i 9Te_^ڊ / ;R#=Nج@ڻ} _cUʇpT`5!u.t].\t /ĠwU|!Ez'a%|hZ-zuܧk|UӥE7Bvs+fMb>r_6(Qr ERDWnxlznKcы [tTJeIt WCw}4J"GJ]m~-lkpbU5ͺM[S4~̕ ҡ)2Ǎ6*i\+?y?avDCM~u9ǵ'68N,aGNd %`S@wХ}@ ȄnB jFw+U~ +!C +;4.ғ$%F8WP˦dut *,$S@@OY|gߤzcզ!&dNƟ(}P~A)J50=σ&̆0 B +99T)LE s-Jyґ߁Kv}jܹh9g9s0 K&<1-8>Ӝ3y-(a2y!&AWZx,6xaymy,fw(r"(58?L*pNuYZC@Jy$_ *}Jr:|EP;?.JDhTh"y&oNK\_N9EI֠BT$oJ"e>$7t +'ܽ['בG(1i)l->DVߋ}%E΃mPUCdSR3TωǬvj0JF +ych">8g̳Z:+Uȏ߳+M.igO| gAnN!\e>Eqϙ#'fʏU$νRhOX;< ~xMb=?޿OdǣWoNT.5O Fa'ڳX'}08VF=F!] +2I jAYe!i´) Cvجd-eLkM|SJwy + &-J*!N=5EGxE$5zF+%8Hw^z<-@,yv <O1ڔ118G5Ap+[S>52JIӸ6F8*YH(=_#S:JNp Z8? YY ؼx:; +.Sʝ 3Y$Vŏ +O&W &Vs(-*_j^'h:NbYb|N/?L6#~/xqalb>1q uCT,t_>\6CWx8תs ]Qp o1[گI={[a&R-F O#,*.܃م8;g,OMs + |;|;!w|ʮa/^@TkCN{Bupksf ~@Є: $7J +}$k${}6 tzEDϼӐ >C]p]LQ>/1'ҀzM1O4C-A*esBuq%(8lD8epw4 )a7&MZ; )Fea;\ O%H@*_F`!Er޺&F2odxX%n# U{qs-nzK2)|CFy w6rD0uJh'߰E==P;d|zmȔ)/l2azޔ4? 7^z)+{@]Œ7j aO4ۡeVpk*II(ݬ#c2SW:ܕޝ3Tu[5{(e꒹ f;'CeTK8vNlxG{XVȜƔ*١~ #HdgtOU| HDӐ OMgh +Rd匝R,mb($*!yχ}2r$u|,tj^σWu<3ln +[.t +ZV 焭Gx&q`(|0CW%;ǾRxx V~M N  +C0<(yUv!JN&?`VazgT1)'f]&WbΏ2Dfc\0`c5s L]Γ4w&AsM',8 [TQzFoA70r ]Q,uJ i1˽Vh{c*:HFT—̺I){Iq C0yLۑ§]-McփXC}RsXlI(^_]vgv=AzU+DjWeWSL_T~ ڛAly.X4iz +V}qcg$V*N$Ӕ4?hXh6>GiS]17.w}/uԄ$25Z_v R: ̈d~GR/@ْXkteyA~t@A$BlfEm7OS`#Q$noZTKjk+GBBz~[C4ײ +cbґBJ>z QPP::vWUKʉh + i8+]); qqG3F. +Hj[_>r2)[e%@]D7Y( ?Ad*?kcjnDcnXՔUd7yQf@(ԣ̕VK#P/5#jWl@az4HZѫ AɸCOXPi ЦS>>}Wڗ7WU}FnjkԖ_i}unϒLzx#ӥ8!ia`+~Nǹn1Nz53MgzJ^hӒjZX0ɲhq_JLkCWȴ⬇ :H picՒSi՗^6Zgѥm\ ēi`YuMeX:4uK``Jܲɬ! TzBO!U6GrGTS+|}G 3%)|0k2f)񬈏 TjM1rc9HQFk$ )YqR%Ɩ[1bW˪9ybDZ-oGJ;j~%rQ̺8ۀĘ&8E+eŴiImZX1M}L#'H$uaL5ŕc5>VvV~Tog ~62$TmOvIfwzYAw:\;A?ߔPqpA$!|RE7ZŰQ^a zTe`iRYJYJ<]I5ҩA[B2*3Yz0nÊ9T͡UU+XE9vjUmVb=b;gl\]pUVշ%hUc)ԾgRUjW:ee<Ʀb߱nr{uGqzjrmVZ=H(yZ;Z"`c&V&׭C686wokuVm ڪ)E@k"ڻf< Mk7B㪞לʑ -u|W vxvLLu.\  zb+D.qu)!-™.ЬjvW +)B"2~`1otzoU?j\/jZ3jQj+眮zƷҌ;vinJ͗?߹f6j+\sbm5W3usⷷZR[qi\_m]Uۜn?JLuߝ({ߛst43ؾ՘syoֿ饸rM[93qӾz+ҌbsZs3hi\t42iQ(QHPX'#t4p:kkXxkgWkWLeN9y6s5ϕַ}?֌w[4]Ksu}5͚8[j};kkZKm7߯o?i-ϿrM1*ޝu/9^g:j)Ƹֻowto|Zhg: _ϫ͗WL赔o_WLqT_}]NjnҿLݜ樆ubw{ks5ۿ֯6kUy_k-kz9uŚ߭g:tƟ9?ݖ[8}f\>{Z뽼rwb3 LUrT[Kcj)4KViƚt4Z9չWZk[-f:-h74|K[LGu9lW~޸׵f:ߟ9縵ߌ)O)G_O9ksxkR֚u +endstream endobj 15 0 obj <>stream +>G5J-)[s8.꽚Vjuտovm͕g|oJ5||ickͭ7ribl:~4UWs9Wzlt1[[ͷ[Wn7bJ5i>G59{sKyś+9})qeN{ifos^)_^c/5_jb3~jwӚ~t]3m͜&r}q9f:N3 ߙrT뭔jS/95o> ;uWZw}r/Ó-~f,^vO͙ѯVm/It'Ɩל7ź/׃h#ICτÌu ;T\gjPw`.R6z\蕩S3Y!e`[)'Q3c1 |}/>3(9[w/_/?^ gY@8xplQd%?PaMb +7>2l$P`9Fd'2=5^//H?G1O9qy2+˹gQk9o[z+zW[+׽1~n>^nZLzmUWl5Ug5ksM7om{ߊw}if:kzozs5MW}5ǛS][猩Z9mYcfiy>S"M<resf"Yլp +,(ip&I%H(Z QЅ, +j+he7g sk8.IADehX42n$*5fIv,J %JTJ.RPV(zJ Pc!% p+B_L d/:zNy"b_4n APl4 ў\"6Г.bzjk8Tzr"D45,P!5A$P< 0); ŦB:{)FQȕ Y;DQ7Qʇ6bQHDbq>,R۩*R<:j7C) QRH:܊dk8^2A7K"1 +*Nik8r8:0* +5L(ʉÚʅƾ61\i*M܁I)Jŏ|4+ mWБ%D2 $58r|d  Pl G$ $`Qhi@(O IF݊uP#p&8ߡxdHLsTR߄l MmY'UfA!,x$'$(m#;ؘVMP<$qCt<0RB ]h խ(L??Lā}BSD(  ȀͼhaI0V@ a(<!4>CXz!”+39B># La(8ppe' %zy| J`Vyp. ,`8`.Rc@v/j9!bRP #PZ dH"A9)0R[Y +]kU/QeIS'ҏY8a&eBE ֢[أ +%wk8(V@E57|"  61š +%"u<b4kYs,Wp*RHn G$#5uAaPпbIcQTf-I4 Xr%8I0+[ÑD`EdSLFRYHSypbt< A.,2Zppk +^NxPfS s<췆kSEJ"e*k*z Y\r؋ "X0ln5OD䣒RC1,զ-ҿ?xFh 9GW78IwCO**1#%zg周 +aa=r@OyBD3I_z蝨Ouӡ`DF1&4tZ7gZqkL I٘ᅚjxj_63Qz&z!ƌn4M4z4M +LM04S:ziTizXd`PY,Ob>++ <L`*zI+S4VzTLò'+7gaKQKtؿ'S%5cSr,9 +`8Buzzlq2YS+HzZ>5$N$i1qN3`PeW'd +1zaaH\TEqUnTumF'!xOtΊNDuiP +b`!Stu*3m  Yod Yo̱Yof$fo(0L +3/$&29'hq,DةvBtLY4O^UҜR]'61#<&~Y0wLSby=:M%D*@IK%BY"HHDl,D +b,G"!081idfrbX"%`[G,)W(1-,0Hxe< +"D+ `x!4l aS<D;:*C 3N^*B i5!I1#6f4:xpyxb,EbH}+U<N ˾ :P8bL("שLiPz$"*y4lH9l죡`@R25VTa@n{ +6%fUpRJ 2 D &:0&:Me ]tL6 r.FI!FPDV'bjNO]At"܊`0`E/h 1IdY^T@tjahNMI+. Eqdk8V^ +X@tjMXxXInk8L;)U060Hp yIǂQYHЙQԐ72*F<0*KSi(U7Y]wB<)B|" +!fv +踦L<gTBRMDh* s쐖Mah]4ptNV FRVaELES,[1Itft|m Xl ЙHt\m 59t\HLʇၕ1ۆhpXwF.,&84 QD1) B +L2 /Nv9ì,{<fE^{m4e b!N.,w +HPQp+rU&#\!36mqV+&v7̶P~* +j>xxFBoe|UIel 1|K-@<-Zjj8P[H_Bdjw!jg`_"7I-o়s~#-+]YfYz);pPDmlS7gchdX$g,YIYT 1h3I_7֌M>R kSI+D.o(ҕ:6Dcz++xS-NVm:FSf6%B9-R*̙JwAnTc{m4  +"dBքi̪[;^Ap} ё_GWg#4*Y[Byx@Uqje=M9wUm">ðpsc;(BDG!,ʓ+{hJUf zXpK%o8FĘTIњJkod.,겗5k9NJwJS1+4lp1`R=CO~ GDM~IE\gbk})5E%I53X w۰هܓ6cQ 4"p1cEk:-ёZe#n϶p)bIH&`Ƀܨ.+䏐"SaBpZz<GgTY8`ȳH;h(5N27,Hkp' +-㝐[ p@ s5vf۷ +)]2%,qz&n7i$(vi_Ƃ] ̵}U'b!k +Ts +,B̷ll1Ű?&w6`7Rw4ʓ +fRwB[&n K%rux}qE w:oF7 Y!]lC0x-aɋkF([eb +6bH.d5X-Y8f{ a4z'49`sB׋̂!^%P2mG?d@(l~GO)/穪ȹ/rXu9 "3g:RzUAD@p} M #ikdI>>Y[(r洱Ʒ6h۫ͲzO3VH +GNU1&RizEsLDLR| r>%C Ř,ACwL /E6 + +䈯Sp/ kѪЖZZ~5(*ƕ_-lkG{2οꀻ r'q|-$ ZIV&DslԦ+!dQ9SƦ(;2W<&Qo \^ 8Aٚ3ˤ%(TM|6F`wS|I5I\ oO…3 CW!^#($F({KK8آW +쥏WL9oj~RFa& j3O}ry{(NuH"v!TA]JĐKAowaB"R80 =f]_hZI@WXBFxV_V[ щ|#]Z~?s;gD_jJ +Lpyf}-be0@|wyeNdf+?G'DcS0KyL' vlU$N)h~ +ne\J.@}cCϘ]\y/L͎Nd Ń7O v#H[_<'\՘ Av@LSi tZ]J$!A}x-āp5\:-pҙ { +D#%֧d.0}tHIk8bFU(qZTYA)7_m,6ʕRE fk:*Le5 O3BMӫ zs1)<4J&yҌҌb/⥔Kx]GNmB#(BoWrB0l&DVMcA~zkT1!>0BH7*`m|0XҼ%nKR\gc&c aӇU{CZĈvϛsu,*8k9g1Qҫ$nH U`ݛ@zgU b!K*srxVbT5P̵~0D;J]hH뷠)9Y5_򶫩a>mZBua>fʦZy\q 2Ujh߭IUGKwE2],ǭ?/j cFXO2hALhsUcKP o`[\Hԣc^W:N!x]>7EhS*@Gsn#i Ӏu +jPZKsf9m9RJ{4ŤEs1bvP繵.&qKgD0*uTqpý]H>02JJΤ6H- +׳[е}S$p&&1mPp%w.*SNÃ_9A1#ax=? ƿB } :МRJ u$b<KzАR^[S8{\'Ȅe/mzeB$p| =F_g&]shM'sz-іhCrcc\f55p-T+uɾjkjrYI̟T= Ο>LF$VԷqt@|AДlF8:7ϰ7ˁf!"p%rS~!%ccqm,"l nc|Q…wEN+ܛsTp +B߉HD,=>ڈW^gk`AЏK'ZPW˘r(G|.{oBnahDĎ jnn{vQܠBbC< m^=TۛPm`"xdNzbsґ{|/Z3qL>(ٜ56/z/qX1#}#+LU@W]=־_}z؀_4?~ÃEs錀)dmB#%,T/)9{Gs ?RkmFD1x}9}1!񣭿5a@xU aʔĒyFv*6HˋےB2 +z|qxE= *6WF yhl85V87ܽYFUV!pG/ğG.>Âb*4Fitp|Xe`W?LjUo ǰ)l my9nTEtbprS{ +3'&"2yT"ՠ&cY"p +vr0K3_R*+#\#/S&PE`$h RDg):"%yo~2`5>Ne."b +6MG5O& ?R7(  KZL^LUn|r + UU߈e?HʼnhĨJn[FKKlvL#v n58@B_+<&;bieN+3`'1Z}V'}CKbV4HآȅW-!$bҾhC EYG;;Q*',Jd8캴^r{.Uxo+FJ,`x6m 67=,|LHvCOO!IНy)E7lԜ2 TzJ6€@d1DgW) %R5x-@Pg c *Z%bKPP9gUM'':r^Wu K DRONo"ZfjFTK8*$1hٜF4+ +dV,0 \yQSB;`$,qCDh 1ciݠ4Ulx]nM,fEӶfqCy79n'0WۺvM:x/ג/|$|*vQ\AȖZLjw,Qh2iÛҾ+!i).cL.o,;+pqثMJYDՠ+N )˟QHs%s{n~.qY' P{&Uz:' #j]lo+?ȟʹ*qMʯ˦EWAHK*8 +s cJd#=b[aqQh Ԇ-%* hc^_ר0Hf7_XG*l}jh|+P`}D^KD9;9bŧo";G-CɇħUyTﰛ6kr9k"X3Bt O,xyAs8u.EWu@˭q@ *r1i :\2NlcREh*09PTBLRAyDH"#svyZSNPJL+vƤp,,Đ"N_դ 91K](%9ZGiDuƞH8X)TXO 4NԒzU灅AJ8FLj=hoEÒmK+| Uq?78V Pɍ/؜f=?{TA 9T{>fkݨ 8R%pA wj2l*3ZnT?V%S{7/f=dˤ6DzUtC s2T[GZ'ۀH3&12aTQKNITK:Ri˶EAw $RhC %\G}؞ +gisڷD-߻ 8Ɵ(v5׀M!\@ ,"l}jg :>2=OckA ܍i Of]} +TL٘kɆPN=ܔJ[3Gѳd3k4yr&ׁ+d/[b%۱ aOBC/z.\E]j4o5693DYb:U1̘/d +T=뙭-|ب^(Y׶qCKS(yHp +<,QDP;=L(:8wẌ3; 9ؖwQ|^\ -_Z󄢸9w`s<ٯ\ha0y+5TJO![A` +z0kB+SzՆìFGyDNVp>혖OĿ.3W DKb7LzE6~i IoGj{)'!'ڴU89ѣ_H}_a&3noƃQz5.pnPj.Nk#,{)=1D)O:\4@(ovGU['A!o4Yc6{|s .}/ȇ~|3nM_ x mЙVղd+άy +;#{{ lJVO +ML@DVii<68(rSb苊1#$Q^;C~eAMf>Y0rLE8|8 U7Yf +&3e0c$oٔKAQ $(!B)+#ʹ|*۟S1 ղoל,,}G-a޺"Ď}牐$ɚFb[$] к~@iR<Ѭ$8["V'j gApj)Sd73cEiD%MJ{e!l_~;[ +Ə.6a_hz)GX}U +ʹL\7͡V,\fc%pK:CY){uR+|qmrR)E+MmX.%ϳ@qCv9:n3'4G"}HmEcpBJA,qn2/l Iq(eDe~DTOÛv(+CBi!fPLM՟ ُaA8G|S2.#qH0OErNYPRG[s7fKf 9to4MzLzԽBcYrHcU&ȿ1Fig> %dR;}E5i%{KnE+KYNgCLvhDYPNI;FOY{H7h@%?ZJ.KPY8| t%yԂ+(?B=d&[QVvxA#A˂wP D@ !`/䇟;;!@)iDɫὦ?/%kF0(e (e,{ ;5Q*%ϣ +-{Yt\Sk@]tf 򼩋VzURBMvsL!z#OG`4*4p-T+ Cn(\iCWR13PYlsZ.o 84Q_Gg<}iλt|f} +,woħەaҶJ_O+CEu] I1Bd?gJtc ްS-/xGK>+ +>x#1 A L a!!8sD:}u+)OZrXBڴs\[ػ`~< +~  JAc&h43َs2Hü.3F"daޅz0^iOq_hT^fpAL|a/]aM@ 1Rh< a66ȊI ]@P4u('"e$Bڙ-p7c:]@`khT"6T{l8>n(@R)u m1 +6W+/ம1 $98:vo1+P"-p~ڠiK]m>m# l;sn؁twgỶoL]Ii w(;Jz0]@` _۸k|9%2'D0DUߠڸXN-BJmI}=c>+pIt s @q0!Y& #_N2 GmbE):)O?W[gcI{0q'@iQDh4gKACm]@. a( |Ű0peHrJΙU;(;O;G+c2 :OUJ. Zv j. pӛAV|}C;\-(XMbz[n@wmYN:Գ J#wlJ. (V"Gl2ij`~QJ4"4U.c#es6SF^.8fƵ $8@~hȽ6plFr'k'9Iy< [Fsl0#9Ŕ`ݽ&fY˻ 598d'? +dL̳ e]@=חFv9C74ԤUoU$i1r]j&9췇J+]k-$N޲jhf>)E}#4Sgv;XC@ . jJQ d}Immu>v#>s CiilC *.Y?8vLqt5c0\,e1Nr, +N#ڜ*FVZTh67 h~9bM4:~6/3$ڡlpcd/lٛ$lVU]@RM'60.~[uʺukU1W+3zdcdL %jGEq8Zɒ1æ. h&R+q!7F\)3ie_:c qbKHU3,X7ѣQ}xJTe(QU`C|`C;N!i$rf!L' JpCiH角ydt4jtU ]SC=FTJTt0X9Y`4QdSd^CF"5OٶLĻ0wƸ.]lBPV q7z@hlφP#00ZC ;F8 I1nr\-A+@ZD_~cuvѐ<-??9Ѿik!Kj&Vj{:E=di$Ě۽a 2 jℜǓ%BlɃ!B*G?է2~rR;¹0 JTJmltEF /FB S\\?cn.waQpanl\d^RGەzԤ7Ra֑tޱ4XicQ,cZbTӐeTGꘆT8GyhTSNASP*t,vP4d2{A=X +0mf=DèB7' +3]xCa$9H. LρF#$FFKRrJrny:X$` 7.na!cackG8*);Ƶ^2?JbVDk&3L,/QUvhN`ljFCzvWP<27ڜm!_K0_BU;-4$W3-/հkEJ :K^ ݉sfrv FV|b= = :o#Of#k&;c9_N(5H*cNqzILf feS'.Hgw UmB̖'du/O 0RU 1u V껖^=9sD|yXgBU>vvžYι. BQQ^\TiXn  +qªښ_NL4Ec'?30zP\ӰDUn|Zߛk`8{rv6)>N.2Y 9MXMF&[CVhXMz٬9)3I]`]A&8? R|U"YC=^hGgS}p;?XXART +՞<-} 8Xx9"1&a;(r$u +7uB 4eebTU lMuؠh`-),p঑b+5j5Cu:isgX{<_zÀUgwA:#/X&h4gEam*#7֏L,vFLӡPa"XX. (ς7W/~/U&cZ3-gt7ɚ1Co=yX_އK4#Y͎= H MXHf-񕒶sNT&,1X9E_)$>[)S5eb#ˋrvZbZ-.*r 9EՉkMT먍s6 -rk6 \*JCrcv7#uZڗO.%keF i!}⤊U,?OK"KaP~? #Y9,6Cd;>. +죞 (>&%|ogeb}d5(ggXf䑦{@̱ݢQ ̘[4ra  Jb8]hy#܍p]!f7?3Ls}X‰5UvC;Ӝ -݈Y^` t#g ;Kdpm5,kn3tiv)N\Rq\3!. PWj9*L> ]aĴr ePmpZ/4hhqT*,g[xdmuH4,VwW"-uF>uF>?9jޢ#N@Ѽ cҺ%e tw89O*cA. 8Q(% ysUTh">a>EP'/EÔpy\F",E_ "#"9%6샃& ~9Ծ6b,=iM:ю1Ld8!/n&l5. 8-[b%eRTX(8RS4fKS2G^# =#7Mɦ@ĺx,^1Z&.CZkL|_Q&/$gQ:c?9󞧈߷2-ee:mqyv gWj[n/d@g1O_I psoT];ިQ+;Cx (3--j|#ęCְ2L3‰=^1._]EnNRb4h/_IK]@P^D!h ֻ֦]R@u.a\7 +{Dj36!DKDZD.00J`tE~.\`SǶinÚB ځsL4x(V>ly*9<tkQjr}U'WK"g6q8v6x@9auY"X7c_81XoBᐥ99 0M4qiV=GزR|qYiju**N%r8rvƴ~wkdvٶ-z9h*EZ$'QEf6+ -YZ}%e l$nlKˡRV/E[.mh'a6} ?ydLF--R9n + +韛x8gQ#S6#YЉLO&8'Te |5.v{~͛Lݙ5Tde2F -oRU!yu$)MHhhT{X]aMi\s_+=McCHQ2;e,k9qT_3' +CUy)m6 J +{ $ N%9hf S/uB)Q9$Nq`S2`2 r= 鰛3]t[ rLlqnH\g 4OsMȑV*nG! +((#5, ٙq|^9f ޕ /ו:-OX?ƕگʧo5kInh} 2:(˽K!e;/8{uU Ah`O{A,gZCHػ@CKk-w].ǎ'}Z!Orp[-:miDЊqL3q$Wk . ,{v\XsR#py̒9 -b&y2"Q2Y֢Y}|$`|2TGu8t?r/jg%kΫYQZh8%F79v+l(J&I'/H L-UY$MnVxpE7hw#R*XG^PCw; RѲ(^Sc@[NQ'ϞL3aQ"ܭsfKd]Yw2#FNc1_qVuLrF\"ʺ繍JmlLZ gc{ST/z Nٖ+ɉ󫬐oL )C;8$<0EOK4ԑPc1GYlz:=)EΘ$?9bZDX,?#`\-}tC*ɵ)G,o;Y,Wnfi<{ZrekFF" !u=:a[yϢ,-ݻڭ҆6H^r<qpj?eruLjp#|jZ?+S)0V$Y+ WhLhT 8ɹ0:+4⤙vK֙g>ʢ5_MMd9L9_yR + #lqq8w#g +b ʱJ3_iG0Z @+:ϑ隍 *umr ljI _JQ~PZ4\e'ͫ/]d`A8]&@'~DCrc1,ۗuz[i zd]?K.w/96X".C>GҜ:GJ[aқU +T!x~]-(je "Ppײi:N[Ԕg-Ta(Z^xayU,yY͸ pUSņP[$RM8n9!r839"bY$Fߥ6*b;,AF[2XA:jy>6 z90Hī}f'~Tӕ~E+zi/Tg4*v8aO<,f] +a_b91g*d.n 8^#l< RQԢg$tB> V4TNb]gw-D 1s.z+LcKm@Ҝ +d GsH&nѾ"YɀMeЬڰ Y wb +ÙͲ@gk]DB!`c a9j2*;^s5QW et Q:g'Weg0({fyIUhqA9)RY.QNrqInV`gp}i7Ү=\}9 nHk3pǫ`~_NK=@C!#&wMIo 0R8/'yMlj3%FQRGvs4\Wk~z [5T;a8QKj\#%b?T7ɴoT@V$&aV`{*'w 뒊҆ k +GKyF8#a,-C@G9&l9aKVN?h.']]Ě@@Kݐ%Z0Wn3EiPwHP WnPoeӋ R|3(qlW\NiiN&_Nkɖhac=Q-WbՖڦPC;qՀ<6xj6RWU|c&PAI`%4 +=F!jsRG6Iaf$i4#+-u"uyxV,o7*#9e"E!:H?(qQDDQ҇keDGnBcZQ<p(w6QGGwy;U[`NDbI'vCuUŹ?@ V#[PKtbIΪz["|d6cu =2PJ=߮L-!ow1<>yĞYG28k -cifPr ;'|iy5,/;T5GRhyFzaqcYs"68ˉ-bHX|+jyjopBKc2 # <<9*iG#~!hixϽoT<0n&abJmGy>O+ ejⰗ Ӑ)tI|;QږgV26׉J,q5:emT9G+q$w>[A+XX4Ҝ??fpke(ߡ swAG3LMVDS'bԗ@. 8#nޏcBa9,]\ȵ#Ҙ1p!P PƼV"̑^To H i8:cG.` 9"-p8In^@`h'`Uj=24z'F|ys$<ǫbsG9/${v%[a|}s'c.sϟȉ3()[>ZZ!O g -9ʙT{"a-nlRv/G\ +1,g_'pbG"aף +Ւ<=lڻ~2aeVP:p9K˭t4"Y1Xvrw gR2繛u~YƌYu`S;`=&Jۥ. #1R^d6}d]@@ɓ xަZGEN 4.*e7) :k3`n}hv}/j/_˒]@2+[jVI&&2=<a5%k߫ky;,5a43܉"b])VRT' nTB8!NZbQvY0FN%K9ܲЉ_<{F!,4b\O8D<I. 2̛[;/#&DISX&%QRn3q~; _R tbÊ5 Fd +5tPݦfeZK*QCarV\ gػN^q(w2=n0}NP14ٙ6~)`4&-7M޲KVV)"yPD y0vG5HZR?E%Ch WX%G)[o87IYYlI_|K8mH] +2c|kɪAr }o8E_aZG]Pd,x`_?˷NkFBTx3"q',y|֧LGٕ5:X0>Sx eK4&ՠ8qԃJ&##-GOBKˣu8//mrArwZTM"CoawѲe/:cDۖdy ̐kZ'lsdg%jhoZЁBrQ顶2qusHSnG ş<ΖO$8G WH2YSFe$D#'#ns$4&'jX>YhpKmF,/{6քõXi`>f4:P|2_NR>7;UĵE5Zgx:䬏^{U]@pXIp? kv1bJ$ĺ0 +@u z('5y,iqk&ı9@jH7Z(i-B>cM\W!TF'Kdێ%tC@aJ)d 1А# EIS6 T>P*(0(AQ(RF$H 9*b^F`so4yY_C,C.KFZ9lõ*kBaj))Dd#lTv*֘6 + B.O^ +ͼ#5j7bYVt.+):}ukafMֲO^$-JSpi&-PT \L(F-ˤ7 +W ٹ}X{фI 5_u=7+A7HqW>-$} A"95jp +C RBU|A]` +k\jyQkwg.PrImO+!]I&ȓx)}=`#8~FC7n + 4 +*| +-1( gss{#]Hb EB \(o@[O u0(!m}xH< BqDAKZ3V8Ds;8=v=PEW&eMyv^Du"~m}Y95Mݖ͌Pf_"LSd],봖).8h& AH$DPH DTli^сQkr $'r$2{~fʥ^XJ#J?j&o6=$w,3cћ.|٢pewy3edu-JsTg L-b'MhJH#X|a.z;E7V4* ۑ"5Q狾eh:}~2zKM2;}4Yx}_,Xi+_XUsu_ }]ruXO n '8vw7povZOYhQso67 Hef&_jy4 ٠ F`> ?)Oܪui)(CWȾ^ 2` [º鮋T|3c4\_ y1>Rܠ4rB# @< =N1_6оkYB=k`sDD0ęT)3.7\d.%bt}~VP}K+h/d%K0/6MIP$ࢡ 2.W+Fx_vXO]|?"*h>9 7fԧE# +XagcYІt?q(0zJdF=#!ej4D228s:79Gφ˰&rH4A{+8|kD?L㨕X@(ӥ\kT"<9F/.C0/=CA}_}qzM7Hd:ĖTrI&ccTBբR*k2(BE"m"4EӄMs@M|}޶]_i!Yp~ +$MrRŸvSI ?uetO LV8gJ +K3 @J5Y%ǔ6:5R05'&#.:#㇆3*)6n 2N}(O%#YV.XF Y x_@Q5HHY CSYeTo-`S._ky3E`ׇEfQi@c@Mƭ-:~$D8F=ɳވ}g@1S F Q|elR3^#IiZrdJ`skC:B h!:=܅g`Ƴx(Da4@7NYK;27 P+5޶MK\c"ZK%%;`.xj~ l}y[G%mo0ʟ J_[ZUGα2$Su`> }I]U*@\΁7+qj MWh;{ 6C0h%OI4j$ j5xܢ0؇Ϲndw:RuڄN_7 jkts:i&jcsR hz`Sk?uy뢝~d0@u=qXХHWt^W^ xz21ik!>2e5Tl qC!Dy iDq٥OK7 ͤԜjz4$ǿ'zKbcUlѓ +x3: YM{M{iDS/CCDp.OɟX@=2qdS4oR}vgbVd?]ϤR +Qۦ[t5ɤ,52 ^Hy(G$lμ׶ް z=Ev~">3Q'VW / cK-Ϛb^Oѣ[ѓX˷nGym Jb'1`|6$35y$>~v?HFZWW/]dE(^ƸBtNb5{9y2k>L'uЌY}.rP@>?ʼ\5G,f˫AR4ZGw0 c0*fCS}ܔwh0d 6ylz.lRCċMy Y ؀bHeP:#Onu)5ʦFV +P!Z3{,e8 F֑>h}/Fp4q{Yj|&QI!'wpdIOU\5)1Ipm^pk5\Fs 5AEF* #QGhQa [S '7uj7ˢ)! KgsjNxuM s+iȲJ>BˋD^(PE)& }ҊGݵAQ8a 9W"L"4yW$|"cHN&bE.XS9Έ̸֤ZxY]B Vj4Y Ļ5Uۓib9\[:R](`fMn@NS0К!Db۲&w&/-|`}%㬉#H&*5q}L#skQ ƍy0F9Hic:7 +70:B̠&0Bxb: <`xKa.z5}h4m§Scxph$VرrG}#S]N˛Y*zCxg~\|B>1jAٌR>LF,ig`_+ >[SRz.ISOٱ,Gbj[Tq\kVCFd{R18V?pfqGHt!6CcxPCP*7`{cMfK+ +I1Ih3~?jYttHu$B^\$dLhڌdg>8)OB9c 5iIa5ux97պjk͘Uӝ~5 %PĤ$e| 8S#zKz_-T1GuX]NC|+M⥏0R)U2'[M? +VQY7*l5392ƿQe +&AD; +f\pta&A@sQJj +\#l8|Y6\#(DZyƑKein+)L]^~h\;L5qLuۮ.B[:Qj6rjxktz!c1YjX#?pdQ55G@'T#F5=} EZolX㩻خ9+HyrrG\k:IB CTC*s.SACh43! ĸ9[ăe ǥ%͢kP}T&,5؜"SDj+4 qDAE[~rce;5g)/TAQ=LupzͣTo&ճ[sPz]}0-R] 9/kH:`|R"5>1KY;)u>rTAd膖d<mg|+tAnZXw7b]=7WL:)3鮘pL!蓿 W>^pZKD\%kCc9AI-$AB\tÏHq" *5РRXj|Dxbx@ L +t4~H| 'pC4x +$@ԑF`i\|{˔/SX8Rsg@t/кq/bQ̞I"֗80#J /?;FD{O/ۤf:}"Ɖiry yحq;+<~{}c#->GN&{ 2geYM-c߳߬Ӝ"8qu !kYe)K1a1ha9" 5Z<1X)")-Ԉ5ɰMR !`NHwZ#L;"ͺԇ&>~g.,:.eY %.K($#A3ar8ě2 p 1t~+En?]N/bV3/Ǭszmѭ l($5־s/O4(}MhBX;pDAݖٻ8-5NsP` ̛Gp4}97 _)1 u7FM[7HPh?,x-V\rP;)Cuk5j[<":TἓʮVֹkO'(z L%f/4+B̓ѪHќ1;oaE,0hU!dӋc]`"22l]8먌C׌UZ,I&K >M.#{L=$RRV'E$="Uʓ饄: +#y.ldRyt0PVZ$kD~uC,zqE*،~w X4D'_/č-űB)lsci7 ȕ`0 x + +׬\4'GAcHQRb1:^_*R׭[%6nT]qMJG~I\A6փQ▶DROųdor(8F,{)ړ܇1} BcA67rPf蚏) My\C $zk\e;~!:i?ﳲADt&yX(s +leOd,&WXD [u1$ePfg Y18e FkG%p +!M I0*klHf!ScњcCU>A!!QBK^3LqXr*ގ ʿe +DNY;Lc-gߖyp@A}y:K0ck&j b e ӆbp^^F/|VlA ӑ%=_ì`oI*& Q |$+ȋn@"9qf|+m9\F<`^eyÈ'w ¾RJ/ܔ}[gGxle9q>A>55t6#Ne_gncfg(% \?:Pfw+1f $S̰nf3;.7g +2Qwe6&3C i~7#֝g՚p +Yuw?f}ēh$M ТTОxlN݀jwD~_HLfr8 ч 1U,a?:q{d4qF7$S'B6 "5nӂI7X2`MGQ<D̝2Whʉ]?:9)Z.1c(붢mY\oo?dmAHZؿ"\&ZjO(Y?YrM | Y[ck_aJ⋯pJӐX7Bߐ DnG"Tƅ^bC: +Z^}NF{`E$XѼRޖxud0R^/;34-UX o!+:Ҿh_6_}ia3E3"SK.ѧKm%)ѷqS:@۫@&ZRr(;~D~ +β ; +RL[\ 53~LkhUjAX|M;5DDMş8Yrd},AsޠȵzRUg +BpM]q!Cc]][-?Xaw3e˞goPJP?v6, 3+X*@,9fCBOb{k\%D}T`H҆Y3yq38-BT<QH5{fAŐL$9=|9>oN \FD ,CavenYvUe4mΘ*+[kC +U,p"c&4L %qBnx$R:s !OhPN;A2j[<vؑAU_pniG2Ƚ+H[J` G2;Hwp߼QS R}^ mM_XR.*,vꑷ S)`k0`Bz3/<ܮ0%i"4Mq,"*Ec,ICx*^WP`X\i:XӕUN6$VT1Q G"T/^\ aSwD %Km2^1O&xj@g~ uBmdN6h~HrXiN76Uuys'J'7]mVUu_%0Hj}{|C![ w> '&'e};-OE.hTzA߄hy~Jv.g4+=/{:퉰 |ְ @лJ4pIYĞ)'Ó\!7%G +RK"VUo75 +JBboQjc30B=^`Q=Uw>c|X:6rE (k?=)LIC0)"`8|-2R#üx#)~uk6̟YTKõ \W2t ڀ[^WD'd,t;}FG²L Z_9dj,[7GJAgK]i %hbֆ3* %KLmqp2)&+Ib|n\l[)yUYP ;&@ ' 0ԧ0?o +.J, @ +m|Tmܲ6:.M+@ u3m~KOϗCQCN-V&1^-We_o+#҄dEY&Z~8Xq-{5&#*lQj~XKR7~+ KK?ZXuJ +6mzy! -xkm7,GZ,R*;0}6{N/X2B+n<|~7FϪE-=-$GTT@r, .@B""%K8F.?fzK{y{uc`K +5o f׵8 +Rbnp[D3w %Ԯ5Q3 +@pudbOHM3e!f?. ǤrvҤh8bxL09yjk #jjlMI=Jі6 9)/jLj#6ΥdBchR ZFk ~;!;O:LQ©!O ycR>C,6ET*)P _"B[t|lOakNlvt"H5d&}%" AZ8g6c>=)shEN*޽uQ)CU%vhcZ.ֳ@{ߦK(ݡmq!8UOB@yu!ZǢǪ`38#%VTtk+׌n.aZ~+`!wo[ƻ!L*l`, +W `UX5%ox]ίsR!9Ek8<8:oFp,mp8ūKN&lnux?E6P36eלu$ H,CpC5K&C,dyb8 HE,=u Y"K!֠l.7=Nvv$t>D3! tABBP}R N/\9紺3hfJ"ʈ/Wb. 57sB<%@KOxރg;GNin'e+Z6AEBNB \0!?pm!6ߒ4pXxڏ?vG%p/{m}J~ԯ/z09U4[⁘fFk2 +hzfmQ u[kUx,=͛1q#o\e ݬC殗Az2Z& xԖv)M4?vRQ~=2 99Ad +KڮĮMyV*4B,}ζxqgu2^KueYMꚍ͹a/RB3ރ3FUl ›3Ct1Cq2:> e5[d-߫hϓDL`rۯ'KX/FUM3LA4uiz.C1Žq[h>~ [WCdC !6`n F|"L+l +zFe$7} ]qA(68hFG(U vs7 кs oڸ 7rƀ2F,I`T-ᵃ5!|T3 F!b 4CRdBk&UǠr_1)ZXp>HE B`DY +$@)`s#Ѐ_~݀QG&Em/"<D6.*G_8Lrh|TCH.jST357bX- +?M|/eˉH[}p] +>n&|¾2 $D`‰zk*FhYGR0#XJYȀUVqzH -[տIRZ9sn .,FgHbjԧVN=QIhQSXTC!>޵o<[2=  +,[b + +e 0JC_Ж{ARGW+%x@!"r,TVN4@ B7z\Mx3j3˃WQF(O~Ó$|^rt qxєMf3mZ̊?梲Ć6^G[.*k$)vCF.4".=* ?pғK,O]oeo2 Q[JݱK\\q~Œ) o;9=P$\? M<5wie~C|\mbg<}`3j,sOYJa戨.',#@b!^H8 ᪬jyW@y ,V8Av²{h=N)ȭ-epfrG h!] ƒ.^֬6¢b˄79Nu \zp>jBǎ +FTytROw[g{aox k@5ٵ{<$/#Kt5Ázc +2#*-94sٌg:dbatCa*\`r3lfɐѱٮca )M8ķweGBF hֵJ؊Ҷz1T?)Oĥ>Y&XmQc߿AWgBKvX^'G/!3\aV^Lf00ҷ|^->P#i;=*}g1Kn0'R9[]-ÉR;J;'g7}~jq!)}2V P،1iMAB4KBzyRYFE&w'ƙ|1 3`ڄm7e꾐&%$K)^w3JaPO)n7a[`awxUUl:X[< M:&!$4fZ!V6iKJPOVndv[inz?J|+1 WLzj*|Yh6te pZ$M4(޹ncѰ]X/ǜDc<\N }W%S/8H", D֫;he5:>CNvcտI&:;lIs?o- +(n@yv;(e}Y8b(p,nuH[5dNg)p݄D+qH%Q^;c[!SF3!r&:֯Bc뷘 f&1o+;@['7QWit޽̮lSp̵: s&%ſ T>©G(e?ޫT‰P8n~P#ڸϧ#,yEe2+z׈XX + rGR,&3Ջ(2C.;@`з%sQB] /^USC?0@%>`  +FN88k*XmgWea玲 +h.A @mQ8kad0M +Xzh8`rytf4K\S;Wލn=to-ֳ%{b>L !ay'/OxFiHu#N0081)C6?6qg,0VW^`g#UF/X5?a95QaG-@ԑ,:^L׸:t}6_*&J/\.0.]9zmɣfS,˹KN0{+2;fS+ sA=uh bbteQf_"Ztsy]>Z󌅡^&. xŔxJ~gؖ~I3 4YgHַD]NJ]px.MxMp3 ̪_5M%Bw]v栣-yZrFN0 WJXػ #8,W)/?y|y$!;Օ=CSdOnj.L\D҄}b}KVG!THM2 I r c[I2Q55;ŌI-Ra}N I<7?VQҒ$8gg6OCEa1 *M}A!# 76o^jZy&&-浑o.a3h2%xD"z)'I(:H׳7|$>r.) 1,% 7 byJyr e~"QW>/)BiȠ{=\^sÂ#*f|?a2$\Rݤo X- |\R|STҟZcR%%3Q0T'zwS[7n +-aL@ ;Ai[BmEH^2;A~Ihl9G +ؼsAT-+.J {w,q~%f*?>i)8E D!l¥vKQf -er $b"(҄4 Hg(8|N0 ǯLZ9@+fkj_RDwu`4 +" qcV5 F +Y%f \O EQ7ep?FP(CqědɆ㽰0A@6ޜ_4j>ji;raI񖫒%1 r4[AȆeejC%ϽGҟ0}!I[۟Ŏe($vZi u!E8"b(r?{c C 8BRS9 8TԪ4RIEd#]T r!GW0@~p&a6rsV~6fvha1< *,FR:(ΐWfHT")gGnP: eY}X[$m5sI#="(cuM }[ܨ)’̩[R)zmaT#BGݩ%Et8DM:F`f cdR*&`FJ$, +($ +AQ0 C1 zKQ~K +șwy浼Us:'CHXwQʄab!"QAA,.B0LtVZ+a^Gaz+q7^y|_x{(׻-M F8('(}YC3|x(rw2۬z'}7L vіҵkLL͙B=)sD(:)Q^L-)('(Yck"aY1_XWN `.U4U~f>z< Ha bH'ON ''Z׉X,a|Ad=&!?fly[~lj|'qp4Sٝ(uuh|bR ̆FL9P*meRƢ"`]@ð ,^l۰U7 @_|=Xj1e& H7z&3Tnک 7P(qIua= )Z^}^ n)u̟=k!ﵾlOD#>XI*AAn Z9 Rg zιLjĩ+ XP +OA%ĘjȧF.1Kzw593;'>C5eJ63bK{>VsIFK{ǘĀ6Otˏ,tN. +ϳōezs>R$G^p9!ToŞ '3=q*H{GhzڱqFnB/b{H{Ⱨ&@Nя5" ht-~>W :̃zY~ѭ"eHX|6#LLW&.?N@Y$xʿ^^,pL<+] ְy$X~'3viJTqoknk ԰J*N651Y/{^ۀ'CBl x#b\Zy@aؙY?8_裾RC`c1;@q>RG +0yzq!ҙ٭Yegu M#vlU[t8^ݯx(ޥ3EHQBgGMؕHVoXKIv~-W=:'3ƞUp:0G}>xA,[Vct_Po/;QxltUC y&;ޔ$Q  gUj7_ZE>|IwQXPqk+" mk4;ӣ6WS^L%F+\|ƎŅEJ%Md׶1شvG;Hйc 'G]H|NWt*=%Sp*5?SOS.hh#SWk_;ܴu25f¯sdU' =[`i޹}^-b^VmpЎ]#  +>Q@T:Wb"S9ew>f1ԩ.2@ `d4VD' s*Oׁun~z^t&iq&p*k +.߯U#.W|+PB;[]ӭI֦k#"oU}^Ƒ1]a5ӏ/q0Ρ;^,v1.@ʭ2(tJIW^)S+̀~* M^:Z%>QvYو9NGZD6 D/da<[zcnPzk^:fupXt.u"]+ߋ->]o"J2UɁ:.Ԡf]` b5^4^l$Ă+7CG+A=Y; Nثt{uuOWgW{ ]LŠ&b\GHPG}~p8XWzʰT.U{jDeU:YmMWi az@C=BмiUG$FJ=p<^̐Vw󨫍 +L\ʙCbhY͑ep^܃Qm{ ^{f@W6AUb(*>Q~$jيyq>z}*=1!lU%fKj_^>ZWXl +I(`nJv + +qZ#ƵzwŔ~yu3mx+LJjWf"cLPd^UW4KEWk^1+U !KW^mV\̯,^y4-!W{B> {$DbbAv_U@h&f98W+P'TWs/2 ]b20tuF9HUsW,B^}R*ꍮNmj\235H."ږJN]]rՎthHr8?}6hWFk/OnYTNݼozTX)vd=#|%߬g+~{4STl]6g,@A) +endstream endobj 11 0 obj [/Indexed/DeviceRGB 255 16 0 R] endobj 16 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> +endstream endobj 8 0 obj <>/Font<>/ProcSet[/PDF/Text]>>/Subtype/Form>>stream +BT +0 0 0 rg +/GS0 gs +/T1_0 1 Tf +0 Tc 0 Tw 0 Ts 100 Tz 0 Tr 12 0 0 -12 -103.0967 -61.2783 Tm +[(T)7 (his is an A)11.9 (dobe\256 I)-10 (llustr)5.1 (a)4 (t)5.9 (or\256 F)25.9 (ile tha)4 (t w)4 (as)]TJ +0 -1.2 Td +[(sa)8 (v)10 (ed without PDF C)11 (on)4 (t)6 (en)4 (t)3 (.)]TJ +0 -1.2 Td +[(T)71 (o P)5 (lac)6 (e or open this \037le in other)]TJ +0 -1.2 Td +[(applica)4 (tions)10.9 (, it should be r)10 (e)-28 (-sa)8 (v)10 (ed fr)10 (om)]TJ +0 -1.2 Td +[(A)12 (dobe I)-10.1 (llustr)5 (a)4 (t)6 (or with the ")3 (C)3.1 (r)9.9 (ea)4 (t)6 (e PDF)]TJ +0 -1.2 Td +[(C)11 (ompa)4 (tible F)26 (ile" option tur)-4 (ned on. )41 (T)7 (his)]TJ +0 -1.2 Td +[(option is in the I)-10 (llustr)5 (a)4 (t)6 (or Na)4 (tiv)10 (e F)31 (or)-4 (ma)4.1 (t)]TJ +0 -1.2 Td +[(Options dialog bo)14 (x, which appears when)]TJ +0 -1.2 Td +[(sa)8 (ving an A)12 (dobe I)-10 (llustr)5 (a)4 (t)6.1 (or \037le using the)]TJ +0 -1.2 Td +[(S)-3 (a)8 (v)10 (e A)6 (s c)6.1 (ommand)10 (.)]TJ +ET + +endstream endobj 5 0 obj <> endobj 18 0 obj <> endobj 19 0 obj <> endobj 20 0 obj <>stream +H|TiPYA2횩jAgTNQqEEE׃nEmqEiUAEYdD@A/DcXd\'fg11[_/"#{ߗc" ^[}V{n:LU|IP%O_M|g1N_T~9v*Fศacb!Z|'ury[2&H%ۧѪ4rulZU)^q\Ҩ:U4rU6T+dHp_RʵjRPGc̙ *yX\G#?+sqh:LqoX|\ڃa0d`Lð1lc,u"̏.`1 a,+i.Xbqb$EEE#$r +L#XKvIXN,r8y(%i;H)c)$d4Z'Drts0vB2Fa bo2GB~#4`/:^t:"E|9Sh2O;<"}/P&mH770 vlKfW>Uei"O)O~L'z75?߰A ~XOt>%L8KA*d+hl{P{r'6O|M<3GkR0e\냲a lS ¬In`-}\Yf>ós*̶E"3Yn3 'IBIPݨi@: +<}d i$G_e/xp|15Cb:wbH +]x[~~FӅa +ba%,0Mȇ;fu +Gd[O$E,h 0frT]~'e? _2$ +Y[4 ahoneCR/0W\9%nt;j+^ `g\9d7lV/U*fMBdʥԆ2OttM9!p pE4s)e*h[WE.~B?v}xZ`Y]<G٬v'"݇"¹ ~*v1W u:1vE8? t2 !wD LzTQW̙IDy!0_-]=9 ${+rv*G_0A9w|xU-LL?QMB? {{0HXB}gP\S;DFՔ-ʕY + +SkL:]΢nA~~iouPP)x6[SU<.[¡$b^t9r*%ءfΈhv>A4wtU̢];c2Y1M]q4MnAMlV>l,&Z7eMN[lOkk  UcSTHDG , 4fB}gForϹ9>`ʦ<"KjO"](#nFqǃnWR\#?w IZ>,SFYNf<̘_ ܦw+^B"AQ[OJΑ} Uy TYֽCDMA/J*f=$)'9n^$I]ʂU⚎s0rIS3͇1za$J$USkr'5qn,\gPݨARzaP7ɃB 9{>&Y%p]o}]_> endobj 21 0 obj <> endobj xref +0 22 +0000000000 65535 f +0000000016 00000 n +0000000076 00000 n +0000049817 00000 n +0000000000 00000 f +0000171853 00000 n +0000049868 00000 n +0000050219 00000 n +0000170706 00000 n +0000050428 00000 n +0000050666 00000 n +0000170144 00000 n +0000050740 00000 n +0000050914 00000 n +0000052554 00000 n +0000118143 00000 n +0000170192 00000 n +0000176016 00000 n +0000172367 00000 n +0000172451 00000 n +0000172833 00000 n +0000176129 00000 n +trailer +<]>> +startxref +176338 +%%EOF diff --git a/content/GraphicsIntro/Outstanding.svg b/content/GraphicsIntro/Outstanding.svg new file mode 100644 index 0000000..5ff9047 --- /dev/null +++ b/content/GraphicsIntro/Outstanding.svg @@ -0,0 +1,129 @@ + + + + + + + + source + outstanding + + + 1 + + + + 0 + + + + 0 + + + + 0 + + + + 1 + + + + 1 + + + + 1 + + + + 2 + + + + 3 + + + + 4 + + + + 5 + + + + 6 + + + + + + + + + 2 + + + + 2 + + + + 1 + + + + 1 + + + + 2 + + + + 1 + + \ No newline at end of file diff --git a/intro-rx-dotnet-book-cover.png b/intro-rx-dotnet-book-cover.png new file mode 100644 index 0000000..3161bde Binary files /dev/null and b/intro-rx-dotnet-book-cover.png differ diff --git a/metadata.md b/metadata.md new file mode 100644 index 0000000..97664cd --- /dev/null +++ b/metadata.md @@ -0,0 +1,15 @@ +--- +title: Introduction to Rx .NET +subtitle: "Early Access Preview: August 2023" +author: Ian Griffiths, Lee Campbell +rights: Copyright 2023 .NET Foundation, All Rights Reserved. +publisher: Endjin Limited. +date-meta: 'August 2023' +lang: en-US +tags: [Rx, Reactive Extensions, ReactiveX, Reactive Processing] +toc-title: Table of Contents +mainfont: DejaVu Sans +Filter preferences: + - pandoc-crossref +linkReferences: true +--- \ No newline at end of file diff --git a/scripts/pagebreak.lua b/scripts/pagebreak.lua new file mode 100644 index 0000000..49c51c5 --- /dev/null +++ b/scripts/pagebreak.lua @@ -0,0 +1,125 @@ +--[[ +pagebreak – convert raw LaTeX page breaks to other formats + +Copyright © 2017-2023 Benct Philip Jonsson, Albert Krewinkel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +]] +local stringify = (require 'pandoc.utils').stringify + +--- configs – these are populated in the Meta filter. +local default_pagebreaks = { + asciidoc = '<<<\n\n', + context = '\\page', + epub = '

    ', + html = '
    ', + latex = '\\newpage{}', + ms = '.bp', + ooxml = '', + odt = '' +} + +local function pagebreak_from_config (config) + local pagebreak = default_pagebreaks + local html_class = config['html-class'] + and stringify(config['html-class']) + or os.getenv 'PANDOC_PAGEBREAK_HTML_CLASS' + if html_class and html_class ~= '' then + pagebreak.html = string.format('
    ', html_class) + end + + local odt_style = config['odt-style'] + and stringify(config['odt-style']) + or os.getenv 'PANDOC_PAGEBREAK_ODT_STYLE' + if odt_style and odt_style ~= '' then + pagebreak.odt = string.format('', odt_style) + end + + return pagebreak +end + +--- Return a block element causing a page break in the given format. +local function newpage(format, pagebreak) + if format:match 'asciidoc' then + return pandoc.RawBlock('asciidoc', pagebreak.asciidoc) + elseif format == 'context' then + return pandoc.RawBlock('context', pagebreak.context) + elseif format == 'docx' then + return pandoc.RawBlock('openxml', pagebreak.ooxml) + elseif format:match 'epub' then + return pandoc.RawBlock('html', pagebreak.epub) + elseif format:match 'html.*' then + return pandoc.RawBlock('html', pagebreak.html) + elseif format:match 'latex' then + return pandoc.RawBlock('tex', pagebreak.latex) + elseif format:match 'ms' then + return pandoc.RawBlock('ms', pagebreak.ms) + elseif format:match 'odt' then + return pandoc.RawBlock('opendocument', pagebreak.odt) + else + -- fall back to insert a form feed character + return pandoc.Para{pandoc.Str '\f'} + end +end + +--- Checks whether the given string contains a LaTeX pagebreak or +--- newpage command. +local function is_newpage_command(command) + return command:match '^\\newpage%{?%}?$' + or command:match '^\\pagebreak%{?%}?$' +end + +-- Returns a filter function for RawBlock elements, checking for LaTeX +-- pagebreak/newpage commands; returns `nil` when the target format is +-- LaTeX. +local function latex_pagebreak (pagebreak) + -- Don't do anything if the output is TeX + if FORMAT:match 'tex$' then + return nil + end + return function (el) + -- check that the block is TeX or LaTeX and contains only + -- \newpage or \pagebreak. + if el.format:match 'tex' and is_newpage_command(el.text) then + -- use format-specific pagebreak marker. FORMAT is set by pandoc to + -- the targeted output format. + return pagebreak + end + -- otherwise, leave the block unchanged + return nil + end +end + +-- Turning paragraphs which contain nothing but a form feed +-- characters into line breaks. +local function ascii_pagebreak (raw_pagebreak) + return function (el) + if #el.content == 1 and el.content[1].text == '\f' then + return raw_pagebreak + end + end +end + +--- Filter function; this is the entrypoint when used as a filter. +function Pandoc (doc) + local config = doc.meta.pagebreak or {} + local break_on = config['break-on'] or {} + local raw_pagebreak = newpage(FORMAT, pagebreak_from_config(doc.meta)) + return doc:walk { + RawBlock = latex_pagebreak(raw_pagebreak), + -- Replace paragraphs that contain just a form feed char. + Para = break_on['form-feed'] + and ascii_pagebreak(raw_pagebreak) + or nil + } +end diff --git a/tempidgcourse/01 - The heart of Rx, IObservable.dib b/tempidgcourse/01 - The heart of Rx, IObservable.dib new file mode 100644 index 0000000..d0e66f8 --- /dev/null +++ b/tempidgcourse/01 - The heart of Rx, IObservable.dib @@ -0,0 +1,230 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"languageName":"csharp","name":"csharp"}]}} + +#!csharp + +//#r "nuget:System.Reactive,6.0-*" +using System.Reactive; +using System.Reactive.Linq; + +#!markdown + +# The heart of Rx: `IObservable` + +The Reactive Extensions to .NET (Rx.NET, or Rx for short) are useful in any program that needs to take action when something happens. + +Rx embodies a fundamental programming concept in the same way that loops, lists, and recursion are each fundamental programming concepts. At its core is a deceptively simple abstraction, `IObservable`. Here's one: + +#!csharp + +IObservable numbers = Observable.Range(1, 5); + +#!markdown + +This `numbers` observable represents a sequence of numbers, from 1 to 5. You might be thinking that C# already knows all about sequences of things. Can't we represent this sort of thing with `IEnumerable`? Well, yes: + +#!csharp + +// Non-reactive +IEnumerable nonReactive = Enumerable.Range(1, 5); +foreach (int value in nonReactive) +{ + Console.WriteLine(value); +} + +#!markdown + +The difference is that an `IObservable` gives us values when they are available; an `IEnumerable` supplies values when we ask for them. That makes `IObservable` a better model for some sources of information. For example, financial information, data from monitoring devices, and user interactions all tend to happen on their own schedule. If you want to run code when the price of a financial instrument crosses some threshold, a `foreach` loop is not a natural way to model that. + +Since an `IObservable` provides values as and when they are available, the code to consume these values looks different. We need to provide code that the `IObservable` can call when it has a value: + +#!csharp + +numbers.Subscribe(value => Console.WriteLine(value)); + +#!markdown + +Here, we've _subscribed_ to the observable source, `numbers`, supplying it with a method that it can call back each time it has a value for us. + +Now this particular example is a bit pointless because the nature of the source means that all the numbers are available immediately, so it supplies the first value as soon as we call `Subscribe`, and as soon as we've processed that, it immediately supplies the next, and so on. + +Although you _can_ represent a sequence of numbers as an `IObservable`, doing so doesn't really offer any advantage over an `IEnumerable`. `IObservable` is better suited to situations where values naturally become available at a particular moment in time. + +Let's look at a real example: observing the movement of ships through the water. + +## Using Rx with AIS to track ships + +Ocean-going ships typically have equipment that tracks location using GPS (Global Positioning System) and which also monitors speed, heading, and various other attributes, and then transmits this information in radio messages. This enables vessels that are close to one another to be aware of which other vessels are in the area, where they are, and where they're going. The standard for these radio messages is called AIS (Automatic Identification System), and it is a legal requirement for many kinds of vessels to operate AIS equipment. + +Ships send AIS messages on their own schedule, typically increasing the rate when they are on the move. This makes this kind of information a natural fit for Rx. + +[endjin](endjin.com) maintains [Ais.Net](https://github.com/ais-dotnet/), a set of open source libraries for working with AIS messages. We're going to use these libraries to show how Rx can handle live information sources. + +First, we'll bring in the library that provides an Rx wrapper around AIS: + +#!csharp + +#r nuget:Ais.Net.Receiver + +#!markdown + +We will be using types from this library in a couple of namespaces. One provides types that represent the structure of AIS messages. The other provides the "AIS Receiver", a component that can receive live AIS messages over the internet. + +#!csharp + +using Ais.Net.Models.Abstractions; +using Ais.Net.Receiver.Receiver; + +#!markdown + +Now we can connect a receiver to a publicly available source of live AIS messages. The Norwegian government very helpfully provides live AIS messages from its coastal waters. + +Our `NetworkStreamNmeaReceiver` provides access to the raw messages. (NMEA is the format in which the messages are sent.) And the `ReceiverHost` parses those messages and presents them through Rx as an `IObservable`. + +#!csharp + +INmeaReceiver receiver = new NetworkStreamNmeaReceiver( + host: "153.44.253.27", // Norwegian government AIS service + port: 5631, + retryPeriodicity: TimeSpan.FromSeconds(1), + retryAttemptLimit: 100); + +ReceiverHost receiverHost = new (receiver); + +IObservable aisMessages = receiverHost.Messages; + +// Note that by starting the receiver now, it will be running (inside the .NET Interactive +// host process for this Polyglot Notebook) until either the notebook is closed, or we +// explicitly stop the host through the cancellation token passed here. +System.Threading.CancellationTokenSource aisReceiverStop = new(); +_ = Task.Run(async () => await receiverHost.StartAsync(aisReceiverStop.Token)); + +#!markdown + +**Note**: towards the end of this notebook there's a cell to shut down the `ReceiverHost` we just started. It will remain connected until shut down, or until you close this notebook (or restat the notebook kernel). + +#!markdown + +The AIS observer uses the TPL thread pool, so it will deliver notifications on thread pool threads. Rx is perfectly happy with that, but when you do that in a Polyglot Notebook, calls to `Console.WriteLine` stop working. We need to jump through some hoops to ensure output continues to work. This class enables us to produce output even when we're no longer on the main thread: + +#!csharp + +using Microsoft.DotNet.Interactive; + +public class OutContext +{ + private KernelInvocationContext ctx = KernelInvocationContext.Current; + + public void WriteLine(string text) => + KernelInvocationContextExtensions.DisplayStandardOut(ctx, text + System.Environment.NewLine); +} + +#!markdown + +Now that we've got a way of displaying output, let's subscribe to the AIS for a few seconds and see what happens: + +#!csharp + +// Unlike Observable.Range, the AIS message source does not naturally come to an end, so we should +// unsubscribe from it once we're done. To enable this, IObservable.Subscribe returns an +// IDisposable. We can call Dispose on that to tell the observable source that we no longer wish +// to continue receiving messages. + +OutContext ctx = new(); +using (aisMessages.Subscribe(m => ctx.WriteLine($"Message type: {m.MessageType}, vessel id: {m.Mmsi}"))) +{ + // Listen briefly. (Very briefly - it's a busy source!) + await Task.Delay(TimeSpan.FromSeconds(2)); +} + +#!markdown + +## The power of Rx: expressiveness and LINQ + +So far, we've not seen anything particularly special—this just appears to be simple callback-based event delivery. But the true power of Rx is that it offers a very expressive way to describe how to process events. To demonstrate this, let's build up an example. + +If you have run the code cells up to this point, you'll have seen in the last example that there are several different kinds of AIS message. The libraries define specialized interfaces available only on certain messages. For example, ships don't tend to change name very often so they don't broadcast their name every time they report their location. They send much less frequent messages that report fixed information such as their name and dimensions. We can use LINQ's `OfType` operator to filter the messages down just to this kind of message: + +#!csharp + +IObservable vesselNames = aisMessages.OfType(); + +#!markdown + +And now we can subscribe to that to report vessel names: + +#!csharp + +OutContext ctx = new(); +using (vesselNames.Subscribe(m => ctx.WriteLine($"Vessel: {m.VesselName}"))) +{ + await Task.Delay(TimeSpan.FromSeconds(2)); +} + +#!markdown + +Notice that we get far fewer messages. This confirms that vessels typically report their names far less often than they report their locations. + +We can write more complex expressions. What if we want to take the separate messages that report the vessel name and position and recombine them, in order to get a stream of messages that tell us both the name and location? We could use LINQ's grouping features to first group all messages by vessel ID: + +#!csharp + +IObservable> vesselsGroupedById = aisMessages.GroupBy(m => m.Mmsi); + +#!markdown + +This is an observable source of observable sources: each time the `GroupBy` operator sees a message from a vessel it hasn't seen before (based on the vessel's `Mmsi`), it creates a new `IGroupedObservable` to represent that and emits that as output of this `vesselsGroupedById` source. + +Now, within each of these groups, let's pick out the latest messages reporting vessel name and navigation data: + +#!csharp + +IObservable<(IVesselName Name, IVesselNavigation Navigation)> namesAndNavigation = vesselsGroupedById + .SelectMany(g => Observable.CombineLatest( + g.OfType(), + g.OfType(), + (name, nav) => (name, nav))); + +#!markdown + +Of course, to see this information, we need to subscribe to this new observable source: + +#!csharp + +OutContext ctx = new(); +using (namesAndNavigation.Subscribe(m => ctx.WriteLine($"Vessel: {m.Name.VesselName} is at {m.Navigation.Position}, moving at speed {m.Navigation.SpeedOverGround}"))) +{ + await Task.Delay(TimeSpan.FromSeconds(10)); +} + +#!markdown + +Most of the standard LINQ operators are available, so we could, for example use `Where` to ensure we only show vessels moving above a certain speed: + +#!csharp + +IObservable<(IVesselName Name, IVesselNavigation Navigation)> movingVessels = namesAndNavigation + .Where(m => m.Navigation.SpeedOverGround > 0.1); + +#!markdown + +Again, defining an `IObservable` just describes the information source. To receive events from that source we must subscribe: + +#!csharp + +OutContext ctx = new(); +using (movingVessels.Subscribe(m => ctx.WriteLine($"Vessel: {m.Name.VesselName} is at {m.Navigation.Position}, moving at speed {m.Navigation.SpeedOverGround}"))) +{ + // We'll give this a little longer, since a lot of vessels are stationary. + await Task.Delay(TimeSpan.FromSeconds(30)); +} + +#!markdown + +The AIS `ReceiverHost` is what Rx calls a 'hot' source, meaning it represents a live source of data. Although we had to subscribe to receive the data it supplies, our implementation remains connected to the AIS server even if there are currently no active subscribers. It has been running in the background the whole time. It will stop when you close the notebook, but if you want to disconnect right now, you can run this next code cell: + +#!csharp + +// This shuts down the AIS receiver +aisReceiverStop.Cancel(); diff --git a/tempidgcourse/JsInteractivityExperiments.dib b/tempidgcourse/JsInteractivityExperiments.dib new file mode 100644 index 0000000..50ec4f7 --- /dev/null +++ b/tempidgcourse/JsInteractivityExperiments.dib @@ -0,0 +1,71 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"languageName":"csharp","name":"csharp"}]}} + +#!csharp + +using Microsoft.DotNet.Interactive; +using Microsoft.DotNet.Interactive.Commands; +var jsKernel = Kernel.Root.FindKernelByName("javascript"); + +#!html + +Output: +
    +
    + +#!javascript + +show = function(text) { + var o = document.getElementById("output"); + o.innerText += text; +} + +#!javascript + +show("test"); + +#!csharp + +Kernel.Javascript("show(from c#);") + +#!csharp + +var jsKernel = Kernel.Root.FindKernelByName("javascript"); + +#!csharp + +jsKernel + +#!csharp + +await jsKernel.SendAsync(new SubmitCode("show(\"from C#\");")); + +#!csharp + +//#r System.Reactive,6* +using System; +using System.Reactive; +using System.Reactive.Linq; + +#!csharp + +{ + using var _ = Observable + .Timer(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1)) + .Subscribe(t => Console.WriteLine($"Tick {t}")); + await Task.Delay(TimeSpan.FromSeconds(5)); +} + +#!csharp + +TaskCompletionSource done = new(); +Observable + .Timer(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1)) + .Take(5) + .Subscribe( + t => Console.WriteLine($"Tick {t}"), + () => done.SetResult() + ); + +await done.Task; diff --git a/templates/csharp-highlighting.tex b/templates/csharp-highlighting.tex new file mode 100644 index 0000000..219fc95 --- /dev/null +++ b/templates/csharp-highlighting.tex @@ -0,0 +1,36 @@ +\usepackage{listings, xcolor} + +\definecolor{verylightgray}{rgb}{.97,.97,.97} + +\lstdefinelanguage{CSharp}{ +keywords=[1]{abstract, as, base, bool, break, byte, case, catch, char, checked, class, const, continue, decimal, default, delegate, do, double, else, enum, event, explicit, extern, false, finally, fixed, float, for, foreach, goto, if, implicit, in, int, interface, internal, is, lock, long, namespace, new, null, object, operator, out, override, params, private, protected, public, readonly, ref, return, sbyte, sealed, short, sizeof, stackalloc, static, string, struct, switch, this, throw, true, try, typeof, uint, ulong, unchecked, unsafe, ushort, using, virtual, void, volatile, while}, % generic keywords +keywordstyle=[1]\color{blue}\bfseries, +keywords=[2]{bool?, byte?, char?, decimal?, double?, dynamic, float?, int?, long?, object?, sbyte?, short?, string, uint?, ulong?, ushort?}, % nullable value types +keywordstyle=[2]\color{teal}\bfseries, +keywords=[3]{add, alias, ascending, async, await, by, descending, dynamic, equals, from, get, global, group, into, join, let, nameof, on, orderby, partial, remove, select, set, value, var, when, where, yield}, % contextual keywords +keywordstyle=[3]\color{violet}\bfseries, +identifierstyle=\color{black}, +sensitive=true, +comment=[l]{//}, +morecomment=[s]{/}{/}, +commentstyle=\color{gray!50}\itshape\small\setlength{\fboxsep}{0pt}\setlength{\fboxrule}{0.5pt}\colorbox{verylightgray}, +stringstyle=\color{red}\ttfamily, +morestring=[b]', +morestring=[b]" +} + +\lstset{ +language=CSharp, +backgroundcolor=\color{verylightgray}, +extendedchars=true, +basicstyle=\footnotesize\ttfamily, +showstringspaces=false, +showspaces=false, +numbers=left, +numberstyle=\footnotesize, +numbersep=9pt, +tabsize=2, +breaklines=true, +showtabs=false, +captionpos=b +} diff --git a/templates/docx.docx b/templates/docx.docx new file mode 100644 index 0000000..d9ce0db Binary files /dev/null and b/templates/docx.docx differ diff --git a/templates/eisvogel.aux b/templates/eisvogel.aux new file mode 100644 index 0000000..b640121 --- /dev/null +++ b/templates/eisvogel.aux @@ -0,0 +1,2 @@ +\relax +\gdef \@abspage@last{1} diff --git a/templates/eisvogel.fdb_latexmk b/templates/eisvogel.fdb_latexmk new file mode 100644 index 0000000..e8e264a --- /dev/null +++ b/templates/eisvogel.fdb_latexmk @@ -0,0 +1,17 @@ +# Fdb version 4 +["pdflatex"] 1695029184 "c:/_Projects/OSS/reactive-extensions/IntroToRx/templates/eisvogel.tex" "eisvogel.pdf" "eisvogel" 1695029184 2 + "$if(beamer)$$documentclass$$else$$if(book)$scrbook$else$scrartcl$endif$$endif$.cls" 0 -1 0 "" + "C:/Users/HowardvanRooijen/AppData/Local/MiKTeX/miktex/data/le/pdftex/pdflatex.fmt" 1689317134 24124063 eda354e1746a9a0fafc01d4da2722663 "" + "C:/Users/HowardvanRooijen/AppData/Local/Programs/MiKTeX/tex/latex/hyperref/hyperref.sty" 1687702344 220504 46df85c2e38e43c3f3b0192fdbc1150d "" + "C:/Users/HowardvanRooijen/AppData/Local/Programs/MiKTeX/tex/latex/url/url.sty" 1388490452 12796 8edb7d69a20b857904dd0ea757c14ec9 "" + "C:/Users/HowardvanRooijen/AppData/Local/Programs/MiKTeX/tex/latex/xcolor/xcolor.sty" 1656236890 56148 51a9a8571c07b9921892ae11063ae853 "" + "C:/Users/HowardvanRooijen/AppData/Local/Programs/MiKTeX/tex/xelatex/bidi/bidi.sty" 1690972292 11205 1985c5c493bbe42ba3bdf13a7563b61b "" + "C:/Users/HowardvanRooijen/AppData/Local/Programs/MiKTeX/tex/xelatex/xecjk/xeCJK.sty" 1659726270 183137 ed372550124703b2b73cbcc0b47cb20f "" + "c:/_Projects/OSS/reactive-extensions/IntroToRx/templates/eisvogel.tex" 1695029171 29249 f2245eadf683cf988d5c29d05f153529 "" + "eisvogel.aux" 1695029184 34 3985256e7290058c681f74d7a3565a19 "pdflatex" + "eisvogel.tex" 1695029171 29249 f2245eadf683cf988d5c29d05f153529 "" + (generated) + "eisvogel.aux" + "eisvogel.log" + "eisvogel.pdf" + (rewritten before read) diff --git a/templates/eisvogel.fls b/templates/eisvogel.fls new file mode 100644 index 0000000..3e4128a --- /dev/null +++ b/templates/eisvogel.fls @@ -0,0 +1,9 @@ +PWD c:\_Projects\OSS\reactive-extensions\IntroToRx\templates +INPUT C:\Users\HowardvanRooijen\AppData\Local\MiKTeX\miktex\data\le\pdftex\pdflatex.fmt +INPUT c:\_Projects\OSS\reactive-extensions\IntroToRx\templates\eisvogel.tex +OUTPUT eisvogel.log +INPUT C:\Users\HowardvanRooijen\AppData\Local\Programs\MiKTeX\tex\latex\hyperref\hyperref.sty +INPUT C:\Users\HowardvanRooijen\AppData\Local\Programs\MiKTeX\tex\latex\url\url.sty +INPUT C:\Users\HowardvanRooijen\AppData\Local\Programs\MiKTeX\tex\latex\xcolor\xcolor.sty +INPUT C:\Users\HowardvanRooijen\AppData\Local\Programs\MiKTeX\tex\xelatex\bidi\bidi.sty +INPUT C:\Users\HowardvanRooijen\AppData\Local\Programs\MiKTeX\tex\xelatex\xecjk\xeCJK.sty diff --git a/templates/eisvogel.latex b/templates/eisvogel.latex new file mode 100644 index 0000000..195d3cf --- /dev/null +++ b/templates/eisvogel.latex @@ -0,0 +1,1034 @@ +%% +% Copyright (c) 2017 - 2021, Pascal Wagler; +% Copyright (c) 2014 - 2021, John MacFarlane +% +% All rights reserved. +% +% Redistribution and use in source and binary forms, with or without +% modification, are permitted provided that the following conditions +% are met: +% +% - Redistributions of source code must retain the above copyright +% notice, this list of conditions and the following disclaimer. +% +% - Redistributions in binary form must reproduce the above copyright +% notice, this list of conditions and the following disclaimer in the +% documentation and/or other materials provided with the distribution. +% +% - Neither the name of John MacFarlane nor the names of other +% contributors may be used to endorse or promote products derived +% from this software without specific prior written permission. +% +% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +% COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +% POSSIBILITY OF SUCH DAMAGE. +%% + +%% +% This is the Eisvogel pandoc LaTeX template. +% +% For usage information and examples visit the official GitHub page: +% https://github.com/Wandmalfarbe/pandoc-latex-template +%% + +% Options for packages loaded elsewhere +\PassOptionsToPackage{unicode$for(hyperrefoptions)$,$hyperrefoptions$$endfor$}{hyperref} +\PassOptionsToPackage{hyphens}{url} +\PassOptionsToPackage{dvipsnames,svgnames*,x11names*,table}{xcolor} +$if(dir)$ +$if(latex-dir-rtl)$ +\PassOptionsToPackage{RTLdocument}{bidi} +$endif$ +$endif$ +$if(CJKmainfont)$ +\PassOptionsToPackage{space}{xeCJK} +$endif$ +% +\documentclass[ +$if(fontsize)$ + $fontsize$, +$endif$ +$if(lang)$ + $babel-lang$, +$endif$ +$if(papersize)$ + $papersize$paper, +$else$ + paper=a4, +$endif$ +$if(beamer)$ + ignorenonframetext, +$if(handout)$ + handout, +$endif$ +$if(aspectratio)$ + aspectratio=$aspectratio$, +$endif$ +$endif$ +$for(classoption)$ + $classoption$$sep$, +$endfor$ + ,captions=tableheading +]{$if(beamer)$$documentclass$$else$$if(book)$scrbook$else$scrartcl$endif$$endif$} +$if(beamer)$ +$if(background-image)$ +\usebackgroundtemplate{% + \includegraphics[width=\paperwidth]{$background-image$}% +} +$endif$ +\usepackage{pgfpages} +\setbeamertemplate{caption}[numbered] +\setbeamertemplate{caption label separator}{: } +\setbeamercolor{caption name}{fg=normal text.fg} +\beamertemplatenavigationsymbols$if(navigation)$$navigation$$else$empty$endif$ +$for(beameroption)$ +\setbeameroption{$beameroption$} +$endfor$ +% Prevent slide breaks in the middle of a paragraph +\widowpenalties 1 10000 +\raggedbottom +$if(section-titles)$ +\setbeamertemplate{part page}{ + \centering + \begin{beamercolorbox}[sep=16pt,center]{part title} + \usebeamerfont{part title}\insertpart\par + \end{beamercolorbox} +} +\setbeamertemplate{section page}{ + \centering + \begin{beamercolorbox}[sep=12pt,center]{part title} + \usebeamerfont{section title}\insertsection\par + \end{beamercolorbox} +} +\setbeamertemplate{subsection page}{ + \centering + \begin{beamercolorbox}[sep=8pt,center]{part title} + \usebeamerfont{subsection title}\insertsubsection\par + \end{beamercolorbox} +} +\AtBeginPart{ + \frame{\partpage} +} +\AtBeginSection{ + \ifbibliography + \else + \frame{\sectionpage} + \fi +} +\AtBeginSubsection{ + \frame{\subsectionpage} +} +$endif$ +$endif$ +$if(beamerarticle)$ +\usepackage{beamerarticle} % needs to be loaded first +$endif$ +\usepackage{amsmath,amssymb} +$if(fontfamily)$ +\usepackage[$for(fontfamilyoptions)$$fontfamilyoptions$$sep$,$endfor$]{$fontfamily$} +$else$ +\usepackage{lmodern} +$endif$ +$if(linestretch)$ +\usepackage{setspace} +$else$ +\usepackage{setspace} +\setstretch{1.2} +$endif$ +\usepackage{ifxetex,ifluatex} +\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex + \usepackage[$if(fontenc)$$fontenc$$else$T1$endif$]{fontenc} + \usepackage[utf8]{inputenc} + \usepackage{textcomp} % provide euro and other symbols +\else % if luatex or xetex +$if(mathspec)$ + \ifxetex + \usepackage{mathspec} + \else + \usepackage{unicode-math} + \fi +$else$ + \usepackage{unicode-math} +$endif$ + \defaultfontfeatures{Scale=MatchLowercase} + \defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1} +$if(mainfont)$ + \setmainfont[$for(mainfontoptions)$$mainfontoptions$$sep$,$endfor$]{$mainfont$} +$endif$ +$if(sansfont)$ + \setsansfont[$for(sansfontoptions)$$sansfontoptions$$sep$,$endfor$]{$sansfont$} +$endif$ +$if(monofont)$ + \setmonofont[$for(monofontoptions)$$monofontoptions$$sep$,$endfor$]{$monofont$} +$endif$ +$for(fontfamilies)$ + \newfontfamily{$fontfamilies.name$}[$for(fontfamilies.options)$$fontfamilies.options$$sep$,$endfor$]{$fontfamilies.font$} +$endfor$ +$if(mathfont)$ +$if(mathspec)$ + \ifxetex + \setmathfont(Digits,Latin,Greek)[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} + \else + \setmathfont[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} + \fi +$else$ + \setmathfont[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} +$endif$ +$endif$ +$if(CJKmainfont)$ + \ifxetex + \usepackage{xeCJK} + \setCJKmainfont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKmainfont$} + \fi +$endif$ +$if(luatexjapresetoptions)$ + \ifluatex + \usepackage[$for(luatexjapresetoptions)$$luatexjapresetoptions$$sep$,$endfor$]{luatexja-preset} + \fi +$endif$ +$if(CJKmainfont)$ + \ifluatex + \usepackage[$for(luatexjafontspecoptions)$$luatexjafontspecoptions$$sep$,$endfor$]{luatexja-fontspec} + \setmainjfont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKmainfont$} + \fi +$endif$ +\fi +$if(beamer)$ +$if(theme)$ +\usetheme[$for(themeoptions)$$themeoptions$$sep$,$endfor$]{$theme$} +$endif$ +$if(colortheme)$ +\usecolortheme{$colortheme$} +$endif$ +$if(fonttheme)$ +\usefonttheme{$fonttheme$} +$endif$ +$if(mainfont)$ +\usefonttheme{serif} % use mainfont rather than sansfont for slide text +$endif$ +$if(innertheme)$ +\useinnertheme{$innertheme$} +$endif$ +$if(outertheme)$ +\useoutertheme{$outertheme$} +$endif$ +$endif$ +% Use upquote if available, for straight quotes in verbatim environments +\IfFileExists{upquote.sty}{\usepackage{upquote}}{} +\IfFileExists{microtype.sty}{% use microtype if available + \usepackage[$for(microtypeoptions)$$microtypeoptions$$sep$,$endfor$]{microtype} + \UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts +}{} +$if(indent)$ +$else$ +\makeatletter +\@ifundefined{KOMAClassName}{% if non-KOMA class + \IfFileExists{parskip.sty}{% + \usepackage{parskip} + }{% else + \setlength{\parindent}{0pt} + \setlength{\parskip}{6pt plus 2pt minus 1pt}} +}{% if KOMA class + \KOMAoptions{parskip=half}} +\makeatother +$endif$ +$if(verbatim-in-note)$ +\usepackage{fancyvrb} +$endif$ +\usepackage{xcolor} +\definecolor{default-linkcolor}{HTML}{A50000} +\definecolor{default-filecolor}{HTML}{A50000} +\definecolor{default-citecolor}{HTML}{4077C0} +\definecolor{default-urlcolor}{HTML}{4077C0} +\IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available +$if(footnotes-pretty)$ +% load footmisc in order to customize footnotes (footmisc has to be loaded before hyperref, cf. https://tex.stackexchange.com/a/169124/144087) +\usepackage[hang,flushmargin,bottom,multiple]{footmisc} +\setlength{\footnotemargin}{0.8em} % set space between footnote nr and text +\setlength{\footnotesep}{\baselineskip} % set space between multiple footnotes +\setlength{\skip\footins}{0.3cm} % set space between page content and footnote +\setlength{\footskip}{0.9cm} % set space between footnote and page bottom +$endif$ +\IfFileExists{bookmark.sty}{\usepackage{bookmark}}{\usepackage{hyperref}} +\hypersetup{ +$if(title-meta)$ + pdftitle={$title-meta$}, +$endif$ +$if(author-meta)$ + pdfauthor={$author-meta$}, +$endif$ +$if(lang)$ + pdflang={$lang$}, +$endif$ +$if(subject)$ + pdfsubject={$subject$}, +$endif$ +$if(keywords)$ + pdfkeywords={$for(keywords)$$keywords$$sep$, $endfor$}, +$endif$ +$if(colorlinks)$ + colorlinks=true, + linkcolor=$if(linkcolor)$$linkcolor$$else$default-linkcolor$endif$, + filecolor=$if(filecolor)$$filecolor$$else$default-filecolor$endif$, + citecolor=$if(citecolor)$$citecolor$$else$default-citecolor$endif$, + urlcolor=$if(urlcolor)$$urlcolor$$else$default-urlcolor$endif$, +$else$ + hidelinks, +$endif$ + breaklinks=true, + pdfcreator={LaTeX via pandoc with the Eisvogel template}} +\urlstyle{same} % disable monospaced font for URLs +$if(verbatim-in-note)$ +\VerbatimFootnotes % allow verbatim text in footnotes +$endif$ +$if(geometry)$ +$if(beamer)$ +\geometry{$for(geometry)$$geometry$$sep$,$endfor$} +$else$ +\usepackage[$for(geometry)$$geometry$$sep$,$endfor$]{geometry} +$endif$ +$else$ +$if(beamer)$ +$else$ +\usepackage[margin=2.5cm,includehead=true,includefoot=true,centering,$for(geometry)$$geometry$$sep$,$endfor$]{geometry} +$endif$ +$endif$ +$if(logo)$ +\usepackage[export]{adjustbox} +\usepackage{graphicx} +$endif$ +$if(beamer)$ +\newif\ifbibliography +$endif$ +$if(listings)$ +\usepackage{listings} +\newcommand{\passthrough}[1]{#1} +\lstset{defaultdialect=[5.3]Lua} +\lstset{defaultdialect=[x86masm]Assembler} +$endif$ +$if(listings-no-page-break)$ +\usepackage{etoolbox} +\BeforeBeginEnvironment{lstlisting}{\par\noindent\begin{minipage}{\linewidth}} +\AfterEndEnvironment{lstlisting}{\end{minipage}\par\addvspace{\topskip}} +$endif$ +$if(lhs)$ +\lstnewenvironment{code}{\lstset{language=Haskell,basicstyle=\small\ttfamily}}{} +$endif$ +$if(highlighting-macros)$ +$highlighting-macros$ + +% Workaround/bugfix from jannick0. +% See https://github.com/jgm/pandoc/issues/4302#issuecomment-360669013) +% or https://github.com/Wandmalfarbe/pandoc-latex-template/issues/2 +% +% Redefine the verbatim environment 'Highlighting' to break long lines (with +% the help of fvextra). Redefinition is necessary because it is unlikely that +% pandoc includes fvextra in the default template. +\usepackage{fvextra} +\DefineVerbatimEnvironment{Highlighting}{Verbatim}{breaklines,fontsize=$if(code-block-font-size)$$code-block-font-size$$else$\small$endif$,commandchars=\\\{\}} + +$endif$ +$if(tables)$ +\usepackage{longtable,booktabs,array} +$if(multirow)$ +\usepackage{multirow} +$endif$ +\usepackage{calc} % for calculating minipage widths +$if(beamer)$ +\usepackage{caption} +% Make caption package work with longtable +\makeatletter +\def\fnum@table{\tablename~\thetable} +\makeatother +$else$ +% Correct order of tables after \paragraph or \subparagraph +\usepackage{etoolbox} +\makeatletter +\patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{} +\makeatother +% Allow footnotes in longtable head/foot +\IfFileExists{footnotehyper.sty}{\usepackage{footnotehyper}}{\usepackage{footnote}} +\makesavenoteenv{longtable} +$endif$ +$endif$ +% add backlinks to footnote references, cf. https://tex.stackexchange.com/questions/302266/make-footnote-clickable-both-ways +$if(footnotes-disable-backlinks)$ +$else$ +\usepackage{footnotebackref} +$endif$ +$if(graphics)$ +\usepackage{graphicx} +\makeatletter +\def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi} +\def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi} +\makeatother +% Scale images if necessary, so that they will not overflow the page +% margins by default, and it is still possible to overwrite the defaults +% using explicit options in \includegraphics[width, height, ...]{} +\setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio} +% Set default figure placement to htbp +\makeatletter +\def\fps@figure{htbp} +\makeatother +$endif$ +$if(links-as-notes)$ +% Make links footnotes instead of hotlinks: +\DeclareRobustCommand{\href}[2]{#2\footnote{\url{#1}}} +$endif$ +$if(strikeout)$ +\usepackage[normalem]{ulem} +% Avoid problems with \sout in headers with hyperref +\pdfstringdefDisableCommands{\renewcommand{\sout}{}} +$endif$ +\setlength{\emergencystretch}{3em} % prevent overfull lines +\providecommand{\tightlist}{% + \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} +$if(numbersections)$ +\setcounter{secnumdepth}{$if(secnumdepth)$$secnumdepth$$else$5$endif$} +$else$ +\setcounter{secnumdepth}{-\maxdimen} % remove section numbering +$endif$ +$if(beamer)$ +$else$ +$if(block-headings)$ +% Make \paragraph and \subparagraph free-standing +\ifx\paragraph\undefined\else + \let\oldparagraph\paragraph + \renewcommand{\paragraph}[1]{\oldparagraph{#1}\mbox{}} +\fi +\ifx\subparagraph\undefined\else + \let\oldsubparagraph\subparagraph + \renewcommand{\subparagraph}[1]{\oldsubparagraph{#1}\mbox{}} +\fi +$endif$ +$endif$ +$if(pagestyle)$ +\pagestyle{$pagestyle$} +$endif$ + +% Make use of float-package and set default placement for figures to H. +% The option H means 'PUT IT HERE' (as opposed to the standard h option which means 'You may put it here if you like'). +\usepackage{float} +\floatplacement{figure}{$if(float-placement-figure)$$float-placement-figure$$else$H$endif$} + +$for(header-includes)$ +$header-includes$ +$endfor$ +$if(lang)$ +\ifxetex + $if(mainfont)$ + $else$ + % See issue https://github.com/reutenauer/polyglossia/issues/127 + \renewcommand*\familydefault{\sfdefault} + $endif$ + % Load polyglossia as late as possible: uses bidi with RTL langages (e.g. Hebrew, Arabic) + \usepackage{polyglossia} + \setmainlanguage[$for(polyglossia-lang.options)$$polyglossia-lang.options$$sep$,$endfor$]{$polyglossia-lang.name$} +$for(polyglossia-otherlangs)$ + \setotherlanguage[$for(polyglossia-otherlangs.options)$$polyglossia-otherlangs.options$$sep$,$endfor$]{$polyglossia-otherlangs.name$} +$endfor$ +\else + \usepackage[$for(babel-otherlangs)$$babel-otherlangs$,$endfor$main=$babel-lang$]{babel} +% get rid of language-specific shorthands (see #6817): +\let\LanguageShortHands\languageshorthands +\def\languageshorthands#1{} +$if(babel-newcommands)$ + $babel-newcommands$ +$endif$ +\fi +$endif$ +\ifluatex + \usepackage{selnolig} % disable illegal ligatures +\fi +$if(dir)$ +\ifxetex + % Load bidi as late as possible as it modifies e.g. graphicx + \usepackage{bidi} +\fi +\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex + \TeXXeTstate=1 + \newcommand{\RL}[1]{\beginR #1\endR} + \newcommand{\LR}[1]{\beginL #1\endL} + \newenvironment{RTL}{\beginR}{\endR} + \newenvironment{LTR}{\beginL}{\endL} +\fi +$endif$ +$if(natbib)$ +\usepackage[$natbiboptions$]{natbib} +\bibliographystyle{$if(biblio-style)$$biblio-style$$else$plainnat$endif$} +$endif$ +$if(biblatex)$ +\usepackage[$if(biblio-style)$style=$biblio-style$,$endif$$for(biblatexoptions)$$biblatexoptions$$sep$,$endfor$]{biblatex} +$for(bibliography)$ +\addbibresource{$bibliography$} +$endfor$ +$endif$ +$if(csl-refs)$ +\newlength{\cslhangindent} +\setlength{\cslhangindent}{1.5em} +\newlength{\csllabelwidth} +\setlength{\csllabelwidth}{3em} +\newenvironment{CSLReferences}[2] % #1 hanging-ident, #2 entry spacing + {% don't indent paragraphs + \setlength{\parindent}{0pt} + % turn on hanging indent if param 1 is 1 + \ifodd #1 \everypar{\setlength{\hangindent}{\cslhangindent}}\ignorespaces\fi + % set entry spacing + \ifnum #2 > 0 + \setlength{\parskip}{#2\baselineskip} + \fi + }% + {} +\usepackage{calc} +\newcommand{\CSLBlock}[1]{#1\hfill\break} +\newcommand{\CSLLeftMargin}[1]{\parbox[t]{\csllabelwidth}{#1}} +\newcommand{\CSLRightInline}[1]{\parbox[t]{\linewidth - \csllabelwidth}{#1}\break} +\newcommand{\CSLIndent}[1]{\hspace{\cslhangindent}#1} +$endif$ +$if(csquotes)$ +\usepackage{csquotes} +$endif$ + +$if(title)$ +\title{$title$$if(thanks)$\thanks{$thanks$}$endif$} +$endif$ +$if(subtitle)$ +$if(beamer)$ +$else$ +\usepackage{etoolbox} +\makeatletter +\providecommand{\subtitle}[1]{% add subtitle to \maketitle + \apptocmd{\@title}{\par {\large #1 \par}}{}{} +} +\makeatother +$endif$ +\subtitle{$subtitle$} +$endif$ +\author{$for(author)$$author$$sep$ \and $endfor$} +\date{$date$} +$if(beamer)$ +$if(institute)$ +\institute{$for(institute)$$institute$$sep$ \and $endfor$} +$endif$ +$if(titlegraphic)$ +\titlegraphic{\includegraphics{$titlegraphic$}} +$endif$ +$if(logo)$ +\logo{\includegraphics{$logo$}} +$endif$ +$endif$ + + + +%% +%% added +%% + +% +% language specification +% +% If no language is specified, use English as the default main document language. +% +$if(lang)$$else$ +\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex + \usepackage[shorthands=off,$for(babel-otherlangs)$$babel-otherlangs$,$endfor$main=english]{babel} +$if(babel-newcommands)$ + $babel-newcommands$ +$endif$ +\else + $if(mainfont)$ + $else$ + % Workaround for bug in Polyglossia that breaks `\familydefault` when `\setmainlanguage` is used. + % See https://github.com/Wandmalfarbe/pandoc-latex-template/issues/8 + % See https://github.com/reutenauer/polyglossia/issues/186 + % See https://github.com/reutenauer/polyglossia/issues/127 + \renewcommand*\familydefault{\sfdefault} + $endif$ + % load polyglossia as late as possible as it *could* call bidi if RTL lang (e.g. Hebrew or Arabic) + \usepackage{polyglossia} + \setmainlanguage[]{english} +$for(polyglossia-otherlangs)$ + \setotherlanguage[$polyglossia-otherlangs.options$]{$polyglossia-otherlangs.name$} +$endfor$ +\fi +$endif$ + +$if(page-background)$ +\usepackage[pages=all]{background} +$endif$ + +% +% for the background color of the title page +% +$if(titlepage)$ +\usepackage{pagecolor} +\usepackage{afterpage} +$if(titlepage-background)$ +\usepackage{tikz} +$endif$ +$if(geometry)$ +$else$ +\usepackage[margin=2.5cm,includehead=true,includefoot=true,centering]{geometry} +$endif$ +$endif$ + +% +% break urls +% +\PassOptionsToPackage{hyphens}{url} + +% +% When using babel or polyglossia with biblatex, loading csquotes is recommended +% to ensure that quoted texts are typeset according to the rules of your main language. +% +\usepackage{csquotes} + +% +% captions +% +\definecolor{caption-color}{HTML}{777777} +$if(beamer)$ +$else$ +\usepackage[font={stretch=1.2}, textfont={color=caption-color}, position=top, skip=4mm, labelfont=bf, singlelinecheck=false, justification=$if(caption-justification)$$caption-justification$$else$raggedright$endif$]{caption} +\setcapindent{0em} +$endif$ + +% +% blockquote +% +\definecolor{blockquote-border}{RGB}{221,221,221} +\definecolor{blockquote-text}{RGB}{119,119,119} +\usepackage{mdframed} +\newmdenv[rightline=false,bottomline=false,topline=false,linewidth=3pt,linecolor=blockquote-border,skipabove=\parskip]{customblockquote} +\renewenvironment{quote}{\begin{customblockquote}\list{}{\rightmargin=0em\leftmargin=0em}% +\item\relax\color{blockquote-text}\ignorespaces}{\unskip\unskip\endlist\end{customblockquote}} + +% +% Source Sans Pro as the de­fault font fam­ily +% Source Code Pro for monospace text +% +% 'default' option sets the default +% font family to Source Sans Pro, not \sfdefault. +% +\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex + $if(fontfamily)$ + $else$ + \usepackage[default]{sourcesanspro} + \usepackage{sourcecodepro} + $endif$ +\else % if not pdftex + $if(mainfont)$ + $else$ + \usepackage[default]{sourcesanspro} + \usepackage{sourcecodepro} + + % XeLaTeX specific adjustments for straight quotes: https://tex.stackexchange.com/a/354887 + % This issue is already fixed (see https://github.com/silkeh/latex-sourcecodepro/pull/5) but the + % fix is still unreleased. + % TODO: Remove this workaround when the new version of sourcecodepro is released on CTAN. + \ifxetex + \makeatletter + \defaultfontfeatures[\ttfamily] + { Numbers = \sourcecodepro@figurestyle, + Scale = \SourceCodePro@scale, + Extension = .otf } + \setmonofont + [ UprightFont = *-\sourcecodepro@regstyle, + ItalicFont = *-\sourcecodepro@regstyle It, + BoldFont = *-\sourcecodepro@boldstyle, + BoldItalicFont = *-\sourcecodepro@boldstyle It ] + {SourceCodePro} + \makeatother + \fi + $endif$ +\fi + +% +% heading color +% +\definecolor{heading-color}{RGB}{40,40,40} +$if(beamer)$ +$else$ +\addtokomafont{section}{\color{heading-color}} +$endif$ +% When using the classes report, scrreprt, book, +% scrbook or memoir, uncomment the following line. +%\addtokomafont{chapter}{\color{heading-color}} + +% +% variables for title, author and date +% +$if(beamer)$ +$else$ +\usepackage{titling} +\title{$title$} +\author{$for(author)$$author$$sep$, $endfor$} +\date{$date$} +$endif$ + +% +% tables +% +$if(tables)$ + +\definecolor{table-row-color}{HTML}{F5F5F5} +\definecolor{table-rule-color}{HTML}{999999} + +%\arrayrulecolor{black!40} +\arrayrulecolor{table-rule-color} % color of \toprule, \midrule, \bottomrule +\setlength\heavyrulewidth{0.3ex} % thickness of \toprule, \bottomrule +\renewcommand{\arraystretch}{1.3} % spacing (padding) + +$if(table-use-row-colors)$ +% TODO: This doesn't work anymore. I don't know why. +% Reset rownum counter so that each table +% starts with the same row colors. +% https://tex.stackexchange.com/questions/170637/restarting-rowcolors +% +% Unfortunately the colored cells extend beyond the edge of the +% table because pandoc uses @-expressions (@{}) like so: +% +% \begin{longtable}[]{@{}ll@{}} +% \end{longtable} +% +% https://en.wikibooks.org/wiki/LaTeX/Tables#.40-expressions +\let\oldlongtable\longtable +\let\endoldlongtable\endlongtable +\renewenvironment{longtable}{ +\rowcolors{3}{}{table-row-color!100} % row color +\oldlongtable} { +\endoldlongtable +\global\rownum=0\relax} +$endif$ +$endif$ + +% +% remove paragraph indention +% +\setlength{\parindent}{0pt} +\setlength{\parskip}{6pt plus 2pt minus 1pt} +\setlength{\emergencystretch}{3em} % prevent overfull lines + +% +% +% Listings +% +% + +$if(listings)$ + +\usepackage{listings} +\usepackage{etoolbox} +\usepackage{color} + +\definecolor{base0}{RGB}{131,148,150} +\definecolor{base01}{RGB}{88,110,117} +\definecolor{base2}{RGB}{238,232,213} +\definecolor{sgreen}{RGB}{133,153,0} +\definecolor{sblue}{RGB}{38,138,210} +\definecolor{scyan}{RGB}{42,161,151} +\definecolor{smagenta}{RGB}{211,54,130} + + +\newcommand\digitstyle{\color{smagenta}} +\newcommand\symbolstyle{\color{base01}} +\makeatletter +\newcommand{\ProcessDigit}[1] +{% + \ifnum\lst@mode=\lst@Pmode\relax% + {\digitstyle #1}% + \else + #1% + \fi +} +\makeatother + +\lstdefinestyle{solarizedcsharp} { + language=[Sharp]C, + frame=lr, + linewidth=160mm, + breaklines=true, + tabsize=2, + numbers=left, + numbersep=5pt, + firstnumber=auto, + numberstyle=\tiny\ttfamily\color{base0}, + rulecolor=\color{base2}, + basicstyle=\footnotesize\ttfamily, + commentstyle=\color{base01}, + morecomment=[s][\color{base01}]{/*+}{*/}, + morecomment=[s][\color{base01}]{/*-}{*/}, + morekeywords={ abstract, event, new, struct, + as, explicit, null, switch, + base, extern, object, this, + bool, false, operator, throw, + break, finally, out, true, + byte, fixed, override, try, + case, float, params, typeof, + catch, for, private, uint, + char, foreach, protected, ulong, + checked, goto, public, unchecked, + class, if, readonly, unsafe, + const, implicit, ref, ushort, + continue, in, return, using, + decimal, int, sbyte, virtual, + default, interface, sealed, volatile, + delegate, internal, short, void, + do, is, sizeof, while, + double, lock, stackalloc, + else, long, static, + enum, namespace, string, var}, + keywordstyle=\bfseries\color{sgreen}, + showstringspaces=false, + stringstyle=\color{scyan}, + identifierstyle=\color{sblue}, + extendedchars=true, +} + +\lstset{escapechar=@,style=solarizedcsharp} + +\lstdefinelanguage{XML}{ + morestring = [b]", + moredelim = [s][\bfseries\color{listing-keyword}]{<}{\ }, + moredelim = [s][\bfseries\color{listing-keyword}]{}, + moredelim = [l][\bfseries\color{listing-keyword}]{/>}, + moredelim = [l][\bfseries\color{listing-keyword}]{>}, + morecomment = [s]{}, + morecomment = [s]{}, + commentstyle = \color{listing-comment}, + stringstyle = \color{listing-string}, + identifierstyle = \color{listing-identifier} +} +$endif$ + +% +% header and footer +% +$if(beamer)$ +$else$ +$if(disable-header-and-footer)$ +$else$ +\usepackage{fancyhdr} + +\fancypagestyle{eisvogel-header-footer}{ + \fancyhead{} + \fancyfoot{} + \lhead[$if(header-right)$$header-right$$else$$date$$endif$]{$if(header-left)$$header-left$$else$$title$$endif$} + \chead[$if(header-center)$$header-center$$else$$endif$]{$if(header-center)$$header-center$$else$$endif$} + \rhead[$if(header-left)$$header-left$$else$$title$$endif$]{$if(header-right)$$header-right$$else$$date$$endif$} + \lfoot[$if(footer-right)$$footer-right$$else$\thepage$endif$]{$if(footer-left)$$footer-left$$else$$for(author)$$author$$sep$, $endfor$$endif$} + \cfoot[$if(footer-center)$$footer-center$$else$$endif$]{$if(footer-center)$$footer-center$$else$$endif$} + \rfoot[$if(footer-left)$$footer-left$$else$$for(author)$$author$$sep$, $endfor$$endif$]{$if(footer-right)$$footer-right$$else$\thepage$endif$} + \renewcommand{\headrulewidth}{0.4pt} + \renewcommand{\footrulewidth}{0.4pt} +} +\pagestyle{eisvogel-header-footer} +$if(page-background)$ +\backgroundsetup{ +scale=1, +color=black, +opacity=$if(page-background-opacity)$$page-background-opacity$$else$0.2$endif$, +angle=0, +contents={% + \includegraphics[width=\paperwidth,height=\paperheight]{$page-background$} + }% +} +$endif$ +$endif$ +$endif$ + +%% +%% end added +%% + +\begin{document} + +%% +%% begin titlepage +%% +$if(beamer)$ +$else$ +$if(titlepage)$ +\begin{titlepage} + $if(titlepage-background)$ + \newgeometry{top=2cm, right=4cm, bottom=3cm, left=4cm} + $else$ + \newgeometry{left=6cm} + $endif$ + $if(titlepage-color)$ + \definecolor{titlepage-color}{HTML}{$titlepage-color$} + \newpagecolor{titlepage-color}\afterpage{\restorepagecolor} + $endif$ + $if(titlepage-background)$ + \tikz[remember picture,overlay] \node[inner sep=0pt] at (current page.center){\includegraphics[width=\paperwidth,height=\paperheight]{$titlepage-background$}}; + $endif$ + \newcommand{\colorRule}[3][black]{\textcolor[HTML]{#1}{\rule{#2}{#3}}} + \begin{flushleft} + \noindent + \\[-1em] + \color[HTML]{$if(titlepage-text-color)$$titlepage-text-color$$else$5F5F5F$endif$} + \makebox[0pt][l]{\colorRule[$if(titlepage-rule-color)$$titlepage-rule-color$$else$435488$endif$]{1.3\textwidth}{$if(titlepage-rule-height)$$titlepage-rule-height$$else$4$endif$pt}} + \par + \noindent + + $if(titlepage-background)$ + % The titlepage with a background image has other text spacing and text size + { + \setstretch{2} + \vfill + \vskip -8em + \noindent {\huge \textbf{\textsf{$title$}}} + $if(subtitle)$ + \vskip 1em + {\Large \textsf{$subtitle$}} + $endif$ + \vskip 2em + \noindent {\Large \textsf{$for(author)$$author$$sep$, $endfor$} \vskip 0.6em \textsf{$date$}} + \vfill + } + $else$ + { + \setstretch{1.4} + \vfill + \noindent {\huge \textbf{\textsf{$title$}}} + $if(subtitle)$ + \vskip 1em + {\Large \textsf{$subtitle$}} + $endif$ + \vskip 2em + \noindent {\Large \textsf{$for(author)$$author$$sep$, $endfor$}} + \vfill + } + $endif$ + + $if(logo)$ + \noindent + \includegraphics[width=$if(logo-width)$$logo-width$$else$35mm$endif$, left]{$logo$} + $endif$ + + $if(titlepage-background)$ + $else$ + \textsf{$date$} + $endif$ + \end{flushleft} +\end{titlepage} +\restoregeometry +$endif$ +$endif$ + +%% +%% end titlepage +%% + +$if(has-frontmatter)$ +\frontmatter +$endif$ +$if(title)$ +$if(beamer)$ +\frame{\titlepage} +$endif$ +$if(abstract)$ +\begin{abstract} + $abstract$ +\end{abstract} +$endif$ +$endif$ + +$if(first-chapter)$ +\setcounter{chapter}{$first-chapter$} +\addtocounter{chapter}{-1} +$endif$ + +$for(include-before)$ +$include-before$ + +$endfor$ +$if(toc)$ +$if(toc-title)$ +\renewcommand*\contentsname{$toc-title$} +$endif$ +$if(beamer)$ +\begin{frame}[allowframebreaks] + $if(toc-title)$ + \frametitle{$toc-title$} + $endif$ + \tableofcontents[hideallsubsections] +\end{frame} +$if(toc-own-page)$ +\newpage +$endif$ +$else$ +{ + $if(colorlinks)$ + \hypersetup{linkcolor=$if(toccolor)$$toccolor$$else$$endif$} + $endif$ + \setcounter{tocdepth}{$toc-depth$} + \tableofcontents + $if(toc-own-page)$ + \newpage + $endif$ + } +$endif$ +$endif$ +$if(lot)$ +\listoftables +$endif$ +$if(lof)$ +\listoffigures +$endif$ +$if(linestretch)$ +\setstretch{$linestretch$} +$endif$ +$if(has-frontmatter)$ +\mainmatter +$endif$ +$body$ + +$if(has-frontmatter)$ +\backmatter +$endif$ +$if(natbib)$ +$if(bibliography)$ +$if(biblio-title)$ +$if(has-chapters)$ +\renewcommand\bibname{$biblio-title$} +$else$ +\renewcommand\refname{$biblio-title$} +$endif$ +$endif$ +$if(beamer)$ +\begin{frame}[allowframebreaks]{$biblio-title$} + \bibliographytrue + $endif$ + \bibliography{$for(bibliography)$$bibliography$$sep$,$endfor$} + $if(beamer)$ +\end{frame} +$endif$ + +$endif$ +$endif$ +$if(biblatex)$ +$if(beamer)$ +\begin{frame}[allowframebreaks]{$biblio-title$} + \bibliographytrue + \printbibliography[heading=none] +\end{frame} +$else$ +\printbibliography$if(biblio-title)$[title=$biblio-title$]$endif$ +$endif$ + +$endif$ +$for(include-after)$ +$include-after$ + +$endfor$ +\end{document} diff --git a/templates/epub.html b/templates/epub.html new file mode 100644 index 0000000..c32f645 --- /dev/null +++ b/templates/epub.html @@ -0,0 +1,69 @@ + + + + + + + $pagetitle$ +$if(highlighting-css)$ + +$endif$ +$for(css)$ + +$endfor$ +$for(header-includes)$ + $header-includes$ +$endfor$ + + +$if(titlepage)$ +
    +$for(title)$ +$if(title.type)$ +

    $title.text$

    +$else$ +

    $title$

    +$endif$ +$endfor$ +$if(subtitle)$ +

    $subtitle$

    +$endif$ +$for(author)$ +

    $author$

    +$endfor$ +$for(creator)$ +

    $creator.text$

    +$endfor$ +$if(publisher)$ +

    $publisher$

    +$endif$ +$if(date)$ +

    $date$

    +$endif$ +$if(rights)$ +
    $rights$
    +$endif$ +
    +$else$ +$if(coverpage)$ +
    + + + +
    +$else$ +$for(include-before)$ +$include-before$ +$endfor$ +$body$ +$for(include-after)$ +$include-after$ +$endfor$ +$endif$ +$endif$ + + + diff --git a/templates/html.html b/templates/html.html new file mode 100644 index 0000000..3cf0743 --- /dev/null +++ b/templates/html.html @@ -0,0 +1,65 @@ + + + + + + +$for(author-meta)$ + +$endfor$ +$if(date-meta)$ + +$endif$ +$if(keywords)$ + +$endif$ + $if(title-prefix)$$title-prefix$ – $endif$$pagetitle$ + +$for(css)$ + +$endfor$ +$if(math)$ + $math$ +$endif$ + +$for(header-includes)$ + $header-includes$ +$endfor$ + + +$for(include-before)$ +$include-before$ +$endfor$ +$if(title)$ +
    +

    $title$

    +$if(subtitle)$ +

    $subtitle$

    +$endif$ +$for(author)$ +

    $author$

    +$endfor$ +$if(date)$ +

    $date$

    +$endif$ +
    +$endif$ +$if(toc)$ + +$endif$ +$body$ +$for(include-after)$ +$include-after$ +$endfor$ + + diff --git a/templates/pdf.latex b/templates/pdf.latex new file mode 100644 index 0000000..4ceca43 --- /dev/null +++ b/templates/pdf.latex @@ -0,0 +1,497 @@ +% Options for packages loaded elsewhere +\PassOptionsToPackage{unicode$for(hyperrefoptions)$,$hyperrefoptions$$endfor$}{hyperref} +\PassOptionsToPackage{hyphens}{url} +$if(colorlinks)$ +\PassOptionsToPackage{dvipsnames,svgnames*,x11names*}{xcolor} +$endif$ +$if(dir)$ +$if(latex-dir-rtl)$ +\PassOptionsToPackage{RTLdocument}{bidi} +$endif$ +$endif$ +$if(CJKmainfont)$ +\PassOptionsToPackage{space}{xeCJK} +$endif$ +% +\documentclass[ +$if(fontsize)$ + $fontsize$, +$endif$ +$if(lang)$ + $babel-lang$, +$endif$ +$if(papersize)$ + $papersize$paper, +$endif$ +$if(beamer)$ + ignorenonframetext, +$if(handout)$ + handout, +$endif$ +$if(aspectratio)$ + aspectratio=$aspectratio$, +$endif$ +$endif$ +$for(classoption)$ + $classoption$$sep$, +$endfor$ +]{$documentclass$} +$if(beamer)$ +$if(background-image)$ +\usebackgroundtemplate{% + \includegraphics[width=\paperwidth]{$background-image$}% +} +$endif$ +\usepackage{pgfpages} +\setbeamertemplate{caption}[numbered] +\setbeamertemplate{caption label separator}{: } +\setbeamercolor{caption name}{fg=normal text.fg} +\beamertemplatenavigationsymbols$if(navigation)$$navigation$$else$empty$endif$ +$for(beameroption)$ +\setbeameroption{$beameroption$} +$endfor$ +% Prevent slide breaks in the middle of a paragraph +\widowpenalties 1 10000 +\raggedbottom +$if(section-titles)$ +\setbeamertemplate{part page}{ + \centering + \begin{beamercolorbox}[sep=16pt,center]{part title} + \usebeamerfont{part title}\insertpart\par + \end{beamercolorbox} +} +\setbeamertemplate{section page}{ + \centering + \begin{beamercolorbox}[sep=12pt,center]{part title} + \usebeamerfont{section title}\insertsection\par + \end{beamercolorbox} +} +\setbeamertemplate{subsection page}{ + \centering + \begin{beamercolorbox}[sep=8pt,center]{part title} + \usebeamerfont{subsection title}\insertsubsection\par + \end{beamercolorbox} +} +\AtBeginPart{ + \frame{\partpage} +} +\AtBeginSection{ + \ifbibliography + \else + \frame{\sectionpage} + \fi +} +\AtBeginSubsection{ + \frame{\subsectionpage} +} +$endif$ +$endif$ +$if(beamerarticle)$ +\usepackage{beamerarticle} % needs to be loaded first +$endif$ +$if(fontfamily)$ +\usepackage[$for(fontfamilyoptions)$$fontfamilyoptions$$sep$,$endfor$]{$fontfamily$} +$else$ +\usepackage{lmodern} +$endif$ +$if(linestretch)$ +\usepackage{setspace} +$endif$ +\usepackage{amssymb,amsmath} +\usepackage{ifxetex,ifluatex} +\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex + \usepackage[$if(fontenc)$$fontenc$$else$T1$endif$]{fontenc} + \usepackage[utf8]{inputenc} + \usepackage{textcomp} % provide euro and other symbols +\else % if luatex or xetex +$if(mathspec)$ + \ifxetex + \usepackage{mathspec} + \else + \usepackage{unicode-math} + \fi +$else$ + \usepackage{unicode-math} +$endif$ + \defaultfontfeatures{Scale=MatchLowercase} + \defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1} +$if(mainfont)$ + \setmainfont[$for(mainfontoptions)$$mainfontoptions$$sep$,$endfor$]{$mainfont$} +$endif$ +$if(sansfont)$ + \setsansfont[$for(sansfontoptions)$$sansfontoptions$$sep$,$endfor$]{$sansfont$} +$endif$ +$if(monofont)$ + \setmonofont[$for(monofontoptions)$$monofontoptions$$sep$,$endfor$]{$monofont$} +$endif$ +$for(fontfamilies)$ + \newfontfamily{$fontfamilies.name$}[$for(fontfamilies.options)$$fontfamilies.options$$sep$,$endfor$]{$fontfamilies.font$} +$endfor$ +$if(mathfont)$ +$if(mathspec)$ + \ifxetex + \setmathfont(Digits,Latin,Greek)[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} + \else + \setmathfont[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} + \fi +$else$ + \setmathfont[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} +$endif$ +$endif$ +$if(CJKmainfont)$ + \ifxetex + \usepackage{xeCJK} + \setCJKmainfont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKmainfont$} + \fi +$endif$ +$if(luatexjapresetoptions)$ + \ifluatex + \usepackage[$for(luatexjapresetoptions)$$luatexjapresetoptions$$sep$,$endfor$]{luatexja-preset} + \fi +$endif$ +$if(CJKmainfont)$ + \ifluatex + \usepackage[$for(luatexjafontspecoptions)$$luatexjafontspecoptions$$sep$,$endfor$]{luatexja-fontspec} + \setmainjfont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKmainfont$} + \fi +$endif$ +\fi +$if(beamer)$ +$if(theme)$ +\usetheme[$for(themeoptions)$$themeoptions$$sep$,$endfor$]{$theme$} +$endif$ +$if(colortheme)$ +\usecolortheme{$colortheme$} +$endif$ +$if(fonttheme)$ +\usefonttheme{$fonttheme$} +$endif$ +$if(mainfont)$ +\usefonttheme{serif} % use mainfont rather than sansfont for slide text +$endif$ +$if(innertheme)$ +\useinnertheme{$innertheme$} +$endif$ +$if(outertheme)$ +\useoutertheme{$outertheme$} +$endif$ +$endif$ +% Use upquote if available, for straight quotes in verbatim environments +\IfFileExists{upquote.sty}{\usepackage{upquote}}{} +\IfFileExists{microtype.sty}{% use microtype if available + \usepackage[$for(microtypeoptions)$$microtypeoptions$$sep$,$endfor$]{microtype} + \UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts +}{} +$if(indent)$ +$else$ +\makeatletter +\@ifundefined{KOMAClassName}{% if non-KOMA class + \IfFileExists{parskip.sty}{% + \usepackage{parskip} + }{% else + \setlength{\parindent}{0pt} + \setlength{\parskip}{6pt plus 2pt minus 1pt}} +}{% if KOMA class + \KOMAoptions{parskip=half}} +\makeatother +$endif$ +$if(verbatim-in-note)$ +\usepackage{fancyvrb} +$endif$ +\usepackage{xcolor} +\IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available +\IfFileExists{bookmark.sty}{\usepackage{bookmark}}{\usepackage{hyperref}} +\hypersetup{ +$if(title-meta)$ + pdftitle={$title-meta$}, +$endif$ +$if(author-meta)$ + pdfauthor={$author-meta$}, +$endif$ +$if(lang)$ + pdflang={$lang$}, +$endif$ +$if(subject)$ + pdfsubject={$subject$}, +$endif$ +$if(keywords)$ + pdfkeywords={$for(keywords)$$keywords$$sep$, $endfor$}, +$endif$ +$if(colorlinks)$ + colorlinks=true, + linkcolor=$if(linkcolor)$$linkcolor$$else$Maroon$endif$, + filecolor=$if(filecolor)$$filecolor$$else$Maroon$endif$, + citecolor=$if(citecolor)$$citecolor$$else$Blue$endif$, + urlcolor=$if(urlcolor)$$urlcolor$$else$Blue$endif$, +$else$ + hidelinks, +$endif$ + pdfcreator={LaTeX via pandoc}} +\urlstyle{same} % disable monospaced font for URLs +$if(verbatim-in-note)$ +\VerbatimFootnotes % allow verbatim text in footnotes +$endif$ +$if(geometry)$ +$if(beamer)$ +\geometry{$for(geometry)$$geometry$$sep$,$endfor$} +$else$ +\usepackage[$for(geometry)$$geometry$$sep$,$endfor$]{geometry} +$endif$ +$endif$ +$if(beamer)$ +\newif\ifbibliography +$endif$ +$if(listings)$ +\usepackage{listings} +\newcommand{\passthrough}[1]{#1} +\lstset{defaultdialect=[5.3]Lua} +\lstset{defaultdialect=[x86masm]Assembler} +$endif$ +$if(lhs)$ +\lstnewenvironment{code}{\lstset{language=Haskell,basicstyle=\small\ttfamily}}{} +$endif$ +$if(highlighting-macros)$ +$highlighting-macros$ +$endif$ +$if(tables)$ +\usepackage{longtable,booktabs} +$if(beamer)$ +\usepackage{caption} +% Make caption package work with longtable +\makeatletter +\def\fnum@table{\tablename~\thetable} +\makeatother +$else$ +% Correct order of tables after \paragraph or \subparagraph +\usepackage{etoolbox} +\makeatletter +\patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{} +\makeatother +% Allow footnotes in longtable head/foot +\IfFileExists{footnotehyper.sty}{\usepackage{footnotehyper}}{\usepackage{footnote}} +\makesavenoteenv{longtable} +$endif$ +$endif$ +$if(graphics)$ +\usepackage{graphicx} +\makeatletter +\def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi} +\def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi} +\makeatother +% Scale images if necessary, so that they will not overflow the page +% margins by default, and it is still possible to overwrite the defaults +% using explicit options in \includegraphics[width, height, ...]{} +\setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio} +% Set default figure placement to htbp +\makeatletter +\def\fps@figure{htbp} +\makeatother +$endif$ +$if(links-as-notes)$ +% Make links footnotes instead of hotlinks: +\DeclareRobustCommand{\href}[2]{#2\footnote{\url{#1}}} +$endif$ +$if(strikeout)$ +\usepackage[normalem]{ulem} +% Avoid problems with \sout in headers with hyperref +\pdfstringdefDisableCommands{\renewcommand{\sout}{}} +$endif$ +\setlength{\emergencystretch}{3em} % prevent overfull lines +\providecommand{\tightlist}{% + \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} +$if(numbersections)$ +\setcounter{secnumdepth}{$if(secnumdepth)$$secnumdepth$$else$5$endif$} +$else$ +\setcounter{secnumdepth}{-\maxdimen} % remove section numbering +$endif$ +$if(beamer)$ +$else$ +$if(block-headings)$ +% Make \paragraph and \subparagraph free-standing +\ifx\paragraph\undefined\else + \let\oldparagraph\paragraph + \renewcommand{\paragraph}[1]{\oldparagraph{#1}\mbox{}} +\fi +\ifx\subparagraph\undefined\else + \let\oldsubparagraph\subparagraph + \renewcommand{\subparagraph}[1]{\oldsubparagraph{#1}\mbox{}} +\fi +$endif$ +$endif$ +$if(pagestyle)$ +\pagestyle{$pagestyle$} +$endif$ +$for(header-includes)$ +$header-includes$ +$endfor$ +$if(lang)$ +\ifxetex + % Load polyglossia as late as possible: uses bidi with RTL langages (e.g. Hebrew, Arabic) + \usepackage{polyglossia} + \setmainlanguage[$polyglossia-lang.options$]{$polyglossia-lang.name$} +$for(polyglossia-otherlangs)$ + \setotherlanguage[$polyglossia-otherlangs.options$]{$polyglossia-otherlangs.name$} +$endfor$ +\else + \usepackage[shorthands=off,$for(babel-otherlangs)$$babel-otherlangs$,$endfor$main=$babel-lang$]{babel} +$if(babel-newcommands)$ + $babel-newcommands$ +$endif$ +\fi +$endif$ +$if(dir)$ +\ifxetex + % Load bidi as late as possible as it modifies e.g. graphicx + \usepackage{bidi} +\fi +\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex + \TeXXeTstate=1 + \newcommand{\RL}[1]{\beginR #1\endR} + \newcommand{\LR}[1]{\beginL #1\endL} + \newenvironment{RTL}{\beginR}{\endR} + \newenvironment{LTR}{\beginL}{\endL} +\fi +$endif$ +$if(natbib)$ +\usepackage[$natbiboptions$]{natbib} +\bibliographystyle{$if(biblio-style)$$biblio-style$$else$plainnat$endif$} +$endif$ +$if(biblatex)$ +\usepackage[$if(biblio-style)$style=$biblio-style$,$endif$$for(biblatexoptions)$$biblatexoptions$$sep$,$endfor$]{biblatex} +$for(bibliography)$ +\addbibresource{$bibliography$} +$endfor$ +$endif$ +$if(csl-refs)$ +\newlength{\cslhangindent} +\setlength{\cslhangindent}{1.5em} +\newenvironment{cslreferences}% + {$if(csl-hanging-indent)$\setlength{\parindent}{0pt}% + \everypar{\setlength{\hangindent}{\cslhangindent}}\ignorespaces$endif$}% + {\par} +$endif$ + +$if(title)$ +\title{$title$$if(thanks)$\thanks{$thanks$}$endif$} +$endif$ +$if(subtitle)$ +$if(beamer)$ +$else$ +\usepackage{etoolbox} +\makeatletter +\providecommand{\subtitle}[1]{% add subtitle to \maketitle + \apptocmd{\@title}{\par {\large #1 \par}}{}{} +} +\makeatother +$endif$ +\subtitle{$subtitle$} +$endif$ +\author{$for(author)$$author$$sep$ \and $endfor$} +\date{$date$} +$if(beamer)$ +$if(institute)$ +\institute{$for(institute)$$institute$$sep$ \and $endfor$} +$endif$ +$if(titlegraphic)$ +\titlegraphic{\includegraphics{$titlegraphic$}} +$endif$ +$if(logo)$ +\logo{\includegraphics{$logo$}} +$endif$ +$endif$ + +\begin{document} +$if(has-frontmatter)$ +\frontmatter +$endif$ +$if(title)$ +$if(beamer)$ +\frame{\titlepage} +$else$ +\maketitle +$endif$ +$if(abstract)$ +\begin{abstract} +$abstract$ +\end{abstract} +$endif$ +$endif$ + +$for(include-before)$ +$include-before$ + +$endfor$ +$if(toc)$ +$if(toc-title)$ +\renewcommand*\contentsname{$toc-title$} +$endif$ +$if(beamer)$ +\begin{frame}[allowframebreaks] +$if(toc-title)$ + \frametitle{$toc-title$} +$endif$ + \tableofcontents[hideallsubsections] +\end{frame} +$else$ +{ +$if(colorlinks)$ +\hypersetup{linkcolor=$if(toccolor)$$toccolor$$else$$endif$} +$endif$ +\setcounter{tocdepth}{$toc-depth$} +\tableofcontents +} +$endif$ +$endif$ +$if(lot)$ +\listoftables +$endif$ +$if(lof)$ +\listoffigures +$endif$ +$if(linestretch)$ +\setstretch{$linestretch$} +$endif$ +$if(has-frontmatter)$ +\mainmatter +$endif$ +$body$ + +$if(has-frontmatter)$ +\backmatter +$endif$ +$if(natbib)$ +$if(bibliography)$ +$if(biblio-title)$ +$if(has-chapters)$ +\renewcommand\bibname{$biblio-title$} +$else$ +\renewcommand\refname{$biblio-title$} +$endif$ +$endif$ +$if(beamer)$ +\begin{frame}[allowframebreaks]{$biblio-title$} + \bibliographytrue +$endif$ + \bibliography{$for(bibliography)$$bibliography$$sep$,$endfor$} +$if(beamer)$ +\end{frame} +$endif$ + +$endif$ +$endif$ +$if(biblatex)$ +$if(beamer)$ +\begin{frame}[allowframebreaks]{$biblio-title$} + \bibliographytrue + \printbibliography[heading=none] +\end{frame} +$else$ +\printbibliography$if(biblio-title)$[title=$biblio-title$]$endif$ +$endif$ + +$endif$ +$for(include-after)$ +$include-after$ + +$endfor$ +\end{document} diff --git a/templates/style.css b/templates/style.css new file mode 100644 index 0000000..5d098ef --- /dev/null +++ b/templates/style.css @@ -0,0 +1,342 @@ +/* + * Custom CSS file. Override it as you like. + * + * Credits to @killercup (https://gist.github.com/killercup); Extracted from this Gist: + * https://gist.github.com/killercup/5917178 + */ + +html { + font-size: 100%; + overflow-y: scroll; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +body { + color: #444; + font-family: Georgia, Palatino, 'Palatino Linotype', Times, 'Times New Roman', serif; + font-size: 12px; + line-height: 1.7; + padding: 1em; + margin: auto; + max-width: 42em; + background: #fefefe; +} + +a { + color: #0645ad; + text-decoration: none; +} + +a:visited { + color: #0b0080; +} + +a:hover { + color: #06e; +} + +a:active { + color: #faa700; +} + +a:focus { + outline: thin dotted; +} + +*::-moz-selection { + background: rgba(255, 255, 0, 0.3); + color: #000; +} + +*::selection { + background: rgba(255, 255, 0, 0.3); + color: #000; +} + +a::-moz-selection { + background: rgba(255, 255, 0, 0.3); + color: #0645ad; +} + +a::selection { + background: rgba(255, 255, 0, 0.3); + color: #0645ad; +} + +p { + margin: 1em 0; +} + +img { + max-width: 100%; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + color: #111; + line-height: 125%; + margin-top: 2em; + font-weight: normal; +} + +h4, +h5, +h6 { + font-weight: bold; +} + +h1 { + font-size: 2.5em; +} + +h2 { + font-size: 2em; +} + +h3 { + font-size: 1.5em; +} + +h4 { + font-size: 1.2em; +} + +h5 { + font-size: 1em; +} + +h6 { + font-size: 0.9em; +} + +blockquote { + color: #666666; + margin: 0; + padding-left: 3em; + border-left: 0.5em #EEE solid; +} + +hr { + display: block; + height: 2px; + border: 0; + border-top: 1px solid #aaa; + border-bottom: 1px solid #eee; + margin: 1em 0; + padding: 0; +} + +pre, +code, +kbd, +samp { + color: #000; + font-family: monospace, monospace; + _font-family: 'courier new', monospace; + font-size: 0.98em; +} + +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; +} + +b, +strong { + font-weight: bold; +} + +dfn { + font-style: italic; +} + +ins { + background: #ff9; + color: #000; + text-decoration: none; +} + +mark { + background: #ff0; + color: #000; + font-style: italic; + font-weight: bold; +} + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +ul, +ol { + margin: 1em 0; + padding: 0 0 0 2em; +} + +li p:last-child { + margin-bottom: 0; +} + +ul ul, +ol ol { + margin: .3em 0; +} + +dl { + margin-bottom: 1em; +} + +dt { + font-weight: bold; + margin-bottom: .8em; +} + +dd { + margin: 0 0 .8em 2em; +} + +dd:last-child { + margin-bottom: 0; +} + +img { + border: 0; + -ms-interpolation-mode: bicubic; + vertical-align: middle; +} + +figure { + display: block; + text-align: center; + margin: 1em 0; +} + +figure img { + border: none; + margin: 0 auto; +} + +figcaption { + font-size: 0.8em; + font-style: italic; + margin: 0 0 .8em; +} + +table { + margin-bottom: 2em; + border-bottom: 1px solid #ddd; + border-right: 1px solid #ddd; + border-spacing: 0; + border-collapse: collapse; +} + +table th { + padding: .2em 1em; + background-color: #eee; + border-top: 1px solid #ddd; + border-left: 1px solid #ddd; +} + +table td { + padding: .2em 1em; + border-top: 1px solid #ddd; + border-left: 1px solid #ddd; + vertical-align: top; +} + +.author { + font-size: 1.2em; + text-align: center; +} + +@media only screen and (min-width: 480px) { + body { + font-size: 14px; + } +} + +@media only screen and (min-width: 768px) { + body { + font-size: 16px; + } +} + +@media print { + * { + background: transparent !important; + color: black !important; + filter: none !important; + -ms-filter: none !important; + } + body { + font-size: 12pt; + max-width: 100%; + } + a, + a:visited { + text-decoration: underline; + } + hr { + height: 1px; + border: 0; + border-bottom: 1px solid black; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + .ir a:after, + a[href^="javascript:"]:after, + a[href^="#"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + padding-right: 1em; + page-break-inside: avoid; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + @page :left { + margin: 15mm 20mm 15mm 10mm; + } + @page :right { + margin: 15mm 10mm 15mm 20mm; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } +} \ No newline at end of file diff --git a/templates/thesis.tex b/templates/thesis.tex new file mode 100644 index 0000000..702e065 --- /dev/null +++ b/templates/thesis.tex @@ -0,0 +1,350 @@ +%%% Hlavní soubor. Zde se definují základní parametry a odkazuje se na ostatní části. %%% + +%% Verze pro jednostranný tisk: +% Okraje: levý 40mm, pravý 25mm, horní a dolní 25mm +% (ale pozor, LaTeX si sám přidává 1in) +\documentclass[12pt,a4paper]{report} +\setlength\textwidth{145mm} +\setlength\textheight{247mm} +\setlength\oddsidemargin{15mm} +\setlength\evensidemargin{15mm} +\setlength\topmargin{0mm} +\setlength\headsep{0mm} +\setlength\headheight{0mm} +% \openright zařídí, aby následující text začínal na pravé straně knihy +\let\openright=\clearpage + +\let\sectionnobreak = \section +\makeatletter +\renewcommand{\section}{\@ifstar + \sectionStar% + \sectionNoStar% +} +\makeatother +\newcommand{\sectionNoStar}[1]{ + \pagebreak + \sectionnobreak{#1} +} +\newcommand{\sectionStar}[1]{ + \pagebreak + \sectionnobreak*{#1} + \addcontentsline{toc}{section}{#1} +} + +\makeatletter +\renewcommand{\paragraph}{% + \@startsection{paragraph}{4}% + {\z@}{1.5ex \@plus 1ex \@minus .2ex}{-1em}% + {\normalfont\normalsize\bfseries}% +} +\makeatother + +\usepackage[utf8]{inputenc} + +%% Ostatní balíčky +\usepackage{graphicx} +\usepackage{amsthm} + +\usepackage{upgreek} + +\usepackage{listings} +\usepackage{tikz} +\usetikzlibrary{arrows} +\usepackage{xyling} +\usepackage[hyphens]{url} +\usepackage{microtype} +\usepackage{lmodern} +\usepackage{epstopdf} +\usepackage[printonlyused,nohyperlinks]{acronym} +\usepackage{paralist} +\usepackage{adjustbox} + +\usepackage[center]{caption} + +\usepackage{needspace} + +\lstdefinelanguage{CSharp}[Sharp]{C} +{morekeywords={from,where,join,on,equals,orderby,descending,group,by,let,select},columns=flexible,showstringspaces=false} + +\lstnewenvironment{code}[1][]% +{ + \noindent + \minipage{\linewidth} + \vspace{0.5\baselineskip} + \lstset{#1}} +{\endminipage} + +\newcommand{\lstBreak}{\discretionary{\texttt{-}}{}{}} + +\lstset{language=CSharp,basicstyle=\ttfamily} + +%% Balíček hyperref, kterým jdou vyrábět klikací odkazy v PDF, +%% ale hlavně ho používáme k uložení metadat do PDF (včetně obsahu). +%% POZOR, nezapomeňte vyplnit jméno práce a autora. +\usepackage[unicode]{hyperref} % Musí být za všemi ostatními balíčky +\hypersetup{pdftitle=.NET library for the MediaWiki API} +\hypersetup{pdfauthor=Petr Onderka} + +\usepackage[all]{hypcap} + +%%% Drobné úpravy stylu + +% Tato makra přesvědčují mírně ošklivým trikem LaTeX, aby hlavičky kapitol +% sázel příčetněji a nevynechával nad nimi spoustu místa. Směle ignorujte. +\makeatletter +\def\@makechapterhead#1{ + {\parindent \z@ \raggedright \normalfont + \Huge\bfseries \thechapter. #1 + \par\nobreak + \vskip 20\p@ +}} +\def\@makeschapterhead#1{ + {\parindent \z@ \raggedright \normalfont + \Huge\bfseries #1 + \par\nobreak + \vskip 20\p@ +}} +\makeatother + +% Toto makro definuje kapitolu, která není očíslovaná, ale je uvedena v obsahu. +\def\chapwithtoc#1{ +\chapter*{#1} +\addcontentsline{toc}{chapter}{#1} +} + +\def\secwithtoc#1{ +\sectionnobreak*{#1} +\addcontentsline{toc}{section}{#1} +} + +\newcommand\ApiParameterNoAmp[2]{\texttt{#1}~\texttt{=}~\texttt{#2}} +\newcommand\ApiParameter[2]{\ApiParameterNoAmp{#1}{#2}~\texttt{\&}} + +\begin{document} + +\hypersetup{pageanchor=false} + +% Trochu volnější nastavení dělení slov, než je default. +\lefthyphenmin=2 +\righthyphenmin=2 + +%%% Titulní strana práce + +\pagestyle{empty} +\begin{center} + +\large + +Charles University in Prague + +\medskip + +Faculty of Mathematics and Physics + +\vfill + +{\bf\Large BACHELOR THESIS} + +\vfill + +%\centerline{\mbox{\includegraphics[width=60mm]{img/logo}}} + +\vfill +\vspace{5mm} + +{\LARGE Petr Onderka} + +\vspace{15mm} + +% Název práce přesně podle zadání +{\LARGE\bfseries .NET library for the MediaWiki API} + +\vfill + +% Název katedry nebo ústavu, kde byla práce oficiálně zadána +% (dle Organizační struktury MFF UK) +Department of Theoretical Computer Science \linebreak and Mathematical Logic + +\vfill + +\begin{tabular}{rl} + +Supervisor of the bachelor thesis: & Tomáš Petříček \\ +\noalign{\vspace{2mm}} +Study programme: & Computer Science \\ +\noalign{\vspace{2mm}} +Specialization: & General Computer Science \\ +\end{tabular} + +\vfill + +% Zde doplňte rok +Prague 2012 + +\end{center} + +\newpage + +%%% Následuje vevázaný list -- kopie podepsaného "Zadání bakalářské práce". +%%% Toto zadání NENÍ součástí elektronické verze práce, nescanovat. + +%%% Na tomto místě mohou být napsána případná poděkování (vedoucímu práce, +%%% konzultantovi, tomu, kdo zapůjčil software, literaturu apod.) + +\openright + +\noindent +I would like to thank to my supervisor, Tomáš Petříček, +for his help with writing this thesis. +I would also like to thank to my family for their unending support and patience during my studies. + +\newpage + +%%% Strana s čestným prohlášením k bakalářské práci + +\vglue 0pt plus 1fill + +\noindent +I declare that I carried out this bachelor thesis independently, and only with the cited +sources, literature and other professional sources. + +\medskip\noindent +I understand that my work relates to the rights and obligations under the Act No. +121/2000 Coll., the Copyright Act, as amended, in particular the fact that the Charles +University in Prague has the right to conclude a license agreement on the use of this +work as a school work pursuant to Section 60 paragraph 1 of the Copyright Act. + +\vspace{10mm} + +\hbox{\hbox to 0.5\hsize{% +In Prague, date ............ +\hss}\hbox to 0.5\hsize{% +signature of the author +\hss}} + +\vspace{20mm} +\newpage + +%%% Povinná informační strana bakalářské práce + +\vbox to 0.5\vsize{ +\setlength\parindent{0mm} +\setlength\parskip{5mm} + +Název práce: +.NET knihovna pro MediaWiki API +% přesně dle zadání + +Autor: +Petr Onderka + +Katedra: % Případně Ústav: +Katedra teoretické informatiky a matematické logiky +% dle Organizační struktury MFF UK + +Vedoucí bakalářské práce: +Mgr. Tomáš Petříček, University of Cambridge +% dle Organizační struktury MFF UK, případně plný název pracoviště mimo MFF UK + +Abstrakt: +% abstrakt v rozsahu 80-200 slov; nejedná se však o opis zadání bakalářské práce + +Wiki běžící na systému MediaWiki poskytují svým uživatelům API, +které lze použít k přístupu k dané wiki z počítačového programu. +Toto API je rozsáhlé, často se mění a může se lišit wiki od wiki, +takže může být náročné napsat knihovnu pro přístup k tomuto API. + +Tato práce popisuje LinqToWiki, +knihovnu pro přístup k MediaWiki API ze C\# nebo jiných jazyků na platformě .NET. +Díky použití LINQu a generovaní kódu pomocí Roslynu, +kód napsaný s použitím této knihovny je čitelný, objevitelný, silně typovaný a flexibilní. + +Klíčová slova: Wiki, C\#, LINQ, Generování kódu, Roslyn +% 3 až 5 klíčových slov + +\vss}\nobreak\vbox to 0.49\vsize{ +\setlength\parindent{0mm} +\setlength\parskip{5mm} + +Title: +.NET library for the MediaWiki API +% přesný překlad názvu práce v angličtině + +Author: +Petr Onderka + +Department: +Department of Theoretical Computer Science and Mathematical Logic +% dle Organizační struktury MFF UK v angličtině + +Supervisor: +Mgr. Tomáš Petříček, University of Cambridge +% dle Organizační struktury MFF UK, případně plný název pracoviště +% mimo MFF UK v angličtině + +Abstract: +% abstrakt v rozsahu 80-200 slov v angličtině; nejedná se však o překlad +% zadání bakalářské práce + +MediaWiki wikis provide their users an API, that can used to programmatically access the wiki. +This API is large, changes frequently and can be different from wiki to wiki, +so it can be a challenge to write a library for accessing the API. + +This thesis describes LinqToWiki, a library +that can be used to access the MediaWiki API from C\# or other .NET languages. +Thanks to the use of LINQ and code generation through Roslyn, +code written using this library is readable, discoverable, strongly-typed and flexible. + +Keywords: Wiki, C\#, LINQ, Code generation, Roslyn +% 3 až 5 klíčových slov v angličtině + +\vss} + +\newpage + +%%% Strana s automaticky generovaným obsahem bakalářské práce. U matematických +%%% prací je přípustné, aby seznam tabulek a zkratek, existují-li, byl umístěn +%%% na začátku práce, místo na jejím konci. + +\hypersetup{pageanchor=true} +\openright +\pagestyle{plain} +\setcounter{page}{1} +\tableofcontents + +%%% Jednotlivé kapitoly práce jsou pro přehlednost uloženy v samostatných souborech +\include{introduction} + +\input{problem-analysis} + +\chapter{Background} +\label{background} +\input{mediawiki} +\input{linq} +\input{roslyn} + +\input{mediawiki-improvements} +\input{linqtowiki} +\input{future-work} +\input{related} +\input{conclusion} + +%%% Seznam použité literatury +\input{bibliography} + +%%% Tabulky v bakalářské práci, existují-li. +\listoffigures +\addcontentsline{toc}{chapter}{List of Figures} + +%%% Použité zkratky v bakalářské práci, existují-li, včetně jejich vysvětlení. +\chapwithtoc{List of Abbreviations} +\input{abbrev} + +%%% Přílohy k bakalářské práci, existují-li (různé dodatky jako výpisy programů, +%%% diagramy apod.). Každá příloha musí být alespoň jednou odkazována z vlastního +%%% textu práce. Přílohy se číslují. +\appendix +\input{cd} + +\openright +\end{document} \ No newline at end of file