From 4ccca4e520e51e5f12b14cbc287d4156e512c90e Mon Sep 17 00:00:00 2001 From: Chris Mackey Date: Fri, 13 Oct 2023 16:39:23 -0700 Subject: [PATCH] fix(results): Ensure coincident peak respects input HOYs More info is here: https://discourse.ladybug.tools/t/error-in-reporting-of-hoys-when-using-hb-annual-peak-values/19698 --- .../icon/HB Annual Peak Values.png | Bin 1096 -> 1096 bytes .../json/HB_Annual_Peak_Values.json | 4 ++-- .../src/HB Annual Peak Values.py | 7 ++++--- .../user_objects/HB Annual Peak Values.ghuser | Bin 6622 -> 6638 bytes 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/honeybee_grasshopper_radiance/icon/HB Annual Peak Values.png b/honeybee_grasshopper_radiance/icon/HB Annual Peak Values.png index c91187b07ec12c5f291ef5360c25d3bbb6c53353..5b8f89851bc7432bef69c98f012a5276d6c8f2bf 100644 GIT binary patch delta 670 zcmV;P0%85g2*?PKNDB_b000id0mpBsWRX)*0jiOurhk9F7xW?bnx{aY0kH?ahnc&& zBbO*ng)U7VQI5E;a3R`G(madQmdlvDo({|sjtrH7KH>sRlib+f1$Z~Th!D{;Dtu|I ztYBfvtBouJC*sv^IbDm{sW{Z0E64O?3X)sKKu3RBEY!+v_aKrJvfoqTOOu)EtB9&O zwLong%zsI~8p}t=XbCdfMu3}y;&3y|Hivx!Pa%A<5aIE)xN=kLyEScZJ_1?~CVnt7 zsbP4+kC6!j_?1w0@(}1SaB0Ge>a$x>dZH1SBXnLULC5eqgu9bwZv8~8R0Qnb8CP-O zSOIE>wxYKGSyY_af!OA2PJ!yP+i>|>7S3M{qJL|29f<4D+utEI))!Xc5fPv#_6*vm z#L?M4HH|k|mU*n{>2_qo458>)yK`g{cXr|4C>+J&WYjHIE@(o z&P>JQ!ng)%2jcgggAvFZRAG_BH&hFFJj3a`J_Yr15x!W|`ZE_vpFoGYKJrQa8ISQ}$bi@fw?=7iv9sKmCG*txTTwQ!Y4{CLo9HSN0jT%Semh3YNB5a9fHB&N-3po_oXnu;2qXG4u$l3 z0|C?g#4wB>f1mf41PuA8FvK@%cpKh9l3$2Zld%JX6r_)-@+l`OdH?_b07*qoM6N<$ Ef)E`@vH$=8 delta 670 zcmV;P0%85g2*?PKNDC5l000tn0p4aGc#%_40j!awrhlK>3;K|I%~PPyfY^iI!^~aX zkxLY(LYF3wC`a5^xDah8X`V%D%VkVnPX}fRM~2EkA8~=ENp9@#0=%1EM2P4a6}~i9 zRkF&!hzQUVdj{=O z;^=Ijn#LO}%RJWfbUQL(hEQ~@-8r&}JG*ZLT*<{MHZr7;ue){}2K8`q=frSpoJI_P zXQtwDVO#^X1M&OL!3g9Hs<6o68>$67p5gRepMrY12wyB}{h5m_U`t;;)_)yFOv81) z1An>mIYcEYYBQsheGvDf=zMc#Tcbi)x-bAa(JQWm^4~&o`h<4pNhO9*<|v5@I8_ zc4-)`%4HXR+|tcD;S;5nAr`pEBg%DWEX`OiHPNqz4#8k>rIb>)`%;)+@DA%8heCS2 zfq-d#Vi?Aczt8(i0)~847~&f>ybbRl$uGp`ld%JX6sm-&^A9VTR{#J207*qoM6N<$ Ef)24nDF6Tf diff --git a/honeybee_grasshopper_radiance/json/HB_Annual_Peak_Values.json b/honeybee_grasshopper_radiance/json/HB_Annual_Peak_Values.json index 528ac89..f0f2467 100644 --- a/honeybee_grasshopper_radiance/json/HB_Annual_Peak_Values.json +++ b/honeybee_grasshopper_radiance/json/HB_Annual_Peak_Values.json @@ -1,5 +1,5 @@ { - "version": "1.6.1", + "version": "1.6.2", "nickname": "PeakValues", "outputs": [ [ @@ -50,7 +50,7 @@ } ], "subcategory": "4 :: Results", - "code": "\nimport os\nimport subprocess\n\ntry:\n from ladybug.futil import write_to_file\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug:\\n\\t{}'.format(e))\n\ntry:\n from honeybee.config import folders\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee:\\n\\t{}'.format(e))\n\ntry:\n from honeybee_radiance.postprocess.annualdaylight import _process_input_folder\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee_radiance:\\n\\t{}'.format(e))\n\ntry:\n from pollination_handlers.outputs.helper import read_sensor_grid_result\nexcept ImportError as e:\n raise ImportError('\\nFailed to import pollination_handlers:\\n\\t{}'.format(e))\n\ntry:\n from ladybug_{{cad}}.{{plugin}} import all_required_inputs, list_to_data_tree\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_{{cad}}:\\n\\t{}'.format(e))\n\n\ndef parse_sun_up_hours(sun_up_hours, hoys, timestep):\n \"\"\"Parse the sun-up hours from the result file .txt file.\n\n Args:\n sun_up_hours: A list of integers for the sun-up hours.\n hoys: A list of 8760 * timestep values for the hoys to select. If an empty\n list is passed, None will be returned.\n timestep: Integer for the timestep of the analysis.\n \"\"\"\n if len(hoys) != 0:\n schedule = [False] * (8760 * timestep)\n for hr in hoys:\n schedule[int(hr * timestep)] = True\n su_pattern = [schedule[int(h * timestep)] for h in sun_up_hours]\n return su_pattern\n\n\ndef peak_values(ill_file, su_pattern, coincident):\n \"\"\"Compute average values for a given result file.\"\"\"\n max_vals, max_i = [], None\n with open(ill_file) as results:\n if coincident:\n all_values = [[float(r) for r in pt_res.split()] for pt_res in results] \\\n if su_pattern is None else \\\n [[float(r) for r, is_hoy in zip(pt_res.split(), su_pattern) if is_hoy]\n for pt_res in results]\n max_val, max_i = 0, 0\n for i, t_step in enumerate(zip(*all_values)):\n tot_val = sum(t_step)\n if tot_val > max_val:\n max_val = tot_val\n max_i = i\n for sensor in all_values:\n max_vals.append(sensor[max_i])\n else:\n if su_pattern is None: # no HOY filter on results\n for pt_res in results:\n values = [float(r) for r in pt_res.split()]\n max_vals.append(max(values))\n else:\n for pt_res in results:\n values = [float(r) for r, is_hoy in zip(pt_res.split(), su_pattern) if is_hoy]\n max_vals.append(max(values))\n return max_vals, max_i\n\n\nif all_required_inputs(ghenv.Component):\n # set up the default values\n grid_filter_ = '*' if grid_filter_ is None else grid_filter_\n res_folder = os.path.dirname(_results[0]) if os.path.isfile(_results[0]) \\\n else _results[0]\n\n # check to see if results use the newer numpy arrays\n if os.path.isdir(os.path.join(res_folder, '__static_apertures__')):\n cmds = [folders.python_exe_path, '-m', 'honeybee_radiance_postprocess',\n 'post-process', 'peak-values', res_folder, '-sf', 'metrics']\n if len(_hoys_) != 0:\n hoys_str = '\\n'.join(str(h) for h in _hoys_)\n hoys_file = os.path.join(res_folder, 'hoys.txt')\n write_to_file(hoys_file, hoys_str)\n cmds.extend(['--hoys-file', hoys_file])\n if grid_filter_ != '*':\n cmds.extend(['--grids-filter', grid_filter_])\n if coincident_:\n cmds.append('--coincident')\n use_shell = True if os.name == 'nt' else False\n process = subprocess.Popen(\n cmds, cwd=res_folder, shell=use_shell,\n stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n stdout = process.communicate() # wait for the process to finish\n if stdout[-1] != '':\n print(stdout[-1])\n raise ValueError('Failed to compute peak values.')\n avg_dir = os.path.join(res_folder, 'metrics', 'peak_values')\n if os.path.isdir(avg_dir):\n values = read_sensor_grid_result(avg_dir, 'peak','full_id', False)\n values = list_to_data_tree(values)\n with open(os.path.join(avg_dir, 'max_hoys.txt'), 'r') as max_hoys:\n hoys = [line.rstrip() for line in max_hoys.readlines()]\n if coincident_:\n hoys = map(int, hoys)\n else:\n hoys = [None] * len(hoys)\n\n else:\n # extract the timestep if it exists\n timestep = 1\n tstep_file = os.path.join(res_folder, 'timestep.txt')\n if os.path.isfile(tstep_file):\n with open(tstep_file) as tf:\n timestep = int(tf.readline())\n \n # parse the sun-up-hours\n grids, sun_up_hours = _process_input_folder(res_folder, grid_filter_)\n su_pattern = parse_sun_up_hours(sun_up_hours, _hoys_, timestep)\n \n # compute the average values\n values, hoys = [], []\n for grid_info in grids:\n ill_file = os.path.join(res_folder, '%s.ill' % grid_info['full_id'])\n dgp_file = os.path.join(res_folder, '%s.dgp' % grid_info['full_id'])\n if os.path.isfile(dgp_file):\n max_list, max_i = peak_values(dgp_file, su_pattern, coincident_)\n else:\n max_list, max_i = peak_values(ill_file, su_pattern, coincident_)\n values.append(max_list)\n if max_i is not None:\n hoys.append(sun_up_hours[max_i])\n else:\n hoys.append(max_i)\n values = list_to_data_tree(values)\n", + "code": "\nimport os\nimport subprocess\n\ntry:\n from ladybug.futil import write_to_file\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug:\\n\\t{}'.format(e))\n\ntry:\n from honeybee.config import folders\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee:\\n\\t{}'.format(e))\n\ntry:\n from honeybee_radiance.postprocess.annualdaylight import _process_input_folder\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee_radiance:\\n\\t{}'.format(e))\n\ntry:\n from pollination_handlers.outputs.helper import read_sensor_grid_result\nexcept ImportError as e:\n raise ImportError('\\nFailed to import pollination_handlers:\\n\\t{}'.format(e))\n\ntry:\n from ladybug_{{cad}}.{{plugin}} import all_required_inputs, list_to_data_tree\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_{{cad}}:\\n\\t{}'.format(e))\n\n\ndef parse_sun_up_hours(sun_up_hours, hoys, timestep):\n \"\"\"Parse the sun-up hours from the result file .txt file.\n\n Args:\n sun_up_hours: A list of integers for the sun-up hours.\n hoys: A list of 8760 * timestep values for the hoys to select. If an empty\n list is passed, None will be returned.\n timestep: Integer for the timestep of the analysis.\n \"\"\"\n if len(hoys) != 0:\n schedule = [False] * (8760 * timestep)\n for hr in hoys:\n schedule[int(hr * timestep)] = True\n su_pattern = [schedule[int(h * timestep)] for h in sun_up_hours]\n return su_pattern\n\n\ndef peak_values(ill_file, su_pattern, coincident):\n \"\"\"Compute average values for a given result file.\"\"\"\n max_vals, max_i = [], None\n with open(ill_file) as results:\n if coincident:\n all_values = [[float(r) for r in pt_res.split()] for pt_res in results] \\\n if su_pattern is None else \\\n [[float(r) for r, is_hoy in zip(pt_res.split(), su_pattern) if is_hoy]\n for pt_res in results]\n max_val, max_i = 0, 0\n for i, t_step in enumerate(zip(*all_values)):\n tot_val = sum(t_step)\n if tot_val > max_val:\n max_val = tot_val\n max_i = i\n for sensor in all_values:\n max_vals.append(sensor[max_i])\n else:\n if su_pattern is None: # no HOY filter on results\n for pt_res in results:\n values = [float(r) for r in pt_res.split()]\n max_vals.append(max(values))\n else:\n for pt_res in results:\n values = [float(r) for r, is_hoy in zip(pt_res.split(), su_pattern) if is_hoy]\n max_vals.append(max(values))\n return max_vals, max_i\n\n\nif all_required_inputs(ghenv.Component):\n # set up the default values\n grid_filter_ = '*' if grid_filter_ is None else grid_filter_\n res_folder = os.path.dirname(_results[0]) if os.path.isfile(_results[0]) \\\n else _results[0]\n\n # check to see if results use the newer numpy arrays\n if os.path.isdir(os.path.join(res_folder, '__static_apertures__')):\n cmds = [folders.python_exe_path, '-m', 'honeybee_radiance_postprocess',\n 'post-process', 'peak-values', res_folder, '-sf', 'metrics']\n if len(_hoys_) != 0:\n hoys_str = '\\n'.join(str(h) for h in _hoys_)\n hoys_file = os.path.join(res_folder, 'hoys.txt')\n write_to_file(hoys_file, hoys_str)\n cmds.extend(['--hoys-file', hoys_file])\n if grid_filter_ != '*':\n cmds.extend(['--grids-filter', grid_filter_])\n if coincident_:\n cmds.append('--coincident')\n use_shell = True if os.name == 'nt' else False\n process = subprocess.Popen(\n cmds, cwd=res_folder, shell=use_shell,\n stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n stdout = process.communicate() # wait for the process to finish\n if stdout[-1] != '':\n print(stdout[-1])\n raise ValueError('Failed to compute peak values.')\n avg_dir = os.path.join(res_folder, 'metrics', 'peak_values')\n if os.path.isdir(avg_dir):\n values = read_sensor_grid_result(avg_dir, 'peak','full_id', False)\n values = list_to_data_tree(values)\n with open(os.path.join(avg_dir, 'max_hoys.txt'), 'r') as max_hoys:\n hoys = [line.rstrip() for line in max_hoys.readlines()]\n if coincident_:\n hoys = map(int, hoys)\n else:\n hoys = [None] * len(hoys)\n\n else:\n # extract the timestep if it exists\n timestep = 1\n tstep_file = os.path.join(res_folder, 'timestep.txt')\n if os.path.isfile(tstep_file):\n with open(tstep_file) as tf:\n timestep = int(tf.readline())\n \n # parse the sun-up-hours\n grids, sun_up_hours = _process_input_folder(res_folder, grid_filter_)\n su_pattern = parse_sun_up_hours(sun_up_hours, _hoys_, timestep)\n filt_suh = [suh for suh in sun_up_hours if int(suh) in _hoys_] \\\n if len(_hoys_) != 0 else sun_up_hours\n # compute the average values\n values, hoys = [], []\n for grid_info in grids:\n ill_file = os.path.join(res_folder, '%s.ill' % grid_info['full_id'])\n dgp_file = os.path.join(res_folder, '%s.dgp' % grid_info['full_id'])\n if os.path.isfile(dgp_file):\n max_list, max_i = peak_values(dgp_file, su_pattern, coincident_)\n else:\n max_list, max_i = peak_values(ill_file, su_pattern, coincident_)\n values.append(max_list)\n if max_i is not None:\n hoys.append(filt_suh[max_i])\n else:\n hoys.append(max_i)\n values = list_to_data_tree(values)\n", "category": "HB-Radiance", "name": "HB Annual Peak Values", "description": "Get peak irradiance or sum of illuminance values over an annual irradiance or\ndaylight simulation.\n_\nThe _hoys_ input can also be used to filter the data for a particular time period or\nhour/timestep of the simulation.\n-" diff --git a/honeybee_grasshopper_radiance/src/HB Annual Peak Values.py b/honeybee_grasshopper_radiance/src/HB Annual Peak Values.py index 86ec61e..7198d4a 100644 --- a/honeybee_grasshopper_radiance/src/HB Annual Peak Values.py +++ b/honeybee_grasshopper_radiance/src/HB Annual Peak Values.py @@ -45,7 +45,7 @@ ghenv.Component.Name = 'HB Annual Peak Values' ghenv.Component.NickName = 'PeakValues' -ghenv.Component.Message = '1.6.1' +ghenv.Component.Message = '1.6.2' ghenv.Component.Category = 'HB-Radiance' ghenv.Component.SubCategory = '4 :: Results' ghenv.Component.AdditionalHelpFromDocStrings = '2' @@ -174,7 +174,8 @@ def peak_values(ill_file, su_pattern, coincident): # parse the sun-up-hours grids, sun_up_hours = _process_input_folder(res_folder, grid_filter_) su_pattern = parse_sun_up_hours(sun_up_hours, _hoys_, timestep) - + filt_suh = [suh for suh in sun_up_hours if int(suh) in _hoys_] \ + if len(_hoys_) != 0 else sun_up_hours # compute the average values values, hoys = [], [] for grid_info in grids: @@ -186,7 +187,7 @@ def peak_values(ill_file, su_pattern, coincident): max_list, max_i = peak_values(ill_file, su_pattern, coincident_) values.append(max_list) if max_i is not None: - hoys.append(sun_up_hours[max_i]) + hoys.append(filt_suh[max_i]) else: hoys.append(max_i) values = list_to_data_tree(values) diff --git a/honeybee_grasshopper_radiance/user_objects/HB Annual Peak Values.ghuser b/honeybee_grasshopper_radiance/user_objects/HB Annual Peak Values.ghuser index 0a7a64741f951cf551886c11ef3068e27463b4f4..0b7151c8b93681596ff5da04a8839335a0ad2583 100644 GIT binary patch literal 6638 zcmV1Pc?D1$Ft;x5PI2kd#VPLY6e%u6iY&0eVhg*tyGwB?rMSBnEmn#YEAG z0Gz-eI{*X*`#**NC=391bpSxE01$h7R|kmWe@op!_O4(!0O|&Y0YHu~-O&|f|G(XE zu`EHJ_7H0u7XTdM;A#*0mx}`z%M=$&#|8{AwSjuVO#u)`Cs!AM#lIN#a45hW3~+^m zEdef2fEC2v)Qd1q85qsRRUY0>NA$7BA6WG!O^yOSv!z)bhX6*+5-kod1My z7qHX6g8#Yxuhi@q@*Yl5xGN0&Z_UUrYexA;N*4dK39nul#TQAWhT1z^EXsd-f~%w` zr}Z+E|04{vmvPNq{N`msgloN%L8uy~-u)NBT3S{5NSFt^tX} zb*tLzDoGi87VjZzNOBTAi=!|w)cFnJaf0zQXqIPne(g!>|^)A`gAte-Zvz_j<< z_#L1UNQU@~eq21IAdjB-1We?c6b-jCf*md=NKf=$y^*F@9$`Ar#e`XU;6x;(Ey>F9 zQLaJ<`2iG+XqI>o38CKdE#;3B7_k*dAiNyKNGvmoUOk{i!=%GK24_ zon95sA~ES<`#$KfZFq#T&fHl`_)jJ*6}Dyd4{s{VNStxWYV$(J{9Ior4!Z-(D;|8G z%I&5uz{G`}SB?T9lSH;Y*OKE6wwvc|up^UOXXec8a{4Gu?KiSEdCZFqdvA@aH`fo5 z1d6JlSJq(Pj6*fiArS>)$h8ynKze1%ls0$d+ z#c0Q}82S_(4=1?{Q-(PnS_(hA_An{4%f59=c$d6Cs-IbFVw-1*OB2++rp!|4cwNZV zwMH`|iWhp}g^c+7p^q!(wg1=aG!&fM60SZ$TMw!g=|s~AZNKG%KkKjmZ1$Dc75d0h znjb*j%^<0(oR%paMAav`BJm4_=}vi zBa12fV>hmvgM(pjQx(1XJ^h#p9o!oP|CU@t@-}^a?lxfxNNDX`CfXt?rcj{b94h?y zOxxsB;hRbMlt%D~RuEr$#=^wv^$(bpxZNJCKrIe`BI!6b?@0O|k1QjLE&toPqAi=hUwu={__5YYbbZSW7YE{PEk zmJKmIw81lKEM_dEEu`Emj1cg&`D0r_VG1cL>Z=7x$0W|d%E69P4hQ6zptn&} zYxu4j3R2;}P?w~V3FVzxup-8CO1OYn`RBbDVgcAW58FQ1D}Pq#*@fJ;{IezT83voP z;GOW5`emQpO@=pdrR7Ytw{P2d-jb@I-|TDAdni%7*4wcWiOFI=7>mJnBHG*x3JJXC zVjGHCvrtCOmfOQ{wko&|_kW*25Eqdp3E&k_C*@_f9yb%!9`ypcMt!AV!jQ%!;~_>3 zO+X4Fy5K=<3gmioN>CN(L6LfjfJpxq9fKEk;ZgK`8<_V!hF*+t9-v)1g({4%J?e?U zNm$U2hPpDw2ZI4vM=**;22O(Pt?6absgUcaM}Lhv+S|L_dG@nJJRRh;nhteF4h8$) zE|6<;)zvL=&t+^9$i4fN5?P}4DInz4l|aTd)7wrKpg2&RmzNjlOP`Rz1v)kp(zNWL z9`?onM#L0^ocMg?DhUzFjuWVn4pod9M`+ileLBUWo^D_VE)OdN>C_UDX$6olodDWI zfxgr>Lo@_$5ZSo zwZen%BIW{k$~>6+Xxz1VdGO$=_7w%l3EpF!3J=Ja3tFp7<|ff?Ru2haa4583R5q~E9AAn>93>vN z{PZ0pMeIa5mGXv|llB$3@7F~O7t>Vk0|LMnU}nacX<+j6RjqVi{Chzwg+r}4H&8Gk zrppHd){AGv30+^>f@Q#lzWyJDw3Z!&SC&ZDztE8kE}h5LQ44=@j*`eMQNX}BgJ z{v0Xme-e)qT~7KmczvqU zLyJ&1XMR-=DO?(5lA)~cJ#KO)!I;fXT`VyP zlAk@i)bn<|{c5sHMD%HFbhGT1q~$_VZtl zj2r1jG!mtD#%kN!oxsA9`^@&Vl+CxQ9eRT3Ko2qU%&C%sd<5f2O^*oH#EH94#lITp ze<~uN=tM|+Oh0{FD^4%RfnXW~Q-hr-aA%xnwgErcR9kLl4#ok+85Z0eI+Z__3@1-v z>5~(GC{Yh@N`T^65JYBbod-W6Ysh#lK`f8%+A?+DuvX6yoq&DJV^|MD4gp}IB(IL2 zc$!+@j8X`55>t?Y(kIJMWK0p={tS2Y&6hwpAKGWJACxLGI-$fR5|v6b@NCfw@s_&j zP!co68TDD?ZR#!RWJ@JNp*m9nAgh0P_J^d33a!1E=y0xY81`Nb^yS#?CTgnEh4kbc zfi1^GV!Cw^B?E`dDheWGG6QBPEO(k(dky*_=XK;0OOaM_Ife)#zELC6#`#A!uY@yN zz=+5~^rVx2=s+1P`APjdP#kQD>I6aqJPlBKgMr-*5vkkco&vD+bg**W?oVk~vj`%T zPOmAUqJmxq^^KVNu~% zBTm@`?VA#zZTF}e0vs6xX@YOa8kVTipVj!M6-N;QuzK=;NYhu$R(j=s-hxg4U3m9k z3Cmi=x>b_o&qoKlljOhWN%6xt#w+b#{R^s07roIES&i&7Z=;1OXB6T(A^FKo7> zE)$JaGriDAAh>3%`7UAK-e=I2{$60NVw+^6vDV4mLmwSL!Gy|U)~A=BrBmmpiXm=< z`e1Q4X3hW`ofM3g3~=Se&hW-9RW6CsX8Fyhb1bMTcr;2-(bA7&ublTuxK9M2{Ko}i z3562{jX!uWQ>*|s-`^AnHrujLm8AdhXaf~4YZQ|Q=obh8aiTB3ekFInB@v&M@&J;4 z(NqneWBh}*^Fy7QwnDG5MnpXtfez=#c<7*v`V`glT*7xt?(U6-7?WyDl#B9QGj#k{ z^(8J-JRl0>bZsnj;rXInb(&s-SOa!#3&!$V{&Z9+NL0N_FP6*pH)(@)Mk+yQZhzEm6=AyoJM;fx4aejFJlxP zHjf%ILl z5aEvHEI}SJCL};x++&<~mdxhM1(5(OB&lA)x}Imul*&a78>7R>lbVfoU@JnWtRKAO zDyW)~n{f|R|OYR3QcG&^_s%cg%;36_XrB1qFZABIc0;^c9OZADHiZcLZ=8D}Q?Y<#iDN<; zy3pdWCwqgPirBYI@5&7x2`)~V$CMD0taQz%N@@sJY1W`f+PAfz+wh|1@n=-pWah#O%H$A{iGs9h_OJ7Q+^5s2k zz!KbKtx`hWKGa0UsAN0&2EElQ)KwVdAl zI&dKrWReELdgV|OnfKds#5GKlTEsnTN^oFcsP8=mkUYL5T^`qN+`Nv{V1kO6OkDX& zUe)|}Iez||9%q`iyXchLG{Qkg+ob^5WVn1$5|u>-cLBNg54eQ2263`DgbKR=WEaqs zeWGAz$B;W%AkhCkPiiB&s6AwcMM*|?vmqf>)4^*b*SM{8AjY5CF+Pu&a?mzd{idP# zHb?gLQ&C=-NN4(N>xh7~fK-nuuyQ1Tykqa>MvgBqdG`EH%G}d>UXUIE{_?J_2nUE7 zR;RyeZGU?CrLytnbXu7WG1q}{+bfP>Om3K^);4=mZ7OI8y9{FTX}XHdvt*KGmt4($ ze?cfS0bx0fB)+8yF8_fnL5$%0sdJGZi-rHt^EDe^tUi!kqg!!)45XDhV!T0ydB6zX6>Uo#k!=Yf>z}Luzh~c?>Xy4Z` z<|Lb87e8YzhBe11z=c({Co5FXlOny(rUF}!7*Lu=R1#FuVeIqPY>?=BM~2g`+@Sd` z%ZBODVZohUG=r;YM!DI?-ICyF+hMyXPOs{FneMBiy^7(lAd%t*ld)ucEKBg!3Q@kq> zbmdaLT2}`<>K9jgtho*rH-Z9ZJG^(7+0otJQat=h?6-cNJDZr#_??~A+2nPVoD%B} zy)k#k_e3={u66pgQm=B7nKeq3e-fB00U|$sW9UJNDcmy4GqULiL>fY?| zpW6ekS6XY|{Tve4{P>K>(&amX+Q|XjwjVxr-;PNEKdb|3o*r%vtL{5H4mN(tGQRha z*j_tI+@k1+o9?Q!JQNKdFxKpXCs+^VsIlXX)fKPQm}>bu^-j^U8%v1RZS6#*o+WJ> zx23%|6sTBSt#*@iIbj(ars2fC^smo58I6q|+=N22Ys&_<7LP>lHVZO8%w{cJ&xNf& zYGt?O%=%xNJfvk&U3XnSRV4EYE-J`P{#l_4Kj4(OorgEI8$(_{aR_>^^Qiw$fgZ9^ zJq#IV5Tm)>JpBwF`}o>#rmnN(E|qh10sTlQ>$RAKi|*6lm<(01aI0T^)p=EJwd-P; z&;1zioKte@G9&-G(09ttcH3Xi_tL>>*W&o9*57H9iRlkv@SJeUS^d4Ehx1W{s?0!U zsyiAU)kX2`z5xHniCatk2P?aqI0go?l|$3p=`Z%Gdv{Y7#qRb>qMuGL&X13e&s~bC zzRvmQx$TN;o2bGU_x(*C0({Sy-B+00O%1f3n(Blvr#A&#&~R`Ru1INi_`Un{j`fV& z%zBRQ1xG#Cnv9{hs~(l!!`-UbKh$sj7S*?f&wTZAKGMIQTKC2J#vv>C^@uFJ;b~62 zQIM}n6#ghrB06yGW9>Q=E*==33QCsC4^EZCa4pFO}k4*=;Z8N`io4_!S{+`j}s3s`h55p?%&W zxcl^_pZf}xsCxV!m-Rx+is>cA-F6J{y~@=L^k~8T=KGT$dgsdBNHkj!iLr^hzD?E$ z|1#^f{>+SZA47OVpHEXey=hDf?=mRo| z2lI*CFOsay4Gkl;f@x}=Ot3Up`I6JW>*yc8ri*&?PSzVaHmjMQENuCTKl`1Q4J+SS zLX9-xO~+x7v8SinVSmqtfw3=Nvm3WOuIdH9sqfj2E z4l0J8Z`hELXGwak<=Z{iE=~LVjYD0x%j{VmDnNoFSDoo$*ox(SZu=nhi^MFLpxvqM z6)uG4^x9JV^~bD>@EyKKYinNvuY>083nYrxAQ9$BW~KJ~@aoafpq}!NcbnG_LFU63 z;U7s=3={h6T5Gf!W&z^_3;5P>EGPOUZ?u zm35QIAZwElY_(qtzpo3U(67{&z3bg4lJ0zlgttVqEF>CJZmVorxduc1A$uP0YbNTS zy|kyj!_TaJPtGnHEG(XgjB`{3ChnhlS)eY?9aqbRBown(cg(FhvTWEou^ORtNmVw= zQ{I8-!*FAA>OB(z9IV*Q9MXtin!`+N7=Fr-RVw#G*WoH-$C*ao&m5)RwchtS%;5WI zYd=Zbt(#!GBSBA<%qMEzZfSE~i-vs<&C2JhsJ$#hjtPAVUBnt)`uEDYp4{nWSa^qY zj5+IU#-@f_lMJ-5UH;zH`zqK-*ff5@B9QcrcAQM96om`-iG)oR81qp}HCcXhp?I#Yo9 z>*sjc<4kw;)lWQsGZO#ezwX@V36i?34lsFoXvz`v4J7nZWO_91p5JCUbnZTHHrca} zkIsrr6HMyaE`au`6T5Lzo&SkZy>2z}dXgsg;oy@fRYFMcy!s$Dc;-*rPYdCVV&{`# zxik%(#AoSxG(3QIV%;xRcRk&)RVz7Pg^!VuxqNlabMfq|UmQsFhvI1)*~MtaZp&-N-`23eWti(aW0s1e`Z(&WDpWeTkB;x{ zF=hK<7u(KyWJQbG?7gx&>H|F^+NB;WD4V^q?BL;|J7&rj9qr;&vt5EQjZ{)wa;zXe z8NOqosoZ89@>H4jHI^(pDRX@*b6-O2)Jl0>oMe+jb(!nNAc;c6oKdYNSmf@wCI}p8 z6)LV&5s)xp6N#j8F;1%%Wkj1l~r$PnZNA9)^;`j|G#-~_y8gz04*@w)!qgEU!j-_TmS$7 literal 6622 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