From b340c85fd246a4ec85f3c33407f521b024d0de3c Mon Sep 17 00:00:00 2001 From: mahon94 Date: Fri, 22 Oct 2021 10:34:56 -0700 Subject: [PATCH 01/15] Init: actor.forward outputs separate deterministic actions --- .../mlagents/trainers/torch/action_model.py | 29 +++++++++++++++++-- .../mlagents/trainers/torch/distributions.py | 13 +++++++++ .../trainers/torch/model_serialization.py | 6 ++++ ml-agents/mlagents/trainers/torch/networks.py | 18 ++++++++++-- 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/ml-agents/mlagents/trainers/torch/action_model.py b/ml-agents/mlagents/trainers/torch/action_model.py index c5de586e4d..a27b1ffdef 100644 --- a/ml-agents/mlagents/trainers/torch/action_model.py +++ b/ml-agents/mlagents/trainers/torch/action_model.py @@ -9,6 +9,10 @@ from mlagents.trainers.torch.agent_action import AgentAction from mlagents.trainers.torch.action_log_probs import ActionLogProbs from mlagents_envs.base_env import ActionSpec +from mlagents_envs import logging_util + +logger = logging_util.get_logger(__name__) + EPSILON = 1e-7 # Small value to avoid divide by zero @@ -161,23 +165,42 @@ def get_action_out(self, inputs: torch.Tensor, masks: torch.Tensor) -> torch.Ten """ dists = self._get_dists(inputs, masks) continuous_out, discrete_out, action_out_deprecated = None, None, None + deter_continuous_out, deter_discrete_out = None, None # deterministic actions if self.action_spec.continuous_size > 0 and dists.continuous is not None: continuous_out = dists.continuous.exported_model_output() - action_out_deprecated = dists.continuous.exported_model_output() + action_out_deprecated = continuous_out + deter_continuous_out = dists.continuous.deterministic_sample() if self._clip_action_on_export: continuous_out = torch.clamp(continuous_out, -3, 3) / 3 - action_out_deprecated = torch.clamp(action_out_deprecated, -3, 3) / 3 + action_out_deprecated = continuous_out + deter_continuous_out = torch.clamp(deter_continuous_out, -3, 3) / 3 if self.action_spec.discrete_size > 0 and dists.discrete is not None: + logger.info( + f"dist: {[discrete_dist.probs for discrete_dist in dists.discrete]}" + ) # TODO: remove discrete_out_list = [ discrete_dist.exported_model_output() for discrete_dist in dists.discrete ] + logger.info(f"discretelist {discrete_out_list}") # TODO: remove discrete_out = torch.cat(discrete_out_list, dim=1) action_out_deprecated = torch.cat(discrete_out_list, dim=1) + deter_discrete_out_list = [ + discrete_dist.deterministic_sample() for discrete_dist in dists.discrete + ] + logger.info(f"deterlist {deter_discrete_out_list}") # TODO: remove + deter_discrete_out = torch.cat(deter_discrete_out_list, dim=1) + # deprecated action field does not support hybrid action if self.action_spec.continuous_size > 0 and self.action_spec.discrete_size > 0: action_out_deprecated = None - return continuous_out, discrete_out, action_out_deprecated + return ( + continuous_out, + discrete_out, + action_out_deprecated, + deter_continuous_out, + deter_discrete_out, + ) def forward( self, inputs: torch.Tensor, masks: torch.Tensor diff --git a/ml-agents/mlagents/trainers/torch/distributions.py b/ml-agents/mlagents/trainers/torch/distributions.py index 1f5960d10b..00aee3aaa0 100644 --- a/ml-agents/mlagents/trainers/torch/distributions.py +++ b/ml-agents/mlagents/trainers/torch/distributions.py @@ -16,6 +16,13 @@ def sample(self) -> torch.Tensor: """ pass + @abc.abstractmethod + def deterministic_sample(self) -> torch.Tensor: + """ + Return the most probable sample from this distribution. + """ + pass + @abc.abstractmethod def log_prob(self, value: torch.Tensor) -> torch.Tensor: """ @@ -59,6 +66,9 @@ def sample(self): sample = self.mean + torch.randn_like(self.mean) * self.std return sample + def deterministic_sample(self): + return self.mean + def log_prob(self, value): var = self.std ** 2 log_scale = torch.log(self.std + EPSILON) @@ -113,6 +123,9 @@ def __init__(self, logits): def sample(self): return torch.multinomial(self.probs, 1) + def deterministic_sample(self): + return torch.argmax(self.probs).reshape((1, 1)) + def pdf(self, value): # This function is equivalent to torch.diag(self.probs.T[value.flatten().long()]), # but torch.diag is not supported by ONNX export. diff --git a/ml-agents/mlagents/trainers/torch/model_serialization.py b/ml-agents/mlagents/trainers/torch/model_serialization.py index 0fa946280c..6924f3a5be 100644 --- a/ml-agents/mlagents/trainers/torch/model_serialization.py +++ b/ml-agents/mlagents/trainers/torch/model_serialization.py @@ -56,10 +56,13 @@ class TensorNames: recurrent_output = "recurrent_out" memory_size = "memory_size" version_number = "version_number" + continuous_action_output_shape = "continuous_action_output_shape" discrete_action_output_shape = "discrete_action_output_shape" continuous_action_output = "continuous_actions" discrete_action_output = "discrete_actions" + deterministic_continuous_action_output = "deterministic_continuous_actions" + deterministic_discrete_action_output = "deterministic_discrete_actions" # Deprecated TensorNames entries for backward compatibility is_continuous_control_deprecated = "is_continuous_control" @@ -122,6 +125,7 @@ def __init__(self, policy): self.output_names += [ TensorNames.continuous_action_output, TensorNames.continuous_action_output_shape, + TensorNames.deterministic_continuous_action_output, ] self.dynamic_axes.update( {TensorNames.continuous_action_output: {0: "batch"}} @@ -130,6 +134,7 @@ def __init__(self, policy): self.output_names += [ TensorNames.discrete_action_output, TensorNames.discrete_action_output_shape, + TensorNames.deterministic_discrete_action_output, ] self.dynamic_axes.update({TensorNames.discrete_action_output: {0: "batch"}}) @@ -164,5 +169,6 @@ def export_policy_model(self, output_filepath: str) -> None: input_names=self.input_names, output_names=self.output_names, dynamic_axes=self.dynamic_axes, + verbose=True, # TODO: remove ) logger.info(f"Exported {onnx_output_path}") diff --git a/ml-agents/mlagents/trainers/torch/networks.py b/ml-agents/mlagents/trainers/torch/networks.py index 4a2e1dafc6..a79f5238f0 100644 --- a/ml-agents/mlagents/trainers/torch/networks.py +++ b/ml-agents/mlagents/trainers/torch/networks.py @@ -571,6 +571,9 @@ def forward( class SimpleActor(nn.Module, Actor): MODEL_EXPORT_VERSION = 3 # Corresponds to ModelApiVersion.MLAgents2_0 + is_stochastic_action_sampling = ( + True + ) # TODO: this should be a user input both for training and inference def __init__( self, @@ -582,6 +585,7 @@ def __init__( ): super().__init__() self.action_spec = action_spec + # self.is_continuous_int_deprecated = is_stochastic_action_sampling # TODO: self.version_number = torch.nn.Parameter( torch.Tensor([self.MODEL_EXPORT_VERSION]), requires_grad=False ) @@ -675,12 +679,22 @@ def forward( cont_action_out, disc_action_out, action_out_deprecated, + deter_cont_action_out, + deter_disc_action_out, ) = self.action_model.get_action_out(encoding, masks) export_out = [self.version_number, self.memory_size_vector] if self.action_spec.continuous_size > 0: - export_out += [cont_action_out, self.continuous_act_size_vector] + export_out += [ + cont_action_out, + self.continuous_act_size_vector, + deter_cont_action_out, + ] if self.action_spec.discrete_size > 0: - export_out += [disc_action_out, self.discrete_act_size_vector] + export_out += [ + disc_action_out, + self.discrete_act_size_vector, + deter_disc_action_out, + ] if self.network_body.memory_size > 0: export_out += [memories_out] return tuple(export_out) From 07c11d8c52cadc49622a3e3cfa08dd916c0f2035 Mon Sep 17 00:00:00 2001 From: mahon94 Date: Thu, 28 Oct 2021 12:30:46 -0700 Subject: [PATCH 02/15] fix tensor shape for discrete actions --- ml-agents/mlagents/trainers/torch/distributions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ml-agents/mlagents/trainers/torch/distributions.py b/ml-agents/mlagents/trainers/torch/distributions.py index 00aee3aaa0..c60426c998 100644 --- a/ml-agents/mlagents/trainers/torch/distributions.py +++ b/ml-agents/mlagents/trainers/torch/distributions.py @@ -124,7 +124,7 @@ def sample(self): return torch.multinomial(self.probs, 1) def deterministic_sample(self): - return torch.argmax(self.probs).reshape((1, 1)) + return torch.argmax(self.probs, dim=1, keepdim=True) def pdf(self, value): # This function is equivalent to torch.diag(self.probs.T[value.flatten().long()]), From b047e5d84ad237e03ab9ee6f642c4095fe19a4ce Mon Sep 17 00:00:00 2001 From: mahon94 Date: Thu, 28 Oct 2021 12:38:42 -0700 Subject: [PATCH 03/15] clean up --- ml-agents/mlagents/trainers/torch/action_model.py | 8 -------- ml-agents/mlagents/trainers/torch/model_serialization.py | 1 - ml-agents/mlagents/trainers/torch/networks.py | 4 ---- 3 files changed, 13 deletions(-) diff --git a/ml-agents/mlagents/trainers/torch/action_model.py b/ml-agents/mlagents/trainers/torch/action_model.py index a27b1ffdef..7eb1fae0ef 100644 --- a/ml-agents/mlagents/trainers/torch/action_model.py +++ b/ml-agents/mlagents/trainers/torch/action_model.py @@ -9,9 +9,6 @@ from mlagents.trainers.torch.agent_action import AgentAction from mlagents.trainers.torch.action_log_probs import ActionLogProbs from mlagents_envs.base_env import ActionSpec -from mlagents_envs import logging_util - -logger = logging_util.get_logger(__name__) EPSILON = 1e-7 # Small value to avoid divide by zero @@ -175,20 +172,15 @@ def get_action_out(self, inputs: torch.Tensor, masks: torch.Tensor) -> torch.Ten action_out_deprecated = continuous_out deter_continuous_out = torch.clamp(deter_continuous_out, -3, 3) / 3 if self.action_spec.discrete_size > 0 and dists.discrete is not None: - logger.info( - f"dist: {[discrete_dist.probs for discrete_dist in dists.discrete]}" - ) # TODO: remove discrete_out_list = [ discrete_dist.exported_model_output() for discrete_dist in dists.discrete ] - logger.info(f"discretelist {discrete_out_list}") # TODO: remove discrete_out = torch.cat(discrete_out_list, dim=1) action_out_deprecated = torch.cat(discrete_out_list, dim=1) deter_discrete_out_list = [ discrete_dist.deterministic_sample() for discrete_dist in dists.discrete ] - logger.info(f"deterlist {deter_discrete_out_list}") # TODO: remove deter_discrete_out = torch.cat(deter_discrete_out_list, dim=1) # deprecated action field does not support hybrid action diff --git a/ml-agents/mlagents/trainers/torch/model_serialization.py b/ml-agents/mlagents/trainers/torch/model_serialization.py index 6924f3a5be..f204b52445 100644 --- a/ml-agents/mlagents/trainers/torch/model_serialization.py +++ b/ml-agents/mlagents/trainers/torch/model_serialization.py @@ -169,6 +169,5 @@ def export_policy_model(self, output_filepath: str) -> None: input_names=self.input_names, output_names=self.output_names, dynamic_axes=self.dynamic_axes, - verbose=True, # TODO: remove ) logger.info(f"Exported {onnx_output_path}") diff --git a/ml-agents/mlagents/trainers/torch/networks.py b/ml-agents/mlagents/trainers/torch/networks.py index a79f5238f0..b041148c24 100644 --- a/ml-agents/mlagents/trainers/torch/networks.py +++ b/ml-agents/mlagents/trainers/torch/networks.py @@ -571,9 +571,6 @@ def forward( class SimpleActor(nn.Module, Actor): MODEL_EXPORT_VERSION = 3 # Corresponds to ModelApiVersion.MLAgents2_0 - is_stochastic_action_sampling = ( - True - ) # TODO: this should be a user input both for training and inference def __init__( self, @@ -585,7 +582,6 @@ def __init__( ): super().__init__() self.action_spec = action_spec - # self.is_continuous_int_deprecated = is_stochastic_action_sampling # TODO: self.version_number = torch.nn.Parameter( torch.Tensor([self.MODEL_EXPORT_VERSION]), requires_grad=False ) From 085e56e4836cf94bdde1e5e3befa1307c2097509 Mon Sep 17 00:00:00 2001 From: mahon94 Date: Thu, 28 Oct 2021 12:50:23 -0700 Subject: [PATCH 04/15] changelog --- com.unity.ml-agents/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.ml-agents/CHANGELOG.md b/com.unity.ml-agents/CHANGELOG.md index dab7cb200f..4af61bc356 100755 --- a/com.unity.ml-agents/CHANGELOG.md +++ b/com.unity.ml-agents/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to 1. env_params.max_lifetime_restarts (--max-lifetime-restarts) [default=10] 2. env_params.restarts_rate_limit_n (--restarts-rate-limit-n) [default=1] 3. env_params.restarts_rate_limit_period_s (--restarts-rate-limit-period-s) [default=60] +- Extra tensors are now serialized to support deterministic action selection in onnx. (#5597) ### Bug Fixes - Fixed the bug where curriculum learning would crash because of the incorrect run_options parsing. (#5586) From fb7849f6cc841b2e831eafc002310ef0b9c9acac Mon Sep 17 00:00:00 2001 From: mahon94 Date: Tue, 2 Nov 2021 16:19:08 -0700 Subject: [PATCH 05/15] Renaming --- .../mlagents/trainers/torch/action_model.py | 21 ++++++++++++------- ml-agents/mlagents/trainers/torch/networks.py | 8 +++---- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/ml-agents/mlagents/trainers/torch/action_model.py b/ml-agents/mlagents/trainers/torch/action_model.py index 7eb1fae0ef..e8c6577e92 100644 --- a/ml-agents/mlagents/trainers/torch/action_model.py +++ b/ml-agents/mlagents/trainers/torch/action_model.py @@ -162,15 +162,20 @@ def get_action_out(self, inputs: torch.Tensor, masks: torch.Tensor) -> torch.Ten """ dists = self._get_dists(inputs, masks) continuous_out, discrete_out, action_out_deprecated = None, None, None - deter_continuous_out, deter_discrete_out = None, None # deterministic actions + deterministic_continuous_out, deterministic_discrete_out = ( + None, + None, + ) # deterministic actions if self.action_spec.continuous_size > 0 and dists.continuous is not None: continuous_out = dists.continuous.exported_model_output() action_out_deprecated = continuous_out - deter_continuous_out = dists.continuous.deterministic_sample() + deterministic_continuous_out = dists.continuous.deterministic_sample() if self._clip_action_on_export: continuous_out = torch.clamp(continuous_out, -3, 3) / 3 action_out_deprecated = continuous_out - deter_continuous_out = torch.clamp(deter_continuous_out, -3, 3) / 3 + deterministic_continuous_out = ( + torch.clamp(deterministic_continuous_out, -3, 3) / 3 + ) if self.action_spec.discrete_size > 0 and dists.discrete is not None: discrete_out_list = [ discrete_dist.exported_model_output() @@ -178,10 +183,12 @@ def get_action_out(self, inputs: torch.Tensor, masks: torch.Tensor) -> torch.Ten ] discrete_out = torch.cat(discrete_out_list, dim=1) action_out_deprecated = torch.cat(discrete_out_list, dim=1) - deter_discrete_out_list = [ + deterministic_discrete_out_list = [ discrete_dist.deterministic_sample() for discrete_dist in dists.discrete ] - deter_discrete_out = torch.cat(deter_discrete_out_list, dim=1) + deterministic_discrete_out = torch.cat( + deterministic_discrete_out_list, dim=1 + ) # deprecated action field does not support hybrid action if self.action_spec.continuous_size > 0 and self.action_spec.discrete_size > 0: @@ -190,8 +197,8 @@ def get_action_out(self, inputs: torch.Tensor, masks: torch.Tensor) -> torch.Ten continuous_out, discrete_out, action_out_deprecated, - deter_continuous_out, - deter_discrete_out, + deterministic_continuous_out, + deterministic_discrete_out, ) def forward( diff --git a/ml-agents/mlagents/trainers/torch/networks.py b/ml-agents/mlagents/trainers/torch/networks.py index b041148c24..7e94be9133 100644 --- a/ml-agents/mlagents/trainers/torch/networks.py +++ b/ml-agents/mlagents/trainers/torch/networks.py @@ -675,21 +675,21 @@ def forward( cont_action_out, disc_action_out, action_out_deprecated, - deter_cont_action_out, - deter_disc_action_out, + deterministic_cont_action_out, + deterministic_disc_action_out, ) = self.action_model.get_action_out(encoding, masks) export_out = [self.version_number, self.memory_size_vector] if self.action_spec.continuous_size > 0: export_out += [ cont_action_out, self.continuous_act_size_vector, - deter_cont_action_out, + deterministic_cont_action_out, ] if self.action_spec.discrete_size > 0: export_out += [ disc_action_out, self.discrete_act_size_vector, - deter_disc_action_out, + deterministic_disc_action_out, ] if self.network_body.memory_size > 0: export_out += [memories_out] From afa4d8375d6d165c41bf2c319a87fa135828f5ac Mon Sep 17 00:00:00 2001 From: mahon94 Date: Wed, 3 Nov 2021 09:22:24 -0700 Subject: [PATCH 06/15] Add more tests --- .../trainers/tests/torch/test_action_model.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/ml-agents/mlagents/trainers/tests/torch/test_action_model.py b/ml-agents/mlagents/trainers/tests/torch/test_action_model.py index 9722931446..facd612755 100644 --- a/ml-agents/mlagents/trainers/tests/torch/test_action_model.py +++ b/ml-agents/mlagents/trainers/tests/torch/test_action_model.py @@ -79,3 +79,36 @@ def test_get_probs_and_entropy(): for ent, val in zip(entropies[0].tolist(), [1.4189, 0.6191, 0.6191]): assert ent == pytest.approx(val, abs=0.01) + + +def test_get_onnx_deterministic_tensors(): + inp_size = 4 + act_size = 2 + action_model, masks = create_action_model(inp_size, act_size) + sample_inp = torch.ones((1, inp_size)) + out_tensors = action_model.get_action_out(sample_inp, masks=masks) + ( + continuous_out, + discrete_out, + action_out_deprecated, + deterministic_continuous_out, + deterministic_discrete_out, + ) = out_tensors + assert continuous_out.shape == (1, 2) + assert discrete_out.shape == (1, 2) + assert deterministic_discrete_out.shape == (1, 2) + assert deterministic_continuous_out.shape == (1, 2) + + # Second sampling from same distribution + out_tensors2 = action_model.get_action_out(sample_inp, masks=masks) + ( + continuous_out_2, + discrete_out_2, + action_out_2_deprecated, + deterministic_continuous_out_2, + deterministic_discrete_out_2, + ) = out_tensors2 + assert ~torch.all(torch.eq(continuous_out, continuous_out_2)) + assert torch.all( + torch.eq(deterministic_continuous_out, deterministic_continuous_out_2) + ) From 60e4206ef25db4a180e63a8fb5d883065f2f80ae Mon Sep 17 00:00:00 2001 From: mahon94 Date: Fri, 29 Oct 2021 10:52:43 -0700 Subject: [PATCH 07/15] set stochasticInference in package --- com.unity.ml-agents/Runtime/Academy.cs | 6 +- .../Inference/BarracudaModelExtensions.cs | 106 +++++++++++++----- .../Inference/BarracudaModelParamLoader.cs | 17 ++- .../Runtime/Inference/ModelRunner.cs | 16 ++- .../Runtime/Inference/TensorApplier.cs | 9 +- .../Runtime/Inference/TensorGenerator.cs | 13 ++- .../Runtime/Inference/TensorNames.cs | 2 + .../Runtime/Policies/BarracudaPolicy.cs | 14 ++- .../Runtime/Policies/BehaviorParameters.cs | 19 +++- .../Tests/Runtime/RuntimeAPITest.cs | 1 + 10 files changed, 154 insertions(+), 49 deletions(-) diff --git a/com.unity.ml-agents/Runtime/Academy.cs b/com.unity.ml-agents/Runtime/Academy.cs index 85cab21b26..93993051b1 100644 --- a/com.unity.ml-agents/Runtime/Academy.cs +++ b/com.unity.ml-agents/Runtime/Academy.cs @@ -616,14 +616,16 @@ void EnvironmentReset() /// /// The inference device (CPU or GPU) the ModelRunner will use. /// + /// Inference only: set to true if the action selection from model should be + /// stochastic. /// The ModelRunner compatible with the input settings. internal ModelRunner GetOrCreateModelRunner( - NNModel model, ActionSpec actionSpec, InferenceDevice inferenceDevice) + NNModel model, ActionSpec actionSpec, InferenceDevice inferenceDevice, bool stochasticInference = true) { var modelRunner = m_ModelRunners.Find(x => x.HasModel(model, inferenceDevice)); if (modelRunner == null) { - modelRunner = new ModelRunner(model, actionSpec, inferenceDevice, m_InferenceSeed); + modelRunner = new ModelRunner(model, actionSpec, inferenceDevice, m_InferenceSeed, stochasticInference); m_ModelRunners.Add(modelRunner); m_InferenceSeed++; } diff --git a/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs b/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs index 6fd3872535..a596888c30 100644 --- a/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs +++ b/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Unity.Barracuda; +using UnityEngine; using FailedCheck = Unity.MLAgents.Inference.BarracudaModelParamLoader.FailedCheck; namespace Unity.MLAgents.Inference @@ -11,6 +12,8 @@ namespace Unity.MLAgents.Inference /// internal static class BarracudaModelExtensions { + + /// /// Get array of the input tensor names of the model. /// @@ -112,8 +115,10 @@ public static int GetNumVisualInputs(this Model model) /// /// The Barracuda engine model for loading static parameters. /// + /// Inference only: set to true if the action selection from model should be + /// stochastic. /// Array of the output tensor names of the model - public static string[] GetOutputNames(this Model model) + public static string[] GetOutputNames(this Model model, bool stochasticInference = true) { var names = new List(); @@ -124,11 +129,11 @@ public static string[] GetOutputNames(this Model model) if (model.HasContinuousOutputs()) { - names.Add(model.ContinuousOutputName()); + names.Add(model.ContinuousOutputName(stochasticInference)); } if (model.HasDiscreteOutputs()) { - names.Add(model.DiscreteOutputName()); + names.Add(model.DiscreteOutputName(stochasticInference)); } var modelVersion = model.GetVersion(); @@ -149,8 +154,10 @@ public static string[] GetOutputNames(this Model model) /// /// The Barracuda engine model for loading static parameters. /// + /// Inference only: set to true if the action selection from model should be + /// stochastic. /// True if the model has continuous action outputs. - public static bool HasContinuousOutputs(this Model model) + public static bool HasContinuousOutputs(this Model model, bool stochasticInference = true) { if (model == null) return false; @@ -160,8 +167,13 @@ public static bool HasContinuousOutputs(this Model model) } else { - return model.outputs.Contains(TensorNames.ContinuousActionOutput) && - (int)model.GetTensorByName(TensorNames.ContinuousActionOutputShape)[0] > 0; + bool hasStochasticOutput = stochasticInference && + model.outputs.Contains(TensorNames.ContinuousActionOutput); + bool hasDeterministicOutput = !stochasticInference && + model.outputs.Contains(TensorNames.DeterministicContinuousActionOutput); + + return (hasStochasticOutput || hasDeterministicOutput) && + (int)model.GetTensorByName(TensorNames.ContinuousActionOutputShape)[0] > 0; } } @@ -194,8 +206,10 @@ public static int ContinuousOutputSize(this Model model) /// /// The Barracuda engine model for loading static parameters. /// + /// Inference only: set to true if the action selection from model should be + /// stochastic. /// Tensor name of continuous action output. - public static string ContinuousOutputName(this Model model) + public static string ContinuousOutputName(this Model model, bool stochasticInference = true) { if (model == null) return null; @@ -205,7 +219,7 @@ public static string ContinuousOutputName(this Model model) } else { - return TensorNames.ContinuousActionOutput; + return stochasticInference ? TensorNames.ContinuousActionOutput : TensorNames.DeterministicContinuousActionOutput; } } @@ -215,8 +229,10 @@ public static string ContinuousOutputName(this Model model) /// /// The Barracuda engine model for loading static parameters. /// + /// Inference only: set to true if the action selection from model should be + /// stochastic. /// True if the model has discrete action outputs. - public static bool HasDiscreteOutputs(this Model model) + public static bool HasDiscreteOutputs(this Model model, bool stochasticInference = true) { if (model == null) return false; @@ -226,7 +242,12 @@ public static bool HasDiscreteOutputs(this Model model) } else { - return model.outputs.Contains(TensorNames.DiscreteActionOutput) && model.DiscreteOutputSize() > 0; + bool hasStochasticOutput = stochasticInference && + model.outputs.Contains(TensorNames.DiscreteActionOutput); + bool hasDeterministicOutput = !stochasticInference && + model.outputs.Contains(TensorNames.DeterministicDiscreteActionOutput); + return (hasStochasticOutput || hasDeterministicOutput) && + model.DiscreteOutputSize() > 0; } } @@ -279,8 +300,10 @@ public static int DiscreteOutputSize(this Model model) /// /// The Barracuda engine model for loading static parameters. /// + /// Inference only: set to true if the action selection from model should be + /// stochastic. /// Tensor name of discrete action output. - public static string DiscreteOutputName(this Model model) + public static string DiscreteOutputName(this Model model, bool stochasticInference = true) { if (model == null) return null; @@ -290,7 +313,7 @@ public static string DiscreteOutputName(this Model model) } else { - return TensorNames.DiscreteActionOutput; + return stochasticInference ? TensorNames.DiscreteActionOutput : TensorNames.DeterministicDiscreteActionOutput; } } @@ -316,9 +339,11 @@ public static bool SupportsContinuousAndDiscrete(this Model model) /// The Barracuda engine model for loading static parameters. /// /// Output list of failure messages - /// + /// Inference only: set to true if the action selection from model should be + /// stochastic. /// True if the model contains all the expected tensors. - public static bool CheckExpectedTensors(this Model model, List failedModelChecks) + /// TODO: add checks for deterministic actions + public static bool CheckExpectedTensors(this Model model, List failedModelChecks, bool stochasticInference = true) { // Check the presence of model version var modelApiVersionTensor = model.GetTensorByName(TensorNames.VersionNumber); @@ -343,7 +368,9 @@ public static bool CheckExpectedTensors(this Model model, List fail // Check the presence of action output tensor if (!model.outputs.Contains(TensorNames.ActionOutputDeprecated) && !model.outputs.Contains(TensorNames.ContinuousActionOutput) && - !model.outputs.Contains(TensorNames.DiscreteActionOutput)) + !model.outputs.Contains(TensorNames.DiscreteActionOutput) && + !model.outputs.Contains(TensorNames.DeterministicContinuousActionOutput) && + !model.outputs.Contains(TensorNames.DeterministicDiscreteActionOutput)) { failedModelChecks.Add( FailedCheck.Warning("The model does not contain any Action Output Node.") @@ -373,22 +400,51 @@ public static bool CheckExpectedTensors(this Model model, List fail } else { - if (model.outputs.Contains(TensorNames.ContinuousActionOutput) && - model.GetTensorByName(TensorNames.ContinuousActionOutputShape) == null) + if (model.outputs.Contains(TensorNames.ContinuousActionOutput)) { - failedModelChecks.Add( - FailedCheck.Warning("The model uses continuous action but does not contain Continuous Action Output Shape Node.") + if(model.GetTensorByName(TensorNames.ContinuousActionOutputShape) == null) + { + failedModelChecks.Add( + FailedCheck.Warning("The model uses continuous action but does not contain Continuous Action Output Shape Node.") ); - return false; + return false; + } + + else if (!model.HasContinuousOutputs(stochasticInference)) + { + var actionType = stochasticInference ? "stochastic" : "deterministic"; + var actionName = stochasticInference ? "" : "Deterministic"; + failedModelChecks.Add( + FailedCheck.Warning($"The model uses {actionType} inference but does not contain {actionName} Continuous Action Output Tensor. Set `Stochastic inference` accordingly.") + ); + return false; + } } - if (model.outputs.Contains(TensorNames.DiscreteActionOutput) && - model.GetTensorByName(TensorNames.DiscreteActionOutputShape) == null) + + if (model.outputs.Contains(TensorNames.DiscreteActionOutput)) { - failedModelChecks.Add( - FailedCheck.Warning("The model uses discrete action but does not contain Discrete Action Output Shape Node.") + if (model.GetTensorByName(TensorNames.DiscreteActionOutputShape) == null ) + { + failedModelChecks.Add( + FailedCheck.Warning("The model uses discrete action but does not contain Discrete Action Output Shape Node.") ); - return false; + return false; + } + else if (!model.HasDiscreteOutputs(stochasticInference)) + { + var actionType = stochasticInference ? "stochastic" : "deterministic"; + var actionName = stochasticInference ? "" : "Deterministic"; + failedModelChecks.Add( + FailedCheck.Warning($"The model uses {actionType} inference but does not contain {actionName} Discrete Action Output Tensor. Set `Stochastic inference` accordingly.") + ); + return false; + } + } + + + + } return true; } diff --git a/com.unity.ml-agents/Runtime/Inference/BarracudaModelParamLoader.cs b/com.unity.ml-agents/Runtime/Inference/BarracudaModelParamLoader.cs index 21917b303d..6752ffbc06 100644 --- a/com.unity.ml-agents/Runtime/Inference/BarracudaModelParamLoader.cs +++ b/com.unity.ml-agents/Runtime/Inference/BarracudaModelParamLoader.cs @@ -122,6 +122,8 @@ public static FailedCheck CheckModelVersion(Model model) /// Attached actuator components /// Sum of the sizes of all ObservableAttributes. /// BehaviorType or the Agent to check. + /// Inference only: set to true if the action selection from model should be + /// stochastic. /// A IEnumerable of the checks that failed public static IEnumerable CheckModel( Model model, @@ -129,7 +131,8 @@ public static IEnumerable CheckModel( ISensor[] sensors, ActuatorComponent[] actuatorComponents, int observableAttributeTotalSize = 0, - BehaviorType behaviorType = BehaviorType.Default + BehaviorType behaviorType = BehaviorType.Default, + bool stochasticInference = true ) { List failedModelChecks = new List(); @@ -148,7 +151,7 @@ public static IEnumerable CheckModel( return failedModelChecks; } - var hasExpectedTensors = model.CheckExpectedTensors(failedModelChecks); + var hasExpectedTensors = model.CheckExpectedTensors(failedModelChecks, stochasticInference); if (!hasExpectedTensors) { return failedModelChecks; @@ -195,7 +198,7 @@ public static IEnumerable CheckModel( ); failedModelChecks.AddRange( - CheckOutputTensorPresence(model, memorySize) + CheckOutputTensorPresence(model, memorySize, stochasticInference) ); return failedModelChecks; } @@ -376,17 +379,18 @@ ISensor[] sensors /// The Barracuda engine model for loading static parameters /// /// The memory size that the model is expecting/ + /// Inference only: set to true if the action selection from model should be + /// stochastic. /// /// A IEnumerable of the checks that failed /// - static IEnumerable CheckOutputTensorPresence(Model model, int memory) + static IEnumerable CheckOutputTensorPresence(Model model, int memory, bool stochasticInference = true) { var failedModelChecks = new List(); - // If there is no Recurrent Output but the model is Recurrent. if (memory > 0) { - var allOutputs = model.GetOutputNames().ToList(); + var allOutputs = model.GetOutputNames(stochasticInference).ToList(); if (!allOutputs.Any(x => x == TensorNames.RecurrentOutput)) { failedModelChecks.Add( @@ -395,6 +399,7 @@ static IEnumerable CheckOutputTensorPresence(Model model, int memor } } + return failedModelChecks; } diff --git a/com.unity.ml-agents/Runtime/Inference/ModelRunner.cs b/com.unity.ml-agents/Runtime/Inference/ModelRunner.cs index 422e0f9744..05d4fa2ef8 100644 --- a/com.unity.ml-agents/Runtime/Inference/ModelRunner.cs +++ b/com.unity.ml-agents/Runtime/Inference/ModelRunner.cs @@ -4,6 +4,7 @@ using Unity.MLAgents.Actuators; using Unity.MLAgents.Policies; using Unity.MLAgents.Sensors; +using UnityEngine; namespace Unity.MLAgents.Inference { @@ -28,6 +29,7 @@ internal class ModelRunner InferenceDevice m_InferenceDevice; IWorker m_Engine; bool m_Verbose = false; + bool m_stochasticInference; string[] m_OutputNames; IReadOnlyList m_InferenceInputs; List m_InferenceOutputs; @@ -48,18 +50,22 @@ internal class ModelRunner /// option for most of ML Agents models. /// The seed that will be used to initialize the RandomNormal /// and Multinomial objects used when running inference. + /// Inference only: set to true if the action selection from model should be + /// stochastic. /// Throws an error when the model is null /// public ModelRunner( NNModel model, ActionSpec actionSpec, InferenceDevice inferenceDevice, - int seed = 0) + int seed = 0, + bool stochasticInference = true) { Model barracudaModel; m_Model = model; m_ModelName = model.name; m_InferenceDevice = inferenceDevice; + m_stochasticInference = stochasticInference; m_TensorAllocator = new TensorCachingAllocator(); if (model != null) { @@ -108,11 +114,13 @@ public ModelRunner( } m_InferenceInputs = barracudaModel.GetInputTensors(); - m_OutputNames = barracudaModel.GetOutputNames(); + m_OutputNames = barracudaModel.GetOutputNames(m_stochasticInference); + Debug.Log("output actions:" + m_OutputNames[0]); + m_TensorGenerator = new TensorGenerator( - seed, m_TensorAllocator, m_Memories, barracudaModel); + seed, m_TensorAllocator, m_Memories, barracudaModel, m_stochasticInference); m_TensorApplier = new TensorApplier( - actionSpec, seed, m_TensorAllocator, m_Memories, barracudaModel); + actionSpec, seed, m_TensorAllocator, m_Memories, barracudaModel, m_stochasticInference); m_InputsByName = new Dictionary(); m_InferenceOutputs = new List(); } diff --git a/com.unity.ml-agents/Runtime/Inference/TensorApplier.cs b/com.unity.ml-agents/Runtime/Inference/TensorApplier.cs index d311010aae..9075c4a3c5 100644 --- a/com.unity.ml-agents/Runtime/Inference/TensorApplier.cs +++ b/com.unity.ml-agents/Runtime/Inference/TensorApplier.cs @@ -44,12 +44,15 @@ public interface IApplier /// Tensor allocator /// Dictionary of AgentInfo.id to memory used to pass to the inference model. /// + /// Inference only: set to true if the action selection from model should be + /// stochastic. public TensorApplier( ActionSpec actionSpec, int seed, ITensorAllocator allocator, Dictionary> memories, - object barracudaModel = null) + object barracudaModel = null, + bool stochasticInference = true) { // If model is null, no inference to run and exception is thrown before reaching here. if (barracudaModel == null) @@ -64,13 +67,13 @@ public TensorApplier( } if (actionSpec.NumContinuousActions > 0) { - var tensorName = model.ContinuousOutputName(); + var tensorName = model.ContinuousOutputName(stochasticInference); m_Dict[tensorName] = new ContinuousActionOutputApplier(actionSpec); } var modelVersion = model.GetVersion(); if (actionSpec.NumDiscreteActions > 0) { - var tensorName = model.DiscreteOutputName(); + var tensorName = model.DiscreteOutputName(stochasticInference); if (modelVersion == (int)BarracudaModelParamLoader.ModelApiVersion.MLAgents1_0) { m_Dict[tensorName] = new LegacyDiscreteActionOutputApplier(actionSpec, seed, allocator); diff --git a/com.unity.ml-agents/Runtime/Inference/TensorGenerator.cs b/com.unity.ml-agents/Runtime/Inference/TensorGenerator.cs index feb521ebd8..fe7f15fbe9 100644 --- a/com.unity.ml-agents/Runtime/Inference/TensorGenerator.cs +++ b/com.unity.ml-agents/Runtime/Inference/TensorGenerator.cs @@ -44,11 +44,14 @@ void Generate( /// Tensor allocator. /// Dictionary of AgentInfo.id to memory for use in the inference model. /// + /// Inference only: set to true if the action selection from model should be + /// stochastic. public TensorGenerator( int seed, ITensorAllocator allocator, Dictionary> memories, - object barracudaModel = null) + object barracudaModel = null, + bool stochasticInference = true) { // If model is null, no inference to run and exception is thrown before reaching here. if (barracudaModel == null) @@ -76,13 +79,13 @@ public TensorGenerator( // Generators for Outputs - if (model.HasContinuousOutputs()) + if (model.HasContinuousOutputs(stochasticInference)) { - m_Dict[model.ContinuousOutputName()] = new BiDimensionalOutputGenerator(allocator); + m_Dict[model.ContinuousOutputName(stochasticInference)] = new BiDimensionalOutputGenerator(allocator); } - if (model.HasDiscreteOutputs()) + if (model.HasDiscreteOutputs(stochasticInference)) { - m_Dict[model.DiscreteOutputName()] = new BiDimensionalOutputGenerator(allocator); + m_Dict[model.DiscreteOutputName(stochasticInference)] = new BiDimensionalOutputGenerator(allocator); } m_Dict[TensorNames.RecurrentOutput] = new BiDimensionalOutputGenerator(allocator); m_Dict[TensorNames.ValueEstimateOutput] = new BiDimensionalOutputGenerator(allocator); diff --git a/com.unity.ml-agents/Runtime/Inference/TensorNames.cs b/com.unity.ml-agents/Runtime/Inference/TensorNames.cs index dc20e1f8f3..48ae04b5f6 100644 --- a/com.unity.ml-agents/Runtime/Inference/TensorNames.cs +++ b/com.unity.ml-agents/Runtime/Inference/TensorNames.cs @@ -23,6 +23,8 @@ internal static class TensorNames public const string DiscreteActionOutputShape = "discrete_action_output_shape"; public const string ContinuousActionOutput = "continuous_actions"; public const string DiscreteActionOutput = "discrete_actions"; + public const string DeterministicContinuousActionOutput = "deterministic_continuous_actions"; + public const string DeterministicDiscreteActionOutput = "deterministic_discrete_actions"; // Deprecated TensorNames entries for backward compatibility public const string IsContinuousControlDeprecated = "is_continuous_control"; diff --git a/com.unity.ml-agents/Runtime/Policies/BarracudaPolicy.cs b/com.unity.ml-agents/Runtime/Policies/BarracudaPolicy.cs index 5e76084b20..db39fb159e 100644 --- a/com.unity.ml-agents/Runtime/Policies/BarracudaPolicy.cs +++ b/com.unity.ml-agents/Runtime/Policies/BarracudaPolicy.cs @@ -34,6 +34,7 @@ public enum InferenceDevice CPU = 3, } + /// /// The Barracuda Policy uses a Barracuda Model to make decisions at /// every step. It uses a ModelRunner that is shared across all @@ -45,6 +46,11 @@ internal class BarracudaPolicy : IPolicy ActionBuffers m_LastActionBuffer; int m_AgentId; + /// + /// TODO + /// + bool m_StochasticInference; + /// /// Sensor shapes for the associated Agents. All Agents must have the same shapes for their Sensors. @@ -73,19 +79,23 @@ internal class BarracudaPolicy : IPolicy /// The Neural Network to use. /// Which device Barracuda will run on. /// The name of the behavior. + /// Inference only: set to true if the action selection from model should be + /// stochastic. public BarracudaPolicy( ActionSpec actionSpec, IList actuators, NNModel model, InferenceDevice inferenceDevice, - string behaviorName + string behaviorName, + bool stochasticInference = true ) { - var modelRunner = Academy.Instance.GetOrCreateModelRunner(model, actionSpec, inferenceDevice); + var modelRunner = Academy.Instance.GetOrCreateModelRunner(model, actionSpec, inferenceDevice, stochasticInference); m_ModelRunner = modelRunner; m_BehaviorName = behaviorName; m_ActionSpec = actionSpec; m_Actuators = actuators; + m_StochasticInference = stochasticInference; } /// diff --git a/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs b/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs index ae05284d50..a7ba284ae1 100644 --- a/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs +++ b/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs @@ -122,6 +122,7 @@ public InferenceDevice InferenceDevice set { m_InferenceDevice = value; UpdateAgentPolicy(); } } + [HideInInspector, SerializeField] BehaviorType m_BehaviorType; @@ -177,6 +178,20 @@ public bool UseChildSensors set { m_UseChildSensors = value; } } + [HideInInspector] + [SerializeField] + [Tooltip("Set action selection to stochastic, Use only in inference mode")] + private bool m_stochasticInference = false; + + /// + /// Whether to select actions stochastically during inference from the provided neural network. + /// + public bool StochasticInference + { + get { return m_stochasticInference; } + set { m_stochasticInference = value; } + } + /// /// Whether or not to use all the actuator components attached to child GameObjects of the agent. /// Note that changing this after the Agent has been initialized will not have any effect. @@ -228,7 +243,7 @@ internal IPolicy GeneratePolicy(ActionSpec actionSpec, ActuatorManager actuatorM "Either assign a model, or change to a different Behavior Type." ); } - return new BarracudaPolicy(actionSpec, actuatorManager, m_Model, m_InferenceDevice, m_BehaviorName); + return new BarracudaPolicy(actionSpec, actuatorManager, m_Model, m_InferenceDevice, m_BehaviorName, m_stochasticInference); } case BehaviorType.Default: if (Academy.Instance.IsCommunicatorOn) @@ -237,7 +252,7 @@ internal IPolicy GeneratePolicy(ActionSpec actionSpec, ActuatorManager actuatorM } if (m_Model != null) { - return new BarracudaPolicy(actionSpec, actuatorManager, m_Model, m_InferenceDevice, m_BehaviorName); + return new BarracudaPolicy(actionSpec, actuatorManager, m_Model, m_InferenceDevice, m_BehaviorName, m_stochasticInference); } else { diff --git a/com.unity.ml-agents/Tests/Runtime/RuntimeAPITest.cs b/com.unity.ml-agents/Tests/Runtime/RuntimeAPITest.cs index 0c3b6312b4..4aa5cf9784 100644 --- a/com.unity.ml-agents/Tests/Runtime/RuntimeAPITest.cs +++ b/com.unity.ml-agents/Tests/Runtime/RuntimeAPITest.cs @@ -70,6 +70,7 @@ public IEnumerator RuntimeApiTestWithEnumeratorPasses() behaviorParams.BehaviorName = "TestBehavior"; behaviorParams.TeamId = 42; behaviorParams.UseChildSensors = true; + behaviorParams.StochasticInference = true; behaviorParams.ObservableAttributeHandling = ObservableAttributeOptions.ExamineAll; From ec953508eef8612dfeece17d544e4f0f06804f78 Mon Sep 17 00:00:00 2001 From: mahon94 Date: Tue, 2 Nov 2021 06:26:08 -0700 Subject: [PATCH 08/15] Add test and editor flag - Add tests for deterministic sampling - update editor and tooltips --- .../Editor/BehaviorParametersEditor.cs | 4 +- .../Inference/BarracudaModelExtensions.cs | 4 +- .../Tests/Editor/Inference/ModelRunnerTest.cs | 82 ++++++++++++++++++ .../deterContinuous2vis8vec2action_v2_0.onnx | Bin 0 -> 73999 bytes ...erContinuous2vis8vec2action_v2_0.onnx.meta | 14 +++ .../deterDiscrete1obs3action_v2_0.onnx | Bin 0 -> 3715 bytes .../deterDiscrete1obs3action_v2_0.onnx.meta | 14 +++ 7 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 com.unity.ml-agents/Tests/Editor/TestModels/deterContinuous2vis8vec2action_v2_0.onnx create mode 100644 com.unity.ml-agents/Tests/Editor/TestModels/deterContinuous2vis8vec2action_v2_0.onnx.meta create mode 100644 com.unity.ml-agents/Tests/Editor/TestModels/deterDiscrete1obs3action_v2_0.onnx create mode 100644 com.unity.ml-agents/Tests/Editor/TestModels/deterDiscrete1obs3action_v2_0.onnx.meta diff --git a/com.unity.ml-agents/Editor/BehaviorParametersEditor.cs b/com.unity.ml-agents/Editor/BehaviorParametersEditor.cs index c5e8ddc802..19e456100c 100644 --- a/com.unity.ml-agents/Editor/BehaviorParametersEditor.cs +++ b/com.unity.ml-agents/Editor/BehaviorParametersEditor.cs @@ -25,6 +25,7 @@ internal class BehaviorParametersEditor : UnityEditor.Editor const string k_BrainParametersName = "m_BrainParameters"; const string k_ModelName = "m_Model"; const string k_InferenceDeviceName = "m_InferenceDevice"; + const string k_StochasticInference = "m_stochasticInference"; const string k_BehaviorTypeName = "m_BehaviorType"; const string k_TeamIdName = "TeamId"; const string k_UseChildSensorsName = "m_UseChildSensors"; @@ -68,6 +69,7 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(so.FindProperty(k_ModelName), true); EditorGUI.indentLevel++; EditorGUILayout.PropertyField(so.FindProperty(k_InferenceDeviceName), true); + EditorGUILayout.PropertyField(so.FindProperty(k_StochasticInference), true); EditorGUI.indentLevel--; } needPolicyUpdate = needPolicyUpdate || EditorGUI.EndChangeCheck(); @@ -156,7 +158,7 @@ void DisplayFailedModelChecks() { var failedChecks = Inference.BarracudaModelParamLoader.CheckModel( barracudaModel, brainParameters, sensors, actuatorComponents, - observableAttributeSensorTotalSize, behaviorParameters.BehaviorType + observableAttributeSensorTotalSize, behaviorParameters.BehaviorType, behaviorParameters.StochasticInference ); foreach (var check in failedChecks) { diff --git a/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs b/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs index a596888c30..d4ba781ddd 100644 --- a/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs +++ b/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs @@ -402,7 +402,7 @@ public static bool CheckExpectedTensors(this Model model, List fail { if (model.outputs.Contains(TensorNames.ContinuousActionOutput)) { - if(model.GetTensorByName(TensorNames.ContinuousActionOutputShape) == null) + if (model.GetTensorByName(TensorNames.ContinuousActionOutputShape) == null) { failedModelChecks.Add( FailedCheck.Warning("The model uses continuous action but does not contain Continuous Action Output Shape Node.") @@ -423,7 +423,7 @@ public static bool CheckExpectedTensors(this Model model, List fail if (model.outputs.Contains(TensorNames.DiscreteActionOutput)) { - if (model.GetTensorByName(TensorNames.DiscreteActionOutputShape) == null ) + if (model.GetTensorByName(TensorNames.DiscreteActionOutputShape) == null) { failedModelChecks.Add( FailedCheck.Warning("The model uses discrete action but does not contain Discrete Action Output Shape Node.") diff --git a/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs b/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs index 0e81c4f8ad..3eb3351382 100644 --- a/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs +++ b/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using NUnit.Framework; using UnityEngine; @@ -6,9 +7,31 @@ using Unity.MLAgents.Actuators; using Unity.MLAgents.Inference; using Unity.MLAgents.Policies; +using System.Collections; +using System.Collections.Generic; +using UnityEngine.Assertions.Comparers; namespace Unity.MLAgents.Tests { + public class FloatThresholdComparer : IEqualityComparer + { + private readonly float _threshold; + public FloatThresholdComparer(float threshold) + { + _threshold = threshold; + } + + public bool Equals(float x, float y) + { + return Math.Abs(x - y) < _threshold; + } + + public int GetHashCode(float f) + { + throw new NotImplementedException("Unable to generate a hash code for threshold floats, do not use this method"); + } + } + [TestFixture] public class ModelRunnerTest { @@ -19,6 +42,9 @@ public class ModelRunnerTest const string k_hybridONNXPath = "Packages/com.unity.ml-agents/Tests/Editor/TestModels/hybrid0vis53vec_3c_2daction_v1_0.onnx"; const string k_continuousNNPath = "Packages/com.unity.ml-agents/Tests/Editor/TestModels/continuous2vis8vec2action_deprecated_v1_0.nn"; const string k_discreteNNPath = "Packages/com.unity.ml-agents/Tests/Editor/TestModels/discrete1vis0vec_2_3action_recurr_deprecated_v1_0.nn"; + // models with deterministic action tensors + private const string k_deter_discreteNNPath = "Packages/com.unity.ml-agents/Tests/Editor/TestModels/deterDiscrete1obs3action_v2_0.onnx"; + private const string k_deter_continuousNNPath = "Packages/com.unity.ml-agents/Tests/Editor/TestModels/deterContinuous2vis8vec2action_v2_0.onnx"; NNModel hybridONNXModelV2; NNModel continuousONNXModel; @@ -26,6 +52,8 @@ public class ModelRunnerTest NNModel hybridONNXModel; NNModel continuousNNModel; NNModel discreteNNModel; + NNModel deterDiscreteNNModel; + NNModel deterContinuousNNModel; Test3DSensorComponent sensor_21_20_3; Test3DSensorComponent sensor_20_22_3; @@ -55,6 +83,8 @@ public void SetUp() hybridONNXModel = (NNModel)AssetDatabase.LoadAssetAtPath(k_hybridONNXPath, typeof(NNModel)); continuousNNModel = (NNModel)AssetDatabase.LoadAssetAtPath(k_continuousNNPath, typeof(NNModel)); discreteNNModel = (NNModel)AssetDatabase.LoadAssetAtPath(k_discreteNNPath, typeof(NNModel)); + deterDiscreteNNModel = (NNModel)AssetDatabase.LoadAssetAtPath(k_deter_discreteNNPath, typeof(NNModel)); + deterContinuousNNModel = (NNModel)AssetDatabase.LoadAssetAtPath(k_deter_continuousNNPath, typeof(NNModel)); var go = new GameObject("SensorA"); sensor_21_20_3 = go.AddComponent(); sensor_21_20_3.Sensor = new Test3DSensor("SensorA", 21, 20, 3); @@ -71,6 +101,8 @@ public void TestModelExist() Assert.IsNotNull(continuousNNModel); Assert.IsNotNull(discreteNNModel); Assert.IsNotNull(hybridONNXModelV2); + Assert.IsNotNull(deterDiscreteNNModel); + Assert.IsNotNull(deterContinuousNNModel); } [Test] @@ -99,6 +131,15 @@ public void TestCreation() // This one was trained with 2.0 so it should not raise an error: modelRunner = new ModelRunner(hybridONNXModelV2, new ActionSpec(2, new[] { 2, 3 }), inferenceDevice); modelRunner.Dispose(); + + // V2.0 Model that has serialized deterministic action tensors, discrete + modelRunner = new ModelRunner(deterDiscreteNNModel, new ActionSpec(0, new[] { 7 }), inferenceDevice); + modelRunner.Dispose(); + // V2.0 Model that has serialized deterministic action tensors, continuous + modelRunner = new ModelRunner(deterContinuousNNModel, + GetContinuous2vis8vec2actionActionSpec(), inferenceDevice, + stochasticInference: false); + modelRunner.Dispose(); } [Test] @@ -138,5 +179,46 @@ public void TestRunModel() Assert.AreEqual(actionSpec.NumDiscreteActions, modelRunner.GetAction(1).DiscreteActions.Length); modelRunner.Dispose(); } + + + [Test] + public void TestRunModel_deterministic() + { + var actionSpec = GetContinuous2vis8vec2actionActionSpec(); + var modelRunner = new ModelRunner(deterContinuousNNModel, actionSpec, InferenceDevice.Burst); + var sensor_8 = new Sensors.VectorSensor(8, "VectorSensor8"); + var info1 = new AgentInfo(); + var obs = new[] + { + sensor_8, + sensor_21_20_3.CreateSensors()[0], + sensor_20_22_3.CreateSensors()[0] + }.ToList(); + info1.episodeId = 1; + modelRunner.PutObservations(info1, obs); + modelRunner.DecideBatch(); + var stochAction1 = (float[])modelRunner.GetAction(1).ContinuousActions.Array.Clone(); + + modelRunner.PutObservations(info1, obs); + modelRunner.DecideBatch(); + var stochAction2 = (float[])modelRunner.GetAction(1).ContinuousActions.Array.Clone(); + // Stochastic action selection should output randomly different action values with same obs + Assert.IsFalse(Enumerable.SequenceEqual(stochAction1, stochAction2, new FloatThresholdComparer(0.001f))); + + + var deterModelRunner = new ModelRunner(deterContinuousNNModel, actionSpec, InferenceDevice.Burst, + stochasticInference: false); + info1.episodeId = 1; + deterModelRunner.PutObservations(info1, obs); + deterModelRunner.DecideBatch(); + var deterAction1 = (float[])deterModelRunner.GetAction(1).ContinuousActions.Array.Clone(); + + deterModelRunner.PutObservations(info1, obs); + deterModelRunner.DecideBatch(); + var deterAction2 = (float[])deterModelRunner.GetAction(1).ContinuousActions.Array.Clone(); + // Deterministic action selection should output same action everytime + Assert.IsTrue(Enumerable.SequenceEqual(deterAction1, deterAction2, new FloatThresholdComparer(0.001f))); + modelRunner.Dispose(); + } } } diff --git a/com.unity.ml-agents/Tests/Editor/TestModels/deterContinuous2vis8vec2action_v2_0.onnx b/com.unity.ml-agents/Tests/Editor/TestModels/deterContinuous2vis8vec2action_v2_0.onnx new file mode 100644 index 0000000000000000000000000000000000000000..56c1cd43559efe72121adcd439d51348661c4dbf GIT binary patch literal 73999 zcmb^Yc{rBO`#+9TqAb~xCE1q{WhrsbIX5j@Xx~#IRI+6&(jt_IBvC0+wvZN;;+}JE zrA;XnEhyUev{O=j@8|RN{v4n0`}_U<@&5dNzxz0@WA2%2=9=?yosT(Z=A85DFRLcE zIwmqS+;^o;Ut60o&fVu_;h=1hT(C|R7WubmCHj*;`@HIY>0ihvY z{vp1he*WP$tHVQm{Uahm!y{})+JuCL2m1sCtdo?7M}>q0gskui_V)=<>tpMr({Fav zGOv+FeI<#!mb|1yzR#$UYJKeNb^47DSmR}D)K`+oJIG5)B&7~Ivg3jRR(shQ$^4UA zD#`Z^_KDVzlFE0khB`5(|=rOh_6qim%WkffBso2$jkUd z2SjK|=S%mIFO-(l93^k_-(dXZl$TlNANjxYO7gOi;XWY|WB((j zu0+bwlIJ+;4456TA~-a_&uf&C+<*Q3=k3{zmRydK8e??&O^pij8g0~9lKcnWnExp} zJDdMAcy>1b1<&cf;5q*P0MALSkNwF14R6eUhbJM2y@VY0w*P_W^dER767AXB%8&5z z{g;Bl5`6{Pc=?8gLd$uM z|Cb1DC9>P=C`c$65$O{W>1Atc^ba%t%~%eS@3+P$D9Ycmzl!t}87avxUs^_9TY}9_ za!J^4FJb>bTD7(N4{kR}vBUoqc2MXFuLucgd?e2qr6cR==jUZ>Z`4IQ%J=h&j9Kmf zAF}=03G*s7`h-}L?;{YL1>{%c7jZ28BO{L_O3mV?88^*p;2~f27dmL{^7v^Are*v`2OE{_TT!m`;X~TQ~B43TCWO-wDyl)9U2~KsncIt zMSASOH4)lx1*#35CY~|HnvAF}Pb}@7!{Bic_P~HaInAyo>oR#8(%ko$nk= z=DfqvK53EE1tuZ;_KXRdtdQ=)j+Xc`#ie) z#!s66>NwS^(G$O1r6=}3yA1R%%FtVPF49EtS-vm(EDpUgN*L&Q47Zox;;s+MVZ=#q zZuQMjC|Z-q(-s@y1MMW9@Y|W*lRgZVb5255-yB%K-HB=p>qE_bHN-NLy{PUhfA$u@^FALWcw{Nk74bPN>vz$w5k9Q6Hpe{UGN=-ODWgES< zbsbM=(V=T<61bI05N$guOUJti>Bsf*VwVJax^RRN?b9vd!`Bt^u4rvuIdd=f)H4-& z{XN2Ojrz?$*wpgtkK35ipeJx)%K)LZ%XswnVyeGoAm6u6jh8P?K)=Y{JSOY~eGqt!3sFZruG1CXFF!|aI^Tt*E{-&M z{xh7azngcC?I2kT8aVt~%$FbkOCR@fpn;oosHbx)dlx7rUZ@{UzZ@;*zQyvwto{}B zioG#4k&Wk#%RkZR-4#?YbrufZ8uHxbLT@(edh939+)CO|0XCDvt z@`XnxMRa-UDHzayHD7dEM8}z_ac@02u4BB3=8XQaFmcl;1w>b7H(<9xKYmgLb8eC1S7VO25iDvYUXBE%7 zaRRR2-h-NgJf3G_A`Bj5jiT?K%sx{=ctUnR&n?`?Lmy3Nzg0E)&7weBH~%VieAq@U z^IG|#4GK^lqK+cQaHLo3+lO>qFEq|M&o5My$2) zPtFMOt%@$^A47N1edj9Ju+DwF)NV30s~AJZjR=6YxRrduJsV;EXea8H!np2gJvhK) zQTeYI-~Pf4q9(M^KPtodqxLt%O5+)Q^IJ_=U^P>CaIqSenyx zX$6@iYi>EH@2Me0$JK>*4fOCs!DJdywVVIqgrDjD#jU?~@Yyog`P4u4bm`^?_&QRZ zq+W~VqFL>9z}qSw{qYt5U_73VcxgvJoZm`ade5P6;6R}k?JJ%Yl}Lw`2zf`B9SvJN zmFI1&lE`QxR9Jn6ciwgq&MJA&Pd`iG@qR;uKN{!Ilv9+t?S9I`*fZ3&Iz@9}wm7!N zQ0T5Rmydluj{XhEgrJo}X-7>LfA%Mkzw$fIr#6_w`id-?`{)S0a?gs!Mh_DY8MH^> zuze7n@TC@tn2NCWK?BC5_UBO@8@QXl1NSTQq%Ond#D*(J@tf1*=;kwraoW|Z^nB|? z;Tx|^o}<1Res!HE7DHu)M^%gQ{yazF;~x!l!?6(VxL^)d+B6qWstAOSS8e95kE^&z zur>~8yZ~z#Kja?HzaUJ*nXhWiq#Fw_(fuKsG-~ZHu`4#UbKW|Ce<8jHT3Z(@j3{t2TA_+=g|^A@tE)FWz)uH8)e)#n+tZp~ejDn!iJ{{0TOr{1B-hwS*XJy*H$ncbqOTRY)!^#yA2${if1 zSyTB;Aw8q|6Ah;%(x$g+R9{_@mTXDq*PHa{u|P9%Z@0Smjb;xos@o$_Om`6LzfPr= zldQ#OhP~p$=jrja+aKZOJ%{MNsYj_U`_6TK-=L?jPN3Fg-K&n&O09~BiGrlXXfpxU`g2pwT6{;>KD+u%K1>^!R$Z;M`2wH5aud_y0ht6C9{ zS~XtWLb9lR_)+SA_yg6HDI_?_4%+rhiS>m(_~vyp&K6nWn~g5Q4>Db}F@Km)Vfq0& zr8u02V+?<`;U+IzIGjG}K1?-l#M24JwQT#%J@kToC!b$H#FaM~GyP%A*L%K0!M03l zKh{d@uDYL@%cu&cIS_U-#g?9tE~&|G84Y$*2hcCKn?bq*`I1{8ynFi*4g5HdCq5s? z&z(~h>aV%WwH#IpcN)zXI~>rZ@;5HhHL0b1V&(#VqGcSBdL}E3y*2~Bp3|n2=GXAR z!yCjiO%Bp+dw;{T{X)9?)jQrW%9d(smlO8~72LGHvG|zYWbuF*X2MR@|7dp zzJPDO)kYnzEyCTkPTWqZf$r9H;|;>g^rhl7dLU;6{l2~iE3d@yM3*{VEIolA-gTa< zDO})pH_V5CHP3i!`EmYZ%Q|kqDTN!}%A#{MuTkBxS9t0ac^tY$Pq;nULoDm7Lwln2 zgpUe(aC9LT6M zyRpj8N&HRlkso+u1`Ud{QKdzXFKD}pe}A{~;+!_R{f#<364r{b{3MTW(S$`$mqA8p zB9_}!LuB>~nl|kOp1kfUY*u`LI(kv8Wc&^osOHPx=^D{Vr}b%UyN~!qj5GiBy@Q$+ zOcQ6OpP}u))P+j!v#3egTc)?Gh7zT*{DjU?>eD`i8)bgsdq=9$#;JtvRDVehpYW%! zV!Uu&_;uVLb9U;ZnrjauJ-1P!lug7=&|l6g3bel5+#lV3ivu(ywC zi~1%uzx@z;d)4FIlO^p*RG54O6!I$+vLpwTEghg}>y`{gA%J}W1HJ7%~Uy;VR`Q0u!veF-8!xB)>vz$8q z)DQ=#jKS9;Q?Z=jEM*JEh_9p%6qoGj<(ZrTv}8S9BGKbPGgIM&tOb0# zp+08HTJb?)Td2PNXee|0j`j8PgiAajU$(!#VlMKp5MYN_j z1zrmXuC%#J8+(IcPf!89f54m`4_D&Tp6$e;3mRDNa8F^!7fou6eRz!5TKZABn`Z7) z7HXH+VT)HDJDC2P-fg-`J2Y}>(;Rh{Johfk?;0yKmb;3P?=twsCn~T(6hTc^0pBzF zJEZ}AxWxi#aUZ=(PDbv9;QQ0WJzPYMHHzt{f}?c6;CR{`??TVc{)APM`S0X_wcPP= z6AAmHg6_xvP~Cp%T+#Lfo)1$MPZ%_eo9RvE7oBVP7TId9-anqDC>-M-6g-7Sjml!- zq8e&lrpBMNw(>i>rcqqk2ZyfjW;%QJ)A$J*;=-a%u+}-pk4x4-_OeUqwn9B&Ow4&Y zqxlF$ua|t*cWs7l1BHpJyx6AZSKO~jo?9tp(c$m)=ohadA)ay+cU-(nBex$S?jiC* z)$o_FLhCyHH2Ea`sU0(g4I8Jc@aR#?!R z#0`bIbly`BacuKY`nT&E%6s({UQiw){;c_juQ>dT-@0icoEsu1{&M{wH`$X#gY#_p z**?msa#KY(ufK&bsWOues$0jtjvIg{{x}Ff^tkdj9xfmpb{#FNWF*c5iMQha$d@`L zPNwHt7@OFUg7@ZZ!ozn;P}yTL-u5`fMr)elg3PVhKWq{%=r+TM7f(rHcO>y<9|Yym zKiHL5sw7x^i;QxY#icKHV8JSRJf58hyL}bVbJI%f6&`1I6P%cFdkImqOLb1Lu3|lE z<1kr}Ne0PPjL4Lg&jye5IQDR;0TS?|oUD5Z+ zLosN<3%2gwNFv{>k82JYp^ua)ev3aWxX|@eRF#my>|J-TvI8>cb2$;F4?D(Izuth; z&lZb%XGS`=G)yMa!gA*GC7Wr!k0H-ZV@dG1VR*XoGmc6rCKkj4&A!RvhykDQrtcD# z_cjfGe6YiPZ?D$uimoOe@1kL(uN!+QcbxsOt#*#Mt%eJ{Hlhm+5GeB|e3y2B?2FJQ zM=o@+Dq#_uvE6`7%=HvlvbcYk^B?m35B*O6%d0%HqY&rx*$&gI52Jt0RBVe> z!}mQUsQWbpRxTSvH9AM~u~YYurcIZ@Z6f06phF;9{(>0%oWqCtAHm0SWw>YlX7YWt zGZ|m(2=1|ixW@VK;J5V&QE!q=^d(}9X!H_kZ88V5@c?1c^GW?C1)eQ91EDV(L{;^> zNL>b@VHa9)u$4NmDk+BOl6Wr6EJk*II*yugAFt;We5OL%#t03N@TqW9vOnRej`(TBS#bl#QUc(~;($Y1beO2hV&XF5WZ zouJN*awS|In~L72vT^_L5Z3;w7j?$S@<{(-w9}#wwzLl6JLYYx>DnQWhM((EIa!qq z4%4D78>&$;ZwMxq7}EgVIeetmIgGCD6^+v>WtOo$xIuOs{QbC?&-vL#+T-i-$!TL8 zYH$<-F7L$HHZ2&OYzaFOK9WUkNkp+%8kv(D`Eip5`o>nkZZ~f{($k0^ zXXXhCE%M3bpkADBH3x^a1hVM5NqmHFJP6K>fvO#+V9ugJ;QP3O_wF4+D%G+;P4)vM zCN5xW|HMLjoCIU~NXWj^O)jdyff&6VZ618ZQ~}nWTv>qwH-5&;=b11L@FOH#}1N0T=0Nz*-rT7l3=c21Qr*O7`1>G8`iPdXf3CyZpSjaSgnDj9d zbFMsM=O3wa+jz#*9#^q%i)G;8x=X~;HIH3e#;_ti4c}kvi>CgYnV(N4v1*vYN0~e! zIl=qb!t)9|WWGADTs{k10}bfB0f#YMQ<3L7M?mT{HBoMCF0$|dx@&V39IX!og%nqA ze&Z-^z26rs77oVeNjeaGH=P{qszi@Vuh=W&nIt1rkCqD-VbTC2zGd_djIi7bqsMvS z)z^wN9hXA#z+-6jUJvg2lrY}`GBpLCf53|&_RholUxJHI)j=~^nuOep2kiqJuy=(y z&A+0{Gsa2LS(fkMx$JJzv~Vylx>3tYevRQ#X2~$UG6fzbS>XPrcKEY@Bbi=x3ABb7 zfLc>5QCxWjZf;J+{80nZTK5QC>=}(w&lqGpAIa~VKZFV9zi?HQCtiN9$)j8{@lkDm z+VAKHI&B~zF8G87?!9QN`WoKk2jVh~y<@*X^^_W&{`DSaIUf~Wy(L3sQl|3(Kev#gU5hc|qBID%jDw^2A?09JdJN39B@R71MVNEa1mE78gcq-6 z!F;+BGR03s&gOe?(t;#``qWyqn_3IAo=Thx@#XmJy(XnIci@iXc=$Rt2rhn?!b9_Z zpjMPS%pM`b`+n$#75cSs^|~sbqZUUpPpe?Qb}Go4Btd3tFgD5#m24ojxuQ}KlU_R! zP3=oWEnBCt_p7F{oC{O=H=9iUY3_HNX*GcQOz42D3HP9EekqRi{6&6a4RP;l1(TB8 zVD;BSklf|Ry=O0k)O=NXyTKAQ9{<3a``LJB$|@F=Y*`a_$BWEodPy1(MS=1q16rGCZlh2dG=L^Zz?WaL1-<;I*$=}cTW=OkMnYzhO9N20aQ1CnIf zOXl@Uf-eWOpk#L($=UN17{nsk`_HAr8k1~Z23#jJy# zFlXu;TsD6!)ETA`vlaJ<%2!3YvT8g@Y91)qUeXs5B@WAGZD$?`dvMssP;4DG0>zQq z*iSDN8h#(eWxZ)2S=7S&iS2@GQ;)&MUCUtI>g|v$r@$*_Hxl9SVyFMlS-H~ej9xt=TWj@mDx zlO9LxmaaxqxiWI#ST3$j9!;MHucsmdYZ`s$KF;#CgVOlhV0WVxtDgt&a48o-i#e$M`TVcb!?@mvz+R^2Impk`L32AF@3?H#spRQl*!gs`* z;-TkuXq`KRx)yr0evA*cba!Lm25Y{0hcaEdjp3o4I)d{f`ce%GJy_$% zV5U_#>3K5_;u0w?TD%Op`sFoQ}GIri!1M1GX3BxWs zLE%v~df&Mb?1Q4w;I;|*{YZ)rUfm2g3l!*z;(Qn;y#N)y^bl*C#pHp_E|!y<2Fd$# zAyz%I=HS{Ff<+Y>D6Ue&5cy-yFO1)l`VH&BW9k}|4sxTHoeN1~p%vL~)C(GAS0Oau zI^?~Yjt}LN;azw<`SncVAj-N2^IInpY{j3> zr$zjI5JUyd!@51CAg!JP)7$&=ODT2`^q~s+<{d-*--+n9*BZ3+<=DCWLu~3BOZ?Sa z$qY}~@;B3NLxO`Y*Pj>4=cakVuNi8*Fx#JHI_0C4a}2tS6(R(GB`&_Ts5Z$Eo^`ik zt<)EosSt{hJ?5CY(GlKzZ^7$fquIJYM=(?{^S;4>(@Mv-R? zyn$`sQ=!w!80QztlEb3uvoOLZ7>+#a7Fh*nGMjxXctdvwvI~89>#i+e ztT&$NP0n?$DxJwcE-|E5wH3H?dMB(ee1{QQ3S2eg0ch^c2Z`4S+Xt+KTVszxz!PnJ zHS-*K8)k?WtLE{reG_q^mJvN1_5vpi%m$fGGa8e;1m;}dR{h5K4O@T|lIN#kQT1tTy0n=4N7!NP zo=4j8<%_9M0`H^5cKo);WZ;o;_lKi}fbMy3aX&4+ZT(mMy9*UacX zPbHr9sRY(HsMDCWVl3eIP<`BIcs_0udw4DnUY<)L+eSs;5sy6#hOc1TJTr-4x(MoR zUGey&2EieZJaQ~eip6JkvWveuA#YtP*bj^ZHf{nO)GOhi(h@=UPXTj$cM!WBkHP#v zM?UXY42X|aV$B~LOkMj?^h%~1x)UF)?+DfIfOBl*lEZV^v8g7uqLy`qi3!PAU{vsGz z@Ren}QpeuT0!Ut#50jjO!Q`w89sjNXZ?EXVz3YsKQhPuAtlS84MMco8(;}D>QjOA^ zD#0YL9L?jtkXcg%&w3@UR>yy%P{6&BCN&kQkbCeWVl-8R)Y?IOc>h317(W7EjP4R$&Q2p|HZ$OJU7&IM8vIhOi`voW zu>68N-D{eO{l}cg6kjbM5noW?rw^hL9jyJwOQ_jd$QHlO!O33JP(xBr`)?z@ePIWI z1@ibH?-iupj0WXL*KoAsdb0kA98=J(CHpt{!uS_gu~7N|Y%Ov?`K1f6a`Gk^qZx$p z{)9BR8nDMov*4<)DoG2w$8JV_uPJh^MTg6p^y+|29Pq6GPg!Nd!f(zv>D_*TgCzPU zPob80ZrqMs=Q-}(G#LC}KPGRt-oxotMYy@FM07V=n)}``!^?P{z2G}RDc1w0UvYH4 z5UGNjr{u$uW+m>EJBlm6uY$VO^^k8pS@5KOER<~D0VD2)fLC@0QilY{d)@~#ofhGc z)Ri?A1EbD_lxvM}D-UY5XWGwsMA_ zKjY!{8Waqum&fwA7r{{f2kgAu50iy=*^X0|s4{R8d6J!g!*=Epxi5Wq{K&=VaA61d zJAXoPKgv-yP-3$V;yELeN@BpLtkI0-m*2cB2B@UF67 zH44Ed^i!e~>)ZMgolI|uw#QAztMkvG;`&r}s?CfmRF=c4hQ8Qcssq|et*B|S9KP-J zfK_R(y#Jz&?C_9aUi+~h?{j4d^PN+Ux}Nd4%OjVZbv%RV(z#Ini13Dwy?C)?70c{T zaLIHJ)_U|T>v(()*ztCF`5*+>K5Bw+IECwt9huRJv7!OCvV8wi2Y$y`T%8kHjYn_E zv-WYeqRBl2sQ&2!yjw6FZ_G<4-Ugxk<|QxkCw?nTce_gtcKM)#c?A2asz7&IUx6^s zzF3=O$1~Tyhbl{Z{L{D##)Wv`j+go1wqP=Sy)cz+w?-&iV@*H3TZ?lYfsYlZqabz= zJ{^ttu} zE%QF%rD!9XApZ%pGsdCxwXZeqhgXPtnGKgVoXz|mMxbG8EY4V9%`RTp!hac;!u+HH zX3%&Awk=5j-3!U+mac>POMIv}LV?~{dVo!haA6nZ^ieQ=2Rhuz6O8zN6r68A0Owcv zsFonb+gkT9?S^=$?Yj~bu6MIh(q{Cw?ME0h@to+2Uo6mblkspJiaKZfVug$3+3>)F zIP0bicGZ5uU(u7uslUOZkayE0hg%0h>46h42ct*RKDQ#7Zs-J*b2)~46?U?If;^1U zlfk}e3FL;iFJwsqN%n`di;nFXhzm~*g4*@Yf}7{7(9qx@T#okU&ey8gcDWnG>HK>j zOLSSuPM*L$ih=jVxeQB{uEak?nWgM|>|6A~X;l;0o8{>r$R$#7tjl5GXN7K75^qTh) z6yMn3yylh`8Z6$1*{059Q}8HwvnEe8s9ITc`p_a`bzud3$$g8jpO&$S)33nVt4>ha z>Bn0qhojz%C-_pg0;J8W;7#LEII^~wsISgN;}u)Ll1F2Ir>Ruaeg=LFX@sz5JNPi^ z7}QR^iqGX!a7}7IKHx`x`p!R}om=-^P~2%s0#@j8rPyJh=dDUsC;Ovl*k+9CIt3TE z+VIR1Td^Z}EgLkIu+0zD=w9g%(o*RPVJ%wJFeM%|Q{!N9OdR$-@`}uRbp~Q8^5I>d zYmz;nrMsv?lz8EVcV|{h2cN!P?)*hd2(?Ayx4LPOZ!}cy;zOuWj$={xWU|Fk2y^j)RT8RT_I@j z1!Paec+H@bjPB?J$G==OzUMBSJF*AkgLN@K>VuP8sgNc{oxw9bhhb5OJU9Bf9(z*X z!ghy4us(h%$|}2o^{+Ub(tLpBD<-f71x{S;*h7K#&?3|e5lC!E2{^5NgX^7h1@Epr zBC3;W9Rx6fjKRn?k+cRz5#qhwe+qaW=_ zoyI~Gn{mN?4a^-ffVO)&^MEV2a8Aqv++ZYdK2oBM73ccH@#;5%hQouY%=d?Qt5pui zm&K9uf6}0==QiFsXF>Or6=3kH1RCTfhKFIg{H1|9-NONxgED2AIfibZ4;f|x`kQ}S&r7;dH8jZET4J%4y5F3QSUB_(#AFDb%Wuh zx>>j<{xevpJ%{;z+1%u0Cd*Qr2aoihKnc3w#+ypK(`qa9)+B>LN*arbh~P@COW?rS z9fGHVb`p2AFCUmG#o22g-0K(523yL2M~4Lx3wJVWg*q?%Q4Z}g`7pTkKGZIMfjiuD zFm%`tv`xK)Y2QEKi#Z4Ifbl2%_@OW z!s`k-$hF;uWBqE#nyw0nEKuc5ndzu8KLr+F>I>caCQK(#8h@q8(8X4gy;4>Qo8)>E zX_yr0av2IS8cEP^rwi}<+~`lrK%12$=-O;*$bbh72?yCDez^r0#$#wl}z@t;xqa!qBU0Cq*MNcpl!=O z_$+dzsWGKYuB93Bzs?1h_4$G+#DX>K9L`O*YyfH#L@gK)JgWE21UKZ@Us(rrbnp#^h_)~n~Nu!tof{B6&7)z5o=$?fI-ANl4em1 z-rKjrtdu5 z2vl6a&YZ|5=O^g%=gGDp9gzp}$5Y{>)y+ zUESr2vJNFgNBan#%YMn`gy`VhUyET(i9CPQzX=u!EP2z)N^JeD1g+;e+Epx|ljmLl zmx;+>dUGdsO&r9h_k0A}T85iKS4o1B`=H4#`-2) za+0B|i$}tV3Ian1-osD&4{`hmJ615`9opYZ+e}x*)&GpA;QWiM; zMJYQ!l(3>xD@aID64rG-gPrnWBLB{NWORldbtS!+uO-bJbelxFyqcX|+KU5=7jfmm zow)n2DI|2Az-HxJ#MMRvm88nZwMTPmYPC%O?(e~u)w@IserBQm?t%3FQhRLPZAi%= z3cpXkCEu2PCij*q@v&J|XezPE;($}IE@Y9@XtQ*<-ToW9{w{*vG86uB-$~3pu1({e zQ@~lvMyiPSXz^l+z#cHN14CYZCDmGrGxsyWHTGlrpw7tW3!UpTa^f(5<2i{p)wh!+z%Yo zn;_v&XmE0Wi|p^TM_nqbRF}j zIPsmzt3+3VT*!oAd)&V{n>2=o!p7Hqxu@N5w#wra*82|RJHIX=$Jcw4l%UCIwpAVW zrCh^dZ;nCRg)s0^eu?*P#p0OONrcUhB42K`{WIs|w#6f1%#264QDX=jy-5J38a2@S zh6>X>VDDpnbU0y)E462U+j=>kQE2|Nuyc3+9=7}BKpRt3Am*81U8M(Xl6^uPv#`236lCy2PsJ~@0EEj%a z6Zfg|K4WEZhIBcelac4kuMOv0=YNCx5x+sNc!g;5fCP-M+gGD#RLzW3j^qA5uJmNl zb3FOC5Cez!;jLo@f?HOKbhFe{O!W&#`)VEZsB;7pd3kox~-$^FxZ?adL4h%yugy zUyNFrQk5}W^gj=6C6r~RNH%Q6FP-aD22$17b~I^`<6D@YZcM5sn&8!0dstS+6T!0Q4X|WHB!5-nj~QE&;ke~z>`pS^ zwaPNs_Vf{)+V@wo59efeSr#h4eIlqYm`56!fNi`Tf_V+E@tE=sd}rN?ks?`|+%t{8 z?0!#-_MXOiKP}0A${b6L_Tuyb*J0(RVx~I#11VYeTM}r-(d*t2Hd6B8M6lix64u;` z^(RO24RbrmONqa|a7ZC;bY6p{rypZ-whF)V*`7lI9%H!iJS+B)>KC##V z)@SU6k=sv`klIABh|D3oD^7v)lvGx<){y#K=}&#`?*{dlm#Dc*i#-b31+^Ce)@V+H z!B3x6Ux+v2uj{mE(nDwL5E;Q1i+VP>K)|g}AV%(Zj6ZG#vi7@u_)8|hg0V7mL%a~R z?GC|bEni%-CIxdObJ#A%1(whL43Dp`gGmog6GImzdOf)vyRM(aMXhNVK5C1o zz;QJEv|T_7zn7s~e_wq4{vn(jFafVbzQpbuA{;Sh6Uizw<1-CE;G`mB9&ln04lq6C zyfwxiN4%d+)_*tz4d_7QRAZn{S%DUilaQ1z1F=o&bY;9cKbF*tb6OwZdh^RT(q{uc znq$R}%k`ktE<=8^s*40n?*`f91~A2sa>cz|FuT4MFSOkt7Rsvyx*OcUy!IF#+-%4l z+cMyg@=Z8d?#&fo7N5Rp0H0}f57WwDW8;U_Xx?bY;q@+vJerI%#WFZJNr5b=%3|{? zPKf6Dzro1++I-~uY=P@rIp<3TR1$z32)a8H(d)z+=Dev1^e1y@SKJM|gCDaI^XH;C zkrAms38GQ1A4qC+3(DL#rbqMYaa;Rvtoovl)e3UBHm(lpHWxXsSN1 z`#cdwrXI!~j~Q_EOgSlSTme#r7vY3e56iWBN5*`)NuHG*!^`^zP$RwXz>iDTf;!b? zZuUYf`zwIWrriuzxZ(KmMG)`v22U)>MuYLT{9Ta;c3zdHty0!JO8y33I{E{CE`ET= zGCpCo)nk%av=2h`cERB0Ke)BGv}X8>UEsOeiQaE9$9>oLv*!61Fkr+qxOzwl9Te@T zV)kw{dv*+MGuE)UsSS8Bf3sk^elJ<5`j8#mF2ZT%8RXF;4H|y*0<722q4KUpI7c)I zvnNn;E3b`|E?L5VCMELXpjzVMs=^y92BY0c1sZ@4*wcsOXw}iHXb{yZdiH!1Dh(RW zDo#ql)P+^h=u-+)e#z7A+Q-?)?8i9x(2yEC^%4BUC=t6A{tg$s>ca=yKEs6$mvE|a zJS^HiP_nPyi7iA1Iy%Smi;{Km;CpYeo}Xk?xR2ejo(1=NUqkZ?V`{(l2<)t-a4ohU z|8%DjO-d!_lIx#h?zQW9Xux4uI^7fQhQ~pUas_O@puo+nt-)t;EIWoq6eY19IWdgn z%}&CgcsF+Ahc+(w=?2DIp5Tl7mJkqk6>c=-k}2PwLbby)eB@9@Hm5{D)b;DI#A_g( zcj~1;?nDhXofd=hiv{fDmyM9%6@b0^d!4tx`v{9FwNPqwFRr<(EwWLVO7v1QF;#Mg z`ay9UabA>-AFDFS+n;urytE<(Be!H^QZ5a%#0s5|~q z@TJR_lorU*k{zSC-1Y}pbf}gje7S}G?#}$k=r5R-u!6iky@*JgtAX2MAB>#z9Y2LB z^P9yA_%Y!GUUJc*8ZgJ- zt!JV1?;X5Sq6k*H3nBl76xH04kKxuDban1EJh0B0lz2acZ+)JUMAagcRn>x+X{$iR zvOnC>m;sYSE3i>m>1-bt$q$Tcz*ieTiSDHv!PAhfY~(F9I^1J3^e-;Mm2)3J+FnOo zrWXi-X+F*q3uZvqs&-iJ_>(nB8Peb1JRzERFx~JtoPNq2&V`Ecn`StfwYZH{m6?<0 ziE^yN_W^|Qv+!rtaJDPU2STrBg5O_h*r7+UV){cgu2@8U-tlyU}Ffo8~hkoqiO;@@w z_o>8ZG6**;cE^{p_0Z991I9>I)0bx^!^92W$;$>MG#Wki zeMlM@j(f)nqBX&9vJ}_8Y67RK(^0WjnkLNA<(~=_M9%~3SW;~Zdat!6H+VWa|CPLN zufH&^{wFs4y$i|~uCVB&9bK_xKhZ6_FL*MlFXK|11-#e}UX4nCy(-RlU}`^JH@S(t zu$oEdefOr`&Xpu7FaRoZWch~@8SX1|#gu_v)jK`isO{P;r;2Y5JlfZcWKGq@*@7cv zm#jPgqh>@>r=>G}lVY}iQZ!CA7qiYio1tyrWL8Lnc+dT0 zB+||(ZAGD!($ErFC8O+>kx(KO8SlAIX_siKl$4^OlxY2o{GPvE*X#0no^#Iq9iM|+ z#5SRx!$0A*3kuxcKa!}B*@GU-BGGl)AekdKf#F|IOnG4gkf0=p%l(cW2IBN@#y!Dn zqiW)*HNfxh8kj87WgVzv2u71z@b!QFsC+R7^nC8%bIlq2VAhK>>Tf{v>J$Y2IsDlm z$IL@Yh1Y(c1zh?83d83M?c8S9ja)O2iJr8;nIVXaO~g3KNC)nzu0ON{ekBq+{?h7W zChTg?6?)>I1FfDAOWVdWqU~dYHPKPvcsFtWi`%p&Lb617m$4)qJ*Qp_R|dh z*P3VFgg=FgSjfdRT+^%yp}`}${V7wKJh<_D-bL#Fdp_j;5Te+$M(Q6rmaX_RpH|v+ zzz(-~m~rm`{oGKFqoe*%h3$6uN#MxraI(wK#zlHfHjzEj~Ai$pzTJ5CA zo&R~B9935%lEr5@HK%5B^u-rqH`S3WHCspZ`q#th)4Dh=Hy$pvZ$#tQDlA0wJrP`e zMn3#CW>+6=z@9TP@Z)YIU2<^@%54N;bnzXZF`c_-HAF6~0h4um;7weV!2hr}y0@5+U49=x?}8&arGCcx^pdk^ zHDWteK!5OFr-MV~#&9=n1I!#@!9=G&hj))fne>*C+~lP>pce0iyRBc)r7q9Wc19;Y zDbJxLZAs|Y>k3(G&!N1%INR^q03vfHf&j;{HrLDenv>`FdOT-qz6TroBiV|Q$(X3q z4)R{zU^UYLKg_m+tT*`}(SN=!Z25EYz)p_cbts}6bj8>;RzbopO~4+5yLfYMJ2iQs z3{IO8@p^3m>FoIg?PUwmZ}L?9CvL><4xc3lZ?D0J^<$aw=7YHHdml_&o(3y04>Z1b z;l1vos3%wk`-{VNO-G z(e}4!kKuGuH!}os#W%7;t*V%?w}KX0s<5B3f5AgtmWdds!0}yCT%P-J_TZ#BwR5-^?Q)p;1Ou)#^CDOUK7wzvE->${$vCFH6hcHFqE&(k_t5JB40iHo zraxn;?eKiunHkLVdrqQBq^nn<6uOQ|6r2?z$7<~Msg5*pQXEy>%ksXVr4SCk= zNmmys4r|5Q3)fMzVjRp&KS|Y2*3smFWlVZu?M=rCH*g6oD>ahYTnqnJQd4o194+qI(}_afK^U8 zuyv9mh8cVj1oU3POQ(N<*pf4FWtAh?ER*8Qj_5&UY9OvItwZhN01O0Ic@ zfYtbiVafAVkRmBSgF;!TG&+sGXARLZZ(3b3?8diK2~+#8N01VF74OBzL7ee4Y&Mf; zW)mg3xD<2nF}2|)?Q>zDci*D13;j^SzmUpZ{0?aoKVcHT@87JQ4Rf?!qkO17m;b^L z$JD)oMLR@Ldhsl}VfYx>yfh*|pcrq*MSz8$1m`WM4wg?8p;`3}Bt+eUZRdvR#hDJw zto1PHJeB4Q<}P9SAI7uY(a))2s}`=%ctBPz2KMhOKY-o&ORgUn&Hc%WVGlo=VUPP7 zNYko7wP~+lS^p3o9umb-F*$53+ib z;i#B9&X9~`GvxlDaO)Bn6IcSajf0?BsLbnJ1_aID;sp8CaOI^MGyV9BRus)ct8Xg^ znK&TqoTh=^^3t%lqa8yxZK3LXHtLpH4qk9L4Q;)KEHnQaq=G-W-x!77Uq-V+Yc+PT z=q;UFEya1nxC`9wq@de~)#&!jiPq>^5V=RnT$R5MmJ&srepCTOYa76&D^<`HAj*B~ zU&OiVk7i4iGGIsVH~RCMHFM~=LF>MLg-MU~VDIb6oYOXbT|BJ}8QNATF1?OM-U`HK zI*xh7$Z`YI%kk>gM;MuFPhXi;k{hYFu;9cHE*mp~5`{-XKRXIyi;mFOTijtC?7`%x zGr2X-rVuVood3cX!GZJUnEYrXTBvy7dM{P(vz-cSa(zJ$=J98?mVBhcFQ{XVBXgI{ zr)7_#*r^}Ua5>bFYg6i{v-rM|gg}yg=-!9pWma%gpU#3aQTforXNpfOmgnwksBp;v zmMHzTgot_Yx7uOVn$K}R4<8ORsKRukh@OXl|>?8!CyiX|E^b{h0Y{D_s zEjX=mCc7jp&(`Ica^p{YBkoT=ki|OZQ11F(Vkz+eQ^wYzEZ=qL-|$fwwmlqOq(-66 z;sLThcMm-nvXZ?Vp@L`b2g0iocBhYow;Jnm-P64*f>WxwtEOfPx7C= zae3Wj*=(3)>A>p`#;Cn9hWfdT1TOLkO|Xi=pOXrxVrMHU+o#0!j!wqGL#60HSCjKN zo{iZnRzh<84$L{a0!FtPvjw)^SRj4@w>!(vny`ZRDgs*}x=unk;=K=^+Fgrvw>Fb4 z#TCNachWI-#3j5}7fYx6Ide~~*0FNmA~>+ujI`yN*4fUFvYN{0vcE+&!WH%nCe1To zZFRmlwR8$9SxLaBnmZu2QyJUdf2Dr|#DxLovZNUGP((eQ&X~J^b@X`&6s~om;$lzM z)1=0ks2Oq>9U7U1&THyt9SIU@UYtMQ9qG#a3&O&8z`nMkhSf9Z3s7ZCub47B%QTd# zdjU#@hv7h)Bx`)yBbacn3Pk+(qE>Pq20rs*uLDNl5^|URJf+SW?~TE%-yEDiUcwiO zr;`ogf6;O4Y?O2R#_OB&an1_<41d=EIwlR!#s4*t=$#eRYVsuLjuqnsS(BKRgD#si zc@7s_*N=ATBiY{<0-Rk|3Hsp|(RbJxe6*ctTdoAtoRx?hvW0>VSKOG|^EH^kIkVu^ zhHUqBf3opw7;N5n6cjTD=$Ia5Xl~ahYF)!P>yQHmEZ9!I9+HRUzn9@sUw06u=b%>e zeLQIqLE9$!pw1}+y1H{N$`@#&=dK4tZ9yeQ2>gkt`$!hw6AX#QlI+A^D^OUxm%Uk8 zLdR|D1gFq-a69g%P|DX&7``9?wbcb&meDfSVReK}Gm6HByK!Xa+T)P7!VCP&N7BwU z!$d#10SA+Ma1Wmk9R9M#+I*e^-Pi7jum4DJpC4|=#0BT+=J}7|v6U#A(*V$`EWz=1 z=1kMRP;l*C8#(kafT_Q~0Z08$(!X&IctvRhxRGr_Zu3hVmrzM3XPXMvXuZN0#iO~= zwMv{&E|gq7n1bsz^`eI%@5KhKft`2a$&v=bx|ME`?v!PM-D*c5$36u%r8NrrFN?B- zH}>pKTsKD9O2fD_34$fFx^ZdkdtOfsMav*Uq{M}odX6v;>qu;um_tYTDzQTO5wNS@ z5q+9s@V}RVIOdoVg>_lP_}0czFL+^UL0FUhH7+~&VuE**Y5!_&isviVv_`QcSX3fe^+v{ZTHdt zpAy^<)#m<-RA5dsaAI8g#!|9fzxM{&suu0yN8HkQ%ud)NV##6IkLg`Za zd!i9tthE;YTK$Bjk)!c>(?_94&v`h%M)O0n>7mz;sXEyEs;j zb|?D;74@okZ`ujm?Ku+v?fHy~aV{iGLJhWQPsRWVE%2S81GlR(!NqhwR#v?OD~J7{ zY@v&li3%)d2@m2DmCtk-}VdqCz=69+Tj~&+qBl-E5 z_A*R(Trh#W9;uC~?dQPuLM~Bg6;E^SmWK1?1PUPa(i!~ z?rW+uhW=Axx!;zX`LLePLN9~^la^xZ)obwT#7R)K2qTl~ zoz_zGkj$w=N`0X@0tW9CE#T*iR#uEB~WV)EZLcq_!=>hXxT~?|K2G3T8se>@1vyyWq>pHv-Y(LDHFW+j`fjqY!0x zn;7LKk%D?He4;)`*1r?yI*UiM>y~_m^p+T&3_StDkG0mhkM@A@!enf*T_e=YD8e25 zxweE~GpWlR`Zd9hE^}-XZVOz29n1R#NkPtVZ~u8rd0YrZGn=WBzbx??JCeH=HJ5FH zM68O`N3Ui{Zn|eQ9QUdbp13`ob(lP(qc5%l2ahIrZ>x)r#y+U`PKExq7!58{Us6N; zV49-xoSdH2fs>UEFz4@LoccreI-N=x;cEXbl==Nu@Nm{dII;Ty7ClJ<<9E}!T_X30 z_XGp>>hvkR8cu}v3QxfFeg+lwk_UKo26C$>;AwMRc)k4)yc-t+PQ6~R`_>vhJ2;v( zBoz@0z8h!|oKHw_w{?Bg4M=vJ0ZYS7-H+hc^i&kN6xmwDv3IJqL7ENs4276hOg-EgQ<8ez?s-9q{ArJ zBvNcJ4mNfk!IAE^tk<%Hj(^TSx1J_+$B$m(&;P8tCac7nU zW^HnYvs3iQ`R*RH-x$Q^*c_oxr%z{}JK7+;uK_Z|+#z+sb?`Nw$@YzJ7B>B-$ksb0 z0uNY)u`6}(!>$=vwmXr2CZCZ$sK%YsE}_{!bN0Z*iFNBtrJlb|q995a8$D9-nHF#* zuT1dabuUyMtAv{l4GC19MbU-21^83?Fi2$E;lA8s*xv0AccwywYY`JAXr!+F5vF{)T4pz0Aw^ zWVk)%HJEfXj7}JAz)I5xs63p28e{7*#Ak~zHo8UF62klYnJ(0R(k-w#dIc31*fYbD zRBS2C6xpw7reA?8iYIOff-)&0!dLB?^u@6oKfj3#2FX2Ke5d zf?{iTf=#Z3^{254+&6`d?BW>$pMR;biHcAgr@0;;nTVjQ(hdyDT?a=44Peh46WS!H z%t|}XSx+&Zht-c(VmP09s;XX1mThW)Od~Nk=Q|Q?<0DxV@;!{^Be-Jk2rOey;dkLw zqFDAAMUGZb)rKNArsy+HiaJgV6m_@-Q_8Ftyr8fn;0?_@HCAX4>c=%1EM^n@e$b0S zi(%!eXttd1{XcAo5>+I zgSewgnB}{UBlFVeBfiHY6C}>Ly6?xo+kK#(&+_02O*kke%8gW>i}#dcP|`92cR#v| zRt|SC;%_Pl*(iLJ^p1QzzlJy!u4lK7&4DrJmB?+7U~jJ+#R*#m(07pn*Sq);>Y_i^ z_Y0t=@;3Zjl#Oh(5{qrz=LWOD^?uNcwK1rFTCUvKdI2Y~a_ z#JG!%dGLz&A_OYQWEG#w@p;yVxoa+iL7JP;W1s?p9rvSSU?T*-T8>}EHlyK^sm#{q z1->o1kHHN(%qDpfw{DUZOLQrRiLvuQobP;y6}aN?&tACbY>O>(MsTYSjO9*e8bYMD z9*4}27A)+6+A2NvKKQs`w97p9?V>Fa^_Amh`z%7up}abIZ%vr}T1@zTy1URLz=HMe z>czg#&8`H!Kzt~{0Rhn|35R~!Vnxe4Zld$3zi zGx2HANvIGEk*=B~cxZ1*Z}^jotn%$^Pvb!S!D5M^{-f zxU6*okFsa9vD+JoxEU#ktOV2d#V}H1I(KGjCaKR{1h3t0p-qoI9Pe06JZ25x7o${Q z(p?zaq0A&ohlF;m*Wk|G52#yu4jq&)^Bv-NP@6G|nO_oTeOxb`i|in3M{y$#*KM#zlTJD8=CchEkK^b;_dmEM{X49P-U4IZro$cy zZHV&?!%ZS0X!LqFv_*UclQ(l<;T9X%*TuglvG1UHfduy;LI&+7D8eK6O2LH_!DQIG z2Xe{^z;xs$l4v~{Lw>}8()w&>H1q{^KUNFxJkT;Xq6g?bm1UO-Bw%rgDi<7b z21`P+@s6|}o?U*HX1>hCpOG{0gJT!uERcoB^DZDcT8T4!au?ESVIF}P-m%J#NMTKBRPbMTdXVOzUjxo)`3wUa$Aty819j<(r z<9>yyp>&ZbSM^~z(YYo?ZVi^<6UfC@V|BV;GYH4$H`46Rll1WJ{rG7^CkZ|niz-#6Ikr)ZYQ14RWtLzG7+7sy9d=nzHt_dD{_hHSFNc7z*%|+h1#d8=$I8C2n(ov_*oh+9| zk+)?iTBFZYbCbyjgC;n<(gQ9X&V-n6ePoYU6J4-sGOI8xqzgud)9%dz_NM3>$rzFa zJ|4%GrG5i-k$h^mxtE&#L*ORrGm#K`{Hk=BKE6K%B@eHp=fr0d{SV4mZDWe@0a0w~ zzyW&oS2&K0x5C=F^4J#RF6_E!#bWu6@B6dwVA}`>PWjGLC{)$uL}okznQ{$WIpr-p z$gf1j9kr-k#$nQ7KKmAMA5Ytzft9jbpfbW8y#E}sp8a$#+jO1c4S@xC6nnEX`FHW) zn{srkJcE|M2^S=AW$T>p;2kefFq4}}-9ok5>je*C%iv;;%a}>s6H?%LrYTNtE5gL+ zYmn`hN|e6t6fT`?!I_92f=K8=*&n;{_W=dinL8bWLL$($S_Smi-X%7N8j#^ToHsav zjgUHy*KXV5{_qo|EX4-rM%SWE=~8a>s}0=ULN^ePwPaU05mx``54Jjo<6^G@nEL2D z4xL(oYgde7#&z#dw^x(6{1_!D*&xQ9AG!}a_bi0BcD=MuM;BUNKZE~nsInokk*uPj z7dz~utP8idW01x%;RGViq?bGrIR3GNDjqU?`-?WY{H2?WcnB!*H&hUEwi1md+K{Rw z7jTYiq96Z8V$re_xbxRDn9=wOBH9vA@@zBMj`GGIBjlO!%6+)eKMh_c9c5isw#;C& z66dQs4G+zhXI|}c;H59l{XDJ3^k>>*NZ=csx8X0Hqr4fNl|R8TvrlmQUk1)mw8fqG zy4@CmFfrM_5bk8HaX&^ehO!9T0?hdPh|AS z4De9d4}W$p5yslMVY{s^S964s>Ia_e=F1znp>7k(-4x@tDlEYCB}Y&ud^?Vl3jjfs z0oR^DVQ>5%Tr;Nt_AYP21^FMTMOp`1j!zauo>xKFbxauUqKGuXlLh%j;$qQjuy9t7 zaAim-Ic%Cm9&^+2vbru>y*tEp#oMtys~Q>2NE&t|(aUJ_e=)))P`7v`DwV93V=6c3$< za`B^(@jy0*$T#?~CLXsNm=IsrCGc}X6}-Hl0r;T?7qu0^%ZYLDwqJx>K4vwvgAeBW zO0v(Z7@TUm3ieN)!(Z1lG|tZktNFXAQT`EuRpvi5SF&UQCx2nJxFH(+H=eV30c34f zEbY*>V9)vIJHTo=eo$BDr0*u<<84iXw}p&0jMfCj3C^HvO<^#o9d}+jjt7>$q0?<< z304I<(7-T#wA%5J1}oZv%E^gBi5&;wqXe`==ykW&Y=PUqtl!sv@NwizBnc9?#bLTuAxkFzr zLGP!tLYcY%x-zzuDC|54X*&lW;AkLzWXDLVq^5q0JGGNh@<(l}|$0;119mdlh1g z8wE)k)3_<`1F$^U0%LpQ&|z6S*;v08eh-x4y>%SBR_@8xPT_TWaTn@+OpIiOig6MP z%IUM=0$97t9AD4jV0*@Oeg!#7w%4zK$wGvN0z)P{Gl*|1Ibr2ZUt)1>E2SSlW81Jc z(-j^@qk;-_ce+e!$0QSFOvlSFpQCD%GrsJbNh%iU!iI4}Xs~Y|n^_UgCHTuT(?{O8 z!Zirr^_LQvi7M9ZX?Z9fkdI>{MX14K8J3xQ8Il{rN%fwibWV~fv2vAQJ}2%Wb^C$A zukMrk3eT+<>5t$Ej(sa+J4<2oj|Awt#A|5Z^U!GjSxC8?jm76TqF(g{ zOnjxr*-UbR+`4q)t38R`YgDWgzxWCl29JlW6Wu^wxn1z{MGVyHH9@znH%OOz3IfAq zaQ?X{%-OaKCiX5y(V2%pth|sg^F*?#R1&<*Z{oerB9N|ehyIfg!42~^!}*{tq0zfC z7`?F+!gZ|Kp^0`}!54kJ8q-LF*WM76hdaZU`Q`kcF3o8z-j0*3)3CR31<9WL39^(s zV5FKUi;-<6^CqppIT@8azictS$}oZWlh)P;;?kh!(^kQ#OUCGQ`#7FAuR>3aatujbNK|e#y!KS?K13ReiG{ZTEdlwMM2-bBr^JNr!Z{yf5L;#GWdLsBDYd( zE?gEf=PD#l(K6*6{Fl2ESEXOU$v`GQPT__QWdV1+NqfC|s?>Z}J zX2F1>1vk*cYo&J|(EbEh79*WSX4P-Rfwu#+(daZeZ*&{q`ML_c_qx@sjF;iw?zo1= zt5Y%Jtv~#-kHni|QYaS@g)O5m&|aG`d^I4)_AI}M&upeJkMOxLccd9Rs8b9{P9~V5 z7lXn60_xD2hO#abS#Qy8Y#N@2HxyG);qYhb;>1d|B05l$DaMJ7wv@cM?cTwu3LcWQFyWPcmPvS|~ zz82z`Du?ddv#lMh4$`ffUHtz25v3-bz*(l7arILTc%HigrlncXanGjk)>n$PXHO2T z%_;_w_Hbza8x5uJ&QrSh5On$;!do+Dc4W0FijCkw{ouh2H2<>J*FX>GlIz^LX zVp80+>SWY=kxR>8Nw6S$Uz|Bs2gDTky!C{$7^ilEG~auRkBgfSkM+5DM5Ajvux8kcv$-}rQa!QZ#S z%UU{k;aV-NT9-=$PQHf2d!wL1_66Pf_!`QWu40!S`=Y_3{kUfKHj??{B+Lnv;Lfc$ z4(cDX@b7bbxEk6F*Hq8*d;MYNq!-T5!TrL1Efv_K+b;~2sE5brB)Broa>3l)j`gJJSRW^aBNCy;eD5B0!X0!MGl`w9k32A5#6DDfCq?*-3 zFn3Qg+{r${4lN(cE(ct~Uk~OG+Z*dy+sR6JleHO_wGV>*o(%l&Z!NEVKg6OLuHg4* z65FgKigPdOLTC>#pZgLZ5S}C6CfE3`^G`7RngL5T2SW7A3m7>siJpA)h&;TLLvJie z#oO(2rPRVM*%SN~ACh-0Hw7Cci6@g5**-Q)d`$6EW0{h>Wz@0e& zo-=+z_9Gm(;*<+sTjsid6bcux3#a1XqPvga&n;{8QgMa#pQ0db#VRV9Hw1ONc?hf8SNfyrK7H&b z0W|L_=GJPHaYo9xXuC5$8;qjVbv)|r&%*QS5jaz17kZW^piX}%JXmLf7q%Ir#MD#J zl3Ro?Gi;z$Dv``zX2gnge#3ze3fzZT39wrt8=lUz#Cv^HNx7RI(+yGq9N7ha@m5@O zf)ZP#HUpgHa$&aKW#IA>ASptF!0BdO`qGQd%*lXLJ(g@Luji@8$kNBzS~UNMKF(Dc z$D}t8&{4Ieyv9>a<)bc&9h$!&Mhzn>79H|JE4(WvyOo->3)n7IJ2U4<2c1*0{HdnECxO2 z^Lyqqc#k3-=7vji@0NIC;@1jPKYj(PWC`QM6>-P)oiu;HHjHlE2iGJ5gO3)o@!5OWR; zQl`QCKhq=NT1hvKH>(ub9`hrcUN6U%9iGIfVp#ZuZN|Qx88FtQ0F}!(Q1RzEB=*n; ztP`2ftP)0YtJX&l4NDCsdt8^~sG7jx0foBbOSDmUW+boC_TdcaadgT@b2Kvlj^y}y zoHP0c)hHK-n{7elX4ZLdDt|^kZa1TUwGKgIawvo)Y4h6P4cHa6f%a7G!^&}^SVv)+ zaQ4OfP&aQ6K3%Lxs(H`)dz3gkR~w0Dt39@(Yp79mXJKb984(Fz@P8 zSUvn7wvR}$Ue*yur))S)n$~Kvhc^U}>Yap{X}TCQb}UQ|n!>}auc2q(V(dDv%DE3G zljgG;nDq?d=q6=$%zZ4E?p-Z3^z9^t`>S!#b_(yKet~QKtu*x6Lo9hKN<#+L{k zIA$MfZ7(Yne9tDF8b*SrL?^i()AB~Ls$!2q5mj00VV5;E}8kv|{rq#ulB zIPW}hs50M6`6A>pMue9g5;J9}PDGIK1}ifD=(E9?!&A8E8xr)KF80Z#aQO?g@i;-2Ptzt*AZHQ=_PC7 z$Aa&8Y*!?lPf0{3ae*m7{>4YN7kT+ z%pkotWh;8s+@Wo*17z)?Qu<@cJ}6%)2Ge?~$s#ubCzdATtM$3)BZ$Go*E)2_DGehd zI*I167Iu_E8B{J^#XmS-(ll?_Yvb4cp+X#$0~AF{hf*r!ZV-3o{cuuq!2y z$8o+PS2ELJX2Eyiez$iZ621#?`&RgIbUL1EI!9ycMzhdPN3dF>&6vG8YtJ`FgXAsn zaHBE43|<90yKTU6S`oEUlmz40ASy5U9A@3Fs8em(BE&iUxOez1ysuJZe|9;sC?6|) zes&5J294nKDr*D=?jun#`XbSZXu~IwU1Zra8SZ`8RQ7d_0$nC~8n2vw0V|qhS!Ay| z?RP7~3S|rSWzxI4xqpILR*4Kz<+*yx24)I1KYgKJ4f)R7H2(jl6G;~Cy@a0|>L7B3 z8ms3SY5kpV$>1AVWIpoQ@Ki{3Vj$up?`0U} z-~#=<0>Og~%)G~gRXKemol|8v;nl^=<$#m0%sLk>WNtvs>vcGyR|I^|IYG%)MY5?% zodv%fCQG_TfuHw7FdOHFhXs?+@MaU_M16&x1Nz+W2u*wxtj<`PAF8#=3I{H(;V$lp zhMf9ha`w@0cyc!YuBrv$b>8!_7=A?OcRqmUiklsTAecn%;=B&W})>b_27?1J`nZU@;4GrH-XWKP%=}3V-ClZhZ zZvIB>+towZJAFNO=R3niw$YH0^;j6Vem5%oyu!2IG~nuNj=eCFgbT|rP;sBv7@f5S z_y6(~=v|h9TW^J6JiQW57ZeDkmOP~&YYfPh`ELdNd+&i*oG+9I>f(tqA3Us?2%?(> zq}AvI%u#s-dltN^s~P)}^1x=URnHeEcJD@saW|>T_2SyfdVlJo^bP&2cm}C(5MwP{ z(bGzX^?wj!KQc?}md;uVKgP%6t#KQ;tHqJ@#qQ0x?z9aagcs<^UB=3U37qx6eC&I< z73QziB>OB5!2Vh5AS`u+KsG9i9)I;vkg{ByT%MFjSCy*3xR0gSnd}IvOMOXdB*SdG zJ+S-oEbfCxKY5kYiT|zgph<6E3kF6@agnBHkj3=U@7oTN9j5omu!$itOIix5l4kJ! z_ZVU7VtE?=QW6HXszH84D(3c{z$MPQT>Z1rkRZxy2Nf>3ctip;PNX>Nuq(u_x*@!_ z$`)3vlI8LX9ATrF6EZCkZkdWQG1ODW)61%1Tu}=41!tht-8(orG#QdpKcRe?8)VGB z53vcy@$-%U;G6Faa%k@<;uQN0=E+52!p#!Q*GYxaFY}OCS3`u35SBIO)4zFxQ2Z&I znk2;$xvB)D8Eeqx$#-1bnTVgF)996@oph#+7rZ|Ei(HtJkH@rw@Zh%#Xgo=Qez%{` zpOxYv_I?RW#T(F_bC!ngumw|D3GU|FXNP;;s(#QRelKjLxa#( zKcaSQh6vBWx>=X8OoV%sn}~}#P0)GWH`MvI98{yKVXuD?{MN972tf(_;v zR?(?J%@}6ZC~#gnnSJ}svkR7|)595KVd=`bxHn&y+d63ryto$$Z?kzXHTJcYs@)lS z#xjj=vdSXAqPhT=3}8z=6`Jzz)nf}!lK=N7Hffh*L#{SX_!x!vw`|6FW7p%rCM`~O zRU+s~G+IaO%_LcV8`!g9OJOtnK zc?!1f6fv+~eU-q{UBb#wTFXzk19sWqV zh$5S#H(g*Q#o))QjU>r2fna?Q^^HCQ(X^DV>Rik-$kyXiNi+N-kl}oUhv9*x4?5>- z!rt0dxGe28T#Hcx?W8EQS5trsvgxRFX(t`#JJjW8=L*G+hrwgRe@JySxsZl^IPF_4 znGjk{cQ>~~;J<%xc8?-Nk5D3p>fZ%@%!6g@Nv5CfcauR^eWtUu9M-%W5Qvm%a=QB4 z(9lSc%T$hHB?=LOpq=BXo`)#NSBAm;Z&%3ajxZ?lZ=&9fT`0_3iFZzS&`~i8Z2mu0 z?%tam+GBqjJT4uD?b|Mq>&Sf|5EViSmeD}=Jw+{^GrjQwzr+~-1a>(|HAc1~u{2nC<=WG4o zYX3vHx4Z!@V`sB5a*{Z=LkbipDX~iLGw|O}QP$e}oLn9yfJs5&cya0zLHt&06f4){ zs`g(Jc0b)m&orDOa=stwnAb|o?9?{)U`zy#`gReoojwi*+X>F-x)0lnXRzE|uc7MY zW$4Unf~^{duyWN+-19>n?;qg%sX8*;eEOYgysxEeOOvr@-A1aT%dxP=(Sl_vgq!z4 zl#ZDijQ6igunNKEy5nYE==F~SubTBNd$Ao1d)acYN^1ol`MmlizdWp6V#EdaIiW}Xqb1FAfzcBUZ?z}X-0`C-_S+GT%uq_x-))|%Zx5qEVPntZ7%p@M1$Y&b# zU51OjqJgPpQ>?2bZlTJNHo;34%EZ1Nr zUPb8d)FX6zegIl3C0MK6XW+5_@al>TC+_+O4OPa%DT{~nWFTJ(@UVl6)Az!qTahq= z&R_vW-)% zv-CZ004S~cad-Af z($Sp(&!X#T=*Fqo=N67fY-dx0?k*5F&%uV9&!BB&DAmLUA*XqaEt)k9ZpJ&vW^onn zOq(3r+s5~FCm6AggZ1c`ID?Ji-+hy%XYk*vdb)n_6rSI-0^iK@gYogp!SpSEA?uDb znv{AR$TersIkXpVn;jJti1cH%W;~gY@vUxbSr?9wxlTI+^uS3f2x<<9DbaW0qF}n`ues`ow!{?zkPn>m?52A%{1oCbZ^>TJ%%6;dMi5{cN z-c_OH_R~V`@)KaEH5(WGYP9xUeg?}HWkE!L3W*Fb=Y%i!aR1#)!-U|)I4N)pdppjE za~;=~{`7AF#kI?vU(_U03Kr_h** zJ5NP_ZBh7}&_M1C&tiCJH2ZrB*c)Y4)?A`Nbj)0EP1P8H?Y)AJ4RbV~-l?p9$px0|5dv$6OgdLj!^N)echxejl4t>YA<0_nfF zYPkA*Dk?N@LQmDTVE#>sEmd&l^CS$vdluldk|IE#lj#3k2gf{~!4+tGk(qVVxb^4G zgX@Kdw56;L_szNrqstpn^qwm-=)6j9J~x2Lqf>}_xF6~6K8ym5*__z(Ix3@HMgLvE#J+{|KJ1TMM^?$8qE0 zrEt}hDtKC7LA^H=pifT;-KhTnWDjs`-x!XIi_H`k4o>AZ`*sOFN)C}RrZsHux^s~K zelnaJxPXde7-BQ!xs`hN&@1x{gbfd1hvpP^^jtYUYRL!HJY${%smj8?@w0QPJa&zm z#+LP_Vc_9mC@+6Vmj36A-97wSTW|#}-x8t0z!%VPIYRX<#aK&UQr*3yVlXRwJ{O+< zMBw3d6H}*#(`jz2a81JvShFPw%jaAX78hQ^`3rl9{0A*k9GZbukFCK@P(IuBGcO)k_g2Oz! zH@LuF3_?}jz<0ZTLCkPJO4mt}T}_G5QXdS5XcKJMc~$7Q{svXfJC4gDYEbs)dmR4J z2A#i4sKYsg(nr^6$$59yA3qoGEHFX+|JteY&`-g;chk^#LnC$vp62zR#SoHZ3cU_- zk?pn}vWp5HTwHXWTC78k7D9*S) zgamw;!4A*f0v`?8RZI$bEoXZ?Enz0Or)-2>$+fS>8`ulh@>?W_J zZ^Q*=r%3*R&lo=E7*t6abJEuvP?cwU9KH0LOv;OZj4u;ddC~s5uXnd|pO%>L`H2=x z$-h9NY!uiSn_|4SFccH5b0FCMEVOUP13e{2A>U)=4u<~3suS**wq!Njc;f;s(-LsR zm0XZo)CXVqzTi&lDJ=Jb7R&!rfM;tK2*^J(xRR8Ok1EDe%her3I_e44>-7*;1igak z|J{WJF7L7F%NAOF{VORyl>$zqbHU@41>BouMyq8uWBaAmJmbt68~ygt*c$$CakwD# znW0Bav*lP7&l1-4&H(B5Ul=>d5AI}`!EfJo;ef<+)_gLRMy;{}t&cKfQ-7=Q)cEbp zb$k}6|2T)i&BmyuE(GiJBd~q@7IcUK?yBT@`lI>)HmA5kb96fyDsqPz64&rY3g7n( z55RXDrVFCBG|&*QG%h!GKyc7giD!F{<7(#K#Jo5$E_44)L3vy`t{1UjtGN2(Oe>LBhL;wM8miAk+^ks46|Pl0CdX;gg?@p zNwNz2y3C)+2$Jb;#{zWun#p&~dg;||e_(~C85S&*Vkb7g;5npYNWMmk@XPLbe5Ymy zEEM^T`m5XFNpBS1`WH{sEu`o^m6LeXvjk-&jKSH}6{I{O*eOq*iQUiVsvwfy+O5ag zWlV&q)tV@^)dr6QIw4dSr zH&Mi5gBpLC?PL1(z+38Kd5ZMS?4Y*<52(FWC$iO)=L5%Z<)``e+d2uKuFM3os0Tk6 zDPev18~laGsew`%#NEyl?j7w7>XAIZk>}MF7^uS3#B!8+UWQADlA*$6A(hxL3TNxR zCQ4@nyz(AKZd8osf^+>b$)lbw%$&g*{zbt3KTTjr?YYqG^W?|tDdgp|` z*E4r5e1G#6!~!BQD*Xq!EEx`Y^|rV$Xa+vh5N8i-3*dZJoZ!~I2w30~N&EcEarkyD zBuoxxdum6(s^3L;dU_Dqa%Km;_efh9Hl)w3(~gJ7pIR_a`3{cWhtTPifbP%3v9M$U zxA<8WcBt%N-a*%JiP=1QZSe=}ysrjRo1)1Q4Il@1s6o4rI4-Lj2a3}Fb95dKHNNj3 zZ_`lPX=x}Gm8R<4*Mo|PNJXebqz_WEDKtbzNy99q6e*ENI`?%Og{H_#DkEtK8Ih3h z^ZWe==bTRGx$oKiF%KR^gXb|X&ZDX)uY{@5*~}^ za|HGbww;{<_b+UMRqwS}kf<#u_LqZt9&nlZ`WyDQ)x*sTS>U8_7R@gfqr*KBjx#XC zEfNng#YIJ6Gs7PxPV;=Tg0Hx<&=MNP>T(m;iNJact}XV66k!=)fTn3`t%D z(NBx$qGKbu!1ZAmwzYty&XmO-ni zncEGE-hU+rpNg{bZ4)_dJx_eche+3N5g{$t+*rKAEwp!hK}uQ{;!vy(>wW8vgFiKS zv9mt+J|-GjVI~ya4}*d87I;fF4qeWkqgxiJG3yX{fuJb|?vDLIYF+wh)MWuTygUK2 zp9Ycy+Ya)I=MStN)Z{)K%!1>eB)DV=9j1KbuJFv#wUFPmAE{U%Tci_5qU*Q8n!j@( zNjh8D{a+?Vj2a-Bf$Pz>HV4k7IGhRm31Q5Wl`k#o6?g@X~8rPSU)ZhF4nRy4}~& zZ_OsGQf;ibY#af01D&Ll_m6eHR_2x+JVjla3`t$5q%hmG2w#d^BQ=g{c)9SjfZHa5 zzs{SW_bNZ8X{(Jo)0eWXbxG7QBaA+MrpR%kmGHYQo<1n0gkSTr_TS%WRFFJ5{_+)Q zIPm!=(N9nmBF2&i8*xRAG%UP&9M5hO;0&HGbmDv<4xP0^+f#95wNQfOo=Ya%=cIzd zMGcTLc?#cVp62=Mim;(J6xy#;lJj3K;ux_j*y*_#>)SGjS+x)TR^Ll(e$0STbuOs7 zG8P?k#h~7K52nsbs?ZhxOSAZy*g(o>Y<;kd_6a@M*d_B&zFCDceIbKpkFP@S$Zwb` z$)WL2BXZV<{|(dYgrnPTBFmR&Mj<`qp=cR&9r*;i(!Y@kdIE=QB=F;br6`b?$@Drj z*tp~7+-#FqSbe}&VnEDeG<_A}2?r1K(^1WFJiFl>5q}fJC5gTSxgFo3rNx#z{xcjet0&>&jj6EZ ze7~^c*<%oYyoY|(<$Fra-gF<&&>1JY1LpLY;NqD9*!J`;Y`SX??HBp%R)UIfcv~}E zJX(VJIr7|*-d)7unm0WZk^!!h7sAQ8lZk%LWxDiHHkeKxhc-Japw?29o3{KW74_;9 z?7Q_5G~XVj*|VBy!a{kFyJW|<{TK@KcFJ^y28I77h z6G{GEX)HRqiSHjbkVX9G@Hpiqy_P!@eA4)CuR=Lkfg*f5u8emd+`+`|5v*}nIV`$1 z5pzAR;K8qs&}8Dn{WDC&`60GsvXK(lf0@rVerbYBDdrF$eE?#w-bOo*TKf3DHJ)wl z#{eG-Ixg@8O#D$u>&GdA+{aR~Z8#2;hb1+u*o(BE!hRl)n=+CngA*Hk(sTpSSaH#D3_RMnEq(}bXpSMylYkf2*>^Ue1-73a)tD3?F!-wFw+ls>)Lsn5#N!}Eya}I+}V7o5^ z-%c?FQ!PKZu(=-xll%%`+0rpQSMM&-OybY{dq?6m zt_`*FRpH`{ZcH(0Cl+FadwOCYy}BkC^i=&YafgObM=&_5^*XRh?X8J{R_ZAoB#-xlHM1U?_~BORx`K8?`|1F))onb3IrVVrj$ z61P3Lj6DSrGR;c?|7(Ovz;uEXRx+C9V2q);iJmu#2{mUcEui|($7x8 zfx5e}XYzLVtENhN!lxijmcp0e6Syxc4`ALK8CX;Fm&XA{T!GmgTiH^$t|ljGPF|HGKDI!NIAD+mT7 z4%^uMihdBUA&F0|S>sJvoYJ3Ep%|G>54-n)a@bjc@7Y$oYnuu$)1;ZIKF@8OGK&ES*Z5F9g2@8p+XTQcfLgME{a4*{8j99D}9*n=-AP{yQ_)vzSCGFo{Yus z;)Ki8EZOhUEczfu1HHO?=(}|dq(@x|!h{Ma=41k%_y3Z>3rR4}RY1;9v*Ns8EW^=L zL-EQjHC&xhjd!mYqF&^4*l-C+y14^J%}K=?17+^l_OB#9)w-g$tcU7CC+u^n+*LyWwrjSSQol$?)6T z3BD|UO3#~D!tM+?9Ou3e$~U!PON%)O*6}mcAu^%u zB9_*PgSqZz!HZkvnDRv&f)Cfj(7n0D_p~||RIg@531_QuFXV6re?E^+D+m2wQiAToMm(?BmHj)ijqkJA za&0PKagNmoR4P}dp5LONsl0_I&r#$qMn1=?al9KyqJ%rD>&t#etj6;Xa*5_UKEGEv zg2iqAhu~$-3bo68{)}2#S`?NQaZZL z;{7Dw%IIgiwQTQyE8us464TJl#nEroSsszXz7tjCxP376#coGS$r<2uwg4XYZpC9< z8^r8TM#Ixvg`e~s*xhBu(7V`-_^jb~f!b26dG~pzf*B96V4n;9C8mQDUY@}H{l(z; zESa5l%R#-`9K4@UNSgmOk$@fNsBnZL3ky%hm-6u>XZStL3#q3c`d{Jsuxc9c*pE&T zvB0H^tx2wlgz(bME%MAF0cLije$|mAoEWlrpR0qcJH_dd1LI! zl#LvlG!a26CJ2jN(?IS=7TgMo#xaNg!GX1dur>54&$O=>*m+(DpP^|)`)fb6Y>S~A z9#?|)X-N`3IbC3KS%y9Fk>!jnld&wdRH&6Oh7;;6hkoy~AYPq98#cQ!F)u|{=2L~e zv&7hdS`9sLW-Auw?}0wQP5Ac9BP=a5fqlJwg4ZFBg*E%V>8NG=IcdfyCRlHQx5s|N z6w4tT-7C$7nLWUBR|@fbq6CZk?FA1O4Zw|{tN3(57SZE-xn9ekP?5pKEQLvMUE8B@ zY{FjVw&OjvRwbj-pR-6a`a%6UKZ)Z#W`iTLa76ztcx#pqbvoj3Bd`dnF37R4m>T+| zm{A#jJHQoIOixjcITu9Zd&6|B;N7G1PYlqtqry=uav>|*G!u>8rjYly@8HY-9@9t3 zDj?WW0BMF9*eEZAG4vwYdv+C$o*6{S-D2Ua1>f7DPB1kk1*-dwLiI0M@LA?hGgHgy z@TGHb*d!09epTgU%m(PM7k8kzaTe>de@D4#*96j^PNAkmGU$C?3rWMR(P?cB9k0Z_Sa8-D8Ulyq}Qf>j^JyPD2ml1H6kP8TM~{N9?OaxmR^3 z@zl*^*tz#J%zJW%J}C$%&O9G)TdO-g8@7pabaO`gQTl9NDWeP4@Gjx7XyPk#7|UB` z(kyQi?i(A0GYeuk`)mcaXgT5T9ubE&iDb0iIarZ9YYn&ixE5QZ{Ry6ms>AH>S8-OH zJvlOEH{SBIMrWRLz52lx_%`VX8g*@Ap?k-1kuS?oeg7`BG&>9bA{((r+mK1$v*uJ^ zuVWtiH5J<=0*T7z?`XSL8#bxNFb5WcJS7VW?A9rq_A*?pTJHoJ2a$jV4^H?RhObw@Ivopl1cQ{U;}@kAIA zEQa4+%o1p52U3lYBycF;{R7KHxs?1=vRip7#6Hr2oh44hVoWAIF49TFhR@PO`T3|@ zqK~&{zQH3R-^g|-B8CTE(a0;Btj;0;d;^-GSEmugPM*RU)}vU^yfebei;i54^lX5O z#@x2Q)xx)Xt=S5VpXf2*09Izf^yyt)rk?Le+MP7H_A_~4{^m3$y{^X##ZjO$uY?@p zJ@?|i^KijFUn)1byW->5{kWy<8`%>-32d)Wl;-DrDK2}+uz|WD%-Vz1jH!f_qZW8X z^E~`}e;50A&Oo^%T^K*~oP2Fph8deY+4Qv&h;ySQ`}{K>cBqxp@hUvupU?QugIu=f z)d@HwS0;FT+<|@Qxd!7;tijBAr{GA~7`9_mJWh!Yq)t-LP)zY7bgm7Bhf{3WD_2KW zaN--EAF<-BQ#A4Cs##23qm6c184Lbg8%a_lfIB}TkL)@p3-2aX(|L!+f`{!*(xP?{ z)Ov4{JLf!@)?hgXq(;zxJP)Ag%Tx3oq0C9ChJR6#s!&d?xDl_7Z_l_7J%GJ^}a?00Fhiu(om(im#K$hR8g! zLsJZ|d@6vsu1mS2*;ayAe+U?DpTx?nc^5W|hL>6Dbmd!BR@5X$qF<}Ww7Gc5_F!olGRic;NxQ#a5K-_knLO! zjU%2A#q9B{HCvqNDSZ(}FNr0Kr`uw<$YiE|)r5IG8o@s7lNZJ;x(z$DUeLjoQ&@YZ z5@de1k}`cDp*dG@mhD&!$XfxK%WLtaSsE4iC(vu<0jOg6k*ZWxqKwyTJe9H!CUlw5 z)VnI&>hWc?*G-Zg(R>6?3#Vel#4O=snS1=5C56!Udfe;mQxN=qH0`yGq9Oh6+{xxX zytCvth@&Aye?5%5*>r}KIA!4fPvm_*k(nF)R8nRDT{IhHc% zJNe{)lj?-rqk5)2q(#^#^mSejkCrFll$cQ1E?O--x4??e5!Jwt`O#oCuo?DGHfA?I zj>n64mx1kqktA-@Bf3(;8PY4nSwu`SbnHHgE^1y_d%%n{mQ`2x=x^k{CZ4RseygZ zt>IOACKyI{|YvJDZvVCJh`uV&5$NP zi96h;$<9=Yaqp#c@T`L(N3xr!-+?{28OnQJr-OQ{Sg+wO95GLeolSj#ORsq`(N@`t&H+D~*6L8vI!=Ts$)1Lrf#b0K zydB%*6i5722GOFV9*@hY!WVy4RvR%Lm6xsKw#pr%^;ed%QMzroE^q=29@B#?@9|tq z?gH%D`-yiS&V}Sx(N2?oNOQ`&$Dqw8Ip!t*nM60G(L(i!RI9+7Wc#Rcjj0u&8nXeX zcQ*?jxkZu2ic&#Qbug=+p+uA(pQITY<1uk$85Vh60LS$SSexF55@{!K$f6#q#Em7%P#*bjfKyhltXy+yqpE%0xnA?s%)Xk2y*_IuQW;+mIG zVz-E1S#}jbbOyUWu7jKlp3auL&x1CdRWRT^lFay|gx>$jqMd9YM71x1yS<;tcuzU* z*th+-wL2UfM8~n#30YLNMuOWTlS+;`D>Cm3v%U3a1z5QDxB^0Nc(}13?Fd zkLH<1?GH)fkObOq>BXjvN_=PY3~9I(j2Tu%xQoaFCK(>Y^(W%_?SncJ=^uqY-DY?`yc&(e*JSThFqSk?1Pj#p62 zXPylCZdgIF5WmhJ2OmZ+L$=5kz8{Ij6}7I|qwxhx6^*%1-mw@qRRXpQ-6q!ES7B=H zZ6dR15bEh`G&wUQTsnv_u&|hJob(0f8}V$Dd~r7N&vdA|H;<)0(&7Y~f5_|d+az3S zKLpJ0z{z8^nZ=v?f>r-q9A)k~ap#F6w(lN9uP5WVYqQI7apr3n?iIk|sk+R2)Je8x zL@K^}r^tS7li}FWdh*Nr4F7&cux+VcpuHmo0{?x%nVZTt9T3)_B1xPXd_z9fSa~t8H-#e`Nr^_u+F2nB`22h}U5O!8=hl+KpphqCW z)Tei2-_SHF()WIA(Z{d$~mh`i-BF6Mfa<_fNh+AzXvH$s#isY9Omt+2T z=9dUdo@G?AceVp)NGJ+Q`W-NIbS^&laT5YM`{1*|Qa0jRHAY&G6^{4xq5I?qp~d7e zzWcO;O<0kR`}|yC>sg+k^E92xwCw}K`92`Isz+MTqmlt4f!cBwEPIzSw10wR`(D+scdb)}*E%`^} z#U@h-6bT2JPtWnWcR%W>OS1zb$MXzkBNjBug4-!CNnx`N3$qeJUz#U(YX1+o5quJD zCx4=*ef31PjdzK;FRPx7x;kE=}z!At!2A#J}Q+aztkT5Yp&gwAtlwU~-0c1S=9 z{~pTrrBJ!QL(t?GD9F`04c|Mu@UpEf7{0dVR%_Yg9dk>kZ)1nZ==qwgT{8zTk`*_Wbrn0)dIsSkYsrekNaB~uenO<9J<$Bjfe zn?(B7M~s>7lOW?(4?~*Z0dbXxa7s=2iT)1<>4l@}#Hj5F*2NDBFK_=v42?r@^sULX zJrE$-R*DmrL_ne3GJNHrMO}({|L1=vp=jn8tm~N!e?{KFuaB!qM@|8`bH<$O)4l}f zmmk9Hq1&`mu#K*G9!;+-p9OV+YG}kWw})Sb(bTgqu>IwA*nF{=UgY!7Ss7o1vl=gw zwUe6R$e3PuTOEzE6Aod`PA^dE)8PKtNkjSX9Bkh{k>}hVBsZTJGb5UXXCz1A`R$8v z_D)rS=R-d%{xAl}xp$;St_dG2J%H_-+dxk1BCNXaOgAsChru!wLjMG&9%u<4<+l<0 zH+n25M;TirT`@KOE9p~{=Q>`fvhvQ!oPld5wd4H^LBW?Wb4`t)>3bnAUY-b1i+pK{ z%v<8U@;Yue45HsXHJH!AS(wJgp_S${)b<|(PJOrNA=M@--CKhj?wy0Xs^@Wff;!i2 zJcZCn2QjbcGZ`~^G`qZR2^1$Kpz+FS?7Ko7`IfK9jr@ELulvZMSLtpV=vPL6jP+-p z52tZAb_vL?Pgg+X-D4KSe?EWQRaq3D@^~IJ9Z!6Efr}j!Sog$7l*n0sOmtbf^$ z>y95LA<2GtA?u=Gvd=RJ|KTR+{wB%2?l57KjV0KoenWQH;x7$IE~S^+|3LXEU3RbW z7TvTf6As&s7w(Qk)RM75lln?Lp5=~p{nzp06;n>4UI%JNxNvQI(n;jc%jC|ZGK^k# z9N&u^KvC6DCabB4e%+q9IBPFV?JI{B-}VQ4<#ZMxw)=6u~~?0s2&>fPaD%w{+GB zcJFUGddBYLcm3DMq^0HfT_2&we;V935oL0G=C-^-n=O!Ngq`nnFyVI;>u}$UD(?-j zWlWxM|A#|tIp4*!5y}e`d(=3Ms0Mfyqy$@g)ENfJunYbtfgIdGCK!~!vzTGtRj!C7 zEf2uq;}Kfapv69%O5+ZQAAm{E_k)c70>IXDpjFjOFTWjx*PD|C-Ye(B*3W%7QmqF+ z@Hv6z^|L`GWErj){ft<8CP1-9IG8Se1G4oR+$odS@WL<##)n6cb?a;S4s$ka^Q%I! zhA6Uc!#0FjBLyS!TS$Pr2#Y)>!d?E94l)CW;gj4}IJdBZ{Iy~NU7;HMUG^Nr6UDf} zJvW?c7^BJW9l6gM0=BC62i(wsh;_>MSI)>kojv?s@(h z&#*&V2W~f4Vzfm(35$w^9uaxYCu$#zk=%^Uqz&FI3WTcmQQTjf8(>}|iKfZ+lxmsa z-?D7#RHV!WThC$KDhDi!8zu(AY*0R63opGqSi+u3=yYcY#ouR=0lwcEwD1YNWh;(_ z7bEb|D|JD^%Pja%D*+1*f5r(1q&SyVi`d3t2QEG-AJ=zY!;uvloc;U}%;@_f&Qdua z919gtE@TtYR#*v1FO`MAg0F*39Kmb{bLJl;&mGNoWH!22pi;B9LVx%>$@E%H#xHy% zFk0G9_k?UEDg(Xz|I!#H@0&@#jTB)gBWxU-2n}lLd=Fv@31vtsB7S)g(rYn&`j|h%tMK#CFiE=ghAX_X|0t|* zbRcaB$54y!i|b(sdPgkbq?Q{n@e5U z#cDz1C}UJwP=ZQZf>;2t8R4P(9CT_ zzpO%P(vVF)xfyXEfAmr#XLa(X#e~>D+fQ74EMUFYUmQGffF{%o5|Pw?)R9?7Ocin< zpxcPGdHSQAN;`IoSKy8KL@+qjM4KF(xW|tw;bvB!Acm`_PbFlCw&64KuqBx+^?Xd5 z&cwjZS(j-}bT+P3B`8yU1ZN4pfOf=4l0YH^bHnGMM12yx8RzmrL6)i0W{e-B2b z>!ZjgYwo&?GBM+s|MMKR;99q`(-JfSi@Q4g2%}Npu#^l`s?0d z$C^;M-_6fAd9Hz2sRC^9c}J$!{30(uhQhziCwQK3G8#N{#|_5P%h z-ys1-QU3To^%cawF=cM2!qLTW4eeZT4q~nzfLHoC_;kWGdg`AQ$&yUN`-?_6y=Z>` zYg~bb%KQXN-^WCJ)?f0i=R5r-o`WqvFVo;XN!ryX~W@f^uG)c2an z>Hb!93Q<<{g7sZyFpYc>a#d*n7xIZ$@FgWo9wQ2ea`ZogT zO5bldAC6&IkO=2?OqLaXG-C7TSPSCfr;%6nYe4gBD8llC5Y;o9S)c`*_|l5FuS})? zCN9Pi+tyI;SrL5Zyac9OeaCG+hoCPakPU~}bN;S@IL3J{Hp+Dp->dn2=WsqI-aLqt zlg-(R@(`S3xCIYw9-?P2^EqGheefjxH)%G_!+`^hnAX1z?Iuh^1C>y?{Ob`}kRs1X zMc>A0vr2J_u>yB=_H0>PHY_` zEwMu3Oz$|nf4>l7d@oVSY5~cawFK4Qr@%h-_ozIxO-S(|_xj#Qfq6v-9hf2oz6p{z zYp*+uv&(~tPq*S>{l(;#=V6>av6+5VRfL|kZ{Vs|JxT`*W13Sui8t7S3v7WJ`fTTl z=NuDUed&W82SOmg?H5d|69ezkn}SkNRp#Zmit~?{N$1SfW*u2$Sp3FxQZdqm$f~-r zx}Q0uy?g@KtWVhaUf{eG$8rzbWw~-GYxHr+#*x#ez|!6#FfB@;j=Cw(=QENKLrI~} zzX*I^<_l$nzYC{rN3YX$s9o7fa5c{`G?im&Z{MQ0t^zB(_5sXJ`m@B~k=zuIbr{-_ zfs>~xvT1w=>*CFU$bB!qaf?Yi>o+c0DM{ zs=)p{2@qsTbGFf0lx0-n3b7aP->6#fN=YMr2}g-T@L06*jv-6;T_PnL{c(M3qHyC) zan^|=h0@0+A{k7^%UAmFiIyf;#rv&`j*VhR7l(7NlT&dd?0^%$o6*@;pWTb&J0cQ# zZ01}`SY5pgH%#h=hE;*AlWd0I)y{0A$3!^jCBwydjI3}rL$dvdEco6z0E%rFVV;o! zSL(YOj_gt9xhZa3gADI~juS`EJTnkI{ESTeGZtKy24hB`0%z`Qz>b15`o6sa#6pSO zz){Hvfi#DR6( zI0h#3Pmo@D{ye<(A1+w8ku;lk2?w8js`wdlnY7H@>0~wdAFkZfjqO*AIomPw@#?6r z2+2ojgtoD8d#xt-1*m7`2pQ;3_;u%(yJDvEMTBHela`v@*8zZr*)mI%*H;P<2- z;_yoY-!0mv!L`+z@?RlG#J7X^v1AY4)g8qa%Bx|?i+h44=e40i$UCT8B1x6cbfE~p zi^fBfnB71s%t^iiTmFW_K@Dr@&zJ{c{XeMYMqM`Kx|9a>f59!08UpDte{tAaoO{0$ zvH9?5_RS|cyFe?Zz6bp=W|$tPhs^|H`W$;8Z_Il zfP!x*#@>4cKg-u*qs~zjNfPDa?_Y&QMm=Pq&NDb#Kg{n+oVj4r$y{`DKG^&h&t2;K z3PXF$*zGpnA@(#JR7LaY=r`u9zC=+V@uw8Rb>73ty;bNeD-R>B|AW~x!lAgf6%sF= zhrcH^xI?cAj_y)|A2vB;%EzbBT)G-Rw5K#joW zbToHQO$8>1Nr7h0A9|~ygM9Y3;^*}GEbYk<>YOt`Xz>L7r4R95ZZH%^?#BsV2C(kj z1bogrf?j;^gZKrZXq!OsPVaFNb+;JS99oQ$&Ppij7flD@yimtc6qbKb=BnqeK#M0w za8L3Cc+`;yN!%Sso;Q{~>UM^%%U!VhfCn*qkc9yc??CEKG1OXc367YT2&E^j5^Rv? zx#kAE13gm@&sV6Dj+8El(5WOa&-i8{ zoo|CMFnh0Xhe#3)6TbkRqkFKUCJsecIN%rGm$2seEv$Gw8`swTq9Q}@iKTHh#(sBT zXN_fHzF(bie250z^>l$*KSl#etj6GhM%s7vI&I3li7V#C6355tT)gvbl+eA6_Gjy0 z!RhII|LPVUu6KamohjhDM4P3iS3oB?O%s6`iC-^MdS;|RBME_PK1RXi2UWb-bs5u8%AVIlKI z)5}Tm+`7MlJxDf}Nw->@Z>pHJhP_yU|(QBJ*f z{6XP`H4OECL)`8=U@rR^bABF#rLz8D@F<18y{X3n?pc%S)d%2q`XJv`+>2hbOjuz4 zCo;2!_X4`);iIqr;jM%`Fpw@q5yd{KA?#~I9yS3ya2 zQA{!z%Ppu%qnibC?Ax6A*p>JXt|q)iXSJ!qdvQHbEIvRCE9OF_ml-avROVcJjL6+X zt+4JxJXYFjVEOtQ3`;cPvbP7bBL>@`HpmCRl>G}LG^xm}BU?jFe{WbkK4NmbC(TLEhicVk7iEv~*~f>x5&g3^dE@=k7u7MN~i zyC$3>zsP$K^0T8?rA0gsAU{QA(_ly4^ zt13pYKbcLiJSCRc|1)G>wo)Y7atoFZJRtr_BIxj@R1gz%ipP3O^$;}@G_lLW>4t!UWoEo`do$cqY2xK5k7-rErIQ zJJE{b=kHSEg>wqiaaKSSX#VkG8PN&C)OS)`e!CT$J^BLf=#9o5{w?(9ds~ncs^Y07 zNqGEs0qFGXLsOm47~*>mr~Ij-JJL7O-Y=E(jQs>wyiAv^n|crPbL;SQO&&Umt)~-R z-V4lo8t`T7E}S3zu;OY;BToHz4Qn*>pcLBfs(^!|}YHIwY05o|1!?5#V^wQy-V>cJW<=QORXf#AudtXBr)u;G=+hvSB zFpen;OVHJSzR*?T6j^zw8ytI2k>TyK?4iONm~QEY+*NgM$~_yF@bwZV%04CKZ?*BM zZ6tA#2>|J{8(IFG3;6Z)320V0h!@{9^Ief`D4V>TcVm{K#Qqkjb9Ug~tR2a6ww%Vz z#g!Q5F^3o(<9F+eKSJ`EsW<$V^IS0}FFd?!7m5`>BU{P@=wzzE)+>FbJ1cI5!q$LNn6dnzu-s=2giN~%f5tmOnIpeH zNy@^T>@7VMCd%@L`13;34eIJSm;Z^C;NpI%z{|i4%-600_3!{{$j?>2JHF<*ohocf zX*k!ka3a2|+)ePwQKw3GNw^+#0Y}xBQFXUkYB8%59fwLF)O808h8ChAax~jhP>b=U zBe*~NH1Jb1l4U1m!a-wOEP82#vd0#au*Gs*{Mi+7W`7r*t+x{+Jpu*ZIi^hLwt$|> zxqwtqhY#mmfbPLFkSS&f*LT{HqV{&MS=S2JqE2v2V>M9sp@k3}e;`{{Nbjv5#oo|& zu-vBy{-`pj5nBjj7so=o^-Yj9yM@0FEMd}p0KZ6lZ;!YFxt9LfF(6#Fib^WV>P>Qet0Ce$@40G?~?_4$K+wbVNVdhy9e)`w*fD?r?Bk3IAqOxgFV|Mz+&oj4BHb8 z%PZ@tVrwnEkyuK$FD<2F|7p{+D*eQD%|W=KxfXwA>tooHOcL~M8qa`~gTLmFQM@n@ zFZG4e)=hj9@`5RmotXm9BD)1v@rn4+`xnMJ5d3M*JDiey>6P)HF5L zzBUX?PhP`tem_+Eax})(XHjuC3(jCpCEr=)yCE+$1;J}vux5q@lem5fO#>$}&2eR@ zf?Md%U`_P5d`ZIJ@LiFHKD>0>3oMWK!Cs{-@I2^<>mP9V=Eq(^R7n^F+nWhjOluY{ zxZ6kfE6WgCkqqkIH7J)+4OSZO;2+Pino;ATATM9ZxZ4=nX7e=7C*W2g#9` z7vyZQ6x+^oovG~?9924-nL3pdRU-uuanpv&2g<3~Egy`^mEcnJj1klQ1+H_B(pyVL zvF46*P$pA~P0#(vx#jL)gWBM~-VmBDOu*PDW0-7A7G0I5T|QtGfy;JJ!@T07R7}@^ z+Ze_7!;CEOgT5r|FODUTTjOxx_YSb}l&6*cN&=Iyh0qf(3fl}-nD*@w*pPG*b@D^m ztjcR3UgS#0w^-t@V zGJn=2c;XSrT%%J_n6XeWw*DP@ugiv%&(dtY%|Z6@_Z=+jKFc$P8U^lsF<|jdg=d7x za9jWOp!JLhr?4DJmeTnTwiP|VNguxoENUYJH(3!4*nlL8(_>DT(@?W{IxSy1kt6Lb z7$w4w9VS8Ncu&AZsP;IsbwAd)D< zh9>30?$A(bGrpfnoo+z!bNqe6fsn|YO2PH{=i$#B5%{5+N)&He(D0qXT#v?fX04;d z%`A8WS#qcGKkL0to+qmXyWQOV?stZ@Wvlbge6XUFybNHW?PS@{9?G7l!PtjS+1gr^|kP zv!y3?MB~*1@oYtq6t0^t19{?a@S0TzieJ4<=R6kW8Atc<=a>Rq;E_#gR@e!uEHlVy z83TIsm=zbFyB~6I-vB8!Gbo(07`|V$<1T$xAQgUMSSsH{{_rydchhI+;;4&13!dZQ z8fEAlv}6+wy3qT14&+_;btJ!Matl_qqK~i(JmhTHc-bD57!!bd|HyE&{CF0sB=48n z`GSU*b)vJ%HFA;Z(HgNk*#GA^NnNK-vX7LbN)Cf*+q;R0p)L*xL-BO48@yv1=~b&u z5St|nISWr>O-B_o>iJ4DhMu5-Cg1BE^@Qx%)`hPhPsQSkJ}li-3MO)g!E|Ujd$Ds7 z?l!3eGJYCtbW4L{pCg5Pb`^q{`%^66=!K0kfAG{?ac-%o5Rw#IQA{a{B#Fx7xXrWK zr%#7q$0~6ee#9L`t1__N=suc6oo>&(Ypy`erYm%Bp#-=;8pnM+`hjkI;)XlICeg@a)3H=AhJD6-jU z{Ji=sNH`m^0=`e_v2iV3_*jaqzbC-Trb24@y%8SuxkGQxC6vBbhm4hD;x+zz8)U+5 z*4m0SZhP?SE)9CY+=`gX{y;mPkucpMj7&Ud!4;)MGA$i|D_tMqj^PnhKE4au#f)&D zS1PW#><2|61A-hE7gDuyA9=sygK*W`TJq8QFOD>oqv-*+Q2qK7yqdL`HLkiN)ZwJL zFS^y#dvhh8e9{eRCMyILdij{P^(dWQQHe(FPAph70G<`hVuk-kkesgPR5_sluf}DA z{g@@BPVNmH(&-fFE~*rkOc$cp8cD8HSpuhx9}RcCoiWklB21Cb6Ik`%gi8k*%-UQD zhi`~;`mu}o{q0>i`tc!l)`+pTuyA;Je=WPTP8wwAAE#R_w}5z_BhWlwFblGVd?jUM zHGSl|e>BYPzlHO6Ey4!1r}SO0Kejmx(YCG*!2`u!ewO#SqWf|Xyt2svZpL;n<0!AKUlC*Z98Xg-Vb2~j{2Hvm zbd&yqtNkc4sbVU;%g@F~4T>bcV>^%)^>ldNet}bN3l2Xmhs1T>pPa*>Z^l1kKrb0-p!?^Lwo3q-wouAYYpUPhO_*o{X&m3*&wGmhW@nN zNm82clajNeprvDoDsunGkNFpI!S!;|dfkS7-Z>L&cXWfw!vOrU^a&Jg@eua%J@QGH z%E_^*Q_v(h2k+I4;iS5Asok}27`0a##*Dg(&zGBXBXd8JaFHeWN%d`oh0jkCqB)J& zKRJLUZ9L0(C<&Y&NTH~iC4LV2il6kBgDvk2=}}j~*Y0LSe@zHHD_MaTefQ)0lcLYKI@~_!tcus z;RoMkO5NlM=4m?It>GIm7k0Ax#SiGPVjA!NmEg*HjId($BkWqX3+>)Yas6`|QSbLg zAg{9#LdN0Bc)kC3oXNXvN4aS(>~B?WMU&2MJ}<0_+p9(~uX!gyZCMBSUVB0}>`&zM zuNDhDlyz8aVmw=zJcJhnCrRth5JGfZwJ+m(1nW5WVmQo9)z_FN>rkxz-p zIdQbp*5X{3H(-gk3AH+Y2QEs^C-tO?j9EAnttJ}45?3WOoYg7x2>n83L+5jW&j+b< zj2|B4@BHQeU59!neLw|*otskVi_U(s&T&5uG?;R#zkBKXIt$djUJYNRW^>z5uR+ae zJK$T)|0p^Sf2_YYj@w&iMn=*OWtDNC>nnsxQre3&^lPU@n~3Z!BZ@SXN?CEA>y(wO z(judzqS8i1%kzEy1Gg9FKIghVpZ8mlyVVd1$B&ys*NBg>!h8Z(TGRY14o0&cI8XKIgs!?bZPhU&iN(;PxA_J;N6hK3UtKjkLyXfKVjJxLrvP%9eKH9!CDM_eZ-Cw#+VYZ=_Co(NlVucFq2+i*3pSCF#q8wOcQquwte z$e$K)XD1bqwAcWUO__i-&SSx8gC{QGGe%4EXOdm-=9d5%*jZ0<=zn zCqFx&>!A+(Jn)cAlFNhzQ9l9Ar0s987RpmCK$d`j!mv}q$=TdU_iA&Lg}&~ zY1;?*`Rgv8SJGkK+dq@@J|+BT8NvNnkw!yqZ=_T5S8@q@SD`Vy0gum1#k=+;7?RYB zN0zGcX!3u{+G;5E?-f6F((Abbhl^?BAnEIDsH+--`6u&L|kCEnWonk!$$o#ptYO#Z`g>lioX+KLJ8kT9!Nl!+zQM+T@2yv zUvL$h$hH$n_!j2KM2!Gt?pMN*^~C}u-BiJvhqK{Cbq(EcJDGT@41i1VbTZg)i7t{C zv6O%BEZ2O6~G6NyZ3oN@dC zZEKhh&ElTmKD?0qw=fne_HQAxzD?ll|2xVAALAkTyA82^{TZhoy9Gg8<4DVyJNSv8 zi)4APq_?Cy1#&}XtX5Qa@lyKBDxHcg zDuv{VGepdL2D$ej3WslmqtYxPiIBQ193}gjYIm(CRV#}~=$i>tEjk)~bT31?s|uTM zEXs;MY=M6h3{bu!AJPuCpjOT_ytzCP)Pv(O^TH>rn|_4^Uyp^_X8`furreOtOH!vC z0$1e^z@9iQ?(lX4&P?tYiZB0zTcj;u{+wtUbBoWs&7O-=a`yPGLX!jz??D%fG8)%( z57W+xUpDH5~lL8^XVPryx|-f~}9$2YJ6c zl+*7ea}~mIQuS@}bd5cDU6tf6_8XGZ`%Uq1eg>T+o=CTyt))$=S}?9zgP{?M%Z!0fs!^qH0?peI#3j z8bTRP<#aFXmr2EgYFA={jT;OAoznJG-K*#*;vok-J+FZ9O5G|W)A4F7o7jPDz3I<-!oxp~h=afM)w({fSoo`fSFujbOg!H|(nXO;Q3`6S%9 zSe@0zKc$+tWtretEj~K67{32H!48YM!omFx?C~L2F53DDIPEOJ*fpubdb5wvaCAG) z+ab%Dh`pif#+&nR)CAmQuFC!eUW9Z$TORWz3MW)e#s_iH@YZAuPT0NzmWXb~4*x7X zaP$m@iR$pIi7@o~6od&{zv=!$J*NI>Htu=#mIP-0!B~}25~f~?W1swiX9sOyWAT8n zWThpyu>6y7TG>um@#_z4HhDx!H~zwPeg6@u?N9K=>QZo9Hw{$7=3`J<4D=;0Wan1d z;MCPh?9J7O#LejefBvlCnGwfmj444=#R%1w>cOQ^#7-ga#1UqYO!YQTQ>Qk>ZBz|?d3 z+|FwL_tBb6j}agA$k{;>BYSb3vjNxg{wcWqt%uBtZVu2Rr-OAYoPT7yr+=71Rd1)Xvgc9T6KIX;fu_-lDo zdW#b3L<7njNHZr{H5A=o%)Ou*+;QV8m_2s6?JI}#c<0S4Ow#1ITO_w6^qHngGPDqA)a@UT<8V! zE{any#qAZJ#Cq-$bj#k1vjjwZe0g^* z7IdDY-RrIswVMHO>pv5y{H+KNs2^!=Q^lK}!C=yCCS2@Rjp^Gmp~dVD{q;--A1!Xe zdW&j7TJjgv{#OofJWj&gZfU$TITvs2T!eR`iv@ZQKhYawlJV~vo*k^^k3?f1PQEsU zoeK$pQ#+pt|CnpjkuJO=>5>*2e($CKIS&bs@?3|=19ea}?uo!@q+=J8j_IZsZIpc^f$(J8_RFWyV> z$(xMHti;c9%J}03pU>m56X#t(r;vujNob(O$4*ei?S&JqIpRUt!;y0`i)_)4n=r zf^xn+@JIbSPByfMUH+!b@5m|UrM(TQ_+vDh&pUoz?!aSGCQMslGTWy(3&noqQjJ4u z@cm&CZAq4a#-9t(V%#)#VWA;&(OwOwrzqef{UU5^_JHAH71k81!u6LXL*+PG^a~Ks z@S-YcteJv?+pf`-rmrF5K@qH9a22|j$#HIOGTdO}Vimjk!n z56Ah=M1j0BRzJT)cS}{Hi{m{~ec=}knq2^IZWFjPax<}6y$Ju!NXIHI1LAY!0YA%A z9*D4FjMU{zmrhnxDatp48-THQpTN;MCs5~( z0)%CT2+TCI>D@AZ1_X|fZ^1i8tvA57x)34c`o%9gnH!q)T+p3_EVxlOk}5 zv}b*n!*Ob=A$fO81WtF1r~8AxQ+A<35a$p|j%K~Y{inmhAlHt)o!&+=CtIRzz7ej< z$N^PO6W$gv9Pu)VWY%9p`SG{OW2r^#UbP#L4?6JX#4@yXXd)AIT)A$@W>-NJJjA z6VsPxK=i|J_%QPUy>=*FSO)?B_qw9*&rp=f(7?n%-rxP>D?GHULOq@%ViHu2am5y3 zS`vZRr;R|D{Q@?rEC@&cz6yU9SKt%T1aibITqs%;3%BIQ5S&m_!x|)IMK^8S}v;iyL=aA@Uu-XrfP-0tqk)&J$0Z0_$to}agVD5?j$?GH$ul?)kP z`~=s}ZGqsmwXoJ!nuWbp;Eq(B1*3vay5IdN+I^|Tf&E7@&&`P)O*jA%ToVnGe@SDE zb@12M3~=9h5B%)Hkt<&bN3?ZVo2f6Zc;SIP*}2Mz!3bL-8?8eN+Gldk*09L`mopNU`^qXEEU)Ar@$d z;NrT6B)_-{+`N+^=Akck=?Y*LpW`(T$`eNHaKlY&#L1OBao`r8rY<9D!0yyOOmT^$ z%XU14#(kgpe2^ZuW`#9ploJ6Z-_>f*Hj@XGJwT`Ec6vHkPTAiB!A1Z10MHq zK-maiZHywFQXQ}(vV)#B@h&Uj7Y%XdRrG7(HyZV#02)_)hrbIamI;haz%bdJwH%lZ z7c{z2u=yH}s5WDpdN0wAZBFnaVFQfzT?X$)EQUL`F5zawznGS>lr>y0h7Agf*}_%H zxK?E*7p(OFmQ#LT$a4cmd^(7h*CufSD|0@>@4&5UPoUOHIq>^G5%#f69n~Kk$GO9z z>~>%RhKXem<(^71a@QWb-**~%#@NIB{=%{k-^DrQyUOr7#~dQrGWap!CRlk{p|rLL zWL{W|mK*d)vRo%z{P>&PZ?a?_&HqU2JKh2IpAHMicjYF2^rD+>ZlT?dU}YpfJf3oBq>h_B+)u_m(&lu^GwbwvQ{_x1XP}c%6rDlcVXa-tjnR#Xf5HFa>p+ zkHN!kXL4BcFzxvGnEH1`!kR)?l+4e-6@$vGFPG;dIm&Z=?|A0X;cK>YRI1>&(_$!@ zA_PzEHWcA|Vn@II6m*F#;6}TRV3l(NAucK%KD-HnJ?muH+VZVf@T?jp$DF6XBX}Q& z|6a(68pndtlQ2=p_Yx1uF|FO_p`n83{3-Xr-bL5To;_+I15Y!_p@FAD--;V#f-*vI z<|{hks4{%0eukQ_KY-P99b9>=3{#uBNb|$zG*tKt(^enEW46BR>=_$+!rhBMA2nfI zQWS1}Cm|d!76?VzH;A&}H}9(TfTNi+xi#H~LDyTG{qp$u zvuV_paGK_sBsg;E2667#%O+&VFn_Tk9Js5?rVHJh_Cr9YzRBqy#U zPJziqR*;TS+u7Db>DXlM3O`If(O(Hd*vdmUtWF;w>&)DR@7D9P_is~}I=w6)fjp03 zr5>g%{DM25Ok-8&Mu0bqMLD}@aN7_~@61zTiYwBguZo`?-{tQWTLR!gfDRlq96{7K zPY{aQE3zv;TFCJaH|V&Of7HDtM$oY|4_^(3!)`$wyld^pr)RxcMC>HGx!^7787Tlc zl1E$k|Aq0g*`#n*tzgyRRGg@Ho&N63f|rh3TxrY-w7ZdnS$lioa-c5f@hFNdnl>M& z+5LuDw#wW&gPo|mZx!A?|AqHxRO5{yMrDo%(V4H-F?k&i+8vtr-X)S5Xk0 zyRjP1H{ZpJ{x_j`=3il^HprgjeqzK=Tr_RHK@USBllT;C4mF%@*Wc^qWP zc3@)SN3vP*9=u&Ym+!EsV~o2sS2SILGuyYGUC!T%qqhc<@Ae8TDDbFYEv(@Tq^7_N z-xhTBG{&UK*RjD{k{Qhg(2qC@v))8;i&S}+ZtFEDx-GyJ$I3zSXg>CzSPbPOtEi-u z4A<~$2Ph>(!C94eFzmhx^qO|z*xZrKG`fvCZII{GjZzVoo-iG%l*=ne4kRV!=ppZolCfn)$tqXJ_w# z$nRIERMTjTdbtr6A5(!(9mmnQv5pisxpHjIbG%o5isb7Tkxf6(!+X{3G{`OzzxS5m zj)r~U_G$=&<-G;ApLKCuW+{{wjbUf!EW^^>r{GeV0jJbbgn9vIG03}vrYntwS+7%x z%AsN?@*2&%3j*eZ3QykhOCJ8g3fPCalSm@Rwp04TDY4o zo3$4c{$zs7{O6Qac$2>!*I>pfBQ%T2fEwQ#(&d>(cYUbEh0(zX?UT8%SqpLM+z?ba za9yzBW3Axp@~iYtt~yigo=cwI?go=>i6rd7Xu%rXh9Rr@x8q&DXS9vKmm7V9TTjlh zWyKuIudv|Oc-#f4=5!2JbLLF^cA@4mMb;n8`!PNG!6|+Kwv2Ou5|er8#&@NwE2D+7 zvW+C?xGZ{}5n*u(d3gA-0`IT*Lar~b5hQI4C;m$$AvJ71kXNVC(ar#KdH2z9fgIcn z8^_*lcn1$(&cu6S@$e=71J2I;4uWP4{C2B>JX^LCU4~uZ+N=NY&?5=n;nfQ^d_G{~ zAq}?1{0sGo<#$mtJlWNSGjL(MBefY!6BJ(M=Aid^DTEzXhrA+N^p?q9e6*ms%N#PHB<=-Cbv)%!j@dpIqZoo+dq=H{$phYPW5=_#5(+$WX+;)&tS%_b`WJL%VlinrRKAwSe3OK zUe-59zsf0S;wQ-#Tx638bq(K<_LlT6RIk@~wE~+-XFH2F~gNZrY(93%|j*)7> z(x119eDiZ~9gM(xjeY1h=M?t4?Z@ZlY2?Z42&Trbx|?o>z}2iKSSA{`4c&5OE)&#Q5nWI zOyumJEPy@T^1_ufRE68shtTqbFUp+A!xiP`xZvP%XfqFnTaA&ZG)0At9rX+3QlHTG z@800AlvD7NvtrM8s?rqS7+B&Nhz$~ZnAF8uVanL&LLDV_;-@+Yrfw;)dbcZ=_i_ts z`}3=;@nItFt_j86N$=tMv#-QQUyGS59i=;4KV$b?JEpq$5;%XqCpbOtCDsLrlYcuu zpwM?ZJ9V`bPd!?~j)Y`ktBN&$7k7c0;XsgF_YlH`c`(~@o$xI4 zp^eHS+zy?SOyYJl?v}ZRarTp$*47Hl=fiB)FnR|6LUR8fUpDy($(%e?TV_jW##3Y{>?Owngu2~0R z{EWfK{Sw~ieR*vqFR140x#;cGkF#`o!DD3xsXARq%$A+PwW)L9poXB;~n>cQPj&_kVT&(W_@p7Xw4iXNf!p|xxWULY$4BOa@AU+$;FAxRl7 zNc|K&ldzu78raF59(Y4l`@Oj)EietLygF>RNPER&w5wWFv z(EFn;x3ggo`bZA;$v>l(X8=Z}dqaq6vM|aj4I}-7g}pCY;BjaOxezDH^fhk*dp`&6 z@$Bl)NAfUkek)n0sRQd5nF2b@!-f(cyL-U3)5 zYYk^3i(y3UeA2HKh1XN!Fe3g9NT&ot-rEcCg!?A^IxiJG3Rb~62}$-5KH}P|DL8AY z93DJ+g;e-j!l(Q`?0fJR%H=D-;=U^p=kDUu$Egr^E(X`?@vu&39I}%>px%hhVpOEnR2*1~2jsT&LhC&y7L>((srv&g`gJliL0 zR~|_!)xcf-o!E5396qfYjZYFo$ui)(HrWl3Vj#=5^bNt3$N+p~SV7OK_t^F|Z@}ca zZctftgU|L&=jMH@#g_o+}Mn-bWeH75a!jkbmpt1#9 ze9>pyK>(BgP~80mH2n(zhsQq!Ti!*$HkU8>?%f%v@L2-V^%VWgvT1u2#ohW>uy5uQ zd_HH0T#Z`>)_yu{=fqA_RkxzybQ+$>d4jd;R`U6MKA)cML3F*Iz;@Sjm?A!is=MUK z-*9p4c2R}kvqF&m>xd5V>%nJ&1-R_zS+h~!$mJR-*5TWQv9g9JFlv^(`5kQYBS2nN9d=F|#OVXIxXDwFnP*C)Q&u60XkNsPSG3R}YXN@j z+z%hFUdB;|@4?yf3*Ix}xnYmCqv#tO_C9U_Gkz^f?jN*dzg}8kP|H!y*kn4}(#ZGK zWyHA92rqP)5yoaMKaOf{zU1PaOH5<^8m`ShgBt5*;{JuRNKL09sCc%4>Psd1@>L}~ z<751Z# zMiNS{Pyie59NzumC$zh=2S0om%{fdz17YqWZ1Ms*XkIpvrIqc2fXT=PA6!S<0!!*q zo+wzy^oUOZ|D7Iq0nLI_g%#tvp?OpVS)R8MZk(C`9`9tCT}?YIyFQb1SvQIm+d43R zN8Z^xeI$#hi9prQB7(!8&I)t_qVcnR30<(nime=X8pKsZ8m{0(1+t5HWrr*QrO>X4Z*F6#HM^P3kmxKb!SGiF0pW)vz}9Sj%TVA{hrI2 z{gGg^wum!xLvvQU^#(bnX$W5wEn!c4J>TJ#VYF69_{D!VPLIibKG4BvGJae0F z%pHlj)3f39#BBHgccJgll+uG)SVY3w7XPGocyn#*N-Ha__GUk zKM-RJzcu3Nn}u|Xu??HC_&nZ=wPX%nFQBbu8hmrCpGTDyF=Izg>SD(1a9nO%NUqojX#9a^7(7OueXvKI7M+ETNG+fufpx`rs%9rsSS!l_n-~nLG0$CWkxeUM7oMf|C{72^hNy7im7=nIT8{OsaKu^~( zI4b)YBKI6bG%AK1sU2`mvH3Kt=g?vwd?iH`rN4{t4q?>hySg@$)$MMr;nq z1b(&EKHrIt1Fhhrcsd@ixP>OXPcl(Kk;P@mac{nSgNqLm@oRDsj2jyd58o6Dz%mWH z?p~$udh<#Dq3K-0(~)Qpdlf7PrHGB>O2}0fVfE=w=u^0)SQixibjRl>qImAdbWS$r49^Ch z%EJ02p*6J^wnloDBH63-`+&KF_BRF;GHPw2LRBFvAmU!n8)+Q;i z)1^&Nv_*x}ueys)H*?{XlpWL7b;tg1#n7-)gtHN=B(c#dwvHnY;-0go=xSz-k9If0 z#usc?tIKf(J?@o?mRD@3QR#i-Ut5aE(gFUWtDVSSoH*h#~2&D?N$C$1BNJ7ekP3wpT7Fb@;d#JQ4EGv=qdkg8pK3rl^@ z;B1)zsI6_{-NM^3y2}=8cebJUqf-3RbQUZ>mY||t7Dz9?ZJV;W30tT8P_ZgiRx>>U z;!ntNPAv_nq%@YbY}XdXL|r8AMdzTlsT5pJ@^`U;d2r~47!ztiVdhw{5)=7<59EB645(?@U!1`8g^2#QTWzSJTx5}xgwc`%ynN})D zeBFj;`rcsf3~Sad+7Hqeo*2FM8ydz8P|LDYIChr`u1gl;RuL%_;eBl;`}gA!S#22c zK@(TT^DbIrJ#OcI5gd4eK$Eq&GLKGF344$E8b$QtMp>4d_#WquX~Q+mGoiucCyXk2 z26}gS*6;34T0gl68mbfM@te-zW7z`|f!V0wVaBnfa$Ps3#65qRLeA-gRb3L(-H!Fp>WUN8+t z!A%vmTiK1dZc^o%npC(=qBGduOA^=>vKB_|mx5=!C*lTEq4J8;+0=-&@SkWc-6HXV z#A*k?r?^6l_mO87_>R__j)Psx7SKz#J_+{M=;K|d&v;dEf~qg*KgNqqlQfNR@@AihdjSmbNU z-F@{3TWLLP_uGgQZ^#l^w;S+5T?*f9&Za8QtKp9Bd93iBg~8&=FnY2o_sYT#hcd0; z!`Kq~>W?CK{r4=PO#VVni7m!cL9*Og-;qKYpA(pnGzZ7o-y}`28CSVl;BWuyILD@( z_|Pjj-To|i`_yB}b;7xC^MwcV#$ZRwZ^879@+@Mn7#^EY1nrWsuzaipwr+{1yXqgK z;F}vYQ%)6XbYI1u!Yja!6v4#)2`0~$frivoaQwkGH2bk%_&|a%?uiV2ug}2P;V^-O zQaHVTIbOUpfg2Xn$N$6+;BWJtcyvoL-fTDnE$J~3x!Rbm-u51MoDkrkMaPX-Q&n=YV?&nsLw#$9kJo5zoeNG9F)l0A`!SbwmEG6Z6 zry%_OL>xG(ilf!yVD|wjDp7YB*0m4fe_@BY>7F*^>(C;s^_>FEQ^Y{?);2P}w+C`3 zi~(m`dm3*xTexfNI~-b-g~cu7==BvTSW|z8ri@FbizQUBMr05wu2q3xPZ9Crc@>tg z_6Tq8A-Ln)VIouNi-QCFo7Jik_Q%VCR;n0XKF@&5&gmvn9tKR>$P}Yjf$*>Q3Mxh? zV)SNZ+&`qxy)98>*{?5QNqPo(KWQX%j*W!_zPT{3VJocuTS*dnyRqFwj_T<3;=hwF8h79FRh*Eq|<+iAypvD*B4@L*HnIfu^lf>EQCXufDK0*s$=jaL?FSpx>eT>GO zuNL@W&<83`Rni+7fiynqBsk4pkMB7(dOX^Y%NcqEQ#Qr2#f9;7L$$u(N8Aqd``b-l z^7~7LGh<b1(G8p!T&kp+b>>h%(t63WL*P~W+mbYy_=wyDZzL6 z6EP^Q8N1Ay$yASij63@czfRC&HlJRT0pp*-_NaKH@sTn0RFiRu-{LWg=E~pjec49tz$DK<1fM`s1wcz)#DwU*9EGriS6G+~vqqcgZ%VUaV#& zoWmCvxDfsjw%E8p2)}db%E^WTKG)dtB#&CmR^meBs>!A2qHv?N1#7gk!0`b?moOD_ zTlFX2wOWLi!%h<26kq=5iGc6drC8;Jo!BFzBBj!l_K~ zZTW#3jwglldON{-^96eS+ps|XQaHrSx8OZCl1zBzIoR@ViEUXoF}K+k{m0+KKa0{> zoTLiVf1%BirIx@vi`DSrmiCG!Ma5P(-haRzfI@i zps^0X2~C)ukOA4biO}J^l~mv1IL~MIK*sMCN&lRLr+)gQv*|1RJwlYqg&|DaI}3*A zi7@ZA#biV2HoSeg8uBlT;%SSI`0--|`Jn!mB(J*z;|3V}A+So(02&dvh?vp$eoQr{T3xIk?kUms=cqlWZ&=q8CLoF~55= zEQ$Y$Z2f)ce7GIXUs(+%S%X;oR|E4jbMgDo7qUarnR-d_d+{<0G!Ri^o2L$fso0U2gBuM(VHa_Pd_NpQS02+Iw3(vQijp@2WrR_6PH}K~@Cs;D#^n#md(l2LFww2|<}Se$7jwdR&ED zXDEaAM`S@WSOixM*~07%W$<&73G7!%z)2BTFgFsIpJN@d%{CH@neYIcZ>xc-dJT0d zIK;bWPY8bhilu)<){rHGMqF*f7w}zb2&a53AU9((D_Rm=7HD=6Ccd(w!Ks5FzW5lP zi0el2HVtl`MlICe5@i*Y5|@g_7X+u9IkeeSy;g&w~ATzGxbD5p|aaqlbe6OVc}x{jPth`;UEOf^HPo zw|5#>_g0a;F$o3rDW6Ev%GsC^Yl$12W07urE9}uyW~+uz!j%4z(Ee&WRy>IWH~VyW z(fk87=URhqVjVHXndG9q7$^L`k`prj$E3FCyv#1H6 z@bn?v`d|!6t3vQ*o;9fdQ{zP5Ucr|e-qRpaU7GEFl1wZd6ugsC0F`}LffJpI+m@+u zPH~H2&5>!`f8nM0G$t3m6&P}{)%jF$jS<@N-6*g0OOP>2ie1oFMBnrLonmPg_u$q9 zc3*c3{`=z!7gtPTFRXf@FYE-{S{Q=zH+;}9?JJ+HP=ZLMcUWSYNEJ#ggqExM-{)vK zRMWddOxp zym=K>godcEE5f$-yRj2zWO4VclcYi^1oc@I%-h~cFXtvhnZ^S8)_x0{yC$0);k}+a z&pgEfzB{+5eG$e~HsQo0{qXIH6*q$S-nMkupx>QB*kmY8CO>zfs}5L`$NyBh(rrZ~ z!f238ul!4D`z+D$;uYIyo<)8?Y!LOIn*P7HfN^WQ1)Dw|6e`K&30K~fK_lf-nqoeQ znd$9Ahux+$*{qzN%aX$`#odrKco$PGeqw2tJC@Z=#BB$X$ll*sH|5c--ruNOl9wH27zN~06l%2XZ56i!rO2H)6Bg< z|Ii3__kIG?*>DY%Qmirilrb0kXAH}XOkl%tt6-8r9jsOhVOO6=5pPT0`4t_5F60_^ zRsX@}-8mFgZgJMlB7;0BIwXiu{W& z($(OCC7p0uDd?Vho%&4?qFZ+ZCR_3>6Q?xHajL`NdAIp)$|s`KSI0A! zr!%u1!O&-;%Z=9eLch=oV&XS~`%jtCIk$q@vJ_Xgd4C`aPYeSRCC&mvD{wd-Vfmt? z5Tzs(%t`8ppfSPF@M9gz?;Hic2IaZmqdnm2dne}bAJ1>9I|SJiYDn#0ZIIj)DCEon z1?qQ|xWDIr;9TAH;B9>gypo3Lc4rH8oLV4Ajy;HzEWcsppDcKL=^Q*M+RQX=+L0q4 z5=h?XB$l}~0m9@`;h{hbmu0TNJ+&ESY2WWtBkc-)ZrP2CCSK$@j&mS(awj?lD)4zq zWwtU`$G2u zHPAVj&AVj8Fk3i`O*Oou#;=Dw><@ry)`H3!GHgo15&ZJ#Em^!HPq?tI7)zG6(q;om z)beP=8SAyUOCm#P`{E`3g>Krt!X9ocx`Ihwe3p1e2d1WJBd0M$ou*{KbtwUN@R}cW zPn9H})XnkczLDIMfm0yj{1&Q|O{hCRgP8KKjrgui#TozfxkkTKDE;UL(TB7cuUUa7 z^H0FHNuQwS`7`iuHv@$)G2k}U9Ak9;;KH(-yz3wmcRpH&vAwI=M5>PxpZJ|(sTiz( zw2mc)?1%HO$HAMvv+(f>-&ZKCaShJdentNeV^BS4z&q0mMGF&vSFZ7oQ}OVJ@l|! zJIL%Tq*b$r$m`*Ug6CtpK-%f6h=e_ldK47V_IL_LqgOTBZD5G@| z)|oz_jYB0kb< ztB--11D&M%)>f*Vc$z#Zu7o!O)7cJXHPG%*;&!2(XY zW+XiF*@FjgLQXS4R#8v zaRWbFt;qjNB7gsc<~2b$@jnGF;9Nh9l3NLP_dP{Px20t>y+*PA^l~C5_yg2fH19}2(BYN5Q!1J}!(L;X)l5H%xH zxIon2^`oBqGXt~{En zZ|x((H?!hLNGVf71L=GAb3}s*QAwqN43$deL=%YyB`Ha1kcj3a@jd%FXilXf6w)A~ zNdr<+_uSuo|G0O(?|Rq$=d5+s-s_xo*4gXX&wifIfJp@-z%TeBxgKZ3I`t7ru2Kec zCci@GStm$R&lF54YLZNF>7y^@M!=bEBK|u6`Q=`ROKY34fweQ1}qnA-~^|sLKXu`Rxw$h+W*?6h;BkXmLMa}-MLY^YWh0im` z&Qf;>bQniu6&7N^#a(3naywRgo+9ALO7y#a7O%UnfcCe>)Tm7ca%V%b)BG`=jCV@-SW>E~Ityi%klXssL07inC_y)L8S z*@^YEVskt`v$G>QrzEgSU}1PuRsPns{`~D|AKLGYpz&mT0=MUd^Q)7Juxr;P{JkXs zWo;awYK9@Peenf?ZW^<;OXPTm-gf+acM$Q9-v?gKnt0stDZTW{0!f}K8*CCTVDPNi zghO*lN8xQa)S5&z<~{{8h26xaVh?veVGdpYwh!(n7U0UAMbPeZ1$_U!l4uJ#Lh*5T z(w#k6&?&S5KPMCZoo*2*uCE0(uO~ttpIqW>+Jr9pE!f}ewb##hMMDwl(+@jCEc-7Sz4LgTm zIg&%|vZIK@T9kh4ktj#6-ngxaE5>$j^7656m*zt(&h3 zXN;Y2KS(&A&ljQl9DA}vd=_3#?m*Xh3vk?z=h(SFO7ckE0zaNTNym1+CRO`waqnk> zA1=kw(rXLwa_9=`Ke-sV`+~ln!)xLarO&_nYR#RWITB*!ZK=5W5{zv!2bUx*^49|a zQ@ly2Yi`UYwzoL0r~MmMdU=yut(A^LKZjz>_e8R5_hNLsdH^=x2?hJELY%v}9kM&K z>8V=517ijcQj=u(Hyd^Nan6Fru+a3W}CC_>Z8cZpV%fPSF5r|3!tgkb` zXr&Icnf3;jxL9EG*BK;ua~_I1OxgHrjw~7v;BD$pq20GE`mC;7@DBYzoR_(pNbHqr{{;G$K`h)Szx-@#aLn zBtw}mi9SihMe+QX3>Nm(hp>S<#pt}X3?#=4c{QbVknvcHt}}DP0~y&6abP06$}uE6 zJrgj}dJ>KgU&g2pSPRDcN5dwUXl9hlESNFaf}h^q0%;rbQ2RzIwN$(aS9%N}!X}q& zun6WR#nq9u4>h=~nkZ-cfebDK4-0kqm(?qa02W<$0Gno>~afja#wi za2%W>viLY?AUk-_eKlg%kSgpIqa(Z#({GGX`twtDVWA$PB4mJb{V+L{&ctk4ys_OF0h2g@-w z?IaE?86}(<55qfeHQ2mWo|O^s?bj;v!A@WBZH+ucGX<{xtzbL${s(Q=?CU~I?~S8= zDs?n?p98<_vo7oN))=L{zhT~n{`}cRjdau@YrIe@;8A)c;%lxC-_9?> zHH!V&x>E{Jv~DU^9=B!pKT?I2LGy%j9LE@(Xh4h6r=hTS3Ems_lSCTWb1Um*!Q_Ji zJI`l1mENEPZJ{@z=B^cgtxTv33O!h-k=D?)UX!<2FOQ#xE3#S+4+YQK0(hWdJz?RI z#Z*JkY-|0h!V{kY&ie5yW?-l%_RO`wMS4%kyUsM$v8Eqy{8u2`CwO4H9@Ar_<7??a z^)#r-Cp{dKrf zJQC+dZi3q~zll+~2uIgX1-Csfu_ z-%G}1ycd10oR7Dwj?lGw4&?Q35tLUt(J9X7v0}n$IBy`sry8mA_Ge-Rox}xXkNth9 zYxF@M9VzzQ@K~r?)XlsaWCt_qEm@BxGQ8#elW=yj3V-Ef2~B!+5d+Hyv01uDVO+-( zT-K(?9%H}KiDj?J3+qS3Vu>^_Xn>QAwJV_5HBRWm=%JE_oS+f%5HcQ2#e{{jd`*am zZF|~}k9_c*&R!XY^IKJTYttHL{NxK<*r4s;;w9)8$+XgPl};`^u@}liRoO7j{z8q% zh$x;8M%>k)OU)<}d&l-AdrA1Mxa3Q}LQc9#C$0I@FdTIk%)RRs} ziN~^CdkW~$ncJb^+ZkB?VhDS@HXX)0hhcMa0(y>Fj!HiDz*Oe|C-@kdo^Zq7utu^! z(}1e{QpYm_A6!n*E>d{C53fACj77&Lkopr&d|s0&^W}wbmQMIawbS=O+vWk-vosZY z-|Rziw+U62m4|^k=Rnud1f8OqA$svqy82rvd6xuy^R->%(Up8y87Bp$QGh2lJ%xu+ zKgche>txl)3GDTV7|a_Y#+GZ3@Qml4?K3i-ziu5ZQ2tyq?a6j~z>^HqZS3lZ@xgDnC+xs6u^U+&zd9MjL z$=I@_3)z`S3u!(EDW#e{*&~)**U^oS%7!id~urAAsn)B0o&^* z%VsV+i?__ifc?S&aJT6rXA|JWy^ATQd(H}uOJVi2EG-)fE$=YDmgaMrvkw!`oSP(J ziG+;ZWRB1>3Fk!0q1=mHGH>S)wqf@s@_d>ZADv%MnyJ!PUVP%+pS&#j>y@rZ>)!FIOTm+uZM(k=5uoF%?I3Xkn_q&ZGH~P!tR&Bxa;bI<> zvEGd&o^Zi0tT7+@`7)X>uA>KrE8y2NCrGp6PB1T$Rfv^VlXkRN6A%#<9Jb2aH`qVg z^6#s6z#9Kx|9~*d@PJV7)k~uT!on@bSo#Jo4WAJ!YqI^fBxr{@I*-`Rm}T9ke_0#T z(k}u)S+jx}Ugs)acrK1JjdTX1te=!5tfK4KRMJr=Oll6=^9nd{S! zC96dyz_e%6+|VvYMqw6gn=pkoXcv)7N?P>8p4lXSo-s9Os-wL|)1f-PocnV8FVtU? zPq*kwQ%yB#YNf$bHII0jnPv!Lqm$&0Vh&M$S4uY=^?)547ePbfcrtD=PrWK~xYm+A zw7^P=(OoW!%d*_)jmr&W*PSXdxZz{T+6gbH<9b8zy`a=&^S+ za>r_D*`dpATRD!NT~LX1<`%j;c^fHD4#vACcHG_G-ONqfR4QMq4EmvgxN=o78BrNe zQZ#wyuiFPW#pr%wkC&&>$0P+;9#BF_@G-3F&Bkt_N-iIBliJshf%)aDV3$cE@!ytA zRtcTZdzrz=n9Qe(TXgB`@DSWWfD9GiXR^A^V!(GPiSE&TFCYQk;9#V{gS8lM~7CK`F>$b|kO@BMw~g|DAT_%9i> zx)Z_emh=(XTYK=()lSkp^8r0psL3>m8_1TahJsW4PtGv?n!tuBVNOjCBem^Hbf?V* zZpI-3r3pvrlYvAW8Q3W)^fSl6>#;QK=wPDZP>uoe<1uvFL2A&xOX6u4O9DswL-+8# zTuayu;xp$xm)S84?KD#{LdZ}HA~MP2x#rB~0ShED)>WKSZy~X@n$1+q*+eg7JOKB` zKql$C4V?mNWMB>8%z_`Zd*Kc)zi9{Ac}0hATBgq2-fMz#^M{e+v2LVvj6Kn+zCt{H zIZ>la=eSj4TS-ynR&khYJhP}Sg(JBtoTs%ay<8DN!bI04Uwuw+JKx_UiAg{Vng-(T z=3zwZk~|zyErJmx+sR|Sy-c`~41{WJ1m)!^jM<_pPSZ>tOQMxw+hq@oHjsnn_`P^t zWfG>w7E-eLDkrtBK(hOcGHr}aryf}eOwo~5c($~H?DFeR^+c;^&rMbAYduN+Xpe%Q z=3a2v{2gh#~ibUkA*-OY)oWk|Zi2~hG#bm#$JRa;m4_f0@&{jGDMs~%}^D7^Ll)f+A zI5`M^XsUr-X&6L2P2$p{JkaW;E%`oO2Hk`m^kR<;=CsVmizTm$6_loPu3kpqDkVeT z)fIE8O*gpos~gDLH>T7*ehN&yHHLW;bqrOj4EBMVC(|Aq2Bzqx(0XL4;R$G8OMERjf?;?DsM0xa$!z)VVs3Ox;|->c0y=Lge^+<5KVopE8=ha94uj>i?SE#DWhaUC z?MF0yWLv7W?3W(vWX+ZSo;iAktd!KmiT`sq=3jSSdLp@C-*9hh4NZ}{g0zORNM1wE zcWH#*3XzP0g0G%PWv#Gp{xd+Xi45`$2-6UW6ohHA!l<;bj%YwoKu~a4v=AC&0{&yj zOr-H|q4ZxL8UBC0L&n!aWbnVHdIv{FghWPohp$)~@}CbLFEaEGhzJM^3S1+^=Rm)I OkJ$g;YaC_N6#oI-_It_z literal 0 HcmV?d00001 diff --git a/com.unity.ml-agents/Tests/Editor/TestModels/deterContinuous2vis8vec2action_v2_0.onnx.meta b/com.unity.ml-agents/Tests/Editor/TestModels/deterContinuous2vis8vec2action_v2_0.onnx.meta new file mode 100644 index 0000000000..cc92cc94b8 --- /dev/null +++ b/com.unity.ml-agents/Tests/Editor/TestModels/deterContinuous2vis8vec2action_v2_0.onnx.meta @@ -0,0 +1,14 @@ +fileFormatVersion: 2 +guid: e905d8f9eadcf45aa8c485594fecba6d +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 683b6cb6d0a474744822c888b46772c9, type: 3} + optimizeModel: 1 + forceArbitraryBatchSize: 1 + treatErrorsAsWarnings: 0 + importMode: 1 diff --git a/com.unity.ml-agents/Tests/Editor/TestModels/deterDiscrete1obs3action_v2_0.onnx b/com.unity.ml-agents/Tests/Editor/TestModels/deterDiscrete1obs3action_v2_0.onnx new file mode 100644 index 0000000000000000000000000000000000000000..3aa846e204fcf2a36a91c60709bfbc9fb1aba43e GIT binary patch literal 3715 zcmbVPX+Tp~7A65gcwtKjB}TA_AQ~YOkUj4_!G*=TqP0>>NFYj(B$xyhQ3OHjUMNLz zry_#36`?Mp|5L(BoW~;TTB$=PP)hiMcn7H%^pd|u@mgCb?W&ZBw^n{JWY~+asl~yBD%k^_i zo&*W4VpCO#O4$f^%U+@{A4>g5Iv-Z11s%73q`7H9JE1qobq4)NGkS?tn{pM11^lX! z&Q7XWmZZ`r>XZhhi7@C?igc4Xv4VC!Fg8Uegu) zXS(QX?bYSyi_xd(lYTCPm+eP&F;BZQn^-IPfr z|J9D05xr-!Lu>Q%qX&%W&dvrwF9S(C5yeY~fgEt3Seo6|EENpAmZVa@s{PRz(_g|HkVJ!5r&eib_^J|r zgYzF82zjdmJcr)L$0tQ)@KG*E)9MT&A)DdAh_IVSzZj;<)1<2vN}UN`BS(rvY(_8F z<#&O~wCRSlbc0NvEKgIul6CHtC5$^ zsvKgVgOcKvTRqSdg$6&xx1>(zA4IE+<3P2g5PzbJgxGi;c-T1^a$=NQ2|iR_4F;j=DcaAoFDqWt*Cau7E`x}>ED>@B)r zb^eF=zXp#Zrj!+c#(x6nKjguQ`X6xDMKAGH6LW!+x|J+Zj7G6`7f9!0Kh|W$e?^XM z+y?(Bb|$;3#OP6lI|~2g6V!6~OSt3q2nL;S#2xJIh~Ox5LbCfJp3=XVRKJ`E*9OG{ zw0FS|qsO9M1xN74!b9XdYqPMeAKK#Edy2_LnkdlTUJE|YRwC&X4!I{|Ik+tu2h~n< z(cY$dxKlR=nmPn{p5S}3%W4aHdE*JGco|7{M^}Ou(*eq&Hv=b^L!PKgA&1|OML$Lf zP+8b;bi1<@Z$CZ|%`W=@rmVLi1_WG!!^H>SxnU;J6Fm3vtCN+nT;@>SSfFo_& z$WQ;th6&#uLme|)j3@b1jo!PK!Rh31xYW5B)dXkp9tj)Lh;lEo_<$wdE0r8J_@6QGW1q!g5rn&yX$| za2m9f4RI?k7an#T#b>@?A@A61>C;`+D1TEN(1n{0|Z)TNI;H{&yxAOwaB%ENj#o*9!4eBqK#Lc!MUkLux=C=t&#AM zb*K)Vgc363NebFHWe@CPHKCO1LL~2)K+eDUK3OTqf+d_3od2K|jt15vEVC4rjJs$o z;}#m{9NHkwO|C$-d#@noUMBdsoWw^TiU#3|6Xa><0(`IFEFQdhma!=66Y>ma8mjzq z1)T2Z2%TfULqF%ng7{7zGM{ln+G%$P_6=D9E$wZ1#7AL7DZBmFPi!YZR78RfjdHptJOjx zeDDL{dNY7Ow*od?^`v(9?8g%u@^PcM4*8Coh-dj^;eP3>QJlp=xL*;CGH+NDK6AF< zO~&acW{)G4&{~L^rl*i6>v%+5n~SlwxrUs-M?x%kw*khFUx`}Rmf?}X)wreEBmATy z4?na$8{Pi#5xTYHqA@FH26|!d2|4x+cn`M;ovE`Shb5JPRFqGSb!#Tq=WNBN?#sXv z_b{jc!krRyeh8xAn3i2dIHvE>-t^y{wq)MG1ryz?$8%478Y z^Zca-IA&5K*O~GZ51^)a#}J#}A4^>InM4%)1jH$~F~rbq1aVu^-E?>drqRl)HF zGpcP1hkDTZ5EbTCf%A|C8hO^l)P+`r_0xXTi)DOjV$cg@NZ?W~JTI!^lz__2=TWTe z#{i3(Vb&rxwdnW`W2ZZxO7(mKiRDfd-|_}J&*D(7jCzPFxDWh?L#TV}{He6u(ZuRf zARd245RC^%5@nt-goo>RVpu{j@vKBh@dwkd(5+E~TMY(maujh^@3nxDJ8AM!r z5K5#fgp}K5H%b)AHm6&Lzte`t*f?djqv9TfI2*V`oXfA@SB@B$&EVN$Ry<3E+>n@z zG1>gMLBIRtc#Dv^!Ug+Xd&I*qHm%EoR>4s8!(MNC-e7rQe`pbTCJR Date: Wed, 3 Nov 2021 12:33:57 -0700 Subject: [PATCH 09/15] Reverting to "Deterministic Inference" --- .../Editor/BehaviorParametersEditor.cs | 6 +- com.unity.ml-agents/Runtime/Academy.cs | 8 +-- .../Inference/BarracudaModelExtensions.cs | 71 +++++++++---------- .../Inference/BarracudaModelParamLoader.cs | 20 +++--- .../Runtime/Inference/ModelRunner.cs | 18 +++-- .../Runtime/Inference/TensorApplier.cs | 10 +-- .../Runtime/Inference/TensorGenerator.cs | 14 ++-- .../Runtime/Policies/BarracudaPolicy.cs | 17 +++-- .../Runtime/Policies/BehaviorParameters.cs | 17 +++-- .../Tests/Editor/Inference/ModelRunnerTest.cs | 44 ++++++------ .../Tests/Runtime/RuntimeAPITest.cs | 2 +- 11 files changed, 109 insertions(+), 118 deletions(-) diff --git a/com.unity.ml-agents/Editor/BehaviorParametersEditor.cs b/com.unity.ml-agents/Editor/BehaviorParametersEditor.cs index 19e456100c..a95b2846f3 100644 --- a/com.unity.ml-agents/Editor/BehaviorParametersEditor.cs +++ b/com.unity.ml-agents/Editor/BehaviorParametersEditor.cs @@ -25,7 +25,7 @@ internal class BehaviorParametersEditor : UnityEditor.Editor const string k_BrainParametersName = "m_BrainParameters"; const string k_ModelName = "m_Model"; const string k_InferenceDeviceName = "m_InferenceDevice"; - const string k_StochasticInference = "m_stochasticInference"; + const string k_DeterministicInference = "m_DeterministicInference"; const string k_BehaviorTypeName = "m_BehaviorType"; const string k_TeamIdName = "TeamId"; const string k_UseChildSensorsName = "m_UseChildSensors"; @@ -69,7 +69,7 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(so.FindProperty(k_ModelName), true); EditorGUI.indentLevel++; EditorGUILayout.PropertyField(so.FindProperty(k_InferenceDeviceName), true); - EditorGUILayout.PropertyField(so.FindProperty(k_StochasticInference), true); + EditorGUILayout.PropertyField(so.FindProperty(k_DeterministicInference), true); EditorGUI.indentLevel--; } needPolicyUpdate = needPolicyUpdate || EditorGUI.EndChangeCheck(); @@ -158,7 +158,7 @@ void DisplayFailedModelChecks() { var failedChecks = Inference.BarracudaModelParamLoader.CheckModel( barracudaModel, brainParameters, sensors, actuatorComponents, - observableAttributeSensorTotalSize, behaviorParameters.BehaviorType, behaviorParameters.StochasticInference + observableAttributeSensorTotalSize, behaviorParameters.BehaviorType, behaviorParameters.DeterministicInference ); foreach (var check in failedChecks) { diff --git a/com.unity.ml-agents/Runtime/Academy.cs b/com.unity.ml-agents/Runtime/Academy.cs index 93993051b1..0a6624316f 100644 --- a/com.unity.ml-agents/Runtime/Academy.cs +++ b/com.unity.ml-agents/Runtime/Academy.cs @@ -616,16 +616,16 @@ void EnvironmentReset() /// /// The inference device (CPU or GPU) the ModelRunner will use. /// - /// Inference only: set to true if the action selection from model should be - /// stochastic. + /// Inference only: set to true if the action selection from model should be + /// Deterministic. /// The ModelRunner compatible with the input settings. internal ModelRunner GetOrCreateModelRunner( - NNModel model, ActionSpec actionSpec, InferenceDevice inferenceDevice, bool stochasticInference = true) + NNModel model, ActionSpec actionSpec, InferenceDevice inferenceDevice, bool DeterministicInference = false) { var modelRunner = m_ModelRunners.Find(x => x.HasModel(model, inferenceDevice)); if (modelRunner == null) { - modelRunner = new ModelRunner(model, actionSpec, inferenceDevice, m_InferenceSeed, stochasticInference); + modelRunner = new ModelRunner(model, actionSpec, inferenceDevice, m_InferenceSeed, DeterministicInference); m_ModelRunners.Add(modelRunner); m_InferenceSeed++; } diff --git a/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs b/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs index d4ba781ddd..d9180a187e 100644 --- a/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs +++ b/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using Unity.Barracuda; -using UnityEngine; using FailedCheck = Unity.MLAgents.Inference.BarracudaModelParamLoader.FailedCheck; namespace Unity.MLAgents.Inference @@ -12,8 +11,6 @@ namespace Unity.MLAgents.Inference /// internal static class BarracudaModelExtensions { - - /// /// Get array of the input tensor names of the model. /// @@ -115,10 +112,10 @@ public static int GetNumVisualInputs(this Model model) /// /// The Barracuda engine model for loading static parameters. /// - /// Inference only: set to true if the action selection from model should be - /// stochastic. + /// Inference only: set to true if the action selection from model should be + /// deterministic. /// Array of the output tensor names of the model - public static string[] GetOutputNames(this Model model, bool stochasticInference = true) + public static string[] GetOutputNames(this Model model, bool deterministicInference = false) { var names = new List(); @@ -129,11 +126,11 @@ public static string[] GetOutputNames(this Model model, bool stochasticInference if (model.HasContinuousOutputs()) { - names.Add(model.ContinuousOutputName(stochasticInference)); + names.Add(model.ContinuousOutputName(deterministicInference)); } if (model.HasDiscreteOutputs()) { - names.Add(model.DiscreteOutputName(stochasticInference)); + names.Add(model.DiscreteOutputName(deterministicInference)); } var modelVersion = model.GetVersion(); @@ -154,10 +151,10 @@ public static string[] GetOutputNames(this Model model, bool stochasticInference /// /// The Barracuda engine model for loading static parameters. /// - /// Inference only: set to true if the action selection from model should be - /// stochastic. + /// Inference only: set to true if the action selection from model should be + /// deterministic. /// True if the model has continuous action outputs. - public static bool HasContinuousOutputs(this Model model, bool stochasticInference = true) + public static bool HasContinuousOutputs(this Model model, bool deterministicInference = false) { if (model == null) return false; @@ -167,9 +164,9 @@ public static bool HasContinuousOutputs(this Model model, bool stochasticInferen } else { - bool hasStochasticOutput = stochasticInference && + bool hasStochasticOutput = !deterministicInference && model.outputs.Contains(TensorNames.ContinuousActionOutput); - bool hasDeterministicOutput = !stochasticInference && + bool hasDeterministicOutput = deterministicInference && model.outputs.Contains(TensorNames.DeterministicContinuousActionOutput); return (hasStochasticOutput || hasDeterministicOutput) && @@ -206,10 +203,10 @@ public static int ContinuousOutputSize(this Model model) /// /// The Barracuda engine model for loading static parameters. /// - /// Inference only: set to true if the action selection from model should be - /// stochastic. + /// Inference only: set to true if the action selection from model should be + /// deterministic. /// Tensor name of continuous action output. - public static string ContinuousOutputName(this Model model, bool stochasticInference = true) + public static string ContinuousOutputName(this Model model, bool deterministicInference = false) { if (model == null) return null; @@ -219,7 +216,7 @@ public static string ContinuousOutputName(this Model model, bool stochasticInfer } else { - return stochasticInference ? TensorNames.ContinuousActionOutput : TensorNames.DeterministicContinuousActionOutput; + return deterministicInference ? TensorNames.DeterministicContinuousActionOutput : TensorNames.ContinuousActionOutput; } } @@ -229,10 +226,10 @@ public static string ContinuousOutputName(this Model model, bool stochasticInfer /// /// The Barracuda engine model for loading static parameters. /// - /// Inference only: set to true if the action selection from model should be - /// stochastic. + /// Inference only: set to true if the action selection from model should be + /// deterministic. /// True if the model has discrete action outputs. - public static bool HasDiscreteOutputs(this Model model, bool stochasticInference = true) + public static bool HasDiscreteOutputs(this Model model, bool deterministicInference = false) { if (model == null) return false; @@ -242,9 +239,9 @@ public static bool HasDiscreteOutputs(this Model model, bool stochasticInference } else { - bool hasStochasticOutput = stochasticInference && + bool hasStochasticOutput = !deterministicInference && model.outputs.Contains(TensorNames.DiscreteActionOutput); - bool hasDeterministicOutput = !stochasticInference && + bool hasDeterministicOutput = deterministicInference && model.outputs.Contains(TensorNames.DeterministicDiscreteActionOutput); return (hasStochasticOutput || hasDeterministicOutput) && model.DiscreteOutputSize() > 0; @@ -300,10 +297,10 @@ public static int DiscreteOutputSize(this Model model) /// /// The Barracuda engine model for loading static parameters. /// - /// Inference only: set to true if the action selection from model should be - /// stochastic. + /// Inference only: set to true if the action selection from model should be + /// deterministic. /// Tensor name of discrete action output. - public static string DiscreteOutputName(this Model model, bool stochasticInference = true) + public static string DiscreteOutputName(this Model model, bool deterministicInference = false) { if (model == null) return null; @@ -313,7 +310,7 @@ public static string DiscreteOutputName(this Model model, bool stochasticInferen } else { - return stochasticInference ? TensorNames.DiscreteActionOutput : TensorNames.DeterministicDiscreteActionOutput; + return deterministicInference ? TensorNames.DeterministicDiscreteActionOutput : TensorNames.DiscreteActionOutput; } } @@ -339,11 +336,11 @@ public static bool SupportsContinuousAndDiscrete(this Model model) /// The Barracuda engine model for loading static parameters. /// /// Output list of failure messages - /// Inference only: set to true if the action selection from model should be - /// stochastic. + /// Inference only: set to true if the action selection from model should be + /// deterministic. /// True if the model contains all the expected tensors. /// TODO: add checks for deterministic actions - public static bool CheckExpectedTensors(this Model model, List failedModelChecks, bool stochasticInference = true) + public static bool CheckExpectedTensors(this Model model, List failedModelChecks, bool deterministicInference = false) { // Check the presence of model version var modelApiVersionTensor = model.GetTensorByName(TensorNames.VersionNumber); @@ -410,12 +407,12 @@ public static bool CheckExpectedTensors(this Model model, List fail return false; } - else if (!model.HasContinuousOutputs(stochasticInference)) + else if (!model.HasContinuousOutputs(deterministicInference)) { - var actionType = stochasticInference ? "stochastic" : "deterministic"; - var actionName = stochasticInference ? "" : "Deterministic"; + var actionType = deterministicInference ? "deterministic" : "stochastic"; + var actionName = deterministicInference ? "Deterministic" : ""; failedModelChecks.Add( - FailedCheck.Warning($"The model uses {actionType} inference but does not contain {actionName} Continuous Action Output Tensor. Set `Stochastic inference` accordingly.") + FailedCheck.Warning($"The model uses {actionType} inference but does not contain {actionName} Continuous Action Output Tensor. Set `Deterministic inference` accordingly.") ); return false; } @@ -430,12 +427,12 @@ public static bool CheckExpectedTensors(this Model model, List fail ); return false; } - else if (!model.HasDiscreteOutputs(stochasticInference)) + else if (!model.HasDiscreteOutputs(deterministicInference)) { - var actionType = stochasticInference ? "stochastic" : "deterministic"; - var actionName = stochasticInference ? "" : "Deterministic"; + var actionType = deterministicInference ? "deterministic" : "stochastic"; + var actionName = deterministicInference ? "Deterministic" : ""; failedModelChecks.Add( - FailedCheck.Warning($"The model uses {actionType} inference but does not contain {actionName} Discrete Action Output Tensor. Set `Stochastic inference` accordingly.") + FailedCheck.Warning($"The model uses {actionType} inference but does not contain {actionName} Discrete Action Output Tensor. Set `Deterministic inference` accordingly.") ); return false; } diff --git a/com.unity.ml-agents/Runtime/Inference/BarracudaModelParamLoader.cs b/com.unity.ml-agents/Runtime/Inference/BarracudaModelParamLoader.cs index 6752ffbc06..7e6285a1cb 100644 --- a/com.unity.ml-agents/Runtime/Inference/BarracudaModelParamLoader.cs +++ b/com.unity.ml-agents/Runtime/Inference/BarracudaModelParamLoader.cs @@ -122,8 +122,8 @@ public static FailedCheck CheckModelVersion(Model model) /// Attached actuator components /// Sum of the sizes of all ObservableAttributes. /// BehaviorType or the Agent to check. - /// Inference only: set to true if the action selection from model should be - /// stochastic. + /// Inference only: set to true if the action selection from model should be + /// deterministic. /// A IEnumerable of the checks that failed public static IEnumerable CheckModel( Model model, @@ -132,7 +132,7 @@ public static IEnumerable CheckModel( ActuatorComponent[] actuatorComponents, int observableAttributeTotalSize = 0, BehaviorType behaviorType = BehaviorType.Default, - bool stochasticInference = true + bool deterministicInference = false ) { List failedModelChecks = new List(); @@ -151,7 +151,7 @@ public static IEnumerable CheckModel( return failedModelChecks; } - var hasExpectedTensors = model.CheckExpectedTensors(failedModelChecks, stochasticInference); + var hasExpectedTensors = model.CheckExpectedTensors(failedModelChecks, deterministicInference); if (!hasExpectedTensors) { return failedModelChecks; @@ -198,7 +198,7 @@ public static IEnumerable CheckModel( ); failedModelChecks.AddRange( - CheckOutputTensorPresence(model, memorySize, stochasticInference) + CheckOutputTensorPresence(model, memorySize, deterministicInference) ); return failedModelChecks; } @@ -379,18 +379,19 @@ ISensor[] sensors /// The Barracuda engine model for loading static parameters /// /// The memory size that the model is expecting/ - /// Inference only: set to true if the action selection from model should be - /// stochastic. + /// Inference only: set to true if the action selection from model should be + /// deterministic. /// /// A IEnumerable of the checks that failed /// - static IEnumerable CheckOutputTensorPresence(Model model, int memory, bool stochasticInference = true) + static IEnumerable CheckOutputTensorPresence(Model model, int memory, bool deterministicInference = false) { var failedModelChecks = new List(); + // If there is no Recurrent Output but the model is Recurrent. if (memory > 0) { - var allOutputs = model.GetOutputNames(stochasticInference).ToList(); + var allOutputs = model.GetOutputNames(deterministicInference).ToList(); if (!allOutputs.Any(x => x == TensorNames.RecurrentOutput)) { failedModelChecks.Add( @@ -399,7 +400,6 @@ static IEnumerable CheckOutputTensorPresence(Model model, int memor } } - return failedModelChecks; } diff --git a/com.unity.ml-agents/Runtime/Inference/ModelRunner.cs b/com.unity.ml-agents/Runtime/Inference/ModelRunner.cs index 05d4fa2ef8..f59b54ee23 100644 --- a/com.unity.ml-agents/Runtime/Inference/ModelRunner.cs +++ b/com.unity.ml-agents/Runtime/Inference/ModelRunner.cs @@ -4,7 +4,6 @@ using Unity.MLAgents.Actuators; using Unity.MLAgents.Policies; using Unity.MLAgents.Sensors; -using UnityEngine; namespace Unity.MLAgents.Inference { @@ -29,7 +28,7 @@ internal class ModelRunner InferenceDevice m_InferenceDevice; IWorker m_Engine; bool m_Verbose = false; - bool m_stochasticInference; + bool m_DeterministicInference; string[] m_OutputNames; IReadOnlyList m_InferenceInputs; List m_InferenceOutputs; @@ -50,8 +49,8 @@ internal class ModelRunner /// option for most of ML Agents models. /// The seed that will be used to initialize the RandomNormal /// and Multinomial objects used when running inference. - /// Inference only: set to true if the action selection from model should be - /// stochastic. + /// Inference only: set to true if the action selection from model should be + /// deterministic. /// Throws an error when the model is null /// public ModelRunner( @@ -59,13 +58,13 @@ public ModelRunner( ActionSpec actionSpec, InferenceDevice inferenceDevice, int seed = 0, - bool stochasticInference = true) + bool deterministicInference = false) { Model barracudaModel; m_Model = model; m_ModelName = model.name; m_InferenceDevice = inferenceDevice; - m_stochasticInference = stochasticInference; + m_DeterministicInference = deterministicInference; m_TensorAllocator = new TensorCachingAllocator(); if (model != null) { @@ -114,13 +113,12 @@ public ModelRunner( } m_InferenceInputs = barracudaModel.GetInputTensors(); - m_OutputNames = barracudaModel.GetOutputNames(m_stochasticInference); - Debug.Log("output actions:" + m_OutputNames[0]); + m_OutputNames = barracudaModel.GetOutputNames(m_DeterministicInference); m_TensorGenerator = new TensorGenerator( - seed, m_TensorAllocator, m_Memories, barracudaModel, m_stochasticInference); + seed, m_TensorAllocator, m_Memories, barracudaModel, m_DeterministicInference); m_TensorApplier = new TensorApplier( - actionSpec, seed, m_TensorAllocator, m_Memories, barracudaModel, m_stochasticInference); + actionSpec, seed, m_TensorAllocator, m_Memories, barracudaModel, m_DeterministicInference); m_InputsByName = new Dictionary(); m_InferenceOutputs = new List(); } diff --git a/com.unity.ml-agents/Runtime/Inference/TensorApplier.cs b/com.unity.ml-agents/Runtime/Inference/TensorApplier.cs index 9075c4a3c5..a03b3d927e 100644 --- a/com.unity.ml-agents/Runtime/Inference/TensorApplier.cs +++ b/com.unity.ml-agents/Runtime/Inference/TensorApplier.cs @@ -44,15 +44,15 @@ public interface IApplier /// Tensor allocator /// Dictionary of AgentInfo.id to memory used to pass to the inference model. /// - /// Inference only: set to true if the action selection from model should be - /// stochastic. + /// Inference only: set to true if the action selection from model should be + /// deterministic. public TensorApplier( ActionSpec actionSpec, int seed, ITensorAllocator allocator, Dictionary> memories, object barracudaModel = null, - bool stochasticInference = true) + bool deterministicInference = false) { // If model is null, no inference to run and exception is thrown before reaching here. if (barracudaModel == null) @@ -67,13 +67,13 @@ public TensorApplier( } if (actionSpec.NumContinuousActions > 0) { - var tensorName = model.ContinuousOutputName(stochasticInference); + var tensorName = model.ContinuousOutputName(deterministicInference); m_Dict[tensorName] = new ContinuousActionOutputApplier(actionSpec); } var modelVersion = model.GetVersion(); if (actionSpec.NumDiscreteActions > 0) { - var tensorName = model.DiscreteOutputName(stochasticInference); + var tensorName = model.DiscreteOutputName(deterministicInference); if (modelVersion == (int)BarracudaModelParamLoader.ModelApiVersion.MLAgents1_0) { m_Dict[tensorName] = new LegacyDiscreteActionOutputApplier(actionSpec, seed, allocator); diff --git a/com.unity.ml-agents/Runtime/Inference/TensorGenerator.cs b/com.unity.ml-agents/Runtime/Inference/TensorGenerator.cs index fe7f15fbe9..39bed85792 100644 --- a/com.unity.ml-agents/Runtime/Inference/TensorGenerator.cs +++ b/com.unity.ml-agents/Runtime/Inference/TensorGenerator.cs @@ -44,14 +44,14 @@ void Generate( /// Tensor allocator. /// Dictionary of AgentInfo.id to memory for use in the inference model. /// - /// Inference only: set to true if the action selection from model should be - /// stochastic. + /// Inference only: set to true if the action selection from model should be + /// deterministic. public TensorGenerator( int seed, ITensorAllocator allocator, Dictionary> memories, object barracudaModel = null, - bool stochasticInference = true) + bool deterministicInference = false) { // If model is null, no inference to run and exception is thrown before reaching here. if (barracudaModel == null) @@ -79,13 +79,13 @@ public TensorGenerator( // Generators for Outputs - if (model.HasContinuousOutputs(stochasticInference)) + if (model.HasContinuousOutputs(deterministicInference)) { - m_Dict[model.ContinuousOutputName(stochasticInference)] = new BiDimensionalOutputGenerator(allocator); + m_Dict[model.ContinuousOutputName(deterministicInference)] = new BiDimensionalOutputGenerator(allocator); } - if (model.HasDiscreteOutputs(stochasticInference)) + if (model.HasDiscreteOutputs(deterministicInference)) { - m_Dict[model.DiscreteOutputName(stochasticInference)] = new BiDimensionalOutputGenerator(allocator); + m_Dict[model.DiscreteOutputName(deterministicInference)] = new BiDimensionalOutputGenerator(allocator); } m_Dict[TensorNames.RecurrentOutput] = new BiDimensionalOutputGenerator(allocator); m_Dict[TensorNames.ValueEstimateOutput] = new BiDimensionalOutputGenerator(allocator); diff --git a/com.unity.ml-agents/Runtime/Policies/BarracudaPolicy.cs b/com.unity.ml-agents/Runtime/Policies/BarracudaPolicy.cs index db39fb159e..96a15b50d8 100644 --- a/com.unity.ml-agents/Runtime/Policies/BarracudaPolicy.cs +++ b/com.unity.ml-agents/Runtime/Policies/BarracudaPolicy.cs @@ -34,7 +34,6 @@ public enum InferenceDevice CPU = 3, } - /// /// The Barracuda Policy uses a Barracuda Model to make decisions at /// every step. It uses a ModelRunner that is shared across all @@ -47,10 +46,10 @@ internal class BarracudaPolicy : IPolicy int m_AgentId; /// - /// TODO + /// Inference only: set to true if the action selection from model should be + /// deterministic. /// - bool m_StochasticInference; - + bool m_DeterministicInference; /// /// Sensor shapes for the associated Agents. All Agents must have the same shapes for their Sensors. @@ -79,23 +78,23 @@ internal class BarracudaPolicy : IPolicy /// The Neural Network to use. /// Which device Barracuda will run on. /// The name of the behavior. - /// Inference only: set to true if the action selection from model should be - /// stochastic. + /// Inference only: set to true if the action selection from model should be + /// deterministic. public BarracudaPolicy( ActionSpec actionSpec, IList actuators, NNModel model, InferenceDevice inferenceDevice, string behaviorName, - bool stochasticInference = true + bool deterministicInference = false ) { - var modelRunner = Academy.Instance.GetOrCreateModelRunner(model, actionSpec, inferenceDevice, stochasticInference); + var modelRunner = Academy.Instance.GetOrCreateModelRunner(model, actionSpec, inferenceDevice, deterministicInference); m_ModelRunner = modelRunner; m_BehaviorName = behaviorName; m_ActionSpec = actionSpec; m_Actuators = actuators; - m_StochasticInference = stochasticInference; + m_DeterministicInference = deterministicInference; } /// diff --git a/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs b/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs index a7ba284ae1..072b54c742 100644 --- a/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs +++ b/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs @@ -122,7 +122,6 @@ public InferenceDevice InferenceDevice set { m_InferenceDevice = value; UpdateAgentPolicy(); } } - [HideInInspector, SerializeField] BehaviorType m_BehaviorType; @@ -180,16 +179,16 @@ public bool UseChildSensors [HideInInspector] [SerializeField] - [Tooltip("Set action selection to stochastic, Use only in inference mode")] - private bool m_stochasticInference = false; + [Tooltip("Set action selection to deterministic, Use only in inference mode")] + private bool m_DeterministicInference = false; /// - /// Whether to select actions stochastically during inference from the provided neural network. + /// Whether to select actions deterministically during inference from the provided neural network. /// - public bool StochasticInference + public bool DeterministicInference { - get { return m_stochasticInference; } - set { m_stochasticInference = value; } + get { return m_DeterministicInference; } + set { m_DeterministicInference = value; } } /// @@ -243,7 +242,7 @@ internal IPolicy GeneratePolicy(ActionSpec actionSpec, ActuatorManager actuatorM "Either assign a model, or change to a different Behavior Type." ); } - return new BarracudaPolicy(actionSpec, actuatorManager, m_Model, m_InferenceDevice, m_BehaviorName, m_stochasticInference); + return new BarracudaPolicy(actionSpec, actuatorManager, m_Model, m_InferenceDevice, m_BehaviorName, m_DeterministicInference); } case BehaviorType.Default: if (Academy.Instance.IsCommunicatorOn) @@ -252,7 +251,7 @@ internal IPolicy GeneratePolicy(ActionSpec actionSpec, ActuatorManager actuatorM } if (m_Model != null) { - return new BarracudaPolicy(actionSpec, actuatorManager, m_Model, m_InferenceDevice, m_BehaviorName, m_stochasticInference); + return new BarracudaPolicy(actionSpec, actuatorManager, m_Model, m_InferenceDevice, m_BehaviorName, m_DeterministicInference); } else { diff --git a/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs b/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs index 3eb3351382..10c20e84fe 100644 --- a/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs +++ b/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs @@ -7,9 +7,7 @@ using Unity.MLAgents.Actuators; using Unity.MLAgents.Inference; using Unity.MLAgents.Policies; -using System.Collections; using System.Collections.Generic; -using UnityEngine.Assertions.Comparers; namespace Unity.MLAgents.Tests { @@ -43,8 +41,8 @@ public class ModelRunnerTest const string k_continuousNNPath = "Packages/com.unity.ml-agents/Tests/Editor/TestModels/continuous2vis8vec2action_deprecated_v1_0.nn"; const string k_discreteNNPath = "Packages/com.unity.ml-agents/Tests/Editor/TestModels/discrete1vis0vec_2_3action_recurr_deprecated_v1_0.nn"; // models with deterministic action tensors - private const string k_deter_discreteNNPath = "Packages/com.unity.ml-agents/Tests/Editor/TestModels/deterDiscrete1obs3action_v2_0.onnx"; - private const string k_deter_continuousNNPath = "Packages/com.unity.ml-agents/Tests/Editor/TestModels/deterContinuous2vis8vec2action_v2_0.onnx"; + private const string k_deterministic_discreteNNPath = "Packages/com.unity.ml-agents/Tests/Editor/TestModels/deterDiscrete1obs3action_v2_0.onnx"; + private const string k_deterministic_continuousNNPath = "Packages/com.unity.ml-agents/Tests/Editor/TestModels/deterContinuous2vis8vec2action_v2_0.onnx"; NNModel hybridONNXModelV2; NNModel continuousONNXModel; @@ -52,8 +50,8 @@ public class ModelRunnerTest NNModel hybridONNXModel; NNModel continuousNNModel; NNModel discreteNNModel; - NNModel deterDiscreteNNModel; - NNModel deterContinuousNNModel; + NNModel deterministicDiscreteNNModel; + NNModel deterministicContinuousNNModel; Test3DSensorComponent sensor_21_20_3; Test3DSensorComponent sensor_20_22_3; @@ -83,8 +81,8 @@ public void SetUp() hybridONNXModel = (NNModel)AssetDatabase.LoadAssetAtPath(k_hybridONNXPath, typeof(NNModel)); continuousNNModel = (NNModel)AssetDatabase.LoadAssetAtPath(k_continuousNNPath, typeof(NNModel)); discreteNNModel = (NNModel)AssetDatabase.LoadAssetAtPath(k_discreteNNPath, typeof(NNModel)); - deterDiscreteNNModel = (NNModel)AssetDatabase.LoadAssetAtPath(k_deter_discreteNNPath, typeof(NNModel)); - deterContinuousNNModel = (NNModel)AssetDatabase.LoadAssetAtPath(k_deter_continuousNNPath, typeof(NNModel)); + deterministicDiscreteNNModel = (NNModel)AssetDatabase.LoadAssetAtPath(k_deterministic_discreteNNPath, typeof(NNModel)); + deterministicContinuousNNModel = (NNModel)AssetDatabase.LoadAssetAtPath(k_deterministic_continuousNNPath, typeof(NNModel)); var go = new GameObject("SensorA"); sensor_21_20_3 = go.AddComponent(); sensor_21_20_3.Sensor = new Test3DSensor("SensorA", 21, 20, 3); @@ -101,8 +99,8 @@ public void TestModelExist() Assert.IsNotNull(continuousNNModel); Assert.IsNotNull(discreteNNModel); Assert.IsNotNull(hybridONNXModelV2); - Assert.IsNotNull(deterDiscreteNNModel); - Assert.IsNotNull(deterContinuousNNModel); + Assert.IsNotNull(deterministicDiscreteNNModel); + Assert.IsNotNull(deterministicContinuousNNModel); } [Test] @@ -133,12 +131,12 @@ public void TestCreation() modelRunner.Dispose(); // V2.0 Model that has serialized deterministic action tensors, discrete - modelRunner = new ModelRunner(deterDiscreteNNModel, new ActionSpec(0, new[] { 7 }), inferenceDevice); + modelRunner = new ModelRunner(deterministicDiscreteNNModel, new ActionSpec(0, new[] { 7 }), inferenceDevice); modelRunner.Dispose(); // V2.0 Model that has serialized deterministic action tensors, continuous - modelRunner = new ModelRunner(deterContinuousNNModel, + modelRunner = new ModelRunner(deterministicContinuousNNModel, GetContinuous2vis8vec2actionActionSpec(), inferenceDevice, - stochasticInference: false); + deterministicInference: true); modelRunner.Dispose(); } @@ -185,7 +183,7 @@ public void TestRunModel() public void TestRunModel_deterministic() { var actionSpec = GetContinuous2vis8vec2actionActionSpec(); - var modelRunner = new ModelRunner(deterContinuousNNModel, actionSpec, InferenceDevice.Burst); + var modelRunner = new ModelRunner(deterministicContinuousNNModel, actionSpec, InferenceDevice.Burst); var sensor_8 = new Sensors.VectorSensor(8, "VectorSensor8"); var info1 = new AgentInfo(); var obs = new[] @@ -206,18 +204,18 @@ public void TestRunModel_deterministic() Assert.IsFalse(Enumerable.SequenceEqual(stochAction1, stochAction2, new FloatThresholdComparer(0.001f))); - var deterModelRunner = new ModelRunner(deterContinuousNNModel, actionSpec, InferenceDevice.Burst, - stochasticInference: false); + var deterministicModelRunner = new ModelRunner(deterministicContinuousNNModel, actionSpec, InferenceDevice.Burst, + deterministicInference: true); info1.episodeId = 1; - deterModelRunner.PutObservations(info1, obs); - deterModelRunner.DecideBatch(); - var deterAction1 = (float[])deterModelRunner.GetAction(1).ContinuousActions.Array.Clone(); + deterministicModelRunner.PutObservations(info1, obs); + deterministicModelRunner.DecideBatch(); + var deterministicAction1 = (float[])deterministicModelRunner.GetAction(1).ContinuousActions.Array.Clone(); - deterModelRunner.PutObservations(info1, obs); - deterModelRunner.DecideBatch(); - var deterAction2 = (float[])deterModelRunner.GetAction(1).ContinuousActions.Array.Clone(); + deterministicModelRunner.PutObservations(info1, obs); + deterministicModelRunner.DecideBatch(); + var deterministicAction2 = (float[])deterministicModelRunner.GetAction(1).ContinuousActions.Array.Clone(); // Deterministic action selection should output same action everytime - Assert.IsTrue(Enumerable.SequenceEqual(deterAction1, deterAction2, new FloatThresholdComparer(0.001f))); + Assert.IsTrue(Enumerable.SequenceEqual(deterministicAction1, deterministicAction2, new FloatThresholdComparer(0.001f))); modelRunner.Dispose(); } } diff --git a/com.unity.ml-agents/Tests/Runtime/RuntimeAPITest.cs b/com.unity.ml-agents/Tests/Runtime/RuntimeAPITest.cs index 4aa5cf9784..743212a69b 100644 --- a/com.unity.ml-agents/Tests/Runtime/RuntimeAPITest.cs +++ b/com.unity.ml-agents/Tests/Runtime/RuntimeAPITest.cs @@ -70,7 +70,7 @@ public IEnumerator RuntimeApiTestWithEnumeratorPasses() behaviorParams.BehaviorName = "TestBehavior"; behaviorParams.TeamId = 42; behaviorParams.UseChildSensors = true; - behaviorParams.StochasticInference = true; + behaviorParams.DeterministicInference = false; behaviorParams.ObservableAttributeHandling = ObservableAttributeOptions.ExamineAll; From 0153d016b4dffb3f1f4392d6f84c0b329e31c704 Mon Sep 17 00:00:00 2001 From: mahon94 Date: Wed, 3 Nov 2021 12:43:12 -0700 Subject: [PATCH 10/15] formatting --- .../Runtime/Inference/BarracudaModelExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs b/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs index d9180a187e..959349e90d 100644 --- a/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs +++ b/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs @@ -310,7 +310,7 @@ public static string DiscreteOutputName(this Model model, bool deterministicInfe } else { - return deterministicInference ? TensorNames.DeterministicDiscreteActionOutput : TensorNames.DiscreteActionOutput; + return deterministicInference ? TensorNames.DeterministicDiscreteActionOutput : TensorNames.DiscreteActionOutput; } } From 32f38af140829c5de4aa4ec3f16b005196cda2fb Mon Sep 17 00:00:00 2001 From: mahon94 Date: Thu, 4 Nov 2021 08:20:29 -0700 Subject: [PATCH 11/15] update tooltip/warning messages --- .../Runtime/Inference/BarracudaModelExtensions.cs | 4 ++-- com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs b/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs index 959349e90d..60e3740e0c 100644 --- a/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs +++ b/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs @@ -412,7 +412,7 @@ public static bool CheckExpectedTensors(this Model model, List fail var actionType = deterministicInference ? "deterministic" : "stochastic"; var actionName = deterministicInference ? "Deterministic" : ""; failedModelChecks.Add( - FailedCheck.Warning($"The model uses {actionType} inference but does not contain {actionName} Continuous Action Output Tensor. Set `Deterministic inference` accordingly.") + FailedCheck.Warning($"The model uses {actionType} inference but does not contain {actionName} Continuous Action Output Tensor. Uncheck `Deterministic inference` flag..") ); return false; } @@ -432,7 +432,7 @@ public static bool CheckExpectedTensors(this Model model, List fail var actionType = deterministicInference ? "deterministic" : "stochastic"; var actionName = deterministicInference ? "Deterministic" : ""; failedModelChecks.Add( - FailedCheck.Warning($"The model uses {actionType} inference but does not contain {actionName} Discrete Action Output Tensor. Set `Deterministic inference` accordingly.") + FailedCheck.Warning($"The model uses {actionType} inference but does not contain {actionName} Discrete Action Output Tensor. Uncheck `Deterministic inference` flag.") ); return false; } diff --git a/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs b/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs index 072b54c742..b0d369b910 100644 --- a/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs +++ b/com.unity.ml-agents/Runtime/Policies/BehaviorParameters.cs @@ -179,7 +179,7 @@ public bool UseChildSensors [HideInInspector] [SerializeField] - [Tooltip("Set action selection to deterministic, Use only in inference mode")] + [Tooltip("Set action selection to deterministic, Only applies to inference from within unity.")] private bool m_DeterministicInference = false; /// From bdb8c26f705b521cd536362e751e57e425346439 Mon Sep 17 00:00:00 2001 From: mahon94 Date: Thu, 4 Nov 2021 09:22:17 -0700 Subject: [PATCH 12/15] cleanup --- com.unity.ml-agents/Runtime/Academy.cs | 6 +++--- .../Runtime/Inference/BarracudaModelExtensions.cs | 4 ++-- .../Runtime/Inference/BarracudaModelParamLoader.cs | 9 ++++++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/com.unity.ml-agents/Runtime/Academy.cs b/com.unity.ml-agents/Runtime/Academy.cs index 0a6624316f..409ccb1dca 100644 --- a/com.unity.ml-agents/Runtime/Academy.cs +++ b/com.unity.ml-agents/Runtime/Academy.cs @@ -616,16 +616,16 @@ void EnvironmentReset() /// /// The inference device (CPU or GPU) the ModelRunner will use. /// - /// Inference only: set to true if the action selection from model should be + /// Inference only: set to true if the action selection from model should be /// Deterministic. /// The ModelRunner compatible with the input settings. internal ModelRunner GetOrCreateModelRunner( - NNModel model, ActionSpec actionSpec, InferenceDevice inferenceDevice, bool DeterministicInference = false) + NNModel model, ActionSpec actionSpec, InferenceDevice inferenceDevice, bool deterministicInference = false) { var modelRunner = m_ModelRunners.Find(x => x.HasModel(model, inferenceDevice)); if (modelRunner == null) { - modelRunner = new ModelRunner(model, actionSpec, inferenceDevice, m_InferenceSeed, DeterministicInference); + modelRunner = new ModelRunner(model, actionSpec, inferenceDevice, m_InferenceSeed, deterministicInference); m_ModelRunners.Add(modelRunner); m_InferenceSeed++; } diff --git a/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs b/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs index 60e3740e0c..5e7338c057 100644 --- a/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs +++ b/com.unity.ml-agents/Runtime/Inference/BarracudaModelExtensions.cs @@ -124,11 +124,11 @@ public static string[] GetOutputNames(this Model model, bool deterministicInfere return names.ToArray(); } - if (model.HasContinuousOutputs()) + if (model.HasContinuousOutputs(deterministicInference)) { names.Add(model.ContinuousOutputName(deterministicInference)); } - if (model.HasDiscreteOutputs()) + if (model.HasDiscreteOutputs(deterministicInference)) { names.Add(model.DiscreteOutputName(deterministicInference)); } diff --git a/com.unity.ml-agents/Runtime/Inference/BarracudaModelParamLoader.cs b/com.unity.ml-agents/Runtime/Inference/BarracudaModelParamLoader.cs index 7e6285a1cb..6fe10566fd 100644 --- a/com.unity.ml-agents/Runtime/Inference/BarracudaModelParamLoader.cs +++ b/com.unity.ml-agents/Runtime/Inference/BarracudaModelParamLoader.cs @@ -184,7 +184,7 @@ public static IEnumerable CheckModel( else if (modelApiVersion == (int)ModelApiVersion.MLAgents2_0) { failedModelChecks.AddRange( - CheckInputTensorPresence(model, brainParameters, memorySize, sensors) + CheckInputTensorPresence(model, brainParameters, memorySize, sensors, deterministicInference) ); failedModelChecks.AddRange( CheckInputTensorShape(model, brainParameters, sensors, observableAttributeTotalSize) @@ -321,6 +321,8 @@ ISensor[] sensors /// The memory size that the model is expecting. /// /// Array of attached sensor components + /// Inference only: set to true if the action selection from model should be + /// Deterministic. /// /// A IEnumerable of the checks that failed /// @@ -328,7 +330,8 @@ static IEnumerable CheckInputTensorPresence( Model model, BrainParameters brainParameters, int memory, - ISensor[] sensors + ISensor[] sensors, + bool deterministicInference = false ) { var failedModelChecks = new List(); @@ -359,7 +362,7 @@ ISensor[] sensors } // If the model uses discrete control but does not have an input for action masks - if (model.HasDiscreteOutputs()) + if (model.HasDiscreteOutputs(deterministicInference)) { if (!tensorsNames.Contains(TensorNames.ActionMaskPlaceholder)) { From f7fd062d4eccf4bf3a781c3ccfb3873fa9f5390a Mon Sep 17 00:00:00 2001 From: mahon94 Date: Mon, 15 Nov 2021 15:26:50 -0800 Subject: [PATCH 13/15] dissect tests --- .../Tests/Editor/Inference/ModelRunnerTest.cs | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs b/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs index 10c20e84fe..701c89db1a 100644 --- a/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs +++ b/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs @@ -180,9 +180,10 @@ public void TestRunModel() [Test] - public void TestRunModel_deterministic() + public void TestRunModel_stochastic() { var actionSpec = GetContinuous2vis8vec2actionActionSpec(); + // deterministicInference = false by default var modelRunner = new ModelRunner(deterministicContinuousNNModel, actionSpec, InferenceDevice.Burst); var sensor_8 = new Sensors.VectorSensor(8, "VectorSensor8"); var info1 = new AgentInfo(); @@ -195,15 +196,28 @@ public void TestRunModel_deterministic() info1.episodeId = 1; modelRunner.PutObservations(info1, obs); modelRunner.DecideBatch(); - var stochAction1 = (float[])modelRunner.GetAction(1).ContinuousActions.Array.Clone(); + var stochAction1 = (float[]) modelRunner.GetAction(1).ContinuousActions.Array.Clone(); modelRunner.PutObservations(info1, obs); modelRunner.DecideBatch(); - var stochAction2 = (float[])modelRunner.GetAction(1).ContinuousActions.Array.Clone(); + var stochAction2 = (float[]) modelRunner.GetAction(1).ContinuousActions.Array.Clone(); // Stochastic action selection should output randomly different action values with same obs Assert.IsFalse(Enumerable.SequenceEqual(stochAction1, stochAction2, new FloatThresholdComparer(0.001f))); - - + modelRunner.Dispose(); + } + [Test] + public void TestRunModel_deterministic() + { + var actionSpec = GetContinuous2vis8vec2actionActionSpec(); + var modelRunner = new ModelRunner(deterministicContinuousNNModel, actionSpec, InferenceDevice.Burst); + var sensor_8 = new Sensors.VectorSensor(8, "VectorSensor8"); + var info1 = new AgentInfo(); + var obs = new[] + { + sensor_8, + sensor_21_20_3.CreateSensors()[0], + sensor_20_22_3.CreateSensors()[0] + }.ToList(); var deterministicModelRunner = new ModelRunner(deterministicContinuousNNModel, actionSpec, InferenceDevice.Burst, deterministicInference: true); info1.episodeId = 1; From 9b3520fc2f7ea51f69bf07b9676b8d51f782423a Mon Sep 17 00:00:00 2001 From: mahon94 Date: Thu, 18 Nov 2021 07:28:39 -0800 Subject: [PATCH 14/15] Update docs --- docs/Getting-Started.md | 2 ++ docs/Learning-Environment-Design-Agents.md | 3 +++ docs/images/3dball_learning_brain.png | Bin 43926 -> 47424 bytes 3 files changed, 5 insertions(+) diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index c4ec5332ad..bd6e62d269 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -119,6 +119,8 @@ example. **Note** : You can modify multiple game objects in a scene by selecting them all at once using the search bar in the Scene Hierarchy. 1. Set the **Inference Device** to use for this model as `CPU`. +1. If the model is trained with Release 19 or later, you can select + `Deterministic Inference` to choose actions deterministically from the model. 1. Click the **Play** button in the Unity Editor and you will see the platforms balance the balls using the pre-trained model. diff --git a/docs/Learning-Environment-Design-Agents.md b/docs/Learning-Environment-Design-Agents.md index 8953894957..216c8d56d1 100644 --- a/docs/Learning-Environment-Design-Agents.md +++ b/docs/Learning-Environment-Design-Agents.md @@ -987,6 +987,9 @@ be called independently of the `Max Step` property. training) - `Inference Device` - Whether to use CPU or GPU to run the model during inference + - `Deterministic Inference` - Weather to set action selection to deterministic, + Only applies to inference from within unity (no python process involved) and + Release 19 or later. - `Behavior Type` - Determines whether the Agent will do training, inference, or use its Heuristic() method: - `Default` - the Agent will train if they connect to a python trainer, diff --git a/docs/images/3dball_learning_brain.png b/docs/images/3dball_learning_brain.png index c133bf27790e0bacdacf3a536ef8480fdf943e98..68757fa6ce7af95c422eb6cea230bdf3f15cfd6b 100644 GIT binary patch literal 47424 zcmaI81yqz<`v*!4Fd#92bhil7D%}!FNrR*`h;&MKH&W7|lypc*N=b)=bV^9K#NC7E zobSKxUF)(IYlfM(-~B$%uQs7d3es2@q!}FDu!4N-nngD$}eL)671}S;2lDW+AHxVJtqE zeWZXo>g~fTRG(E4;OonY(xcAC&WunYcKIyKCkm-@!)Iy}c8UrUKyMdDQpd+z#WY?( zs6>h5#`87h_=-^J6c^Ju7CWW%PRwVrKVS4tjY)6 zLb;M{xWvy3F*4kG-T5)3P^E$}F|#;=3vO6Zv0q44s6Z>!(NR%@e~Pv-LpfV0QG@DF zF)_KKabNdIVPb|25fj(GVUb~d9w6XzK=#5ewE{O_K&7*_^>XF*cBm2)bElD*_=fQM z_V(TG?X7nb2M5Q3u>r3V6k&HvA89#o=pMMT>L!}9rtMP zsqYqXv=XA#lvkpbu(3Cxe!|AV#z8BLK}}69Xm4!FuPiC`&+6bWAzE`sM_Yb&b{7{H zHWzL-8+$W$PCh<9b`CCfF0MyliAN4@ZygO?AH8*;`@4~Uwu6!~mKwfY z!`C)WjzYAw@E!g4^Y=JSTrK{$r?(FOoEA7icKA2!oNOHI|7{zrDhU6SU&+GN^Ly-NydnSz0*s+t3fFOz>D=DVxinx=G-Xw2w z)}54qnaHOSl!Hlv#}%ZS`XHaLjDDVel$Ak+K2i19)co?PEHcT0hD=^U0*!=BuZ}Xk zO2X|0+n&R`=b6w-^O@~#z178+cbUzZhC(f#gI+(Xez^bWo2au{kig5j$KDjeSP|uS)x8Yv9G!5oYV)D2{>_5XA4aC3A7OMi+HhT=q5YWP7rB z-?pFgBP101evSu%i73$ueGpm%z|$2qqJ|Kghzj|FMvmOq0aG7ZS)FN3*!)$(V0HLnkctcgQ>h?vL%B1L~bt>3$?0D3ux{1 zUo|>^cHA15<#*X-1^YCbJ<&zE{SqZ|<65ZxIW;Q$$TV%Q(ch`!R zcE6V?v!}`JX!XlVRJ@9!kQ&qKUdyfCy!-Yei`g#~JQkz$udg-Vj_R3iMvL4GKjZ&x zt5>E!db!%N_ldz=ty}Gi*mFVv*Ol#4ROXZlx z`(5+R?@TsF^Ob8X)jV6zji`G)|NO2y?B^bmeB*SsJEwg%sqY=B$(E6})|aRvmIqGi zK=p;jkI3TZi4QFqFV~XvS5GGOlL%PU3(Dh5#p85be`NFrLN!HpADfGVxYc|hS zX>7Tz2-mEpypnp68lemeS>pyG$IKr@M#ALNvYstkejp_1sFT8bZdUzQy)WK%MRSd4w_-I^#~8x+{PU;eYoESlrx zk5b)N$-iD&(_D)k3xPWUu1e(T^hbFzF`3-{sS2aDtD~W$XyHql8uUl6YRCEKMdZ_- zU`1K=#L#d#ZK-|^pJ65UUYdzgzgZ4vSD*E|y>_|g%0W%zF!am%CiYJGi>;x_Bl3)d zd>r$sa>LOYD-AILyiyNd;Sq>0x^_|+0ZSi&hEDcx^N!Q*yMYg(-Ty|U4|%mvX%gO_ zA@oXtZ|%OPhJ*hF624T4p9GI@D~lvVAZjH~E``f3;YNQX*@Ikxf0N$uaT9p5ZYQqx zNG9v4?y0fp>I%EB-g9$&P1T!O=d+7)ZL~)*nNy%$Xtr|%sB1|3VMVl)f zJ@Vut!slg~o=~Df3Qw#|3NN3?Q9t*DvG{^q!0`YPEE^g9{v))t7(HAm7$oOq#p_^s zG4tmz_4b1e*Zo?G7Z-u|h%h`$v}>LdvTA0n)7g-?Jepsz z_>@j)ljyiMB6qv3e>=L(I)1qJJ*iZ#;9kGg8~u>p?risytxAhAMn2m`(eg)ccy*!^ zqk5epKG&#y^sn- zB!3EuZDFwmB!Kj>r8xXXHs`YNc?JU#pJww_Yu@8XzJ4|CMKy6d+F8d)S0tGeo|$-{ zSy#|NZgnWEFu{Z1BvgBN2F^6 zrzATTzB8TJu{E!~f3*spP<0yA`N+GH%u5R0kLsrKJ<~Mp-G-%*eCp{xT-xfU=goRz zN>JxcFOt-`12wk&O=vG<2=?adb4fA{Fz1< z8yac?&id>Op?4n-tPHp!^4_+yFHM_eP9(CEBC4wE1&dtat`DZO*tA^NT5|5=?Bkz{ zLn)%3+D31@=KfSh{^I@qm&p5hqZ@k8GW~pF!XIx+c}Z z)j2t6IEsV>js;?5Aj6_YdR_e(^;e4K9yEk@389G}9P%cU+8XzB(j)k#m5Er8I^VWy zqN||XcR+Z6`di(V{DK^ZpkG7D`S-PM7@?pR#u__q%O1L9BCEbe*nQrHpL;Pdk*j?P zC(}+dU%PBsQLI7Z&Z6{Q7tl5QHbffnX>sF)tQYHiVtm8qUsS)!lfj+K@L!4O7}pQ_ z+8sfX%i<)*oXo#GZP#YpyShz4`iqX#TThrvd0v0PGb)IQm|H5~$pdY-$x?yA6mDtx z`@He5H9A@S9ZafiH%h-!V8J0q4<-_?&=bq?2fR=5uu^Ve4oG{|niO<}{_Lq5=8L46 z&9}LHO3F<44c(<#2EF0;?lCq`f0mtIxY@p5kW7@F+V%EobaP{#yuTI0f?cr2$vl_z zC3!c&4R>2*j`RzqdH~s1e9*9WPR2vBp_I`#@B&@h;TM z+^8>s*^>5P-eDvEdEzKLnnK_?Hnn6HmSH#!c5q6z`PIeGsJf<>uYV%==s=k_+~@@z zKL32>ilorv!BtzrhKvVRzbMoUpcRdvaoZ>}0aEy_gW(|Uj20_p`&p)@yCJIBnIenw zRk;c_Nj`j0LTmOqim>j1sz5zniaHruvsGF16V(>OGF*hPMAu4~C6ju4GSBkCW)?OM zeFpyxr&+a)w?MXV_jmoKHHnkn8X2QyN-x@ZcKf`;>`T$_K9I;Uize*~rQg$!?+^*5 zZwGY$Z7CU)3YOGGqqnb_kAI);y$3h#kNH;O*cowi z+l-6YzGt8U6=Z7HfkB*J*`-AlHzn>}JL#(41g9MMemp&o=_eiN}pfC-lB|CDw_WH+`P-7K{ zjGY3ZdR;zq8g|Fj697@~OSht|9HEUl?3HFY7>BJGbZ|qW9wK?)Tzq0wq`OczZI7Xv z5Yya%KdE#uYDbY2;a>Sbi99+#o;a#8IRb24Fc#1yThXx@dFWxrRx_agS3(fyRp@^# zH?J|k4+)L^q4QfPJq)pIHKX-yiN)Q)aYc$2oDnc$_CSK=I;^n%ga3um2*TKqbcN?_ z*@~IMhposszq|*#qp8^6Acsmdy6n9NQ0*BD;iM;*v-7WEvD4i-G7TYb!>$VRksO{R zizKOAx(o2&J+4nC;lS5C zY#+{G3=Vc1_xg@E$NU$b*`zFZ-|A*~9Li4DSS1M_4@=;_Y&{5Vh&dB{&CqB81{@zH zA|az}Gey(ZjM~$mSf`fgHmdg}vM_yqRj06WQU7+1KSj{3 zGA6wW07K;_SG&*OzdM1VaX6+ZfZ%p1IR~nakP;H zn%(oc2B#BBPsMb8wXe4~@0it!5>6wjTkj>S8GY8SG46^WdANGc?LW%I(v3#t!7&wm zMo;ct`a1}V%yI#&PN<$HM+>EVY&jltkL?R}k3RjIrt9g85s55?bbk8@HH|_8B|r%H z&So7CTS#FpAX_(;14G-t*nJ@-Pd%v=jv@fxeiyTW7(Eg$acrm;w( z0$?rE5;^$_UA!#-ntcez=n>?6$}$Jjd9wJM^UaNxD4OX9NYAkPJ0q{Zc>XypGBh|5 zG46@UB3$XjS4`N*^w#@X0?1-%r+SGd%?Ch!Ug$4Z@8v(X`tfW22d7b2k6ky#C(jNh zaa0x=2vl!ysdo0|1{i*7Kc8qLl}$c$YCk~3iMSUZYW1!?;J_Y09OicbS*2T2Vi2+t z6OutNb5|u021vb7p^4Dy*aDA>!-uTy-*p^Ep83t;iuvxf{7KJ3k=T}^D3FPzy_`Gx zQR8rTbC<=g()Yj}?ZrEi48$N%HMeytRxQjCq~SFnwZm_0+!M(F zRJQ$lM#Z^2VeI))ZDa$VV#bQUJe^PZ!h0WzrE<-DsTCxGsE`Nik{=F#`3DA(Ks3oEz@eA(n~Ae(R~KaEe7pDI zfjTd9W!Pb{ZoLBUf^GGiX)d>eZ$$ah(UcF#-^_k_sc~L<6-cC4(8#-Q#(U6-KmKxH z&Otf=rtYklqv)-vuy?JrVU95@xIgN0KT<@4ZIl!5N$l2iGhH}>`1aEU4?43<&sxig zLEaV23C1N}GFjVJ)j*#u2FUZ96`P}9xzQNbG?H|Z!iPO{59P9YMO9q3Ki9bz&(z=5 zL^~QEXZ+{xRA|Ya7MMYbfWPr=msA;p$FV4`d)DU`3EL<(0ClF-AswBP3b7g~nXai* zsGsCqh{wh&<4k@3LqpnA8$w365Q4?_zlXQ_od29gK*E5M-?6|;pCdAm?yKUJ!c=6Z4P@+_+eZ4Q_FC^ovoB9} z8tx8o2_|!?Ly%0HY=DaiNGca#ts)hqX**hnEFkfd}c%te1t{@+7pVRN40J_>RsCuVSz15zg^3owRgQ5 z9E@f|G9TM76KfoWmvPZ4ofW zs?ap+pb%f0@fRtE5FxG`3*OqJ=;@GZF{etqG*Qqu2*%<39I%B(tZOW;1)^shLMccX zE~O-VNEnLi>P!f{N!l25T{WA3L9=j=-kv!<=3p^ElKAxkQgIY~di@nQ^w;+ppmq6< zi~CUc%)6j%$Rjtl#Qo@|A-#HYFLbib=gEki@%vvtza{s)d3AHC^-W)@{NU(yW)&eK zxn{F~h4~!SHl5YG1k7ux%2YI~A$VV>Lb`!y%;Os0qe5fv06pDU8UiDpO2psnPNwKK z5hMdtc8jLe%vbQ{d?l45oSby^9<^>=l1(LU!~HyxO}dv( zO~f(mxv%QpreL(BKM?+``}eebSH%$`krN#4$b0r>*g_InG@efssU`LTf$sa|$#%Zm z(fVZydWxKn)nw@erVstGo$te0p&Y%$N3Yb`TCVJA3SmgdEN|>lcYf9>mNslXOrQFs zkY0paDf2>300rZ|(r3st#A>1_KeS>700~AO4iX;OA$cU40stFQr^^ADvInFXjcIwo z>yJE~*Ixi!v02$UQ5AiK^%w?Od#!IwRy0`bUc7gKxP(#Nq z&{RsY*as$Ys(@$fi)dJRO|0WQZ6Z!;mxO@6L%TErT8=eQ#0&sG3`>nmb3JhR2mt6m z)G>~vXjybiKX#_8{2y>F)jLkNR8|a=WbY zt*TEB)}hX@Fo~ec@0tAH+`~rxfS~VGnu+dmKdfHz&(%$}`gc0k?U= zNUWkGeDvvEh`(OdzG21@xhjV%imBS~=9}C`zk0s&d?@!7d^2)9IrT~tCEheG(E)s; z#(e;;_!dT5;uSy&j$EI0>MJ>x)QePQhb>FAs-(Qmm(b)*BBcDBlZR7$w#N%|ks#v1 zB1eqcNR_XHXh*$sDeN3iCNy3E!SQ5ecdl^_fQ+P9Z|k2uri{Q93v%o;W{==ugWyyn z>Us6_)xZ9R0hfX;@nE&B$X>y~50GnrMwMq9bVcl{;w#;O?3)M1+r#}Bqz{hE<~B_! zW7YV7jQ+Bmm^i2bmQS3rt$|5D{Vh;pt(-+v_vF@L@j}HG>Jw4PCB29~*C06 zn-aJmG5ctFc#D+t!)g`vp)_9EzV>nnP{izz(EE?S(lJ^}n`O9t^Z@E{(SiAok`y=q zs=<|OPtviM?X;ueJ--lpAkN~y=woEs;&rW~@$mkVgKyz-t1oifcC13qlCSNGa_q)n z`l|~>G<Xx`JmzyD>EhF?y=8G^; z?R8Natzx6(0=y7?LPfOsEw4zNcW0T?o>=33xJU7lDDz&KsDJCqsIf|LFI5BR9;*I+ z^(a(O2vyhpw>s6oKLHV0iPCiuaiaQz>oWS*kQj;cyDYmlH012pD_xOAzWbAB-h&S) zZ=83k?)p`bs3q;<2fEuj9>q$#TJsU{Ya9+0IO-AG<=}oZRW3F_P_RVOQ*PLyYS20o zgPz`tKhxw^Rk|}*MD`o$)D8Md=uB5jed8VxTLQq=Ti3SU9Jq_BJ*6015(QUiIFu#Y zxMhpJiUw#J{e)+$Tg7Xjxk zv96p@b&L9`kH9ua=QB;03>QS@=5%X8?V4G>q(kiFv7)QL1+9W zQHbT{Q-=F0A6H<_SS% ze@2>Ar&H|^fYtTu3a@GHn%H&E1Qo|d;J+)Q4dsY_jLCb|{Zxe!7qvjH$rO>@YL?|? zPQaP+{_Jl-TO!&-CzEyxi+fA-co!(h8ibM0AB6FgDy?!0~Y$s$H92?k~fcV_W$tvcXX}ocwB8X??j|m=Kt1Cpia<24f<{b7H4Cul$kXQ5`zTc6*P3_zH^s8obOi@WtS3r5&+5&<)m*0Gbcm8$UMWJuv z!DsI?`}szrLp+~A@Irhv>=g~3{;0ki-6>Mk<9{&lQ4h%ER$?q%Kf@)Czt#Q(*MLPN z!G16nqE35@oSldMOY(A??ClpC`Q_r=6IZ$esfM@8HZA+@_h|f|P&~@`ywFKq4rtlt zL~)YG#TpW`ddZhrp#772@^Ch56$5x+05zsWtmKhk{$%%Sp{0f27N(F=Jxd=)%55sQ zj{Dt@koG{Ty##m-DZ?=+yk^0S{Eok}PCEdZli+27T!7|Ug}mJbr~a!-5S@V4gi+o_ z^ylusm@+-MlI4#8mq`Rt_@>eN4d7vUpbvA|ElZxfwV5bNOkmU1-ovMK2lP2LigeFK z*}l<-NJe|?qx{(E_xF#A&vbaLT%vc9^IE~~ok$f`Nu#^S=8Ik=u_ZP*ZCjoW)XlrU zaI^OYh8BbMO!Y`~#qq}0G$$XfXx7@F&a;SHXq~?2Nurl`T)fdrAGm_{{77$E+@4#I z??X)dC>~oTP(wrmGQOTKho5-t%{Rjxo@Vd$6f;ZON6ISklYXJ;^oq&&J=*m^wjK~H zBtZ5CW<&uJbedI*#C^=&AvzSH3VP;#w)?vXCAwn&iNm_wqv+7J1QiAJn_!|!7yht( z5se>Fy`cyTxs!VDpw=;zY%#B%+^fQYI6@hM8~Phsmusi)8IzHxh01*8=MsGi5cGwWcvE<|=KS~Nu<}%Hj>h13!3`*uF#mv(6@K-j?YBn7&O0MfqcalAz zf`GG()>^-E+ssc2*rWF=81rDDs_XjeLFcq>PpQS)`$;Ud2cjH7Sol<1k{Q7PeVTz# zdH{vNiDB<61V}`Q>|f8tMM^=Ft-&qX#UbZi3^S*Wz#w=C3B-A8(B%V-^L>wG9heIM zS+VFc5qZwR`c@;|$LI>9?jq3@E|;#cOsnpw-av$?*gVsg+nYaYAJg9AWjOpf{T;YR zYccfK%8*fqC@ZY^xV;8r*N6q$=z_lp;p2VKn;2Tf-(f&sV=3x2If9aY0pL?d3!Nq_+)PD<-)D}KT4`O4;x2q>}FxRcZPXP|qXcPj@<7#v)-2nA`Uu^SN`(AF;jcqZK6L|lj zYLD8a5o=RD2Cu|2N1f%w!;Ae6oD=(zoM#go%X#q%VTB@-?eJiI zH(733ju*ss*~eNV2)5kanh%6Kb$H&x#@&oa_#GehFK#5psI;|-YfkRz2th76n@J0<0hxZ=qwr& z$zuVA-DIdU?f3ocz9Gza{GZDrhER0_So=pUE(*n_Vp24oCFTwCUWAGc*K4HNtY+z! z*2Ue6$0)n}($1y#zf zW&xC402v%Z5lsC2i3t8}k$|2;5%(;G!czrb4+JdFS&;Xu+I3R;I~hYmJb>hN_`7al zt5$EHnwNxA;-OlR@%~>J^9Uxx7m4gfCly7`4C--w<8H$Xz;p(9qL?8z?OyRDw6>}M zTXKy_mb2CKtPf>UUG?_@?wA!?H>!i_LEW&(Qr5KGLO!`sLhP+Wp}jUxI<$;F8bYLivi|zA82Xm)@Z++gYyy!k>ruYSm3fT7Sgvd8WqN+ zG#Uu%s6mxb&=rD)u2W)Aw5d2F9pikNakFHRUyl{1^$d@7ys_5ok#s~Jd3N@N_^fRQ zmiFWX-L_%9{Tk1++A5-@H}KXsU?Ub4p(H7cK?&n~+LH{8wQ~S$W3&_enoAKkN$u8TPmL$&iwna&S%h zjd8IUfJJUB0;zp7o#TMJcLkC?#x01hybAR@$*z7}7eFb0K1PU787~tJgu4ptv{mud zq6b=)dF~Nhd~yDM#>#*R#(*$D@m8`R;V>WuY6yHFIm7epOaezJPds$~YL80UEFC$Qv&7F4sk$71u-R*pr2A2``Q~yvbvWFP z_?a|98xQGA(>6bpk+5k`cm?Kyv_C?kNK??HE|qZpnBWv;JPLqK(&JD9BqWw&_0QUy zd4mx740Ls;lC5kSi6BUa44tar?bVSxg$jhlL)n@RR^E`oW1&oX3uRQ>im&|>!9585 zA<@t|jNxG> zU${A-J1*BCQo&BW@A`~4ncFPt`8RVq*n3z{`M2DBUv}G~m$^Y7$<8XQsqPF<4v6x7 z#Eq30<&PK{1hStw6gf75siINzR=@6Tx+S5w&X>KvZLgXV1Xaw`OeH8zJ(EJ6G{)rC^$UQ zu8#Q@PE?$-oDWMl(qMEQ;du?U3cI)DtbhuH_U;`eX71ibq*VYhf_w;g<+mU8A1)(XO`qjWw{KR zUp?wmNlp8cv%OFY44PQJBB5Xu)cbnYeaId71!760HNprvx><_k^eLLM-{+d&xsV9D zmRq7a?@ZA^5!iH}7CY}QeF-J;=@lTSalNCN;l5tvFP|z?CCj!S{H74`Py!}kcIt2d zr=IuqiDj6N4Z9(~7}*G*i3F6eko`7}FbYISDNwVt5DKPfs^Z|cUlMB2AibAfY_ssS z(*f3Wt`e9$u8CEJf|rRVVn;=VV%B07w@QxhlL>n;EN8&Be~EPd{X#?~+BzKN9wFu_ z;DO#14jT&0K%{R>$TrForakyg)xvU5M1g=HistHRLG=FXJE;>mQ}@0 zac2)5=+~Vpjrm`5{`_K?J3h+$rETe+&nXSL-2ysRg58Xd(pqdeZ4E&Y_GAIkpoUK` zUzNB-_1)&Z^D1u+4!&{zV*!UmARr-Q|4Oc|O567tM%eTEI{iMnSoe9#Ls}83MytOJ z-2h5X0!el}U>f7r<_b)NN4?`;vM7Uzl+C!%dF;5V%~0X|{C5$r z{jiqeKW#TibWj|j>NLAm1fSggheO@So$n7fu1UmOzrl&8#J(28e>~2NrZr0RK_N;o zz`vG*!LF286;K&H6kLNsiC}C}TKdMa?vI;K z@q9*+PAX#fcv|PtUkmk-u}4sK%})IPR+JR?NfuEWecOEna&3=?AihaZl(|~#pNp;^ zl-h*HKmyD1klBy>CH5m^-={sRG8pES$^hJJY%AMrjTbhEBp&|+(#Ma281n}JNq?HA zk!6t2leu&_pqNtjhe?Psi9d>G9{Wd%1UU{_D1CD@vN!_1M?lJzPl=-3gXm-kFn30;eoQ26X`Py zvU{sXihB`?NKsKcF`_YWO5u2hd;EU2nnL|%A)ypPT4n|5sbS^U<<)*%^M2xi&qrplP~*%2t(+D1oMjLYgF9V` z3L+$(tjo;=VRE+6z%X#Gcs_77N%ZJP-rbh8g6;w&5qVgA=_Yh_LH`&raP!~=%=Si$ zxzLWfecx-?P6pn}DK5Uxcemr0)t$bI^eJYF$h4wV8!G zyy~~aaTna;Ow5b2$v+yLtm-#Erj0TfzA(LsdV`<2h1jWnwn*1`soLryPbEn@)bbN*(V6$f7 z3*qa+aU3?gW4l5UtClj8VIM88XzZ5TcZ;P~Dm*tnn+chwFQ?ZhFsUBORCaygfhoSX zv%Dz)*Rs4{4}lGXq!2nZM7+VC9gCb-scv>rsL|`%-3l&SHCmiEJg&Df(0 zT@~IYf)b44kWU>E_xS{1ZJBRNGs*D5_Y7t$jLl5Y?MuYL@(R)LxWh}ISNEwxf?#MphcfqScfbFJUhll5S`aO3HDL+qZ2?Ep%Z=&Px3P<*hmck zUR=N-_KeX${SapPPL+FPChv{Z*0YsBX9-;GXi(b=9dv=D*kMV=VU)!H7fei+J4505 z@D$J}o`miPAovb1{yO~59!>h;0qM|^ZAhY7_X`ESWu^|75@09tne;qSfDh}B(5#W* zo2ORBW4^A_T}gd0znS0bU`clU9Dt zBXgj!UOref-e8i$KeyrVx>?!a0M~{ZJ3>G&tka>o_Vp5JZ(cUL;IqX5h_|f``ByHNJuQ8z&aOrz#{e+}26dkXKfGxarQOD$zN!*>t`r zk)qm1NYho{KNsqhSY`jDe<^C-$~-7*9qSsZisR4$d>F_4nJh8n$o;^wprj{nrm_@b zlMR#0FT6L>T`befzFt1x=CI%+Zw>hE%eyPK)I}OwEh zE1WTe5)w9Oa;m<*mGDE}kU9U>UbVtG z5e;{Q&{V3Q?iAPt)ccuP{x$TuXnih(%dy|<`2G%m^L<>Dr?vo*yZ12m=A5diOr}Y)2#ifOg1BJ?;{=_B&inh9p2K>16%FnO z%Si-+W{xG&Rg`{|gSGRY<)!1PwtwE$U>PXKP(^y?w;Vc;^;F2b5pzv(J606ky044? z>#$V9gO}~E7(P18M1qi~;ibbVjkh%XhD8S_YlV%w#jx)9a4oi`cDv zB`4=9^W&4T2WXH8P->q2uNWt#eGK)=8n)%#Hc)ze5MUVbv7T#qoHE6wch_j3p$L1d zEsL#wu~V_k#dgrp09_tP6fM%Zk3nT?ztP_dQ%URN;}g0J<_9Ts&}y55sN=t_G3&BJ zuI+RxBDn#tBeBL3j)+CwAycZNB~;aUdY}F8F~lhUTKiT_WyQnc@q7i0*f zRrj{ybgfIw&7LGi^Lsu@bWLP` z0v+oaX!ImK*fZ?h1DbOj&@3c!4g((#a_Bt;Of0T=Jd}S$%4e+>;d@%Ml|Q1zf|LJ9RA zzB?&p1fD+v63Nn6=_vBT!U5nJQUj_fzwzxu^89DDJKn(+gz)E~vVtuFMw~A0Y$1@x z;i{A2$qk50Zsonm2m!Gh%N~=y<;VO$&7xsA11Y=DoB8!oXAdUP{oqr+XdLKc2@al6 z0R1)yBZJc@s8*{)lk-ud^KWftjnWU*Z|AgyUYZT0GMMvD`AI+DEG}($I91xh>Ur54 z>Ax-GzFO}iejs|j%&hXUpgdqh_^L9$E0JNG=WL|C+WMzDa0~LR1Ia$kV_sK50)*q0!gKL<#f~du6ta3%|b=Psl(MHpw?wz4vVH@jtqWyzqDi?^9apw1KikH5D2wG7r}u- z&9$H#{k$MQ!3w6xV7~`Nqew29Lk!=8v+!(t^68Yy7dvHy&mDjETFS)n;?uu#^flYA zgak03L`cGFVY4t&@>oU+-yi=9#ncRsvAu?006z=3z{mRU;bD@;i13+6=wIPnWQ&wQ zs2$0oI3)pC>ks1@<(0+!r8M)6;=7K;romzbeIynUiJn02cfu97PX?h^)Pdj zYs>~oUAY;^vO%Tu>YA?={enGBHLg0`OVUui*qx~<#*kTkL0w3s4dZ$(MatyY0aW19 zWFIkLz^LW=E=Yah>|naOm7Q%OyNMo0bl*MX3xmWE=NRE}Vtqst?(2#w0- z&YLMSu+aOmKLnG(80#QK-XRmhgJFZyv6UsqS_c9%Up*TYu%Xy`VV_t}P=Vc!g#5ee za1R)eg4f4mGkHm9Z1BJaIOlx)#ut&FQINff8;>>(sMwqi(n6jnY`NL3kc)(yuRsF} zSH4B%gibo|-Oyp(hZ?2Gs=xop`R6Z7M1sjo>k?#_r-BK8N&|Z>-aoDDHlLHKbb#46 z%yO4dtdbrh8pc&HT?kE{q%E7by@dMHq5(A;qT zrL6Pj744<`PJY{#f}apLT4bKmimf zhAS4g-3obocu26JTT=9)<%}afSG-8PC#y+~QnE+{qP19o8uMlwC+;|~m>$#cnskXr zJpEzk{xh6s-8R{Md*j%lR(~0%!&xI_4`;A3*)QU*BFi`fRZn%PA62f}?1Np9;OUf+ z*q@d&-^HdL$ikf-lzdF(b#6H4CQ9~Z$@p0a!f1t&^aO(Iw!@%glp?Yziuy7=mnE=& zY$uUE;#}dpXV)(^p-nug!@}A6M!FcDP6m$;lD~U%DIF9VNI_Y0Jni{1|2cuT3XKSN zt!DS8HT7t+Yy=*LijAj$$WA;w)>_!=qX_*AJW&Zal-9hrikEWvW&MrFcXABeb zfWkTYwqqE0vXJ7Msin|9*adGW(v*nnthJ$4#Et>#&321I@4YUz zp08%n83fRJpwB7*JIwI;*-a1T9B|ahXaSmLwA^i`0reb9q9hx z;XQ2gAsVp2^(Kiov%hEo)$2aYLDbiG?AMnZpN~sgP@e2Bql0U6q7V*6iH~+N)IA_H zu=orCP4-Q&!rVr)8GHf)AgT9|&bhK^PWYwT$*>P%(6KT6uQ?%jAkB*0=%65SeOw^m zi1$6T2A)yj!$nkPHGXe<$RuF;9L=YXRYrXQ*7FdUCCS4vYUbJkV2idqFws|*!r0Qt zZrj|_w5>R7+N?3;=n+_V(-^Rj)>mSzDHAzUx1c+7jf!6PjBOsldMK334SprlPNO4> zSK&9zr9BZ8IIw9VMdCyE1!3zxX)4dW>^%~f=}MEwmv%sR9cx?kAdeeW9RVRi3K{-V zWTFhuQ>_6J+%3IoD>j>FdG^MUJCkMld9=<$upTZ5p;wjId}@V0v zcTR^*$DGaTM-2hom>7!BwDssRjoVBi_4QR3m}%O#Sfly92@uIP{`ouS>!`!E?3=rS zUJ96H`iym#f<1XKLrCDY8<i z4U&KIjS3JIFBZA#z2>tFg$TC1nmSB;+6ztFSr9S!w9UARONF&1KJVadc=)#I`t)b2 zX4JcJnr@8v&-XSJu7T~$rTO>=7kwI>q8+gI z-#0ae8k>Pp;{lS_4_UvkHb;B{!Hv5K%1^VL_AM!~DFq886rM*Sf2dHtTu-w}kUcX4 z^LA2S&v>r)h-%K2+b;%wv+TkZaaA{gN6}-nf4&$`fRB?^d@Reg`}cnVq-gfR(;32`SMmIgQTPhdb{3^HSJA*@>u$BlY+{ary=g_x z4qs?VDtPLCY%YKGcbL`!aW6!edKQVHbuIW2Jy+sK>PqEr4 z+^RTV+4=3}xZN@W9xX^uIpd4E#)RjTqSO3(Q9 zx{Gur5BNM?;OXuHiXSEmA-mz_N?ouIWz=hCf7um~4n}{{w!FM>ds7ED#M`Vs%-Vl{ zbO@%8{L%zA(E}hS6V6i&M?Rf@0MF*=*tVe*h1#({-*p0WlmZ_>9`cZQ{YEv(0#C)K zciEI*jl8jZcl;4ZT`Q#}uCTkJD2i|AZ2aCO9K`JwLEjY1JXb-90G6jRyf=dY#Q2Uc zh+~Y3RT9mA`=bvgcV7YK%>~5X6HPOeph(oThs7}fC;h@eYQ$W}8dZ=eV5;v93lk+8 zHwvtA+*F3c*!LA8L83XhU#f5R5QBR>xc;PxAGYF)$Y#@a_vS}BKOcDbjX`mDzqbq+ zoh2>z7-bkMWE=qYg>VZFy1qu`eGIR7VC|?|-v#!XR8UX@Q1zq~s7J)XjCC0fr218Mz8VjU&8edDo!MFi)Y+Hf zr>tponR;_l+8ItVrnMH2wr>S!+_f%Ff7&>iL}Z22y`~4$J=3p9M&oUJ06Kp~&P#Lf zj}S`R=Y$yb$XNiAE&7UHu(v{^etKi>Fy6g{vGM$hr~PF$*SSmUb9 z$!R9aV3rimwD|~csb+=v?UC31SM~?@Xx_l6gHLgZ&xii_xCCZ$+%8f9UkTJAO&(rv zVK2I;v$p;>8|2HBCQYkyNwQW&K3aKRJXi%-74rOvMPyJG`Z7i2NxS1^{#(AYIF@iF zKcxd;(xKcM;h&_5H97sBh4nm#9Igq0(26~;N3oTL#7i5@0vswbS^}inW23F72@(P> zzpVQ)l2@s#ji+V+BPwdaX(nFBarU^~3z`rrz5(+w(ANdkXQ0F-od8o84|+qlrv9uj zFrHcetPL!21_f?=@3C^@NFV%vPCUj+COa4U<6XtepOb%#_mL=oWcMO>qrP%hx{AZ= z>U{AQNFuE1BmvI2q^-j}u%1Ea{?9gw)`4&+rUIBUdv?{rKL4^F={8Q=miKq{*f>7f z|HIZ-M@89w(ZVpm0Mgx!2q+;+cZUclA`+4cDo7~Z-5sKogoKEcNOyzM2q;}r(t?6; z&-i`c@2;xWr3{ zB1H0D3-y?=6VTrg!n}~i`^P_+stTS11W9#nhk=lfjhWj*N7CMYRU7$q?RH~=0)cdj z5jH`geeByOVg`Q(16pQu#w41UIeb2ovdEKOs{f_6h}B#YY~SuR8uNnlxS_NaElU;` z`Tfx&_;H(uRHE_dbQf~xz0V)s0zvyP4PDk&_M7cSVO1oOr=x&N56@*${sK4V7X51c z9bU^diy&LKTPK}rRy8w0tTF!L)S9g!I_ch}9o!p%v#cZ26*;W2#w5+pLJAYo_4;3- z+D}*09{1 z<^w1Z{y6PLp(%)?LFrO9x$=eOo|-uHI;wTqQ1dr-3TBHr)eu6?{gK(>>s2*cb zeDF#Waecuogo1dPk$i30Ui!Xu@tasv4j@JTZ?;~ftle2_D3$tRwj`U`#s-oN?1b>l zW5S9-(=W&-C*cc~&(ShPowV$y4Ts~tF^X}%TR92SJk-`Q8kR>1Ohwc5;yOLm3eR^CgeBOgG@Fa8b3v;xNt0dTIm*%NG!T%NAzMu3s18V z5*Z$?TAQ#>kaM-FM#rioH1!|cSn9rucwqI0`0Zm92LdzDtEtl?u%-c_Z6wJ=-LjS zh%(rizLQa4Aw|sv1dRKMV(y+&WiQuaF_>Hf;M2T1wCt zuF1@FNRdaeNd5~3j7eqpF|a>cG}acx??4d%lCBnEqSvuykI_$Na}~`~ zj!tB$*^R#~CK5`_Q&$0fS3L60zisIHf{7Z|SQe0~3FraUCvU;WQ9%EA{4X)6$=+OL zS>?4jx<$=oEeV$DXE(>uupv+h<3<1;q1k*HJsM;vqDW+;kMu+d3-O554oV}FMwNY; zbN*3=PGb6LZ}Ubm(3(oCyyDUBA{lxQL`{f-&4ZA9H>9hgl$8WVG{&IcmF`!vRVt48 zrDI_vrIv9V53xuusF81nqC%GpSQEupe@L5+L$$gU-UGVi{7Vb>p?sXXCINzK=n=dn zQN0v}WtPtj-T-JnXx;Wi(Q3pqER5F$it6xks$q&~b*lgH$Hc-x;e{C`SM-$8i<2yHSnTW(`j)taWlOC#4(euh6bYKa#(#{q2Wvt=O@(N!UjnT& zq{INl>c(E(y%d>CR)BlD}Po!G*yS+`{ez=zmIy`8Xbk__ zw8V~F8?ZHH224d1FN}4$N1um=`K!ho*FMuB55ih{9l{h3?xlNexAxNy$>*b4houZB zEM&xgq8pQ~tZ+E{OASY}L~bYRRbOzaB5ugf?3-|`Uv?|kRbQNAWZ}08#JKYa)K+5* zix9d~z%@Ic*B2h67FSuP*oGjv-HuLf(wcc8aknL*=<5fiDdQ3Pi|un<7xFB>xU=PTK=ORDp)?cgM?UAez ze|<>8KoUyxu)69>!-4LY=G<4EI%nI%UDH;1`o*ZWfg(|zdSyBlt?E_tM)qoDc45m? z6^X#Ah;3``m{seo3A9YEdlL2S*`kakIC{#Rw;o|9ptsH8pIUmeI?dFl0cz4Pr*S=IX3b9LZ? zo`C4;JxR+rb}8!`>dPx(*-8;O+h7t3V@MD}O{Nb*$4{<(b+w}w%b{1{h7rBGpwgU6`t;S4I*SfO|p9-P}!fIou6z0yg8hWA(|8I=xU?y)8}Zc~|?x29;C3$P=OV^jk$t-$|S%p##W^u+A@|j zPir>Sj0_35T<3KB<6r&@g%q$^UH|&=y}LwlkB@8K)a#!vVc&VbR)+24N)Wt=RNy?? zA?BFbEAh^CkNZ*cyNszK`<<$-Vl6dqjM!_2mYUC!`~7JGx9QOzXJMGV-$pBt}R|(CTZUM za3vNukdgRDSBhgmB_=R-vg$0p5Bw&ipP`4R?J5V<4*S753Hg^-AD+O6 z1r^7bP$Q?z~PzfGQow7Fk>)P|hF z9N8cKmmmBqL;*=j5H=rW730;5K6pcyu1k#x0h zE2glX@}IS92vvRgFk!0D`&AWS)eX_vK?9_ICx!P>Q#BJ^j zyZouo?*gNlD)Y;$%Od0AGv1=O73pWbDQTB>>3=`zEV^ZL0PAIWyASWDRk3~V_}|pYaVjTc$@Ol!Ki(!hDE|5Hr?Ff{ zfDLfem=a`w+VhFzxvp`Nj>LT;U0HNVTt!x8I0X){kb=&x@k>*nqE zDBaLd&sdC(t0AcBxw&y3+fYZ`efGzv&+4+eT6MWKCzn^XT2Z==wsh)wMdc#{tH}ByhUrhRW%)XoEOybSt`1JWw$<{lghFhPD?r7_8RXS*GWeYw2 ze5WTx=A)6jx6P$z3l28DEeB7Vm}3q6oPECT=BF8ah!qyEvljOrc&aOJ^mWvs{^>Ug znuk4Yi;`CaKHD#KQiNxwW}b1p@|ocDIX%nwjFm{KtSQhe^^%D$VGOzP^^R4Z>T5|> zK^H59Lt5JlcAjTvZBYXy|4P&>8PH;^Q@;4!s)Ul2UzgQ;_d1%ef4Mu4@&GlMKU%D# zTJ>ax_omcG`R$}DargJIxwgzNt|si|Qw%`HWmdQ)>*0 zu#sVmO@A&85XtmwymZ85wd&65@T2%vljr;?doQzxeyQSpRYd0fr;OshM@g8r2>;HN zX31We1b;}i+5QeR4H!jjmieO|)Xg**E57iB$Ou0pRsPGNUD=3`MZdweP@gN&Zm3G;PpjfEDjN0S0HPbRDQH)*cp zWSHfyzQD2kr$+`=z75*(8BN0AO|XgRPB`Du{_=F@@(JRe?-^DKQtotd)oX90s)0uo z$&aJOmZW4adVN-qN4Q$t(>Ye}U zhqL!FN7Q6_j``D#4qOW-ivtflb{=2S*+mu_=ND93-)@~Iatl0)&(Fw`58$;|+mHVt zyeY^agN=AV!QT;ZV-$T+go9OW*)aZ7urjqfDCo8e55Io9!FEcx|*K)K+-{&L@C4@4tf zShlL@?=p}%q(IzN=;)h|g#Y+&WZ5y5cwaq_VRO{jz&gXrxqqL2&~7QY7PBd` z=o&%a`>({WOzKbF4GRtOv`y1ImcDMgtSx7HPx3dM@ZlF8?u&;1Wxr8(j}k?4ie&rLCI;=|wS z(F89p5dHB|t)0=)^ElY^@W{XFZ$yC-<(L`#^~`Pg{@bNv@|Ywq3F7W$kqn+VXFViu zmpvJtip=%h#6xFBn+qVSayqE8_y<1GAPhoeOB%gS)^W~2Km5@6?POM76IR0Zd%vDU zHNsV8{aq1$eif1u1(bBDhEd9@l!k2cD<7@zhr0icP~{HML=3&wy@18 z_qA(8Wm#Q6d(4CO1NqeL`7R1;v*=F75^{WJli-n$gC&C2_2L_)Z0^6OeOct6)BcCu zL93O=@#ZP-XAXT8f02#9N^rlsB!kA3e*Ui?X5YP6Dg>J*$yJ19O=X~-V4D>-?22IO zL|HG5tbb2P(pl{LzwkX;8i5&Ak4L0(E=Ys>7hR$&grBm_;PhG)qfPnmrRE`J-^8iS zHv-HdIe$2c&lv)m!6ro;niE%@Wh929N?!n_5C?R>%Z&{>TO0d+1*3N%)g; zxDU~>+)CjTcy0q%-dsnZ&6F2FzlaXJfj)dJH2u&CQ%j(Hh+eA;F16qK@TE81WhT`u zYp3BZ=wC|D(0Dlt!D{uidOr9+lA@W_K5|J*E2<69=b_bIkod{b&`3BxVd)uRvH`!= zqSO+p$A#w=@n|LhjB^odN=zlQ5Fz?~sriq-iJMZu7P(Lq&p>3?3Uc@dD#ERAiqfw{M%8@j$r|?2nz~G6_?{s256a}$8Is|v~ zTg|PF=7@i&9U{Nn+2|e(vegTy!)~}ZtFF{0Z$L1f09{*ku(Rc&yAqNzU%&>;>sd(& z-fMKw^y6uC*4i78xXxrlm_bovLD?kV-BQ{6iT_U$+Us?6klFt)2MwLWIoagX6iF|l z1(1>x$OvuLe84dG@*yv*)hDz(l3>0?r{L^-`~2)CKd6^MQ0SmXuN7cJlcE_VdeM>(kN^*FQ_P;Dd4i*(qPHdSkfJ7gzMpQ; z^__ymh%D^(ABa5bQ?*rfX5-=Xi&T!q^RP47C+{g zu&m5g@*-}ejGe(*3vf2B>OmXcTCS6*uJoto@ave=UVZra!VJBzb?kWFHaz?W?%<6` z5LqTscV*pYi|JC16qw)k`MEjys5Q$NnFQKcytlW?6U@HMfFyp80xyDP#5vxUM3zK0 z0)w?w=oh=Hk^5TS9xxmmKMT-C?WPk{yXVE1(0Q2^LE<-VqlnWbT&Q|G6i^K%H~J~j zXed@3#X^(j<=pTLYvCTBJQn3x7puMIy;arG0#y;sGqCrUPx+fM+-gt0Y-qXE9k;$e z_-`m$qovy#n=hV zqAk*FDw?Fk$AZU(pul?rAdYCpI988LuyUo9a;M5U1ENpJ4rKVZVnQeM7KXh zu7S~hh-x=m<)rzz)l3H(e>5H+&3ghdTDko>4&W&+n#4F~QrUc`{GtE?@aiZe*)-WF z9A!qY7n#bT2}VDLu;v>gX9}~I9)DrFzNTO78f<_0j(G-|+5(35gefV_AW4=SIu!ve zN)mT)`m{mPi67NN(c+iX=<;DejGVu{eV{iz<^6d_FGoKQGpm2ny(SW(Q7R(XUo509H=s3T6<8!+|nkImpt~!{GmT66dfr?91^q~C9E<}&VJnEaH z!$c$?>2Ueonr46e2EMS+f7c>Lit4Uj+PgQE9zSYSo-VHXu6!n?6HQ}7yOCMIN@QN| zl>c=p^Q` z)#C{M#)2w~KrDvtL0QpSP!5bCecg}2eI5(epjyKgcK#=6*BTTvQKG4wzq>Z)e?N?j zcJ0aeB+9oHrXCq7LPk|m!dQWecoNW#M^iBY{lXm0oI`mM(}%?VET3bnD+hUY>UM@f zJB+PjvMy_#%OG}6Cv1W0w(dXmui(`PHK=6zd$YYnl~!*J=e;GmG80rnE@V!9Et)MD z2%#?m4V$v2kGTxLXb)M~nH~y5wo9lyR`DWvGcx8#+|(?3hwMn;VQFTA+x~w2NB`B) zW$dl0_pfVU8Ga4FEO3`A=c1@)t3tsUDcw%G@6OnN-uxZ`D%$0tbt@@^==L7puG!s6 z>$o|J2n-Ctd14v?*`VpZD|MQT97C?Zf^d4#9cJvlh%2?K1Eh-efj==T=(q&DuYP7& zxZEa&5RdMDag5ehv0NkNzFVrza@J=S^2b#OuiA+LLzIBq(vc65c)&tqod zXwnu}yUC$rYwnkyrQ}hQn7sHmRILSzJRs8=cNQ=DHR_`JF`f9cf*;PNDLDN1oUIkt zC2kwOE*av#N9uEqQbkFlY1k;-=u}uV1LgAGI4x-~t-omY$OI#wA%saV&>!Q`L#s_g?!4_e5Yk7_dM< z%$h&F%{YGk!U2`~iW5gBx&f54(vf3l=AU1Ggw3!my7nq8Eh+OVbNg52f;|w8z4nBU zt_}J;9df^FsVrqS)ttn%K2e9S-gd&xoV2+;h`jjU?g6$Rkc#;Rfu*6g!#m%W#zYmd zs0=lfJqgkpMDCr${TTTeAxK(iRD3j8R1dP(+P!_;<=v`zYMO5ik>>O3Uw6A%d@Xbe zj<;3bxNLNw%PhYM(jD>J9*;@Igba^GT~oC>oT}2yqtVePKh}4mNk4U8`(lKq_9sa& z`>~GOC+vcs9PdP0DX@h6jbfwRzRX{y`p59Bw~-Ea+qC>dtQAws@inR<+=bbvK*;&> zFH+Nwt+ld>w-7OA_k{joSPV#IWo0F{*(fBWiMy!O7etAob-=e&=#L>Ie@-l@*ZE)( zeQ2X9zKq^}^;3{f`$xr={okw|0i+OwxZbOY7jv{+39-Hm66PQbL!L=mi#Op*?pHda z(4?0V#x3m6$7h77`nrkW)Jg}2VWKu^T67{nT2!~RT7Qgd74gGu9r`2mqRmLkGRSg? zF!WA@Pcl2mjm2#1co^6q+XoV6{%WKD%Eh3ufd@?2-S5XPeqy%J+c7{?Tr)>0sxVzK*h{3&r@Twz~Lt)B%)M3W#5G0mSNY zY)bMo8ly_Xh6xwhZ@2eA8^nfZyI9pRiTmu`^pYY5B@13LI?nTtR>EO9x*gL~>|phm zC?oMx=(#95TA-tl!`-?FIRR{tDMHw&t!Do`*9NxHmvKV3n=IEj#;W32N%#@qm>=PL z%K>>64?>6-g=-RLBgAH$-_^tU`rAHP6DBI&oLy28c zJ5U~KjaJU&%3l{k3i)HOcB&amfmHj(YvTs@=bvu*XF)WFTXi#ICLieeqKPeZ(3m(~k8%(11vYcqas=D9bItG2gqY3_+&_S;XbW6 zY;Ssa&GoAN9$*u7bjX~w{TIO?Xov2yfiFUCvf(5~cub&-k&rn>rT?J{G-su^>Ri;H7)~tO~O-&2f`dWs|zCnFfv2#e`zb?ub>g3`T(5ZjktlG z#h@CgTrqFF*>o&C$iIr>LUAhx%PNyJQ{$e~A#t1y&{dZ1+dJv%Dbdi}BpD=UHI#`M2Ot<(Zu=guW9O=KYteMM+AY++lJk5`N9f zOZrL7cHVO%=G>&*vL_A8_n$9Hh4nefo08It z({MU;aWMmNX7ta3h+cYYQcT+EE3He?(W`06`!ciCYzf}@e+9BTo|EKmIY-Q`u zahLwQu(kgWcFT^lG?&PC!~ej!`M855G!$dfzy|;`E0HQ+J8@#-9@Q)>Rb_D`EHMmt zNT}D}%Zk)!+KZBz8ZrlCRLQZWk=cn;u~pMv`u1-~3>A{7%R%6lQn@7YB6|lZ%dN-7 z@>tpwymvHDCyj40u>G}|afl&87DZ3f)1-+Y!C;7i!sG(LDr!yqrG|`DCm}K-s6~EM zFA}+dBi0HYo0gSsqlx$To8}&F#~gl4|MxbJjYKVhfX}pmWk`=~7@&|91gqGHvX7s`GdgV4*B=7z z{cd;`OTvnNG@jVmOjQ=XSpvyI`Srd>#M1IQ;7fVd+I7ZI$ndtPzg}wfqH}e% zPUm9wX}7a&p1GiNXPDb9p z8QkeR5OTx=p!9i%zl!{GE$Fag2SZ=?qU*GWzqX?2&0(H|Hx#wd*G1REE#~u`a>kW3 z=(P}>D5`!4>|D@by<|-6jk5>EWzkQ=qT81`f@({ogs#RLRDuC9T%Y?%K>ws#GQ02K z4vdrO{_w@*?WBH1J=WuY<`?nF`gX^u6*`1ozs^+$kNi!k&j8=D$k!n3x^fEK)j?w( z4ep1Q=%^RSW`Fj{?2)Wvc4P`=&@S1Bh z7iRwTsZPZ*SgGcuT;x}@xV8cCVe6;34x*(JC82@zxL)iSXxqsLP82?T!kBxUJuQGo zK4@G1`VH-Sy%9fqc@Go|2ntf*dJ_~C*iYc3RwQV$HvhoqcMA%JM6fZBzR!PqI;wO? z`{+}N9ezWI4FC`o7*=o9mn{GCCfDidul=_+W)RJ`F43%>q2~t6D)yG_rrFET#B8vnQ(n zDz~azKkP)_jZdE?O~y_{ULvGrAH`;4LHLkiPU$^a;?#A>dc6fYYLQxr&C-A&SJ?2r z(5v_p0o}DpGY)>$->vdG9e#%$c#kwWx!LReOaFi%q3$c&W<;Y!kM(eF7%!oj%(4dB z``~zlY&g3q{j2>QSG5wiM3iT_dzPqnv_K;>0cQAxL$yxqkE+1K5azL}Mo?XrWTwC^ z>B9Ht&|o*pKk_F9V{!J6=g2B2QMbhTU960d0 z%#vBk&E2k=lP1pBUpD7c#EjGJ>@Q_L`RUF=?%wd;LI}nHQ~5?{5pMxfAgU8`&zH;p z!knatkN=!?8!&%u{1CW89z({LvFm)0_od|NQ|`tq8&;63D;6V z824`G7FVa={_fY0HY1$@WP;99L82c)69qv#WK4)?1Xm-1)07vPK|8$Rmy(udC$&JX)tLuRys{LncX;zY ztaMed9(G+c>lvQdRNUew!A!&sMrhb@lPDYWA=tD7k*$fC9m9HZF-IU2Itt;5J3mMU zUzshedoGHE^w*zRna(x)l}Ki>2oNhkwJP<-tUZ*}E?t(^w5kynRlDHi%4(wfG7i}7 zge2rGX+*y$_wqNF2ClF8&~3T4#0yyuvOt>F#wo)$W=$drJ|efV0CCO)OC$SFa|pCX zILeknXo1Z>r#(M~FUr#GkeyTT6im+d{qGC#70!(z|fiNJG-wS<%J^9 z%jR`nW(N%6GvP`dIbJplG9SmCX_Xk2k2M)_cxm)hJE2Si1J}J-j93d10zLE^c+c5a zSWScqRSu@=tBdcJwe}?ymGlgUuHPv4pm<(o9E!;>2qFQ>DkZ?7OZ z-{^-iR~o`UE@;zG>d~=j8=K10`n49klJmVFkch}C#Df4z0;Zc5>i_UPu&e(LVrJB)M<<_MaX&CVDi6z50%A7 z=rZVH44aXXVE*$Hak)#IFT`SQFyjQ5LCAgo&smk9v@cqsyTF z`KTa1*%ZM|sK{|O1h?Y78vKG{RyCO^sVWz~X6CQ z8!B7KDZ3iHswYHHND-oYGWOQIBg&VLuw#o}#|_>go>cXq-h5b|uuD@}^F%2Ng%Yq5 zkEr}Q%J79J|4(z7hi0294?y7{5k_TIYGpoyJmM$ro?l8UKDQtZKiPedw1N^REt(#NsePKrr^|uN(M{ zst50`NcR47WUL&4WTzmoo`gX0cMv+=h@%0UqvzUl?-MA3b}7vb4wERa88l(fe{AV3 zE3SkQ4i@(xWw2;cX)TK1iSYsjuUA|JSOmU8e8}=@d8MRA-eayl`QyFBKvbe2qC?lg zRBa0ifUc>JPq**m64!tse-dVTJ%Kq?H;d;jF!`8X3KvM;6(cqq;-R}og>T-o#U$l^ zd^U&yu}oi?%V8JY57nW&>Rte$S0N4X2v)(aesX5g;Qs6RDSG7A7O;|s9R=SV!1U3Z zqh(;W7pp+DOL@Jmr>B=PdOxF>Z^)_jC(ISJ1`+TcrLckbNho{tj>40X2(G>7@cxb3 z`Uh7vLR2=M!@GS2tFi6-hM)LqCv4`s7v@_UA*Gwsc?OGE(iBa#Ho!t$vnL5|hO0#y zd@UUGJrlv(p_H0t14V`QD0PY)DoEv9>njRb{<2sx=R&rg$s-k=F!OQNd!w(WVvZ&v z8Fm*Q=}Gxt{f`Pqv9ayHRXbd_>ZOo?sL6NWV49hzxGmY#1%ax4S`u5r`Z(R9#Vfy4 znoc`re9pb@%l(Ae3HEQv)LX%%Xa{o!Gxx=T67^ygN!*4z=S$Q5#)o0>jIUjUB8GhP z7xh^}X|@WJ!;{bQDbr+35kD+B*UV`bW!t*2q`3)S55oOl1_2evSl2e|;G22bSi9d7 zd+X^QlYuL(VfFOG*qxcL+4H$&3MDXw23;rSH<-2{T>MHiXPBZ$r7EVKzkZ_s-o@;b zrf`LuF1IBh{lFmmWrk2gP2sJ4x^S)AWJ$d4X1(4V^*m3s6&v?gGq^dn z@gLR`2+$widWkMHq%kQ1q>jFg(#9kQd#!O38Y$rRI^GU3f9TMx9{WV>QGdmdX*qPJ zhQ*yXzh4Mt&B|AL|6=9{%E|4hM?iN>TSld(gZTn@rnqpQ%kz@YG zFgYDP%kZ0?eA}n;MWYa#qhNZwo!2~;nr)EP;^INu+N8J)JfyYgCcajAx#?CYzLY&( z2$~g1Vx>dSMZXb~(n+={F|MmJv;aZsVv3Rs`Aw2{`ktPc)VVy5EBT&ut)^0cIj*&r zip&|R6}E$tXhmDSiks@-r42*fv$X{~`r^ygoO5A_uA^~KtIKmX)%UwqYwYJp7zyQ@2^JafLGncJ(t4xuGRG zn6}cVyB)&l{K992d9hN5t5u3ZB8!le#7|dk?){&eJFCr`ldrz5NNiO`l<|{adPo_b zx!~(V#mN%4Q+k0|J9;D(yCpEPFnLBq+MMa2n$+6#d(^x{r@HS47LL^SHgE^xTon!!&KixH#V3@a zxN;J#BmAbdf-DA-K2rW~BGo6QqKeo!WbjqX z#c{t zb9Cp4nwH_k6MeO}JCHhP&N+#{lsV)SZ+nZYS}|x=zY4iQJx<8?pnW~7G+TR@zF(vD zIkVS6T;1*JhxUb15L^7pIJjdILVBGz36?4JiH}+Vm%Z9Jnb?%yg4%#v5BZ&inAWlO z%cUs;t^D{`iIL{j#n&5@{eSPYTrsJi*pPqv1PV163M%myBKvg|pDH>UxhOg4M(n>4fD=@XQ3jIRd2RTLHQ7>g z^}S>o91jCLwUe{CT%$)uI9%g~NUP0CT}h;FPG@I25ah-yS}oJD{XPftq5F*DrpmPu zYo?{t$j3$Xi!u3a*U#vr+Y+_|%1dt;*qrY3-W9OFVP^1qz~^icvw}M-=<-mrVxkU0 zAYkVtk3o2o*>gv3Vr!e6OEc8#L88w4v@8RPblZ>92;YZkh1Fo~~3*k73C|1uJ*803W4 z%<;+Ef=23Mx@{t1$<3cTH|~^JPX*~wt%o^Mu9KgOy?mGr`%Ru_{Q@QRaOglz>AL(> z;e?|WTT8mX#%lrhq0=9s7x7w`^pM7H9GL_67iV=C5(t+qDM*uXg0Zf?3^lzQOx^^I zex_5p%w*{ZG(6HF$x@(;;%&b-+u$^?K)py3>O4hx5_@1zPJkwQpnlb@IbxUeS^oBmV^ zjj6$Y#1>18kbJ*jb<0D$|1!~T(D*}AZXU#4GqkLlIk|_&0VwV(vAX_Bp*keh!DN?j z8QwnBmHzu5!!ZVU`Sz>-OwRKO)RU87dBP z^a+)qsz(?0mSm`+ca+>z*!Xj` z)Fq<4vsZVp|9cA()RTMevVufGzPf7vz!7wjQ))@sNGL;^mpIP@BZoQ>;VVx3pNqu$P(@MO8mK4a#8Cu5j1)~dr# zSvqN0R!8Rw7<8>6_V0l6J-GqX3wBU`9BFwJn%(*N8#sVr&e!|@o-JAAFiWgngaX~K zF0DzJSLr#{3^}QFdn6J&=kySFW;msel~U@t3)~)6W~6kADFW>7*OQ+op--W#1E(ym zD}-THf!~(j918zpkZ6npc_Dvk4-DIjYYa~Pg1@w4qL0aMO3r^__Bq|HvhfZc?FmX{ zk1%q0arDPXV(o{;<~uXyyD@KnR_YJrRQ$nvKq^ z%z+4B$V<*jU0P8GtQyPOer;zIX|tKWdL{`st-%;?r2Fwh2!KpC1e~@ne%YC?v3}TK8z*fhH z0PTQFp5Fmr#MLOlf$tyyqDwCXJIk!`(b_#G_@Ycc-pYM0fF!%5qiF#vF)*&(Ra2C^-7D4A zsU>yxv(2+^W7%WE_TubQW9M}$iR8?vEMW$(gP}`F!PdWFkhBXg)=5eA0=|O9QeP*& ztcA4WlB$T!{_G9y!Ze7_6 zyeRZ|oNrNC{BZQv7RW*lyzZj4QBS#P6Y;DNjojpkn2m{-60xtN#~g1#A$BHVAv5J5 z>fMZq>-q;qEH3@Z9%Aq5)$n@p9j5R(D7~?F^E-g`4%XWjkilIX4ef&6)eD%v=YQRN zh)=Ct8?L|;#8RZ#F7(;d0&wQQ+~&9ZH=E98Kl49|z*K>6`YWF1KH_$mSH68{vHSVc zDq(XyF6HZZ(%a+M=xZC}6O=wRomC}?A8n+i7mquIii8KXt=K$M4`$R7Iun)g^ZYua z_@iwTb07m%P;~9}@rk{3IM#E(XHH&7f?ruA!*gxKoMAXF5NyH3>`%J^ zC20i+V@CRj*^zztYwdl|VxInu9=IVBC=}o*s}R*gBtmh;e~t@f#p?SU;>IhXE8*%r zRG!sLGU6K}koX>Y;o}gV$ImOm(@SdwlUX7i*yE(@$&*rqOl`8Fm8=TGR&xhnYsgE$ zV0_8KjjD{|CX{>_pg4ecBIGp-RL|80%ZmLnSBpvI?X?41S!i2Xkxv5A9L!A-Y2L14roXB`?QLL=>2&3g?NzV|KU zUsOokm$?ayZZ4jIU#+$SL&=xAS2dfNRzwjNbql%cFPg5@Z3^9lAzTcOp}@%Ut^E8A zl7w%q{@G3-emaQ|pXb5%$M&PaYkZ~XpRHto4mi@kBJX)2{C4)9IcJL%rV0=kz9`EC zS57td68L zM|3IKL+iaxNbV+;lhiKRL1UPoti7Bio2H%8VwWT(H|y~qojdV(gkn;Miqfup7$`xi zYD&~XdxKvdlGESq3W@tTO3F#|Tuxq_u(6D#njjRxB|wfh#TO;&zs$)fCl^kHymB2j+7TaoMRBkgh?$$tq_K;tUWN-ZqxsnD6WWOf=-KNdR znuU5RL{;rQLI&!cw7fYa>-L>&vmbvy-Q`mzdyXVGZfi8P)mke;;bn36_pN;zwjPq* z5hJhJjD5R#;-x=T_VKXd;rUrny8GRD#~Zb0GJEq=ETtMMaX~63>3+iWjXKtj2M% zF8xkM-YvGKj*5=#6jGG%$_+>o(c@DrKlNWu8VX#)rwXa`XIJpQ{gR30%xvaYTG;4B zQShhxVqexr3m#wlSYj*lC++H6%Y=p}Z^K{FRcH<`7$xEwaVi#5DGrkCwv7;Y!C1w5 zlY?5pp<61$`DPvcnPK*qr8yR4ttEA;RRfe6oWuxe{5t|OL+Ca0?p+ICh=s-}f4$WIi?@dR+{gWYhVM*bzWek>Hm&*cYW^jt7u; z_Hr%~D_04~-HlaTRuXH;P6&&*oSW~0-fp`iFw!%w7}vUnkS=5LI+09aWi+KeizK^* z)Zt{yP0w`62)H{!Iz=>9{VdQumvNDVEO%J4;A){~b+U_)Wc{LG)r09rCmQ)V`|p77 zASe=!CGz7%ca4c#+W7_wf`K&iFhWF~#j+|$zVQ#fLT-lO@jOAU3-TpQU{D032d1YC z2Z=?vB7)7x{Q3&85yFc>!IYE0Ogb^L&9DlmadWm)&aKF@vb^!fkwSf?HgSDjC-Q>8 z&(lF?$Kobuq^oR=|IKVs;+4|oEz)n;uv}NHDoxVRfyi9HmJ7B8v6eEiWL$HSQqQal zuTeTa-;`TE#TQ93te!MM-=?1FuCV`Fltoyt#-w(xJjTV2IXwDEY>Q7JI4`2Hk4XWNV_*DEfJNu5=H0d;U1MewFka zvDv5X@6TW4*$mpmj*w>WtmTc|rB<}&z%8ko_6draLaNIbQr=o_O&A7r~D(YpK0kROT$&WZM15 z(_|PgLv20XQY#}&b^RlUKRiFI5KK0LD)rb*7PUWeGIFVyx zJ2Dbth0CJSG?MO^T^kLWCx+38x??Ge?N+8`%|gv2x~{|4SLv;83y4@flACug(a06H zzc8cuL}`#~7w34!pX1SJTiIzcr%}wnWrN>Cd%9f364qzBbO@=2zu@;bvemx7_#T4* z!G5!raHW8bjt-r*{R=F)co{^y7D;00hY&Fmnn(9uaFv`U71_UUeW?g@giHlh0u}$_ zDV7uDchMhXTo0wwo;(Ob#LmuNoaEED(R#(7Jj9x3V6Pvf_FmmdN>AkP7u7=ApR=VB zMsaIh(>C}Y%n6T_MRA52Fpw$rQXAML#{I7aRmLSdAy)$cY$3L1Q>UZ$0Z-ch{_Qn6 z%H7|HGAQ~E-hb$Dja@cU;d6OxxC6J>{dbQz{)KTtH+_G(-d$0r3Tb`#mf}CmIZBGU zs?urBpl(d4$4bim0D0Fl((QU{>>Kktk}#wW4lYghg(N>;EKNuJ&+@6Eh)QVk#)@G+ zIJ9KD!d2XB`EtA8X_yj?6tx8qeCplmnZ-*+g}Qln&ABPU9YW^^gHe_#vf2!}!j>r& zx@c-h6f@)2vuCb$N>(G~Q!g=idKJI2#p)q+K1sc$z2xxs1hM!j4!kY(4D@FwKMo>@ zLC34OP!R_>khPg78#=k#C0~F}ZE$^oYaoQqEpzT>4Vi$($&0rW*isq1f?jCI>?e#D z*!-h7s^}P$H&Hgl=q8zw(mfaz%S!>Z^}vTF7G{WPQzsU$`<@-K32llhfM?w~U% zI0-hD$E>Y2SzQ*ij}*}7U5KUhQYWj>@-v(-Z@ku_pnd|`_Yg(CF>wtNjElw~Jn?5L zAs+$|80e>yYY?*h5#K)nQkwu<$tJAL`}JI~>{P2?ff0WB(CEj6VUP|2TBOHom;iREdrshx*e3P;)t`eB2LCz_9QH z%p*Iehc}V<5zSza--OQE2LL=P>hZJRp;UQ_4$KFfI^+D+R^$Y#qRluJCP9itf2M(? zRO!E$5eMnt!TPuazqcF;gGm_cV$qRpmLck3euWo#@>%-iN!v7fpyVqM$4vX2|M9*7 zfW#KEoa~CGs~vLCy~J1778HHP-`}4_$KC16R@(z)tdREl?MesLSS7eQe(lXDhOwbn zbzT-20w==9@*;?og0WZQ-T_X{4>0Rg0=7MN_(Sg3%2sOGL-q=ql#BrVf0$T&C-)aJ ztaxxkpHAKcL`Ib`q=>qAH}=h68iPd!>3G>ODHEw{TmcS?uVSld79fZ#2Y-$0woi(& zGkRdJ;m+#t&MZu1nwdyoiaG>Px20zBoW*V)gaZ{oU3e^_Jt?2cl}KGdXJre@NN*x4 zEIw?bSx6!pcn@;FA0^E}tqSy41*q^(yU_+DnHOc!bL%u9(0*;tl1RSM1|lDkK79#A zmL?>~vP{R`>Wd^I%>C?h=A-A1sEnvsZTX%7ib(h2`~JUUzlC^J{G0y#qxge2c)3o} zyf)Cj|7=e%XNR8f`J~ww^GAHBNeqF%iF8RgT$#)6-7tg5hMOLlAO&}FO>_{kq_x9M zf!pAJl6G)ut9RS~7Ek&aZ~I^qcKBtKJRc6RB}K@)k2B(LgzeK#9a2;kvjp(I0kK`S zgo;nQ_Ba`hTY}}woVQ+12wt~eer?IZJ%=s!BkX%7`T<``XzongD5qke?R^Fm!4y13 z55a?wfATPZjWpT^Q2I}}pK7hlp)33WO*CC5nHMdIgrpXCqjrdQT>24XAL+tWbR1zb z_i@6U;D}ZPSbyUSL;J1Qdo_Wv+Jx~#cy*`ge>8D5+r{xwHmr9wT6MD<70wm2e38dq zq$eW~*LPRFedrY2E7EJ|fIFwefDd;dPW1njb=~n)|KFeM%C$FTZ#QIQXLMyG6v~Wb zhKvwG*=4VE$%>0eMplWG$jpw6;**s~lAWx6=j~IU?;pSZ?BVYB`*qHFu5+?iCG33c zq`PQ`r@qE-_MCxf}4NnWY?!zhYmfjm+o#u~R zdD|>2(jq9iz6MJdBg0UcKyEnQ~$hvDC!mODC^Yo7(tXSXIN%APrC~J z9wTyk-J-*g&{es65?lj!Ha9}r=_(E%laQjk*6^!P$~!r#2S()<8oP!-z*gd?@J-x$ zyGUlb?HnCO5ws!CuM+&|@*A5|2yHx{^*LG*5*8LGETQxC4aalyp}yE=j~E- z7+2z#f`jfNN;pJN=HsD(&>zE(9XIVbrYVybrB(LBFhWeFv~O?Eyo;26)gYOBF3%;e zf`M~HmA(T{6yxH(f1~i41J?8SW6*b1sW-ePw6Ez|l0o(Clp)SGeJk)yistQc@r0eP zA2@y98@5(DiCiET4tawUQ@4`0BQMX3k$}RegIUg+_GkV+Zy+o7Q}K6fVr#C#avPuL zs7)kuWL^q#Yfl1?JPG#&uGSVpv*jwhCJqFUtWpQ=wP&`3bJ(o`uh(LEGLW)0*5 zslpMoP1?mMd5lm0eWWP5(}L8TqqFmVS-{^$LJi|ekuc8Fn#)7yQ}(Chz%!$uuMnMh zy72cE?%)u}n#k}!$l9E@qLn4HFleACrfN05B>P_!na_X-CDv^!K3CrFh=@YGh+_(L z|AU5OC_`yDPqf6GiX-8RRi}!U`>!iXrsrw13Ewl^Vu>vBO%}WU{m+MhM#3D81U}Cc z^()f|ezUmX{O8#w_4#mdBwbg#j8}vMi91dH0Ny%m#62A50}WCsrhootUjT(Z%`n3g z+nlpbinaLrRh&UX;`Ur?%CtdpgKUcN|Ni*(@BR8YvDPX5Z4~~$??V6;SB!h8fPZ-H ztON6(@AqVM$Z+x2y++aho--O9Fk;ZsxYiRCh@Wi6iG)NhFDBG+V z;#doPS_NubSzf5je3-9>?re%-3q$~Jkm9)s1GN7es7cQOTz+?T`8Yh?S{D6Dl*fm@CiRS-b+ECyPui(0B=15R<@O!^B|ge zHZS+cHU`P%+-p+HcsK$bi&tIs)5sa3_#!A2B8#C6MS6%Iv6ik4`eJkV{gXvGT@kXZ388y60}uYP&M{) zhjP^;ZsRqv|C=7l+#fJiU-N+{6G)b3S@Mw>3*^#laGbAiiFItEt%ntTL{Je21 zz60J_DPY^~@whY43*}8%fSiBor1D?D^N+P4$)p-ibbh+*Cff5*PzT3wAO`VR*8c9#xgD=q} zUyJ(3>?BE{JVG**fX@*Su`WMJ56%?wcFFu@qWnmWA5mnSg)F%Kd_KA|z!G^yU&Z1o zg_hT@eRF-ke!t9g?Umc`>b26fgiDtc3GzL!m{JhlR8o)+%d@^3K8?GUlZ`St(K`l4 z#Pb*V=M7~*2gn|vch5}C;CR?HPL)@G=}d1RtPfs?UM6C`(E!_*zQs3pRyzU9}w0N9K_E1L@p43_2@TD65GGbI1LD! zu&6pI1!?jI1R7Oh3wS?DO`Tr~-1Kw>>31gZY1dOyCbgM}@S%_$*1_&Y(HtHYiAw_4 z(JzviNHW?N@}K;gX;*lDEmrsKwyMWxvr-&lNC4^4+p}0Fb?DKL>(ygv)6V-B39a_{ zp`d8B#x-`zin^_i(=Ohh2&&X)y>+!K=;Bq;;ZkjLoh8yG$|dgG5u4G%*~>d_pp>lw zqHPpNvirw5Eg73mECasw5vHF^U1x1P5w!yCgQEVl{Fdjg9|o@UW*t$nhZXEkk+EQ) z(Fa)S<%pbnwZUIE2o=_Rnn%^<1qfNoS0NWw>PeP&5?rYM>0iln-y<~Wap4fsO2FxZ zcvCe`aK0SXQec_px^=qu6N?vDhQb6kj4`ppYNXD zcI0AH_VY92G2=(pR9DiQ(dE5ST(3A)?gBd1eL`NOI_^kHfujI@XflqVGb>_J=0!bL z_;EWc(<@x|$v^Ll-OP_Uzsm{Qc7E+R?>rc?34B9e>xs}Eo^e*}yR@~z6qu5fhHuh# zYFD|uC2>%}s01_7jAP9x71~sKUqx+%B{bPYa(?+K{U8!l*WD6eT6}E5dx+}rWGa0^ z?(XvBsb6zebBeehWTN~IpZ#%>VF?{DsLTb88qgp>fc<@GEQl2lqlt@OauFdqD- zndCaZN(MADJI!H{6n8ME^htAtTh%2$^vfpPiR7PKTTVL}teVL{kM)G}d&YDuDPQva zlk;OiW;@}=Wk#(vpb#A!kD`>zSRfK4c;xPRQUbF*-Q7?p=<=XSyjzteXK3!$&NswpSEk%dQx{mgj z(WD6)ayaeI93!{NklBAnnY;Wjc5*~eB^ra*Kv9~ASNMy5F^dX^(VKd=6Z*7#py(@2a@p>jI-15GhLs}3^bF3 z5O0aZV;#Dl$Ruqi&hVsJ*6_LerRMF(ig@-r2_*5#KOvRHamk`r1(J*NBlm*79N`1v zx`T_j)UQ@3>g=hbv=k(YUcWtaVMlo;HQ@G+Z&^i>MUp6u<_A;nl6C>O#&7CHm+MO@p zZ8!&Xuulo+u*wqet^ILInGRlY?0&{p9~js3CNdXK^9u7Qn#a+Z&`hV+&V|`F1$I-8 z2(TQoLde6DQBjw8REc%@O`?}2Dbxly)IDJ!VP5(!*~#CKwUSde5`7ct=sqVB{Z#Cu z)Y6}1CP??ix@~&K^=cT+S2!MP3+=cX;U_6@FY+)g)#gU$k&NhyL^3KBfdB#JBt&3G zeXKoO_#gAQVrSP}t z;|kvOEN6%pBkn+?>l5La%o{HTa>xJZ*O!+*ab~TC%8i zH3PiVuXnYlXKv*=%zon0PP(y_=zZtV{hLSM1>QAf!_HXV`|9Iga3o=6Id_Kcb}bcC zEZxZMg7E7_!VhBf(BG~Uj@TzR^fH5(gXe*qT*H&``^w>A9*IlOZniJBDBRWq+Ye55KF z?p1EKLRGw2QtXbINR-bT+0fYISLrn#Q@D5Qb@3GNUa&p!x|Ctk{9b8(auoU8%(19X zcHW)xA0rk;Xrd`DBn2va9JwJ5C!$tJro{ToU!0C5x#I4Y1{5A{lUhwTVs73s#Pbr{M0ZM6Ei?^be=sVb877HUv=dCQo zQX8i6hNK79N-(!@6y&?nW3A8?Nvqv47um5k9yhRb1i!$`B4>{Z(Yb3p^*x}Zm`qXK zUCG*~4&zKB?1VZEcK&O`b;OCajPYH+?oAtby;e`L{L>c$Y8mC9G;Lttk@6R@#9N|j z&Hw-35jEYN-7D{?GSd3f0P~;(Q;4K!%q>dt9-cC+`)?RXX0$eUs}o6Hdq@ALC#MVj zEey)ILp$m*;X(BOJ^V5GZF6A%&Au4jf+d6!d&m7PE zUMQ{&?UuY!>PP~>SEB&u9abs;r&difjnXItz&}WCh9I0p5ZPBjT))NlkI(GJpxKea z=Rh{m8`a;t*Gmj?U-~S(|B#^g!a+99Tt)hpmLv+M5G3do4~uZ4J8cyOFa zJy%Mn$PRLoP45pkpe~L+ew2L`0$>n`v!_emv6n-FL}eSmTP=xrw+r&6R2|gvj>h|FUMbEf4u@YQHvjK>(4pwsTGga0*=61A@5BJNANYp%Glw%G8_S&xmiM5wzJ_o^mH9RB9Y;WI#Oh zqo78xL7-6x^R}?UvJF8n`v-Vd7c3o;fLnjqKtaJ5+Uo$?o6xldEka|w@yT18Rr+Lm z)HR$U0WLm)0OfM;mvp~>OFUG+_^{b0_tyHl;q8jj-Mt^d&jH&+KIC=Pk$j$9%y&SW zpU6y}%3UQmR|j|+5wqa+^z8boqZVF;v+Tz99;Rw zOf5_=Tls?>nxTO4*)0z6W3<(2YtMmF##{akP=e#9Lf&}58)ScSx4QR)s6G8nVXjct zFo(}i2hYWF=6{0gW^J#QU7MftK8j-K^6za^{m8=+6`Z;X?sjk)U2i4Uq_`jbFJ}Y; zraF(yF-*Kf`G;!9o6WIzrot%2J+231oj?^8SPl4K4gAuzbq_e#JLysE;m+vUu!x*P zjLtB+yq0$!Xn};KsWG%|p=j;lB#o~Rb@=fTmKNn!D%Z9uy#Dp2J+W2!aeHzXYcX=(lw5e~mWv>b_baM78eNIvzr;rZZ_BIW3 zT8}0<0w^~KnNEY+BT-O?uO?A}Mk{tQzzIYhuOV3B3Tz(@IIl}OD{vAoeNeKH4l6RG ze&KqA-4*2GPbvDH>Hgv^dmUmHVV7e4W`a^Z|s6;}j)|+RL@l`8&>g9~pJTOPM?0On zRs7w1(8lNxKQ?azGjiTnjQ{=G@6&Vep0($ajA+QYb=l-SG42)ti@sRtIGNZ4?|biN5n( zIQP4+=F)9t$qnh0<7)Q~lzRtQ=(N7|;6j?YWeJ}Idh2U*T(bXzM|S^p7_E`GVE|zD znT??nRYjX+#rbPT#os+>H9Q4F*t2WjNV=(Eh5Mew*YNI0(eSDR+F>pfG7wS_|4u#q zP*bXddvQJd&{K3gX$^!om^UYnwdE4-jBM~33}CFClLib10AvJ2l1In-LY#UkOI^&B zhX`PO{pW2l>TN-ICA>WitZIZ zNG6mpulFs}e{`#r+0@Kisocd|53R!&%573sxxzU2ab0ojLs+@|#qZm%&Axr9F1-=y zR%#gjG1E9YR2ydNpkkNN~&AlO7H4E3IqD>i>YX%cj1;& zS_On^hWR&4f`P-36|OV}heQr#TcP7wmSZ&by)qqo=WnAL*{GG*)3y~PY?$?)Xy%Ri zU&^=RGf2kl?vHVAZj*_+uCt#U%a}F(p19%S74y1@CGcm-v2=4ON48bjn)Ynb@1L95 z&4dk7Y>I$=Etj6=Tm)Z!5uOkcAODwgRKiz|I z4=DUqU^d=f6BIjt1ZU0rk9Dwp-!x9kp3NjqplBL2Dzv|L!mmLQgH(^>mvuvp`7dl_ z*|NH*qje;SndZGLOsc=Lr9R&%8U7G)dA}n%bkrz!HKo%$-c96Vg3@s1o!H=t)SiX8 z;-e*bwmKS;>|0a1uuUPkaTIiO&2mQ+kp+Xr2zBHBaW+8LumQX?L&NQmIpMBZcuI=2}i`MuEpnJDZ zd1l={jx;d8M9L5%sddl{f0hs76{0b8gLEva8qSTPjiOjO4oXkjseq4OaQ?zX8z&iR zt;sV?ymr;>=skyGB)l+9Htdcxq&qAb$ieOLSa^9XW8!=Dji3shq30EaWi>U@`sUYu z1RC}Ty{_&YF56XUM^!et-phUv+X_nCc@y+_uo)Mke>~Z{P>f){vhL%fCr3)nrLRPH zq=fuSE31-E7I$oS-vzG9;|_5bki0Lg!Ch0Z<8*WlS+me}ex{fwRgDkjaJRAi%t z`k14Qnl9z`2e5`#og;GL%*QW`l>dm?0v1*IoD&Gt<-k(SuHZq))gA}Z+krD>0hpsC zGU5o!1aJnDNqDePs2i|VIC;fg&tuTiyfS1=-;+ef{*gl0Nk3Dr=NLV>(Kaps*2}J9 zJjQ2GOpcc@{nY7Z*ekc6eXA$NOL5MqJ36#O7g=NQ`jLk&0Y9iT4@m--SE0e1^?95C z9%#HL3a#S12(yGf9p=b1XN3$wMv_CC?J&8xZAnt82n|)chI*4kRgc`*D8$RTbSn=P z=(65~O$2VslTB?-VCBE)X9JSkPE;tTuakY`=4E%giT$BranWn+UJ;Imj+*AaG=5^< zwisA)L4{oyH51_l(nAQ@OqHB6#1iJr?)oe4PA+M z`Jg|%askwhW1I1IJ^4%3 zZ8g4M(?#9tUmUys;NDSov6}}~CQ2ChlJ^2HlSXZJT~0L{hj|ie;UYcuEV|bD$Qh=S zVNp|^W9}E)Q|?5Jjfg@$$nbkC4zGSkH-vYrR#rX4PU!K>zP-1-S%y-H6iqsl-dqgM zS;zo-FK)vY4JT4r*3>KKiZd}$jXC;6vthk7ePu<ahOu0 zIQ6gFR>qmOI@Z9KsLFb$`5!ZuO2@_vewA%MBXa(PIGp$I(GL}vWIbn1g~%lrll>=P z$a`S2m)`LeysSFkEFT;Qk~GiYp2XxmEPL50M*QWPwy84_1EXSV-!FqJQ;(ic_~h&MlQj#POQzf8%Ip2= zzV8iBr{g0BeqSny;4@$#r{ggHB(ht_h(j25IoJo+oKruSee)v3sb&erOYVRP-3`K$ z5R&efn2BLO%;+mAvS7#3b%AM|+m<&MuzoY}Bb+Z}Tuqa9OprKz3uHjww|K*r?d7Lfbh#8WvMkscL@be&(`IJoV&1kG-@g0Mlo`e~)iZzL1h>eRg9t+CZ=* z)xbDn0Yqpi}Oblb-=eWKy^AQZ-X6+vFpSrk0p&tLb#?*z<3ByNJ{bV z;)g-l@jk7x0fJPsiH1P8rSZB3iV@gl&1t7g@(NOfdYB?3sM!M^Za>Nix(Xl0XTk$! zh20k~2b#uD#kB}wGOL=5=_N{!g)k&(M~#5l8%0I6uVrxm7?;ZHvPeH7XB5Uq1q;Qh8K~ zNQvc%%Kb*v)?XxX{b-Sn<39O`8G}!htY)4Q)^&u^qt%?F6LoL77_)DXUK)KCfnD`T^TS-QBeL>@1JEWeg^&K89tnc{jeySwT53)h%THf8M zO`b%1AF7f0SBnfdbL*&b_=R@Z4SL`gqQ^8#RkO_C^mmX16*7LIK)rn`heZgqtA5iH za$M455@*VVrCXr@RN-gZAvVX-Lo)BNWV!O(v~07sa}RE8`k}?B)Xo=#KbtJfboaVJ z(%EzLQC6~t5$SA!D_YT{$e<}`Z^JY(nh<2j`^Bc8pr-0f>rQ9ORS_gmY`1>G?fxYc zuTbG41H2vRF&T)*ci7#+Q@V~2SKJMgKE=!arbQclYM{cT*vPqB%%t4vJGC@*pwWCC zV^);D0$T;*{QQpGV-OZS-eU-h)s*k4`xChEWTbp|2_Z>WUMAlwO^6=oZx}ce(r7Vl zT}wM~S8~={EokTK=ltu)5zcc7)v~uSKcKXeFH*rK5UWq9Fr#81&p7*@IcYx2dvi90 zrae#Uuzx}12G0<_X{A$NTf&^P+4I%?AQYCURE2)r?(x+)KG6bRW6)K6?E_}~@O{cbSP0Tq8K9`Bus?zYpQ1$m2c*6O@ds~7*HzX_#`t^x!K;RJ z*c|D?Q&KdVY8U;HQ4E=+$^Qk2FU6>n|K>8j^#?t|GN2k*jC!-QKmbGT?r?DYL3I(# jY=msM?CRo8t(}vVlQK7b9d3jW!M`(_=QN(2vJU${mUsi% literal 43926 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

d(jHm@JJaI&!Ud-}LYQO9*6vFe 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%0Aa z4`G_sXWjI)zlh!htT6 zVCPFK=A3^BwVO>Zl!gagEh^VyNxJm-`l>2&u+4Qm6A2k!r2Fc1=1HuQGYB2f@aNGQ$58N_7Y>B!`JrvGF-*_u zIkc^XpErmL-}KZEsazYlp?_5TOntts2$%oNakBVw9)CAc(ew!KktC z{#whxi()|g%@5bWx5xV_D&-k9oc6j%77M+YvjW22j^ZT3T6BfE*iS7PK2F`%$-ia@ zQZ3m!>DWU^`{ce;=X%;xb=UCDA}I>ImvW*l#hOR8qLSTZ*uu_Re;RlywQ0;jquJt? z_4GKI8>YlfyT3pTa6n8>JuU9x;AWU`U7O@^mXQZ*EFl68<7)ePV0 z1@|1`NE(B=S9Di+u|J~U1#1-OPf=bRPHyr9s+Wf4@m Date: Thu, 18 Nov 2021 07:32:32 -0800 Subject: [PATCH 15/15] Update docs --- .../Tests/Editor/Inference/ModelRunnerTest.cs | 4 ++-- docs/Getting-Started.md | 5 +++-- docs/Learning-Environment-Design-Agents.md | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs b/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs index 701c89db1a..da802a38d5 100644 --- a/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs +++ b/com.unity.ml-agents/Tests/Editor/Inference/ModelRunnerTest.cs @@ -196,11 +196,11 @@ public void TestRunModel_stochastic() info1.episodeId = 1; modelRunner.PutObservations(info1, obs); modelRunner.DecideBatch(); - var stochAction1 = (float[]) modelRunner.GetAction(1).ContinuousActions.Array.Clone(); + var stochAction1 = (float[])modelRunner.GetAction(1).ContinuousActions.Array.Clone(); modelRunner.PutObservations(info1, obs); modelRunner.DecideBatch(); - var stochAction2 = (float[]) modelRunner.GetAction(1).ContinuousActions.Array.Clone(); + var stochAction2 = (float[])modelRunner.GetAction(1).ContinuousActions.Array.Clone(); // Stochastic action selection should output randomly different action values with same obs Assert.IsFalse(Enumerable.SequenceEqual(stochAction1, stochAction2, new FloatThresholdComparer(0.001f))); modelRunner.Dispose(); diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index bd6e62d269..7baf461ebd 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -119,8 +119,9 @@ example. **Note** : You can modify multiple game objects in a scene by selecting them all at once using the search bar in the Scene Hierarchy. 1. Set the **Inference Device** to use for this model as `CPU`. -1. If the model is trained with Release 19 or later, you can select - `Deterministic Inference` to choose actions deterministically from the model. +1. If the model is trained with Release 19 or later, you can select + `Deterministic Inference` to choose actions deterministically from the model. + Works only for inference within unity with no python process involved. 1. Click the **Play** button in the Unity Editor and you will see the platforms balance the balls using the pre-trained model. diff --git a/docs/Learning-Environment-Design-Agents.md b/docs/Learning-Environment-Design-Agents.md index 216c8d56d1..dc7022189f 100644 --- a/docs/Learning-Environment-Design-Agents.md +++ b/docs/Learning-Environment-Design-Agents.md @@ -987,9 +987,9 @@ be called independently of the `Max Step` property. training) - `Inference Device` - Whether to use CPU or GPU to run the model during inference - - `Deterministic Inference` - Weather to set action selection to deterministic, - Only applies to inference from within unity (no python process involved) and - Release 19 or later. + - `Deterministic Inference` - Weather to set action selection to deterministic, + Only applies to inference from within unity (with no python process involved) and + Release 19 or later. - `Behavior Type` - Determines whether the Agent will do training, inference, or use its Heuristic() method: - `Default` - the Agent will train if they connect to a python trainer,