From 2352fcec2c67fcab6499c18df222700ef90670c5 Mon Sep 17 00:00:00 2001 From: "Brandon T. Willard" Date: Sat, 17 Oct 2020 00:55:57 -0500 Subject: [PATCH 01/11] Update and refactor logger warnings --- tests/unittest_tools.py | 2 +- theano/compile/debugmode.py | 8 +++---- theano/gof/cmodule.py | 6 +++--- theano/gof/compilelock.py | 2 +- theano/gof/vm.py | 2 +- theano/printing.py | 2 +- theano/tensor/nnet/conv.py | 4 ++-- theano/tensor/opt.py | 42 +++++++++++++------------------------ 8 files changed, 27 insertions(+), 41 deletions(-) diff --git a/tests/unittest_tools.py b/tests/unittest_tools.py index 5ecf621af6..949ea43203 100644 --- a/tests/unittest_tools.py +++ b/tests/unittest_tools.py @@ -255,7 +255,7 @@ def _compile_and_check( else: shp = inp.shape if len(set(shp)) != len(shp): - _logger.warn( + _logger.warning( "While testing shape inference for %r, we received an" " input with a shape that has some repeated values: %r" ", like a square matrix. This makes it impossible to" diff --git a/theano/compile/debugmode.py b/theano/compile/debugmode.py index b1ef6ba2ed..f9b137d6c7 100644 --- a/theano/compile/debugmode.py +++ b/theano/compile/debugmode.py @@ -1437,7 +1437,7 @@ def _check_preallocated_output( fn_attr_name = ops_with_inner_function[type(node.op)] fn = getattr(node.op, fn_attr_name, None) if not fn or not hasattr(fn, "maker") or not hasattr(fn.maker, "mode"): - _logger.warn( + _logger.warning( "Expected theano function not found in %s.%s", node.op, fn_attr_name ) else: @@ -1482,7 +1482,7 @@ def _check_preallocated_output( if not out_map: # Map is empty, there is no need to execute thunk() again - _logger.warn("%s: out_map is empty", name) + _logger.warning("%s: out_map is empty", name) continue # Copy the inputs over, if they were marked as destroyed or viewed @@ -1904,7 +1904,7 @@ def make_all( thunks_py.append(None) if not self.maker.mode.check_c_code and thunks_py[-1] is None: - _logger.warn( + _logger.warning( "Op %s doesn't have a perform, " "forcing check of the C code" % node.op ) @@ -1921,7 +1921,7 @@ def make_all( elif thunks_c[-1] is None: thunks_c[-1] = thunk_other else: - _logger.warn( + _logger.warning( "We won't check the perform function " "of node '%s' but we will check its " "make_thunk function" % node diff --git a/theano/gof/cmodule.py b/theano/gof/cmodule.py index e0aa33239c..760300d53e 100644 --- a/theano/gof/cmodule.py +++ b/theano/gof/cmodule.py @@ -2055,7 +2055,7 @@ def compile_args(march_flags=True): and "clang-omp++" not in theano.config.cxx and "icpc" not in theano.config.cxx ): - _logger.warn( + _logger.warning( "OPTIMIZATION WARNING: your Theano flag `cxx` seems not to be" " the g++ compiler. So we disable the compiler optimization" " specific to g++ that tell to compile for a specific CPU." @@ -2124,7 +2124,7 @@ def get_lines(cmd, parse=True): ) else: reported_lines = native_lines - _logger.warn( + _logger.warning( "OPTIMIZATION WARNING: Theano was not able to find the" " g++ parameters that tune the compilation to your " " specific CPU. This can slow down the execution of Theano" @@ -2137,7 +2137,7 @@ def get_lines(cmd, parse=True): default_lines = get_lines("%s -E -v -" % theano.config.cxx) _logger.info("g++ default lines: %s", default_lines) if len(default_lines) < 1: - _logger.warn( + _logger.warning( "OPTIMIZATION WARNING: Theano was not able to find the" " default g++ parameters. This is needed to tune" " the compilation to your specific" diff --git a/theano/gof/compilelock.py b/theano/gof/compilelock.py index f1ffaf996c..e073a0f823 100644 --- a/theano/gof/compilelock.py +++ b/theano/gof/compilelock.py @@ -349,7 +349,7 @@ def refresh_lock(lock_file): # This way, only 1 test would fail. while get_lock.n_lock > 0: release_lock() - _logger.warn( + _logger.warning( "Refreshing lock failed, we release the" " lock before raising again the exception" ) diff --git a/theano/gof/vm.py b/theano/gof/vm.py index ecec3039a5..362931f06a 100644 --- a/theano/gof/vm.py +++ b/theano/gof/vm.py @@ -924,7 +924,7 @@ def make_vm( if self.use_cloop and ( self.callback is not None or self.callback_input is not None ): - logger.warn("CVM does not support callback, using Stack VM.") + logger.warning("CVM does not support callback, using Stack VM.") if self.use_cloop and config.profile_memory: warnings.warn("CVM does not support memory profile, using Stack VM.") if not self.use_cloop and self.allow_partial_eval: diff --git a/theano/printing.py b/theano/printing.py index 93def28783..966b663edc 100644 --- a/theano/printing.py +++ b/theano/printing.py @@ -862,7 +862,7 @@ def pydotprint( ): cond = node if cond is None: - _logger.warn( + _logger.warning( "pydotprint: cond_highlight is set but there is no" " IfElse node in the graph" ) diff --git a/theano/tensor/nnet/conv.py b/theano/tensor/nnet/conv.py index 155831112b..e985cf76a4 100644 --- a/theano/tensor/nnet/conv.py +++ b/theano/tensor/nnet/conv.py @@ -559,7 +559,7 @@ def __init__( " bsize(%i). We revert it to %i. This" " won't change the result, but may make it slower." ) - _logger.warn(warnstr, self.unroll_batch, self.bsize, new) + _logger.warning(warnstr, self.unroll_batch, self.bsize, new) self.unroll_batch = new @@ -585,7 +585,7 @@ def __init__( " nkern(%i). We revert it to %i. This" " won't change the result, but may make it slower." ) - _logger.warn(warnstr, self.unroll_kern, self.nkern, new) + _logger.warning(warnstr, self.unroll_kern, self.nkern, new) self.unroll_kern = new self.outshp = get_conv_output_shape( diff --git a/theano/tensor/opt.py b/theano/tensor/opt.py index 0a55343d65..42c3c6e5fa 100644 --- a/theano/tensor/opt.py +++ b/theano/tensor/opt.py @@ -3251,7 +3251,7 @@ def merge_two_slices(slice1, len1, slice2, len2): # sl.stop backwards n_val = sl1.stop - 1 - sl2 * sl1.step if config.warn.subtensor_merge_bug: - warnings.warn( + warnings.warning( ( "Your current code is fine, but Theano versions " "prior to 0.5rc2 might have given an incorrect result. " @@ -3843,7 +3843,7 @@ def local_adv_sub1_adv_inc_sub1(node): if not inp.owner.op.set_instead_of_inc: if config.warn.inc_subtensor1_opt: - warnings.warn( + warnings.warning( "Your current code is fine, but Theano versions " "between 0.7rc1 and 0.10 (or development versions " "between Nov. 2014 and May 2017) " @@ -5851,7 +5851,7 @@ def local_sum_prod_div_dimshuffle(node): break if compatible_dims: - _logger.warn( + _logger.warning( "WARNING: Your current code is fine, but" " Theano versions between " "rev. 3bd9b789f5e8 (2010-06-16) and" @@ -5906,7 +5906,7 @@ def local_sum_prod_div_dimshuffle(node): if config.warn.sum_div_dimshuffle_bug and isinstance( node.op, T.Sum ): - _logger.warn( + _logger.warning( "WARNING: Your current code is fine," " but Theano versions between " "rev. 3bd9b789f5e8 (2010-06-16) and" @@ -6016,7 +6016,7 @@ def local_op_of_op(node): and newaxis != newaxis_old and len(newaxis) == len(newaxis_old) ): - _logger.warn( + _logger.warning( "WARNING (YOUR CURRENT CODE IS FINE): Theano " "versions between version 9923a40c7b7a and August " "2nd, 2010 generated bugged code in this case. " @@ -6102,7 +6102,7 @@ def local_reduce_join(node): # I put this warning late to don't add extra warning. if len(reduce_axis) != 1 or 0 not in reduce_axis: if theano.config.warn.reduce_join: - warnings.warn( + warnings.warning( ( "Your current code is fine, but Theano versions " "prior to 0.7 (or this development version Sept 2014) " @@ -7691,7 +7691,6 @@ def local_fuse(node): for i in node.inputs: do_fusion = False - catch = False # Will store inputs of the fused node that are not currently inputs # of the node we want to create (to avoid duplicating inputs). tmp_input = [] @@ -7712,7 +7711,6 @@ def local_fuse(node): # computation due to broadcast. i.owner.outputs[0].broadcastable == node.outputs[0].broadcastable ): - do_fusion = True try: tmp_s_input = [] # we should not put duplicate input into s_inputs and inputs @@ -7746,12 +7744,11 @@ def local_fuse(node): ["z" for z in i.owner.outputs], {"fail": "%(fail)s"}, ) - except MethodNotDefined: - catch = True - except NotImplementedError: - catch = True - if catch: - _logger.info( + + do_fusion = True + + except (NotImplementedError, MethodNotDefined): + _logger.warning( ( "%s does not implement the c_code function." " As well as being potentially slow, this" @@ -7819,8 +7816,8 @@ def local_fuse(node): ["z" for x in s_new_out], {"fail": "%(fail)s"}, ) - except MethodNotDefined: - _logger.info( + except (NotImplementedError, MethodNotDefined): + _logger.warning( ( "%s does not implement the c_code function." " As well as being potentially slow, this disables " @@ -7828,17 +7825,6 @@ def local_fuse(node): ) % str(s_new_out[0].owner.op) ) - return False - except NotImplementedError: - _logger.info( - ( - "%s does not implement the c_code function. As well" - " as being potentially slow, this disables loop" - " fusion of this op." - ) - % str(s_new_out[0].owner.op) - ) - return False # create the composite op. C = scalar.Composite(s_inputs, s_new_out) @@ -7850,7 +7836,7 @@ def local_fuse(node): assert node.outputs[0].dtype == n.outputs[0].dtype if len(n.inputs) > max_nb_input: - _logger.info( + _logger.warning( "loop fusion failed because Op would exceed" " kernel argument limit." ) return False From 74af3ca25eb336428e6ca7b349b363374bd6b729 Mon Sep 17 00:00:00 2001 From: "Brandon T. Willard" Date: Sat, 17 Oct 2020 01:04:12 -0500 Subject: [PATCH 02/11] Rename scratchpad class to Scratchpad --- theano/gof/graph.py | 5 +++-- theano/gof/utils.py | 2 +- theano/misc/pkl_utils.py | 2 +- theano/printing.py | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/theano/gof/graph.py b/theano/gof/graph.py index ad57cc9c40..881a45a08c 100644 --- a/theano/gof/graph.py +++ b/theano/gof/graph.py @@ -92,7 +92,7 @@ class Apply(Node): def __init__(self, op, inputs, outputs): self.op = op self.inputs = [] - self.tag = utils.scratchpad() + self.tag = utils.Scratchpad() if not isinstance(inputs, (list, tuple)): raise TypeError("The inputs of an Apply must be a list or tuple") @@ -383,7 +383,8 @@ class Variable(Node): def __init__(self, type, owner=None, index=None, name=None): super(Variable, self).__init__() - self.tag = utils.scratchpad() + self.tag = utils.Scratchpad() + self.type = type if owner is not None and not isinstance(owner, Apply): raise TypeError("owner must be an Apply instance", owner) diff --git a/theano/gof/utils.py b/theano/gof/utils.py index 939f19ad09..261702756f 100644 --- a/theano/gof/utils.py +++ b/theano/gof/utils.py @@ -239,7 +239,7 @@ def __ne__(self, other): return not self == other -class scratchpad(object): +class Scratchpad(object): def clear(self): self.__dict__.clear() diff --git a/theano/misc/pkl_utils.py b/theano/misc/pkl_utils.py index 09a4df8243..07d253a152 100644 --- a/theano/misc/pkl_utils.py +++ b/theano/misc/pkl_utils.py @@ -68,7 +68,7 @@ def __init__(self, file, protocol=0, extra_tag_to_remove=None): def save(self, obj): # Remove the tag.trace attribute from Variable and Apply nodes - if isinstance(obj, theano.gof.utils.scratchpad): + if isinstance(obj, theano.gof.utils.Scratchpad): for tag in self.tag_to_remove: if hasattr(obj, tag): del obj.__dict__[tag] diff --git a/theano/printing.py b/theano/printing.py index 966b663edc..38a8fbd8a9 100644 --- a/theano/printing.py +++ b/theano/printing.py @@ -371,11 +371,11 @@ def c_code_cache_version(self): return (1,) -class PrinterState(gof.utils.scratchpad): +class PrinterState(gof.utils.Scratchpad): def __init__(self, props=None, **more_props): if props is None: props = {} - elif isinstance(props, gof.utils.scratchpad): + elif isinstance(props, gof.utils.Scratchpad): self.__update__(props) else: self.__dict__.update(props) From 9df70fdad159c1205531202a2dfc14295c0735d6 Mon Sep 17 00:00:00 2001 From: "Brandon T. Willard" Date: Sat, 17 Oct 2020 01:06:29 -0500 Subject: [PATCH 03/11] Move some local optimizations' comments into their docstrings --- theano/tensor/opt.py | 47 +++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/theano/tensor/opt.py b/theano/tensor/opt.py index 42c3c6e5fa..565b12e923 100644 --- a/theano/tensor/opt.py +++ b/theano/tensor/opt.py @@ -7229,19 +7229,21 @@ def get_clients2(node): register_specialize(local_erf_neg_minus_one2) -# Stability optimization -# log(erfc(x)) => when x>threashold, -# -x**2-log(x)-.5*log(pi)+log(1-1/(2*x**2)+3/(4*x**4)-15/(8*x**6)) -# for float64: threshold=26.641747557 was choosed with: -# [(i,numpy.log(scipy.special.erfc(numpy.asarray([i],dtype='float64')))) -# for i in numpy.arange(26.641747557,26.6417475571,.00000000001)] -# for float32: threshold=10.0541949, [(i,numpy.log(scipy.special.erfc( -# numpy.asarray([i],dtype='float32')))) for i in numpy.arange( -# 10.0541948,10.0541951,.0000001)] @register_stabilize @register_specialize @gof.local_optimizer([T.log]) def local_log_erfc(node): + """Stability optimization for `log(erfc(x))`. + + log(erfc(x)) => when x>threshold, + -x**2-log(x)-.5*log(pi)+log(1-1/(2*x**2)+3/(4*x**4)-15/(8*x**6)) + for float64: threshold=26.641747557 was choosed with: + [(i,numpy.log(scipy.special.erfc(numpy.asarray([i],dtype='float64')))) + for i in numpy.arange(26.641747557,26.6417475571,.00000000001)] + for float32: threshold=10.0541949, [(i,numpy.log(scipy.special.erfc( + numpy.asarray([i],dtype='float32')))) for i in numpy.arange( + 10.0541948,10.0541951,.0000001)] + """ if node.op != T.log: return False if not node.inputs[0].owner or node.inputs[0].owner.op != T.erfc: @@ -7270,21 +7272,26 @@ def local_log_erfc(node): return [ret] -# Stability optimization of the grad of log(erfc(x)) -# ([y*]exp(-(x**2)))/erfc(x) # The y* is optional -# ([y*]exp(x**2))/erfc(-x) => [y*](when x>threashold, -# sqrt(pi)*-x/(1-1/(2*x**2)+3/(4*x**4)-15/(8*x**6))) -# for float64: threshold=26.63 see at the end of the fct for the explanation -# for float32: threshold=9.3 see at the end of the fct for the explanation -# TODO: remove the contraint that there are only 2 inputs to exp(x**2) -# is the second. -# TODO: at the test point 10 in float32, there is instability in the original -# value. The original gives -30.0, the stab -20.1 and in float64 -18.1. -# Make it so that the test does not generate an error in that case! @register_stabilize @register_specialize @gof.local_optimizer([T.true_div]) def local_grad_log_erfc_neg(node): + """Stability optimization for the grad of `log(erfc(x))`. + + ([y*]exp(-(x**2)))/erfc(x) # The y* is optional + ([y*]exp(x**2))/erfc(-x) => [y*](when x>threashold, + sqrt(pi)*-x/(1-1/(2*x**2)+3/(4*x**4)-15/(8*x**6))) + + for float64: threshold=26.63 see at the end of the fct for the explanation + for float32: threshold=9.3 see at the end of the fct for the explanation + + TODO: remove the contraint that there are only 2 inputs to exp(x**2) + is the second. + TODO: at the test point 10 in float32, there is instability in the original + value. The original gives -30.0, the stab -20.1 and in float64 -18.1. + Make it so that the test does not generate an error in that case! + + """ if node.op != T.true_div: return False if not node.inputs[1].owner or node.inputs[1].owner.op != T.erfc: From 74ee82b5decee70d31930ea55b40cdc835a3fcdc Mon Sep 17 00:00:00 2001 From: "Brandon T. Willard" Date: Sat, 17 Oct 2020 01:07:44 -0500 Subject: [PATCH 04/11] Improve variable names and comments/docstring in local_elemwise_fusion_op --- theano/tensor/opt.py | 88 +++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 38 deletions(-) diff --git a/theano/tensor/opt.py b/theano/tensor/opt.py index 565b12e923..97ddf04f69 100644 --- a/theano/tensor/opt.py +++ b/theano/tensor/opt.py @@ -7609,43 +7609,52 @@ def check_input(inputs): """ -# ############### -# # Loop fusion # -# ############### -def local_elemwise_fusion_op(OP, max_input_fct=lambda node: 32, maker=None): - """ - We parametrize it to make it work for Elemwise and GpuElemwise op. +def local_elemwise_fusion_op(op_class, max_input_fct=lambda node: 32, maker=None): + """Create a recursive function that fuses `Elemwise` `Op`s. + + The basic idea is that we loop through an `Elemwise` node's inputs, find + other `Elemwise` nodes, determine the scalars input types for all of the + `Elemwise` `Op`s, construct a new scalar `Op` using the scalar input types + and each `Elemwise`'s scalar `Op`, and use the composite scalar `Op` in a + new "fused" `Elemwise`. + + It's parameterized in order to work for `Elemwise` and `GpuElemwise` `Op`s. Parameters ---------- - OP - GpuElemwise or Elemwise class (the one that we want to fuse) - max_input_fct - A function that returns the maximum number of inputs - that this elemwise can take (useful for GpuElemwise). - GPU kernel currently has a limit of 256 bytes for - the size of all parameters passed to it. As currently - we pass many information only by parameter, we must - limit how many ops we fuse together to avoid busting - that 256 limit. + op_class : type + `GpuElemwise` or `Elemwise` class (the one that we want to fuse) + max_input_fct : callable + A function that returns the maximum number of inputs that this `Elemwise` + can take (useful for `GpuElemwise`). The GPU kernel currently has a + limit of 256 bytes for the size of all parameters passed to it. As + currently we pass a lot of information only by parameter, we must limit how + many `Op`s we fuse together to avoid busting that 256 limit. - On the CPU we limit to 32 input variables - since that is the maximum numpy support. + On the CPU we limit to 32 input variables since that is the maximum + NumPy support. + + maker: callable + A function with the signature `(node, *args)` that constructs an + `op_class` instance (e.g. `op_class(*args)`). """ if maker is None: def maker(node, scalar_op): - return OP(scalar_op) + return op_class(scalar_op) def local_fuse(node): - """ - As part of specialization, we fuse two consecutive elemwise Ops of the + """Fuse `Elemwise` `Op`s in a node. + + + As part of specialization, we fuse two consecutive elemwise `Op`s of the same shape. - For mixed dtype, we let the Composite op do the cast. It lets the C + For mixed dtype, we let the `Composite` `Op` do the cast. It lets the C compiler do the cast. - The number of dimensions is validated at call time by theano itself. + + The number of dimensions is validated at call time by Theano itself. """ # META TODO: PUT THESE THINGS IN TRAC, NOT TODO NOTES!! @@ -7672,12 +7681,13 @@ def local_fuse(node): # worthwhile if the summation axis doesn't line up with a # contiguous dimension) - if type(node.op) is not OP: + if type(node.op) is not op_class: return False if len(node.outputs) > 1: - # We don't support the fusion for node with multiple outputs. + # We don't support fusion for nodes with multiple outputs. return + inputs = [] # inputs of the new Elemwise op. s_inputs = [] # inputs of the new scalar op used by the Composite. # Inputs of the new scalar op that represents the current node. @@ -7710,7 +7720,7 @@ def local_fuse(node): # we still want to fusion. So we take the set. if ( i.owner - and isinstance(i.owner.op, OP) + and isinstance(i.owner.op, op_class) and len(set([n for n, idx in i.clients])) == 1 and # Do not merge elemwise that don't have the same @@ -7736,9 +7746,11 @@ def local_fuse(node): tmp.tag.test_value = tv except AttributeError: pass + tmp_s_input.append(tmp) tmp_input.append(ii) tmp_scalar.append(tmp_s_input[-1]) + s_op = i.owner.op.scalar_op(*tmp_s_input, return_list=True) # if the scalar_op don't have a c implementation, @@ -7786,8 +7798,8 @@ def local_fuse(node): s_inputs.extend(tmp_scalar) s_g.extend(s_op) else: - # We must support the case where the same variable appear many - # time in the inputs + # We must support the case where the same variable appears many + # times within the inputs if inputs.count(i) == node.inputs.count(i): s = s_inputs[inputs.index(i)] else: @@ -7834,15 +7846,16 @@ def local_fuse(node): ) # create the composite op. - C = scalar.Composite(s_inputs, s_new_out) + composite_op = scalar.Composite(s_inputs, s_new_out) # create the new node. # Do not call make_node to have test_value - n = maker(node, C)(*inputs).owner - assert len(n.outputs) == 1 - assert node.outputs[0].dtype == n.outputs[0].dtype + new_node = maker(node, composite_op)(*inputs).owner + + assert len(new_node.outputs) == 1 + assert node.outputs[0].dtype == new_node.outputs[0].dtype - if len(n.inputs) > max_nb_input: + if len(new_node.inputs) > max_nb_input: _logger.warning( "loop fusion failed because Op would exceed" " kernel argument limit." ) @@ -7851,16 +7864,15 @@ def local_fuse(node): # we fuse as many that we can at the same time to make debug mode faster # debug mode will be faster as it won't test all intermediate step. while True: - ret = local_fuse(n) + ret = local_fuse(new_node) if ret is not False and ret is not None: - # print n,ret - assert len(ret) == len(n.outputs) + assert len(ret) == len(new_node.outputs) assert len(ret) == 1 - n = ret[0].owner + new_node = ret[0].owner else: break - return n.outputs + return new_node.outputs return local_fuse From 21daf4e0fb82b9024ac1a7b57a3a815ba9f1ba68 Mon Sep 17 00:00:00 2001 From: "Brandon T. Willard" Date: Sat, 17 Oct 2020 01:12:31 -0500 Subject: [PATCH 05/11] Validate test values using a tensor's type --- tests/gof/test_compute_test_value.py | 14 ++++++++------ theano/gof/graph.py | 2 +- theano/gof/utils.py | 17 +++++++++++++++++ theano/tensor/opt.py | 5 ++++- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/tests/gof/test_compute_test_value.py b/tests/gof/test_compute_test_value.py index b25991f4a2..34c4c7808b 100644 --- a/tests/gof/test_compute_test_value.py +++ b/tests/gof/test_compute_test_value.py @@ -167,14 +167,16 @@ def test_constant(self): @theano.change_flags(compute_test_value="raise") def test_incorrect_type(self): - x = tt.fmatrix("x") - # Incorrect dtype (float64) for test_value - x.tag.test_value = np.random.rand(3, 4) - y = tt.dmatrix("y") - y.tag.test_value = np.random.rand(4, 5) + x = tt.vector("x") with pytest.raises(TypeError): - tt.dot(x, y) + # Incorrect shape for test value + x.tag.test_value = np.empty((2, 2)) + + x = tt.fmatrix("x") + with pytest.raises(TypeError): + # Incorrect dtype (float64) for test value + x.tag.test_value = np.random.rand(3, 4) @theano.change_flags(compute_test_value="raise") def test_overided_function(self): diff --git a/theano/gof/graph.py b/theano/gof/graph.py index 881a45a08c..f36dd122be 100644 --- a/theano/gof/graph.py +++ b/theano/gof/graph.py @@ -383,7 +383,7 @@ class Variable(Node): def __init__(self, type, owner=None, index=None, name=None): super(Variable, self).__init__() - self.tag = utils.Scratchpad() + self.tag = utils.ValidatingScratchpad("test_value", type.filter) self.type = type if owner is not None and not isinstance(owner, Apply): diff --git a/theano/gof/utils.py b/theano/gof/utils.py index 261702756f..bd5e333a8e 100644 --- a/theano/gof/utils.py +++ b/theano/gof/utils.py @@ -259,6 +259,23 @@ def info(self): print(" %s: %s" % (k, v)) +class ValidatingScratchpad(Scratchpad): + """This `Scratchpad` validates attribute values.""" + + def __init__(self, attr, attr_filter): + super().__init__() + + object.__setattr__(self, "attr", attr) + object.__setattr__(self, "attr_filter", attr_filter) + + def __setattr__(self, attr, obj): + + if getattr(self, "attr", None) == attr: + obj = self.attr_filter(obj) + + return object.__setattr__(self, attr, obj) + + class D: def __init__(self, **d): self.__dict__.update(d) diff --git a/theano/tensor/opt.py b/theano/tensor/opt.py index 97ddf04f69..00e61ea548 100644 --- a/theano/tensor/opt.py +++ b/theano/tensor/opt.py @@ -7743,7 +7743,10 @@ def local_fuse(node): if tv.size > 0: tmp.tag.test_value = tv.flatten()[0] else: - tmp.tag.test_value = tv + _logger.warning( + "Cannot construct a scalar test value" + " from a test value with no size: {}".format(ii) + ) except AttributeError: pass From af703ed29b1483177a81a89b45dd41c31f861cc7 Mon Sep 17 00:00:00 2001 From: "Brandon T. Willard" Date: Sat, 17 Oct 2020 12:12:55 -0500 Subject: [PATCH 06/11] Remove low quality FunctionGraph test relying on stale pickle data This test simply loaded an old example of pickled data. It was both extremely indirect and brittle. Changes to the code base will eventually invalidate nearly all of the old classes used, and whatever the exact issue was, it should've been tested directly. --- tests/gof/test_fg.py | 28 +--------------------------- tests/gof/test_fg_old_crash.pkl | Bin 172463 -> 0 bytes 2 files changed, 1 insertion(+), 27 deletions(-) delete mode 100644 tests/gof/test_fg_old_crash.pkl diff --git a/tests/gof/test_fg.py b/tests/gof/test_fg.py index cb1cde4d2c..9c1bc1288b 100644 --- a/tests/gof/test_fg.py +++ b/tests/gof/test_fg.py @@ -1,12 +1,7 @@ -import os import pickle -import pytest - -import theano -from theano.compat import PY3 -from theano.gof.fg import FunctionGraph from theano import tensor as tt +from theano.gof.fg import FunctionGraph class TestFunctionGraph: @@ -16,24 +11,3 @@ def test_pickle(self): s = pickle.dumps(func) pickle.loads(s) - - @pytest.mark.skipif( - not theano.config.cxx, reason="G++ not available, so we need to skip this test." - ) - @pytest.mark.slow - def test_node_outputs_not_used(self): - # In the past, we where removing some not used variable from - # fgraph.variables event if the apply had other output used in - # the graph. This caused a crash. - # This test run the pickle that reproduce this case. - with open( - os.path.join(os.path.dirname(__file__), "test_fg_old_crash.pkl"), "rb" - ) as f: - from theano.misc.pkl_utils import CompatUnpickler - - if PY3: - u = CompatUnpickler(f, encoding="latin1") - else: - u = CompatUnpickler(f) - d = u.load() - f = theano.function(**d) diff --git a/tests/gof/test_fg_old_crash.pkl b/tests/gof/test_fg_old_crash.pkl deleted file mode 100644 index e4e8a97bbd21a0bd52ed13646adcd135bdbaa16c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172463 zcmeFacUV))^FI!vR6*<=VQ zGx}B&83Q?E$mJUe6XIh9D8`7dj~*w+m|sH@6D~?(Oal0N@xn-^CRZgiGB(0CAzl(C zGnR!BVSJc4P8e>?n8q?@a;6r)Mm!OQi5T+`rZ$&v;VFqrju*$qbU@`gICc<5gog=T z!kj}L-R(R3RpT3aM#qLHM2R{xbq3Yd(b4HeGr{rV1lxv33*(6-iE9IxbnD2x%UMK4bJ5GZy@65$=9Ee!A28GcCA&dVF2a zfPjGB74V0SpH6%z-$WKCBr=g8k_f{YPO7+GKtgCjsE}_En-Gts)KW|fpp~3y%@rH* zjnV4PuJ%suOdGL@pN_aDR}2)7%7t3DH?zswFtBryVy7xwGz+oE#D|wI8r2ti47Mq-h5XgiccUh9GY9q zCs!tlim>4fO$q@=yA?%^5{1Rb60Q}smygO`n3XPa#uu~F70uC2&UDw#i7V5Cm6M)w zrWeggZ?-x78CB)H#31kWBCujOoAwgU**KdK?ir<)3vca}OB8-U? zabtq5LTuuhU@dj|a%KP~HBjBCtMnAUu`$p}V&aXNK~PqM<;)POjG>}?aI$BHicJEP zwRD)A3E|gFh><|W5{wc?C5V{e0hl$uewZ*!EDC1?)A`lIh4DgWgrYIohQ$(*ZFE9Z zyhKQdFxgl@L}4^Vk`M+-3YASRCR`E9g!<`BE*|boJS&X^ zIWvl;k;oN8Z598EDw?FFN3xt5je3m1td5m4a!r9cf)e9cN{p8?6Q~jsxqOHJwW&!W zL{TykZDo`Ej)2~t%BH2&tinP#)|@DbES@VQkuodV#%yW#%eCS}#1c+CM-sz{m(d0` zSxfsVa%L)OpQ5nhG&wV!t-c$R%2I!ZoS8}0pT*_ZQg)ls$>A|ERJqxHC4A#ZQM^FW z6Ek!8mP$nx8Q=!niWuw#Yy}colt@ToBr%aT4pz{L=4$CQPtMFoofcGXMNl1xD4dSE zL1DruAqhgiX6#)E6>^cBS**!{gS`v0MAM>{%9&*}1IxMm&i||ZRb8DbacG7#zbpI- z`1%SSvy$Hk2X0$oc(?!tbr?iotmcBEVDMFtIld7z!g!&Cj<0F#kswMEGtwBk1VxxJ zlMXppEoatnVa(tg$e>$Lm2(>FCNB8edq}! zVew1`@QFwm9vc&t%w(o28}mjvvq{;QVU_|zHnimpsB%4sDB^*-FiCpy<$?nJkU?&k$dNP0xqLGK3KC!j6DA0wL*)s6O{|Eak{G*4Yis5tPHFfi;UXxh z*knPpFpfDTXHN4C1Yr3v(Ckcbw;DP+LXMXgC)C)O1Aje4P_KkhlF=d=N61km&WVlV z#K&?Jd63yEfId-C92k3{GoW)NPIQ7So+B2H5+PFt^IZ_8z=4AgsTnk{9^;Bb?`pKSG;ST>w7_MDC!dwHekG<|C?XkgIjs3B2= z*I&wTs4RoV$0o&*(fL_8SUxfV8eBYwn@oi#RZ$4$bXHuLdxmoz>}@zMFvKf7l%@(& zt(3A9)`q%Ehps4*s=CsyqBS%kMWuz-AR*BH|4?%kfhei6X<#lX5`;@e{Z<{&%>nhn z%0SzwIzgozs)SNeb=sAafWe2I0z1Fz1X$HT43!R>n$k9v#YM$NDwTDz;W(qk5W<}0 z8^=T>#DocC(BCk*{2H)^6Gr(}gGDHl$FBw1r>#H`Cyb95kr?KjoXO|wL&3*H$1&$q z#V5ok>GD%KdR~w-7qtefOWK1K&JQoknJb#?U)pC{i? z07H)?9!7zie0#d0vQ;(&T-e9PE9O^NYBpeG2p?z#)f=hMc&1Qmo^?yk+{Ss<9mU-5 zuAI5YR?VKd&rc> zc|p@J{f%i}`uTxE4wVfO6C;YZl}RF_VlBD zEUPj(^Ny{MEAyVE&<8p5kt+1*Ul`=GR`Oru%vVhQo5CR9<;)Lu`VP!bR{Fo>OgT-z z;-9BabaY`R!CEX6U0}#bHE^jSL=OqrQ6beapa%L?%T)&td!i2FMCBpI?`}IUH`ahQ|ZP^ zg0o^{g>*vxZ#OtogO3~O=)3`o?@ef!0)T|Hg1WE-&N*d{wB|>*!c5`18FXei=OD6 zVYY4Cf6C`zNAv1AEuO7vGs*ivGk5dYiajl+2+QUy9Z_nwqh;S^M!Q$<&ggtPWL1y) zBWlYxAL(Bp^x2!Tph@jP$1F~Vy}y$`AVv4}jQ1~ey6kkR*GVVQH`zbzcEmf=8iiTb zt)1gnKFxTr_>j?p#+$qQ>Tj5~&)F#I=#rMfw^Ib;*Ka%2e{SQ(`>QRRe647!P8KOO z)U#M>aK^58$6{W=1cMD}r#Co#nD%hMCed^2v2%+ijF)uSmRDvJ$^SIaazbBEL8qRX zY4--X@dTT@-|GLQ#%t-+59Zt{JA)_QiP)OE;WdBHw_%qbj*HMc9m!wV=}UsmxNR%e zIG5kr>*U5S4jE%%xZ3BQ+1jD=rfisgTYOqZ<{hxGHh2~P;llU(*=FxHUN!s}x^0|S z!L1Tw&mMYT4(C`7l)gXcF?&QxSnEw`UmyS6+OZjzv#9%&`4e_>KYXjPfsutYt&_g^ z#1p1f^oIB!;>F`?C(bCITJR-l_?NDQ?TYHJINomKrit}yr*3(Fw5IuKMO8w*%4^kq?%BTGZRK+&I1KxOp4qnXPVR)~{~+ z0rfi;U6kKH-&MDuny2N(eP7DDHMh&tyspS4 zZ*;vaMJB?1lTT*e^ex(V=v2MT1e^YZkyjN~tRe@}0nzU`$guGQTpcv?3*dLv2T|CLk^ikEMy;92-j6)ldv z)Vxdj1S6NbjrUt`|CG76?|~s7O1LS@h7>ux$?x*gnz6d?S*KC$INyCo(_Z^}xJ)>) z&eYxTrgi6rL4~HXW_w+~$!YI5KGXE<%#U@L(C)l3J4QHL)a#MA*GTBS+yC9`jzbBcV2X0P`+0^X&@z;V(#>neO*o<>t-JLdl7_oNR@QmG3k0Y6< zUWNsJX?65=o$`Jij9=Gn+B|5|kogk=KJ?hlq zku~%#_`kp3@QP#aX=`HKUanvgt9SFCtotLed(WiH?mD}A@wai0`0m^(vimw~!oHJ! zea{316mXaKKlts=fn$rp$28MDeC4d^Lg&zR=Qf%RzT7b;@7IC`P0uA7W$ZcYcXemB z>!>poULW+oUo~zyF4*CQ`NYfJdTd(YRR8$Klv=(aMGsSn#m<(Q6FFl>&%DsSt80UO zBSJhYvf2nO4Zaq)`PuwV7txmn=dRvc7ZmmKRf62@Y;s=BoeL(kzSt&a*d2NH^;&yn zzxG+BeA;^cUSQMfQ@(v~Dh#?5ymVL^Z%<^Q{OiUa_9olk?5-;VRe5}_pFz?cYnv&_W^`*kA7q|C{9eKWiQ^yEqe~;xR?H>K=siPbEjeHz*?i~li$wFX&adZBmtE+f+jjYf?N8GW z{CfMOe8~3tK9P${>lKX;Z-4NaeKeTyiyV4!Kec#2Vm)817o-A2f z?L_mK8Y8ngZf>)^qe9MMnz^TSIwzpj`!?)vT%R^jW0?0Z+Cv*ft$sKAwmt{?o4 zaF2>dKkU8zSi4<|ABf%G#dZyyIBrL|X)EdD8}30OrqQjuOKrZNs4E+U$cs0_ zZ(pv_<+#pU{c|_ok(tj5TQ<2k@xxgAM}-3i#mbm@gJ=Khl77&_-XYcJz%E~MasI1G zODB0$8)qMpJo`wN@ze&xS{%twy?=IUdEI6!Ba23NpS+}(Z|@6pA`V_jIBRmEFev-E zL-zC*p7-jMj;&#&XZ&Pl!S*`eEw9b!QTw9v)3i1w9p6l|D2weqBTjswe(L-dxf5E+bRTau00NYm(=XpWjMv^@Z(46j zUSuR1_58->O-HJq-05|6^M_8E+bWEYTBR?4Be$4xIp{1C@a@*4x+{BppD=uMte%Ou z_SUC;xW6o>G4ITu%{wmbl;Sq4uYb<6)Y9fIylvmxWr*gTs@5;r?9zy_B;Zo~z7}^n z2R6DZEZWuXUB;6uUmH~HaU3{2(=WJRv%;J4p?;ozIz5#8?r>Np6lbK0={9?%$t)2j zDrd6nDQqx3T_>I}ON2S97yQUcXK+KssvURrJl)Be@E}C73r@Td>H{u4OO^@f47v;W zrqe0AFJ+72)I$7Mrxm0tz=W`+0k;6m!bK56I7t*F#K94xh;)M>mHwJjZ1w32=?>wb za1Z#Alb+zR)~?R>q!(~l3yR(d^P^1At`GdO*P;Fx8UQY?75Y*_O6TynTnNO_ei*8; zg$L zP_fx1p!>)&0qo^OhzPxLL1YlDs+ls}L{^@?&}}_Qgd~g}N*a@Rg~$XHISO2UbqXMf ze~KQ2vW!WRB4RQ|91X70HcpOY4ESWBQ=#sHF=1~Il_O(;E2VI8J-#{Y?~W3YcmZ4n zf_+UVM}Bp>iX?IfRvyPA+n9_44m7<}ax$Jq%?%KrlamQR@=XPBY($TD>FJ`3OaxvC znM6Yj=usT(ij&FU6N)QuN|GtSmXoRADhpLgHHOlpC;h4gX$lSg{f!&d=^yB|g&y#c zX((_y3e+?YSCUG_s;(}O8Hk!md8z|HtBf$#F|Gz$PS$`6jZsWbG#jbTN5;Ux0{cjltOe8`TBd(OlrdQc;b4dL zC~^Z8Dg9&nONgV@Q_=b|s9b0y&?NLAO+`h|q|7tHgW*I|1Shg^076e^;baLeuqe*j zXwevR;P1avPI}^jd;9;La^j^+IGO(Mlv8n^;lESP|4upobjnFK(oQy)Z>a25lkt3$ zY@%G4U~HzpRpt#_fGa0k!TtR(2i-TFEN3BX8^YAH0T*|&9XPDX@(zUUq)Zsbc2RiM zW1-!^_A7*gyru)aqJ+sJVmlDdf*fo)b{sCa9XR0xE{l;p0MnW!?*)dOWK*H^Twxy) zRJr{aZ~*-p2eHZRb(-t~K}I5TYV$DD%YJ#B3sRy#f`qi>_q zxR1PBDg8&*-};5;nLbe5`>?L}h_T=E+UHgCHZ*nIT(?z?<7fB8?UOhDk~7O`{P)1V zMLh1Id4-Rs>Ur1fayB!6fu1*O4Uf~ni)gh=Xf?$&P<_vxJsVUm*{NCU!!fq#H$JfsoW6L?rKJp!J% zxs=M%Oyt$;$;ZIPDeMy!g8vlq7HI7&BBTsha`FybFw6Amuy~OE)*k99{d<6- zJw5;-r5zgBLrSOfbY+1x@gsQT+Y zQ;}K=LUlbdfFQJ+ArMlUcWO6fB{2e?oEU@4m12F6Dyo4A#VSg_CJn^WmnzELl!jq( zOW9>?2JA{@TnkuoVon99%DAec>H@9}I8?L_5K=mvs@80EQBFOSqgb;wSFhPrSFp4e zZuJ5BhlLxx{n%A9%9u0&3^bF5D7z7rjf*!aYjI|%60TUGHKtO}1dpn;LK{j^A#i(a zl!RUq<#6G78Q6%yD~sBzH|71j|CVq6E#KfC@PC(7|GT7$7hZ92rWdULyQGS<8SWpK zZ%t?qoXa;+^~;iQkzxhclm@^Gt{MHUvVv<4Tsg4-_fPWz^&r#&KsX3-fRHv(3__Kb zEsBR3y2ipjdP_jTAkqqcuh%azKYD?c|R=ql7513b{L)&*uS9f8Brr4zzDDHC+@qVWG! zm(Bpz(uIezyr~STiw_D@t}DA>m@kH@)vAKPnd|KqZ4w4Z>@~uHkkV>iWo;q?rkq58OM8N;Boe5~2PR5;iV-S7sJdRd z*%K*^qOs>lghheNvS&0hW57jw#sY!slQ`rv$XCmBbtQxX)aw%&qT(s<_v@1cD&UW2 zRiiL^BE?i%pCnO{tlGpnH%*2hwB2YRq%`lW0v-ciIT;JCb^*&NR#Cv?fYcQ5cp6Zt zfF}U2QUOl{mYhriS5?4OHB^rYlL3YrP60w{3l7zgE=r)l6f4iE;E|IQa22IH4XCQ6 ztFF}3As8j50wJZ{J}b{Nz$+&+!Bym0N`)!cWwU^!nN?(cw$i6qm(8Itt*ra$q>#D5 zt(5tBz><^s;L^-jJ-1_>qb&dsD!LE|sX1#+wg`nRMj@5Y(cn2Ft>xGfK>cAk<`W)n zOqN183=hjt#q%2G@ZWRevS1SW@5;gvwaQY@&_QVc*6Ji#=EQ?mb#$?!?2|BlK2 zdl>M)hXLt>h5xOyXs^h69`ujvtW~so&6U#rR#Nnkg*z4C($VjC z0}3uh)*@^j!W56TNbO-nkvf0_-T!(F-k=CpJ(rS!%uHk|o=e#XwA778=}$IMzSILe zT2txGl&yH$MA``g6_1iw@&nGKPq;L{xJ>^>hJE0L$GZ%U=8n%expHLb3{ghvwZoq} z4cQWInA_v}bNzZzmtH1#Htv&Ce`dPwvLzGW%$>1u)TZetE1s-%iq5dSsKXr=dUy9X z?ogYJ;W`babvJ$bdS+#+)nW(RoS%Xgv%8h;w4Qst<*H=!pUd-g$8L1^)hX|5hdZ}D zvws|3;C`j)vxKA5u6N0}uD`qFi|+2jXY1vzC_cXB&8wp$o$Ib!d!^R=Bb_=-v^tpZ z#l3xOZmkXlXXMYh$-(PKY@K{-2!Bn^Onxn2-+t}8&7YMktvS`s;$^Al!-`|V#9qaV zJM8nV(IVe#yy@7?ao7&Z;G9{>7F z_O$9z8D}3`lyop%S}}0$Wxv8n5#L7+ShhW__VqC{mo9I1xlLi~L)~M0Ofnldfale66# zTa{0}Qe$8G{?BEzvZfmLY}BiHwFVX?-G@GC*JAS-=@sweL*Lyt`f%RT(n(Y+scnzm zdmFs<|JCEvv?hkvT;3Xm=wuw4v0$COU#Ikwzq-wT{*zqL$HiRiv)+-`BtX8Sjkw`u)q%J>>CW{v@2^)C!B zPyTjy$;mkDk2}ih2k1$8-EW7Ttl!K;9{tjDrEf;Y#B~cE+fHA@x3V@|x3QjZ#P%2c z4&+&sZ0_g%lCviGaE~#|2VC_S(BhoE)yfB-$89a%=v+RR?3J1{lsn&@Sva6e<{=)p z&+Ky_`pmo$dpdG*g4K4jb^(cA*LR$F_Hz z=(ewsV1BoEyZ9lo$^L2QU2|V8zWaS?edpQ2`h{21+r1vPAn()>se9>_M-%-EwjY}I zb>#Ge`rY1Nv1^wbI^=Q*Z*K6`W`4gywxoF)eL8UPb@rf%>j;6uVf}sZsDAQ#BZhohraVnp0)Ik=$uibR?)7*UKOYE4~Y}HIkbJ(BJ;7)lUTiG zx9pEi*=yhYoX(4y!)+TJ$?9_Ma@LxCmgAQ;@@ja+He=Peg;%C;>GojU(11hcuF#L6D}TY z@Z3z?a$k4DGn0?#O(@Lk^Kw$(g%i8R$+mr(f8&WqR7&h4&xwVWB?G=a9NYSFbms7O zdL78}VO`ivLp3Ctg4Q*}iQ&e%w&M zkDRuBPwv_Mz12^?-)8%*vbU%6-R-a0&sqI5>SFN&-KXCtuI$=(tTj~X{$Yz7T5S3f zzUZaitf#&sO!{A6BIS?ll)ud9*}D&E6(w8Nz8P=U(JpX(vk5ott1r~GUGc6>_k~{{ zne3iEJ>$0D8QGbGrAgKaeV(OGwj0-)lSqo1ZRDF;?Yw{EMdV`sS~7sFn=tHB?EI9F zHu^>F+{d@f6sNVUczUo`?ztZA!nZ|~JZ_nmTYkVGf5VWD4`=s$IHmgW`l;{yTQxdg z-!L`Jaa3w!lid%BEOLLe&#>+BqKAKvpbKWpM_E4VJhU`v-^S!;^M>r{HTcr9Z11Z} zXGF~|i@n%%EEBsSuDnyh&u3r!U$5)6bmicn)&9(eM#qzU!tYs5`o=Wx{d3>_tcKgl zd%ieV-j63}Gnes6(|ckZYBH7jnkS||vt?|4%tF`n!F^)0a+ znbTKVtJmY7>8@{v6m?(dG^wjg=Bcq}K_MAGmUm7w@)CrK zD+yX!-*e{nd(C!r$#x#u{?o-RCee>x>BfAzfBfK0m-toN{GWC(V`??M=C&AkA3x97)Zl1x$3%Acbz7*5@8HGj&P6mE9ulI#;B>KJw!>aZlJ zZ`~8Gc&?ru{UoIX%%b;&hO2-%G%y0H}&4zB$87JF(+)yGum2MY&8#m()EWP>w5}Zdem#xGUDW}8Rbha z80=ZUB=O^@k0w7WEd0EGjCkX^z|ZQ4*~i-=zt*?}E9?5?;T_;mgF$bFW^X(hTHhpo*yJ%7#W zlGqmk&BpnCs@`u+!KiPaO79)*@&2Rz@dc}@*Bd|JSA*`49_KCGGkw^-y%pU#n*+id z`WMzrtJT#hWx@obS@Y^{ST?U!=bdTX2ExsAtVA9;-j-#XdU6(6+-l=*xvF0s-JwCl z(#@DW?<-@3;kh$R@@s5t(YeRciJ^NpK9Q};2xwT~bE}z5cXY_4sgKIfOv)PiZIjRY z8rMG*j<~U_eVcB*%%{`{d%LIimUAs;otb`gs(w)`v2H!z)hRPhI0e^z+qL`Q(68=m zNA9Y@nc-zWxnkz*v`0w`vUk?Fk^OS_E_jqA@Ar?A;I-nakA%2+kS#D1RNf!n3Z$H5 z;f%2hObYQ)kZsia_m6^X2bkhf5NS7vD(wyq%vg7z$ek!dy{YEzLUsX%wNbYlVS6YO zc98eNFZ=n9Yz*B8F7DXvr-YQwg>lY&07DOAs3r+#a)<&ndx3`$c7!q^iKFn#PU0Ab z=75Vy9H%h4AAbV*Cy}of?&eNTQGiDHX@s4jOb~t+;s5gaj$9C;wNIaiX`DmZuuq>4 zgmSwHZ-2|ldEnqq)CC$uHK$u<5l|3x6HY2dLcPIuQH8!l(bR|(9j!#;Ws%Fkl#?sq z;+7rk@s5FDxQ~-Ue>89HQ{440K=f5at4-zZK&}BtvwwOWQ8y^>_xqK(%=azj$Gv)^{i{ssn4I zu?W1Fm0}>I)gc*LU|P3(N+3i|o`L&&trJ3}Clr}}j>s2?R2PJ{aI zey-Fskap;5+bXtw!I|V0y5lxG)&Z(bLf}e`FUOcJ?x_=*qcZ zm)5^{ZC2OVxa{^*UZV|D1}ENnkmEh=_^{8>J92LAnB+0f`##V9K;Qe0`R93_t+~az zkALyvw>ECYANq@TOaoq}`Oo z-*R+zThDkJxttgEBFri8jLvcAZA1NPiz|3$t-L0bzxm87@P5i^ucLcBYtoj1bM}1B z`Pnpkr1h+4IU~xwpXELHmeXW~bRdt+>fs zHQ(ci{oL=oO|FC6yIFtbJwLH~{nL>@c_YfA(nn7?n`2)0_TGi%=Xl-S;=Xi!`;lkU zBDmR6lQ%iub-yjzem^5;&8LrhJkQ_d&8fY5?W`X<$IGU#x!=<54lnSs+XS-wYR=ew zOYX$Z`o!zJ!eaE(XD@Qvh1}~fdgc$_aqErz_!~EPHmf+6ftH_hHVpaJd8ge+-h{Zt z?Fyw2dGop3Pw~4~**h^^BVPQ$%K4* z%zG2j)8nrBo16oBp&joHzQr4~^QVkI?H%uGi?eZWExz&Y<@f2tee#ZX<>kyVf-^69 zovoMFS~mL|uUYwqVW00k&oO+JFmulPXS_jU3e(oiKA)49xFYK3v`0C|dp`^MQ0;b3 z-p`#Qp02r@^D1S`fF>(n9;~Zaqp}=UmS@KCNv%n=i#V0mb2*kM}?F9-LjC)MCSTUc14^{mcV@^49bT zTUmOggy*r_5tJ&+xflI*K;p1>IlCK&WUYDpm3O>vx6Dlqa(EAJ1q55zed29u_-2(s z@O7T}+lA#}gUWcE1>bn1g0gv|OH=$#{d&)9lpXsbtnW46m1?c}$vU0NxoWs4>9ko% z4mn)SWqY^soTxr;B;T7q&UwIjIrnVRubeFBV;_&b((#sVGif-m|L2^*`@Onb-~R-+ zC)Ll1VvqF!8`?)~X#a;hrZgI>llcT*>|{OzA^uK#m{y2Tp5lA~kDPo3SJlJ()oJ}V zM1M!Lx|i{AAwPh_8a;m^>=(GKW4dx=R)C8mvJP|!s?JLnXt*N?F6%Va!G-7nN7H## zM^p{UgU(AI(SO@{830Vvc^RSzBaEs#l{N;SR_A3xga7|Km9B|8nW9egl+=vs!#b6& zMg6RE#LB1AG(PKW(Hy*3;I)B}(gLGpuDttL2Y7N)7hL5z8%<1k&Q=dd%{g0rrB89r z)_}sQoU=6qZl!a!M!=Gj#^6GA!gDrhW&K%|vkBl(*QQib8C8^?v8j(3o1wJk3TeOH zWL3|8EL5UeP*KGo3ZFxz1CN}vL{{xt=8rS3K>>rDT<@W-H*z zNqcZrO;r1tL(L?>8j&`LR5wpY7t#SZns#K1C_Bo7c4Uv}zimej0MoQ1M-<_NQB{)w zXYgsYBNrNslYnaY)W90i&kH6szUp{~3vNGIZTgOPVDrQa54pE_b#A_YE!$C^6W23) zTjw9SIVC+zFFn+Mm9sJLeALG$Uvm~2?jGMD=Mm4*S7?0US6PnfwuxISZdUW=jhj06 zU}`mQy{{`f?fCI2=j#2LS*0s=kJtCLGjz)Tk)va8eYbJ9u$=PQpMBzXe&lTsdQ|H! z`JS^mXXzT-OP_N_+v%o6Ua#O8mWF-nllUN~au4i^xpc!^{vUc^8jV%S-NB2M+ye;l z-6}nBM_|iICva88`&T`%C!)O&ttw(iduP%aIIJF+hcIt&Sv{~1GP{6_J+Lnjsvfv2 z26e+Al~@NySJE9gnwrxCQ9UUSYECai|833b4KPj3@k0@PFsiBt_6MI<%?Y5vP;=nT zS#U4Nyz+gZ%H`A-Wd@>5+Jp9^@>o4+f9n5>9+bvs^`JrE#R3ZkLP`zJ>OuM7m6HMB zDtl0xn6d{Q2&AS59i;RrdeFfXR;33W0^CYH=ulw6{T^`DJ!ob9S(PgUaH#8WD(P~S z9#nwRMku8Hwg<&ku@GUQ2vZk7JgyN299FFgN0^8*q5LBd{*PZ$7YWg6y`Y~*z{gs| zC|!b5VLdEGg09&|Vn7u7)!I3_k!awsw2MJlEM z#XB}xVDRv|&POT&P4^sBYAcPFcX!_saI!q!qIXtxgG|5&^A5M4{V4JbVW zrGj>uNEk@zo_5mYf^`SKa#uLK5uJi`Dxd1sq!T7!XogU8@WLIlz>Y zbpntm@+1&a+CHlk)+u1h$!Ty^BL8Z_at6_75v?vPM|+YB99CiF zA?zHutisAi=6P_jur2^0)dfdA>%Ryd)boRy@w-el|i=6`CY6|PB(x)h_ zYv8L|Sl5ADsjzMUOHOWrt1hfs6vdiqunMaXf>766KuBr7T&1vX15-}!fUB}><-$_u z{4Rh{+C3nobPS@>IPj}pJ>3TnYWjF9|KcPo`9 zfy?FVMaCtNav~ePT@F4nBy!xKD z0pbk7#ai;GFCHn9G$uwgGJVUD@)Ie<7#M$e%aP_IDJlJl360o5nxxoPuL(XmF{Qkw zQYrB0gD&clFathXq80_f5=LDTs;B)ELKT#8keVXq0EUd$h95bp11`L2ND`hTpl^60 zb-^#CsZOm_bmcMI7e{*(l$;sQWe`cV>Fo1xX@sGmTg>tAt!F& zDs1!HdgqQH4+{EYy`$67j)?1oI96WZg;t)xV|Djl2<%LmFd^l^FMAQ_jiEl^V!z)7 ziEe+F!ec?w=0>Pz?O7YxWJ+7-|y)A3-CL0MS~-h*5$BW5Oy%iUieRBnCvG zU!#L7-FpBYONST)#!@Ee5C^~PJTe$cz{Nbukf8G8F(3i`Z22x^6!2K`6A_q1nIJzI zBmB$z2%|xWmKDZe8e>s5SV0a17W6pek4HXRNoO*FLNuEQ6A?IxGC|47@XI#B6bzjT zE*c>P32KCC7%(0EEcp&@Bo%lp`7;nWlQKd6ER4YQON4@M`TLJFc5tx4fBLaTG8^z( z`JIE}=AszL?>r=^Lh~_T0s7erIg^FJV=1%MMbR{lY3Bq*P7l7x0`T4QRNk4sO*~{K*nRwH{4c4JggwhxQyTA=H?xfpDr`n-$hWi{_fumV| zWFcxB<*BatRe5FDb}ED3@}r;dtWdwocLzq@Ndpzv`|vZtuxw?0Bv?wdP_JBffd|L9 z-9Sj`+UA-nb2{1I+ zDIlbDMoAN|v?HThah`@?IXMF^d$+|EPI}J*Ph*Z;#N~nem*zM}BULd+K1Ks`oCiWm z=g=&3TmY|}Tm<(o7M+(6beV$wa?yDOaRrEDm#zo73Ov?K?HU5FQzp#RZon^lYI+kx z3&F*y=`BiNF-m=bgj#eP0e29kGfM&R1R?K2@QMD4LppW!_@&%1H^htlFko9X^AA%C+q|MtMP_{IRy- zLZB3ZFA>PHg0mZW1w57&UL)`gWrDTe!Y|v}Wf=MnTr|gfB`wmAsVT1r`98ZA&ZETuR=pb=UkzZLS? zN;;C(6r!ic>Wq9BINklw&!Dd2~|K9mUx_+x~B zS)&6$h?bUpF^xc!4O;dCf}Z^-N09#D#Yq5tJ&2Sl{uh&gUxsUa8*#2RD9pbQ2q~??keX`OSa}$uJo13g*oPUDP=G)Yhry4WgoCTujm6v7A`FNC7Y9WW zNg++8RXcEX5r_eSDoB8k(zZ!6th{(D1+Scp1Xp<=N#!Xo4nzT|IgpH2`V$i0ZUF8a6wiQPl>s58og&Lz?2pIv7pY&M3tvzH9M~)Ye&l2n zxLm3Pzq&9sItkV9LoXaQ`R{`_I{kn}ax3_vPeH z<^qq^ea%DQe9DCGYXSVS2mFN?x(HkxCl(_?yT>IMuoV4l`L1Lc@L2MfBX9*}g8Y>j z;a_%-t3ZfW_n3xhq@!%;9#;dQtk=pOat(0Muxn`$)%`Cj);b85ll96($Zp8tS#V$H}He|7ZocDLE9+kZ!6YzKw!Kqn^ihhYixogu8UyGiisC%3S4>!t z+Cz>(6sXcU@B_D{z*Sg1gq)zVxFAcjhB^r>A-_eq2#)Jxlg04y;V2P-3;*E4htdSX zD2Y%e3MZ!k7J?mQ9hoQ|KK>+&7YV}!u@OLqk<$=5oo}FemG~L(s;*nmVt_Iki;eg! zc;qA(Tv`(ig$eLrfrovjMM*;KWXaKj(5R7ucp?dx2qPhLqv)LX27vs0_zme` zk^okz1S2;goZq0*`J9iBO_wfordSA}TFv_wFy!PmxR`@GNYEVI#ejS0SLMJ3UTAqA zIIJ8zK-fddgd9AAU$%W7W9SobQTkIPsPrNXC`P|Vx(g`*4omtoggvKBkp2SU|FVNC z1tD4%c!_DeLfO#4y#@j+$Q$IpMZQKwXHrH1n#uY*guSOsQ1Ju&vJLPNLqCCw2KbBw zHNY1P_=E99dgEB$-PYRb}4d6+!p#O9}=g2yr`vtMIa$Jr=E2uEK z?9tJu1XW5GD0o~9{Tij5i5_rRN>xW#4ax+i^eO!Rtds%7(o)J0g&I*|R4HR5s8S{v zP!s)XrQ95dDR5XynIWteWr9-X6s}OJox)OeV}e;r79St0q4uz5sSP+SHR_<4x->df zqaG4ejrthS0R3t;Ts=rb;IPzags{ex32HP!_`j^NO+kp3j?FNQ<|rFL_w{=)*PRH@ascFae+|@vVQTTEy&W+v>hg8Nt2+~X@vpp z(XUd~(Zi8g1Ba!m4Z=E5Ca7vl;eV$pu>+J}L(tb7^i`@#7tEo;_%N|3To4``&sDI) z#IaE}%G^-eHe3>|{DKA7-p0|!!3ugzdl09UCkJ52i6hm8=E(^OnkQ!ra6!L1Pi~IH z6*#OsxgpG*G9gbM6fXULtaC>Ys-<%$Ow*I5MRoQ+I@6yn(~g*#}`= zC=+z{gWq|-p zqc6$^3j_jzZKfac`y*eiqKgX&q5w^s2}T&7GC{=w@XI#9KnxuOE*fAk64U@gFkmS9 z)zV>GcNlP3(nAn7oH9YWfWrT_nvVdKUjwM-z0}p5)?w`9h)A(96A&PPnr<5jvn0hg zKDkz~8`diZCJwfUfIiTMlLQ}pQMh!5AOyKumJ0=joP<%OXx#}%f@V&H0TJj|XU@Ta zL;{DEIWfW{lnI%WB7BnH5!kfzRBqa7ZPg;MXjJ#v(>ed7#la#QyDbf#U&2RZx6~Y66Owh*1?UJ)8uz)>DC#X&^mf z+>pxNbk}}rehSK&igKD*sc*5eFUqRlnu9O(rV(Jat+_gz0t{RzO#?zo%LJAY;sfec z(sba+Nh-LCMVI)J_Hib**BJ2L^vw*`Y%Gn5;Lz!T>xd{Kq59iE-=vrku zAEhrqsZgd1k)RcA5e6(qzv@F#aN&9hFj&ehMbt9N1m%_^`X4K|0-|dvw-TkVLaCr! z8WL2wbPQOHezkJ$WDPJ_%B@AzI?4p))+73#KTfv+;%jM_f#Ne!ENHh82rSo4$lr{7 zwQ??G3k}zF!&?!RMVX-7Hbnnp%Wa3~TDt8(={r#>=(Y<9YPsDQum}BWM=;sll)Hwg>y!z~ z-9Yp|R_-Q5*HW$!rQbrSpxkXFsB(8O;4b=A$~iled%$2RcOOv?C=-->NYQ^&c8>s~ zrNLtq@q|XDW%m>b18jLk7*vd$-@Hk;1cRR`f>m$QeU8i*$W#m}r9i9Rr2CTctG-G1 z6=hd>lkVkC=Q}s!HL3q&$fl-V(N`-RTmIT{whecu~;C70h$imhwjv2^)}^u|*n-x=0F zzvP1dl;_iy-H@*uv+iZwzEgsd!1SkGpSKI|=zFHT(S;Xrx?@Iuk$&{h30>(M*yd2L zyZ$$B+I*P0bkppoU7}9#@=YeYc6b;up!e43vYmH&EzNRjM)35^RvMAzS-T*oWDD9Y^}%VMn92` z|5Cr~^(7tQ3fr&I??<@%l{&7OR>(hD{d-}HXrEt6Q%5JXI?~4U;@ZTQ7ndzcwtI5Y z>crjAd+&<&ozD<)=gyxgo28qw_E|RAzwNCuEAiA@+aufx=gqAB zYC+q9BibKpn7^)@x7*S7c3Hb?f8EE-8vNp5`oe(Q!{P#-EN%ASasJ+jUCZhYIre$- z*vxo6$;(fKGiAo0UTI#B`+i=tvET4X#B)dMlN;)+cPYQTXLQzsbe9KLnqRZ{nr$)p zRb0!O6E5uc+VL^P>UGzhKU~b;@8}aX`Lu&m-o3*XM$MM4JGJd$%8lTAXSd%uXwl4l zV}rNVm#yv4%1yucLRR_ywg-2)?_Rjce-+ck;^>Jn$ugT7tM2uF_$6}F)^X_;hR!3y zp8NNG6|k^%lHIGku9?2mwi&*B(|uKLzjj8~oC`Nw<&S9FR4$LQ^Bwix;pX?qyyZ=8 zewx2pR7Cg(ipw7`UmbOSPRqA#6CUv_Q|M;~ehZukPgG|Le|fr-&8}vp%l(eKP;mBd&O0u7T;S z;*_4@AJ4t(cE!8-wdL~*!WwdnVm`eVjq%^<>prcYOMcfTgUedPSv?F)Zs5P8(Yy6V zr3TU8&CmZxU3EUYX6L;Y>mJ2CsX5N#qrs>S8wXzR;N|~5#Cz>KzZ2H!Yj|0EQ?8Uw zwCQ)JePrju`qw#LSHowluitS@-iF(IFFk5Is%cu>$-thjK7x!HcIVWgX4e>QA3yuyd1lj)D{BmbD;kWeS&h88& zJ&X)LjLkUr%DAHLcz&G@*=D`FIPW>%+4ueGvYJL|^}~3JpWp2DGOJiP+p@KeP5AlJ z+`-Yu&84Ac+pDbzYVe~CZ&gK>rQYsU&kr78UK%V3zq8G)Gm z+QQQV1#he6*J`qWZ&hdcjP`9G`n@z<>@VM%-&S~`&hXY#g6h<5^7b}&+0f$4D?&dn zHn@83C$F2~3!jPaem1#w?e=L)pOuqZ4I95($LG)$34dJ4=?9)`yD!S$#z}XbHn;WD z0cow*d6_e5B`%&@KUjX1pA-IU^ySe0gy#jlXY3u&wfat}PHJG&dDXA#nr03>Zv5T* zUdHFLaRnD`hmKz|>89)FKDNt;H7;J|vFPpGF%Ng1I7Sxqyy$0g)+F_*eRQo)C0pNj zA3xY*=EDBx6Nfb2G_>!yg3eu=C!Bp(-8A#QqeF)lg2+sN($!^tgTc$roc3`WpEc0< z*r`FAEiIawq!upkz^`{}dcuejo%&^hqNSzncsj;o-X~x8D>6-GhMU(I#EUc5Oe|3L1ty2pw`lj_uMv(&O^RbZ2f5B&d+y|<2!B5D4|69^XE9fBul zTsUBZOCadtAw)JLLyV4~ixd2CIpmN74tIxRaX;MM4tIBc@2Z`hnN3J|o`?H=|N7+h zN^N)5yQ^!ut7Up-x+gCxoZs`o6$js1*DCtE_ulO8QTuBF_gfz6J>L0`yztoN<@`f! z8g=~jk4=MW|8o0=Ymp*Li(fl7FMW%T8xp4OC>+)6i2cD+QtalzgJV6iJN~t&<^F2d ztIqx?>bo``Qm^r`cMWrHDAwJjj;ZLLX65aNN^5H8@0u=W>-q0rhx&~cW2UreJYZS2 znGJt$U`UKyAv$fCygu^k#QRgax*eJFOMvr|9ovgO^>CF$g@F1tgV*0`+|0R!_lJ_x z);{)IxchRgwr#e53r-lctp1sKy6Xd%FUVJC+xD;ZuAcqsJMrepaUb{8Dfao+<%n)8 z|M@4eT)O=GTTX4Wa9x%^3v_Cct4|5HiM!9%uoB*6ChL!vFpXs!>U8f>{r!VyJ z_`5SfMe0mLpG6?y6DqGH-m@C;H)_{MQ;@nR0VoOt-e{+O}QV^FjBht3`L#I9+XG|5dJo zhrY^Dxqi~I&W7J|tzGW@)yFor>Fq_ciiX@i+;ZW{9xfl2FDp_w{_r*4j45oMFb-ZZsjq~6)F+3B+R;>vq%*!Oam{Q#R~J~0`7 zZTe}(`co4gRPEENS;av^Iz(;j`DWqa`%iR^PP2!1om0O|U$1u4&tGus<{vV8(6h$- z`wg9U=G^WfD+agSa4b*F6}QH0nlOLcfHtu;U*Fk3IOea~j}oh9EnwUbUpzj4hT(P( z`$sQ~yScx}sF=QOPBtravdYYFQ_c*m|J&M*V-HCsV;f1knrzFKIQHeHW^?zxuXFEU z%*LiUDt20b?nFuB*vfN<_s)F#h12z!4ke~kZt=Wy_shlCh19P!D1D2i{mTuSH*4_K z^3vFSFOGEfp7C*h8P6V_uV*N^`=&$R*fIJ4sM}|9qx&m%$G)0YDb)9$phuUp-^frh z?}K$C{xYo`)OpcWQQPM1hWOxSm2LmCt#ok|FdnkGxrry33CbILK1I+dp7D%P*-7^~ zMSr}L?hC-avy<*iR{4r`SSHnOKH@bPno0E=M!h8_Ce`oot6kB1&(aT|JYW7u0g0x< zJZ1jG($6eaa_|;kNUq+jer41*Vj>5dOx8KrB93P*pqxW`1X#Bn^D{7C(d{ZSl3dlD ziBXw}3Ef#3{gX?k_E2JV&ybb#$i}*H&yXE~@Jz~EIXMvJ)tsCNNMtoVLQeKRiGt2$ zLFXnkl^>z4_R!4(3AWG!rHAD7DR5eQ=;mW|enuCVjYPIrJ zn9`@Zg>Df}UX(D_ubW!GX{x*{RSZZjlH!y^B0rSq?n<+TZV5;NL`jw_Tj)9ZsSIEm8#2X2VEyf@a`n&_l?G~9^K(qVpwH{Sq$^=5>>#^xN}uTRU;;h zb0)O;l*aRPVq`n{#K?9jPK?BYs|&zZZtKdr+(-xA>bi4)Oz~iWC&v|2oP`${8dJO( zV^qkSVoTnY^#Cj%B_qvn>C5h}wv&{#%DJb%_UC>M{0* z%fIyjBP-0yzYSPLLr!YhP_PkVR?VO>iLotQdh@Qr`sdyYp2uFia>cawOn8XN@s-Y@ zRi0LbwrAKm;r?wwSoZc4v{-#p^b2^vM)n)YQJ|ze-wxh-tt`cuzfu{V}Ols_+4Yox{kKKgI`yQ z-eaF`XZb4+FDHI4FC_9}iePS0txhDxkK%yp?q9^GJrcnferrDDOGc1H*mJ;>w6ro^fO4PupFi=g2 zc4KBZD3@pi0(_9A#Qg5eS5&%+NRlgiL_{$tnt0zIL}Dm?s)I-@C+|TR>p?^aQfc+g zf-+pjLBypNkAOs1uBqV!MDdy^sC5nZB&@lHdy!CE!@WtCvWEMBoveoYf`wBuNr9z? zQyQvNuMs#lv_Aq8b@pUvUhHh3Jk}Wircn$8O|5zrZG!+}ZG#bzXyul)S#7I4gtZOT zXiICOq+yIsVzi}#y*$KlFf=|hf>9$uH9j+nnWI73XT~7F1KL>T|H^ztrL!1Ea&>+- zo>3EsiJp5Rq5sokhbIASHHu7TT~kO0jUrPyKm*z|7EI^3#S|Qs`x_V~p#lF{=S>8HxYj?KHidZCb%L(LSjw$sWx12Z>*a^-A7za!KxI)B#2*C5iVt!O)cCAx0er)s*BBW*!CQlKdM19xskD z|2Xp%l^)^*$(8ZqB!f;7@B8D$X-c2!cyWf4pCwGP@!}k*)Oe9*=dkk-aiLv6K%y(x z)a*q>vH2aSbtRT3nt*=yh>tJ&*d8O04yu32%DAVsB=W-9&rEg;y; z+XzV1laiTMqs1LCjp83ru3K>zp_CP@43zgEW{vj|kf^)YT=)Y-jp8Aw`NAbqCQpYR zA*g;E@>q_UJ^Km4tgfF9KYj{svJ!X(mQg$hr4snwS(VDAzW|60eTjfXPfW>BtIrQ! zv9{M{ZK-c5{Dxs~8TOs$b?;dG-Ym8}ulvBvkIXbbult0s<$2v_;-`FG_l4Lgp4a6l znq}yzyUhzFWh&=ZE9tklhc@pikhrGU=bL$IR{w2;d%k;ZC*>;OaUO!#&(Jk(dT9!Vn;L9Vw{FNos<40ZZjw!n%*R4i#JKbxPtzc~EJylPE~wmz5R;hCXFCw3}vsaBM8p+|l6f2}gjl0w#xZd9%5xONYU zv@XH4qkGxP_J^iYG$r@#x{4@nEH-I5|$QP z;ix}3VaTKW4!Jk9=)Attxwh%sJ#5h8O}eflMq-_Vq zqc5EH9KTxBK9juM_5GnEF{4vbAIG`IVaw%djfr;@0CU zee3A7vekn=>wM-c+1kI+iQ8dESG_La{bg>@pyHF}lwKIL`O1iv3$NYwKmKNspJ#b@6`-zzw8(9YaF zPdLrL-}LB~rmqt&w*L~g*uU4Tb&bAWvhDPA^&bnLjXkf6|IK6ToRKvfzDZh_t>|-? zvyb;0UT5E!XZnsp=`yeVv-KN4P}6gl>+bTuy{zd_@ST(Gjf9CI5&ee7by;?^ z*WOaEDy}(L{l$@XFS=h`+ODK)wT|5e7P23A_e%ezC0u_md}+zAv*(n(v~+g-rWaLq zEKI+*bWo{%18+q1+4yVKac}DuC|Gy=utr;BI<5?!<#u^$sU;(9UnXSqTUM<1-|4zu z-;=5Opw;g86Qft`xt*~4Law7Zn;ieN@A7DuFKzA=Ty<^pxdA;)tp<#$F{V(0L!-={ z$9NAlT-w&Y{O z*5->3N!+79RBqrGeeE`RU+zt~x3^2FGtoCo<#^tv{e3(CwQZB!X6J}3nrr!@&ovi6 zc-3|Pm8?b2|N3Nh`&ab~Ec~3e%9%+YYUL?$JEG>*mdlQhuV6U5y~49AA#bK!9d+V) zI{zb6+P|vYboSvjG)B5E0c{l#^vQsA# z{d@ZyTQ}VPt6|?SXJ))Dv9IR!VK(V|_IAtGroYSGvIpkeIDNVOoRDG_S35tcJUsK9 zVprTd9J_t5Lz8u;&>NW#p8Ps$$hNU-r_G9=v*f~*HnS$UKF#lS=U^+xjN3hOq^Gvk4G=KNfD96Hs zuGSs#w(rg8;se%h^vzNHP?0>(bGLl^IAF%O{P$`Vy?sA+eQ1{_we+nsw3~kFM4NPL z?W%lgP<2g-*@%3N(9ym6+#F|Dm6OL|o{L5whDF0ZWKljx%K64$?Xh@Y6(Y1zm7MJ%;OUNpNwrLk6moDrPYzQ`4f+?e|&53(37Tb>--I2_w?Q8Uw?Haccx5} zCiY!2Y}BFnn(MuSKEL)T`}=}n#k0nD8k6{s>+yll+jf0lD5m6DubJU?_E!RKY|Un@ zHM?cmx)Tpw4oiQevEAK2OP6z*GizY6_-%IXe;=D{cq3MQE>yv{N0BB2=B0|F^yL09# zx78Ha@a>|ax)q1Iw7g{eaA~-wTi)}h%EmppKXdP({b7B|9-qDU!cj-(ZP})nu0ITl z?OM3V=lnx{^Bg#>rE|quo7>hre*W-*X48re37_6AWXhO+@qT4;QwifuJ^3K0p0Ue2u-Z9CI4fw z?(4DmO(OI_^{+v}Z)&$aJ8I6jPmP>LzC84x`ZS-~14|!s*!xG**P&+~clEwJZ*PEp z{G5YsuS>l0ES0yQpu0Qqysp=O^}G&eIDW-U zP=1W?4MC%@!QLE>lJdOHmZCp?UY8EA?>w(d54KU*u@1{!mAAXd0EXs9EhD2c5fe)X znenTAUYCWX_Mm*nmX!h$O@(>QCL2q$vsB5!P2>PWlS58M$ca!#`d=KJ4A~KxrK~j5USIUAeO=v1YLPuelS6SLC1Ex`w1?8KyA3U-v$LR8mwv?2& zv#0=udiU(eASdE||L(aWrBC(lxe_O@Oc?9CXX_hh<+iK}kX$5HDTzd`DA5g;=8;`B z@bFBE<;o*F7ew(95HbIkM|R|yoLQ1^gAQe-2j{)IQ{4Q>&Vw`YBnF(u3%~rxj(i=D z?8pSoBRg+I`AU2ckhXvCkzI92a3B)s_l;K1yQ;y+nvAqmm5ZCG1%_HxItKX?59ZZo z@DCUB>HwxL=GA2t^*E{J=~R8htQPYckeH9VvZv#&_{_`d)k^$+!11%`O7XE-_LV$m z`aO%k$d=v4*Y`llB`<#4t!sMgmpLQuJ=CrK_4?k5*B+S8xHgX)TmGRgvEr42hZ5fD z2Ha|vbo!RfPX7xRhmRQfLg(1$*tJikZG9h0om*xI!x@WxM8*7vaHeGT_R)_TzSfQE zW;@s8zkI1^ZDD{9eZk~+_I@4x*l(s>p(Q8hFTGdSW@PuI0q^eW93uMZ=d4?;>wIB* z>wcBK=@zfp()VWl3%Zy2-}S24=(BEP?s+4JNgqsY97q3It;q+|g5r6qeZkq7x$1dV zzq96wY5MEvspS&inT8HLx%~9|FS>q1a%LUg{vXr9_{#f+PI;lbGq850R;!=sveg;= zoALTb-SoU?Gc;;aeTLkZ-#2_3tj& zQt+rLT&%HQ@#(B?d;NgXZ+2faoj&Ta@<7Z>Q{{$RChckXS$86Hz_sB^&gj+`h>EBd zWxF%{PV-EkB5&yGeVUnWh5voi-0EGrm+ZJhS7PkZe1GToq`Pxjsu7g_jw$oG+K2rX zeAZQtomM(u)0?`^r@QBS_ugjb<3+!g>{0ZqZp4jc?KyE{LvCn|xS{T#6rjKciDEBc<5RmAr2pSTsp3nF}ViZk5EqzQ{ z&u5x3+Mm&ue#XaLGzUX7c(!0vOHj=kZ7XKB2IcX%4FZ-k*YqrE%OZnA47K-a z&!7&(L+{m*!9VQ1IsvBkUY%J*04KFPis*uvRqqu@;{SJ#BD%6oL2MH}iU=lqG>;-e zD6UyVPW~u@(rXr$LlNZ~Hy|KUby1DWkD9{}HHvPa^7<-eCa{* z6zi+q!A-Wl8VQzBM1jJY%zN)7w_oF((ZI2-F$hSDzqiC1%L;p#6@GV#RasdU7Hx5) zjlP|tE4MlaCZ4q=q@m5?k%^4#$w-Swy1I*AV5lD1n?Zeu2aoK_;2(NqKfqLvG_s2R zoYXR&4nWMxBL|Y0$5T5VPkk|^Yf*Iap)EVE==>kK?LNLdy>G5Bm+BphKchP~wZ8Ym zigr8S=e^cEpUVf`#%w+PDs+8o+WDk&<|o@e=hx+Og?&R*lYf@YL3Crp$Zhyw83;sNoX;ku z_kDla^;4hI=XAS9O3SWXd2g!md0>gmyIz|{yy#i1PxNcu1^x1&V~2e(Imfo|SFZkL z-QFG@7j|Bg-q(HGgaNs7ebH?ha%BGcqi;+$U3OPGwtlYZMz$^!1{JxZdtK~q7uN}& zbm5a%Z0NT8v#FIKDPlz>yPfIwJo3AdKHbh6ei>?P^``7U zUnVu-laf_;)e|C9c9IHN}}TK#&`O^gIb z)8UR{*l18qhdYLuV?nvY{S^U=dyiw$cor${?I9+Bp}O}(22CO!+w*)sQfh@=v+jNVjig6fl_922Ra`?wFCV_j+r~q1q4gcfi46$SqHiZETi}n zROvvI+plrA#lW$xOAwIgOIxzl>KWBiR=CWp@Vgx--xM!r*b0VO90KQ4tpr2kW~&&r znwap2HH`kBKe}6s z0V=sOEI7+?i*Y#W{2Uk>8EpP64iHC@5YeR^R??)TGytM>cp<(RqOw?iytzn=lzWc_|du#6%TsM7Bze+Nm^?`H;% z3pNV^61i%Me&3!IW;HAPZoe*hAOez}Es`Cp5aUr-g-Jv?Y3iy7qDD~^H04cBF>uwo zDlW&&byWhflyy}S++=m-02bc<2UY4S1lDQi7@E2&4II~183ZKikWRaVicOsnRrGP)Y0 zEj91vES$m6)VvF$TtPK8@5W4bP_B6o1SFcPD%bDHc+|WXi6|#c&3hwi6h5FSXJ^&H zRcrniIcBc;8i=K=`I_J+tNB`B8HEm1srfW!XTHF3&DTaiq8V0-nyzWmQx8{{< zs%OzwpR^g|xt@HmWdqjJkn~7=rlq7DWGUHT%SOwFiZmOP-?nSSNg7iUWjjSzY-P{{ z98ER(F{~*uF^V+9uXf7j&(h|gTwg6XKqFL37PR8H<-WxgOFFH=(P(eOFg-D$y)D6| zuDCS6{~z{AY6tCBLs)yxyaQ!Q@5XlI02$wj1)Vvr8t*Csz|k1rg<*ljgz;S&{*#Z0 zf}q68#$e7PgmuHlPy{rebQut34+}#;BD2ZEl06@?9C*JG}yvTG`bPexLr6#KYFaWb^dXapqk0kTKlAUFn5qlg7fV;|QZ3=#z5$3fpa+mos& zj&bpfQ>qBxPbGk(siH)N^&}>$s26^<-rJj{eL&d(`Vx#zN9f0VBl8vIn3?t`fb!bt z07eZYp5?(;inCw_kq&zHMQ6dJQ;tO#%t?ok(EQNrJ7>WtUEWZ@xRQq>oe&k<+nyQrVhpsPL)l{#V-X5J;7=#<{7R|^ z0gH{CC^!Vcpp-}RjEBr9CV-}0*Ap2xiE&DGxVwqT;ArZ43d5!n6LmcezuLN<&eGpN zxvpm*AW=Ol#XJ)cF6LPzqCClWsfd-Qr?U~k(Q=@v8mx5JbAV!Xa}kiJvxT~3Tkyuj z#@jaw>TYOj2#Jpo_F^7TsJHp}!IsOQ>e;i&2BHNFS;!E6q53=RL@{L%U~I~t2uK~x zBS$h*%saxjGH*j?FBSs{OP1gVhtY#N>g}Sj*`ip6uwQzz2KVinb8we4as?sj@#@Io zDPIA$^g}Bdx{9Gnb$Ymq)!?YDYYn5;f~MIIt)oOK`l0ol4E@js1f-VoSSj~I8^JS* zO`vi=M0*;mrx0z1Ao;m4TR6#9N|M^uDSosKC@$^o2uKa(yi@6u|6;{ESg}$TIBD1f zj#?Hw8MO;E&9c}{iBgos9!`d`*o%Nv%epM~foBx^LDT4y4>0H;fzs@g4>9gAITL8CMdK;7r z;0_0<0RCaYU5+c--Mz&0<=sjzGSndct^k$x{+h zR@D2+kBy!oinr!L>w%-{J)U?{O#pk|g+eEuEjOLH<7B6-mvr>(>-i*FMd-;o=S0 z$$GW7U>U_b&{Uh5r4xD2&<_k%3d7Syd;~{rIG-5x88pp?^Mw+nXgFUv8H(f^0ut3N z6^Y!5*w~Y)wxHkbMAAW!yc0>!N$e=e4?B?zKyfFM5dn!@pYl$n6UoGiGqYl~EWAV( zaMZG}XH-_uG|M6zB}!2i**O`?A_oEzHFQl`6UJC-5$^oy<%y69e z4h)(|VOCv)RHr!PHIuAc6ouG4-6@6;PJ8W(J-H3C1r9Q*szYmofgaEJ+Wz;KQI{Uc(cjHuI@IQih&6;n1VuW8 z-~@om4iWJPL^}jU#>F@Q>%uyM6aMc=*ywH!-IU4?)mFGe<7kK2AQ2xCk`NgrP=sMz z$ss|J#GvDZQ%H=!uEeo1nD)U)R^Y&iu#c`89U2n?KjZZB(aGkc-r-+xTRk?7V^+U)GwP)gDpV}+=G^+-xR08I)IC% zThLTTA)d*VW?UJ@DMPB4yC@5eX0BC^VdaU5nOp_@YG-neEOi3qNmoS*@Xd(j%$Q0H zsLTL07dKG_98E4&8CDHcGoy26rVA)5cSS%RDdicR8{;u#yOW5rq9HrA6JtCqXiq}Z zAxoU@i zV>;9q?lm9YGz#_k@I?L&w?$k44Qz zs6iM)>qUgb+lx*B1Pp5>I^$QLB|a!NFi41?-Xehbq6=|zqZf(}p<{x&iJ;I31BQn{ z@SA+YpFU%BV4R^xoahRUUpl>Ad|+5~!&-05?}g~InFo(R8jj*b5bF#E<>Tr?5WqQh zph|%#4=aR$P?~o^g7jGvdGW=ZT^Pgxq8p_hIntodh+~a#IU*d4k^1bgDIDV~4lYWF zj}sAK`q|(dBAlYvGq78TkQkK(+B>>ER|~!BMC0Js2g3hZYb==zseVig+ls+~@j-n~$tZ;A|5qJ8~~JdU!j#xp=yI*oT)25Q8D){%i;WMllpr zv&fE*1cnhnT>?mA*l=Q^Bu3y@+n2p$JLlfyzfJ`l4ewABP!Mh$Er3+=^kQ1Cw`rA1k+$Id`{ zqnHWmsLvEDV#25q&qCB+pGz?zI3XeuJ)fBUA7rMQ_B+t@&}j}SPi|vGjD2HtxpN^y zmpczXMlqj&Saa29493Jfrl=pPiij|;_5|=p@*D8u~ zLtD+_H5|ty85l;IB}%2w6cS0}Nt{@VSb$gu%Bx`O5ip7kpxmxDQa~a_tZ@@dH?uUE zM#-#b3uF>0@v}`5Tk#u&C#RNkK(-++e+IN2L8JJK7`Y_s=FqtLgbnJ6@M?f+YB;71 zI|xS$d?o~N&R_T=s$?o3al|&CDHh3DRdgb@Szk{uR)(91g8{sGvJKA zpb)fiaTZ*q{gFS?Gnu-KX=8@ii*vxli1VOEae?q~Fh+!mi-?ZYXN?XHM2m|NxKR_A z!1AL-B2?{Vge9uhAatKGp@u*lxZ6EWTmi=@u7c{bM;Qdn2{Q!6CkTVM2A;Nc@Dn0J zG%y3~#dRQ1UN>09O;AUDmN+^l3sbgGaSMz9b6##Xay+lulrjAs!Ppn&#byJjN&<5KM0yYSi0C z1|$UQv!H@Qy3>(B;vqx<`W$46xswi}cKHZgiETi}j}g&lQ8E@!NSh?9eo7HlHI(O+ zPn*OD&BslOXMkwS%=$D+`+DZXDI1y5_To9o>k zZ%E7)XvrWdA}*wAjCrby0*<$rr@gJIRNn#))$j0Q6z?f5CK4(jFi^x`sQdsys_L3X zM0d9rAI*wB;Rk03k|IfFePPyDE$bVzY_bxHQb>g14#gHc>{v^Z)H;;(5TfhIVm{2s z)!R$h5y#x5GI08g#K;vu&53)_Ow7#;%Dre71k65e-L2UZpDa*1hOCGLOUb$f%aD!( z;_aI!#N(i=hM01M+KX&JqcF4M$0%}uhVPOrT>%!TsoO$V8f?B~YGVjfQ5b^txvUC3 zDkxUuWG%Ts9oZ&EvejH#3D87sAU6Q|453_2c@UPJAupxF(Sn?uvf`A60Fj?LbdhF` z0XhmGrq7rdVdxncBMOp;JqnUSsw6@b)=G*{L><5Kh2nub_a||InB)Y-$769;6a{Dm zj{~w<#lV1B#p#EKF_~Wi3~X7;Z1ZTv7v}&5Y%axo>J4RhX@-|c4liqlmt#I(wBRE*qi_awl&C3LT80Za0m7B}a$|90mOHaZEHR-Xfv=DdKW5MH zASnr$jiYh(_KMwc!jq(O_Ff2MEd`wZQ3LXZy82D{epjEvGF_90kD8DgMJ-T$c0LqN89sF2 zS-KYasCODcT89V4sw0RmKxkOCSxX&IM~Q|Fqdv1)SxlIy%d&bbQ|3qYnc0At%CyHz zG(=o;W7CL1jfu%!hVpWM6BhV^a<9=80rpv?cW4F<51RfYqRenNW4Rb@FTqz67-27( z1A_}`fghu235tOt!q3uAXkJce#fa8~Fu!h-f!|&cZNR}ssP=gNug+t*=hSnGww%H; z#q$>J2(RA6wP#QVVp?BoM;3GftD(0MD0{%83An z4VFkmxoHgUj;K*Yf~I9K^*C&66vLwluK1CUh(TOqYAl0#5YyUJ!GbtYHZ>jrYf}@z zVN(-HM44$!?TM&S^aA~kugGP~mzQK79H@ZJaW=g-C+|ba+1BsO(ECEDT$YFEM^W== zYUITIiIGbhfznV$)B!9R2+G$qhyvygoeC}4?ZRM4B{D>*oFRxP^6ciBZ+5u+0DE_G>QeIL9NPRV&`XTvoPMRGT&?B_ zq!R+ic21mbWRnCH9`IX!i1T!`j&L@8+4nnYmoZdvza2D3gMFl|Xh zWl!KK_+_t{ihxl}1JyJ>S23OBYUBHjQ8S2X?a?z?FbkAxe>MVq2}=F`4i0A( z8jqffD85JoO~a$jmp-48)5XwK=MRcd&m&*z0t!;GkZ>U)d})giz~^Y7noDyNi%G6t z+7d=BC8qVIEn~rQP`IMt7U>F)@+BF}>_t8Te^!Tf74hiY0M-u>(kq1t$C$ zgOwzfVNh9oGB4HJw@$#*{KSYjdWmo+lEjEzlu+yj)n~!VRXi5CaeKH2al8lysGf3uQfppLxc#Qu7mnJ0*e zPS~FBYFxaWeO$yz@H8%aih-wzsdUE5T;&W4&w{eso}<8b)?d$q&(3=R0g19E=e4ZA zTKZpFFT?BiSY*XiJ|Q|G4i6CcsjRq&6#9%2-J)Y~=YNSJIf6n$46*Sfr-%3AGMFRv zcBo~8xPq{ype?0C0{dn}#8rq8_F%G_RXMH}GQ=H$ky@0D}TU zB4c7=#Vs&K=ywuCnOR?$MJXi0=re}}#l;8W0h#!UC@dh6zDQ7L zD7q?I9mjogU>KT~;ws>hafEYzqP)AA8B`F%ReNO|esj>## z+h(Vf{{DvHt@L=K9&g6v07Jgz%t^8Uk&Aw$03?#SfFMzB=5$5GFCC(JINF_}B+biF z^P>nv^HEfvM}EXdH4zwxz6pazen+@HY{3{1Ot2r;>(>zDTMvOE6vzFQfH(` zv{GTm?Os~aE$Nx5f4u9`t!wV3s%mAGN#^9FFYMo&qrT2D%7L8m%-WH)62!hJ;=+;%f;a49TF1a9_$ku7UJU^>h0;x zy)rwSymFm1GAT(S*D=R!DW2qKJ91dk@c1OjTwBVsVbpS*r~*?0L{-p{K@yj%J~w8n z^gLMe;H(cp@f!|D?gHa8%+zC?|P*xvEsG0|0c6s+P_#mg}qg#spaPso~L~X3axg_i0H)H!VudcG9j*|LrsS^Rk21)FAPNE-?3Ahs^Z<6>UiY{V8fCQIpgQxgtQE%~va zDaS2W?B?t#nt`LaVt{(E#YUQ9}#Zr+;|Tg!CkJR&&{dWS+pQ5KpVN}?&Qc`HTU;>q9q z(2$bLT}2Fn_=FBr?JC@y-9-;lpw^#YSRAOPDaSK20hEh75dn#cS-H*Zi3m?;dy$B; zqUo&s2|#Z|jiL{z{An#+gZ%n`U*e*Guu@?@oS+}c9`UCEMha?P{s$^a^anH9tN#PQ z!kIImmZy+X@>0=!Q#}YcHgzxp5_z?D=FP7G4FMA`Vt|?zieU(93q6VAnnKrX0W%yT zRzCs(iCR9?Tjt*Kx@vS}M08MOH+;WEZM3nuiQ0>i0K!~OIQRsD|FAci2$Wwb^d@DLPLw-O>O0ImNbRa^Dmzd= zL!BhLk4Z>j)iNi8VH8s+J8GFzIY6D@G!{(fxY{gz#Bbne%$~upnZ$(Ivj{Hz_h!vT zIxDk&XSH)k9ho(k17y}b7R=|kYL>V7100Q63mCQ#RMRXLG4oGQR=OAg%O*)nShSQy zs#-6x3>>u+TF$5y#KXm|Wb_X^p;dsXozQAlv4)eHJE64*TXjO~NT}_Ecv@leTxb4G zPM@dfgw}H&8#oW{gf>zZnoejF#Wn50x)Y-0nijGdQEnky5Rj<+HGRcaL~$?;sN7dj zR&rmlow(Y*;xCe!`-&YDOx{;wx3)PO2|MRMciX11G=dl;$KpqtnWj>a+e zF>F6EQH2Kx{=;T=5GcQ5XlBkRdumJ$)XGvFw9R8SQS*>}2+6IS`!EW#Spm<1FbW6VGub@_>DwWfxdx zG1Jw@M_dF)Z7Y`;b(wgu@(QDW*jBCrrnZ%9tl~N+HMf-;2wSz4nm4)f-?mVc! zU$^XPr;nLzkDFTCz1Z|;g?qYwU#Co8H1vLoy*F=h9=ACUZYy^v3r$=3hvKQW6-q8U z;9Uf<1Olq*=bc@|eNv#--UEg`1l9CRkC^!wl#BZb0umLoGGISNgd5>A5>Zx~M)(|2 zqj&)-H$u7w%lCT3)i%OcBr`X{*Az_N2;YF2tP#Ei3#%TWN+V2ODw+ZNJ#cL52LvSY z=@bL@M=*`z6R133e@0MS=wB$VDRfPD^c5mj{|y0&T0N8xz|m;;WSAE*q1_w5TJ1h8tq#iCf8hXWufc+v99Ok_h+5!iwCfn= z3#uuP+RUs2%1Y}Zz~xbo`SqEv>U0wg2%wfnLxwdXCM<4@U#-PWSn3DL%9|353~t7J zf99*oT}5*OsLER~tR*p_ycNNJIM%lY%C9uW`kEN)m1PAQ@8dAwcNa0ym|5eIYMf&y zJnBe@H?KByat^5CTG^$tO90KYX^KFT?KaS5HLB{tFp9Qh2#u=kI6%9Qv}ZvFjw=`B z;v+hOqYkN^7}c40mIt#bb|DEM9hM#V%~M61Aj&U*c3};HgfYJzktr-PCJ5Voh_0m7 zsw?7>4&nsCoIo|qTZ9l?E$L8(8HkCJ4kNhqZv9hrr=V4^su(3Y^nhl5sAj-qXKm;T*n+Cj8zEd9ru~7^L)l7z5 zT*VM@REHkQs9~VoqolS0Q4%Fe@tyi`PKGycM<5_ky^}riH~S+IHHuN7X?&+Xnn7a- zl-hUdyl;#$D31k{i{MuTBx)O!?CWAud$Ppv3--?v7xhli_6aZP{R0JgIhoLdqiiUX$mNrU{Bw91T$|oF&{y7Bey>&W^0*sEFhRV>%b=}bSm~jaFZFk2rRtU z0LsNjr(&nHmOHz}j9kJPYUZzDzN*t* ztR;Zjfv#iNdSaq^Z@{m19Nfs#O`xoNGXnCAQ~m&S3piZHTS-KDQW?wDx(!jI*beF_ zeYZOQV!#dp$kmwzVe8RAaW4}@oYhVQBq}mZb?!nGuOfgd)wzdY=IY#wpjw^#Nc^*ARv!AX=##TLpZ39eS^DTbXUCMxv|ezlc)mZj%Fxl+$_!2B5J z0*fv(N73)gaRBn)Z^INQN#Rclyt4j9o0Ayp|BOp}-A!Frzy*?mn6dyqyCEn3117(J%iJJcW z6JtITCcKCvhM18fs{`2AqSXIYGGr=i&fO>2k9m562blo^B#9I2!Hw7?z)y&|ZMx z|LPpHAT(NeeId@UFl9t@&>|clgNw4D7{?WZJv>Ema5M&&U|30F!e9sdYKx;3OG|^Y z_A(qG?PXa|j^nC!cTpZ3jrIx*b0j9TJ2Ctx+kHhSu_}Q|oJVEWjS{GWfZSf>-mEG( z?AO&uM3#T2-CH)Ma)uaB^FcKonumvQ1xKCax-rTfG|fq_2PH}|$@S!9nB;mPAW=P$ zJ@O>i8&T|m3!26xw>pDVxpF6<5%kgby!*# zlzpHc2gnENv!DUTRqgns0340>Mht6AOlWVy@SpSnKPa*Cfu@{CGu90s@JGPf2bzP! zKG1?hWO*7s&=O*7;{dAl0dLV79MuQfFiH=a(g)JX4|I}RiMEs|g%7mjWblFZ2uM^< z8XxF@C^l&TO~VH|F{m?vQu6_2FbM#Puf7Wc5_M&i+Z=S`$_+^%nD}Y|G*xx%QbCLh zW}H&#UTz`;98IN%GR#0sRC*Y}|J8o58#G#ZKsaX@K^gJl0SCxSyR#sYWIG4fJ5C0A=lQ93buSEJ)zEs@+{Af}_#ilVQDx3GKZZ{*!H>50qGy zKwr+IAL~XNFd|^x2Ks};UNC?}WO*8GU?9XsF$h%C20T5)U~tqnFoaP|4AR12_;rO zFpKk;&AQzFiA6P_* zQux51oD4p&7y*guN#g@c5XH^~plSHPG6pRtP-;G)^n)va;;UbYfJ9vxrF0}^%YnR#!4j1Y^5|QO;xYT}#@%;g) z#-+SG#6fUWmpa6#!=Nc$3JY_VZsiCiO5svRIT>8)Zv-T&Cyh%TLlpZIfTrP6Cm3{+ zKtFV;Q$X?6pGH8+3_`idJ3*g8)F{q^rn(dKIfk5P$Pagdz5p0!b`b%ouzZIm-xOYA zO_xcN`KItY+dN-k%vHkBx(IFa{GB^OcJONqz0OdJ3%Yywh#TN&T<|8tZh>-DnLjbV z&CENX?AQMwAV1ZWr{s4Tk2<@J0F@tG(vxKQ9`hk_wT~?yfuTOO ze2kdp22*)#`2_rAHo(3j`!*`9Uk)?JHBLm#p!X zS);jdl~pI}1t&%8Ym3%5q_w2HxwF+1;kT^yomOk|Eu54)`1cldA4pw(SzWSq8_S)} zM^^la6)P3&e`^SQId>hhhjwcjU}d zamC`Sxddxg3&T^C1V=3l2S$|wO|vjcQ=$}wQHGPDFv=nzUA8Waa^M+7dC)Xmu>yk} z36z>Ea>wb!xQdKZFUChy0!PzvR%TcgVq)yBieK%_pc+e^LAeKYp#WE*WlKa?2DmXm z&Ba@|gQLmCgJGVan(o<)nckqR+y?=nCJi_m?GnSf6BF7a8UB;=)F>#inx{r{ z9x<#N^VC=bxBz-EUoc-a(N)9|Kz*(g&#(ky!o);^OBK*dH0zfx+*e;(7_h%nv;%#T zS)0B%#wN;iqH7tZ?_L=4`D39TGTU)qcW)av3!v8K_afi7+gt5Q6BEK7f7sbUT`;2j5!#+>mC zn?OvMGm+qCb1It6$rH+^@Yg?%wQDJ|STTcPGl>Z+W-pv%LU7apa}lHd1Wh?$YF1|!Q=$|D<`PbZF<>bI64j@sS6_yxQ7i{d zW58U&pp^tlZNOx`$`orApj-s25s=8EDNC#OCf0yy6l+0gY{0iT>k!0Ej2)cUdXBBk zMAierS~nmdQQIS}^1a4JL~)o5sC=(M`s90!&BWE-Yit2Sz1P@ENcCZsa<8!s{ABkU z+rh$yf}pTkzSl^@;vE38#U=zK>cGh2cw8cUdMBcIdmhx0a*=w1jmPtd``L`d$G^($ z$Q}mnWuWC=FO5w10mzx|M?j*Eoia_u{|-QG6bC_7|MPHh7l*)6{qHcNj)120Kh3?v zQA(7;|NiD=@V{dSNHmO*J@TyLIHE>z0yGW(JISC^1WL{S6xTZq6kq)r1SA@lDL1Pd zv9n+r#W~Pa)p1XJo^cl#XX$i2T)f3aa5SCHC5BxlCOVxf_|=ZMS6O-ul)JR+9H1`k z1`BR-T-EL=Zh@oGew$%;hzafgF#IRGw7XDZ)ur9zJnpk@bZHL|keyZT(jJ1th5Cp@ zWO*7c^%!EKcmk?*DKGI99Mz?sG3q&JN|(}fX)h>I3YU7x$>3725Rj;zG%ocTQJiE8 znubfgWzahUrRGxX1MeC4fpJPLx#D;ma5S~_iD93KiCX%CU#$;(W$8Ci_5qt*6d)h4 zMF{%>a$M1lH%rrlqtR~1unfe6_KXbwNgv1rC00I=ne)iPy5R%%2w3|-R&dw{vXO`^ zPs0bYLyXM=K{Y<$>MU}CqxwKDM&$-g=>wVvZ+R$D3LnVJ$>0O|5Rj;zG(M0YJfkQ8 znuZS)WKbajrRD=lw^A4=zWO2vNKNJ3tVXn=tge_@oxCKMvYX~YwcPs^=OiUKiBkV~ zm8c{*n)-KOSSe60C-c%=X=auIWv42OfZVytjkX-)Q9$KML|IV*rFQRE!Gd-qbY1xZ zcbJDNC-97-BB;4I14Jc4(Mp4Q2Ur=bAZZU$1n_va7oic-K2Xb{r&L1N2#Z$7*!`;@ zZWL8PRWEV(^cK~?(OkYW!(507_je_@d2U(SJh#kTrw%5URqE7<7j6(*xwSi|^`JB~ zqx3|8v-V=XH}fqe?~bnwdzXXB11W+yZV^~vS!s2H5)mrS&(&nJ7 zyafly;+8CE#c@SDzK?1Rjz)VMhUtk3?QQX^)!vS!?Lk?42M&<-jx6ZJaf^00e3aH1 z9F6t>hIIkelt&;lyMnUPAOyHPf|(z}d{w8H2ql189tMVm5fc`7!>`ujaF#}ZvT})F zWN>%pM>1bk?joWHpem1MSPU_tJeJ@;{Dz?iP=00c4TC#oPW&|jwpRF0K4HKI4Eza0 zybw@f<>hf;7)3ngPhOrt0jWKfwE1EZS=y7O%Ee%V*x%_nFK{vb`Ix!Mv#88 zb0i1I&QUBF&2iOEXE6pGjh$l|_A4=A=QxJ{I0-BEkCL_v& z))WLJGCPeSdn&}(JP%aujNQCl#B^}fA^SH*%>Yd~WNYpOW>TUQL-s6Ah7oBt0uoiX zX2|{>QKOgxn#PblmqGIgl-iK3d3}FAaD4rLARtjQp!}>JAuIsXC>DaIp-`i85s<9% zPXr|DCP<~#JYX@HMzI7GDu4V%z)}EN<1z#!>eWc2{C4p+#a!`Bnc{C4;T1l700H&RS_*?$wk)ca@o zW&h3KCcAsy0+vy11?7@S<7NMCjNHyhOA~WZ6^xeJur?QR6P z8{EVEz09}hbo22R`v{=+>H8UWfS72u2l1;NFb=WwFeocOLNMwRjxzsm=Bvs*#W4b? z%8xVb1TmrfB*D#nS2+v_K4?Hyutg4S5J6i*@Xio)02np}s}dR!)GY=ZT{r~u*7XjK z39;B}&;fg1U>p5N0~Yp9L7P?QaGEnaLzz$qb`}A)^&IohGhemUOW@YPD&g>dxLbhJ#02S;F7Tn~xYOJfc1&+qp+YGw{s_}$>n0XhJmEJ>u zKND6O#C^u2LwZ0W%1YB99wKTKk3i)WCC%;$kBO`OaQg`u^1IPbDX3`-t6;x>=p_`3b00PWkjVbh8(q zAxE8lVU=G=C7~9ywxN45ysimyX9Cyq*RU}q=P5bBM|x6Qk;a! z2MvM<%Q32LkVhz=9!3YHvY5A2#EGgAI9vz)TX;+y&ZsoY%>Lm($6At^5pkS-uz`-U z{J+CH&5fXbuOK?9GOjw8je~=wtvX`MN*YU0ceI}@4*$RwZ84~D98DV|P`|wp@9p%R zRFxh2Iy+Q%a5g4OPPJ93CQir2iMh0WC99~JrVf|nIw(D#hwC7pV~HL75^!2>yn|zJ zQr;_tK~V{j-#smp4z?r%$*TW5ywiM=X!6R8jOiwqom*uGcP=r82wUn*59l+acSThu z8Zffi4j1W&ixWK4({G9sJng_WiVUEZfhN_rnKA;zgG(j^B$|iOuq97%Gqav7r04q| zK-x1PD*?MTY<6oo^*5v6?Ve<~*vfZcG@fA|T(6$;+SBz~K^hCJ|Xq z&XDXe7?+w0WJci%s^0m!;|(r1a5Put&M*&R;;K9u{*zbb1tnHj<;{8cux?ycbp+(e zn0!^gfWuc+gG6NccdknDvYHljEkcub5ZdZpM;(|(;R`CauC%{qtPLRNUk3rHk?gnf zi>q~6S3S~|>er0*8Pk9;+OHW?ITjeuOnQ?WQD5 z`88uRLYN!4KLynWt^vo3=8z?8<}JX&IVYe$xCHjDR*Y`VXr;|~xQaI5XuM0$u(rg6 zceTT>woSHYX$MellN~ugw?~~=(3#_kb~i5(0FFj`7ls8A6WY5H{9n753xYN)Cl2P! zLMRiumkZ?p8Earc7{^s(okcfrG{%NAEP|LYR>H5gD7v#W5|p(^ae%Z(vml1!igr9q zi3LZay$8btF`+$<;Xm1of3mo?8-XtQ+ztfB@Z?-G91F&C zT-EL(CV->SK9OOQhzaeJ8UB+#Fa=7id|)c)F^zS@2c{!n?E}Ao!#*&BL}YmyJ}?tv zqnHJ%@c}niF&iA!2YzSN9MF_Lpn0G=mlCD$fq9$^J}@5viRww?1Aicj_mn`>@PUO4 zT124Kd_WnR{sfAzelY?Pbz_v9)fYTVz{FQWpsA|k9(EbymNQPNbayYY0vt`HuVmON zVxrPl6Z~K61J^*CRqe0k%+^sR)CaET0D0&J7Hs6WYOJ%^1dhho%?#T@Oc=Wrzgqv_ z#?tMeto<(zkoFxcFmYVb?&d9af}_#Ci($Ko3GI6r{*(T{7fP&3U?1nPpLL@I4j^Fd z{|CWg4>&|3vOEp{KMXN;CTi(2eziVug{4

z+KMc9_xk=+(*FL2OfaK zKJbu4WO*7s@Cag~cnqrX0e4sN1RT`|o-*ngXi6W@e8u>j5~c8g7n}?}@Dc%u>Ph1R zuMowKAfRdZz?=V%x9g6tqG-D`Q4l36AXTt~wA_@N1Vsh0U=$HVMMQ)U45PFG3+lCt zV(-28-g{Thl{{xpo!r)*lf$s(46qW1??AdvHwuyLi6M>JWoOzkH-edi_o z8jaH@V1lo=v~>DVOhc=M4Q=oS63KC_R!hD{@o218mX`S;RE|F*xUN|15yMa&N_McP zYFPO5ftmP7)?9qCsU9DWnLBjCuwes+59iMZ)(15jkfXxa>TMvg4Ji_I(88aSX_6Wy z-f;0^*YL9ajesPsjSy>NVsLE}vKP;_O+huTIc_mW$6%vuCe4x5ia+gARbJ^g2a=Rr zE7lgoQ1X^!FJ8%`K(!_Fr=DTSTS;>?wM@xbzcrAgA$##k9tWx|nf)Jc zSat`X&(*ec2jbtzstVi#tjK1v<(cyG>ayxgb+&!Os@T58;@CRdA&~Ri!zMmfmsb1n zBqX;DcMxp?K{&ai=m*u_gOuI>kgo$`Je?|9JwbRnjr4K7 z1`Qg#+w>`e=gyxoY07T9;=}XP`AnT3bs02!zTX9;+A4njdBq}{cdyX@ow4rIen*3-D^OfncEC3>o))r;G{fv0CIj;Se-(5W597ps-b5nb*54i z_p^Q$2}wiG7HtkexbI1Kfq!msoSKUaDW~R1Z+GhOVq^~m*qnPRVK2oKe=7ZaAjY4) zMcap9i7UbGO}h@=SBW`TiSMTX#rIdj0g5N$75+dVM*JYr4klRQy61@>qQpaCCB6Uw z$FUrr5)MN|r-Z|q#3CCz+w&LF@v3WKl;sF!VG}S*t6;L|;@T*X{w?y6QI?~ag-4ND zQX7em2AK25z&3x9J{BxFeH;RgeHwBNx5X#1)pHJ9(*f0<# zi_ni<|8z*Ddf6E&=}eZuUUrrO>^`(n<_V6wj##FQWRN#}m%ryma}5Ik>M zq@)LhB%WZ)77*jf!=gPx5S~0r`r>%<7>FrP9+%D&%ui3Aln^HfvjOlaL^XDwM!?a* zf~nvcfcQ-lY?D)g5qu6j2|ka2W7|_O^*VwV0OtIQun=tKbp$Vo`?9!ElT}uf`d5G? z>y1~%dJVSlisqm24_7p=GgHA8%^S*wE1EYEaO~IgIJ{Z>7NR--Hf)hAns-Ecm!zf^ zjPVTr9$0Fh_YrU$nJlfTpD}z8u|Fi6Gmr7@BSdrlW7s0TeInAQB!%0u@-HAXoIexo zbJ3!9uEM73FMyb?`K4%I5k%Mgn)Lr>IDZ4FROkIxC4I*d*m=K4K#u+({72!5qgnqG z3CXCW>{;rb#UwzO4kdhLw051k?&k32!Sr(X8-GlaRE+GNQF3h*oG%`hU|3%R(yE3LR9^ax8(Z zu)G4CeLE_lMDfJaa=!u)b|O8P^9F@KxW>skJ^Nzr?F?GVi!M@GnYrmj zR|UpvQDmm5Zh$oeyCdM}V8ImC17Obggl&F`>LuMh1u?LXN z;c0vu2n;`of-U0PV3A5mYGR6tXU_~sa;*#j$4QHY#UJFMlKCJ{dBmDx&+aY*CRa`azlazYX4|U^w`;% z0ox3khsDHPd@;GcZ?t5^1zCTrTJd#S^zfm`pX%Z3OMU~&vWIV|0LR%dB@9)_F0y_Oy~P(#A(irUGnF)wCD7B&Rn%5!+h|^_ z6=4ez5|1-}OCZMMQKD@HYv#q#0=I^h_!tBETmnB%dRX!8l82klMz z5)AWwkU!OJ_LY2&vg|hdAt3Me7k+^7#Jfs=APGtTK1j5K3BtQWNM8c)4n_WycMBwc z7-i|*;RIfT6LKlm0i3o|u75-8n#^cLGyByZE!W9Cqgq8SJ z2&fgV7JiNJM6=vqOG45L*NJvLLA1gRr2jXqa3iErt#Fe{x|t=g6>d>c)jZiJU&y%? zpeCi;5OAylrm|}EBxz>!y%8+(ZPh+VmVc`v;5Zqv#PD~|s{zaT)nUV*c~UIgI=%)``!mlqnHFps zUyH$H(>PfR{MrEXc8m7~l=J;yo0`VsCsXTyBtQEj;5f0<&(!7j0I3Z$YVqEI^d)#K zIw%qyOws*A(bTi6Qi*1aX#OqF_yk=Rsg+Z0_fRdqpFkyZKcOOGSCU=TScS4k$@wbS zsCk;JP<13)L(%PUj$tePfxTTg(+rW^x|DN&16tT{|3dc<0{Bc`r%|p2w(CT1qp5^D z_;j&5j*rS)x#RI~p*w*QoZW+eC*dFNNwA!D$m!a{j9bRsARmgP$?gcN9tzKV`JvCzy4@-CJpj7wP zvO1#M0C(>M;c*D#6RpbmKA^+h2e6~um8j$>_o2cc!7g-f!a8OBn4wQ#@aCaU@eiG3 zJIQP>nJ<|9Gx7r;FMJ2#zwtQz9^eyz;8uiih8<1)6xR`XbCCVkXiP*>&hI3y)tCeX zI~jyC>}ZU}Pex-hl5&2ExK^VM2!2l?JanyI&@>C}BB((Sb2xI`-_S>K0{#iYBt{Ebgb;X5*RM+IB57r0iisP2L(ZZJaV>izMrUtc$X|N8YC*KO43@soxPA315;jmL}~ zxz(tlrF&&c`ByV;jTuubhg8*D20MwN8$1 z1N1O$rQ&?*;K0HK3mUE)*JpC3s(eamWwx$5Q#!4(uBN`eytJ|oBK6to(rM+@e81yi ze%Bzwg4&h~4_$CrqUd5Jmq+1~Bo!J_4h{42r(%vfc>-@*E%3Vq;t@ePc@9U>Ef&Dd zR01!1bW$)0++^Sypo`C{DTnL8(q+s96K*Nw>)2;NenD+pR1GpwtvGpWp8^sj({NpW zt@!+~p?+MSO!egI>YDm#brp3}>!(gDFD)&vEUlQ5sjRHaX6vSw)-F@q;xIoms8|!4 zhlLGNP^>$s9p=M6Q!A^grc_qfRaTXiO_`dl%Va7u*}8K4gsx^XZe~>1woAJo_~xQQ zpTPmIT{^!aQK7tyb3NFW@RW_L)~a19h(s;2W}~cvhBY(yEH!M0lxGdMr8kmNS>x zyM@`s>XXjS#gRLnJ}(!txCW~26-Kxet6epSgu`#%0v9YV+(5Jt_M(BZaI({?z)i#1 z5x<}VbogY)g^A<3B-l*sDo_m09IY`A;mkS@2|2$zY;xIFgIB5U0VH|kx2ITp!Rksd z^~GuP^Nsn;RPe}eZ)L-+`+X2_4~M&X!(9pc0?YXvY;pm6+WjB|++81PP?jJr<3cOcyloXFvyxq9=lXsstYf?o;wyew_11vdM$BK6x zF2{U&^Vd6u-!mPS%0Hq-iZfF)&~E8cm;Q0DpK z7cKJwP;8l#aCs7zd7-o}l7=m_(q9ZLDf1HXE+vLCFB89LnU{lN%Vh7)-zPk%ei;7$AGXD}Vqy4gouMjM0*wbpis+8AYrTsbrj)OS9dGQ7! znxo%j5=-P9o%c!z*9LQUJgvVKWqF%f*Z|D30w~pBI2Ob{1uW?UFK`${DwK$WxiEbwtch!_)diHMM!+eR#y2R zfF%>#kK+9VTet);tM{LosbC54i?U$}@GAn2)n?;|OMu@H#V_e#i`-fKL!>`R!b$(o z!h4DunSY7(w^&JKs_<2ee}I_=ZPA%!wuEJaw!;6gL47zfr%d365&TXf*o8fI!a&5a z2YbNm**IgiMut=~w~^jb)L}EXRe+;sX(cS9xbX<@Qf>##c+_6RWeLKg4$Kf7;>Gi5 zIb=wAw7m2>QimRuNKglUBMc3Awt@&NieNpf@+$!|o^=wjGpu4j*{uL4`7gY*`u4@pHj$f{zkCYGr*zU#3% zFjMI@L|l^~4#!%|@V^|6wUHrpIQmMjA9Z**)=_{Rq`wjdC~iF}^8@hV zWrqL7qYN^nJSvl3Id$k!g#@|Gi5{g_ijWn-dY183z>H_rBG$l~4l+dGy0DU84*?C) zp~BY}ZdFVD2Bah>&kaQ!MiBKK&J6!ceK$geRDDNCZ)57PzMCk(Be1CwHdEYql&$e2 zffCoZkkv z$#a(36fh1v32uvkLj=wK>Xwkwn@QwjE@IPayua4*k6i`@d*)oN3z-P%^07E zDDKX}78&D{MB14oJzl~oy70o!os2#lpQk5-EElIB;5htQXzIyL9l)HQ3LDNG%{}L+ z2UVKW5O6%_P&4%aaTjSeB%0}qKl3DJdL-s3#&tZ!!iOfiN@h38H22ViC&DRGhG6+TMdPl?e(ll>Ko z9-15=svepgsD$XD$w7)m4^0kMEO}^h2;z-BG&vM_^w4Ah!a0AKaxTV06J>4Wp~>Nr zStuF%(Buf=M+&zOD~c%%hbAW= zDd$fV*J_*uWTHP=c=*ud6hWt2=rlp63t|pOPV~@3K={yv#K;4GhWL7D67wAy|Bnw% zI8VD1(ZhmUjCg2rCLAyi9rT!j1ecef2OPZl5ah%IjxZCC_%eiv9 zZQ5;G7;mkm4yNdR)-N zP2lrD$-4=BKA`9(Fz4CuCNS?#CO3gEKtj%62y5>iRpaGf7XeGI0WTKs64=6PKyw@T zQf4Z+2E0t!a6xc60&aU8C3E_}0$9#p3ESvx;H!|3e;fE}Wx0k~!o@)TIacew%fo-3=n%ND!;LoABS>!nj$fx4>$xcB=whJ>I5-+ZDI^75)xjM*mI` z?;;5OyOp7D?V-3W+okZf?0^xxF?$b4sTJeB(zs7~v0}Vm0ebO(5*8^Qdr?tcS>Yc9 zX1sVv#D@vOi%0O^*78xMJ_f5=KCS@upHRY+id+3M{}eEz|FnqD5QP4-%CLlQnt2W) zsaAMiWxOD5w8D!B=up2T{AJlO~p>OTMIMhANn>*6KznzG?Hy?tTs_91(`d9*}=_d*V zzrg)eNuLQZ&Xm{q&w&|dz7X+Cf^g<5Wmp_%z6LYp%r_GHRswM5I|Uld)y4Nfv@ZSu z0Y?Wp!c$ilKLW%{@L`+0x-f!2gD1gX5O8dM3Z{O6`zyel{|z==yf^ZoBD&S}ySRUd zYnlu%WBLR@)@Bk5V}mty->h%M?ngG8z%=hVh~|8MSkv3gXW@{L-`fT%%OGZHvbU+V z2a8xLqG<)(Y0UsLtxzUnIYG2`1^(OCu2gCkR?ShR0O#6jCDbTx^)r45Fr&Y&i0cuA z{!sk4`s*uo16b*A$UyRO@L@_Fu2fS9?)+>7%oH+0#El7}kWKL47P6^QH-l9nBN@;^ zRZDHI)LNz5LdyIWz)T@qia3fO3fT((Z6TwTx;3l{8KVGOYOE5rQQYd|r-HzY{kd)-qyt9at3Bt)K%<#W{ zB)krpQcg{kW<9ldpKTffa%vah4Z^Kc)qXlDiBnF*U17~g-c8^PSgFoLK&rEZ&lYY} z@pO6)DT%5VaV|krb)GWxtvv**gcb316};kV?ir_8xM%zb1d}fb z92v%fJ>y4_mh2f1za(%p(7a9K#{j|&5m-3e=$8ayM~?$ZjvkMI;|QRm;WwsFKomDc zVC^@iPeMHJ8`CF?c#4R@$B6PTbK(L|1xW>-hJfR|zyg~%`=5^FoIe9Lb zAmBK;v$WKG*-HWD{AI9B%4rZ^F5VU5nW2L{3ReO%gZL^DuO^5=d=38FN&8x*UI(i= z;(7%*Y2ToP8x^mdXjkCy3J z4_Pi80I7pts|dS zj%Sp^bb+ce|12=m1)dY}d02Dyc|qWdutk)5zYBGV1xF=`-R9#e`NSA<|+D-VKx!=X6&mifEs0Q1IhV!VA)mLX-8WI zTb=y7fJ_U#C*J!6F$g}ue>=!NRO&~tYKo5);2`@%37;w+%V+UBx6c3>`On4sf*|C- zBtLj#%MQUCTe{`lpBX!#PyE)+ufR?XrmrRT4Mn)v_*MaW^PLjDS3LUBT}^q`{{X}| z^P^}#5ri{8bT3-5r|PQ5p4y6P+yVs;8<@T9P37J(3B3CxlKQ=!Bzr4b+kK4t}|tLw7V!U-Yv@y zX(xM+RtBv3qbmZAo^mXv?qhTVnDgCXIe%b(OCy*0(YmgOxIM*{4gM?EAdSb2wNmP?`$eQ8f!Bqu_!iHQ|FzL z5qop8S$)&AwTR~Y7OK8;F-w#EOf5ZH#H~e)+W?oNV}O`87%SQ~ z1kuvt@ZYxdwo2U&R*kW}0-TJ-D`5x46ZNd00K};8DB47VP~Qpvt@dMDs+^P9lhEo=kdh z>f9(eb#|FPbNajydbQo&^?ugez7wVnn99dkr+}KO+^JGJjS8&X>5}@B4~LKD@9=2i zi@GzAsFT5&2spaR86vgzJ_}&ZpAE}Fzi@#+2SJ=c3m4$=Ddt=-qIz3?W&g+%#_#(MV|u2S#{8UVbj5Zp(&n9RKU2z*?A3Glo-36}!Gr6?@C zK0NptOA(JR2T>khfq-Mrq{rdigewun_J3G=H{oi;^X?{GBjU9pM!%gYQs{Lcs?h5Z za2&BLw23qE4M@g!%3-;hUq-9>8hkwECLqa~_-3(gfh~F_zLl8@&cwGV8%{2_Bj7kc z(VuW1@g0cf{GG5x&ct_#bT>&&or&YFcMn*q{k;e{PPi;BbwA-gfH{9ZY?E>tVh@P7 zNW7$Ns{DgM%n*A>w1){|h&_V;b}D^TsgJ>G*gUQPr_v{s@TB62dZm8~h*5u9v}XuH z{aMkMa4LNcBB`PMyvle%+8Ej|A`o`gaDDI+5H;${OrqySy3{L3&iPkiZI{aW*MKBl z>UFW+fGzA&=1#(!%v8{&-cmMnskaeuJWi%dy@P1ZzYANWOT8!3`y@5hrPK#L5bs0r z;-iFzj~@XsN9kkHJ|T#s^eO(^KJb}RKZjKx_(B2pfiIQtmEy5Fz6A3%5TpK$Xx|cq z`gfu)VITM&BB?&`gUa|(+UNs6A&~9^KLb%8_=QRIyhtDT70GzQ3v2s8jsG1;(g*$! z>rdFiK7idf`opib|6-I3n#(grNm zeklap_^`Cp8Lh44mJZ~?pEwpy(`wXcQNN6`v{RP&_~Ta`?SYu%zpQ8-V9ml{If2W= zs#A4DAe>yoL0cj|8fXP3u_!iBQ;Yf)Blb#Uj}9vs-c0HQEayAJ2F;0!X0p;ZlU4>) z{ALnc2pgFAYf{%6SKIpNOx(m>k*X%{Ms3~)qQBv9KDz_U`5v&1{N}SKGX%f+?1kV& zzY46jbT$2(&)%TuH=lhF$oW-a(F=mZ7VjnYtC96DJ|MHY<><^2)5w0!m7+60MpTO0OY(u}dEUwk@5T2*c9X zmF#+yv!z%0p@5Ro*B5OAVkmt>(igk*VPM5Ez8!q%kT1XR5IZ(A8~}Fi!FK)^PLCC#pMuRJ&>N6V(xZ0+4Y%c;(dC=WO%~)g3`ec|H*UelG#5DSHwF zjuV7hdS|6hR;qEo%ugXBY3VxgrV@nv_2j$2Kerf8O#?sW)Gm^1pbW3;rYpeabV}G& zapODGef+Y1Pi}sGfRoHVWmDt0jhf?%vIc|m-=~tjQZ~4?Ln~cgEo6AaW7b@ z&qu&f{GqUcR-?aBT_Dn7A|;KBjirYJGL5@Xydwyr zD;-IG(3Lh1y3z{44=G2?$P5^Z4GnrH_)%b`I?&M)Ifj|pfsR#xhxj-p9Itrd1-_qp z0wCkXiQ=6^5MG>2e&EGsffs8BUKDu^IKQ*Q--MrnyeZ#KmGo(p)J+QnG}g`#ex`8a zT9rSGjAX2xE#5f<;o7<6|4Y}-L*A5Y=S%tmO479prOnP8EeJe$(*~GZkFSJgjV3 zt3QH(<3XZ7;XRT^5zYC>V2fPLJTB4`BsI0-i)Y0r!BXv?Lcp<~v9zYH44#hI&ydZL zV|;rS(VTw{wuo=fi}V6Xy4P1^qtJ^W$+eddaGXI{So}srDuwH}ml4eQS77bJq!Qo# z1!Q{5YvR355Iy+~@|&IG-UKVvXWx>@+sw>9`;G$av+pY5J;jqgTaoea12SHGAl`=r z;l)Sf2Mf8f;6Ci~BS((ZUD*2h^?j$}{_Dq}q+Iw!3ZF73UHD7^y70LYzEIq_Q0l(~ zWL)@4ysrtCxD#-@)()F*l=v;I)W1`J>fbBj2gMV0>{$O1kWv3hyq^h{xU=%qe^KJE zuu}gGf%JL$cOW|Ke=vz<^04RMv%JvWWBxPB@)xtP5t!vlP|!iTxT_Tq-Y`DC#{4_V z^ba%f;2~4pyoUP8GiME&S3h&^EI(+<< z)=F)o)L<7${$?-Dyi1AJRce@M!*?gFD#B_a7%!{* z>VS-wYlycdtf~510@sF>dS3)o^?t(F5pG1YzCRgB)dz?-kRYl)i2Q$D^})!Us(Pu! zGZbaj%M{?zE>}W@;>NpjUkS)~mldyyV2R_m=j_x_t;8BwsSi;g_&v+IN?K2d(J%8u z0U7=E#oK^jiQ^aLtp0{d90n`>;SA)TuQvjy`C$YCj>U4YM~iI7mmXr&C!04mj`*99 z&t@P$KELx_f;hj29b;1@=lo`{_DoVy=|=)Gwb)#|T3FLDwh*`_th^tEfI7xj!bb}? zO;h2wCL`$>W5gRv5FKM1^8aC@cjf~CX~EOYc?>2`H-k58?+!5U(dHh2a(++P zz}Z4S)vxywZN6wpzpl*qy#bkiy^nbN5=6hwk>BiTY(KD4{da$f9Kg)%zXwWXAp0+8 zw}X^=uu_dHrT!2=#+5_GTR;%597cY#t{e_l%9Vu@If9w#%8^p3;OQm&W!6z594$h8 zl~7&bj{%ZgB^)c(aj=C~31)-W@yt|km2iTx;VR)o1RSexdgMun=KRU9MSA2ZBArT7 zxDs#VWgPKp=``@vA5TZX@yN5h)O$G20GRV3yo<0GA*w6MUmRYRO8K*0v9il+NDO#t;T+7 zo)v`GDVK?PxtK{Wt;zZ;0GVETrFd5nL@&Ks{Qu+z>NSu`b>(YS(se2UUHN(iIG}D& z!i|b2u2xq1n*bSCZx-(sf^hX#@`C}jMX-+RRB%hQly_%t12N^u?b5k}`8lTUl#otO z$%_3hfI6~wBjD&Ej)l1&b`KEjT!%$p!TaADy&ra;NcW4BR4kkI4*)V1TO{6t1W~bv z$PX$uDyY~x(bw~$ujQ5M`_ASwdU)qphWE)(-G`AqRozFV{wP&h-Nz8nJodQoCxjdC zD*cmWBuj;-#Cw_`ynBZHfA8J1$e!}`$;Xy8lAJ@o=)VrdEr8N9=#dX1_GPwdhXYTEZ6bt(8cv zNowLwl)6A0(UuY|=>pjb-xiSR0!xdx3_)~(cI5y25!oKuQ{7=%sdu0%yTft{a6~Sz zgpP_E_cFc&ka2GX@m3@V_f{hR-@Dfd*;DRymUZ!fsJiqK62<7TPv4#>FK zL%g1_W_0!vxC*S)dn2Gj(?|HK!V^(!0$z=bWOS}B-Wmi^_ch7?_jO+j*;932Tk3tO z%DVTHd^vk%xEfqXg#IGLZkCpo`2m2Ep*m2sL9j)K>R@In7^a416* zTijub42KGlDoJW~sAj>^P_071@z@p{s?`y@hHQ3NYJm5S^@+ z{D0rcwm|k&C)-l$qo~SGww2`VCC6wHwiY3FvlL$o9Rn!oWMf6!2DY%1nI{wDn5m$X zZL4hPWZNO&*f8cmZjWfrkB2RCAa@XH0!hJD@Qw&J`WC?CCIT4hYday}c!*hOcp*Fq z(VX8IHoOp~UU(rqnW()Gp2D=?Lb#5>{0rf!0P`+{>jCBbG}x%)6}v{-1w6UhfPmwP zgRZ8ok){L0y#m-su(@lbUBQ*;ZU{K84=Ebm?$SM(84{goMDs6a;?|rMsm-Pu*TPhb zpQ%tOyjGcmAiiV^Ywv{BRQkDqOuwHe-tGj^@An`-==Wp73jq7vm@nZC9v$xqVycJl zC7t=q&mO+F0zAX*qlA4Gk9&4Sb(zlrGLGyg-u?vP$N}UBj*Jc*8Jxdhr!V-Tbb7@e zyd^kTwI2wLl#d6gh=VCj9}hu5bHt&-7YH{lX8d7fBy+^!;w>Zy7mpx+30*uA8Yvf# zQV~Z}nl2ur0GshxB^;-C;$u~*KOT_r@dWWsBnTf*B0unPtDqUzYW4}d+I5~k8Cg@V zog(E^sYusOleAsJpDw}~A|x*2>FSw)jEiT9cQ&k>jYj8EA<-?&_1Gg$loYFX6>7p#IiW#n_5@j9IIz zXT^JtAbfnD{3Z191!$yvd{ITbL}~i?vdXZl|5rqKRRrT}xql6i@%44_-hee7{7r#x z!AkvY1T?VU5&o`lBUZ~U{5P;P!hc7=u_F}o?T?84C)w=c#<#x^&H2A!i}?1BNG*EE zH@(Lpe=VfW(-H(c#uv>TkQ(0_kU1c2#9Ip19FVpGmxk2=Sq1@#wiDi7xDl=P%aW1w z$PVHyM-V-7dGeRAM|Olpsz;Wnh!rT!9=W2*(Dg#tBUciklL*GwD&HB9@wJP1E5n)w z=qj)qtkk)TqG0M`q7q=vXJMNZjF%QwU`ehT0mm*?Y;e~^ z>>*@xu$aNUE}}WV9&C}pJyfLiNeV6>H$brX<>Q86$i-m@I1YOj8eTpQM>OX*f(8jv#6;A2()NaQV0igZY<_n*z+ceB2CB&X0r*{B6|L_)*j5;K|im1RU2ObTxJP zxP=6_G=j}tK8}h+x1uO7R4AG{;-e+HwGqvqGUEe2CQ=(qwS7Xh`0|lT;pO8t2?1&v+?8y!>st_;|?IE#?l1o?8y8aOA{r;O53klAa;K`ir^NT7 zD62eQ>UI&hw+Q=)V7$!weE}ISbK>m>YpTA#zyn~Vejoy>`a!}E7H&i<{UKx|RXFRX-fLQ&nFm@gpe8svjwJyZk#!grh|;USdN4Amin+;vENTs(!q{6JVu& zA_A)VNy1MSZbZxdDP$y7KUKWb2%_qzlmD-)eg<-MeK{oW_KYwyq>)T(VV{& zw#Z56GLbGPsoB-;6=2D?D-m$)@x^?*Dq>$vHb=4X?HWXL{#w`~zFjBM^&|x+#2XMS z+^rM0-Hk|=e>WlEI2p0T@N9T9qB(yHY$Pm8d-%-o~`xYCLkevJ3a;M0j2V z<4~!80g!R%Me$yOHFL?!0$+iZ`l|@2&0iD#y6{A_y2igjMlzSYDc)NIQOmcW56Cd+eyGmG4IY@r6EEbf?C@_80g2Pr#DgrwBMU zVX<-fS;YRFZ1w;%F26uD=f8w4GA_Ro>1&e0`%J~28+-$vy!#db$NtaqQVYND0OtJn zu#sSM3%?)0mFSNMxSe1MM1KN^i+$L|75xQViT;X!+XjZBsl8Rd0mRn;VI$G})nvTj z{R1Sa{fU5^)7XOdFRA@))S9dJKapsQo)G1ZE4mndkEtavT=v5@`#q)^aK~&_YmGee zwGCxA5BGtEvP(&}?c&ICv)E#N=4ok`)A(nemO)appLuFWZ}6i5Ii=4$^@7v*SkkgW z;?F#FP&EF`({hT&pLtqd(cm*r9T7(U5<&52o>ova{>;;gipHOLT1nCPGf$lqwV!$F zOeFctQx_4U&pfTH#6_-W;CWXiMxS}=rdafur|zQaGfzE~5PjyUr()4(o_Z;keCBBt z#2fp}Q*YqWXP){XjJECYKVV$xz@|maAB(u6? z>}Q_V5Wc2x`+23cfZ#3i!Wnin(Fc;SWBpqe_A`9IWkp{k<$OPJt;RY)Ci?!u8Fn;A z!+z#z0FrWkptx3J5Ri#}u<-CRPo;t~7Ag}|E{HiCInigH1caY?A~Ev7SBS6AJjHxR z#{c(cp8BJQ1t0#wXPzqIfceamz5_*q%S+&z)BR5gyzWlrSo{w*Wdo<8j}0}VQRG9j18`P+_V^a;ypXW5xyRbvK#>wGV zv~)MC2}yI(%pf-CSD8~GcY%l%ql6F zyy?On0M*)+(`_)EEj5%)*gZ&y`T{S(9e;yW+r!p(xpa0umTH6*euXY-m9j=z#ad;U zsx6q~K(=MrZ^I3Oo&vcEHz>Tl9y&6g2Z?kJxX(WNS8ERK;B!N%cYFBF{ zp@IpkHPi&WB^-HscG|pNl&6>^w$dc1q{nISowRZdwdF`MeA?lL6 z58`-drFL*SL6ZU>FxHj^32rm8`lNNmT92$u$f{>#g*0n6ecY7?sw_x|N>_H?Dygjq zv-8@owp%*8rcmx?s2vcpwjisr5mqa*28OKZjAYX+mAD68sR~(VFtTEr#fv=Gnh9M) z7B3%btJ5qs>S!j^1PM{Kl)!7M+9BzLLfvy%$hv7=`6%6?L4xCTX6<^7uyjbvLl%GO zQ@cT$#fw094-=LNS@LWBFv3gD+6{w9G%NE0&mF?P(mQ1FI<9t@%|ASqW!?f8Tq3!F z=w}oCM(q2Oovv)$G_2Yu`VoMlPdoD3D*Uu#9~6_kaAIR5Tb`@_R7&L3N~k^f_0Qh#1DhxbUg1eWuoV8i6e9Xg@pW< zIFDAAt(k>)KNcQ_=U#=sh>C9mi~&)X)?*QH{2n8V4`24SjkLy5%LNyy4U60aw%?&+ z{I<*xyzFf|1o5j@SWP!gzwB*1DB3l-0|Gfe0XDi(5#L(yJCbC6zctzE0@4N7wNBTT zF1Y4(x&m!zwXmTL9y>{m4|;Zz`Xt58*CFuh`kkpAzwK?ZcvFbsj#{1g&3sjIDoD}Y zwCE*o^^%#U?7A7o|G5wAf!;}+gCI2~BxmJ@GRVsL9W@tbjL21v#&$Ik+V4+CdP zZI)ExQY%YKtNd(WNvU(h^Tbf{<9 zQuh|WnNs%w$(Cx~CAY8Ca#FFS*7*H^C8h2!-T}l=>Ve`nQ|dt=*-|;q3f|&YUR7S2 zDX*+4E3GNTC0co=LN9SUm<6$89)bW~8U@PsLnDO;^5ln|`!-3J593PX&~F<`j=3bH|{T0fQRZbC0wq! z^{~oc0nB)KrHEG%gojr%!;*M-4FpmiUMv0Us7nv8SAZVgpoAM0w;tjrt-y?jH;Z@+ zta);BtH9e}C3`yp8cufzzf-tXt?+k|k_@N2MZAX~>U}RWEJ?lZgFvd@_e=i)>ayO8 zQ~;;I=x0a|itvyKv8NT)*wy+luw-I-M7&2~3zrh+O7JmeDp*Q9u54IJJb{2?GaMx;BRCGOwqr7eejC6&QMDo(=IA(WfsC?+EoFb#=0q?yW+-OeAcB0Fyn4d5qrU! z)7UBkd&5e$4+5H-Ru#UQaI0GGS0^PojjbW#ngr1ZYca!r)(C4uE!7BpRaie3!bVs} zrRkg&?qcpQ!T=Gh+hu+rFyrt7gYLo%|5*oZ zfLf}9PFG=$g|LI}s?v7hIf8>~H>J){s__~3Z)O5BKF<Od-dzE@yN43?RNT6o^?Ly`?#>r+Z-Q`lA7=Q^+}#&yDR*-!Y(Ex4clTFmT4;vz z_W>dtD1vpn(jNrOxP7pQhrpWo`%r-kU?qPT0_iE{aPhGuS;!<7R%{3#5wVXXn=NXF z@KK27{L!#QhVU^W9ZOQv3sQBjwBAx?lj@P*Y&x4ix`3R(s*9GF^cwNXO z7FO(dT@holNSBk;%!2a@a5U3iiGbtez_L;o4_5)q`Kw_= zu_mq^;``p$fGW*v5pbL&shQeLcOAf-zaF+Z2X6pJVmBh-I>Hn!zZrHh9lQlp zY2J!}%xZn7rK8CsBPKMX_RY$^W?(tiNG}!a@0K&+> zNKm}z?LkH3J#P;w8t-{~SkZXT+arqFJ#UW^N%p)wCPK95?Qtb8a@z);KcU2E&)buV zMSI?!5>oG%=j|EAqCIcVDwgbddk*o&_Pjk0Jlga20>Zd!qMVDd=S^7~+4J_2 zWL}nx-ShT}@K=S~y=Jcgndn~^&ak72c8lfhd3ys%Isc}(R^u%o*qbbzVMk*$?4GxG zkc97piEB091A=YI!oxjp9|-!;LLUkGSP*kKa-uzN0>V9SBt{>j<+7oR{kTbwc%s=@QU^~ zVA$mk8!oTXy8`||LjJCRKb7S#W{DQZg?HqF55jQ^Lc`MWLg{bh(K_TGWnRr~f-McW z=xMPEooESbnju??4-&KjhSg}x27Uj|w-&JtK`f$|Qii^@CpGjN*LCKsnUm($&)L0x zW?lWmR6MdcEC`0tFZ9) zA}&i1g?CVfW(!{qv_=ZA$NDSW7_q!$J5sK2V?+tyaAU*@492Su7p#HYWbHs%k-2r} zdnJT&zLVm?hYRA>tM3fd-Zxc4$kf|y5v}aD2&|D&!oqM{L>KAuI(220(^c`f)haXP z6}}rwiMK^`7q15~G+Iybn{BjSphb-qZH!n&LcJx>#Kwr|i1rb6RZ$b)Dl^r7HDHNv ztBbb=G5EHo_|5vZ7HGz|yw0??B>PIr7G2}}0ZWQrN4);TQ1k%tn=N`EXoW@VGIWsi z22013i;1KZSW<3AyfR`aw_N;Y%dG&-l-qD=_}rvYVp)mULbJXKSW;-Ucs0aO=n(On zEp%PbROq<2+fAyQHG95aj{=?1AKX(Kz^eFAfH}XuG_4O+ego3uez>7{!-&C$;o>*z z!$zQmK1>OG7(oH~ux0SPiDO}7fY{n4P3uFo-<0&&ht0$rNen)0E`GB<)Pk07hb<@| zAAU%D*b*Rah)UD?P~*2EJ@#R=cv};L4`ak{)`zj6rQ2Z}3dn~J$v&NN0CRp@X&N8O zOZ|4F$3AQ?-gsj0VF&S>^2}zW0`ei7_%IP5KF23b>qEv*B0ctDXYnQzgAY^0 zZ`Ox8(9-QNl>+i%tHg(TfOy%UG>s2srG6LEV;>sCn@$WqIPshHVOP-P!>FL#t8qRL z$K!6&n;{)rZpP09mXte7yxGK1?i}%(E!Tr)%jGH{EO)N-=1IqvTjqBMmXy1PczY5< zxqFGe zax477z>;zg5${l9D0hMQ&6ax@Xr|nTuJOjO7VbctiboF2merJJ%WG;XtIKODs>-Uf zWu@8~cDM@QgtQQ$oIgVG#K9~+@OdOKb9e735sxMa506oX|72^}v5<;tp{-%Zsifmo z0`5qjpa6Y6Q3)q0ZhS2-^Ctr{zMdlDsRZHcY0S{v*09q-sy#f}8aCgb0c2da`O}-+ z8g?cKDaX$OfNfo{y4QIQ18!wlvo-8oM76Fx4*|y-;r^Q08g@RAoWB4TE9R!QhFvJq zMIzaTsPq>DGYxTxh?f#XLtMrTPBmTVRK1mg$Jg8eHlWXJy}KT7uD=|-RK>24%$3Z} zd&E~Mz$0?C60T9)I#TYh1!f$%PQ>d8!jT)8VKE%J5xkTmH%aDZW~U>!NQYxP+Vya& z2)BulIFzZV^0xy^#@ij@-3eQGm1J&H+{H`LBO#Mc#kx^O1c+Oyps^N z$W7?`MS6gw=(?zI7mF4xi^P0T%%nnDES?_%W?J}R5g#Fl7Jif&7Ndn9125IWk4xqW zW@igODIK1rq85Higr`MF9LnG!*)zbB7JgQ|=U@w4*xYJ)o|y_-_yuJ{3%`hfV;h(j zehJZ>e;Kw&3%?@Lt0XmZoBuU%G$LO|z;S4?tkiA(Hvrn|=VP)B*iaC4Hn4a6ms+fMfR)C48#5@wKYP ze+JC>`niZ-5QMK^GDFa_jtC}^l?y)iF>{-N`U3J-;HI4UT2kLog1zfoX=#R}N8c&+ zd!<^Bs{IeZj7L9;_!B{R^fNOorboYkoAT&aN&Q9%di1*jbmk8w{HeHgrpo^X%sBJ6 zi2o2QX_#TZY}TSTyG=`2X}3~<+O3t)Mscg1^-BRW+HFN#nqW!8o>qGqrL=>Uc6(AN zy{zyK!mV_rUyhWd2QDvSM}j2{2U_V8rK|ue=@k{A_DV|Vq`1+>X2#CIjCL0hS0-4} zaFo^Vs+4Z9((cZH8^pFtKHk;?pax`51RM)ZPLWTB1FaX(oL>dj+y<)5;u8(Mff-l& zh`1`jl7<(pE2}AGby&Hwh61eSno3wpaZ}A~nO_^2(e5i^KY}FtjbNocLIG-Ttb|Pzx7t;H zQ(#7WGZ9A;EOG5{U2o+#S7I%!?oll;T#t%x?wE$d49rYl0=NbDsPdC60xa z{5A?uew-4vRouu|Wc_x)jQsW@jwe{+dgsaSpu`EVlHXAQ%1>0nPKsN3T%!Ur@;i$- znP7?QmnT0(iFL4&pUQyarHMLDy;7$s)s}&4P+%w{x~1A6;&g)e#%o>~PKmq1s*K$j zaDAdOW+-*0QcW4yH#-ZMsmE*)=MXG$T49mEV(;r0ee` z;(UT7Zl^r;y_L8Rtkm~KK#dgd4$A@2#IzriSSVKte4_$pr7rrZJ|1}ow}5kg|A>76 z*=zu^XGH8ng1{B-Fa+Y;-UmjR4nm-BbeHmAL~{NRLdjm??u*TmA^7<@}MbDbMin z%%gxA&yE)H7=k74dI;FTd#n?oR?{&}eNw4UDbv zTw43%i2VuKTZZgtpO-r~$i^o56lppC8EmRaDzpA`U}%z-;WNcAMEsIqiTeNowljXE z#IIr1G~Y1b){c71w@Up^siwD7R`~CMnP&e%#2*QkxV!Vp_(_RB!>Wv5$YKqC75yo1W+9e2YHp3^=$g5pXP&2cW65Z>xyi znrt=z+40%;8I+Eb_7(0q1mcd|Cd#xF0`b|mEh0I;G$DKT#dk560cPsgPQ>;EOWaLK z{Tz=@a`xpB=h^p-z=tmGEd^pPmW6;S+5rK#ap-zHoIV!YZH$8(XIU=FvOKd44YR~E z>Mub_JXjq;$oUf3bY<`bcwnY7D~h-htQmow1a^kiLF$5l+Y*k%qhw|Aage$)iG{J* zR}3C_ba7o3h~4fMvAdI98M+;hlAdD6+C3t6PqGJw>^_lpKLy-4+b+GLEUPffn)&TA zB*@El=?y~8_km5d3qB;aDlpS7tBJTetZA1u1g;6Ic3BGnH_^7s+Tx>K`Z9@yv0cVR zzE4mfZkK)$dmXZS<+qFZy?cKUa()18%58iSWFRo(_8<`l6D)DlP@J7TN|l&_mHTB1 zuvN;HP@%XP+Erz~5}1+CidaRk#Ldo=uU29WtmKC%K>2l*u%6;pKI4Z1GxF<;xBGeNuu|V#0m|1ZVGG5L ze74$e3Czfk5^*bnCGMC!`O!+;8dmaS6rlWAC2XU(m9O&SfEoF1Mcj^Hi90P%etRX3 zhn4&e3Q&H65_VMF%2)b{z>NG(B2FS$;x5RO-&u*1VI@C>0bMg`c^&uXI^deorXt{2 zG<$zMJzWmf@V-tx((pVKHr1c;soq_H8MhiloDOS-p%b_(tlZfR0WF#0DP)HD7=|;M z#KP#v%|ZEH+?@)O)?iD*d5zmRTcxGW2N!4^wQq#Fe%lUb*>6+H~ z-GP~!?jhoyu%@Pa37ikBn(mE2dLrIOeAIMbCb2L&@=R3Im(n%OMeO~^=D7%{Fd11iTJ=M%R$Ui9%hMqzHm`u|-PL zabzzWPTPM*+QGCPx8m^;`vkIEhwQxdjBC@zoaRrAvYf;$yhBarR{%vfAUXws&ZUk=RpbA^ak5`;fjF+=dsVR8Jq8uXMu*GTPJ zs?eY76rexXE8zyktv{vyMqtLDn?$^sApE(785ZB4TR~6xbDPv|rwaYKLvr@X>YXCo zB|_p?CY$kh152L!-6P(;uo{-B{io)+-+jzf@Z9fyWy9!v00GCQV22H#tS&+{=O2VE z^4#wskscT^pni6_znFO=&6Q&T58Wwg$@0z z!fb}hKboPH60mt#ivZA?PTT$Nuh~H$uhGOwf)J7h%z9;hgA}0-$ z#hWTV0A>!{ha!GN5C`sKW?1|K_X+5!1NW)aKBEc`+~<Wc&*ez7!$xD}%Mm zSHO}3_qBN6z!n}jGeW*)rh*ajowDJ;eUE^955^q09}va6EMSWqxSvG&nWW&rHS&^# zc*_3;G#$HN5pbNlSzL4i)Uo>wAhw0WHqjAuTK_|=KgCKKCyQ^({sqh&w7*6Chae7G zi&a?{ZCN=gnD$qXrhUCmftyysw=HP)55n_&OXN!(y;cD5nhVPD=(SOR^X*bfXsft& zEaR63W*l2a#C8PXSbJvp7mh89d@08|NOn2O(Xr(fpkp1CP@=eT3@`9l0hn=YMG;pb z2*)}x!@qE>GxDVz>mu2eDM!b;N>^uu@Q0z@MCdMpb+FR+0A?KQDPk{Jvr=0{U~gE- z_d!5=rQ-F`s^X)kuf`-6#;(`YZ}C@;*lUo@wlm{mO+<5kE!ZOCVr`N7lGMzZqo0`T zh-oTbQ|kKzGZh~o;y{8pGJ}}mUmTgi$d@`YrIO81jz^|Ux;lk~N2Xka3K5Kh__jwS zFymlW#41>GWU2+$z)F4y0vf{W3SUpSRju|zNlC7!*B5aEf~fX}%>bGFU@${%0-`y;BW#hOHc_OVNNQ&7FbN!awle~bb2ZCKtsN!<%=sy> zp;)oCL!HP|MK)cfroz_)GhJnxh`SI(S7~5|f6-N@BVVekILYoxId+xZq^m_lICN)- zFjEBUV7Z?K%s4n(#5u5L=z4*3VI@Bg0S(>Vh3_HUs+RdZNlAw8ULwvXh-&Z64F96q z`ygMc+WSg2M>$q|Kj~`US8}nsKR_Mo0}ycZxS&rT2r%alf^EJ}9}Je{4ne@NS&Q}Q zLnHPAve`>apFRxHoIf14NS|IP(h(#zzpOeEG&y$^0*-4V7MEI99WA|M61^s0jCE`z zb{xgH1(9OmPxgoEXWTL|N_=H?k*7_LRku<>txcCv>MO<7v!j z9;Wc+#-{@_J@E_?&m@@I%AhUcXDRV)SoNoK7)UmooU7FHlxll>jXxilDdPeWFC0s z#6sEp(^`i2dKwgnXYlKhF7^!wI5q&;Gep8g#34Z-Z?nmbpefT$2=sM4G^*jv2;ie| z1XIT-<8LJ;c_ew8h_@3=Z3uEaJ}CC&mO+m)J57cPiZ-^~0bb7E37aY@ll6B2Gi`ad zi1!doJz7(5xL1ky!Ky9qXCQg_`G8UvDK$A>RhdfvATU$LLn1y*u<+sMBT9S}R%JX! z7JI|v!k-Xs)hqmyq$IuJDG{G0SorYs86`doEA{8dqWbf~Ul4B9%l(U_BM*b}k-zHeNdF>q~z6&e)_Y|P~`%3sgaVuZu zKLlpvKN9g{f`yycK2hSQu#*3bf#4Yf`)Pco`Z*%%%wHhjSTsk#xh=!-azzlJTVEnA z=f8qYb-L25{~DNa>l+cjC0Mv;={qHU4=cBRP=NA3D&Z%^ZLKT(&%liQFCzX*uyD`P zZ%X_fR`P!^knCCdQ>lL`)s|82{|08t_(#MRtFdi5;b&kPFD((p7tvs)-5P=P_K!B= zCb2NK<%cc9w)|FsxaHeM?4`+O^;0eXV~~wavkVA1-wrn2@|C_lFyr>JB6ff^ zZZ9Wrd04sK5rOocr4sSs_6kg5VRWRaJxeP_?3Ku7ryx7t{*kw5sZ+%6Og0A&+0jDO z?pf*rLe8%Yo2q1{%y$K5D%nlM?y#nkJp}fIRV8~NpqXG5;k|{MBc93lKBOcw!Kxyz zMzC@CUWDVgkeebV@LoKaDxtq|zzxLD9w7>#JoZ%xpyPcl`03@{v#mf@DEu_A6m zFtugMam9f3Nwo3C?8O<{QcD(hTzmW;u$9vrl-vo7;MaQm*V3JYe-r&UsMe-mPu0c+ z?~`)NcR(fe!3w`EXm~xYN`?!xst&1 zdL>4$r<$f%^m?jYMAhr58k7*do@%;c(d((4V#({Nc166g*Hi5VJbFFV41{xjrgARE z>#3Brk=IkrlFV$$*w<6d5$=WC*Fwz&GSSZy&ak72UhI_jdaB)#l=FLtYc=)+f?qKU zXV}pg4f}ek`AEw7y~VW}`vAeyeBt5isd9q$v(Wy64iLl~j-2TAR06`+Q;`^X;13jE zucwOnj*KQ>Pu0rxM<4Ik*A3vme*MOE8#Q|Tq+!EHPTF?kF=Iz=HEL+-UYSz<)r?zX z#?;CoRrRG6*{V#YVscegRi?hYx~{ISVoJ@_X>~QFQ>(Mpn(^Eot?--MK4q1aQ|qRc z)lHsUGo`*PJ0)A4#pAEcwCYSqnYUMQ##Daye~n#Bj}%oDhVT;Q z5s`#(0fC8zfQFzm;IJSu5#>=b!lGATIz3%QdQLhslbY`E1C(k;L;>ZYkPue<1@~@E zTp1Q5vf~f1aO?TbqpI%ho=(rAGTrBO-*v0&-0zD7S0MP8r*-3Cn$2bm*D^b|Fqfw7 zQ>SK+f3`4l^7zSXpPcw~W@au0meL^qE_94-kJwWrC>_UwbuEx+LKnF_ig>i8$iEL` zVXs1)`7%KxFc{Myzp;)zMFOnLohc2wtH_JcY?>o>rpUe(%nVg!2(E#uI)0Kma#aUU z5;UpofbZV3UFTV&-16qc6iu?z4IU0lKU#6@Zc9B`5(gLEXJw3$Y> z?}nvA!U_RLLQA*3$nU5-ZziM`iULIfqyQ0+IMi}mk>3q1k*p~S6bd?jbe^ltCL_v> zy6hkH7FvwPwp6WZ_oQn3!m8l_wc?+RZJ}C&{~~Ut0yhlTRv#o2!L_{~c#zGY%vQ2l zDtYgYx8@T9TSl2NWHUvMbi}Y@qJ^0s`q~_Vlda=xOQ?lilk$Q_uxr?=5t8Xj*fk`y zIZ~MyxH9L52K1y48JSu5iPe$STp+XiG6aUK#w4jx-+#!`;ZBRu)3DAEGBiqzHDnPu zdYTtdv~WVcWSzrd?wDN?dtS=;;_Q-yt#S;eCt7#{EvY7VLn{KZt9%)+qxv#RST9%Q zYri&}oPu$LJlDF%!T6zvuR2*a7WuE!a;K7C;hYDuYC^irx)ZGTN0Uv1{Jy&CB^%ec z=LYfsN=QU3N$TEk{WS8{B7dOj!)2qOOb4|-QJE+XHaNshjun>bE2|S&dJq{#4OWc_ zEM<7ULWdhqB_S2TaroREPbYMPFNrZw`Y5afMkDD0%$nNW3qLw0M-zb)bf*#;$(PAT z)^$jLOd`_OQI0+i9bG{a46Sj_Ch6g11mhPcCwip)s8B?fiG)c)YGq=~gAk2y&Rq<9wj>Ej~nakW%vx5ZxFc=?*>^nPuP$d7C92Byf^+7`;{G52s}SyLRQ% zm>|`sQC|jHEfmRUA!!XCjFr;0u%D1e%aI(B$P0B^NXgA~aK&MBqlNS~tX4VWcpMgd zuI=f9m)tJ(iOXoFkF?(OH=4|dA8V831%Exk#Qg#j_cwJ_PTVRQ1yPJqGKxa6x!ft9 zqF7dMd6rGigrijk(rajaX~Q0?14XNXbmxE2`pUF$OvCAly`^Qe;zUMrHf%Qug2NZd zIWx!Dp;2Q7=Pi=+riC*PF}X5wWjlYBG|0%9O)i)|l5L&V(>Xe2^oUjx7797BdXN0t z%ms{g3?l9C)7S)F47UJs=R;a83!EN2yz@o-3_?C_GOxRpJ%950huO?HLIy;We65_mbFO3Qd4^r89O1?&>k_=wq&5ggg3#zjEBstNg#= zj=sl{`U{P{t!{VudUJ7UrQd6&%h#9Mt<~OqW6#oZGtI8G`rTf$zmm3k+3H$jSF7F5 wR(eqGw%Qqgk!K72#ctYM?d6N@-p$5t#@y^L^;ff0JzmjD0& From 746b85f42c9ea1d1f9a1a4bb2f6673b9cf588da3 Mon Sep 17 00:00:00 2001 From: "Brandon T. Willard" Date: Sat, 17 Oct 2020 12:38:24 -0500 Subject: [PATCH 07/11] Remove Python 2.x pickle compatibility class and more stale pickle data tests --- doc/library/misc/pkl_utils.txt | 4 - tests/gpuarray/test_multinomial.py | 5 +- tests/gpuarray/test_pickle.py | 14 +-- tests/gpuarray/test_type.py | 9 +- tests/sandbox/test_multinomial.py | 40 -------- .../test_multinomial_wo_replacement.py | 15 +-- theano/misc/pkl_utils.py | 93 +------------------ 7 files changed, 14 insertions(+), 166 deletions(-) diff --git a/doc/library/misc/pkl_utils.txt b/doc/library/misc/pkl_utils.txt index 214b14ab2f..8b3b057210 100644 --- a/doc/library/misc/pkl_utils.txt +++ b/doc/library/misc/pkl_utils.txt @@ -15,10 +15,6 @@ .. autoclass:: theano.misc.pkl_utils.StripPickler -.. autoclass:: theano.misc.pkl_utils.CompatUnpickler - .. seealso:: :ref:`tutorial_loadsave` - - diff --git a/tests/gpuarray/test_multinomial.py b/tests/gpuarray/test_multinomial.py index ecb1b368b5..bb50929474 100644 --- a/tests/gpuarray/test_multinomial.py +++ b/tests/gpuarray/test_multinomial.py @@ -7,9 +7,10 @@ import tests.unittest_tools as utt +from pickle import Unpickler + from theano import config, function, tensor from theano.compat import PY3 -from theano.misc.pkl_utils import CompatUnpickler from theano.sandbox import multinomial from theano.sandbox.rng_mrg import MRG_RandomStreams as RandomStreams from theano.gpuarray.multinomial import ( @@ -384,6 +385,6 @@ def test_unpickle_legacy_op(): if not PY3: with open(os.path.join(testfile_dir, fname), "r") as fp: - u = CompatUnpickler(fp) + u = Unpickler(fp) m = u.load() assert isinstance(m, GPUAChoiceFromUniform) diff --git a/tests/gpuarray/test_pickle.py b/tests/gpuarray/test_pickle.py index d5cc05958c..3b9f63cb9b 100644 --- a/tests/gpuarray/test_pickle.py +++ b/tests/gpuarray/test_pickle.py @@ -13,9 +13,9 @@ import numpy as np +from pickle import Unpickler + from theano import config -from theano.compat import PY3 -from theano.misc.pkl_utils import CompatUnpickler from theano.gpuarray.type import ContextNotDefined @@ -37,10 +37,7 @@ def test_unpickle_gpuarray_as_numpy_ndarray_flag1(): fname = "GpuArray.pkl" with open(os.path.join(testfile_dir, fname), "rb") as fp: - if PY3: - u = CompatUnpickler(fp, encoding="latin1") - else: - u = CompatUnpickler(fp) + u = Unpickler(fp, encoding="latin1") with pytest.raises((ImportError, ContextNotDefined)): u.load() finally: @@ -56,10 +53,7 @@ def test_unpickle_gpuarray_as_numpy_ndarray_flag2(): fname = "GpuArray.pkl" with open(os.path.join(testfile_dir, fname), "rb") as fp: - if PY3: - u = CompatUnpickler(fp, encoding="latin1") - else: - u = CompatUnpickler(fp) + u = Unpickler(fp, encoding="latin1") try: mat = u.load() except ImportError: diff --git a/tests/gpuarray/test_type.py b/tests/gpuarray/test_type.py index 539aaeb1f1..283ebe0267 100644 --- a/tests/gpuarray/test_type.py +++ b/tests/gpuarray/test_type.py @@ -5,10 +5,10 @@ pygpu = pytest.importorskip("pygpu") -from theano.compat import PY3 +from pickle import Unpickler + from theano import config from theano.compile import DeepCopyOp, Rebroadcast, ViewOp -from theano.misc.pkl_utils import CompatUnpickler from theano.gpuarray.type import GpuArrayType, gpuarray_shared_constructor from tests.gpuarray.config import test_ctx_name @@ -122,10 +122,7 @@ def test_unpickle_gpuarray_as_numpy_ndarray_flag0(): fname = "GpuArray.pkl" with open(os.path.join(testfile_dir, fname), "rb") as fp: - if PY3: - u = CompatUnpickler(fp, encoding="latin1") - else: - u = CompatUnpickler(fp) + u = Unpickler(fp, encoding="latin1") mat = u.load() assert isinstance(mat, pygpu.gpuarray.GpuArray) assert np.asarray(mat)[0] == -42.0 diff --git a/tests/sandbox/test_multinomial.py b/tests/sandbox/test_multinomial.py index aed340ab7d..2a4cccac27 100644 --- a/tests/sandbox/test_multinomial.py +++ b/tests/sandbox/test_multinomial.py @@ -1,15 +1,9 @@ -import os -import sys - import numpy as np -import theano import tests.unittest_tools as utt from theano import config, function, tensor from theano.sandbox import multinomial -from theano.compat import PY3 -from theano.misc.pkl_utils import CompatUnpickler def test_n_samples_1(): @@ -51,40 +45,6 @@ def test_n_samples_2(): assert res.sum() == i -def test_n_samples_compatibility(): - # This test checks if the new change to MultinomialFromUniform is still compatible - # with old interface. Here I will load a graph created (using the old interface) as follows: - # RandomStreams = theano.sandbox.rng_mrg.MRG_RandomStreams - # th_rng = RandomStreams(12345) - # X = T.matrix('X') - # pvals = T.exp(X) - # pvals = pvals / pvals.sum(axis=1, keepdims=True) - # samples = th_rng.multinomial(pvals=pvals) - # pickle.dump([X, samples], open("multinomial_test_graph.pkl", "w")) - - folder = os.path.dirname(os.path.abspath(__file__)) - with open(os.path.join(folder, "multinomial_test_graph.pkl"), "rb") as pkl_file: - if PY3: - u = CompatUnpickler(pkl_file, encoding="latin1") - else: - u = CompatUnpickler(pkl_file) - try: - X, samples = u.load() - except ImportError: - # Windows sometimes fail with nonsensical errors like: - # ImportError: No module named type - # ImportError: No module named copy_reg - # when "type" and "copy_reg" are builtin modules. - if sys.platform == "win32": - exc_type, exc_value, exc_trace = sys.exc_info() - raise - raise - - f = theano.function([X], samples) - res = f(np.random.randn(20, 10)) - assert np.all(res.sum(axis=1) == 1) - - def test_multinomial_0(): # This tests the MultinomialFromUniform Op directly, not going through the # multinomial() call in GPU random generation. diff --git a/tests/sandbox/test_multinomial_wo_replacement.py b/tests/sandbox/test_multinomial_wo_replacement.py index c628c64c6a..ec5e1e55be 100644 --- a/tests/sandbox/test_multinomial_wo_replacement.py +++ b/tests/sandbox/test_multinomial_wo_replacement.py @@ -1,9 +1,7 @@ import numpy as np import pytest -import os + from theano import config, function, tensor -from theano.compat import PY3 -from theano.misc.pkl_utils import CompatUnpickler from theano.sandbox import multinomial from theano.sandbox.rng_mrg import MRG_RandomStreams as RandomStreams @@ -214,14 +212,3 @@ def test_select_proportional_to_weight(self): avg_pvals /= avg_pvals.sum() avg_diff = np.mean(abs(avg_pvals - pvals)) assert avg_diff < mean_rtol - - def test_unpickle_legacy_op(self): - testfile_dir = os.path.dirname(os.path.realpath(__file__)) - fname = "test_sandbox_multinomial_wo_replacement.pkl" - - if not PY3: - with open(os.path.join(testfile_dir, fname), "r") as fp: - u = CompatUnpickler(fp) - m = u.load() - print(m) - assert isinstance(m, multinomial.ChoiceFromUniform) diff --git a/theano/misc/pkl_utils.py b/theano/misc/pkl_utils.py index 07d253a152..7233833a02 100644 --- a/theano/misc/pkl_utils.py +++ b/theano/misc/pkl_utils.py @@ -12,6 +12,9 @@ import tempfile import zipfile import warnings + +import theano + from collections import defaultdict from contextlib import closing from pickle import HIGHEST_PROTOCOL @@ -22,10 +25,7 @@ except ImportError: DEFAULT_PROTOCOL = HIGHEST_PROTOCOL -import theano from theano import config -from theano.compat import PY3 -from six import string_types from theano.compile.sharedvalue import SharedVariable __docformat__ = "restructuredtext en" @@ -80,93 +80,6 @@ def save(self, obj): return Pickler.save(self, obj) -# Make an unpickler that tries encoding byte streams before raising TypeError. -# This is useful with python 3, in order to unpickle files created with -# python 2. -# This code is taken from Pandas, https://github.com/pydata/pandas, -# under the same 3-clause BSD license. -def load_reduce(self): - stack = self.stack - args = stack.pop() - func = stack[-1] - try: - value = func(*args) - except Exception: - # try to reencode the arguments - if self.encoding is not None: - new_args = [] - for arg in args: - if isinstance(arg, string_types): - new_args.append(arg.encode(self.encoding)) - else: - new_args.append(arg) - args = tuple(new_args) - try: - stack[-1] = func(*args) - return - except Exception: - pass - - # if self.is_verbose: - # print(sys.exc_info()) - # print(func, args) - - raise - - stack[-1] = value - - -if PY3: - - class CompatUnpickler(pickle._Unpickler): - """ - Allow to reload in python 3 some pickled numpy ndarray. - - .. versionadded:: 0.8 - - Examples - -------- - - :: - - with open(fname, 'rb') as fp: - if PY3: - u = CompatUnpickler(fp, encoding="latin1") - else: - u = CompatUnpickler(fp) - mat = u.load() - - """ - - pass - - # Register `load_reduce` defined above in CompatUnpickler - CompatUnpickler.dispatch[pickle.REDUCE[0]] = load_reduce -else: - - class CompatUnpickler(pickle.Unpickler): - """ - Allow to reload in python 3 some pickled numpy ndarray. - - .. versionadded:: 0.8 - - Examples - -------- - - :: - - with open(fname, 'rb') as fp: - if PY3: - u = CompatUnpickler(fp, encoding="latin1") - else: - u = CompatUnpickler(fp) - mat = u.load() - - """ - - pass - - class PersistentNdarrayID(object): """Persist ndarrays in an object by saving them to a zip file. From 5194e7e56f9f0bd08d514b19b0f2db3aeb735d51 Mon Sep 17 00:00:00 2001 From: "Brandon T. Willard" Date: Sat, 17 Oct 2020 17:06:21 -0500 Subject: [PATCH 08/11] Remove unused debug-named functions in theano.gof.op --- tests/gof/test_op.py | 21 --------------------- theano/gof/op.py | 37 +------------------------------------ 2 files changed, 1 insertion(+), 57 deletions(-) diff --git a/tests/gof/test_op.py b/tests/gof/test_op.py index 76ccb7b9ad..1ff60d3404 100644 --- a/tests/gof/test_op.py +++ b/tests/gof/test_op.py @@ -378,24 +378,3 @@ def test_get_debug_values_exc(): finally: config.compute_test_value = prev_value - - -def test_debug_error_message(): - # tests that debug_error_message raises an - # exception when it should. - - prev_value = config.compute_test_value - - for mode in ["ignore", "raise"]: - - try: - config.compute_test_value = mode - - try: - op.debug_error_message("msg") - raised = False - except ValueError: - raised = True - assert raised - finally: - config.compute_test_value = prev_value diff --git a/theano/gof/op.py b/theano/gof/op.py index 50a7e37999..3f051d634e 100644 --- a/theano/gof/op.py +++ b/theano/gof/op.py @@ -1057,48 +1057,13 @@ def missing_test_message(msg): assert action in ["ignore", "off"] -def debug_error_message(msg): - """ - Displays a message saying that an error was found in some - test_values. Becomes a warning or a ValueError depending on - config.compute_test_value. - - """ - action = config.compute_test_value - - # this message should never be called when the debugger is off - assert action != "off" - - if action in ["raise", "ignore"]: - raise ValueError(msg) - else: - assert action == "warn" - warnings.warn(msg, stacklevel=2) - - -def debug_assert(condition, msg=None): - """ - Customized assert with options to ignore the assert - with just a warning - """ - if msg is None: - msg = "debug_assert failed" - if not condition: - action = config.compute_test_value - if action in ["raise", "ignore"]: - raise AssertionError(msg) - else: - assert action == "warn" - warnings.warn(msg, stacklevel=2) - - def get_debug_values(*args): """ Intended use: for val_1, ..., val_n in get_debug_values(var_1, ..., var_n): if some condition on val_1, ..., val_n is not met: - debug_error_message("condition was not met") + missing_test_message("condition was not met") Given a list of variables, get_debug_values does one of three things: From 717fd402d0287daeb7875965096dba4d7453ec5a Mon Sep 17 00:00:00 2001 From: "Brandon T. Willard" Date: Sat, 17 Oct 2020 17:18:13 -0500 Subject: [PATCH 09/11] Refactor test message construction in get_debug_values --- theano/gof/op.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/theano/gof/op.py b/theano/gof/op.py index 3f051d634e..c4c0415c3d 100644 --- a/theano/gof/op.py +++ b/theano/gof/op.py @@ -1093,10 +1093,10 @@ def get_debug_values(*args): except AttributeError: if hasattr(arg, "name") and arg.name is not None: missing_test_message( - "Argument " + str(i) + "('" + arg.name + "') has no test value" + "Argument {} ('{}') has no test value".format(i, arg.name) ) else: - missing_test_message("Argument " + str(i) + " has no test value") + missing_test_message("Argument {} has no test value".format(i)) return [] if len(rval) == 1: From 747e80fd69495d9ccb301fc2aef7afac17ecb39d Mon Sep 17 00:00:00 2001 From: "Brandon T. Willard" Date: Sat, 17 Oct 2020 17:19:22 -0500 Subject: [PATCH 10/11] Remove redundant test value check in PureOp._get_test_value --- theano/gof/op.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/theano/gof/op.py b/theano/gof/op.py index c4c0415c3d..60b10e2505 100644 --- a/theano/gof/op.py +++ b/theano/gof/op.py @@ -553,29 +553,10 @@ def _get_test_value(cls, v): elif isinstance(v, SharedVariable): return v.get_value(borrow=True, return_internal_type=True) elif isinstance(v, graph.Variable) and hasattr(v.tag, "test_value"): - # ensure that the test value is correct - try: - ret = v.type.filter(v.tag.test_value) - except Exception as e: - # Better error message. - detailed_err_msg = ( - "For compute_test_value, one input test value does not" - " have the requested type.\n" - ) - detailed_err_msg += utils.get_variable_trace_string(v) + return v.tag.test_value - detailed_err_msg += ( - "\nThe error when converting the test value to that" - " variable type:" - ) - # We need to only have 1 args and it should be of type - # string. Otherwise, it print the tuple and so the - # new line do not get printed. - args = (detailed_err_msg,) + tuple(str(arg) for arg in e.args) - e.args = ("\n".join(args),) - raise - return ret detailed_err_msg = utils.get_variable_trace_string(v) + raise AttributeError("%s has no test value %s" % (v, detailed_err_msg)) def __call__(self, *inputs, **kwargs): From ea44b16df4ddab420e1e57846ad3f36a6a813a75 Mon Sep 17 00:00:00 2001 From: "Brandon T. Willard" Date: Sat, 17 Oct 2020 17:20:45 -0500 Subject: [PATCH 11/11] Refactor imports and test design in tests.gof.test_op --- tests/gof/test_op.py | 111 +++++++++++++------------------------------ 1 file changed, 32 insertions(+), 79 deletions(-) diff --git a/tests/gof/test_op.py b/tests/gof/test_op.py index 1ff60d3404..1deb4eb8ad 100644 --- a/tests/gof/test_op.py +++ b/tests/gof/test_op.py @@ -1,14 +1,14 @@ import numpy as np import pytest - import theano import theano.gof.op as op +import theano.tensor as tt + from six import string_types -from theano.gof.type import Type, Generic +from theano import scalar, shared +from theano.configparser import change_flags from theano.gof.graph import Apply, Variable -import theano.tensor as T -from theano import scalar -from theano import shared +from theano.gof.type import Generic, Type config = theano.config Op = op.Op @@ -238,15 +238,15 @@ class DoubleOp(Op): __props__ = () - itypes = [T.dmatrix] - otypes = [T.dmatrix] + itypes = [tt.dmatrix] + otypes = [tt.dmatrix] def perform(self, node, inputs, outputs): inp = inputs[0] output = outputs[0] output[0] = inp * 2 - x_input = T.dmatrix("x_input") + x_input = tt.dmatrix("x_input") f = theano.function([x_input], DoubleOp()(x_input)) inp = np.random.rand(5, 4) out = f(inp) @@ -255,17 +255,17 @@ def perform(self, node, inputs, outputs): def test_test_value_python_objects(): for x in ([0, 1, 2], 0, 0.5, 1): - assert (op.get_test_value(x) == x).all() + assert np.all(op.get_test_value(x) == x) def test_test_value_ndarray(): x = np.zeros((5, 5)) v = op.get_test_value(x) - assert (v == x).all() + assert np.all(v == x) def test_test_value_constant(): - x = T.as_tensor_variable(np.zeros((5, 5))) + x = tt.as_tensor_variable(np.zeros((5, 5))) v = op.get_test_value(x) assert np.all(v == np.zeros((5, 5))) @@ -278,62 +278,37 @@ def test_test_value_shared(): assert np.all(v == np.zeros((5, 5))) +@change_flags(compute_test_value="raise") def test_test_value_op(): - try: - prev_value = config.compute_test_value - config.compute_test_value = "raise" - x = T.log(np.ones((5, 5))) - v = op.get_test_value(x) - - assert np.allclose(v, np.zeros((5, 5))) - finally: - config.compute_test_value = prev_value + x = tt.log(np.ones((5, 5))) + v = op.get_test_value(x) -def test_get_debug_values_no_debugger(): - "get_debug_values should return [] when debugger is off" - - prev_value = config.compute_test_value - try: - config.compute_test_value = "off" + assert np.allclose(v, np.zeros((5, 5))) - x = T.vector() - for x_val in op.get_debug_values(x): - assert False +@change_flags(compute_test_value="off") +def test_get_debug_values_no_debugger(): + """Tests that `get_debug_values` returns `[]` when debugger is off.""" - finally: - config.compute_test_value = prev_value + x = tt.vector() + assert op.get_debug_values(x) == [] +@change_flags(compute_test_value="ignore") def test_get_det_debug_values_ignore(): - # get_debug_values should return [] when debugger is ignore - # and some values are missing - - prev_value = config.compute_test_value - try: - config.compute_test_value = "ignore" - - x = T.vector() + """Tests that `get_debug_values` returns `[]` when debugger is set to "ignore" and some values are missing.""" - for x_val in op.get_debug_values(x): - assert False - - finally: - config.compute_test_value = prev_value + x = tt.vector() + assert op.get_debug_values(x) == [] def test_get_debug_values_success(): - # tests that get_debug_value returns values when available - # (and the debugger is on) + """Tests that `get_debug_value` returns values when available (and the debugger is on).""" - prev_value = config.compute_test_value for mode in ["ignore", "warn", "raise"]: - - try: - config.compute_test_value = mode - - x = T.vector() + with change_flags(compute_test_value=mode): + x = tt.vector() x.tag.test_value = np.zeros((4,), dtype=config.floatX) y = np.zeros((5, 5)) @@ -348,33 +323,11 @@ def test_get_debug_values_success(): assert iters == 1 - finally: - config.compute_test_value = prev_value - +@change_flags(compute_test_value="raise") def test_get_debug_values_exc(): - # tests that get_debug_value raises an exception when - # debugger is set to raise and a value is missing - - prev_value = config.compute_test_value - try: - config.compute_test_value = "raise" + """Tests that `get_debug_value` raises an exception when debugger is set to raise and a value is missing.""" - x = T.vector() - - try: - for x_val in op.get_debug_values(x): - # this assert catches the case where we - # erroneously get a value returned - assert False - raised = False - except AttributeError: - raised = True - - # this assert catches the case where we got [] - # returned, and possibly issued a warning, - # rather than raising an exception - assert raised - - finally: - config.compute_test_value = prev_value + with pytest.raises(AttributeError): + x = tt.vector() + assert op.get_debug_values(x) == []