From a98b8bfaee0ad345ec6b82f67e933e1566917a42 Mon Sep 17 00:00:00 2001 From: Pedro Henrique Bohling Peres Date: Sat, 3 Oct 2026 01:49:00 -0300 Subject: [PATCH 1/5] chore: ignore caches and logs, untrack pyc files --- .gitignore | 17 +- MUJOCO_LOG.TXT | 255 ------------------ .../__pycache__/__init__.cpython-311.pyc | Bin 199 -> 0 bytes .../__pycache__/config.cpython-311.pyc | Bin 1086 -> 0 bytes .../trajectory_generator.cpython-311.pyc | Bin 7427 -> 0 bytes .../walking_engine.cpython-311.pyc | Bin 8993 -> 0 bytes .../__pycache__/ik_solver.cpython-311.pyc | Bin 5753 -> 0 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 169 -> 0 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 181 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 169 -> 0 bytes .../__pycache__/__main__.cpython-310.pyc | Bin 209 -> 0 bytes .../__pycache__/__main__.cpython-311.pyc | Bin 256 -> 0 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 223 -> 0 bytes mujoco_sim/__pycache__/sim.cpython-310.pyc | Bin 2005 -> 0 bytes mujoco_sim/__pycache__/sim.cpython-311.pyc | Bin 14657 -> 0 bytes mujoco_sim/__pycache__/sim.cpython-312.pyc | Bin 13175 -> 0 bytes .../caminhada_relativa.cpython-310.pyc | Bin 5669 -> 0 bytes .../__pycache__/ik_solver.cpython-310.pyc | Bin 4022 -> 0 bytes .../__pycache__/ik_solver.cpython-311.pyc | Bin 7609 -> 0 bytes .../__pycache__/lipm_model.cpython-310.pyc | Bin 2669 -> 0 bytes .../__pycache__/mpc_walker.cpython-310.pyc | Bin 7918 -> 0 bytes .../state_estimator.cpython-310.pyc | Bin 5273 -> 0 bytes .../trajectory_generator.cpython-310.pyc | Bin 4347 -> 0 bytes .../trajectory_generator.cpython-311.pyc | Bin 9047 -> 0 bytes .../__pycache__/walk_engine.cpython-310.pyc | Bin 7680 -> 0 bytes 25 files changed, 15 insertions(+), 257 deletions(-) delete mode 100644 MUJOCO_LOG.TXT delete mode 100644 mujoco_sim/AnalyticalWalking/__pycache__/__init__.cpython-311.pyc delete mode 100644 mujoco_sim/AnalyticalWalking/__pycache__/config.cpython-311.pyc delete mode 100644 mujoco_sim/AnalyticalWalking/__pycache__/trajectory_generator.cpython-311.pyc delete mode 100644 mujoco_sim/AnalyticalWalking/__pycache__/walking_engine.cpython-311.pyc delete mode 100644 mujoco_sim/Utils/__pycache__/ik_solver.cpython-311.pyc delete mode 100644 mujoco_sim/__pycache__/__init__.cpython-310.pyc delete mode 100644 mujoco_sim/__pycache__/__init__.cpython-311.pyc delete mode 100644 mujoco_sim/__pycache__/__init__.cpython-312.pyc delete mode 100644 mujoco_sim/__pycache__/__main__.cpython-310.pyc delete mode 100644 mujoco_sim/__pycache__/__main__.cpython-311.pyc delete mode 100644 mujoco_sim/__pycache__/__main__.cpython-312.pyc delete mode 100644 mujoco_sim/__pycache__/sim.cpython-310.pyc delete mode 100644 mujoco_sim/__pycache__/sim.cpython-311.pyc delete mode 100644 mujoco_sim/__pycache__/sim.cpython-312.pyc delete mode 100644 pybullet_sim/__pycache__/caminhada_relativa.cpython-310.pyc delete mode 100644 pybullet_sim/__pycache__/ik_solver.cpython-310.pyc delete mode 100644 pybullet_sim/__pycache__/ik_solver.cpython-311.pyc delete mode 100644 pybullet_sim/__pycache__/lipm_model.cpython-310.pyc delete mode 100644 pybullet_sim/__pycache__/mpc_walker.cpython-310.pyc delete mode 100644 pybullet_sim/__pycache__/state_estimator.cpython-310.pyc delete mode 100644 pybullet_sim/__pycache__/trajectory_generator.cpython-310.pyc delete mode 100644 pybullet_sim/__pycache__/trajectory_generator.cpython-311.pyc delete mode 100644 pybullet_sim/__pycache__/walk_engine.cpython-310.pyc diff --git a/.gitignore b/.gitignore index 748df4c..5140822 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,15 @@ -.claude -./.claude \ No newline at end of file +# Python +__pycache__/ +*.py[cod] +*.egg-info/ +.venv/ +venv/ +.pytest_cache/ + +# Simulator logs +MUJOCO_LOG.TXT + +# Editors / tools +.vscode/ +.idea/ +.claude/ diff --git a/MUJOCO_LOG.TXT b/MUJOCO_LOG.TXT deleted file mode 100644 index 1ac58d3..0000000 --- a/MUJOCO_LOG.TXT +++ /dev/null @@ -1,255 +0,0 @@ -Fri May 15 08:25:55 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 3. The simulation is unstable. Time = 0.0080. - -Fri May 15 08:26:13 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 1. The simulation is unstable. Time = 0.0060. - -Fri May 15 08:27:24 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 3. The simulation is unstable. Time = 0.0080. - -Fri May 15 08:34:42 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 6. The simulation is unstable. Time = 0.0180. - -Fri May 15 08:36:42 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 16. The simulation is unstable. Time = 0.0100. - -Fri May 15 08:37:58 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 14. The simulation is unstable. Time = 0.0100. - -Fri May 15 08:39:43 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 0. The simulation is unstable. Time = 3.4420. - -Fri May 15 08:42:04 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 14. The simulation is unstable. Time = 0.0280. - -Fri May 15 08:42:54 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 0. The simulation is unstable. Time = 0.0480. - -Fri May 15 09:25:48 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 14. The simulation is unstable. Time = 0.0180. - -Fri May 15 16:23:58 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 16. The simulation is unstable. Time = 0.0320. - -Fri May 15 16:35:21 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 7. The simulation is unstable. Time = 0.0400. - -Fri May 15 16:38:55 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 7. The simulation is unstable. Time = 0.0400. - -Fri May 15 16:41:35 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 3. The simulation is unstable. Time = 0.6000. - -Fri May 15 16:44:42 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 4. The simulation is unstable. Time = 4.9800. - -Fri May 15 16:44:48 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 13. The simulation is unstable. Time = 0.0860. - -Fri May 15 16:44:49 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 7. The simulation is unstable. Time = 0.1680. - -Fri May 15 16:44:51 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 22. The simulation is unstable. Time = 0.1920. - -Fri May 15 16:45:10 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 4. The simulation is unstable. Time = 4.9800. - -Fri May 15 16:54:07 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 23. The simulation is unstable. Time = 2.4580. - -Fri May 15 16:54:17 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 23. The simulation is unstable. Time = 2.4580. - -Fri May 15 16:54:51 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 3. The simulation is unstable. Time = 2.6300. - -Fri May 15 16:55:04 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 2. The simulation is unstable. Time = 2.7300. - -Fri May 15 16:56:12 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 23. The simulation is unstable. Time = 2.4580. - -Fri May 15 16:57:36 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 0. The simulation is unstable. Time = 4.1720. - -Fri May 15 16:57:50 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 0. The simulation is unstable. Time = 4.1720. - -Fri May 15 16:58:01 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 3. The simulation is unstable. Time = 2.5320. - -Fri May 15 17:20:24 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 3. The simulation is unstable. Time = 2.5320. - -Fri May 15 17:49:12 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 3. The simulation is unstable. Time = 2.7920. - -Fri May 15 17:56:44 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 23. The simulation is unstable. Time = 2.0360. - -Fri May 15 17:56:56 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 23. The simulation is unstable. Time = 2.0360. - -Fri May 15 18:41:47 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 23. The simulation is unstable. Time = 2.0360. - -Sat May 16 10:14:26 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 8. The simulation is unstable. Time = 2.8020. - -Sat May 16 10:15:25 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 8. The simulation is unstable. Time = 2.8020. - -Sat May 16 10:15:35 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 8. The simulation is unstable. Time = 2.8020. - -Sat May 16 12:51:49 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 16. The simulation is unstable. Time = 2.7180. - -Sat May 16 12:58:27 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 23. The simulation is unstable. Time = 2.8980. - -Sat May 16 13:04:06 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 8. The simulation is unstable. Time = 3.4080. - -Sat May 16 13:11:12 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 17. The simulation is unstable. Time = 4.4920. - -Sat May 16 13:11:22 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 17. The simulation is unstable. Time = 4.4920. - -Sat May 16 13:12:41 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 8. The simulation is unstable. Time = 3.0880. - -Sat May 16 13:13:38 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 13. The simulation is unstable. Time = 3.1920. - -Sat May 16 13:13:46 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 3. The simulation is unstable. Time = 3.1800. - -Sat May 16 13:14:17 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 3. The simulation is unstable. Time = 3.1800. - -Sat May 16 13:17:27 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 6. The simulation is unstable. Time = 3.4660. - -Sun May 17 04:30:54 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 8. The simulation is unstable. Time = 3.1000. - -Sun May 17 04:31:06 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 8. The simulation is unstable. Time = 3.1000. - -Sun May 17 04:31:33 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 3. The simulation is unstable. Time = 3.1720. - -Sun May 17 04:35:30 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 7. The simulation is unstable. Time = 3.8580. - -Sun May 17 04:35:39 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 7. The simulation is unstable. Time = 3.8580. - -Sun May 17 04:36:08 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 7. The simulation is unstable. Time = 4.0240. - -Sun May 17 04:42:03 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 7. The simulation is unstable. Time = 3.1340. - -Sun May 17 04:44:02 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 4. The simulation is unstable. Time = 3.1180. - -Sun May 17 04:45:55 2026 -WARNING: Nan, Inf or huge value in CTRL at ACTUATOR 1. The simulation is unstable. Time = 3.0000. - -Sun May 17 04:46:05 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 6. The simulation is unstable. Time = 3.1620. - -Sun May 17 04:46:20 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 24. The simulation is unstable. Time = 3.2480. - -Sun May 17 04:47:11 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 0. The simulation is unstable. Time = 3.2400. - -Sun May 17 04:47:22 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 4. The simulation is unstable. Time = 3.1980. - -Sun May 17 05:04:47 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 10. The simulation is unstable. Time = 3.9620. - -Sun May 17 05:08:29 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 6. The simulation is unstable. Time = 8.2400. - -Sun May 17 05:13:49 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 13. The simulation is unstable. Time = 8.0400. - -Sun May 17 05:16:10 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 0. The simulation is unstable. Time = 8.0400. - -Sun May 17 05:26:28 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 8. The simulation is unstable. Time = 8.0460. - -Sun May 17 10:45:58 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 8. The simulation is unstable. Time = 8.0460. - -Sun May 17 10:51:30 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 4. The simulation is unstable. Time = 8.5820. - -Sun May 17 10:54:30 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 11. The simulation is unstable. Time = 9.2420. - -Sun May 17 10:55:06 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 5. The simulation is unstable. Time = 8.2700. - -Sun May 17 10:56:05 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 14. The simulation is unstable. Time = 8.2740. - -Mon May 18 11:52:31 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 14. The simulation is unstable. Time = 8.2740. - -Mon May 18 11:55:08 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 3. The simulation is unstable. Time = 8.3380. - -Mon May 18 11:57:57 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 0. The simulation is unstable. Time = 0.0180. - -Mon May 18 12:11:08 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 3. The simulation is unstable. Time = 0.0200. - -Mon May 18 12:11:32 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 3. The simulation is unstable. Time = 0.0200. - -Mon May 18 12:22:01 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 23. The simulation is unstable. Time = 0.0360. - -Mon May 18 13:41:42 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 6. The simulation is unstable. Time = 5.5980. - -Mon May 18 13:45:38 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 16. The simulation is unstable. Time = 1.0180. - -Mon May 18 13:45:53 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 16. The simulation is unstable. Time = 1.0180. - -Mon May 18 13:47:38 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 4. The simulation is unstable. Time = 0.5160. - -Mon May 18 13:49:51 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 4. The simulation is unstable. Time = 0.5160. - -Tue May 19 17:50:39 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 6. The simulation is unstable. Time = 2.9180. - -Tue May 19 17:53:55 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 6. The simulation is unstable. Time = 1.5300. - -Tue May 19 18:00:48 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 11. The simulation is unstable. Time = 1.6500. - -Tue May 19 18:04:10 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 8. The simulation is unstable. Time = 0.5700. - -Tue May 19 19:05:18 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 3. The simulation is unstable. Time = 1.1920. - -Tue May 19 19:07:29 2026 -WARNING: Nan, Inf or huge value in QACC at DOF 6. The simulation is unstable. Time = 1.4740. - diff --git a/mujoco_sim/AnalyticalWalking/__pycache__/__init__.cpython-311.pyc b/mujoco_sim/AnalyticalWalking/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 61da5f6b2a4ad11625302bbf63633ee9f9a90ae7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 199 zcmZ3^%ge<81RS^cvq1D?5CH>>P{wCAAY(d13PUi1CZpdMX(erAdUTTqmml9^nRS(fUYpI1_ppA(Z?nw6iNA77lA z8{?Rlm{SSVm6#Krn3J8EmmU)zpP83g5+AQuQ2C3)CO1E&G$+-rh!toR$T7wIK;i>4 PBO~Jn1{hJq3={(ZCqgwe diff --git a/mujoco_sim/AnalyticalWalking/__pycache__/config.cpython-311.pyc b/mujoco_sim/AnalyticalWalking/__pycache__/config.cpython-311.pyc deleted file mode 100644 index 2a3d83e60a66a34437d0b2bc65e6d8c3a7a3037e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1086 zcmZvbPfXKL9LHbxr|VW=FglokhMVjlUOX9NS;j;}#$1Q!ve?YM(v@}XY&*@6!wzb= z$qf}uIB1M9@ytmQ&&F^Z?`Al9X+qS5lfQO<a1_yBn zv$zlU@A~sJ9>8Hdh=)K2a0IFXcvxaE2O}ymt27>Y=F121sDR=zxxFa+AnqZQN+_cn z;Nvt2l$Dy;C_W*K1BR}uB*;2`~;iZ{<24c`(y~sV?MZ{EhlnIS#cqA*11c# zUgpM9HklH$iS%5Guq%n%3t}dbO)Lt8Nn{tr%#x5IUehw%B^hHduW{ayagbByT2;q& zRl06m1ebzgI)9Mur6|g#QEX!G%Hhxy^w#Kj4`btdvxh@*=&iFju_yeCCfj9pmDAJKtpCtF9a(&K^KRTx+n`qc%%Br65omLD6$9ZnQN#(&A!m gwfh5okOyfpc7&qFw*(u3d1bn0uWelZpSXwc7p+M_g8%>k diff --git a/mujoco_sim/AnalyticalWalking/__pycache__/trajectory_generator.cpython-311.pyc b/mujoco_sim/AnalyticalWalking/__pycache__/trajectory_generator.cpython-311.pyc deleted file mode 100644 index 1cae5de663bffa8e4705f40279eea6ee2c220e1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7427 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 diff --git a/mujoco_sim/__pycache__/__init__.cpython-310.pyc b/mujoco_sim/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 37259ae331443bdb479361576c8048f6880eec35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 169 zcmd1j<>g`k0!b~tED-$|L?8o3AjbiSi&=m~3PUi1CZpdltnZwk0wgm^a})FOGgEx?OEUBGbPI}7Q!>P{wCAAY(d13PUi1CZpdMX(erAdUTTqmml9^nRS(fUYpI1_ppA(Z?nw6iNA77lA y8xtR&nU`4-AFo$X`HRCQH$SB`C)KWq6=*QX>SBH%@qw95 zeK>uBPuPC=>_tUZ3#XN*%4x4#QIKL*y)7Fu>Oh^LX6d|E=F*v)E~x1|)q>nw@=GCQ XXpnN{?!#zfN%ghfhVnMFj3LD@cX={3 diff --git a/mujoco_sim/__pycache__/__main__.cpython-311.pyc b/mujoco_sim/__pycache__/__main__.cpython-311.pyc deleted file mode 100644 index caa62120fb49587977aa706a696284f15ed11235..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 256 zcmZ3^%ge<81RS^cv*dyFV-N=hn4pZ$YCy(xh7^Vr#vF!R#wbQc1}277#$`a!)iAXT zj0`DE!3>(rFBySCnvAztauYN2{4|+vF&Ag%7BK?_S2BDCss82RVin_@pOP9Al%JGe zl9^l_lTn(Rn3tcK;tS;F=jj#{rKV&imt>ZuI_Kw=6y@i{2KczG$)vkyGXfVj)VvtimFf%eT-e3^DfQoJ~ur#nVvbC@^fMF3EP$>YG Cl|OC( diff --git a/mujoco_sim/__pycache__/__main__.cpython-312.pyc b/mujoco_sim/__pycache__/__main__.cpython-312.pyc deleted file mode 100644 index 032cd68c10ec236630942784fabcb6890fbfd221..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmX@j%ge<81RS^cv*dyFV-N=hn4pZ$azMs(h7^Vr#vF!R#wbQc1}277#??@1Mutiz zP3D)3K&~d^EtcHG%sf9$rd!O#nYl&GK*5y^pFt{rdAL}`IOnIN#suXj<(Fh87sq6j z<|gLlXQuc9x%qjz1x2YTnaL%YWvR~jc_l^pIWf7VS^3HN@j#Pe;^RRE$H(gxRNmsS n$<0qG%}KQ@;s6>9vbdNRNPJ*sWMsU{AbOL5rIoFS4JZNt+1@u^ diff --git a/mujoco_sim/__pycache__/sim.cpython-310.pyc b/mujoco_sim/__pycache__/sim.cpython-310.pyc deleted file mode 100644 index 9914ed71843191e11da470917e5ddbce491f96b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2005 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 KWVep-+WcSkUHS(A diff --git a/mujoco_sim/__pycache__/sim.cpython-312.pyc b/mujoco_sim/__pycache__/sim.cpython-312.pyc deleted file mode 100644 index e3784fd95791dfbb39c5b0731d2a1b512a1a2792..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13175 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 E17-YQRR910 diff --git a/pybullet_sim/__pycache__/mpc_walker.cpython-310.pyc b/pybullet_sim/__pycache__/mpc_walker.cpython-310.pyc deleted file mode 100644 index 00acf79fd0afdfa7f2ecd2ed175b32b4d05936a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7918 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
7v%UKVl)$EYAL>1Fr93It zykW|dj(+SP&hpqlT;!AqM8_AA(4FNs?B*;IjI}EyixP{-0?M>ACG>`Nf+XE_HWrZ< zlo??pyJt2aAJ#}-J~HV?2k7n_ND85RYX3Yfg7O(5(u7G-gVgKZc?`_^;vlUUjr197 z*f*1V``>kj7x_ieMYht$)k$}#4bKpXdl*}~OrzPvSL$9lNNK^F!V*faYLS*QP8`cT zKHl~JXh61>jJk;w`&|k5k*#}P+NryCk8&XNzsBA@rG<=wgW0IwT;%npYx>A1cLe?~ zaNrXlXH0*8z&S8El&34x$HN3O$)Kl=zbxc_&}IwLPE6T-kGBo755#k1T!X%_!bv*=2 zcE_ulSG}kq>}HrkcCw0my9f>ZKj3)lvd6Lz)DM}sI((!-Bon3wt0t}xgAvVn7~w?r zaz>h#Fo6YtN>Pv#CgeU1V;wkF=7x}dluQcB(C0chqcCR}^*BVkD<+cGQm-Q|7P(_* ziHCwBWW<`L=Tm$aw-C|kq)g(A^jd%J!q-;B^VCep1qu@g#at0)FUTc-iV*S3Qj!z| zrs(rKphelglhhi7bmhs&Mi0X|kKX$Pt`g`HI7>iAZE`;t=EpW`kt-n$yG;!Am0{BL zu6L@}Zbq}vXaaj3NbxGz-bL|fkksLnn!}G`l7niFW#BI&CQd`6`upncPo)FD!) GqV|8e*WkJU diff --git a/pybullet_sim/__pycache__/state_estimator.cpython-310.pyc b/pybullet_sim/__pycache__/state_estimator.cpython-310.pyc deleted file mode 100644 index 4eae4fd9880b36cfcf79fa69d286d1b1aea2ac75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5273 zcma)AUu+yl8Q;Hm=gZk~Z0CO{LkPv!ICc_50n;=NCTXM8#v~zBDbcdN8QYue?QUoH zoIC4`DkVZxs6ql>;3*alRVw;W`cSD4AjPk!NW6958XlskFYroB#qXQl`xg_5(ar4q z{buHyZ+^dTE*u^%YY2b**uMGcsHXjc3jM)EVGc2#MZ&e~8rQku>(`B!G;VV1uEwo9 z#&r`Vn-@?jpk$#`ZcX=p9%(!48PEcU@&4&vta;3|p zaDA^Xg2df^d;1R|3qrQAG(TM~Ulyxqj^y@M;?-O>8H+lLMG%LQB&~#U`)zj$HFb7z z;YUhLb+*h9qA+G`vN_eNvRNv4nPBd+Wj7XV^1>x1nr`CKRIyjDPO%lwPh^Po#Ibu~ z+4Y*C60|OS2>Rs1Urv4U;jc(o-Vf7Xa{am+usB?nf?<{2SyUI7yGnptt0@6*O>O^$ z#s{=P=s>B2J$&Q0Y(q#=$fRs+zpE5e_W~CJxwj&u2(U#=8hBXj=?CwyP;ybPD$yGb zL}!84)r8zSd)c{q?($3Lud>NCFWP=T5!mi47nc@hmAWC1eEbl19G;AOP$QtcH! zjG7`Jfjx|-!>@$5m!RPbP_~eb7a*Ck(7R|xVIYE<=lXQCr#d^GWi57wee~Xg51Q;a zn>=;mqxY&0K4>|S6zkKEFkXe!cKvA0O)b(>jqJA*C~qLnBgQaxt*y5;M{65Mv7;;6 zeSPu1AuY6|_PXmgMAb~KSokZdPLdYxYmzR^zh9kM3+rMg5?qFB5zZ!_nGZSAwFV3! z^!P%Ucwuk?YvW!m@z%wB7+`b$Ow?L#_`XOS*xC&Ao(Ly1fh3gEQ7bJu4z1jA_F$fv z7E{p6`o_Kibxn6V$r3u$Xjh#;`R*=ht=TSVf3kpLRaaxGh8#jk<<*w5WEbjE8`}}d zUSwmEgxSXjXIJg1-b@Qpz=VPw3m{c1H6KN$t=+^TCIB6jJ3wWW_T%eNjSh1#7W4D_ z=UG|Akk0BxkbiP@g+|1owF78U>N%np^o@zZ1!i3q@9UCQQZ12I1Gg?5CoMZpJ>(6a z@)gHV5qC3F;h%OI^v(v&3i&>CZ^DruHJVT@S&OjTb) z)j*()%^}9ukPO_Sak@d=Ml9f_6cl>WO`3S(_Sm>jBj~kT;EvHPJE-B_jq*LXS0z3M zMea>285S*t@ipN$wz0w-oN$#+=ZG7#*8ovd42X6E*T2rfB0R9M(MUqOyx13|9dLu?)n649VP@@BwK#;))m@*8~lkR$@tX^R-*Y zPu;-9M&<_Y^*TP!@ub_1a@I}_3?0~Q=N1i|r0>tz;^fIG_P09@!1ckOsu>#opl>@F z3DAHI5|GbBa)L|_BvGy>sV>}DiBfAgCs*U144#lA1W*aUOQ|Fg^$3_9XLD0y?5);a zTG1?27O#8ZiOA)W&>ngj)Cp)N*C?9=9$?I{{Ej zpmo2sU<*%OKG=1s^*sfB?BScgU?&wvPq9YqvQwD*8?0W>TmY<|#SM2|*;cleN4PNv zrMX9+-~ZR&7nXl|HZ^#X4s{ismU~OB5omS-fi}UOLZf$($>1>2?}5PD`l`Y4T{TvX zEg}OmtLG@M>Z-;#&9#;hKF|Tcz4O-?lP&6NGTK8e!7d zRXvwkKwXiWN`49XAvjfqdQ!&JYV+HftxSQ1#J7WdrMR9{MMMKx7BqXHT(k5KX@>Sgle zbRXy`ydy}QL5vA~3;F?LOfMN_eV;y}m%o^>D#j>r)!u#x>QNOzacTluQWMy*gPQ5o z)G0lOOrqU`-rJpXF;F(i^~|+oag%hbv}7%=o|r%K>BHv_omJ|(S}n*gLxg0MC?tR}4Y?aV2H3BN3bHx1lfNQE zoEMkcYCl`LPGvY8H_cIPlfD$KQB`}>R^P&YZPQF(1vg7=^rA1d+|gAXY~>*G=wr2^ zgRHF$TimqUS&vfNpxy_x%>rB-`(AlVYZr39HIrTUDjc?s?{MA*8CwQeS$C2Hke$04kvbV$U#A(_B4rN^w&z3srH7<>5hUo$q~&gFIe=aN^^ykvG=`iKUOqxOl)*7WH2zoYQ+**>dQ>{06My+;H! zg0oJbd>5#{jY5L=6W8A&^3?nCMq&cK&9-%ifZ7@;Ww325;w3_&0N2EAAR|9=n%SqN zZUFMPR#X)}tpS>%zax4}eDadh1>}e^y^boy3g~G}-#E0xl6%~wcu{XXos?*=fZG8W z2Sou&6{MR)S6tsRRLP>2tfc<^FdNTf_{G4ZH?6nfGMI?KD-T8Ge#46Mv+Fv@xHN#& zGbg7N`!8bi--U<_HR-J^&rtFrk`A3I7(Gk1|AWpV)Mwf-a&7G2sf~=)Y`Td!2l})! zf-^C?69|R64%MqD^t7@`gk)q|0X>1_kcXS|6Jyf`8G-0fwp6*eSxW4Cg|^);!2LoW zRVubiSxJ$N(P%;ZWlKWEF1X$gJ*x=OhJ)3A7yXpCjJ9bkLp%6#~kv1=jlPB z>Ty|vXgYw82@h#<2X0{y^{0?M9gCUX6@m}yz2{_r3)EYB4$GD2kfg>+6S0*V5x(n) zc-R~3ttU^0q~1nU@L#bj`Y4Eq85;+7o=t~`O!*y}rAtQDUH=CU$#2mZa*EPozV)Yb z5?T6>5o3mg-n7PULpOBmGs7^xFihh=hGl$i*yYd0D$f=r^~Mb*@=NWYQIA@gUg%1z jORReIZCn{AxmjuHh3p~zBDn=h1(sGSkC-F7$7cTvcHa(x diff --git a/pybullet_sim/__pycache__/trajectory_generator.cpython-310.pyc b/pybullet_sim/__pycache__/trajectory_generator.cpython-310.pyc deleted file mode 100644 index 20f221e526d0f6cd1a7eb662c113e7090f707aa0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4347 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 ZB6uAnO3k-zR4PSLF&8b Date: Sat, 3 Oct 2026 01:49:00 -0300 Subject: [PATCH 2/5] refactor(mujoco): reorganize package and fix walking engine bugs - Rename AnalyticalWalking -> analytical_walking, Utils -> kinematics, move config.py to the package root; drop the unimplemented USE_MPC flag - Simulation class, cross-platform keyboard via the viewer key_callback - Pose2D/FootPose dataclasses, WalkState enum, WalkTarget named tuple - Fix swing-foot landing: phase/time mix-up made the foot jump 13.7 mm in one tick, and the foot was commanded up to 10.9 mm below the floor - Fix request_stop() restarting the walk; add march_in_place() - Document the IK roll-axis offset approximation --- mujoco_sim/AnalyticalWalking/__init__.py | 0 mujoco_sim/AnalyticalWalking/config.py | 47 --- .../AnalyticalWalking/trajectory_generator.py | 155 ------- .../AnalyticalWalking/walking_engine.py | 184 --------- mujoco_sim/__init__.py | 1 + mujoco_sim/__main__.py | 4 +- mujoco_sim/analytical_walking/__init__.py | 5 + .../trajectory_generator.py | 203 +++++++++ .../analytical_walking/walking_engine.py | 199 +++++++++ mujoco_sim/config.py | 74 ++++ mujoco_sim/kinematics/__init__.py | 5 + mujoco_sim/{Utils => kinematics}/ik_solver.py | 80 ++-- mujoco_sim/sim.py | 391 ++++++++++-------- 13 files changed, 757 insertions(+), 591 deletions(-) delete mode 100644 mujoco_sim/AnalyticalWalking/__init__.py delete mode 100644 mujoco_sim/AnalyticalWalking/config.py delete mode 100644 mujoco_sim/AnalyticalWalking/trajectory_generator.py delete mode 100644 mujoco_sim/AnalyticalWalking/walking_engine.py create mode 100644 mujoco_sim/analytical_walking/__init__.py create mode 100644 mujoco_sim/analytical_walking/trajectory_generator.py create mode 100644 mujoco_sim/analytical_walking/walking_engine.py create mode 100644 mujoco_sim/config.py create mode 100644 mujoco_sim/kinematics/__init__.py rename mujoco_sim/{Utils => kinematics}/ik_solver.py (51%) diff --git a/mujoco_sim/AnalyticalWalking/__init__.py b/mujoco_sim/AnalyticalWalking/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/mujoco_sim/AnalyticalWalking/config.py b/mujoco_sim/AnalyticalWalking/config.py deleted file mode 100644 index 2599858..0000000 --- a/mujoco_sim/AnalyticalWalking/config.py +++ /dev/null @@ -1,47 +0,0 @@ -JOINT_SIGNS = { - # Junta Esquerda Direita - "hip_yaw": {"l": -1.0, "r": -1.0}, - "hip_roll": {"l": -1.0, "r": -1.0}, - "hip_pitch": {"l": 1.0, "r": -1.0}, - "knee": {"l": 1.0, "r": -1.0}, - "ank_pitch": {"l": -1.0, "r": 1.0}, - "ank_roll": {"l": 1.0, "r": 1.0}, -} - -PHYSICS = True -USE_MPC = True # True → usa MPCWalking; False → usa AnalyticalWalking - -# ----------------------------------------------------------------------------- -# GANHOS DOS ATUADORES DE POSIÇÃO -# ACTUATOR_KP — ganho proporcional [N·m/rad] -# ACTUATOR_KV — amortecimento derivativo [N·m·s/rad] -# ACTUATOR_FORCE_RANGE — limite de torque por junta [N·m] -# ----------------------------------------------------------------------------- - -ACTUATOR_KP = 120.0 -ACTUATOR_KV = 12.0 -ACTUATOR_FORCE_RANGE = 80.0 - -# ----------------------------------------------------------------------------- -# PARÂMETROS DO MOTOR DE CAMINHADA -# ----------------------------------------------------------------------------- - -WALK_PARAMS = { - "T": 0.4, # Período de um passo [s] — menor = mais rápido - "z_com": 0.22, # Altura do torso [m] - "z_step": 0.04, # Altura máxima do pé no ar [m] - "ds_ratio": 0.12, # Fração do passo em apoio duplo (0 a 0.5) - "y_sep": 0.054, # Separação lateral dos pés [m] -} - -ARM_POSE = { - "l_sho_pitch": -0.0, # ombro esquerdo pitch - "r_sho_pitch": 0.0, # ombro direito pitch - "l_sho_roll": 1.4, # ombro esquerdo roll - "r_sho_roll": -1.4, # ombro direito roll - "l_el": 0.0, # cotovelo esquerdo - "r_el": -0.0, # cotovelo direito -} - -def joint_name(side: str, key: str) -> str: - return f"{side}_{key}" diff --git a/mujoco_sim/AnalyticalWalking/trajectory_generator.py b/mujoco_sim/AnalyticalWalking/trajectory_generator.py deleted file mode 100644 index 25b04ee..0000000 --- a/mujoco_sim/AnalyticalWalking/trajectory_generator.py +++ /dev/null @@ -1,155 +0,0 @@ -""" -Trajectory generator for analytical bipedal walking. -Port of trajectory_generator.cpp (aurea_walk ROS2 package). - -All poses are dicts: {'position': np.ndarray(2,), 'yaw': float, 'is_left': bool (optional)} -""" - -import numpy as np - - -def _h_phi(phi: float, phi_b: float, phi_e: float) -> float: - """Cosine interpolation from 1→0 during single support phase.""" - if phi < phi_b: - return 1.0 - if phi >= phi_e: - return 0.0 - return 0.5 * (1.0 + np.cos(np.pi * (phi - phi_b) / (phi_e - phi_b))) - - -def _v_phi(phi: float, phi_b: float, phi_e: float) -> float: - """Smooth vertical profile for foot lift/land.""" - if phi < phi_b or phi >= phi_e: - return 0.0 - return 0.5 * (1.0 - np.cos(2.0 * np.pi * (phi - phi_b) / (phi_e - phi_b))) - - -def get_com_pose_at_time( - t: float, - T: float, - ds_ratio: float, - z_com: float, - g: float, - p_start: dict, - p_end: dict, - p_support: dict, -) -> tuple[np.ndarray, float]: - """ - Compute COM (x, y) position and yaw at time t within a step of period T. - Uses LIPM dynamics for lateral/forward motion, cosine blend for yaw. - """ - tb = (T * ds_ratio) / 2.0 - te = T - tb - phi = t / T - - h = _h_phi(phi, tb / T, te / T) - com_yaw = h * p_start["yaw"] + (1.0 - h) * p_end["yaw"] - - if z_com <= 0.0: - return np.array(p_start["position"], dtype=float), com_yaw - - lam = np.sqrt(g / z_com) - - m_d1 = (p_support["position"] - p_start["position"]) / tb if tb > 1e-6 else np.zeros(2) - m_d2 = (p_end["position"] - p_support["position"]) / (T - te) if (T - te) > 1e-6 else np.zeros(2) - - k_d1 = (1.0 / lam) * m_d1 * np.sinh(-lam * tb) - k_d2 = (1.0 / lam) * m_d2 * np.sinh(lam * (T - te)) - - exp_pos = np.exp(lam * T) - exp_neg = np.exp(-lam * T) - denom = exp_pos - exp_neg - if abs(denom) < 1e-6: - return np.array(p_start["position"], dtype=float), com_yaw - - c1 = (k_d2 - k_d1 * exp_neg) / denom - c2 = (k_d1 * exp_pos - k_d2) / denom - - if t < tb: - zmp = p_start["position"] + m_d1 * t - sinh_t = (1.0 / lam) * m_d1 * np.sinh(lam * (t - tb)) - com_pos = zmp + c1 * np.exp(lam * t) + c2 * np.exp(-lam * t) - sinh_t - elif t >= te: - zmp = p_support["position"] + m_d2 * (t - te) - sinh_t = (1.0 / lam) * m_d2 * np.sinh(lam * (t - te)) - com_pos = zmp + c1 * np.exp(lam * t) + c2 * np.exp(-lam * t) - sinh_t - else: - com_pos = p_support["position"] + c1 * np.exp(lam * t) + c2 * np.exp(-lam * t) - - return com_pos, com_yaw - - -def get_swing_foot_pose_at_time( - t: float, - T: float, - z_step: float, - ds_ratio: float, - p_start: dict, - p_end: dict, -) -> tuple[np.ndarray, float]: - - # Compute swing foot position (x, y, z) and yaw in world frame at time t. - - tb = (T * ds_ratio) / 2.0 - te = T - tb - phi = t / T - phi_b = tb / T - phi_e = te / T - - h = _h_phi(phi, phi_b, phi_e) - v = _v_phi(phi, phi_b, phi_e) - - pos_2d = h * p_start["position"] + (1.0 - h) * p_end["position"] - yaw = h * p_start["yaw"] + (1.0 - h) * p_end["yaw"] - - landing_phase_start = phi_e * 0.85 - if phi > landing_phase_start: - phase_phi = (phi - landing_phase_start) / (phi_e - landing_phase_start) - # Height at start of landing phase (faithful port of C++ code) - z_land_start = z_step * _v_phi(landing_phase_start * T, phi_b, phi_e) - z = (1.0 - phase_phi) * z_land_start - else: - z = z_step * v - - return np.array([pos_2d[0], pos_2d[1], z], dtype=float), yaw - - -def select_next_poses( - current_torso: dict, - swing_foot: dict, - v_cmd: dict, - T: float, - y_sep: float, -) -> tuple[dict, dict]: - - #Plan the next torso and swing-foot target poses given a velocity command. - - dx = v_cmd.get("vx", 0.0) * T - dy = v_cmd.get("vy", 0.0) * T - d_yaw = v_cmd.get("wz", 0.0) * T - - yaw = current_torso["yaw"] - c, s = np.cos(yaw), np.sin(yaw) - world_disp = np.array([c * dx - s * dy, s * dx + c * dy]) - - next_torso = { - "position": current_torso["position"] + world_disp, - "yaw": yaw + d_yaw, - } - - is_left = swing_foot.get("is_left", True) - foot_offset_y = y_sep if is_left else -y_sep - - ny = next_torso["yaw"] - cn, sn = np.cos(ny), np.sin(ny) - local_offset = np.array([v_cmd.get("vx", 0.0) * T / 2.0, foot_offset_y]) - world_offset = np.array([cn * local_offset[0] - sn * local_offset[1], - sn * local_offset[0] + cn * local_offset[1]]) - - next_swing = { - "position": next_torso["position"] + world_offset, - "yaw": ny, - "is_left": is_left, - } - - return next_torso, next_swing diff --git a/mujoco_sim/AnalyticalWalking/walking_engine.py b/mujoco_sim/AnalyticalWalking/walking_engine.py deleted file mode 100644 index c491ed9..0000000 --- a/mujoco_sim/AnalyticalWalking/walking_engine.py +++ /dev/null @@ -1,184 +0,0 @@ -""" -Analytical walking state machine for bipedal robots. - -Usage: - engine = WalkingEngine() - engine.set_command(vx=0.1) # start walking forward - body_pos, body_yaw, lp, ly, rp, ry = engine.step(dt) - # body_pos: np.ndarray(3,) – desired pelvis position in world frame - # body_yaw: float – desired pelvis yaw - # lp / rp: np.ndarray(3,) – left/right sole target in world frame - # ly / ry: float – left/right foot yaw -""" - -import numpy as np -from .trajectory_generator import ( - get_com_pose_at_time, - get_swing_foot_pose_at_time, - select_next_poses, -) - - -class WalkingEngine: - IDLE = 0 - WALKING = 1 - IDLE_MARCH = 2 - STOPPING = 3 - - def __init__( - self, - T: float = 0.5, - z_com: float = 0.22, - z_step: float = 0.04, - ds_ratio: float = 0.13, - y_sep: float = 0.054, - g: float = 9.81, - ): - self.T = T - self.z_com = z_com - self.z_step = z_step - self.ds_ratio = ds_ratio - self.y_sep = y_sep - self.g = g - - self.state = self.IDLE - self.t_step = 0.0 - - # Foot states (world frame 2-D positions) - self.left_foot = {"position": np.array([0.0, y_sep]), "yaw": 0.0, "is_left": True} - self.right_foot = {"position": np.array([0.0, -y_sep]), "yaw": 0.0, "is_left": False} - - self.support_foot = self.right_foot - self.swing_foot_ref = self.left_foot - - # Torso state (world frame) - self.torso = {"position": np.array([0.0, 0.0]), "yaw": 0.0} - - # Step plan (set by start_new_step) - self.torso_start = None - self.torso_target = None - self.swing_start = None - self.swing_target = None - - # Velocity command - self._cmd = {"vx": 0.0, "vy": 0.0, "wz": 0.0} - - def set_command(self, vx: float = 0.0, vy: float = 0.0, wz: float = 0.0) -> None: - self._cmd = {"vx": vx, "vy": vy, "wz": wz} - - def request_stop(self) -> None: - if self.state in (self.WALKING, self.IDLE_MARCH): - self.state = self.STOPPING - - def step(self, dt: float) -> tuple: - """ - Advance the engine by dt seconds and return the desired kinematic state. - - Returns: - (body_pos, body_yaw, left_sole, left_yaw, right_sole, right_yaw) - body_pos – np.ndarray(3,): desired pelvis world position [x, y, z_com] - body_yaw – float: desired pelvis yaw - left_sole – np.ndarray(3,): desired left foot sole world position - left_yaw – float: desired left foot yaw - right_sole – np.ndarray(3,): desired right foot sole world position - right_yaw – float: desired right foot yaw - """ - self._update_state() - - if self.state == self.IDLE: - return self._standing_pose() - - self.t_step += dt - - com_2d, com_yaw = get_com_pose_at_time( - self.t_step, self.T, self.ds_ratio, self.z_com, self.g, - self.torso_start, self.torso_target, self.support_foot, - ) - - swing_3d, swing_yaw = get_swing_foot_pose_at_time( - self.t_step, self.T, self.z_step, self.ds_ratio, - self.swing_start, self.swing_target, - ) - - body_pos = np.array([com_2d[0], com_2d[1], self.z_com]) - sup_pos = np.array([self.support_foot["position"][0], - self.support_foot["position"][1], 0.0]) - - if self.swing_foot_ref["is_left"]: - left_pos, left_yaw = swing_3d, swing_yaw - right_pos, right_yaw = sup_pos, self.support_foot["yaw"] - else: - right_pos, right_yaw = swing_3d, swing_yaw - left_pos, left_yaw = sup_pos, self.support_foot["yaw"] - - if self.t_step >= self.T: - self._end_step() - - return body_pos, com_yaw, left_pos, left_yaw, right_pos, right_yaw - - def _update_state(self) -> None: - should_walk = (abs(self._cmd["vx"]) > 0.01 or - abs(self._cmd["vy"]) > 0.01 or - abs(self._cmd["wz"]) > 0.01) - - prev = self.state - - if self.state == self.STOPPING: - pass # state changes happen in _end_step - elif self.state == self.IDLE: - if should_walk: - self.state = self.WALKING - elif self.state in (self.WALKING, self.IDLE_MARCH): - self.state = self.WALKING if should_walk else self.IDLE_MARCH - - if prev == self.IDLE and self.state != self.IDLE: - self._start_new_step() - - def _start_new_step(self) -> None: - self.t_step = 0.0 - - # Swap support / swing - if self.support_foot["is_left"]: - self.support_foot = self.right_foot - self.swing_foot_ref = self.left_foot - else: - self.support_foot = self.left_foot - self.swing_foot_ref = self.right_foot - - cmd = self._cmd if self.state == self.WALKING else {"vx": 0.0, "vy": 0.0, "wz": 0.0} - - self.torso_target, self.swing_target = select_next_poses( - self.torso, self.swing_foot_ref, cmd, self.T, self.y_sep - ) - self.torso_start = { - "position": self.torso["position"].copy(), - "yaw": self.torso["yaw"], - } - self.swing_start = { - "position": self.swing_foot_ref["position"].copy(), - "yaw": self.swing_foot_ref["yaw"], - "is_left": self.swing_foot_ref["is_left"], - } - - def _end_step(self) -> None: - self.torso["position"] = self.torso_target["position"].copy() - self.torso["yaw"] = self.torso_target["yaw"] - self.swing_foot_ref["position"] = self.swing_target["position"].copy() - self.swing_foot_ref["yaw"] = self.swing_target["yaw"] - - if self.state == self.STOPPING: - self.state = self.IDLE - else: - self._start_new_step() - - def _standing_pose(self) -> tuple: - body_pos = np.array([self.torso["position"][0], - self.torso["position"][1], self.z_com]) - body_yaw = self.torso["yaw"] - left_pos = np.array([self.left_foot["position"][0], - self.left_foot["position"][1], 0.0]) - right_pos = np.array([self.right_foot["position"][0], - self.right_foot["position"][1], 0.0]) - return (body_pos, body_yaw, - left_pos, self.left_foot["yaw"], - right_pos, self.right_foot["yaw"]) diff --git a/mujoco_sim/__init__.py b/mujoco_sim/__init__.py index e69de29..ea8ec66 100644 --- a/mujoco_sim/__init__.py +++ b/mujoco_sim/__init__.py @@ -0,0 +1 @@ +"""Robotis OP3 walking simulation in MuJoCo. Run with: python -m mujoco_sim""" diff --git a/mujoco_sim/__main__.py b/mujoco_sim/__main__.py index cd03d8a..2ccf3cf 100644 --- a/mujoco_sim/__main__.py +++ b/mujoco_sim/__main__.py @@ -1,2 +1,4 @@ from .sim import main -main() + +if __name__ == "__main__": + main() diff --git a/mujoco_sim/analytical_walking/__init__.py b/mujoco_sim/analytical_walking/__init__.py new file mode 100644 index 0000000..b8e2f06 --- /dev/null +++ b/mujoco_sim/analytical_walking/__init__.py @@ -0,0 +1,5 @@ +"""Analytical (LIPM-based) walking pattern generator.""" + +from .walking_engine import WalkingEngine, WalkState, WalkTarget + +__all__ = ["WalkingEngine", "WalkState", "WalkTarget"] diff --git a/mujoco_sim/analytical_walking/trajectory_generator.py b/mujoco_sim/analytical_walking/trajectory_generator.py new file mode 100644 index 0000000..7151dce --- /dev/null +++ b/mujoco_sim/analytical_walking/trajectory_generator.py @@ -0,0 +1,203 @@ +""" +Trajectory generator for analytical bipedal walking. + +Port of trajectory_generator.cpp from the aurea_walk ROS2 package, with two +fixes to the swing-foot landing profile (see get_swing_foot_pose_at_time). + +Conventions +----------- +- All positions are in the world frame, in metres. Yaw is in radians. +- A step lasts T seconds and is split into three phases: + + |-- double support --|------ single support ------|-- double support --| + 0 tb te T + + with tb = T * ds_ratio / 2 and te = T - tb. +- phi = t / T is the normalised phase of the step (0 → 1). +""" + +from dataclasses import dataclass, field + +import numpy as np + + +@dataclass +class Pose2D: + """Planar pose on the ground: (x, y) position and yaw.""" + + position: np.ndarray = field(default_factory=lambda: np.zeros(2)) + yaw: float = 0.0 + + def copy(self) -> "Pose2D": + return Pose2D(self.position.copy(), self.yaw) + + +@dataclass +class FootPose(Pose2D): + """Planar pose of a foot, tagged with which leg it belongs to.""" + + is_left: bool = True + + def copy(self) -> "FootPose": + return FootPose(self.position.copy(), self.yaw, self.is_left) + + +def _h_phi(phi: float, phi_b: float, phi_e: float) -> float: + """Horizontal blend: 1 before phi_b, 0 after phi_e, cosine in between.""" + if phi < phi_b: + return 1.0 + if phi >= phi_e: + return 0.0 + return 0.5 * (1.0 + np.cos(np.pi * (phi - phi_b) / (phi_e - phi_b))) + + +def _v_phi(phi: float, phi_b: float, phi_e: float) -> float: + """Vertical bump: 0 outside [phi_b, phi_e], peaks at 1 in the middle.""" + if phi < phi_b or phi >= phi_e: + return 0.0 + return 0.5 * (1.0 - np.cos(2.0 * np.pi * (phi - phi_b) / (phi_e - phi_b))) + + +def get_com_pose_at_time( + t: float, + T: float, + ds_ratio: float, + z_com: float, + g: float, + p_start: Pose2D, + p_end: Pose2D, + p_support: Pose2D, +) -> tuple[np.ndarray, float]: + """ + CoM (x, y) position and yaw at time t within a step. + + The ZMP reference is piecewise linear: it moves from p_start to the support + foot during the first double support, stays on the support foot during + single support, and moves to p_end during the last double support. The CoM + is the closed-form solution of the Linear Inverted Pendulum Model (LIPM) + + x_ddot = lambda^2 * (x - zmp), lambda = sqrt(g / z_com) + + with boundary conditions x(0) = p_start and x(T) = p_end. + """ + tb = (T * ds_ratio) / 2.0 + te = T - tb + phi = t / T + + h = _h_phi(phi, tb / T, te / T) + com_yaw = h * p_start.yaw + (1.0 - h) * p_end.yaw + + if z_com <= 0.0: + return np.array(p_start.position, dtype=float), com_yaw + + lam = np.sqrt(g / z_com) + + # ZMP slopes during the two double-support phases + m_d1 = (p_support.position - p_start.position) / tb if tb > 1e-6 else np.zeros(2) + m_d2 = (p_end.position - p_support.position) / (T - te) if (T - te) > 1e-6 else np.zeros(2) + + k_d1 = (1.0 / lam) * m_d1 * np.sinh(-lam * tb) + k_d2 = (1.0 / lam) * m_d2 * np.sinh(lam * (T - te)) + + exp_pos = np.exp(lam * T) + exp_neg = np.exp(-lam * T) + denom = exp_pos - exp_neg + if abs(denom) < 1e-6: + return np.array(p_start.position, dtype=float), com_yaw + + # Homogeneous-solution coefficients that satisfy the boundary conditions + c1 = (k_d2 - k_d1 * exp_neg) / denom + c2 = (k_d1 * exp_pos - k_d2) / denom + + if t < tb: + zmp = p_start.position + m_d1 * t + sinh_t = (1.0 / lam) * m_d1 * np.sinh(lam * (t - tb)) + com_pos = zmp + c1 * np.exp(lam * t) + c2 * np.exp(-lam * t) - sinh_t + elif t >= te: + zmp = p_support.position + m_d2 * (t - te) + sinh_t = (1.0 / lam) * m_d2 * np.sinh(lam * (t - te)) + com_pos = zmp + c1 * np.exp(lam * t) + c2 * np.exp(-lam * t) - sinh_t + else: + com_pos = p_support.position + c1 * np.exp(lam * t) + c2 * np.exp(-lam * t) + + return com_pos, com_yaw + + +def get_swing_foot_pose_at_time( + t: float, + T: float, + z_step: float, + ds_ratio: float, + p_start: Pose2D, + p_end: Pose2D, +) -> tuple[np.ndarray, float]: + """ + Swing foot (x, y, z) position and yaw at time t within a step. + + The foot follows a cosine blend horizontally and a cosine bump vertically. + Near the end of single support the vertical bump is replaced by a linear + descent so the foot lands with a constant, controlled vertical speed. + """ + tb = (T * ds_ratio) / 2.0 + te = T - tb + phi = t / T + phi_b = tb / T + phi_e = te / T + + h = _h_phi(phi, phi_b, phi_e) + v = _v_phi(phi, phi_b, phi_e) + + pos_2d = h * p_start.position + (1.0 - h) * p_end.position + yaw = h * p_start.yaw + (1.0 - h) * p_end.yaw + + landing_phase_start = phi_e * 0.85 + if phi > landing_phase_start: + # Fix 1: the C++ original passed a time (landing_phase_start * T) where + # _v_phi expects a phase, so the foot jumped up when landing started. + z_land_start = z_step * _v_phi(landing_phase_start, phi_b, phi_e) + # Fix 2: clamp so the foot stays on the ground (z = 0) during the final + # double support instead of being commanded below the floor. + landing_progress = min( + (phi - landing_phase_start) / (phi_e - landing_phase_start), 1.0 + ) + z = (1.0 - landing_progress) * z_land_start + else: + z = z_step * v + + return np.array([pos_2d[0], pos_2d[1], z], dtype=float), yaw + + +def select_next_poses( + current_torso: Pose2D, + swing_foot: FootPose, + v_cmd: dict, + T: float, + y_sep: float, +) -> tuple[Pose2D, FootPose]: + """ + Plan the next torso pose and swing-foot landing pose. + + The torso moves by (vx, vy, wz) * T, expressed in its own frame. The swing + foot lands y_sep to the side of the new torso pose and half a stride ahead. + """ + dx = v_cmd.get("vx", 0.0) * T + dy = v_cmd.get("vy", 0.0) * T + d_yaw = v_cmd.get("wz", 0.0) * T + + yaw = current_torso.yaw + c, s = np.cos(yaw), np.sin(yaw) + world_disp = np.array([c * dx - s * dy, s * dx + c * dy]) + + next_torso = Pose2D(current_torso.position + world_disp, yaw + d_yaw) + + foot_offset_y = y_sep if swing_foot.is_left else -y_sep + + ny = next_torso.yaw + cn, sn = np.cos(ny), np.sin(ny) + local_offset = np.array([v_cmd.get("vx", 0.0) * T / 2.0, foot_offset_y]) + world_offset = np.array([cn * local_offset[0] - sn * local_offset[1], + sn * local_offset[0] + cn * local_offset[1]]) + + next_swing = FootPose(next_torso.position + world_offset, ny, swing_foot.is_left) + + return next_torso, next_swing diff --git a/mujoco_sim/analytical_walking/walking_engine.py b/mujoco_sim/analytical_walking/walking_engine.py new file mode 100644 index 0000000..ccef1a8 --- /dev/null +++ b/mujoco_sim/analytical_walking/walking_engine.py @@ -0,0 +1,199 @@ +""" +Analytical walking state machine for bipedal robots. + +Usage: + engine = WalkingEngine() + engine.set_command(vx=0.1) # start walking forward + target = engine.step(dt) + target.body_pos # np.ndarray(3,) – desired pelvis position in world frame + target.body_yaw # float – desired pelvis yaw + target.left_sole # np.ndarray(3,) – left sole target in world frame + target.left_yaw # float – left foot yaw + target.right_sole / target.right_yaw + +State machine: + + IDLE ──set_command(≠0)──▶ WALKING ◀──set_command(≠0 / 0)──▶ IDLE_MARCH + │ │ ▲ + └──────────────────────────┼────── march_in_place() ──────────┘ + ▲ ▼ │ + └──(step ends)──── STOPPING ◀──────── request_stop() ─────────┘ +""" + +from enum import Enum, auto +from typing import NamedTuple + +import numpy as np + +from .trajectory_generator import ( + FootPose, + Pose2D, + get_com_pose_at_time, + get_swing_foot_pose_at_time, + select_next_poses, +) + + +class WalkState(Enum): + IDLE = auto() # standing still, both feet on the ground + WALKING = auto() # stepping according to the velocity command + IDLE_MARCH = auto() # stepping in place (command is zero) + STOPPING = auto() # finishing the current step, then IDLE + + +class WalkTarget(NamedTuple): + """Desired kinematic state for one control tick (all in world frame).""" + + body_pos: np.ndarray + body_yaw: float + left_sole: np.ndarray + left_yaw: float + right_sole: np.ndarray + right_yaw: float + + +class WalkingEngine: + def __init__( + self, + T: float = 0.5, + z_com: float = 0.22, + z_step: float = 0.04, + ds_ratio: float = 0.13, + y_sep: float = 0.054, + g: float = 9.81, + ): + self.T = T + self.z_com = z_com + self.z_step = z_step + self.ds_ratio = ds_ratio + self.y_sep = y_sep + self.g = g + + self.state = WalkState.IDLE + self.t_step = 0.0 + + self.left_foot = FootPose(np.array([0.0, y_sep]), 0.0, is_left=True) + self.right_foot = FootPose(np.array([0.0, -y_sep]), 0.0, is_left=False) + + # References to left_foot / right_foot; swapped at every step + self.support_foot = self.right_foot + self.swing_foot = self.left_foot + + self.torso = Pose2D(np.array([0.0, 0.0]), 0.0) + + # Current step plan (set by _start_new_step) + self.torso_start: Pose2D | None = None + self.torso_target: Pose2D | None = None + self.swing_start: FootPose | None = None + self.swing_target: FootPose | None = None + + self._cmd = {"vx": 0.0, "vy": 0.0, "wz": 0.0} + + def set_command(self, vx: float = 0.0, vy: float = 0.0, wz: float = 0.0) -> None: + """Set the velocity command [m/s, m/s, rad/s] in the torso frame.""" + self._cmd = {"vx": vx, "vy": vy, "wz": wz} + + def march_in_place(self) -> None: + """Keep stepping without moving (also starts stepping from IDLE).""" + self._cmd = {"vx": 0.0, "vy": 0.0, "wz": 0.0} + if self.state == WalkState.IDLE: + self.state = WalkState.IDLE_MARCH + self._start_new_step() + elif self.state == WalkState.STOPPING: + self.state = WalkState.IDLE_MARCH + + def request_stop(self) -> None: + """Finish the current step and go back to IDLE.""" + # Clear the command, otherwise IDLE would switch straight back to WALKING + self._cmd = {"vx": 0.0, "vy": 0.0, "wz": 0.0} + if self.state in (WalkState.WALKING, WalkState.IDLE_MARCH): + self.state = WalkState.STOPPING + + def step(self, dt: float) -> WalkTarget: + """Advance the engine by dt seconds and return the desired kinematic state.""" + self._update_state() + + if self.state == WalkState.IDLE: + return self.standing_pose() + + self.t_step += dt + + com_2d, com_yaw = get_com_pose_at_time( + self.t_step, self.T, self.ds_ratio, self.z_com, self.g, + self.torso_start, self.torso_target, self.support_foot, + ) + + swing_3d, swing_yaw = get_swing_foot_pose_at_time( + self.t_step, self.T, self.z_step, self.ds_ratio, + self.swing_start, self.swing_target, + ) + + body_pos = np.array([com_2d[0], com_2d[1], self.z_com]) + sup_pos = np.array([*self.support_foot.position, 0.0]) + + if self.swing_foot.is_left: + left_pos, left_yaw = swing_3d, swing_yaw + right_pos, right_yaw = sup_pos, self.support_foot.yaw + else: + right_pos, right_yaw = swing_3d, swing_yaw + left_pos, left_yaw = sup_pos, self.support_foot.yaw + + if self.t_step >= self.T: + self._end_step() + + return WalkTarget(body_pos, com_yaw, left_pos, left_yaw, right_pos, right_yaw) + + def standing_pose(self) -> WalkTarget: + """Kinematic state with both feet on the ground under the current torso.""" + return WalkTarget( + body_pos=np.array([*self.torso.position, self.z_com]), + body_yaw=self.torso.yaw, + left_sole=np.array([*self.left_foot.position, 0.0]), + left_yaw=self.left_foot.yaw, + right_sole=np.array([*self.right_foot.position, 0.0]), + right_yaw=self.right_foot.yaw, + ) + + def _update_state(self) -> None: + should_walk = any(abs(v) > 0.01 for v in self._cmd.values()) + + prev = self.state + + if self.state == WalkState.STOPPING: + pass # the transition to IDLE happens in _end_step + elif self.state == WalkState.IDLE: + if should_walk: + self.state = WalkState.WALKING + elif self.state in (WalkState.WALKING, WalkState.IDLE_MARCH): + self.state = WalkState.WALKING if should_walk else WalkState.IDLE_MARCH + + if prev == WalkState.IDLE and self.state != WalkState.IDLE: + self._start_new_step() + + def _start_new_step(self) -> None: + self.t_step = 0.0 + + # Swap support / swing + if self.support_foot.is_left: + self.support_foot, self.swing_foot = self.right_foot, self.left_foot + else: + self.support_foot, self.swing_foot = self.left_foot, self.right_foot + + cmd = self._cmd if self.state == WalkState.WALKING else {"vx": 0.0, "vy": 0.0, "wz": 0.0} + + self.torso_target, self.swing_target = select_next_poses( + self.torso, self.swing_foot, cmd, self.T, self.y_sep + ) + self.torso_start = self.torso.copy() + self.swing_start = self.swing_foot.copy() + + def _end_step(self) -> None: + self.torso.position = self.torso_target.position.copy() + self.torso.yaw = self.torso_target.yaw + self.swing_foot.position = self.swing_target.position.copy() + self.swing_foot.yaw = self.swing_target.yaw + + if self.state == WalkState.STOPPING: + self.state = WalkState.IDLE + else: + self._start_new_step() diff --git a/mujoco_sim/config.py b/mujoco_sim/config.py new file mode 100644 index 0000000..1f7a3ae --- /dev/null +++ b/mujoco_sim/config.py @@ -0,0 +1,74 @@ +""" +Configuration for the MuJoCo OP3 simulation. + +Edit the values here to change how the robot is simulated and how it walks. +""" + +# ----------------------------------------------------------------------------- +# SIMULATION MODE +# True → mj_step: full physics (contacts, gravity, PD actuators) +# False → mj_forward: kinematics only; joints are set directly, no contacts +# ----------------------------------------------------------------------------- + +PHYSICS = True + +# ----------------------------------------------------------------------------- +# POSITION ACTUATOR GAINS (applied to every joint) +# ACTUATOR_KP — proportional gain [N·m/rad] +# ACTUATOR_KV — derivative (damping) gain [N·m·s/rad] +# ACTUATOR_FORCE_RANGE — torque limit per joint [N·m] +# ----------------------------------------------------------------------------- + +ACTUATOR_KP = 120.0 +ACTUATOR_KV = 12.0 +ACTUATOR_FORCE_RANGE = 80.0 + +# ----------------------------------------------------------------------------- +# WALKING ENGINE PARAMETERS +# ----------------------------------------------------------------------------- + +WALK_PARAMS = { + "T": 0.4, # Step period [s] — smaller = faster steps + "z_com": 0.22, # Torso height [m] + "z_step": 0.04, # Maximum swing-foot height [m] + "ds_ratio": 0.12, # Fraction of the step in double support (0 to 0.5) + "y_sep": 0.054, # Lateral distance from torso centre to each foot [m] +} + +# Velocity commands bound to the keyboard keys (see sim.py) +KEY_COMMANDS = { + "forward": {"vx": 0.05}, # [m/s] + "lateral": {"vy": 0.03}, # [m/s] + "turn": {"wz": 0.5}, # [rad/s] +} + +# ----------------------------------------------------------------------------- +# ROBOT-SPECIFIC MAPPING +# ----------------------------------------------------------------------------- + +# Sign that converts the IK's geometric angle convention into the joint axes +# defined in the OP3 MJCF (some axes point in the opposite direction). +JOINT_SIGNS = { + # Joint Left Right + "hip_yaw": {"l": -1.0, "r": -1.0}, + "hip_roll": {"l": -1.0, "r": -1.0}, + "hip_pitch": {"l": 1.0, "r": -1.0}, + "knee": {"l": 1.0, "r": -1.0}, + "ank_pitch": {"l": -1.0, "r": 1.0}, + "ank_roll": {"l": 1.0, "r": 1.0}, +} + +# Fixed arm pose [rad] held during walking +ARM_POSE = { + "l_sho_pitch": 0.0, + "r_sho_pitch": 0.0, + "l_sho_roll": 1.4, + "r_sho_roll": -1.4, + "l_el": 0.0, + "r_el": 0.0, +} + + +def joint_name(side: str, key: str) -> str: + """MJCF joint name, e.g. joint_name("l", "knee") → "l_knee".""" + return f"{side}_{key}" diff --git a/mujoco_sim/kinematics/__init__.py b/mujoco_sim/kinematics/__init__.py new file mode 100644 index 0000000..c3d0b5b --- /dev/null +++ b/mujoco_sim/kinematics/__init__.py @@ -0,0 +1,5 @@ +"""Leg kinematics for the OP3.""" + +from .ik_solver import IKSolver + +__all__ = ["IKSolver"] diff --git a/mujoco_sim/Utils/ik_solver.py b/mujoco_sim/kinematics/ik_solver.py similarity index 51% rename from mujoco_sim/Utils/ik_solver.py rename to mujoco_sim/kinematics/ik_solver.py index 67b2ab3..baab0b6 100644 --- a/mujoco_sim/Utils/ik_solver.py +++ b/mujoco_sim/kinematics/ik_solver.py @@ -6,6 +6,13 @@ Constants measured from the robot_descriptions OP3 MJCF (mujoco_menagerie / op3_mj_description). + +Approximation: the solver treats the hip_roll and ank_roll axes as passing +through the hip_pitch / ank_pitch points. On the OP3 they are offset by +[-0.024, ∓0.019] m, so the foot lands off target whenever the roll angles +are non-zero. With the lateral CoM sway of the default gait the ankle error +reaches ~4-5 mm (measured against MuJoCo forward kinematics). Modelling the +offsets exactly would remove it; it is a good exercise. """ import numpy as np @@ -21,8 +28,10 @@ L_COXA = 0.1102 # hip_pitch → knee (vertical) L_TIBIA = 0.1100 # knee → ank_pitch (vertical) -# Distance from ank_roll joint to foot sole (from contact box geoms, z-half = 0.004, -# centre at z = -0.0265 → bottom = -0.0265 - 0.004 = -0.0305) +# Distance from the ankle point to the foot sole. The "ankle point" is the +# ank_pitch joint (the ank_roll axis sits at the same height, offset by +# [-0.0241, ∓0.019], mirroring the hip). Foot contact boxes: z-half = 0.004, +# centre 0.0266 below ank_pitch → sole 0.0305 below it. D_ANKLE_SOLE = 0.0305 # ───────────────────────────────────────────────────────────────────────────── @@ -68,51 +77,46 @@ def solve( R1 = R_body R7 = R_foot_target - # Ank_roll joint position from sole target (sole is D_ANKLE_SOLE below ank_roll) + # Ankle point (ank_pitch joint) from the sole target p7_sole = p_foot_target p7 = p7_sole - R7 @ np.array([0.0, 0.0, -D_ANKLE_SOLE]) - try: - sign = 1.0 if leg == "left" else -1.0 - hip_offset = np.array([0.0, HIP_Y * sign, HIP_Z]) - p2 = p_body + R1 @ hip_offset # hip_pitch joint in world frame - - # Vector from ankle to hip expressed in foot frame - r_vec = R7.T @ (p2 - p7) - rx, ry, rz = r_vec + sign = 1.0 if leg == "left" else -1.0 + hip_offset = np.array([0.0, HIP_Y * sign, HIP_Z]) + p2 = p_body + R1 @ hip_offset # hip_pitch joint in world frame - C = float(np.linalg.norm(r_vec)) - if C > L_COXA + L_TIBIA: - return False, [] + # Vector from ankle to hip expressed in foot frame + r_vec = R7.T @ (p2 - p7) + rx, ry, rz = r_vec - # ── Knee angle (q5) ─────────────────────────────────────────────── - cos_q5 = (C**2 - L_COXA**2 - L_TIBIA**2) / (2.0 * L_COXA * L_TIBIA) - cos_q5 = float(np.clip(cos_q5, -1.0, 1.0)) - q5 = np.arccos(cos_q5) + C = float(np.linalg.norm(r_vec)) + if C > L_COXA + L_TIBIA or C < 1e-9: # out of reach / degenerate + return False, [] - # ── Ankle roll (q7) ─────────────────────────────────────────────── - q7 = np.arctan2(ry, rz) + # ── Knee angle (q5) ─────────────────────────────────────────────── + cos_q5 = (C**2 - L_COXA**2 - L_TIBIA**2) / (2.0 * L_COXA * L_TIBIA) + cos_q5 = float(np.clip(cos_q5, -1.0, 1.0)) + q5 = np.arccos(cos_q5) - # ── Ankle pitch (q6) ────────────────────────────────────────────── - sin_alpha = float(np.clip((L_COXA / C) * np.sin(q5), -1.0, 1.0)) - alpha = np.arcsin(sin_alpha) - q6 = -np.arctan2(rx, np.copysign(1.0, rz) * np.sqrt(ry**2 + rz**2)) - alpha + # ── Ankle roll (q7) ─────────────────────────────────────────────── + q7 = np.arctan2(ry, rz) - # ── Hip angles from remaining rotation ──────────────────────────── - R_q7_inv = _rot_x(-q7) - R_q56_inv = _rot_y(-q5 - q6) - R_target = R1.T @ R7 @ R_q7_inv @ R_q56_inv + # ── Ankle pitch (q6) ────────────────────────────────────────────── + sin_alpha = float(np.clip((L_COXA / C) * np.sin(q5), -1.0, 1.0)) + alpha = np.arcsin(sin_alpha) + q6 = -np.arctan2(rx, np.copysign(1.0, rz) * np.sqrt(ry**2 + rz**2)) - alpha - R12, R22 = R_target[0, 1], R_target[1, 1] - R31, R32, R33 = R_target[2, 0], R_target[2, 1], R_target[2, 2] + # ── Hip angles from remaining rotation ──────────────────────────── + R_q7_inv = _rot_x(-q7) + R_q56_inv = _rot_y(-q5 - q6) + R_target = R1.T @ R7 @ R_q7_inv @ R_q56_inv - q2 = np.arctan2(-R12, R22) # hip_yaw - s2, c2 = np.sin(q2), np.cos(q2) - q3 = np.arctan2(R32, -R12 * s2 + R22 * c2) # hip_roll - q4 = np.arctan2(-R31, R33) # hip_pitch + R12, R22 = R_target[0, 1], R_target[1, 1] + R31, R32, R33 = R_target[2, 0], R_target[2, 1], R_target[2, 2] - return True, [q2, q3, q4, q5, q6, q7] + q2 = np.arctan2(-R12, R22) # hip_yaw + s2, c2 = np.sin(q2), np.cos(q2) + q3 = np.arctan2(R32, -R12 * s2 + R22 * c2) # hip_roll + q4 = np.arctan2(-R31, R33) # hip_pitch - except Exception as exc: - print(f"[IKSolver] error ({leg}): {exc}") - return False, [] + return True, [q2, q3, q4, q5, q6, q7] diff --git a/mujoco_sim/sim.py b/mujoco_sim/sim.py index 557aecd..ac4bd2d 100644 --- a/mujoco_sim/sim.py +++ b/mujoco_sim/sim.py @@ -1,26 +1,37 @@ -# Simulação do robô Robotis OP3 via MuJoCo -# Pedro H. Peres +""" +Robotis OP3 walking simulation in MuJoCo. + +Pipeline per control tick: + + WalkingEngine.step() → desired pelvis + sole poses (world frame) + IKSolver.solve() → 6 joint angles per leg + data.ctrl → PD position actuators → mj_step + +Run from the repository root: + + python -m mujoco_sim + +Author: Pedro H. Peres +""" -import os -import sys import time -import msvcrt -import numpy as np +from collections import deque + import mujoco import mujoco.viewer +import numpy as np -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -os.chdir(os.path.join(os.path.dirname(__file__), "..")) - -from mujoco_sim.Utils.ik_solver import IKSolver -from mujoco_sim.AnalyticalWalking.walking_engine import WalkingEngine -from mujoco_sim.AnalyticalWalking.config import ( - JOINT_SIGNS, WALK_PARAMS, ARM_POSE, PHYSICS, USE_MPC, - ACTUATOR_KP, ACTUATOR_KV, ACTUATOR_FORCE_RANGE, - joint_name as _joint_name, +from .analytical_walking.walking_engine import WalkingEngine, WalkTarget +from .config import ( + ACTUATOR_FORCE_RANGE, ACTUATOR_KP, ACTUATOR_KV, ARM_POSE, JOINT_SIGNS, + KEY_COMMANDS, PHYSICS, WALK_PARAMS, joint_name, ) +from .kinematics.ik_solver import IKSolver +# Order of the angles returned by IKSolver.solve _IK_KEYS = ["hip_yaw", "hip_roll", "hip_pitch", "knee", "ank_pitch", "ank_roll"] +_LEGS = (("left", "l"), ("right", "r")) + def _rot_z(yaw: float) -> np.ndarray: c, s = np.cos(yaw), np.sin(yaw) @@ -28,68 +39,75 @@ def _rot_z(yaw: float) -> np.ndarray: def _yaw_to_quat(yaw: float) -> np.ndarray: + """Quaternion (w, x, y, z) for a rotation of `yaw` about the world z axis.""" return np.array([np.cos(yaw / 2.0), 0.0, 0.0, np.sin(yaw / 2.0)]) + def build_model() -> mujoco.MjModel: + """Load the OP3 MJCF, add a floor and tune solver, contacts and actuators.""" from robot_descriptions import op3_mj_description spec = mujoco.MjSpec.from_file(op3_mj_description.MJCF_PATH) - spec.option.timestep = 0.008 - spec.option.gravity = [0.0, 0.0, -9.81] - spec.option.integrator = mujoco.mjtIntegrator.mjINT_IMPLICIT - spec.option.solver = mujoco.mjtSolver.mjSOL_NEWTON - spec.option.impratio = 50.0 - spec.option.iterations = 40 + spec.option.timestep = 0.008 + spec.option.gravity = [0.0, 0.0, -9.81] + spec.option.integrator = mujoco.mjtIntegrator.mjINT_IMPLICIT + spec.option.solver = mujoco.mjtSolver.mjSOL_NEWTON + spec.option.impratio = 50.0 # stiffer friction cone → less foot slip + spec.option.iterations = 40 spec.option.noslip_iterations = 10 - spec.option.tolerance = 1e-4 + spec.option.tolerance = 1e-4 - _SOLREF = [0.010, 2.0] - _SOLIMP = [0.99, 0.9999, 0.0001, 0.5, 3] + # Stiff, nearly hard contacts between the feet and the floor + solref = [0.010, 2.0] + solimp = [0.99, 0.9999, 0.0001, 0.5, 3] floor = spec.worldbody.add_geom() - floor.name = "floor" - floor.type = mujoco.mjtGeom.mjGEOM_PLANE - floor.size = [0, 0, 0.1] - floor.pos = [0, 0, 0] - floor.rgba = [0.5, 0.5, 0.5, 1.0] - floor.contype = 1 + floor.name = "floor" + floor.type = mujoco.mjtGeom.mjGEOM_PLANE + floor.size = [0, 0, 0.1] + floor.pos = [0, 0, 0] + floor.rgba = [0.5, 0.5, 0.5, 1.0] + floor.contype = 1 floor.conaffinity = 1 - floor.solref = _SOLREF - floor.solimp = _SOLIMP + floor.solref = solref + floor.solimp = solimp + # Robot geoms collide with the floor only (no self-collision) for geom in spec.geoms: if geom.name == "floor": continue if geom.contype > 0 or geom.conaffinity > 0: geom.conaffinity = 0 - geom.solref = _SOLREF - geom.solimp = _SOLIMP + geom.solref = solref + geom.solimp = solimp model = spec.compile() + # Damping on the floating base (3 translational + 3 rotational DoFs) dof_start = model.jnt_dofadr[0] - model.dof_damping[dof_start:dof_start+3] = 3.0 - model.dof_damping[dof_start+3:dof_start+6] = 1.0 + model.dof_damping[dof_start:dof_start + 3] = 3.0 + model.dof_damping[dof_start + 3:dof_start + 6] = 1.0 - model.actuator_gainprm[:, 0] = ACTUATOR_KP - model.actuator_biasprm[:, 1] = -ACTUATOR_KP - model.actuator_biasprm[:, 2] = -ACTUATOR_KV + # Position actuators: torque = KP * (ctrl - q) - KV * q_dot + model.actuator_gainprm[:, 0] = ACTUATOR_KP + model.actuator_biasprm[:, 1] = -ACTUATOR_KP + model.actuator_biasprm[:, 2] = -ACTUATOR_KV model.actuator_forcerange[:, 0] = -ACTUATOR_FORCE_RANGE - model.actuator_forcerange[:, 1] = ACTUATOR_FORCE_RANGE + model.actuator_forcerange[:, 1] = ACTUATOR_FORCE_RANGE return model - def _build_maps(model: mujoco.MjModel) -> tuple[dict, dict, dict]: + """Joint name → qpos address, dof address and actuator index.""" qpos_addr: dict[str, int] = {} - dof_addr: dict[str, int] = {} + dof_addr: dict[str, int] = {} ctrl_addr: dict[str, int] = {} for i in range(model.njnt): name = mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_JOINT, i) if name: qpos_addr[name] = model.jnt_qposadr[i] - dof_addr[name] = model.jnt_dofadr[i] + dof_addr[name] = model.jnt_dofadr[i] for i in range(model.nu): if model.actuator_trntype[i] == mujoco.mjtTrn.mjTRN_JOINT: jnt_id = model.actuator_trnid[i, 0] @@ -98,140 +116,181 @@ def _build_maps(model: mujoco.MjModel) -> tuple[dict, dict, dict]: ctrl_addr[name] = i return qpos_addr, dof_addr, ctrl_addr -def _set_free_joint(data: mujoco.MjData, model: mujoco.MjModel, - pos: np.ndarray, quat: np.ndarray) -> None: - adr = model.jnt_qposadr[0] - data.qpos[adr:adr+3] = pos - data.qpos[adr+3:adr+7] = quat - - -def _apply_ctrl(data: mujoco.MjData, - ctrl_addr: dict, - joint_angles: dict[str, float]) -> None: - for jname, angle in joint_angles.items(): - if jname in ctrl_addr: - data.ctrl[ctrl_addr[jname]] = angle - - -def _apply_joints(data: mujoco.MjData, - qpos_addr: dict, dof_addr: dict, - joint_angles: dict[str, float], - zero_vel: bool) -> None: - for jname, angle in joint_angles.items(): - if jname in qpos_addr: - data.qpos[qpos_addr[jname]] = angle - if zero_vel: - data.qvel[dof_addr[jname]] = 0.0 - - -def _solve_leg(ik: IKSolver, - body_pos: np.ndarray, body_yaw: float, - sole_pos: np.ndarray, sole_yaw: float, - leg: str, side: str) -> dict[str, float] | None: - ok, angles = ik.solve(body_pos, _rot_z(body_yaw), - sole_pos, _rot_z(sole_yaw), leg) - if not ok: - return None - return { - _joint_name(side, key): JOINT_SIGNS[key][side] * angles[idx] - for idx, key in enumerate(_IK_KEYS) - } + +class Simulation: + """OP3 model + walking engine + IK, advanced one control tick at a time.""" + + def __init__(self, physics: bool = PHYSICS): + self.physics = physics + self.model = build_model() + self.data = mujoco.MjData(self.model) + self.qpos_addr, self.dof_addr, self.ctrl_addr = _build_maps(self.model) + self.ik = IKSolver() + self.engine = WalkingEngine(**WALK_PARAMS) + self.dt = self.model.opt.timestep + self.time = 0.0 + self.ik_failures = {"left": 0, "right": 0} + self.reset() + + @property + def body_pos(self) -> np.ndarray: + adr = self.model.jnt_qposadr[0] + return self.data.qpos[adr:adr + 3].copy() + + @property + def body_yaw(self) -> float: + adr = self.model.jnt_qposadr[0] + w, _, _, z = self.data.qpos[adr + 3:adr + 7] + return float(2.0 * np.arctan2(z, w)) + + def reset(self) -> None: + """Place the robot in the engine's standing pose.""" + mujoco.mj_resetData(self.model, self.data) + target = self.engine.standing_pose() + self._set_free_joint(target.body_pos, target.body_yaw) + for joint_angles in (*self._solve_legs(target), ARM_POSE): + self._set_joints(joint_angles) + self._set_ctrl(joint_angles) + mujoco.mj_forward(self.model, self.data) + + def step(self) -> None: + """Advance the engine and the simulation by one timestep.""" + target = self.engine.step(self.dt) + leg_angles = self._solve_legs(target) + + if self.physics: + for joint_angles in (*leg_angles, ARM_POSE): + self._set_ctrl(joint_angles) + mujoco.mj_step(self.model, self.data) + else: + # Kinematic playback: teleport the pelvis and set joints directly + self._set_free_joint(target.body_pos, target.body_yaw) + for joint_angles in (*leg_angles, ARM_POSE): + self._set_joints(joint_angles) + self.data.qvel[:] = 0.0 + mujoco.mj_forward(self.model, self.data) + + self.time += self.dt + + def _solve_legs(self, target: WalkTarget) -> list[dict[str, float]]: + """IK for both legs. A leg whose IK fails is left out (keeps its last command).""" + R_body = _rot_z(target.body_yaw) + soles = {"left": (target.left_sole, target.left_yaw), + "right": (target.right_sole, target.right_yaw)} + result = [] + for leg, side in _LEGS: + sole_pos, sole_yaw = soles[leg] + ok, angles = self.ik.solve(target.body_pos, R_body, + sole_pos, _rot_z(sole_yaw), leg) + if not ok: + if self.ik_failures[leg] == 0: + print(f"[IK] {leg} leg target out of reach at t={self.time:.2f}s " + f"(further failures are only counted)") + self.ik_failures[leg] += 1 + continue + result.append({ + joint_name(side, key): JOINT_SIGNS[key][side] * angles[idx] + for idx, key in enumerate(_IK_KEYS) + }) + return result + + def _set_free_joint(self, pos: np.ndarray, yaw: float) -> None: + adr = self.model.jnt_qposadr[0] + self.data.qpos[adr:adr + 3] = pos + self.data.qpos[adr + 3:adr + 7] = _yaw_to_quat(yaw) + + def _set_ctrl(self, joint_angles: dict[str, float]) -> None: + for name, angle in joint_angles.items(): + if name in self.ctrl_addr: + self.data.ctrl[self.ctrl_addr[name]] = angle + + def _set_joints(self, joint_angles: dict[str, float]) -> None: + for name, angle in joint_angles.items(): + if name in self.qpos_addr: + self.data.qpos[self.qpos_addr[name]] = angle + + +# ----------------------------------------------------------------------------- +# Keyboard control (keys pressed with the viewer window focused) +# Every letter is already a viewer shortcut, so we use arrows and nav keys. +# The arrows only step the simulation in the viewer while it is paused. +# ----------------------------------------------------------------------------- + +_GLFW_KEYS = { + 265: "up", 264: "down", 263: "left", 262: "right", + 266: "page_up", 267: "page_down", 268: "home", 261: "delete", +} _KEY_HELP = ( - "Teclas (terminal em foco):\n" - " W=frente S=lateral R=rotação M=marcha X=parar Q=sair" + "Keys (viewer window focused):\n" + " ↑ / ↓ forward / backward\n" + " ← / → sidestep left / right\n" + " PgUp / PgDn turn left / right\n" + " Home march in place\n" + " Delete stop\n" + " Close the window to quit." ) -def _poll_key(engine: WalkingEngine) -> None: - if not msvcrt.kbhit(): - return - key = msvcrt.getwch().lower() - if key == "w": engine.set_command(vx=0.05); print("[CMD] Frente") - elif key == "s": engine.set_command(vy=0.03); print("[CMD] Lateral") - elif key == "r": engine.set_command(wz=0.5); print("[CMD] Rotação") - elif key == "m": engine.set_command(); print("[CMD] Marcha no lugar") - elif key == "x": engine.request_stop(); print("[CMD] Parando...") - elif key == "q": os._exit(0) - -def main(): - model = build_model() - data = mujoco.MjData(model) - mujoco.mj_resetData(model, data) - - qpos_addr, dof_addr, ctrl_addr = _build_maps(model) - ik = IKSolver() - - if USE_MPC: - from mujoco_sim.MPCWalking.walking_engine import MPCWalkingEngine - from mujoco_sim.MPCWalking.config import MPC_PARAMS - engine = MPCWalkingEngine(dt=model.opt.timestep, **MPC_PARAMS) - else: - engine = WalkingEngine(**WALK_PARAMS) - - mode_label = "FÍSICA (mj_step)" if PHYSICS else "CINEMÁTICA (mj_forward)" - print(f"\n{'='*50}") - print(f" MODO: {mode_label}") - print(f" Joints disponíveis ({len(qpos_addr)}): {sorted(qpos_addr)}") - print(f"{'='*50}\n") - - body_pos, body_yaw, l_sole, l_yaw, r_sole, r_yaw = engine._standing_pose() - _set_free_joint(data, model, body_pos, _yaw_to_quat(body_yaw)) - - l_angles = _solve_leg(ik, body_pos, body_yaw, l_sole, l_yaw, "left", "l") - r_angles = _solve_leg(ik, body_pos, body_yaw, r_sole, r_yaw, "right", "r") - if l_angles: - _apply_joints(data, qpos_addr, dof_addr, l_angles, zero_vel=False) - _apply_ctrl(data, ctrl_addr, l_angles) - if r_angles: - _apply_joints(data, qpos_addr, dof_addr, r_angles, zero_vel=False) - _apply_ctrl(data, ctrl_addr, r_angles) - _apply_joints(data, qpos_addr, dof_addr, ARM_POSE, zero_vel=False) - _apply_ctrl(data, ctrl_addr, ARM_POSE) - mujoco.mj_forward(model, data) - - dt = model.opt.timestep - sim_time = 0.0 - real_start = time.perf_counter() - with mujoco.viewer.launch_passive(model, data) as viewer: +def _apply_key(engine: WalkingEngine, key: str) -> None: + vx = KEY_COMMANDS["forward"]["vx"] + vy = KEY_COMMANDS["lateral"]["vy"] + wz = KEY_COMMANDS["turn"]["wz"] + actions = { + "up": ("Forward", lambda: engine.set_command(vx=vx)), + "down": ("Backward", lambda: engine.set_command(vx=-vx)), + "left": ("Sidestep left", lambda: engine.set_command(vy=vy)), + "right": ("Sidestep right", lambda: engine.set_command(vy=-vy)), + "page_up": ("Turn left", lambda: engine.set_command(wz=wz)), + "page_down": ("Turn right", lambda: engine.set_command(wz=-wz)), + "home": ("March in place", engine.march_in_place), + "delete": ("Stopping...", engine.request_stop), + } + label, action = actions[key] + action() + print(f"[CMD] {label}") + + +def main() -> None: + sim = Simulation() + + mode_label = "PHYSICS (mj_step)" if sim.physics else "KINEMATICS (mj_forward)" + print(f"\n{'=' * 50}") + print(f" MODE: {mode_label}") + print(f" Joints ({len(sim.qpos_addr)}): {sorted(sim.qpos_addr)}") + print(f"{'=' * 50}\n") + print(_KEY_HELP, "\n") + + # The viewer calls key_callback from its own thread; queue the keys and + # apply them in the simulation loop. + pressed: deque[str] = deque() + + def key_callback(keycode: int) -> None: + if keycode in _GLFW_KEYS: + pressed.append(_GLFW_KEYS[keycode]) + + real_start = time.perf_counter() + with mujoco.viewer.launch_passive(sim.model, sim.data, key_callback=key_callback) as viewer: viewer.cam.lookat[:] = [0.0, 0.0, 0.2] - print(f"[MODO: {mode_label}]") - print(_KEY_HELP, "\n") while viewer.is_running(): - _poll_key(engine) - - body_pos, body_yaw, l_sole, l_yaw, r_sole, r_yaw = engine.step(dt) - - l_angles = _solve_leg(ik, body_pos, body_yaw, l_sole, l_yaw, "left", "l") - r_angles = _solve_leg(ik, body_pos, body_yaw, r_sole, r_yaw, "right", "r") - - if PHYSICS: - if USE_MPC: - # Open-loop: impõe a posição do torso diretamente (trajetória do LIPM). - # As pernas usam física para contato; o corpo segue o MPC sem lag. - _set_free_joint(data, model, body_pos, _yaw_to_quat(body_yaw)) - if l_angles: _apply_ctrl(data, ctrl_addr, l_angles) - if r_angles: _apply_ctrl(data, ctrl_addr, r_angles) - _apply_ctrl(data, ctrl_addr, ARM_POSE) - mujoco.mj_step(model, data) - else: - _set_free_joint(data, model, body_pos, _yaw_to_quat(body_yaw)) - if l_angles: _apply_joints(data, qpos_addr, dof_addr, l_angles, zero_vel=False) - if r_angles: _apply_joints(data, qpos_addr, dof_addr, r_angles, zero_vel=False) - _apply_joints(data, qpos_addr, dof_addr, ARM_POSE, zero_vel=False) - data.qvel[:] = 0.0 - mujoco.mj_forward(model, data) + while pressed: + _apply_key(sim.engine, pressed.popleft()) + with viewer.lock(): + sim.step() viewer.sync() - if int(sim_time / 0.5) != int((sim_time + dt) / 0.5): - adr = model.jnt_qposadr[0] - px, py, pz = data.qpos[adr], data.qpos[adr+1], data.qpos[adr+2] - print(f"[t={sim_time:.1f}s] body pos = ({px:.4f}, {py:.4f}, {pz:.4f}) state={engine.state}") + if int(sim.time / 0.5) != int((sim.time - sim.dt) / 0.5): + px, py, pz = sim.body_pos + print(f"[t={sim.time:.1f}s] body pos = ({px:.4f}, {py:.4f}, {pz:.4f}) " + f"state={sim.engine.state.name}") - sim_time += dt - elapsed = time.perf_counter() - real_start - sleep_time = sim_time - elapsed + # Keep the simulation in sync with wall-clock time + sleep_time = sim.time - (time.perf_counter() - real_start) if sleep_time > 0: time.sleep(sleep_time) + + failures = sum(sim.ik_failures.values()) + if failures: + print(f"[IK] {failures} failed solves during the run: {sim.ik_failures}") From 846ed7dd7bb34967807e0b4ff76899bb55d8db3e Mon Sep 17 00:00:00 2001 From: Pedro Henrique Bohling Peres Date: Sat, 3 Oct 2026 01:49:01 -0300 Subject: [PATCH 3/5] refactor(pybullet): rename modules, move assets, remove legacy code - ik_solver -> kinematics (LegIK, joints looked up by name), trajectory_generator -> mpc_planner (MPCPlanner) - URDF and meshes under assets/aurea, paths relative to the script - CLI options --vx, --headless, --duration; explicit MPC failure handling - Remove caminhada_relativa.py, scene_test.xml and unused NumericalIK - Document the MPC/physics clock mismatch (behaviour kept unchanged) --- .../aurea/aurea.urdf} | 2436 ++++++++--------- .../aurea/aurea_solidworks_export.csv} | 44 +- .../{ => assets/aurea}/meshes/base_link.STL | Bin .../aurea}/meshes/head_pan_link.STL | Bin .../aurea}/meshes/head_tilt_link.STL | Bin .../aurea}/meshes/l_ank_pitch_link.STL | Bin .../aurea}/meshes/l_ank_roll_link.STL | Bin .../{ => assets/aurea}/meshes/l_el_link.STL | Bin .../aurea}/meshes/l_hip_pitch_link.STL | Bin .../aurea}/meshes/l_hip_roll_link.STL | Bin .../aurea}/meshes/l_hip_yaw_link.STL | Bin .../{ => assets/aurea}/meshes/l_knee_link.STL | Bin .../aurea}/meshes/l_sho_pitch_link.STL | Bin .../aurea}/meshes/l_sho_roll_link.STL | Bin .../aurea}/meshes/r_ank_pitch_link.STL | Bin .../aurea}/meshes/r_ank_roll_link.STL | Bin .../{ => assets/aurea}/meshes/r_el_link.STL | Bin .../aurea}/meshes/r_hip_pitch_link.STL | Bin .../aurea}/meshes/r_hip_roll_link.STL | Bin .../aurea}/meshes/r_hip_yaw_link.STL | Bin .../{ => assets/aurea}/meshes/r_knee_link.STL | Bin .../aurea}/meshes/r_sho_pitch_link.STL | Bin .../aurea}/meshes/r_sho_roll_link.STL | Bin pybullet_sim/caminhada_relativa.py | 203 -- pybullet_sim/ik_solver.py | 120 - pybullet_sim/kinematics.py | 95 + pybullet_sim/main.py | 176 +- pybullet_sim/medir_urdf.py | 31 - pybullet_sim/mpc_planner.py | 178 ++ pybullet_sim/scene_test.xml | 4 - pybullet_sim/trajectory_generator.py | 137 - tools/inspect_urdf.py | 46 + 32 files changed, 1662 insertions(+), 1808 deletions(-) rename pybullet_sim/{urdf/aurea_urdf_pkg.urdf => assets/aurea/aurea.urdf} (95%) rename pybullet_sim/{urdf/aurea_urdf_pkg.csv => assets/aurea/aurea_solidworks_export.csv} (99%) rename pybullet_sim/{ => assets/aurea}/meshes/base_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/head_pan_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/head_tilt_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/l_ank_pitch_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/l_ank_roll_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/l_el_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/l_hip_pitch_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/l_hip_roll_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/l_hip_yaw_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/l_knee_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/l_sho_pitch_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/l_sho_roll_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/r_ank_pitch_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/r_ank_roll_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/r_el_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/r_hip_pitch_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/r_hip_roll_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/r_hip_yaw_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/r_knee_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/r_sho_pitch_link.STL (100%) rename pybullet_sim/{ => assets/aurea}/meshes/r_sho_roll_link.STL (100%) delete mode 100644 pybullet_sim/caminhada_relativa.py delete mode 100644 pybullet_sim/ik_solver.py create mode 100644 pybullet_sim/kinematics.py delete mode 100644 pybullet_sim/medir_urdf.py create mode 100644 pybullet_sim/mpc_planner.py delete mode 100644 pybullet_sim/scene_test.xml delete mode 100644 pybullet_sim/trajectory_generator.py create mode 100644 tools/inspect_urdf.py diff --git a/pybullet_sim/urdf/aurea_urdf_pkg.urdf b/pybullet_sim/assets/aurea/aurea.urdf similarity index 95% rename from pybullet_sim/urdf/aurea_urdf_pkg.urdf rename to pybullet_sim/assets/aurea/aurea.urdf index 5a84928..bd2dcff 100644 --- a/pybullet_sim/urdf/aurea_urdf_pkg.urdf +++ b/pybullet_sim/assets/aurea/aurea.urdf @@ -1,1218 +1,1218 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - edrom_lowlevel/AureaHardwareInterface - /dev/ttyUSB0 - 1000000 - - - - - - 1 - 1.0 - false - - - - - 2 - 1.0 - false - - - - - - 10 - 2.0 - false - - - - - 12 - 2.0 - true - - - - - 14 - 2.0 - true - - - - - 16 - 2.0 - true - - - - - 18 - 2.0 - true - - - - - 20 - 2.0 - false - - - - - - 9 - 2.0 - false - - - - - 11 - 2.0 - true - - - - - 13 - 2.0 - true - - - - - 15 - 2.0 - false - - - - - 17 - 2.0 - true - - - - - 19 - 2.0 - false - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + edrom_lowlevel/AureaHardwareInterface + /dev/ttyUSB0 + 1000000 + + + + + + 1 + 1.0 + false + + + + + 2 + 1.0 + false + + + + + + 10 + 2.0 + false + + + + + 12 + 2.0 + true + + + + + 14 + 2.0 + true + + + + + 16 + 2.0 + true + + + + + 18 + 2.0 + true + + + + + 20 + 2.0 + false + + + + + + 9 + 2.0 + false + + + + + 11 + 2.0 + true + + + + + 13 + 2.0 + true + + + + + 15 + 2.0 + false + + + + + 17 + 2.0 + true + + + + + 19 + 2.0 + false + + + diff --git a/pybullet_sim/urdf/aurea_urdf_pkg.csv b/pybullet_sim/assets/aurea/aurea_solidworks_export.csv similarity index 99% rename from pybullet_sim/urdf/aurea_urdf_pkg.csv rename to pybullet_sim/assets/aurea/aurea_solidworks_export.csv index ff81c7f..093a3d0 100644 --- a/pybullet_sim/urdf/aurea_urdf_pkg.csv +++ b/pybullet_sim/assets/aurea/aurea_solidworks_export.csv @@ -1,22 +1,22 @@ -Link Name,Center of Mass X,Center of Mass Y,Center of Mass Z,Center of Mass Roll,Center of Mass Pitch,Center of Mass Yaw,Mass,Moment Ixx,Moment Ixy,Moment Ixz,Moment Iyy,Moment Iyz,Moment Izz,Visual X,Visual Y,Visual Z,Visual Roll,Visual Pitch,Visual Yaw,Mesh Filename,Color Red,Color Green,Color Blue,Color Alpha,Collision X,Collision Y,Collision Z,Collision Roll,Collision Pitch,Collision Yaw,Collision Mesh Filename,Material Name,SW Components,Coordinate System,Axis Name,Joint Name,Joint Type,Joint Origin X,Joint Origin Y,Joint Origin Z,Joint Origin Roll,Joint Origin Pitch,Joint Origin Yaw,Parent,Joint Axis X,Joint Axis Y,Joint Axis Z,Limit Effort,Limit Velocity,Limit Lower,Limit Upper,Calibration rising,Calibration falling,Dynamics Damping,Dynamics Friction,Safety Soft Upper,Safety Soft Lower,Safety K Position,Safety K Velocity -base_link,"-0,0054386","-0,001346","0,071435",0,0,0,"1,4383","0,002822","1,8394E-05","7,3373E-06","0,0011477","1,1908E-06","0,0035028",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/base_link.STL,"0,75294","0,75294","0,75294",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/base_link.STL,,BODY-1,com_origin,,,,0,0,0,0,0,0,,0,0,0,,,,,,,,,,,, -head_pan_link,"6,8372E-10","0,0084419","0,042088",0,0,0,"0,0042427","3,8746E-07","-1,3861E-16","3,3793E-14","2,1011E-07","3,2642E-12","4,6187E-07",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/head_pan_link.STL,"0,75294","0,75294","0,75294",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/head_pan_link.STL,,NECK-1,head_pan_origin,head_pan_axis,head_pan,continuous,"0,011996","0,00071768","0,1444",0,0,0,base_link,0,0,1,,,,,,,,,,,, -head_tilt_link,"0,018438","0,00044728","0,041065",0,0,0,"0,06657","5,0882E-05","-2,503E-07","-1,8977E-06","1,4263E-05","1,2974E-08","4,3876E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/head_tilt_link.STL,"0,75294","0,75294","0,75294",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/head_tilt_link.STL,,HEAD-1,head_tilt_origin,head_tilt_axis,head_tilt,continuous,0,"0,0002","0,06104",0,0,0,head_pan_link,0,1,0,,,,,,,,,,,, -l_sho_pitch_link,"0,00352137182401335","0,0407154850483834","-0,00945859812868083",0,0,0,"0,0265849110122631","7,26212411316309E-06","6,5171693357601E-08","1,06977457104858E-08","1,68111887165657E-05","1,56116573724164E-06","1,65455750032948E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_sho_pitch_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_sho_pitch_link.STL,,LARM_1(2)-1,l_sho_p_origin,l_sho_pitch_axis,l_sho_pitch,continuous,"0,012683","0,068258","0,1366",0,0,0,base_link,0,1,0,,,,,,,,,,,, -l_sho_roll_link,"0,000189659734588288","0,0505998821833899","-0,00015618572152401",0,0,0,"0,197011517994657","7,72328719397364E-05","-3,30651837574508E-07","5,34864466021505E-09","5,39299927610623E-05","1,71324199171907E-07","7,72363058962331E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_sho_roll_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_sho_roll_link.STL,,LARM_2(2)-1,l_sho_r_origin,l_sho_roll_axis,l_sho_roll,continuous,"0,00084166","0,049985","-0,03201",0,0,0,l_sho_pitch_link,1,0,0,,,,,,,,,,,, -l_el_link,"3,40772950824242E-06","0,0812441997050536","0,0163078157472427",0,0,0,"0,0742846959386868","0,000257242445168631","2,40125472668084E-09","7,63997467824725E-09","2,53349920387169E-05","-2,81862911803432E-05","0,000251570298661733",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_el_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_el_link.STL,,LARM_3(2)-1,l_el_origin,l_el_axis,l_el,continuous,"0,00069168","0,1012",0,0,0,0,l_sho_roll_link,0,0,1,,,,,,,,,,,, -r_sho_pitch_link,"-0,00324300761837326","-0,0407386018885676","-0,00945859812868109",0,0,0,"0,0265849110122628","7,26167934268369E-06","-9,87611207484539E-11","-2,72495337082694E-11","1,68116334870447E-05","-1,56120238923654E-06","1,65455750032945E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_sho_pitch_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_sho_pitch_link.STL,,RARM_1(2)-1,r_sho_p_origin,r_sho_pitch_axis,r_sho_pitch,continuous,"0,011753","-0,067739","0,1366",0,0,0,base_link,0,1,0,,,,,,,,,,,, -r_sho_roll_link,"-0,00018965852905839","-0,0505998890321953","-0,000156184466288206",0,0,0,"0,197011518002432","7,7232874661571E-05","-3,30654304600183E-07","-5,34863006420127E-09","5,39299928078269E-05","-1,71321741828796E-07","7,72363031557339E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_sho_roll_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_sho_roll_link.STL,,RARM_2(2)-1,r_sho_r_origin,r_sho_roll_axis,r_sho_roll,continuous,0,"-0,04999","-0,03201",0,0,0,r_sho_pitch_link,1,0,0,,,,,,,,,,,, -r_el_link,"3,69373478137369E-07","-0,0812441997050545","0,0163078098038721",0,0,0,"0,0742846959386874","0,000257242445168637","1,40129351675067E-08","-7,6399746782357E-09","2,53349920387171E-05","2,81863008773858E-05","0,000251570298661738",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_el_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_el_link.STL,,RARM_3(2)-1,r_el_origin,r_el_axis,r_el,continuous,"-0,00069168","-0,1012",0,0,0,0,r_sho_roll_link,0,0,1,,,,,,,,,,,, -r_hip_yaw_link,0,0,"-0,0359900474652093",0,0,0,"0,00694060291701572","1,66514174382027E-06","7,61187511128963E-14","1,13106493815015E-21","4,14964511041944E-06","4,47500627637193E-22","4,00421747655342E-06",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_hip_yaw_link.STL,"0,792156862745098","0,819607843137255","0,933333333333333",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_hip_yaw_link.STL,,RLEG_1(2)-1,r_hip_y_origin,r_hip_yaw_axis,r_hip_yaw,continuous,0,"-0,0425",0,0,0,0,base_link,0,0,1,,,,,,,,,,,, -r_hip_roll_link,"0,0298644539096895","9,54006661061657E-05","-0,015199101611021",0,0,0,"0,183944855809613","7,35153703278313E-05","-9,12174690443411E-13","-1,99657590840599E-07","7,2145316762463E-05","1,71352044892206E-07","4,46900690426448E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_hip_roll_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_hip_roll_link.STL,,RLEG_2(2)-1,r_hip_r_origin,r_hip_roll_axis,r_hip_roll,revolute,"-0,054","-0,0005","-0,062",0,0,0,r_hip_yaw_link,1,0,0,0,0,0,0,,,,,,,, -r_hip_pitch_link,"0,000158089791263194","0,000105595270809319","-0,0854824898523879",0,0,0,"0,126175620271201","6,48140911230731E-05","2,10740537539349E-09","1,1644914411195E-06","6,27191148165412E-05","-1,73025564156333E-07","2,69153394295054E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_hip_pitch_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_hip_pitch_link.STL,,RLEG_3(2)-1,r_hip_p_origin,r_hip_pitch_axis,r_hip_pitch,continuous,"0,054","0,0005",0,0,0,0,r_hip_roll_link,0,1,0,,,,,,,,,,,, -r_knee_link,0,"-0,000249999999999945","-0,0419003925928639",0,0,0,"0,0371694656443498","3,02487395406759E-05","2,83795802988007E-21","2,69721239634144E-19","3,30337384651361E-05","-2,46337337062787E-21","2,81492271510805E-06",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_knee_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_knee_link.STL,,RLEG_4(2)-1,r_knee_origin,r_knee_axis,r_knee,continuous,0,"-0,00043","-0,12",0,0,0,r_hip_pitch_link,0,1,0,,,,,,,,,,,, -r_ank_pitch_link,"-0,0241355511158929","0,00040459916222195","0,0151991016063729",0,0,0,"0,183944856002735","7,35153675760217E-05","-9,68086292419633E-13","1,99652664804746E-07","7,21453195202993E-05","1,71354502622176E-07","4,46900690405164E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_ank_pitch_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_ank_pitch_link.STL,,RLEG_5(2)-1,r_ank_p_origin,r_ank_pitch_axis,r_ank_pitch,continuous,0,"-0,0005","-0,085",0,0,0,r_knee_link,0,1,0,,,,,,,,,,,, -r_ank_roll_link,"0,0539999996375236","-0,0118311818058534","-0,035114625516821",0,0,0,"0,0877025419110131","4,99364719914314E-05","2,18241108911093E-12","-8,1038721944463E-20","0,000121170006996388","-2,73345204332721E-20","0,000166148487178617",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_ank_roll_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_ank_roll_link.STL,,RLEG_6(2)-1,r_ank_r_origin,r_ank_roll_axis,r_ank_roll,continuous,"-0,054",0,0,0,0,0,r_ank_pitch_link,1,0,0,,,,,,,,,,,, -l_hip_yaw_link,"6,66133814775094E-16","-1,04083408558608E-16","-0,0359900474652092",0,0,0,"0,0187396278759425","4,49588270831473E-06","1,64070596376897E-21","2,90408135599367E-21","1,12040417981325E-05","2,72994201449648E-21","1,08113871866942E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_hip_yaw_link.STL,"0,898039215686275","0,917647058823529","0,929411764705882",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_hip_yaw_link.STL,,LLEG_1(2)-1,l_hip_y_origin,l_hip_yaw_axis,l_hip_yaw,continuous,0,"0,0425",0,0,0,0,base_link,0,0,1,,,,,,,,,,,, -l_hip_roll_link,"0,029864450820877","9,54018942858591E-05","-0,0151991003922303",0,0,0,"0,183944868508389","7,35153751264597E-05","4,82256065759444E-14","-1,99653903762223E-07","7,21453243143375E-05","1,71350802680741E-07","4,46900721055964E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_hip_roll_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_hip_roll_link.STL,,LLEG_2(2)-1,l_hip_r_origin,l_hip_roll_axis,l_hip_roll,continuous,"-0,054","-0,0005","-0,062",0,0,0,l_hip_yaw_link,1,0,0,,,,,,,,,,,, -l_hip_pitch_link,"0,000158090270155786","0,000105593836474918","-0,0854824861313004",0,0,0,"0,126175607572412","6,4814083571068E-05","2,10655113465296E-09","1,16449268251577E-06","6,27191100180411E-05","-1,73029220807411E-07","2,69153363665543E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_hip_pitch_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_hip_pitch_link.STL,,LLEG_3(2)-1,l_hip_p_origin,l_hip_pitch_axis,l_hip_pitch,continuous,"0,054","0,0005",0,0,0,0,l_hip_roll_link,0,1,0,,,,,,,,,,,, -l_knee_link,"-3,33066907387547E-16","-0,000250000000000136","-0,0419003925928641",0,0,0,"0,0371694656443498","3,02487395406759E-05","-4,12887867253622E-21","1,06646590271131E-21","3,30337384651361E-05","-8,46596202007678E-21","2,81492271510806E-06",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_knee_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_knee_link.STL,,LLEG_4(2)-1,l_knee_origin,l_knee_axis,l_knee,continuous,0,"-0,00043","-0,12",0,0,0,l_hip_pitch_link,0,1,0,,,,,,,,,,,, -l_ank_pitch_link,"-0,0241355491298787","0,000404598110121463","0,0151991003875814",0,0,0,"0,183944868701523","7,35153751278978E-05","-4,96608920988143E-14","1,99653900899916E-07","7,21453243189286E-05","1,71350810294181E-07","4,46900721034675E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_ank_pitch_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_ank_pitch_link.STL,,LLEG_5(2)-1,l_ank_p_origin,l_ank_pitch_axis,l_ank_pitch,continuous,0,"-0,0005","-0,085",0,0,0,l_knee_link,0,1,0,,,,,,,,,,,, -l_ank_roll_link,"0,0539999999999995","0,0113311801514332","-0,0351146255168209",0,0,0,"0,0877025419110131","4,99364719914313E-05","-1,8100008925471E-20","-2,31402748877954E-20","0,000121170006996388","-8,68904764115959E-23","0,000166148487178617",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_ank_roll_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_ank_roll_link.STL,,LLEG_6(2)-1,l_ank_r_origin,l_ank_roll_axis,l_ank_roll,continuous,"-0,054","0,0005",0,0,0,0,l_ank_pitch_link,1,0,0,,,,,,,,,,,, +Link Name,Center of Mass X,Center of Mass Y,Center of Mass Z,Center of Mass Roll,Center of Mass Pitch,Center of Mass Yaw,Mass,Moment Ixx,Moment Ixy,Moment Ixz,Moment Iyy,Moment Iyz,Moment Izz,Visual X,Visual Y,Visual Z,Visual Roll,Visual Pitch,Visual Yaw,Mesh Filename,Color Red,Color Green,Color Blue,Color Alpha,Collision X,Collision Y,Collision Z,Collision Roll,Collision Pitch,Collision Yaw,Collision Mesh Filename,Material Name,SW Components,Coordinate System,Axis Name,Joint Name,Joint Type,Joint Origin X,Joint Origin Y,Joint Origin Z,Joint Origin Roll,Joint Origin Pitch,Joint Origin Yaw,Parent,Joint Axis X,Joint Axis Y,Joint Axis Z,Limit Effort,Limit Velocity,Limit Lower,Limit Upper,Calibration rising,Calibration falling,Dynamics Damping,Dynamics Friction,Safety Soft Upper,Safety Soft Lower,Safety K Position,Safety K Velocity +base_link,"-0,0054386","-0,001346","0,071435",0,0,0,"1,4383","0,002822","1,8394E-05","7,3373E-06","0,0011477","1,1908E-06","0,0035028",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/base_link.STL,"0,75294","0,75294","0,75294",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/base_link.STL,,BODY-1,com_origin,,,,0,0,0,0,0,0,,0,0,0,,,,,,,,,,,, +head_pan_link,"6,8372E-10","0,0084419","0,042088",0,0,0,"0,0042427","3,8746E-07","-1,3861E-16","3,3793E-14","2,1011E-07","3,2642E-12","4,6187E-07",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/head_pan_link.STL,"0,75294","0,75294","0,75294",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/head_pan_link.STL,,NECK-1,head_pan_origin,head_pan_axis,head_pan,continuous,"0,011996","0,00071768","0,1444",0,0,0,base_link,0,0,1,,,,,,,,,,,, +head_tilt_link,"0,018438","0,00044728","0,041065",0,0,0,"0,06657","5,0882E-05","-2,503E-07","-1,8977E-06","1,4263E-05","1,2974E-08","4,3876E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/head_tilt_link.STL,"0,75294","0,75294","0,75294",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/head_tilt_link.STL,,HEAD-1,head_tilt_origin,head_tilt_axis,head_tilt,continuous,0,"0,0002","0,06104",0,0,0,head_pan_link,0,1,0,,,,,,,,,,,, +l_sho_pitch_link,"0,00352137182401335","0,0407154850483834","-0,00945859812868083",0,0,0,"0,0265849110122631","7,26212411316309E-06","6,5171693357601E-08","1,06977457104858E-08","1,68111887165657E-05","1,56116573724164E-06","1,65455750032948E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_sho_pitch_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_sho_pitch_link.STL,,LARM_1(2)-1,l_sho_p_origin,l_sho_pitch_axis,l_sho_pitch,continuous,"0,012683","0,068258","0,1366",0,0,0,base_link,0,1,0,,,,,,,,,,,, +l_sho_roll_link,"0,000189659734588288","0,0505998821833899","-0,00015618572152401",0,0,0,"0,197011517994657","7,72328719397364E-05","-3,30651837574508E-07","5,34864466021505E-09","5,39299927610623E-05","1,71324199171907E-07","7,72363058962331E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_sho_roll_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_sho_roll_link.STL,,LARM_2(2)-1,l_sho_r_origin,l_sho_roll_axis,l_sho_roll,continuous,"0,00084166","0,049985","-0,03201",0,0,0,l_sho_pitch_link,1,0,0,,,,,,,,,,,, +l_el_link,"3,40772950824242E-06","0,0812441997050536","0,0163078157472427",0,0,0,"0,0742846959386868","0,000257242445168631","2,40125472668084E-09","7,63997467824725E-09","2,53349920387169E-05","-2,81862911803432E-05","0,000251570298661733",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_el_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_el_link.STL,,LARM_3(2)-1,l_el_origin,l_el_axis,l_el,continuous,"0,00069168","0,1012",0,0,0,0,l_sho_roll_link,0,0,1,,,,,,,,,,,, +r_sho_pitch_link,"-0,00324300761837326","-0,0407386018885676","-0,00945859812868109",0,0,0,"0,0265849110122628","7,26167934268369E-06","-9,87611207484539E-11","-2,72495337082694E-11","1,68116334870447E-05","-1,56120238923654E-06","1,65455750032945E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_sho_pitch_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_sho_pitch_link.STL,,RARM_1(2)-1,r_sho_p_origin,r_sho_pitch_axis,r_sho_pitch,continuous,"0,011753","-0,067739","0,1366",0,0,0,base_link,0,1,0,,,,,,,,,,,, +r_sho_roll_link,"-0,00018965852905839","-0,0505998890321953","-0,000156184466288206",0,0,0,"0,197011518002432","7,7232874661571E-05","-3,30654304600183E-07","-5,34863006420127E-09","5,39299928078269E-05","-1,71321741828796E-07","7,72363031557339E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_sho_roll_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_sho_roll_link.STL,,RARM_2(2)-1,r_sho_r_origin,r_sho_roll_axis,r_sho_roll,continuous,0,"-0,04999","-0,03201",0,0,0,r_sho_pitch_link,1,0,0,,,,,,,,,,,, +r_el_link,"3,69373478137369E-07","-0,0812441997050545","0,0163078098038721",0,0,0,"0,0742846959386874","0,000257242445168637","1,40129351675067E-08","-7,6399746782357E-09","2,53349920387171E-05","2,81863008773858E-05","0,000251570298661738",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_el_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_el_link.STL,,RARM_3(2)-1,r_el_origin,r_el_axis,r_el,continuous,"-0,00069168","-0,1012",0,0,0,0,r_sho_roll_link,0,0,1,,,,,,,,,,,, +r_hip_yaw_link,0,0,"-0,0359900474652093",0,0,0,"0,00694060291701572","1,66514174382027E-06","7,61187511128963E-14","1,13106493815015E-21","4,14964511041944E-06","4,47500627637193E-22","4,00421747655342E-06",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_hip_yaw_link.STL,"0,792156862745098","0,819607843137255","0,933333333333333",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_hip_yaw_link.STL,,RLEG_1(2)-1,r_hip_y_origin,r_hip_yaw_axis,r_hip_yaw,continuous,0,"-0,0425",0,0,0,0,base_link,0,0,1,,,,,,,,,,,, +r_hip_roll_link,"0,0298644539096895","9,54006661061657E-05","-0,015199101611021",0,0,0,"0,183944855809613","7,35153703278313E-05","-9,12174690443411E-13","-1,99657590840599E-07","7,2145316762463E-05","1,71352044892206E-07","4,46900690426448E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_hip_roll_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_hip_roll_link.STL,,RLEG_2(2)-1,r_hip_r_origin,r_hip_roll_axis,r_hip_roll,revolute,"-0,054","-0,0005","-0,062",0,0,0,r_hip_yaw_link,1,0,0,0,0,0,0,,,,,,,, +r_hip_pitch_link,"0,000158089791263194","0,000105595270809319","-0,0854824898523879",0,0,0,"0,126175620271201","6,48140911230731E-05","2,10740537539349E-09","1,1644914411195E-06","6,27191148165412E-05","-1,73025564156333E-07","2,69153394295054E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_hip_pitch_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_hip_pitch_link.STL,,RLEG_3(2)-1,r_hip_p_origin,r_hip_pitch_axis,r_hip_pitch,continuous,"0,054","0,0005",0,0,0,0,r_hip_roll_link,0,1,0,,,,,,,,,,,, +r_knee_link,0,"-0,000249999999999945","-0,0419003925928639",0,0,0,"0,0371694656443498","3,02487395406759E-05","2,83795802988007E-21","2,69721239634144E-19","3,30337384651361E-05","-2,46337337062787E-21","2,81492271510805E-06",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_knee_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_knee_link.STL,,RLEG_4(2)-1,r_knee_origin,r_knee_axis,r_knee,continuous,0,"-0,00043","-0,12",0,0,0,r_hip_pitch_link,0,1,0,,,,,,,,,,,, +r_ank_pitch_link,"-0,0241355511158929","0,00040459916222195","0,0151991016063729",0,0,0,"0,183944856002735","7,35153675760217E-05","-9,68086292419633E-13","1,99652664804746E-07","7,21453195202993E-05","1,71354502622176E-07","4,46900690405164E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_ank_pitch_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_ank_pitch_link.STL,,RLEG_5(2)-1,r_ank_p_origin,r_ank_pitch_axis,r_ank_pitch,continuous,0,"-0,0005","-0,085",0,0,0,r_knee_link,0,1,0,,,,,,,,,,,, +r_ank_roll_link,"0,0539999996375236","-0,0118311818058534","-0,035114625516821",0,0,0,"0,0877025419110131","4,99364719914314E-05","2,18241108911093E-12","-8,1038721944463E-20","0,000121170006996388","-2,73345204332721E-20","0,000166148487178617",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_ank_roll_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/r_ank_roll_link.STL,,RLEG_6(2)-1,r_ank_r_origin,r_ank_roll_axis,r_ank_roll,continuous,"-0,054",0,0,0,0,0,r_ank_pitch_link,1,0,0,,,,,,,,,,,, +l_hip_yaw_link,"6,66133814775094E-16","-1,04083408558608E-16","-0,0359900474652092",0,0,0,"0,0187396278759425","4,49588270831473E-06","1,64070596376897E-21","2,90408135599367E-21","1,12040417981325E-05","2,72994201449648E-21","1,08113871866942E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_hip_yaw_link.STL,"0,898039215686275","0,917647058823529","0,929411764705882",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_hip_yaw_link.STL,,LLEG_1(2)-1,l_hip_y_origin,l_hip_yaw_axis,l_hip_yaw,continuous,0,"0,0425",0,0,0,0,base_link,0,0,1,,,,,,,,,,,, +l_hip_roll_link,"0,029864450820877","9,54018942858591E-05","-0,0151991003922303",0,0,0,"0,183944868508389","7,35153751264597E-05","4,82256065759444E-14","-1,99653903762223E-07","7,21453243143375E-05","1,71350802680741E-07","4,46900721055964E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_hip_roll_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_hip_roll_link.STL,,LLEG_2(2)-1,l_hip_r_origin,l_hip_roll_axis,l_hip_roll,continuous,"-0,054","-0,0005","-0,062",0,0,0,l_hip_yaw_link,1,0,0,,,,,,,,,,,, +l_hip_pitch_link,"0,000158090270155786","0,000105593836474918","-0,0854824861313004",0,0,0,"0,126175607572412","6,4814083571068E-05","2,10655113465296E-09","1,16449268251577E-06","6,27191100180411E-05","-1,73029220807411E-07","2,69153363665543E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_hip_pitch_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_hip_pitch_link.STL,,LLEG_3(2)-1,l_hip_p_origin,l_hip_pitch_axis,l_hip_pitch,continuous,"0,054","0,0005",0,0,0,0,l_hip_roll_link,0,1,0,,,,,,,,,,,, +l_knee_link,"-3,33066907387547E-16","-0,000250000000000136","-0,0419003925928641",0,0,0,"0,0371694656443498","3,02487395406759E-05","-4,12887867253622E-21","1,06646590271131E-21","3,30337384651361E-05","-8,46596202007678E-21","2,81492271510806E-06",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_knee_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_knee_link.STL,,LLEG_4(2)-1,l_knee_origin,l_knee_axis,l_knee,continuous,0,"-0,00043","-0,12",0,0,0,l_hip_pitch_link,0,1,0,,,,,,,,,,,, +l_ank_pitch_link,"-0,0241355491298787","0,000404598110121463","0,0151991003875814",0,0,0,"0,183944868701523","7,35153751278978E-05","-4,96608920988143E-14","1,99653900899916E-07","7,21453243189286E-05","1,71350810294181E-07","4,46900721034675E-05",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_ank_pitch_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_ank_pitch_link.STL,,LLEG_5(2)-1,l_ank_p_origin,l_ank_pitch_axis,l_ank_pitch,continuous,0,"-0,0005","-0,085",0,0,0,l_knee_link,0,1,0,,,,,,,,,,,, +l_ank_roll_link,"0,0539999999999995","0,0113311801514332","-0,0351146255168209",0,0,0,"0,0877025419110131","4,99364719914313E-05","-1,8100008925471E-20","-2,31402748877954E-20","0,000121170006996388","-8,68904764115959E-23","0,000166148487178617",0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_ank_roll_link.STL,"0,752941176470588","0,752941176470588","0,752941176470588",1,0,0,0,0,0,0,package://aurea_urdf_pkg/meshes/l_ank_roll_link.STL,,LLEG_6(2)-1,l_ank_r_origin,l_ank_roll_axis,l_ank_roll,continuous,"-0,054","0,0005",0,0,0,0,l_ank_pitch_link,1,0,0,,,,,,,,,,,, diff --git a/pybullet_sim/meshes/base_link.STL b/pybullet_sim/assets/aurea/meshes/base_link.STL similarity index 100% rename from pybullet_sim/meshes/base_link.STL rename to pybullet_sim/assets/aurea/meshes/base_link.STL diff --git a/pybullet_sim/meshes/head_pan_link.STL b/pybullet_sim/assets/aurea/meshes/head_pan_link.STL similarity index 100% rename from pybullet_sim/meshes/head_pan_link.STL rename to pybullet_sim/assets/aurea/meshes/head_pan_link.STL diff --git a/pybullet_sim/meshes/head_tilt_link.STL b/pybullet_sim/assets/aurea/meshes/head_tilt_link.STL similarity index 100% rename from pybullet_sim/meshes/head_tilt_link.STL rename to pybullet_sim/assets/aurea/meshes/head_tilt_link.STL diff --git a/pybullet_sim/meshes/l_ank_pitch_link.STL b/pybullet_sim/assets/aurea/meshes/l_ank_pitch_link.STL similarity index 100% rename from pybullet_sim/meshes/l_ank_pitch_link.STL rename to pybullet_sim/assets/aurea/meshes/l_ank_pitch_link.STL diff --git a/pybullet_sim/meshes/l_ank_roll_link.STL b/pybullet_sim/assets/aurea/meshes/l_ank_roll_link.STL similarity index 100% rename from pybullet_sim/meshes/l_ank_roll_link.STL rename to pybullet_sim/assets/aurea/meshes/l_ank_roll_link.STL diff --git a/pybullet_sim/meshes/l_el_link.STL b/pybullet_sim/assets/aurea/meshes/l_el_link.STL similarity index 100% rename from pybullet_sim/meshes/l_el_link.STL rename to pybullet_sim/assets/aurea/meshes/l_el_link.STL diff --git a/pybullet_sim/meshes/l_hip_pitch_link.STL b/pybullet_sim/assets/aurea/meshes/l_hip_pitch_link.STL similarity index 100% rename from pybullet_sim/meshes/l_hip_pitch_link.STL rename to pybullet_sim/assets/aurea/meshes/l_hip_pitch_link.STL diff --git a/pybullet_sim/meshes/l_hip_roll_link.STL b/pybullet_sim/assets/aurea/meshes/l_hip_roll_link.STL similarity index 100% rename from pybullet_sim/meshes/l_hip_roll_link.STL rename to pybullet_sim/assets/aurea/meshes/l_hip_roll_link.STL diff --git a/pybullet_sim/meshes/l_hip_yaw_link.STL b/pybullet_sim/assets/aurea/meshes/l_hip_yaw_link.STL similarity index 100% rename from pybullet_sim/meshes/l_hip_yaw_link.STL rename to pybullet_sim/assets/aurea/meshes/l_hip_yaw_link.STL diff --git a/pybullet_sim/meshes/l_knee_link.STL b/pybullet_sim/assets/aurea/meshes/l_knee_link.STL similarity index 100% rename from pybullet_sim/meshes/l_knee_link.STL rename to pybullet_sim/assets/aurea/meshes/l_knee_link.STL diff --git a/pybullet_sim/meshes/l_sho_pitch_link.STL b/pybullet_sim/assets/aurea/meshes/l_sho_pitch_link.STL similarity index 100% rename from pybullet_sim/meshes/l_sho_pitch_link.STL rename to pybullet_sim/assets/aurea/meshes/l_sho_pitch_link.STL diff --git a/pybullet_sim/meshes/l_sho_roll_link.STL b/pybullet_sim/assets/aurea/meshes/l_sho_roll_link.STL similarity index 100% rename from pybullet_sim/meshes/l_sho_roll_link.STL rename to pybullet_sim/assets/aurea/meshes/l_sho_roll_link.STL diff --git a/pybullet_sim/meshes/r_ank_pitch_link.STL b/pybullet_sim/assets/aurea/meshes/r_ank_pitch_link.STL similarity index 100% rename from pybullet_sim/meshes/r_ank_pitch_link.STL rename to pybullet_sim/assets/aurea/meshes/r_ank_pitch_link.STL diff --git a/pybullet_sim/meshes/r_ank_roll_link.STL b/pybullet_sim/assets/aurea/meshes/r_ank_roll_link.STL similarity index 100% rename from pybullet_sim/meshes/r_ank_roll_link.STL rename to pybullet_sim/assets/aurea/meshes/r_ank_roll_link.STL diff --git a/pybullet_sim/meshes/r_el_link.STL b/pybullet_sim/assets/aurea/meshes/r_el_link.STL similarity index 100% rename from pybullet_sim/meshes/r_el_link.STL rename to pybullet_sim/assets/aurea/meshes/r_el_link.STL diff --git a/pybullet_sim/meshes/r_hip_pitch_link.STL b/pybullet_sim/assets/aurea/meshes/r_hip_pitch_link.STL similarity index 100% rename from pybullet_sim/meshes/r_hip_pitch_link.STL rename to pybullet_sim/assets/aurea/meshes/r_hip_pitch_link.STL diff --git a/pybullet_sim/meshes/r_hip_roll_link.STL b/pybullet_sim/assets/aurea/meshes/r_hip_roll_link.STL similarity index 100% rename from pybullet_sim/meshes/r_hip_roll_link.STL rename to pybullet_sim/assets/aurea/meshes/r_hip_roll_link.STL diff --git a/pybullet_sim/meshes/r_hip_yaw_link.STL b/pybullet_sim/assets/aurea/meshes/r_hip_yaw_link.STL similarity index 100% rename from pybullet_sim/meshes/r_hip_yaw_link.STL rename to pybullet_sim/assets/aurea/meshes/r_hip_yaw_link.STL diff --git a/pybullet_sim/meshes/r_knee_link.STL b/pybullet_sim/assets/aurea/meshes/r_knee_link.STL similarity index 100% rename from pybullet_sim/meshes/r_knee_link.STL rename to pybullet_sim/assets/aurea/meshes/r_knee_link.STL diff --git a/pybullet_sim/meshes/r_sho_pitch_link.STL b/pybullet_sim/assets/aurea/meshes/r_sho_pitch_link.STL similarity index 100% rename from pybullet_sim/meshes/r_sho_pitch_link.STL rename to pybullet_sim/assets/aurea/meshes/r_sho_pitch_link.STL diff --git a/pybullet_sim/meshes/r_sho_roll_link.STL b/pybullet_sim/assets/aurea/meshes/r_sho_roll_link.STL similarity index 100% rename from pybullet_sim/meshes/r_sho_roll_link.STL rename to pybullet_sim/assets/aurea/meshes/r_sho_roll_link.STL diff --git a/pybullet_sim/caminhada_relativa.py b/pybullet_sim/caminhada_relativa.py deleted file mode 100644 index 1fcdc50..0000000 --- a/pybullet_sim/caminhada_relativa.py +++ /dev/null @@ -1,203 +0,0 @@ -import pybullet as p -import pybullet_data -import time -import math -import numpy as np -import cvxpy as cp - -class RelativeAnalyticalIK: - def __init__(self, robot_id): - self.robot_id = robot_id - - # --- PARÂMETROS DO ROBÔ --- - self.L_COXA = 0.12 - self.L_TIBIA = 0.085 - self.D_QUADRIL_OFFSET = 0.0425 - self.D_TORNOZELO_SOLA = 0.048 - - self.JUNTAS_DIR = [8, 9, 10, 11, 12, 13] - self.JUNTAS_ESQ = [14, 15, 16, 17, 18, 19] - self.fixed_joints = {} - - def set_fixed_joint(self, joint_id, target_angle): - - self.fixed_joints[joint_id] = target_angle - - def solve_leg(self, dx, dy, dz, is_left): - """ - dx, dy, dz: Distância do pé em relação ao centro do torso (0,0,0) - """ - - sinal_y = 1.0 if is_left else -1.0 - offset_y = self.D_QUADRIL_OFFSET * sinal_y - - rx = -dx - ry = -(dy - offset_y) - rz = -(dz + self.D_TORNOZELO_SOLA) - C = math.sqrt(rx**2 + ry**2 + rz**2) - - - C = min(C, self.L_COXA + self.L_TIBIA - 0.001) - - cos_q5 = (C**2 - self.L_COXA**2 - self.L_TIBIA**2) / (2 * self.L_COXA * self.L_TIBIA) - cos_q5 = max(-1.0, min(1.0, cos_q5)) - q5_knee = math.acos(cos_q5) - - - q7_ankle_roll = math.atan2(ry, rz) - - alpha = math.asin(max(-1.0, min(1.0, (self.L_COXA / C) * math.sin(q5_knee)))) - q6_ankle_pitch = -math.atan2(rx, math.copysign(1.0, rz) * math.sqrt(ry**2 + rz**2)) - alpha - - q1_hip_yaw = 0.0 - q2_hip_roll = -q7_ankle_roll - q3_hip_pitch = -q6_ankle_pitch - q5_knee - - return [q1_hip_yaw, q2_hip_roll, q3_hip_pitch, q5_knee, q6_ankle_pitch, q7_ankle_roll] - - def apply(self, angulos_dir, angulos_esq): - for i, angle in enumerate(angulos_dir): - p.setJointMotorControl2(self.robot_id, self.JUNTAS_DIR[i], p.POSITION_CONTROL, - targetPosition=angle, force=5000, positionGain=1.0) - for i, angle in enumerate(angulos_esq): - p.setJointMotorControl2(self.robot_id, self.JUNTAS_ESQ[i], p.POSITION_CONTROL, - targetPosition=angle, force=5000, positionGain=1.0) - - for j in range(p.getNumJoints(self.robot_id)): - if j not in self.JUNTAS_DIR and j not in self.JUNTAS_ESQ: - target = self.fixed_joints.get(j, 0.0) # Busca o ângulo desejado (padrão é 0) - p.setJointMotorControl2(self.robot_id, j, p.POSITION_CONTROL, targetPosition=target, force=5000, positionGain=1.0) - - -class WalkingMath: - def __init__(self, h_target=0.2, t_step=0.6, y_sep=0.09): - self.H_TARGET = h_target - self.T_STEP = t_step - self.Y_SEP = y_sep - - def get_desired_state(self, t, cmd_vx): - """ - Gera as coordenadas matemáticas puras do CoM e dos pés ao longo do tempo. - """ - step_idx = int(t / self.T_STEP) - t_in_step = t % self.T_STEP - progresso = t_in_step / self.T_STEP - - is_left_support = (step_idx % 2 == 0) - - avanco_total = cmd_vx * t - - if is_left_support: - math_foot_l_x = avanco_total - math_foot_l_y = self.Y_SEP / 2.0 - math_foot_l_z = 0.0 - - passo_anterior = cmd_vx * (step_idx * self.T_STEP) - passo_futuro = cmd_vx * ((step_idx + 1) * self.T_STEP) - math_foot_r_x = passo_anterior + (passo_futuro - passo_anterior) * self.suavizar(progresso) - math_foot_r_y = -self.Y_SEP / 2.0 - math_foot_r_z = 0.05 * math.sin(math.pi * progresso) # Parábola - else: - math_foot_r_x = avanco_total - math_foot_r_y = -self.Y_SEP / 2.0 - math_foot_r_z = 0.0 - - passo_anterior = cmd_vx * (step_idx * self.T_STEP) - passo_futuro = cmd_vx * ((step_idx + 1) * self.T_STEP) - math_foot_l_x = passo_anterior + (passo_futuro - passo_anterior) * self.suavizar(progresso) - math_foot_l_y = self.Y_SEP / 2.0 - math_foot_l_z = 0.05 * math.sin(math.pi * progresso) - - math_com_x = avanco_total - ginga_amplitude = self.Y_SEP / 2.0 - 0.01 - math_com_y = ginga_amplitude * math.sin(math.pi * (t / self.T_STEP)) - - return (math_com_x, math_com_y), (math_foot_r_x, math_foot_r_y, math_foot_r_z), (math_foot_l_x, math_foot_l_y, math_foot_l_z) - - def suavizar(self, x): - return 0.5 * (1.0 - math.cos(math.pi * x)) - - -def main(): - p.connect(p.GUI) - p.setAdditionalSearchPath(pybullet_data.getDataPath()) - p.setGravity(0, 0, -9.81) - p.loadURDF("plane.urdf") - - robotId = p.loadURDF("urdf/aurea_urdf_pkg.urdf", [0, 0, 0.45], useFixedBase=True) - - ik = RelativeAnalyticalIK(robotId) - brain = WalkingMath(h_target=0.2) - - ID_R_SHO_PITCH = 5 - ID_L_SHO_PITCH = 2 - ID_R_SHO_ROLL = 6 - ID_L_SHO_ROLL = 3 - ID_R_ELBOW = 7 - ID_L_ELBOW = 4 - - ik.set_fixed_joint(ID_R_SHO_ROLL, 1.4) - ik.set_fixed_joint(ID_L_SHO_ROLL, -1.4) - - ik.set_fixed_joint(ID_R_SHO_PITCH, 0.4) - ik.set_fixed_joint(ID_L_SHO_PITCH, 0.4) - - - ik.set_fixed_joint(ID_R_ELBOW, 1.9) - ik.set_fixed_joint(ID_L_ELBOW, -1.9) - - print("Iniciando a First Pose...") - - ang_d_init = ik.solve_leg(0.0, -brain.Y_SEP/2, -brain.H_TARGET, is_left=False) - ang_e_init = ik.solve_leg(0.0, brain.Y_SEP/2, -brain.H_TARGET, is_left=True) - - for i, j in enumerate(ik.JUNTAS_DIR): - p.resetJointState(robotId, j, ang_d_init[i]) - for i, j in enumerate(ik.JUNTAS_ESQ): - p.resetJointState(robotId, j, ang_e_init[i]) - - for j_arm, ang_arm in ik.fixed_joints.items(): - p.resetJointState(robotId, j_arm, ang_arm) - - for _ in range(360): - ik.apply(ang_d_init, ang_e_init) - p.stepSimulation() - time.sleep(1/240.) - - print("First Pose estabilizada! Iniciando a Caminhada...") - # ======================================== - - for _ in range(100): - ang_d = ik.solve_leg(0.0, -brain.Y_SEP/2, -brain.H_TARGET, is_left=False) - ang_e = ik.solve_leg(0.0, brain.Y_SEP/2, -brain.H_TARGET, is_left=True) - ik.apply(ang_d, ang_e) - p.stepSimulation() - - print("Iniciando Caminhada Relativa!") - t = 0.0 - dt_sim = 1/240. - - cmd_vx = 0.07 - - while True: - com_math, foot_r_math, foot_l_math = brain.get_desired_state(t, cmd_vx) - - dx_r = foot_r_math[0] - com_math[0] - dy_r = foot_r_math[1] - com_math[1] - dz_r = foot_r_math[2] - brain.H_TARGET - - dx_l = foot_l_math[0] - com_math[0] - dy_l = foot_l_math[1] - com_math[1] - dz_l = foot_l_math[2] - brain.H_TARGET - - angulos_d = ik.solve_leg(dx_r, dy_r, dz_r, is_left=False) - angulos_e = ik.solve_leg(dx_l, dy_l, dz_l, is_left=True) - - ik.apply(angulos_d, angulos_e) - p.stepSimulation() - - t += dt_sim - time.sleep(dt_sim) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/pybullet_sim/ik_solver.py b/pybullet_sim/ik_solver.py deleted file mode 100644 index e9b24f9..0000000 --- a/pybullet_sim/ik_solver.py +++ /dev/null @@ -1,120 +0,0 @@ -import math -import pybullet as p - -class RelativeAnalyticalIK: - def __init__(self, robot_id): - self.robot_id = robot_id - - # --- PARÂMETROS DO ROBÔ --- - self.L_COXA = 0.12 - self.L_TIBIA = 0.085 - self.D_QUADRIL_OFFSET = 0.0425 - self.D_TORNOZELO_SOLA = 0.045 - - # IDs das juntas - self.JUNTAS_DIR = [8, 9, 10, 11, 12, 13] - self.JUNTAS_ESQ = [14, 15, 16, 17, 18, 19] - - self.fixed_joints = {} - - def set_fixed_joint(self, joint_id, target_angle): - self.fixed_joints[joint_id] = target_angle - - def solve_leg(self, dx, dy, dz, is_left): - sinal_y = 1.0 if is_left else -1.0 - offset_y = self.D_QUADRIL_OFFSET * sinal_y - - rx = -dx - ry = -(dy - offset_y) - rz = -(dz + self.D_TORNOZELO_SOLA) - - C = math.sqrt(rx**2 + ry**2 + rz**2) - C = min(C, self.L_COXA + self.L_TIBIA - 0.001) - - cos_q5 = (C**2 - self.L_COXA**2 - self.L_TIBIA**2) / (2 * self.L_COXA * self.L_TIBIA) - cos_q5 = max(-1.0, min(1.0, cos_q5)) - q5_knee = math.acos(cos_q5) - - q7_ankle_roll = math.atan2(ry, rz) - alpha = math.asin(max(-1.0, min(1.0, (self.L_COXA / C) * math.sin(q5_knee)))) - q6_ankle_pitch = -math.atan2(rx, math.copysign(1.0, rz) * math.sqrt(ry**2 + rz**2)) - alpha - - q1_hip_yaw = 0.0 - q2_hip_roll = -q7_ankle_roll - q3_hip_pitch = -q6_ankle_pitch - q5_knee - - return [q1_hip_yaw, q2_hip_roll, q3_hip_pitch, q5_knee, q6_ankle_pitch, q7_ankle_roll] - - def apply(self, angulos_dir, angulos_esq): - for i, angle in enumerate(angulos_dir): - p.setJointMotorControl2(self.robot_id, self.JUNTAS_DIR[i], p.POSITION_CONTROL, targetPosition=angle, force=100, positionGain=1.0) - for i, angle in enumerate(angulos_esq): - p.setJointMotorControl2(self.robot_id, self.JUNTAS_ESQ[i], p.POSITION_CONTROL, targetPosition=angle, force=100, positionGain=1.0) - - for j in range(p.getNumJoints(self.robot_id)): - if j not in self.JUNTAS_DIR and j not in self.JUNTAS_ESQ: - target = self.fixed_joints.get(j, 0.0) - p.setJointMotorControl2(self.robot_id, j, p.POSITION_CONTROL, targetPosition=target, force=100, positionGain=1.0) - -class NumericalIK: - - def __init__(self, robot_id, foot_r_link_idx=12, foot_l_link_idx=18): - self.robot_id = robot_id - self.D_TORNOZELO_SOLA = 0.048 - - self.JUNTAS_DIR = [8, 9, 10, 11, 12, 13] - self.JUNTAS_ESQ = [14, 15, 16, 17, 18, 19] - - self.FOOT_R_LINK = foot_r_link_idx - self.FOOT_L_LINK = foot_l_link_idx - - self.fixed_joints = {} - - self.joint_to_dof = {} - dof_idx = 0 - for j in range(p.getNumJoints(self.robot_id)): - if p.getJointInfo(self.robot_id, j)[2] != p.JOINT_FIXED: - self.joint_to_dof[j] = dof_idx - dof_idx += 1 - - def set_fixed_joint(self, joint_id, target_angle): - self.fixed_joints[joint_id] = target_angle - - def solve_leg(self, dx, dy, dz, is_left, h_target=0.22): - - base_pos, _ = p.getBasePositionAndOrientation(self.robot_id) - - target_z = (base_pos[2] - h_target) + (dz + self.D_TORNOZELO_SOLA) - - target_pos = [base_pos[0] + dx, base_pos[1] + dy, target_z] - target_orn = p.getQuaternionFromEuler([0, 0, 0]) # Pé paralelo ao mundo - - link_alvo = self.FOOT_L_LINK if is_left else self.FOOT_R_LINK - - joint_poses = p.calculateInverseKinematics( - self.robot_id, - link_alvo, - targetPosition=target_pos, - targetOrientation=target_orn, - maxNumIterations=100, - residualThreshold=1e-4 - ) - - juntas_da_perna = self.JUNTAS_ESQ if is_left else self.JUNTAS_DIR - angulos = [] - for j in juntas_da_perna: - dof_index = self.joint_to_dof[j] - angulos.append(joint_poses[dof_index]) - - return angulos - - def apply(self, angulos_dir, angulos_esq, force=5000, gain=1.0): - for i, angle in enumerate(angulos_dir): - p.setJointMotorControl2(self.robot_id, self.JUNTAS_DIR[i], p.POSITION_CONTROL, targetPosition=angle, force=force, positionGain=gain) - for i, angle in enumerate(angulos_esq): - p.setJointMotorControl2(self.robot_id, self.JUNTAS_ESQ[i], p.POSITION_CONTROL, targetPosition=angle, force=force, positionGain=gain) - - for j in range(p.getNumJoints(self.robot_id)): - if j not in self.JUNTAS_DIR and j not in self.JUNTAS_ESQ: - target = self.fixed_joints.get(j, 0.0) - p.setJointMotorControl2(self.robot_id, j, p.POSITION_CONTROL, targetPosition=target, force=force, positionGain=gain) \ No newline at end of file diff --git a/pybullet_sim/kinematics.py b/pybullet_sim/kinematics.py new file mode 100644 index 0000000..1213055 --- /dev/null +++ b/pybullet_sim/kinematics.py @@ -0,0 +1,95 @@ +""" +Analytical leg IK for the Aurea robot, relative to the torso. + +The foot target is given as an offset (dx, dy, dz) from the torso centre, so +the walking code never needs the robot's pose in the world. +""" + +import math + +import pybullet as p + +# Joint order inside each leg, as returned by LegIK.solve_leg +LEG_JOINTS = ["hip_yaw", "hip_roll", "hip_pitch", "knee", "ank_pitch", "ank_roll"] + + +class LegIK: + # ── Robot dimensions [m] ────────────────────────────────────────────── + L_THIGH = 0.12 # hip_pitch → knee + L_SHIN = 0.085 # knee → ank_pitch + HIP_OFFSET_Y = 0.0425 # torso centre → hip, lateral + ANKLE_TO_SOLE = 0.045 # ankle joints → foot sole + + # ── Position controller settings ────────────────────────────────────── + MOTOR_FORCE = 100.0 + POSITION_GAIN = 1.0 + + def __init__(self, robot_id: int): + self.robot_id = robot_id + + name_to_id = { + p.getJointInfo(robot_id, j)[1].decode(): j + for j in range(p.getNumJoints(robot_id)) + } + self.right_leg = [name_to_id[f"r_{name}"] for name in LEG_JOINTS] + self.left_leg = [name_to_id[f"l_{name}"] for name in LEG_JOINTS] + self._name_to_id = name_to_id + + # Target angles for every joint that is not part of a leg (default 0) + self.fixed_joints: dict[int, float] = {} + + def set_fixed_joint(self, joint_name: str, target_angle: float) -> None: + self.fixed_joints[self._name_to_id[joint_name]] = target_angle + + def solve_leg(self, dx: float, dy: float, dz: float, is_left: bool) -> list[float]: + """ + Joint angles that place the foot sole at (dx, dy, dz) from the torso centre. + + The foot is kept parallel to the ground and hip_yaw is fixed at 0, so the + problem reduces to a planar 2-link chain (thigh + shin) plus a roll. + Targets out of reach are clamped to a fully stretched leg. + """ + offset_y = self.HIP_OFFSET_Y * (1.0 if is_left else -1.0) + + # Vector from the ankle to the hip + rx = -dx + ry = -(dy - offset_y) + rz = -(dz + self.ANKLE_TO_SOLE) + + C = math.sqrt(rx**2 + ry**2 + rz**2) + C = min(C, self.L_THIGH + self.L_SHIN - 0.001) + + # Law of cosines for the knee + cos_knee = (C**2 - self.L_THIGH**2 - self.L_SHIN**2) / (2 * self.L_THIGH * self.L_SHIN) + knee = math.acos(max(-1.0, min(1.0, cos_knee))) + + ankle_roll = math.atan2(ry, rz) + alpha = math.asin(max(-1.0, min(1.0, (self.L_THIGH / C) * math.sin(knee)))) + ankle_pitch = -math.atan2(rx, math.copysign(1.0, rz) * math.sqrt(ry**2 + rz**2)) - alpha + + # Foot parallel to the ground → hip compensates the ankle and knee + hip_yaw = 0.0 + hip_roll = -ankle_roll + hip_pitch = -ankle_pitch - knee + + return [hip_yaw, hip_roll, hip_pitch, knee, ankle_pitch, ankle_roll] + + def apply(self, right_angles: list[float], left_angles: list[float]) -> None: + """Send leg angles and hold every other joint at its fixed target.""" + targets = dict(zip(self.right_leg, right_angles)) + targets.update(zip(self.left_leg, left_angles)) + for j in range(p.getNumJoints(self.robot_id)): + target = targets.get(j, self.fixed_joints.get(j, 0.0)) + p.setJointMotorControl2(self.robot_id, j, p.POSITION_CONTROL, + targetPosition=target, + force=self.MOTOR_FORCE, + positionGain=self.POSITION_GAIN) + + def reset(self, right_angles: list[float], left_angles: list[float]) -> None: + """Teleport the joints to the given angles (used for the initial pose).""" + for j, angle in zip(self.right_leg, right_angles): + p.resetJointState(self.robot_id, j, angle) + for j, angle in zip(self.left_leg, left_angles): + p.resetJointState(self.robot_id, j, angle) + for j, angle in self.fixed_joints.items(): + p.resetJointState(self.robot_id, j, angle) diff --git a/pybullet_sim/main.py b/pybullet_sim/main.py index 34acea0..9775d6a 100644 --- a/pybullet_sim/main.py +++ b/pybullet_sim/main.py @@ -1,96 +1,126 @@ +""" +Aurea robot walking in PyBullet, with an MPC-planned CoM and analytical IK. + +Run (from any directory): + + python pybullet_sim/main.py # GUI, cmd_vx = 0.1 m/s + python pybullet_sim/main.py --vx 0.05 + python pybullet_sim/main.py --headless --duration 10 + +Note: the MPC runs in open loop. The CoM state fed back into the MPC is its +own prediction, not a measurement from the simulator, so the planner acts as a +trajectory generator and the PD joint controllers do the stabilisation. +""" + +import argparse +import os +import time + import pybullet as p import pybullet_data -import time -from ik_solver import RelativeAnalyticalIK, NumericalIK -from trajectory_generator import TrajectoryGenerator +from kinematics import LegIK +from mpc_planner import MPCPlanner + +URDF_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "assets", "aurea", "aurea.urdf") + +DT_SIM = 1 / 240. # physics timestep [s] +DT_MPC = 0.02 # MPC timestep [s] -def setup_first_pose(robotId, ik, brain): - """ Configura a pose inicial de agachamento e braços fixos """ - ID_R_SHO_PITCH, ID_L_SHO_PITCH = 5, 2 - ID_R_SHO_ROLL, ID_L_SHO_ROLL = 6, 3 - ID_R_ELBOW, ID_L_ELBOW = 7, 4 +# KNOWN ISSUE: DT_MPC is not a multiple of DT_SIM. int(0.02 / (1/240)) = 4, so +# each MPC tick advances the trajectory clock t by 0.020 s while physics only +# advances 4/240 ≈ 0.0167 s: the gait runs 1.2× faster than planned (steps of +# ~0.25 s instead of T_STEP = 0.3 s, ~0.12 m/s for cmd_vx = 0.1). The current +# gains were tuned with this mismatch; making the clocks consistent (e.g. +# DT_MPC = 5 * DT_SIM) makes the robot fall until the planner is re-tuned. +PHYSICS_STEPS_PER_MPC = int(DT_MPC / DT_SIM) - ik.set_fixed_joint(ID_R_SHO_ROLL, 1.5) - ik.set_fixed_joint(ID_L_SHO_ROLL, -1.5) - ik.set_fixed_joint(ID_R_SHO_PITCH, 0.6) - ik.set_fixed_joint(ID_L_SHO_PITCH, 0.6) - ik.set_fixed_joint(ID_R_ELBOW, 2.1) - ik.set_fixed_joint(ID_L_ELBOW, -2.1) +# Fixed arm pose [rad] held while walking +ARM_POSE = { + "r_sho_roll": 1.5, "l_sho_roll": -1.5, + "r_sho_pitch": 0.6, "l_sho_pitch": 0.6, + "r_el": 2.1, "l_el": -2.1, +} - print("Iniciando a First Pose...") - ang_d_init = ik.solve_leg(0.0, -brain.Y_SEP/2, -brain.H_TARGET, is_left=False) - ang_e_init = ik.solve_leg(0.0, brain.Y_SEP/2, -brain.H_TARGET, is_left=True) - for i, j in enumerate(ik.JUNTAS_DIR): p.resetJointState(robotId, j, ang_d_init[i]) - for i, j in enumerate(ik.JUNTAS_ESQ): p.resetJointState(robotId, j, ang_e_init[i]) - for j_arm, ang_arm in ik.fixed_joints.items(): p.resetJointState(robotId, j_arm, ang_arm) +def setup_first_pose(ik: LegIK, planner: MPCPlanner, realtime: bool) -> None: + """Crouch to the walking height with the arms fixed, then let it settle.""" + for joint, angle in ARM_POSE.items(): + ik.set_fixed_joint(joint, angle) - for _ in range(360): - ik.apply(ang_d_init, ang_e_init) + print("Moving to the initial pose...") + right = ik.solve_leg(0.0, -planner.Y_SEP / 2, -planner.H_TARGET, is_left=False) + left = ik.solve_leg(0.0, planner.Y_SEP / 2, -planner.H_TARGET, is_left=True) + ik.reset(right, left) + + for _ in range(360): # 1.5 s + ik.apply(right, left) p.stepSimulation() - time.sleep(1/240.) - print("First Pose estabilizada!") + if realtime: + time.sleep(DT_SIM) + print("Initial pose settled.") + -def main(): - p.connect(p.GUI) +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__.split("\n")[1]) + parser.add_argument("--vx", type=float, default=0.1, help="forward speed [m/s]") + parser.add_argument("--headless", action="store_true", help="run without GUI, as fast as possible") + parser.add_argument("--duration", type=float, default=None, help="stop after this many seconds") + args = parser.parse_args() + + p.connect(p.DIRECT if args.headless else p.GUI) p.setAdditionalSearchPath(pybullet_data.getDataPath()) p.setGravity(0, 0, -9.81) p.loadURDF("plane.urdf") - - robotId = p.loadURDF("urdf/aurea_urdf_pkg.urdf", [0, 0, 0.35], useFixedBase=False) - - dt_mpc = 0.02 - ik = RelativeAnalyticalIK(robotId) - brain = TrajectoryGenerator(h_target=0.2, dt_mpc=dt_mpc) + robot_id = p.loadURDF(URDF_PATH, [0, 0, 0.35], useFixedBase=False) - setup_first_pose(robotId, ik, brain) + ik = LegIK(robot_id) + planner = MPCPlanner(h_target=0.2, dt_mpc=DT_MPC) + realtime = not args.headless + setup_first_pose(ik, planner, realtime) + # MPC state (open loop, see module docstring) com_x, com_vx = 0.0, 0.0 com_y, com_vy = 0.0, 0.0 - - t_global = 0.0 - dt_sim = 1/240. - passos_fisica_por_mpc = int(dt_mpc / dt_sim) - - cmd_vx = 0.1 - - print("Iniciando Caminhada Controlada por MPC!") - while True: - - - com_ref_x, zmp_ref_x, zmp_min_x, zmp_max_x = brain.get_references_x(t_global, cmd_vx) - com_ref_y, zmp_ref_y, zmp_min_y, zmp_max_y = brain.get_references_y(t_global) - - next_com_x, next_com_vx = brain.solve_mpc(com_x, com_vx, com_ref_x, zmp_ref_x, zmp_min_x, zmp_max_x) - next_com_y, next_com_vy = brain.solve_mpc(com_y, com_vy, com_ref_y, zmp_ref_y, zmp_min_y, zmp_max_y) - - - for step_fisica in range(passos_fisica_por_mpc): - t_instante = t_global + (step_fisica * dt_sim) - progresso_mpc = (step_fisica + 1) / passos_fisica_por_mpc - - com_math_x = com_x + (next_com_x - com_x) * progresso_mpc - com_math_y = com_y + (next_com_y - com_y) * progresso_mpc - foot_r_math, foot_l_math = brain.get_foot_trajectories(t_instante, cmd_vx) - - dx_r, dy_r, dz_r = foot_r_math[0] - com_math_x, foot_r_math[1] - com_math_y, foot_r_math[2] - brain.H_TARGET - dx_l, dy_l, dz_l = foot_l_math[0] - com_math_x, foot_l_math[1] - com_math_y, foot_l_math[2] - brain.H_TARGET - - _, orn_b = p.getBasePositionAndOrientation(robotId) - roll, pitch, _ = p.getEulerFromQuaternion(orn_b) - - angulos_d = ik.solve_leg(dx_r, dy_r, dz_r, is_left=False) - angulos_e = ik.solve_leg(dx_l, dy_l, dz_l, is_left=True) - - ik.apply(angulos_d, angulos_e) + + t = 0.0 + + print(f"Walking with MPC at cmd_vx = {args.vx} m/s") + while args.duration is None or t < args.duration: + com_ref_x, zmp_ref_x, zmp_min_x, zmp_max_x = planner.get_references_x(t, args.vx) + com_ref_y, zmp_ref_y, zmp_min_y, zmp_max_y = planner.get_references_y(t) + + next_com_x, next_com_vx = planner.solve_mpc(com_x, com_vx, com_ref_x, zmp_ref_x, zmp_min_x, zmp_max_x) + next_com_y, next_com_vy = planner.solve_mpc(com_y, com_vy, com_ref_y, zmp_ref_y, zmp_min_y, zmp_max_y) + + for i in range(PHYSICS_STEPS_PER_MPC): + # Linear interpolation of the CoM between two MPC samples + alpha = (i + 1) / PHYSICS_STEPS_PER_MPC + com_now_x = com_x + (next_com_x - com_x) * alpha + com_now_y = com_y + (next_com_y - com_y) * alpha + foot_r, foot_l = planner.get_foot_trajectories(t + i * DT_SIM, args.vx) + + # Feet relative to the torso → IK + right = ik.solve_leg(foot_r[0] - com_now_x, foot_r[1] - com_now_y, + foot_r[2] - planner.H_TARGET, is_left=False) + left = ik.solve_leg(foot_l[0] - com_now_x, foot_l[1] - com_now_y, + foot_l[2] - planner.H_TARGET, is_left=True) + + ik.apply(right, left) p.stepSimulation() - time.sleep(dt_sim) - + if realtime: + time.sleep(DT_SIM) + com_x, com_vx = next_com_x, next_com_vx com_y, com_vy = next_com_y, next_com_vy - t_global += dt_mpc + t += DT_MPC + + pos, _ = p.getBasePositionAndOrientation(robot_id) + print(f"Finished at t={t:.1f}s, base = ({pos[0]:.3f}, {pos[1]:.3f}, {pos[2]:.3f})") + p.disconnect() + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pybullet_sim/medir_urdf.py b/pybullet_sim/medir_urdf.py deleted file mode 100644 index 516583f..0000000 --- a/pybullet_sim/medir_urdf.py +++ /dev/null @@ -1,31 +0,0 @@ -import pybullet as p -import pybullet_data -import math - -def main(): - p.connect(p.DIRECT) - p.setAdditionalSearchPath(pybullet_data.getDataPath()) - - robotId = p.loadURDF("urdf/aurea_urdf_pkg.urdf", useFixedBase=True) - - print("\n" + "="*70) - print("URDF (Medidas de Links e Juntas)") - print("="*70) - - for j in range(p.getNumJoints(robotId)): - info = p.getJointInfo(robotId, j) - - id_junta = info[0] - nome_junta = info[1].decode('utf-8') - - pos_relativa = info[14] - - comprimento = math.sqrt(pos_relativa[0]**2 + pos_relativa[1]**2 + pos_relativa[2]**2) - - print(f"ID: {id_junta:02d} | Junta: {nome_junta:<25} | Vetor(X,Y,Z): {pos_relativa[0]:.4f}, {pos_relativa[1]:.4f}, {pos_relativa[2]:.4f} | Comprimento: {comprimento:.4f} m") - - print("="*70 + "\n") - p.disconnect() - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/pybullet_sim/mpc_planner.py b/pybullet_sim/mpc_planner.py new file mode 100644 index 0000000..959f4ac --- /dev/null +++ b/pybullet_sim/mpc_planner.py @@ -0,0 +1,178 @@ +""" +CoM trajectory planner using Model Predictive Control (MPC) on the LIPM. + +For each horizontal axis (x and y separately) the planner solves the QP + + minimise Σ_k W_COM·(x_k - x_ref_k)² + W_ZMP·(zmp_k - zmp_ref_k)² + W_VEL·v_k² + subject to x_{k+1} = x_k + v_k·dt + v_{k+1} = v_k + ω²·(x_k - zmp_k)·dt (LIPM, ω = √(g/h)) + zmp_min_k ≤ zmp_k ≤ zmp_max_k (ZMP inside the support polygon) + +over a horizon of N steps, and returns the first predicted state. + +It also generates the reference footstep plan: a fixed step period, alternating +support feet, stride length = cmd_vx · T_STEP. +""" + +import math + +import cvxpy as cp +import numpy as np + + +class MPCPlanner: + # Cost weights + W_COM = 800.0 # track the CoM reference + W_ZMP = 2.0 # keep the ZMP close to the centre of the support foot + W_VEL = 10.0 # penalise CoM velocity (smoothness) + + def __init__(self, h_target=0.22, t_step=0.3, ds_ratio=0.1, y_sep=0.085, + dt_mpc=0.02, n_horizon=25): + self.H_TARGET = h_target # CoM height [m] + self.T_STEP = t_step # step period [s] + self.T_DS = t_step * ds_ratio # double-support duration [s] + self.T_SS = t_step * (1.0 - ds_ratio) # single-support duration [s] + self.Y_SEP = y_sep # distance between the feet [m] + self.SWING_HEIGHT = 0.05 # max swing-foot height [m] + + self.dt_mpc = dt_mpc + self.N = n_horizon + self.g = 9.81 + self.omega = math.sqrt(self.g / self.H_TARGET) + + # Half-size of the ZMP support region around the foot centre [m] + self.FOOT_MARGIN_X = 0.07 + self.FOOT_MARGIN_Y = 0.03 + + self._setup_mpc_problem() + + def _setup_mpc_problem(self): + # Parameters change every solve; the problem structure is built once + self.p_x0 = cp.Parameter() + self.p_vx0 = cp.Parameter() + self.p_com_ref = cp.Parameter(self.N) + self.p_zmp_ref = cp.Parameter(self.N) + self.p_zmp_min = cp.Parameter(self.N) + self.p_zmp_max = cp.Parameter(self.N) + + self.v_x = cp.Variable(self.N + 1) + self.v_vx = cp.Variable(self.N + 1) + self.v_zmp = cp.Variable(self.N) + + cost = 0 + constraints = [self.v_x[0] == self.p_x0, self.v_vx[0] == self.p_vx0] + + for k in range(self.N): + ax = (self.omega**2) * (self.v_x[k] - self.v_zmp[k]) + constraints += [self.v_x[k + 1] == self.v_x[k] + self.v_vx[k] * self.dt_mpc] + constraints += [self.v_vx[k + 1] == self.v_vx[k] + ax * self.dt_mpc] + + constraints += [self.v_zmp[k] >= self.p_zmp_min[k]] + constraints += [self.v_zmp[k] <= self.p_zmp_max[k]] + + cost += self.W_COM * cp.sum_squares(self.v_x[k] - self.p_com_ref[k]) + cost += self.W_ZMP * cp.sum_squares(self.v_zmp[k] - self.p_zmp_ref[k]) + cost += self.W_VEL * cp.sum_squares(self.v_vx[k]) + + self.prob = cp.Problem(cp.Minimize(cost), constraints) + + def solve_mpc(self, x0, vx0, com_ref, zmp_ref, zmp_min, zmp_max): + """Return the CoM (position, velocity) one MPC step ahead.""" + self.p_x0.value = x0 + self.p_vx0.value = vx0 + self.p_com_ref.value = com_ref + self.p_zmp_ref.value = zmp_ref + self.p_zmp_min.value = zmp_min + self.p_zmp_max.value = zmp_max + + try: + self.prob.solve(solver=cp.OSQP, warm_start=True) + except cp.error.SolverError as exc: + print(f"[MPC] solver error: {exc}") + + if self.v_x.value is not None and self.prob.status in (cp.OPTIMAL, cp.OPTIMAL_INACCURATE): + return self.v_x.value[1], self.v_vx.value[1] + + # No solution: keep moving at constant velocity for this step + print(f"[MPC] no solution (status={self.prob.status}); extrapolating") + return x0 + vx0 * self.dt_mpc, vx0 + + def get_foot_trajectories(self, t, cmd_vx): + """World (x, y, z) of the right and left foot soles at time t.""" + step_idx = int(t / self.T_STEP) + t_in_step = t % self.T_STEP + is_left_support = (step_idx % 2 == 0) + + current_x = cmd_vx * (step_idx * self.T_STEP) + next_x = cmd_vx * ((step_idx + 1) * self.T_STEP) + previous_x = cmd_vx * ((step_idx - 1) * self.T_STEP) if step_idx > 0 else 0.0 + + swing_progress = 0.0 if t_in_step < self.T_DS else (t_in_step - self.T_DS) / self.T_SS + swing_z = self.SWING_HEIGHT * math.sin(math.pi * swing_progress) if swing_progress > 0 else 0.0 + swing_x = previous_x + (next_x - previous_x) * self._s_curve(swing_progress) + + if is_left_support: + r_foot = (swing_x, -self.Y_SEP / 2.0, swing_z) + l_foot = (current_x, self.Y_SEP / 2.0, 0.0) + else: + r_foot = (current_x, -self.Y_SEP / 2.0, 0.0) + l_foot = (swing_x, self.Y_SEP / 2.0, swing_z) + + return r_foot, l_foot + + def get_references_y(self, t_current): + """Lateral CoM/ZMP references and ZMP bounds over the horizon.""" + com_ref, zmp_ref = np.zeros(self.N), np.zeros(self.N) + zmp_min, zmp_max = np.zeros(self.N), np.zeros(self.N) + for k in range(self.N): + t_future = t_current + k * self.dt_mpc + step_idx = int(t_future / self.T_STEP) + t_in_step = t_future % self.T_STEP + is_left_support = (step_idx % 2 == 0) + + support_y = self.Y_SEP / 2.0 if is_left_support else -self.Y_SEP / 2.0 + + if step_idx == 0: + previous_y = 0.0 + else: + previous_y = -self.Y_SEP / 2.0 if is_left_support else self.Y_SEP / 2.0 + + if t_in_step < self.T_DS: + # Double support: shift the CoM towards the new support foot; + # the ZMP may lie anywhere between both feet + com_ref[k] = previous_y + (support_y - previous_y) * self._s_curve(t_in_step / self.T_DS) + zmp_ref[k] = support_y + zmp_min[k] = -self.Y_SEP / 2.0 - self.FOOT_MARGIN_Y + zmp_max[k] = self.Y_SEP / 2.0 + self.FOOT_MARGIN_Y + else: + # Single support: the ZMP must stay under the support foot + com_ref[k], zmp_ref[k] = support_y, support_y + zmp_min[k] = support_y - self.FOOT_MARGIN_Y + zmp_max[k] = support_y + self.FOOT_MARGIN_Y + return com_ref, zmp_ref, zmp_min, zmp_max + + def get_references_x(self, t_current, cmd_vx): + """Forward CoM/ZMP references and ZMP bounds over the horizon.""" + com_ref, zmp_ref = np.zeros(self.N), np.zeros(self.N) + zmp_min, zmp_max = np.zeros(self.N), np.zeros(self.N) + for k in range(self.N): + t_future = t_current + k * self.dt_mpc + step_idx = int(t_future / self.T_STEP) + t_in_step = t_future % self.T_STEP + + current_x = cmd_vx * (step_idx * self.T_STEP) + previous_x = cmd_vx * ((step_idx - 1) * self.T_STEP) if step_idx > 0 else 0.0 + + com_ref[k] = cmd_vx * t_future + zmp_ref[k] = current_x + + if t_in_step < self.T_DS: + zmp_min[k], zmp_max[k] = previous_x - self.FOOT_MARGIN_X, current_x + self.FOOT_MARGIN_X + else: + zmp_min[k], zmp_max[k] = current_x - self.FOOT_MARGIN_X, current_x + self.FOOT_MARGIN_X + return com_ref, zmp_ref, zmp_min, zmp_max + + @staticmethod + def _s_curve(x): + """Smooth 0 → 1 cosine ramp for x in [0, 1].""" + return 0.5 * (1.0 - math.cos(math.pi * max(0.0, min(1.0, x)))) diff --git a/pybullet_sim/scene_test.xml b/pybullet_sim/scene_test.xml deleted file mode 100644 index 65a02d2..0000000 --- a/pybullet_sim/scene_test.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/pybullet_sim/trajectory_generator.py b/pybullet_sim/trajectory_generator.py deleted file mode 100644 index 70328f9..0000000 --- a/pybullet_sim/trajectory_generator.py +++ /dev/null @@ -1,137 +0,0 @@ -import math -import numpy as np -import cvxpy as cp - -class TrajectoryGenerator: - def __init__(self, h_target=0.22, t_step=0.3, ds_ratio=0.1, y_sep=0.085, dt_mpc=0.02, n_horizon=25): - self.H_TARGET = h_target - self.T_STEP = t_step - self.T_DS = t_step * ds_ratio - self.T_SS = t_step * (1.0 - ds_ratio) - self.Y_SEP = y_sep - - self.dt_mpc = dt_mpc - self.N = n_horizon - self.g = 9.81 - self.omega = math.sqrt(self.g / self.H_TARGET) - - self.FOOT_MARGIN_X = 0.07 - self.FOOT_MARGIN_Y = 0.03 - - self._setup_mpc_problem() - - def _setup_mpc_problem(self): - self.p_x0 = cp.Parameter() - self.p_vx0 = cp.Parameter() - self.p_com_ref = cp.Parameter(self.N) - self.p_zmp_ref = cp.Parameter(self.N) - self.p_zmp_min = cp.Parameter(self.N) - self.p_zmp_max = cp.Parameter(self.N) - - self.v_x = cp.Variable(self.N + 1) - self.v_vx = cp.Variable(self.N + 1) - self.v_zmp = cp.Variable(self.N) - - cost = 0 - constraints = [self.v_x[0] == self.p_x0, self.v_vx[0] == self.p_vx0] - - for k in range(self.N): - ax = (self.omega**2) * (self.v_x[k] - self.v_zmp[k]) - constraints += [self.v_x[k+1] == self.v_x[k] + self.v_vx[k] * self.dt_mpc] - constraints += [self.v_vx[k+1] == self.v_vx[k] + ax * self.dt_mpc] - - constraints += [self.v_zmp[k] >= self.p_zmp_min[k]] - constraints += [self.v_zmp[k] <= self.p_zmp_max[k]] - - cost += 800.0 * cp.sum_squares(self.v_x[k] - self.p_com_ref[k]) - cost += 2.0 * cp.sum_squares(self.v_zmp[k] - self.p_zmp_ref[k]) - cost += 10.0 * cp.sum_squares(self.v_vx[k]) - - self.prob = cp.Problem(cp.Minimize(cost), constraints) - - def solve_mpc(self, x0, vx0, com_ref, zmp_ref, zmp_min, zmp_max): - self.p_x0.value = x0 - self.p_vx0.value = vx0 - self.p_com_ref.value = com_ref - self.p_zmp_ref.value = zmp_ref - self.p_zmp_min.value = zmp_min - self.p_zmp_max.value = zmp_max - - try: - self.prob.solve(solver=cp.OSQP, warm_start=True) - if self.v_x.value is not None: - return self.v_x.value[1], self.v_vx.value[1] - except: pass - return x0 + vx0 * self.dt_mpc, vx0 - - def get_foot_trajectories(self, t, cmd_vx): - step_idx = int(t / self.T_STEP) - t_in_step = t % self.T_STEP - is_left_support = (step_idx % 2 == 0) - - avanco_atual = cmd_vx * (step_idx * self.T_STEP) - avanco_futuro = cmd_vx * ((step_idx + 1) * self.T_STEP) - passo_anterior = cmd_vx * ((step_idx - 1) * self.T_STEP) if step_idx > 0 else 0.0 - - progresso_swing = 0.0 if t_in_step < self.T_DS else (t_in_step - self.T_DS) / self.T_SS - - z_math = 0.05 * math.sin(math.pi * progresso_swing) if progresso_swing > 0 else 0.0 - - if is_left_support: - r_foot = (passo_anterior + (avanco_futuro - passo_anterior) * self._s_curve(progresso_swing), - -self.Y_SEP / 2.0, - z_math) - l_foot = (avanco_atual, self.Y_SEP / 2.0, 0.0) - else: - r_foot = (avanco_atual, -self.Y_SEP / 2.0, 0.0) - l_foot = (passo_anterior + (avanco_futuro - passo_anterior) * self._s_curve(progresso_swing), - self.Y_SEP / 2.0, - z_math) - - return r_foot, l_foot - - def get_references_y(self, t_current): - com_ref, zmp_ref, zmp_min, zmp_max = np.zeros(self.N), np.zeros(self.N), np.zeros(self.N), np.zeros(self.N) - for k in range(self.N): - t_futuro = t_current + k * self.dt_mpc - step_idx = int(t_futuro / self.T_STEP) - t_in_step = t_futuro % self.T_STEP - is_left_support = (step_idx % 2 == 0) - - alvo_y = self.Y_SEP / 2.0 if is_left_support else -self.Y_SEP / 2.0 - - if step_idx == 0: - pe_anterior_y = 0.0 - else: - pe_anterior_y = -self.Y_SEP / 2.0 if is_left_support else self.Y_SEP / 2.0 - - if t_in_step < self.T_DS: - com_ref[k] = pe_anterior_y + (alvo_y - pe_anterior_y) * self._s_curve(t_in_step / self.T_DS) - zmp_ref[k] = alvo_y - zmp_min[k], zmp_max[k] = -self.Y_SEP/2.0 - self.FOOT_MARGIN_Y, self.Y_SEP/2.0 + self.FOOT_MARGIN_Y - else: - com_ref[k], zmp_ref[k] = alvo_y, alvo_y - zmp_min[k], zmp_max[k] = alvo_y - self.FOOT_MARGIN_Y, alvo_y + self.FOOT_MARGIN_Y - return com_ref, zmp_ref, zmp_min, zmp_max - - def get_references_x(self, t_current, cmd_vx): - com_ref, zmp_ref, zmp_min, zmp_max = np.zeros(self.N), np.zeros(self.N), np.zeros(self.N), np.zeros(self.N) - for k in range(self.N): - t_futuro = t_current + k * self.dt_mpc - step_idx = int(t_futuro / self.T_STEP) - t_in_step = t_futuro % self.T_STEP - - avanco_atual = cmd_vx * (step_idx * self.T_STEP) - passo_anterior = cmd_vx * ((step_idx - 1) * self.T_STEP) if step_idx > 0 else 0.0 - - com_ref[k] = cmd_vx * t_futuro - zmp_ref[k] = avanco_atual - - if t_in_step < self.T_DS: - zmp_min[k], zmp_max[k] = passo_anterior - self.FOOT_MARGIN_X, avanco_atual + self.FOOT_MARGIN_X - else: - zmp_min[k], zmp_max[k] = avanco_atual - self.FOOT_MARGIN_X, avanco_atual + self.FOOT_MARGIN_X - return com_ref, zmp_ref, zmp_min, zmp_max - - def _s_curve(self, x): - return 0.5 * (1.0 - math.cos(math.pi * max(0.0, min(1.0, x)))) \ No newline at end of file diff --git a/tools/inspect_urdf.py b/tools/inspect_urdf.py new file mode 100644 index 0000000..667e94a --- /dev/null +++ b/tools/inspect_urdf.py @@ -0,0 +1,46 @@ +""" +Print every joint of the Aurea URDF with its offset from the parent link. + +Useful for measuring link lengths when writing analytical IK. + +Caveat: PyBullet reports parentFramePos relative to the parent's *inertial* +(centre of mass) frame, not the URDF link frame. For exact link lengths, read +the values in the URDF itself. + + python tools/inspect_urdf.py [path/to/robot.urdf] +""" + +import math +import os +import sys + +import pybullet as p + +DEFAULT_URDF = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "..", "pybullet_sim", "assets", "aurea", "aurea.urdf") + + +def main() -> None: + urdf = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_URDF + + p.connect(p.DIRECT) + robot_id = p.loadURDF(urdf, useFixedBase=True) + + print("\n" + "=" * 100) + print(f"Joints of {os.path.basename(urdf)} (offset from the parent's inertial frame)") + print("=" * 100) + + for j in range(p.getNumJoints(robot_id)): + info = p.getJointInfo(robot_id, j) + name = info[1].decode("utf-8") + x, y, z = info[14] # parentFramePos + length = math.sqrt(x**2 + y**2 + z**2) + print(f"ID: {j:02d} | Joint: {name:<14} | Offset (x, y, z): " + f"{x:+.4f}, {y:+.4f}, {z:+.4f} | Length: {length:.4f} m") + + print("=" * 100 + "\n") + p.disconnect() + + +if __name__ == "__main__": + main() From f74727e66975a8dacfe0d443b28f9c69355e07c0 Mon Sep 17 00:00:00 2001 From: Pedro Henrique Bohling Peres Date: Sat, 3 Oct 2026 01:49:01 -0300 Subject: [PATCH 4/5] test: add pytest suite, pyproject and GitHub Actions CI --- .github/workflows/tests.yml | 20 ++++++ pyproject.toml | 27 ++++++++ tests/test_mujoco_sim.py | 67 +++++++++++++++++++ tests/test_pybullet_sim.py | 59 +++++++++++++++++ tests/test_trajectory_generator.py | 101 +++++++++++++++++++++++++++++ 5 files changed, 274 insertions(+) create mode 100644 .github/workflows/tests.yml create mode 100644 pyproject.toml create mode 100644 tests/test_mujoco_sim.py create mode 100644 tests/test_pybullet_sim.py create mode 100644 tests/test_trajectory_generator.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..a402b81 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,20 @@ +name: tests + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: pip + - name: Install dependencies + run: pip install -e ".[mujoco,pybullet,dev]" + - name: Run tests + run: pytest -v diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7d04a64 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[build-system] +requires = ["setuptools>=64"] +build-backend = "setuptools.build_meta" + +[project] +name = "humanoid-motion-predictive-control" +version = "0.1.0" +description = "Bipedal walking simulations (LIPM, MPC, analytical IK) in PyBullet and MuJoCo" +readme = "README.md" +license = { file = "LICENSE" } +authors = [{ name = "Pedro H. Peres" }] +requires-python = ">=3.10" +dependencies = ["numpy"] + +[project.optional-dependencies] +mujoco = ["mujoco>=3.2", "robot_descriptions>=1.12"] +pybullet = ["pybullet", "cvxpy", "osqp"] +tools = ["matplotlib"] +dev = ["pytest"] + +[tool.setuptools] +packages = ["mujoco_sim", "mujoco_sim.analytical_walking", "mujoco_sim.kinematics"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +pythonpath = [".", "pybullet_sim"] +markers = ["slow: runs a full physics simulation (deselect with -m 'not slow')"] diff --git a/tests/test_mujoco_sim.py b/tests/test_mujoco_sim.py new file mode 100644 index 0000000..41ac15f --- /dev/null +++ b/tests/test_mujoco_sim.py @@ -0,0 +1,67 @@ +"""IK against MuJoCo forward kinematics, and a full walking smoke test.""" + +import numpy as np +import pytest + +mujoco = pytest.importorskip("mujoco") +pytest.importorskip("robot_descriptions") + +from mujoco_sim.analytical_walking.walking_engine import WalkTarget # noqa: E402 +from mujoco_sim.kinematics.ik_solver import D_ANKLE_SOLE # noqa: E402 +from mujoco_sim.sim import Simulation # noqa: E402 + + +@pytest.fixture(scope="module") +def kinematic_sim(): + return Simulation(physics=False) + + +@pytest.mark.parametrize( + "body_pos, body_yaw, sole_pos, sole_yaw, leg", + [ + ((0.0, 0.0, 0.22), 0.0, (0.0, 0.054, 0.0), 0.0, "left"), + ((0.0, 0.0, 0.22), 0.0, (0.0, -0.054, 0.0), 0.0, "right"), + ((0.01, 0.0, 0.21), 0.2, (0.03, 0.06, 0.02), 0.3, "left"), + ((0.0, 0.01, 0.20), 0.0, (-0.03, -0.05, 0.01), -0.2, "right"), + ], +) +def test_ik_matches_forward_kinematics(kinematic_sim, body_pos, body_yaw, sole_pos, sole_yaw, leg): + """Set the joints to the IK solution and check where MuJoCo puts the foot.""" + sim = kinematic_sim + body_pos, sole_pos = np.array(body_pos), np.array(sole_pos) + other_sole = np.array([body_pos[0], -sole_pos[1], 0.0]) + if leg == "left": + target = WalkTarget(body_pos, body_yaw, sole_pos, sole_yaw, other_sole, 0.0) + else: + target = WalkTarget(body_pos, body_yaw, other_sole, 0.0, sole_pos, sole_yaw) + + sim._set_free_joint(body_pos, body_yaw) + leg_angles = sim._solve_legs(target) + assert len(leg_angles) == 2 + for angles in leg_angles: + sim._set_joints(angles) + mujoco.mj_forward(sim.model, sim.data) + + # IK places the ankle point (ank_pitch joint) D_ANKLE_SOLE above the sole target. + # 5 mm tolerance: the IK ignores the hip/ankle roll-axis offsets (see ik_solver.py) + side = leg[0] + ankle = sim.data.xanchor[sim.model.joint(f"{side}_ank_pitch").id] + np.testing.assert_allclose(ankle, sole_pos + [0, 0, D_ANKLE_SOLE], atol=5e-3) + + R_foot = sim.data.xmat[sim.model.body(f"{side}_ank_roll_link").id].reshape(3, 3) + assert np.arctan2(R_foot[1, 0], R_foot[0, 0]) == pytest.approx(sole_yaw, abs=1e-3) + assert R_foot[2, 2] == pytest.approx(1.0, abs=1e-4) # sole parallel to the floor + + +@pytest.mark.slow +@pytest.mark.parametrize("command", [{"vx": 0.05}, {"vy": 0.03}, {"wz": 0.5}]) +def test_robot_walks_without_falling(command): + sim = Simulation(physics=True) + sim.engine.set_command(**command) + start = sim.body_pos + while sim.time < 5.0: + sim.step() + assert sim.body_pos[2] > 0.18, f"robot fell at t={sim.time:.2f}s" + assert sum(sim.ik_failures.values()) == 0 + moved = np.linalg.norm(sim.body_pos[:2] - start[:2]) + abs(sim.body_yaw) + assert moved > 0.1 diff --git a/tests/test_pybullet_sim.py b/tests/test_pybullet_sim.py new file mode 100644 index 0000000..6463222 --- /dev/null +++ b/tests/test_pybullet_sim.py @@ -0,0 +1,59 @@ +"""MPC planner and leg IK of the PyBullet simulation.""" + +import numpy as np +import pytest + +pytest.importorskip("cvxpy") + +from mpc_planner import MPCPlanner # noqa: E402 + + +@pytest.fixture(scope="module") +def planner(): + return MPCPlanner(h_target=0.2, dt_mpc=0.02) + + +def test_mpc_respects_zmp_bounds(planner): + refs = planner.get_references_y(0.5) + x, _ = planner.solve_mpc(0.0, 0.0, *refs) + zmp, zmp_min, zmp_max = planner.v_zmp.value, refs[2], refs[3] + assert np.all(zmp >= zmp_min - 1e-4) and np.all(zmp <= zmp_max + 1e-4) + # The returned state is the first step of the optimal prediction + assert x == pytest.approx(planner.v_x.value[1]) + + +def test_mpc_follows_forward_reference(planner): + x, v, t = 0.0, 0.0, 0.0 + for _ in range(100): # 2 s + x, v = planner.solve_mpc(x, v, *planner.get_references_x(t, 0.1)) + t += planner.dt_mpc + assert x == pytest.approx(0.1 * t, abs=0.03) + + +def test_feet_alternate_support(planner): + r0, l0 = planner.get_foot_trajectories(planner.T_STEP * 0.5, 0.1) + r1, l1 = planner.get_foot_trajectories(planner.T_STEP * 1.5, 0.1) + assert l0[2] == 0.0 and r0[2] > 0.0 # step 0: left support, right swings + assert r1[2] == 0.0 and l1[2] > 0.0 # step 1: right support, left swings + + +def test_leg_ik_moves_ankle_vertically(): + """Raising the IK target by 2 cm raises the PyBullet ankle by 2 cm, without drift.""" + p = pytest.importorskip("pybullet") + from kinematics import LegIK + from main import URDF_PATH + + client = p.connect(p.DIRECT) + try: + robot = p.loadURDF(URDF_PATH, [0, 0, 1], useFixedBase=True) + ik = LegIK(robot) + + def ankle_pos(dz): + ik.reset(ik.solve_leg(0.0, -0.0425, dz, False), ik.solve_leg(0.0, 0.0425, dz, True)) + link_state = p.getLinkState(robot, ik.right_leg[4], computeForwardKinematics=True) + return np.array(link_state[4]) # world position of the ank_pitch link frame + + delta = ankle_pos(-0.18) - ankle_pos(-0.20) + np.testing.assert_allclose(delta, [0.0, 0.0, 0.02], atol=2e-3) + finally: + p.disconnect(client) diff --git a/tests/test_trajectory_generator.py b/tests/test_trajectory_generator.py new file mode 100644 index 0000000..78fe97c --- /dev/null +++ b/tests/test_trajectory_generator.py @@ -0,0 +1,101 @@ +"""Properties of the analytical walking trajectories (no simulator needed).""" + +import numpy as np +import pytest + +from mujoco_sim.analytical_walking.trajectory_generator import ( + FootPose, + Pose2D, + get_com_pose_at_time, + get_swing_foot_pose_at_time, + select_next_poses, +) +from mujoco_sim.analytical_walking.walking_engine import WalkingEngine, WalkState +from mujoco_sim.config import WALK_PARAMS + +T, DS, Z_COM, Z_STEP, G = 0.4, 0.12, 0.22, 0.04, 9.81 + + +def _sample_swing(n=4001): + start = Pose2D(np.array([0.0, 0.054]), 0.0) + end = Pose2D(np.array([0.04, 0.054]), 0.2) + ts = np.linspace(0.0, T, n) + return ts, np.array([get_swing_foot_pose_at_time(t, T, Z_STEP, DS, start, end)[0] for t in ts]) + + +def test_swing_foot_never_goes_below_the_floor(): + _, pos = _sample_swing() + assert pos[:, 2].min() >= 0.0 + + +def test_swing_foot_height_is_continuous(): + ts, pos = _sample_swing() + max_jump = np.abs(np.diff(pos[:, 2])).max() + # Peak vertical speed of the cosine bump is pi * Z_STEP / T_ss; allow 2x margin + t_ss = T * (1 - DS) + allowed = 2 * np.pi * Z_STEP / t_ss * (ts[1] - ts[0]) + assert max_jump < allowed + + +def test_swing_foot_starts_and_ends_on_target(): + _, pos = _sample_swing() + np.testing.assert_allclose(pos[0], [0.0, 0.054, 0.0], atol=1e-9) + np.testing.assert_allclose(pos[-1], [0.04, 0.054, 0.0], atol=1e-9) + assert pos[:, 2].max() == pytest.approx(Z_STEP, rel=0.02) + + +def test_com_satisfies_boundary_conditions(): + start = Pose2D(np.array([0.0, 0.0])) + support = Pose2D(np.array([0.0, -0.054])) + end = Pose2D(np.array([0.02, 0.0])) + com0, _ = get_com_pose_at_time(0.0, T, DS, Z_COM, G, start, end, support) + comT, _ = get_com_pose_at_time(T, T, DS, Z_COM, G, start, end, support) + np.testing.assert_allclose(com0, start.position, atol=1e-9) + np.testing.assert_allclose(comT, end.position, atol=1e-9) + + +def test_com_moves_towards_support_foot(): + start = Pose2D(np.array([0.0, 0.0])) + support = Pose2D(np.array([0.0, -0.054])) + end = Pose2D(np.array([0.0, 0.0])) + com_mid, _ = get_com_pose_at_time(T / 2, T, DS, Z_COM, G, start, end, support) + assert com_mid[1] < -0.01 + + +def test_select_next_poses_rotates_command_into_torso_frame(): + torso = Pose2D(np.array([0.0, 0.0]), np.pi / 2) # facing +y + swing = FootPose(np.array([0.0, 0.0]), np.pi / 2, is_left=True) + next_torso, next_swing = select_next_poses(torso, swing, {"vx": 0.1}, T, 0.054) + np.testing.assert_allclose(next_torso.position, [0.0, 0.1 * T], atol=1e-12) + # Left foot lands to the left of the torso, i.e. towards -x when facing +y + assert next_swing.position[0] < next_torso.position[0] + assert next_swing.is_left + + +def test_engine_walks_and_stops(): + engine = WalkingEngine(**WALK_PARAMS) + dt = 0.008 + engine.set_command(vx=0.05) + for _ in range(int(2.0 / dt)): + engine.step(dt) + assert engine.state == WalkState.WALKING + assert engine.torso.position[0] > 0.05 + + engine.request_stop() + for _ in range(int(WALK_PARAMS["T"] / dt) + 2): + target = engine.step(dt) + assert engine.state == WalkState.IDLE + assert target.left_sole[2] == 0.0 and target.right_sole[2] == 0.0 + + +def test_march_in_place_starts_from_idle_and_stays_put(): + engine = WalkingEngine(**WALK_PARAMS) + dt = 0.008 + engine.march_in_place() + lifted = 0.0 + for _ in range(int(2.0 / dt)): + target = engine.step(dt) + lifted = max(lifted, target.left_sole[2], target.right_sole[2]) + assert engine.state == WalkState.IDLE_MARCH + assert lifted > 0.03 # the feet do leave the ground + np.testing.assert_allclose(engine.torso.position, [0.0, 0.0], atol=1e-12) From d7021ce176aaa4143da2d57fe7de59ffb8d68f5d Mon Sep 17 00:00:00 2001 From: Pedro Henrique Bohling Peres Date: Sat, 3 Oct 2026 01:49:01 -0300 Subject: [PATCH 5/5] docs: rewrite README, add theory guide, license and trajectory plot tool --- LICENSE | 21 ++++ README.md | 163 ++++++++++++++++--------- docs/media/analytical_walk_forward.png | Bin 0 -> 208000 bytes docs/theory.md | 163 +++++++++++++++++++++++++ tools/plot_trajectories.py | 110 +++++++++++++++++ 5 files changed, 396 insertions(+), 61 deletions(-) create mode 100644 LICENSE create mode 100644 docs/media/analytical_walk_forward.png create mode 100644 docs/theory.md create mode 100644 tools/plot_trajectories.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fd857eb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Pedro H. Peres + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 1f38ed8..fd7d4b8 100644 --- a/README.md +++ b/README.md @@ -1,99 +1,140 @@ # humanoidMotion-predictiveControl -Simulações de caminhada bípede para robôs humanoides usando cinemática inversa analítica e controle preditivo (MPC). O projeto tem dois ambientes: um em PyBullet com o robô **Aurea** (URDF próprio), e um mais recente em MuJoCo com o **Robotis OP3**. +[![tests](https://github.com/PedroH-Peres/humanoidMotion-predictiveControl/actions/workflows/tests.yml/badge.svg)](https://github.com/PedroH-Peres/humanoidMotion-predictiveControl/actions/workflows/tests.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) ---- +Simulações de caminhada bípede para robôs humanoides, feitas para estudo: **Pêndulo Invertido Linear (LIPM)**, **ZMP**, **controle preditivo (MPC)** e **cinemática inversa analítica**, em código curto e comentado. -## Estrutura +| Simulação | Robô | Gerador de caminhada | Simulador | +|-----------|------|----------------------|-----------| +| [`mujoco_sim/`](mujoco_sim/) | Robotis OP3 (modelo oficial) | Analítico (LIPM em forma fechada) | MuJoCo | +| [`pybullet_sim/`](pybullet_sim/) | Aurea (URDF próprio) | MPC do CoM (QP com restrição de ZMP) | PyBullet | -``` -├── pybullet_sim/ # Simulação do Aurea — MPC + IK analítica -└── mujoco_sim/ # Simulação do OP3 — caminhada analítica ou MPC - ├── AnalyticalWalking/ - └── Utils/ -``` +![Trajetórias geradas pelo motor analítico](docs/media/analytical_walk_forward.png) + +> 📖 **Teoria:** [`docs/theory.md`](docs/theory.md) explica cada modelo e aponta onde ele está no código. + +
+English summary + +Bipedal walking simulations built for learning: Linear Inverted Pendulum (LIPM), ZMP, model predictive control (MPC) and analytical inverse kinematics, in short, commented Python. + +- `mujoco_sim/`: Robotis OP3 in MuJoCo with an analytical LIPM walking pattern generator and 6-DoF analytical leg IK. Run `python -m mujoco_sim` and drive it with the arrow keys. +- `pybullet_sim/`: custom Aurea robot in PyBullet with a CoM planner based on a ZMP-constrained MPC (cvxpy + OSQP). Run `python pybullet_sim/main.py`. +- `docs/theory.md` (Portuguese) derives every model and links each equation to the code. Code and comments are in English. + +
--- -## pybullet_sim +## Instalação -Caminhada controlada por MPC com geração de trajetória do CoM (Linear Inverted Pendulum Model). A IK é analítica relativa ao CoM — os pés são posicionados no espaço do corpo, não no mundo. +Requer Python 3.10+. As duas simulações são independentes; instale só o que for usar: -### Dependências +```bash +git clone https://github.com/PedroH-Peres/humanoidMotion-predictiveControl.git +cd humanoidMotion-predictiveControl +python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate +pip install -e ".[mujoco]" # simulação MuJoCo +pip install -e ".[pybullet]" # simulação PyBullet +pip install -e ".[tools,dev]" # gráficos e testes ``` -pip install pybullet numpy scipy -``` -### Como rodar +--- + +## mujoco_sim: OP3 com caminhada analítica + +Física completa (contato, gravidade, atuadores PD) com o modelo oficial do Robotis OP3. O `robot_descriptions` baixa o MJCF automaticamente na primeira execução. ```bash -cd pybullet_sim -python main.py +python -m mujoco_sim # da raiz do repositório ``` -O robô passa por uma pose inicial de estabilização e em seguida começa a caminhar com `cmd_vx = 0.1 m/s`. Para mudar a velocidade, edite essa variável em `main.py`. +> No **macOS**, o viewer passivo do MuJoCo precisa ser iniciado com `mjpython -m mujoco_sim`. ---- +Com a **janela do viewer em foco**: + +| Tecla | Comando | +|-------|---------| +| `↑` / `↓` | Andar para frente / para trás | +| `←` / `→` | Passo lateral para a esquerda / direita | +| `PgUp` / `PgDn` | Girar para a esquerda / direita | +| `Home` | Marchar no lugar | +| `Delete` | Parar (termina o passo atual) | -## mujoco_sim +Todas as letras já são atalhos do viewer do MuJoCo (wireframe, sombras, contatos…), por isso os comandos ficam nas setas e nas teclas de navegação. Para sair, feche a janela. -Simulação com física completa (contato, torques, amortecimento). Usa o modelo oficial do Robotis OP3 via `robot_descriptions`. Suporta dois modos de caminhada, configurados em `mujoco_sim/AnalyticalWalking/config.py`: +### Configuração -| Flag | Efeito | -|------|--------| -| `PHYSICS = True` | Roda `mj_step` com atuadores PD — física real | -| `PHYSICS = False` | Roda `mj_forward` — só cinemática, sem contato | -| `USE_MPC = True` | Motor de caminhada baseado em MPC | -| `USE_MPC = False` | Motor de caminhada analítico (puro) | +Tudo em [`mujoco_sim/config.py`](mujoco_sim/config.py): -### Dependências +| Parâmetro | Efeito | +|-----------|--------| +| `PHYSICS` | `True`: `mj_step` com física real. `False`: só cinemática (útil para depurar o gerador e o IK) | +| `WALK_PARAMS` | Período do passo `T`, altura do torso `z_com`, altura do pé `z_step`, fração de apoio duplo `ds_ratio`, separação dos pés `y_sep` | +| `KEY_COMMANDS` | Velocidades associadas às teclas | +| `ACTUATOR_KP/KV/FORCE_RANGE` | Ganhos e limite de torque dos atuadores | + +### Código ``` -pip install mujoco robot_descriptions numpy +mujoco_sim/ +├── sim.py # modelo MuJoCo, laço de simulação, teclado +├── config.py # todos os parâmetros +├── analytical_walking/ +│ ├── trajectory_generator.py # LIPM em forma fechada, pé em balanço, planejamento de passos +│ └── walking_engine.py # máquina de estados da caminhada +└── kinematics/ + └── ik_solver.py # IK analítica 6-DoF da perna do OP3 ``` -> O `robot_descriptions` baixa o MJCF do OP3 automaticamente na primeira execução. +--- + +## pybullet_sim: Aurea com MPC -### Como rodar +O CoM é planejado por um MPC que resolve dois QPs (eixos x e y) a cada 20 ms (LIPM discretizado + ZMP dentro do pé). A IK é relativa ao torso: os pés são posicionados no referencial do corpo, não do mundo. ```bash -# da raiz do projeto -python -m mujoco_sim +python pybullet_sim/main.py # GUI, 0,1 m/s +python pybullet_sim/main.py --vx 0.05 # outra velocidade +python pybullet_sim/main.py --headless --duration 10 # sem GUI ``` -Uma janela do viewer MuJoCo abre. Com o **terminal em foco** (não a janela do viewer), use as teclas: +O robô passa por uma pose inicial de estabilização e depois começa a andar. -| Tecla | Comando | -|-------|---------| -| `W` | Andar para frente | -| `S` | Passo lateral | -| `R` | Rotação no lugar | -| `M` | Marcha estacionária | -| `X` | Parar | -| `Q` | Sair | - -> **Atenção:** o input de teclado usa `msvcrt` (Windows). No Linux/macOS o polling de tecla não funciona — o robô executa a pose padrão (marcha no lugar) sem comandos interativos. - -### Parâmetros de caminhada - -Tudo em `mujoco_sim/AnalyticalWalking/config.py`: - -```python -WALK_PARAMS = { - "T": 0.4, # período de um passo [s] - "z_com": 0.22, # altura do torso [m] - "z_step": 0.04, # altura máxima do pé no ar [m] - "ds_ratio": 0.12, # fração em apoio duplo - "y_sep": 0.054, # separação lateral dos pés [m] -} +``` +pybullet_sim/ +├── main.py # laço de simulação, MPC → IK → motores +├── mpc_planner.py # QP do MPC (cvxpy/OSQP) e plano de passos +├── kinematics.py # IK analítica relativa ao torso +└── assets/aurea/ # URDF, meshes e export do SolidWorks +``` + +> ⚠️ O MPC roda em **malha aberta** e há um descompasso conhecido entre o relógio do MPC e o da física. Veja [Limitações conhecidas](docs/theory.md#limitações-conhecidas). + +--- + +## Ferramentas e testes + +```bash +python tools/plot_trajectories.py --vx 0.05 # CoM, ZMP e pés do gerador analítico +python tools/plot_trajectories.py --vy 0.03 --vx 0 --save lateral.png +python tools/inspect_urdf.py # juntas e offsets do URDF do Aurea + +pytest # todos os testes +pytest -m "not slow" # pula as simulações físicas completas ``` -Os ganhos dos atuadores (`ACTUATOR_KP`, `ACTUATOR_KV`, `ACTUATOR_FORCE_RANGE`) também ficam no mesmo arquivo. +Os testes conferem propriedades da trajetória (continuidade, pé nunca abaixo do chão, condições de contorno do LIPM), o IK contra a cinemática direta do MuJoCo, as restrições do MPC e uma caminhada completa sem queda. Eles rodam no GitHub Actions a cada push. --- -## Requisitos gerais +## Créditos + +- O gerador de trajetória analítico é um port em Python do `trajectory_generator.cpp` do pacote ROS2 **aurea_walk**, com duas correções no perfil de pouso do pé (veja os comentários no código). +- Modelo do OP3: [MuJoCo Menagerie](https://github.com/google-deepmind/mujoco_menagerie/tree/main/robotis_op3), via [robot_descriptions](https://github.com/robot-descriptions/robot_descriptions.py). + +## Licença -- Python 3.10+ -- As duas sims são independentes — instale só o que for usar +[MIT](LICENSE). Use, modifique e redistribua à vontade, mantendo o aviso de copyright. diff --git a/docs/media/analytical_walk_forward.png b/docs/media/analytical_walk_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..4e7c40d0974f41c6877bd78dfe0f9b0788503148 GIT binary patch literal 208000 zcmbTecQ}{r{|9^_BMFu4k%l4(QC3z%9C=96PX*nU|TOsFepb_G(fTeJn-M zX)rP1D^gY(0{EZ2%RXI~Qx29c?xs!_)KOCxM|%esdmFQLZWd0?HV)@^NXSb_i?6eG zadC83*uMST|9*pngOk;E;bU~|_>kp}8fTm-YPAdbkEYX7$b+JxsDpdePJ28Y`Qqxq z(b~%RYt(k1@?sKl1HoU8LfG6Sq0bb#`?o;s5zXV|njAcl!VS z8NZ7VVOCx9f4+rDbS;Ok$p8ER`BqJ_E!5Hf`!i2ZpP-`fu{7+QoD8gf*D^9PG&}b1 z-MdVVpH+R&p2O$(W}_9|S@~HR139;eWu4>QDkH;zKdoA|D#@lwFnM1f=aq_{?$7zo z@tOy4>8FKSH%DFYo4s6r??rUi;rkNweibx+6=hA&kJlC`Y`R*}6ML>Bx6g2;y4ce{ zOY>~k7};(-|H#J8 zn}@~I?d@C^W$ao~@(Q%q-`j4?iW}E|c|P1-ON_-_W#H<@O`A;n>mq+mPy68~m3Hrz zI{#U2@BaPw)ZYbj@$sz?4D~y%Jo~gTS<@6Zvhrw@%$4Bal_NiXyw5$S&%fI%TVq(k z>-UyVx#w0~ym+y#tu5B3=~)~8Zh33o$mHOAx?{(V-98lGXSkL;=ndn}wd=04n?2$- zZ+l^}0t1KN6ngH^PSHKt*(|1IWMt&AFe8PJuPqQ0vHFy4!>!=5<&w8I{2!RkHtTP>JZ z*0Hy0Wr|S|Z*{0pbzklMo{^E1oQE+nG4)21ot>SPUy4>=_|>g8GduhEWZeEf!*J%u z2{Bm`{{H^forUs`c3-?X^`palu<^0S__yV`=f4D$EX}RZNjKWpoTPQwGnCnF=9lT4 zKBwQKT_py?Cr^5hRdVU(J8dc~DjFNB7TP1u**o3kb0FQQD8ucC-jN48-*vk#{%$%x z^DXRn_sB@loec-nw{Vzs6?-$STv>?;7W*-&;pL@pEJit4c+XAWhUfa;7`wGh8?ZWS z%Eo%Dc_>a^-kQ%}zC;EFuBd(YZrjT_`!BD;X-qJ&+giM+jGgB`-PMRbRNkIt#Y5#f z_LPsO1sIuE1({O19v*qNdLABfbfr~6Jg3f2uHSS(Ec`{3terG9@~fvJ>AQ4f&`NGe zUmBAxpFc%q!^5BRchHr#q@0P9T5LM5VnMaN_Ft28FVWiP!R)>R2X?UfRan+Vob1#} zlnfY`AKqbFzKp@wd#c@vw+Tb^j>?cQe1&@njI(P^t7~tMJQ)9BJnhg8=h<-!248vx zh8IttZfySj>z8Byw{Ne-#l>Zc_b{&(6s*T@Bf`SMs-zDdJSZ+Hd7H*WC(S^#XZIGK z8{W^c6GmAmMUP)5ku8DPk)kZ|-B?zd5<49_S*V$r-cTvC*GwBXZtU(ZyQWZ^G%)?; zrGrqEj1BG9ty|^fPJYKGYqw+vu)nI`PSa`r<1i;#`rAdtEHc> zJu5vkGgDe$&&tEYqfmQk{O#s$N4d6_4l!bWGSovAAEVyM%NA)>SI2p+EG&Bbgf6T0 zRt1~d*{$H?Fub#0ZHTi8wLR`sVmU zIU4Lq&xwAUBPtiCQf!Soi@lSj9Nyd(#??F~hr(??J>KId>GVxw86#uGr%${sEiKj` z6INkP=<*GWQWR?Xoa!Q^=ul}E=cjez&o|zu(~DfaNm!U>jmQySKR-r{<%t5fb66u< z13R6ZoinZ9uRootx0aKWlZlC`bYXVl&VvW~!lzO`CY+>Gf6Tso`ErGUKffy2a04^n zpT|&?%F@gRY*q#q?pUp2Ch(8omH3C`_CEOvHw_FcsSk&%&`w0&HMe5$t_ z7r!pOLWj-CqOY&-^7Atfn~?G{>UDK>a=rW=jq7a6Gn7@sqvnFn3O}}MQa3Lz&Luu8 z@|tAEL#m!Q!7jdSThF&|*I&5~n|*kCP~*rE@o{CBAD`A@Sf+n|X_EJ8c(j{5&)2~1 z6eVPJRyZc9zH;<-;UC<;AT@Vq~C z)kukB^`rCOdv(*{d+Q`6PlRjkpZ&tHrT#mvS^nX}YSa?lJjchKJ0-VmTWMAQPz_tY zw0Df6%rd!AfYmB)uIw2cWWo}}NW>kFQI>NZx~t~e+=tSIJL8rzyIw_hf|kC|*w=l* zp2nAFH(}80AIj5pd;h%bt+KSVv@b12f^VliMb$mn#TF70lJvc1V#BUoIt~vWb)}aq z1$_Q&fC_Uo?h*bjLeV{~YKl4TQP^x%FkjMduAyGuhr2Iwk^xKA%Q3OCVmy5KFlLi6 zkZb$?W5+`3C#$Ne%ob+G5;WtPF`lbduQog3bsHtAyrzcP@RiHSqXiyg>%At2=p7s! zbai!0zkCsR7bRQqwK8z!r@t+2ij=K6-&x30@@Gkrw6#q0YP0cgZ-4#zRr+VK>(65}PL1Qo zw>#=939q5}Gg%C$o*%0$_MSJfu{ks7Bq}Ou{r(Ak zno&`}6*@*U{J^b{kgB{3z1imndCa*bO(?QWlw(5CPamU{GObuq(fD{zpuzM98Ou6u zjg70L2Sj7vM2c}ROI@0Aq)MO1Tn<39`if1Y`|^Br(&qyAQBpNf)E3Z(7zFh9h+fk+ zEO2X%(#f&aLNV!`9ByTxr^3}f*GDT}t8Fg4o0F4+!QOT|Jp2agwavB%|NL{+*k^HV zm-n>5<)vAxXbYCLt5#ikcJ#seojW-nKYl!j_HZm(;YRMcj<2&51J*4mJeWy;^evNL z-DP?1qk`BRH+MO74jOdfF}sI`0x+_)l$n_s8D5j&med5pf}PKf$AqBw3a?v7b-Z*q zaOB95JHu%UiVHtpcDdB+EE#U;=zklwIazbl_ZK!zB3OBM2dU)B*@qJaZa;WmWM#d7 zvTsG(r%$$Zy-xW_CWdNkOfEbC4(Nl|Sp^hKu%tB(9xS`Qd<~%6%?9OpmWlp%d$1p+ zE_~g~bVHqBh}!$xTl3T64uqZZ{nMhs%F2K?_u@Lsv0xmvfDf#T@(At;y?KkKe!g>Gt_AuWas$ z*1C!s7J043-cvtzj1_y*Ps+SXbqfdW38iHV12I1At5;w3U$gmZT&VKZ6I)(C*%#QD zs=w>aS?OTcO&d4HqD9P1wi>U(o{HRIwSMEqWnm&m%R^O`5?-?A=jYGNkCwcTKg^{y z?~Pl`KG(r{U&2t`k5%B;>_AK$ZVP><(fZGF;p?|GbH~Il`nmR|6fj(^ z%(LM`Wvc`P-pyQ6w)>cHr^V}T-#~qIvXlUQVSoDe)ay<{B1fZMpOucRD*XN1<=#L4 zJel)3dv;^(+qduart!Gk_iHWiARi*m+cSq~r|L-whCa`HXZbEFlVK&Vq?MJ`PAuZ) z-&oDkls!N$@36<%*BdQf7ScR?yS-%SO4AGro=G*_=HLfnOTqFDs_3!yb_*&}IZ*#- zH|~Q1)(I6#zo4w-_50SChmv-d<CIy+xR=HMdalpwNS0qpIz{npW22+ zq|ngBx9hwlmE!YfiMMIZ>eUf1U+!Atzjx&4mx3cAVVPJ}lGI46u}`yJ129B;`HdAT zm)^$ISaz3QVIgZcrrvw-eH92hKT2e5yns=vt!pTm+ zul#}nyKtRcDfH0Rh|55|arKcXsVB{6pIokgwE$SkLZMVg)z4x-SW#^w?O7`fxy*xd zY?^M;m<03fvV8mYZPIr%>$9|1eC6fjBz=5*IC*#yqwYU^SobhG8jv)wMe6L4v0Y~8 z&b`Cdj8u3E?^B`Y=0b3&wdV%DpqDO7NG4VCeu<7M)*$+ZN zRer;(_&a*1xq*bv)`6#)G=ptzx>bEnk-%%UZ{A2+3;j;ie<`to ziK#Kd(w_1{h2!<~YWnbjJEp-3c+!%Nk@f7OM~{+T0KlB3+EBq)>P@3YaZ|Y-V`jnM zZ_vkXSifEu^d~}for$8`NMrubtF(>U+S=l>vRZbLSbsYx@!h)(WsmY)-|e$hs68v7 z=yv)IC1W1sI`r}2)`-__^XsC$IxXS>52Ue((3_+AxnknD+RwHqY*gTPWE1ySky_xYc6N;zQRnuUry4 zN7<{mJF;!o(3KM4J@|_I;-6{9p^u`eoPgKd4`h8%rKu%07fW`zYSK;M0&OjbYgoAnICwFXVYVZ)eVhGSZmNWo)EycV^Xd?-qiX^y>uPEOP?l|#n6x#Q48PAE6H>XX zw1#OJFWbJmG=m_*yoM@Ro=-QvZ_Fd>^CMctukpUaF5f@UX`|4BmOc@ zY~LHw3V(c(bwLMj^OSJtEL@&%5Q0SohVrU$JVz~Gk zszzUPocnF_`{x%v>_ETMV*Ajfx<^Mt!R>oMZvoGq4Xy11>9$MRASJa5m8cq>Z$Ul67{i z`N(v$CdMu|f#u9`YwImouKo}+5}m{XE6qkai}=o;KR?)6RL~;5O+WWFz^s(d(xpp( z7N--mQ=>WypU*nm>Fmcg`#9qRf|uz&YQ(}X$BxEWje5>}sc3fzW=bo~Xw&iI$0=1u z#|-TxM(8GDT1kU-&7{ZznmoY-CfO8SoUVfYBjq}D`k~wfT0m+wz^xO>+S{~>XTK86 z0koWXu4Cu#!S{P)y*qNxVSbYCZaWmV`FK@L_}0t|z5BJ694&q>fw4aUn&$1RC@%-7 z(H`;AKKrUT?CyXn+IV)qXvxI-4I6p}2A)S<9{+am?Af!{PrEX*vi6|M{lfGU^gzeJ zqMEFo+L&$AwECdKWBZq&B*EY!y*(9vw*mvnuy*(MUR%3WyBqCouqE|DXRgsfkuXz& z?0o(x6P9O+e#bkpxVX5oX4EKB*5*m@ljzFQciKDTcO(f?2vzh&ZY3ahR1Se z->~y5d9VY(?~76P?*93cb;XJmR4EXBw$uLMCVV*I>3|kwaKYS@KluTX@Mecxefsn% z<-1?9cwLP56c11dJsTUF7Q1oeu#~tsOS*B1gtj-PfB`gCYpDV2anBZxClAV?*TB~az>2#vmN4LYazMJ>ZHZIOhUW<+va2sj60_-LTMsy%l zaG$2;YRmeEiOxP$EC9>Q+;F<^{o1kj2ZR!7$2J}1(Kv`rEN?7t*kz@qH%#k$Figbs z&(dO^W6xE!zKPa7(c^N%QO_T42V!^z1kpP));rjq^*kwVpuE@UKxx22FT=o+ zLbQy5!3>lA2C~(iT*;DvOZDH3(_dY!r|BzP7yx%+T*tvIE4YjbC|l<<)5AFODd(_k zm9NIea{OJUStWB^$3|CWWo2wl-uf67w#XyTY$p_51}S%Uccx{u50$-nzr1p-AZnnM zl^;X{)qz*fjztIbR)?B@eeEA1Yo~o@`04S3K9{iwH8eC-QQ84pCex00b!AEnbzK|U zxOzE|M<}{XESL?q^jRhYLqjdLcCPJ4DPz$IpL2^Fw7c9Sj0$hf%yH7&pP_Ffol6sAfuOe-Yq+a#m=z)_y= zpw1=u{8{9}mf4Gfc44GVD!BUrkijIo?8m1E!z9hCSV%nuqbv&%Q0(sOy8))hnICef zhR<@37kzbQr75sP!l~!1X$JXEnkzKB*11%nbXd2)*mUfn+!cVAp5Nb_W}xYqT3FoZ z?l$qbc(E7M-?}YB7<4+v>-D+z7xX^b`*t<%gRwbTr&Wv(T%tFR813BsP{EZMWRgYfS5!p8$T1u2AjCjOoOT_g zxq;PPnCL%B*5T#Lmzh{tmc_g9nQmKCA2#b=El1vTn{49uy02DfpsSzIKg{fKU^*a2ANx$|7vHO8_Y$3~#* zRL|XBzf}QsZ%0P@1AFy?vgnpv-t9Ft;;-obGZuC4#t%WVe(nFWe!=q@GIfLY2lFPL z7oSkXt|#mtqSdlx%Q$Qlw;L5wu^u;e`>>B;Al}s1@3i$Mi&n?jJDxcgHPvM9_jd({ zs(gbqZHJMoGBzKU8Axz(Nf@XCqhGO+Gb-|;2aEuiwWE*Ouwld4Q1jLb_asU{!KD=S zBLk|B5-3A2Lq5hx?$V4TuZ(r9Sn@H?k?{1vWJR>?J3|xAI>u#?<6a#Dg$`lHU09HJ6HIIJl2oM?RI zYDc8RJvUj(AeqQ|eWeYcvZZ2-!IWfZ8_BePh(CM*tozbrvktF?ago;zEakF>1~zn_ zZ7yGcFzKiT5P{pZecezfJimYco_A?hIwd8gG3CsTYeQZ@Qk0~1M|=+iYr@kbMfUkO z48%bbAyO}pCxKSI(0?|q_FY1GCm10}NJS8>>n0eN_Hl!F?1=K#RxYfkeOR$$Bkena z1b6d72h_7*6Hs`%L(5kgE0ULH@SlZ;574@>Sq+LW@!(c?e>^vks8;#25SRJxg-cI4 z{YDWH+ew3W4b|0*8}FHMM0#aTvXr#?>E4roZZwEF? ztl@rwIVGbKBBYYG)_13y;f<4*HxGH0K21KFdNPgygXK8=)9~rDXSTcVHr{9bj?sYL z6shfK6rGbJOFk5kvEu#vRVc%<*qHlitN|D-uIhUb!uIs6JtRgdZU|Jii!$wD4u@%U0h36K^ML?sOJd^a2jNqAP<=X7bd?>@!h zz?(3={;tMGVtc_EuxJtlpiHL3JLljX0*EQ3|Mj`}XZvDesZCOl;*7-cyI5naunw7?sJ@F_ zJO6R{GTI<+DO&75y_mj|KTCmF0=CdHHkGqk zmha5a6fNODrJ(=SH8q9J&d%DTSb@JmutJ}x_xe)o{eZ@V(gjEO-QojPhJ_k#&)PhX zdegqE>rw^qR{G1dAgOzI@4f~=sHQR?>16Qiu*^7x=c_AGowHuNFoOiPl5b~_H?~U(yag7@ z4m*wrn@O9g)DkT`)7V#fXJ=<~N*DB_4OW3Jew3TyW{`>t(R9(Q3+m@_Z;6yR|*)DqU8n)_sGW8 znW%Ly&>dgMJNIX(U1N~Cqd}b1w(zYwGBxuvGgtwrwhTY~KH%=P23ROJ%sFjs$;IiA z_f}l=$iS0&Ysz(NU7F6kDMB8UG4Gu)+&1#R`xY_MeABy@6(uoCbVuTF)5?P z^Gv+k#W2cf9@Ly}Z0uSG3*dAU1}8E+d@WrmFm}$8vTaMsTPkB|aXt%e#ddK46?A`d z1v}wjbfqxV+v_5CwBHpy6_J%iTpD8HmWh*F z<|;O#xO2mXYxMN=QdA1yfIW(#JrNzNs`gJtN0bzy>R6#uTTr^F((Rb0HkiUhaRgs) z`P2yrz)ej7mZtt_O*f9o&&$iege9bH!ziWO>Vvtlbn1{6BljO}$|9uvkj4buP9od> z`LSqj%B{PUrYd5L#=468@R1{DQ6enSi|fkEuWF_0g?j@vh@*aOWqPgKgYStDE4}!8 z@MMvNnYnp8nr{TOCTP;Uy8`Ix)1;-P+g^5Et{rMgt%XNNG^%8j?*~%mH}|tk?bs3a zE?V(EjCE-+-Togx-aR`KS&nISVb~Y2+Sf8D6N9^hO1U`wQ*Yv~+C%tQ!v<6D;|`>t z%+)nFho3ofCKDX=1zfY?FFxk9jX_emcC8ZF7Lo9*FG4ysPx(f^dM`Tq9jCZ%DZ(a^ zC$^l$l%4Z z$fLNEszCOAZf@?8b?lPhfGt^8$4MX~ZtC$yy2ew_j+h!q+^?*xva&vlFxI5KE>=nRJ6j&{ zp`xTJr^~@%BGc2;hxiG8d9ODA53_1l-i2#4CPXC?djKgMeYfsYj$JhpHDq{x{`{GO zMr)5!q({>O6Hpog_akL*+mFRtuD!xje~4<$ceY~iT`8!fYxfA@lp)>Wx%boV`Ua@= z7DRZ5a*G9fGw1x5s72H1EEML+g|Qt&Cdi927oV2Yu@o{Tw10s>Qj6Y)u8gw zW_6}m?x!C*I{4Jo)b`bfrn!83vx*QwpT$X?EBQ|kUp0_8fGc6a15?o{Di&ydc_jKk z(OshW&(g(0ugRo0=7Lm!^V{a;wFGVx%i^l+#d6Bd=ap;Q?$kJ-)DY|i6wj)oql0kOSI`WNLx;)%K{HJ&ZjPs|7q&Rx z{=$NM4O<1ob_{}NS!?Ud!sLB_BWPkY|Hjq4_U_%g4lXI88-Ft(X@-H=t@qq>G(_in zLsc>^FM7e{VuHC0c1xU9IA5Wl#Mdf%?z*pCF6xXp1nm_H3$7eu{QJaH&&ShNpMXsblUk*YH4mh56^BBTK+mTLK1`7$FK-PtOICJ0VD4UPZAI&fO zptyAr7}Ha^0W@bYO+n?k!43TKP6*iPT7LNOp)u|N8{zJdBq|{>NZEXND&6j8 zF_@=mG}w8Cy{U5hC^NhIk>e$o7YTFp@bJL%-HIYopmTrSmHYQ~Jaytf5?&oDqylDm z*i)x61&NTA_`n2tO2Kuit|-NOat)G$V@#5TzU)suA3B z^_QQ3aFC3&o(zjG-*x`8s(}IjJqg1A4Cp;~SFyWYm*-caWsrd4E&sLJ31;l zJBtgG)yBtu{gRyZqebNcfZp$^Q!$d4mv^Eu2Dgw{>NeULj1P}PkVg*oQ%jDQwl-&) zaf#8_j?{!!3-BI_I)1#tDeYk89sy<`;47Mr?CKN+k7GZ|D8fF=(BcqdASH&l6k^Gj zWDkjRn!o*LQ-V>E0wB=o^gG|Z42>gwbi0l(8Cb$3*Uh!(LFZh75Kdcr`(reHKn#`a zp|~YpeIz+=vI;O`-VvD~SuOOY=A=K^t27iCYIB00^zMT$E;4XuuL4+;@C(9HD0|$> zm*n8`mOK#}EHr-)QJE@*i6EPsUU%o&Wfn|C2$oiPXQxqbcsAyNBw(0WS#N^0rlh96 zPdK@OghQP24F=pM<4y7}FDfvw@+W-Kf!TQEq1;K=3y+K;wZRzh!?G+z5(mm(jzd)+6S zz3vNuF|{Qv-o=$8^Ti8I!x&nkEulV0I&>-pJ3+b8GGr|Dp4T?-gadK9)#lKvw8f9< zd_d2Bh%%5FKzS#@)F)4#xcsaDRCK@l9Uj z+LPXMm!-&^)E-(|T8cs)Y068J7HN(gpL+}4hJ^8$nakN!7O1L-`td4*;o-yAkjo2% z_Wl?>8B|hLRh8;*hzW(ot?um2H6_pIJgUl+_>i=qWzd~pe|sBd3JDY(pbQHDEFa8o z!57oH=Kgl$n;4K5Czw8^kWG?&dDNFc@=hw8KQ%vdlCIdZBO4mSJ-U) z{QN|U#F{x^Ub?F9vJo5+;Ojl8FOnvu6tcqIhzuc5DHOsle-$ntDFrtLOa!WFPZ+r5 z+BEI&eIoWp3Lskul28aTG^R-Qc`VN5*meS=m-acm&$c<`;pr(XE4vnXyD})ij?i9_ zEBq2Ww*b}}A!$mBGJs0vfF$SNt3m5qT9`O)P~dhnZu4FQVFA+ofa;0V7NO)R{mtob z;uip870jBeSVNr9IbfE8l=Fg-f6Na>S*Y}-p)2LCfF%Ko%?}986mkpEBOxn3eWjtZ z3xd5tp~osBuR=sD>m6hMTurV5+ws<|TYqggsN-iXEx$r?9sHED6&CC*M0s?zlo1+7 z<=r2x;7Y6_Y|Hfu3cQH&l%tKmM>I}7)DE|;muv4oIB1>i&?Sf*Qw2;H;!7?dA4I$` z0Pp1#x2!D(sT!n46UeOB3Zl3!?5&Iw{h^JkA@Q#unmR-mk{?6KBiTR_VM7g0o8b2V z)Q{SEE&yHuktu+OVIMXX2;E#L;y`Geew$vlZ))l`ppTn~@GhYKL3CL`;%wMV>qSME zL-@PCcYPUzbt%|z82?zP1iZrlsQh5;%;+~P+>#V4D{CHrIhcemsA4ZxaQDRb4>!>v z-Zr$i^H9G~6|U91glULcOLE>&cC?z{RHlv;^wl83~cV(9qVTS}*`4yTXG9t-#&b`YUQVs4>%75>ZC_z5H+J%J$a8g9D|0b`Sm$vI=Ynbx^ z4CtiRMkpWz`T_xl)bDVOED$zMfkwjH6z)mGPy(-R=l-L6#8bt-xi#+C!wObPpY3?sb$)!Nxu!7H)fPgRrpLz|yyWY1ZGI zRTE-NbskEBl0G)h|Ng@O_F+iA|dk!GzhR(975ooyn85~WX}*fcNx5G`)aQ1 zEi4U-M&&JQr>38z)PhFKKHK_o7vfqXVQ)`2`ON>=#o#Nipzs3A&-URCEIJb^yI#>k z0Cb`n3oAyGE8lGT9%jwPoZ!q>$9+ZaQooDUqMf`UN;l;R1_TFN0Atj3c0Tg2 zG;99-1ik3hWgVje(1w7%{(dC1HgC>?0(k$;K#D`hb@nDzpZ4n!Ono=*3#$y#yg|Fw zc2E@)74=6_MH*@p_QdI?a4v?SvTd$6m&GE*1aoncgbkrXG)HjZ=H&Q8v{fHURpr{Z zms1%ibBOwE|B8j`!ocF~?H!3Y&U@F7&xuM=UasEzH7%RAadtb?$_r4H8GN@H73!M5 zN~3;5AL60hu)m^*hhRYWj(H@REa0(gsSIQ^f_1s;NPTG?1$smsLs!pfd0hBx`_KPb9;1n?&CDVm6&m|~o!m6Z!Agtle-&^+Y!VP-s3?s5vW5oaE`zuIfKa(<;c@oRHlPkpTvy@BRDt z>(vh5oh(HDucZ*v8lA{Z4g*aNX~hlCLOlYdUO9v4(w*D4sl?MsQYym2k3TX_ZXM*_ zEFxkyJKjg4vSY9o(SL30TJNhIj~>r^sNy3Csp(^s5EmC?lgfH^rLm}3=zszeSMbmpGp zqmbPpcJDv`{6iWEiESwiBSoJ0X|pRzUaLoZPBuNxhP1S3oAZWN?+F*vf}xu@A!`Vo zJNU|N)Em+Q!M#jSbQE5i!>e%iA;kTOB+k(M9&TmB(^kT`BKJXTK16@Y$)OVfkY>4! zzF)m8kUvBKUdC(sXE5e{k61WK1K`wHHMFc>Gh^l?M*|FYa+`U=g1J5wFziNY96P3c z^pufKj(xjkveNz9eE{w`nt>^LIn2-}c$?D7hl9Sic3OUNr-F^Fqo#-k5H{Rjje85;) zPdO&t`9$$m5=4Tbb{)%%aC&27W8E`n$SFK@YiKe?kt|-%M!BCqecFAEO<5JVir`tO zKNTS9mz0geb?R@=Z4wcorEql4$Rr=WQL`J(KHrSInxW69eiB3f0^(IgeRt=XsEbo0 zWdv_y!z4O2`tMy&Qes=RQ&ce@%%3e!PQ8OGjo4fjK-n0S{#f8f_Q1-vqKmtreiKBV z>g~0(AP?!#zEN3p?c``~g3C&mWm}dcRzy8tD&3633_(&j5W|&MLbMQ3@M! z2oWZMG@Kv!)!|U`*tq0!QsHaxw`+*>i;Yge^bqD?e*ePau$ECLfL0t@3*tYtAQl!M zU2mq@x%cU^N=F`%J?d!~KRS1~^)~>OjcXwc+wC&cxe-}|3u#t?NwF6NRVV&V&v9GZ zq#>gXCFN;>D;FOw7k(BDZ74$Cf*M3OhVYczvJer$9qcsZ5*%U>@n><=hlC2rVF04Z zm(C4pp71^ta9XSqX+Y$%LI7ax3{P;z?{Yo)U9k!Y5;aT)pm#!Z^7MbgC~Ctxg4kyt zX&IlK(lk=#{ZR0!@f=rQ*3SOVrzA=c0!2#yORNP%adN;(TrZmmHM$Z_oP_=XRMxFu zPYa|XWBs7&X9ooS49^Kmdz@(`c_CQFH(tNq3twqZ99M`FNcdb2&^^PCFwK)o=W8E0 z`S2lWFeHDR$emx&R=AP<6jbu?CDf9vd-_;*lewLP8j>#B@~hs}{)0;)zYNL`m* z+yKqyt(AcHhHx?3|uR2}QY;BC(h4NUVKKdV4Y6Fi>i)l3{=EdY>&PT++$K4;qeFcxd zZp#)XKx55Gk@pAk9q_vkhrZAbs)XwQ*QUayO84uI*hf`r$H9 zT;71)1thu&qX$%8g|b%@&x#d)3klTPnwa`@lpe|#gG_Q}Nq^`9ey_$hI8|i&OgVjH zqOnfj=KHl5`f9>8+wTbj-Q>HA)($6qHDcT#FNY!34F`j>glk;9Q3TT==56TYVec0>W)^vi3FBNPPGj?JwA4;C|3-WF(Z~to(Yn zt1F>~!w;4e)eUN`HvxWbf|M1uw@qOPV)!J!g~Ny(b{#j)b+Om93QjOOjDF6)2Gy>! zdy?=1=z(yq6SaRTQK$&KTq%fcs}r3ATzdhbExkTy*LT9g;^utR=G#Hn$stA%%cRdA z4k0rH(Opi#Z6qQ5PrELxg8lfCBOE6}ffZ^Atw*wf;*ft#@U0fyjWtLobWhCUP!s+6 zCW!Ljn(BM^Ue~Vw3PZ&lGvVmsvKJC#_B{az7(j2t7GFR-fn;NbEHw~K=?y!stOg5# zoZJ~1u}__R{rYt`fY#QGM>oZO&g!jJo*&r(K@r~lu>rdjIN%=@$>Hhu=D6*@DKge& z;8_)@g)|gm>n#z_N16+v97$FI*#T_f43hv{T_kw)#atcWtJ2cZT>$mq2jejBgi7vW zRKckQG!j(ge!<0soZ$-_7Bf$uoIDwFMjA;VT)g==+H$kc%mS zQh-yUYkW*M7Qz}l0M&Bp=a)d_6zU%BE@&*1V2-=w?TAK;Gl-}AlIL;E*Tlgg6cyMU z5}Gvgdr9%!&=_EZ{K4`R%B!_28@tBpT@`c;DJ4+e+poI8FkZf#KUKg1)4>E zlarh!CGsq(?ntzi^*Lp_4r@mb^Wf%+m*(um4f5Dm`|nMj&iZhbeg#23sCYC4ih<&@ z0TR8gtRxRhvPMM9Mk`!9KLaAC$w|&+5j6)b0nxp^s9oK|!-1&8*Zur@LC<0LiQ`!7 z36V*nB#MQR?E(P>IOG77tyS#o6or^?HR&eU6n~X3M@QB76U|5^VGp7nH*+)T@dSk+ zUX3XEWWR`ngv9+o#LiR!N{ zQuP1B-)6X6i1_gBU-99_;=%dZS*gFmgIy<@m@ZHi@o6DIkmJ{A0D)jGcH!p`oV?QU z>6PnNx>CqENjc}dRYu!h?n%Ll1;O23uW=Hu5&(+i2<)(-I|@CsI$sSYz5IaZZ6`*4dir*vqr88Ar#a2= z!O;lGE78i{ddZj6)oDqz1jNP?(h#A$#04e(xrL==QVr5eQ^*o#`ub9T8CeU$52?K* zH`)L7YdP_v$Hq({5}KVnc~Y-qo3#+P`3TNSu~7QR(6$spLdkXcuGx}dy2lbnp&-Xh zOix>(Vpu_{J&Ot)BvpmTbUUnqGmuOoo6SosVIlc3Hs8>C^EvQRj&G=k6ds>KgX0sUoS*X=;IMueS-d@2d0=)ZCi zy*SYyoMeBp{-i7YVi7jUiQWaR50jo-Ne*c7%ht6Yk(eEbNrAjcf&j?EkW*w+IHd`o z)Sn4;h|8vnIDx08``__R;bj1s!2T~#VJz+JYB3zn?(SCDIOc$IR)CTNFEIXAlrHLh zPCw|$4vOS16z(^>Zi^j#btUs^x(?0?RiSUaK}m@~j$yd*Fy~&X@WC=)K zfjG#4K|yE#=5JWiLW>*PZ(n3o*XVm~DsKD_Txnqu8@RJ=SypT?Dkeid{`+JXR)QDm(uPy-qZJ)QGjy;?6V zy_%ehfomjk>|w$kp@;11<8d7Pkea^2dsxRANTS&(2Kg^OGTb7C3z%2{TM}_8z+!pGqbV!o$A!uc>T0V z_)aPs#s$u!^I|O=aB`AHUH2nM89)Q4z#-$BfuiTZ>4NeVj7y=sR&;e4$Khi^NSQs_|)zZn7tz%FWEr!_M$NoxQ~|A zi@k{adwgdf5v7qCCvj#VWs#qdce4yo(K5Z~-BQ2f7D@K?8VMc0(|>VxIfh8)*~#O3 z{?B!8k-W7J#}KYVCRyfL(A8QBs>FyBoSYd zKg2ywN({sC)YtD=e{LFW3TI&Hfwv#OJOZ)wtn`^XErgnosr>uPv!|L2u>@mcd&20n zIieHtDrtM_{tG{FjDY&h5ZjSs#|V)E^yD#QUJp2ki65{1?~mg%#I7BRq*D+PVL-l6 zuPM9<`{VGX2hx!&pt)+OdRUB4r5fOcAwiu){RV{l<@0CSha7^&q^=YailJ>4;NtQl zha_z957{Qfd0nw0aCMi^-}`jFhgE_w{S)XSGL{GE0ramzT&O^%EbYqQT)`n-PeVGL z8p_4ZEw_G!<{gcvQN|z0i&1b`vJUpWT_^I=QqVA(|G=FJg!>W;4O|z+j&$knSFNe~ zewBe-_dTNjew;=ba?00{-MZ^8tMLezBFCbMcTGkuOuMDEG+dfGXJg}!C7Jpivzs)# zi3oal-388p+*0cUFZIU?_zL6m#=U513uYqYN&Nt3tH6z9f`GwL;7qK)3C#~2=SES{ zZVx4+#Pi87YHDiK&!3loCk9v-kRzecY2g>*+Ww^1&}~rn{xB;@~pMr0#!)esf?rU?% z`!FM?x=D{qbIPdDgXnCSu>Z5X6J5kCC@*NVx8I&^%eD!#)x*$|H#%T$l3v{@LCHim zSafO%)rlTHGp=!bU}1rdiMEs+Bm}~H47gR=C=t&#w5F&YE6C)=fh*_&brDVo@L|^0lr{>1h`u}d@>l{bp zSybstQ9EyBZFa$b*?~SM`PXJrNKKXYY1bSZ zi$|P-7^9T$n>TOF*aA+sjrBX_XS{sr7Z+!qnY-t|Q=;LSoK0hm*Rcnj;lY8*bi2Q?jr>&&Ig{z2>wJ&ri~g>22C z*qhI)ar6Zwjox?l@KM|y-n&hg^;SV3O26 z1>!a^P9}X@gcTL}Kt9ndeZ#}GGFx@p#_k=8C+4Q+sZ*kVU+{IC+4TE|_y`prW!u)Y zU_Uk?+d6_mEE^sLqwZF~(B{`4^id#tSyd?@^6E*=8^0wJS}MDqL01(7?Gk~!sdWI2F4Sl|K4_@>Xt5A;TtAFBrG4k^!HM)4Gdt*6KkIwitLXE z^MVJ3Eyj3WrPNE>3|TtVfK*4=2dvMWul$A3?fGv~_6qJ-dS5T^ zj%e%duH4-zEnhLzwB2$10>Ante#w{mu5AH>ZaYg5|GD$Robem$R7R8{SAt$nVe;vo z&p{ehHDL|HRxB}YEaiS$(SbR|0Uz((lo`Pwqfk)8T8ph1B_tu)R}U%;H@ZOOvbppvDl8G5pL^^tUs!cHx3eKD@Mw1v~I zp-}aREo)l+dBdTOtv`mhdJpnFI4uVUbT6a|S))D~5{BS5w+5{jZ4M%1^jZn$fWAUd zr~-`!yiLjq8eb`e2A=A<{!T*Ut~W59Np~H7cpnuI{bGaNIey?%2%M5tr33f7&aEQfM$u}XGT_t`VwT($4w zV3}klw@uh{RY5^k>XvcgvNP26AD_Y__y`JMw$&$?KoZZ2JH+X6z#OXFA zj=v0f`oTXSpd84~5r_%OipyXl4Y=$TC@boF_GBU{jbt5p3m%FW(buV1D9PmIPV!D0 z0ZC#r>+x16dT?ku3agf)P-<_7g>_L-Es1@rD6xSCO? zYpUp$`mvCM@6_6Y-FcfNiVyAcVBiW~ z@+g*fHC}s(!PkJNVyeSmclXoFcUsS!`~-+M-r=V8>&(H0VeP!>A1jW@j~efjZ+Y4E zG~sH)q4}Sc=9&dVzg)J=RRje*ldIOhckkw{ZTd2;)7>=ULQ1}_^d1o>S4mc7422Hd z-r?KOXQE4|Gvk?8+TNaf`lX$NgK^Su>vK%hGV0g++y@@f5nA8+c+448Zj3wUmfw@w zclZLyRFN7C9!W4Mw>gRZ++-690l{03!`*nx7Ln?KC6{%3^9I48{(iJf3VI-*-4n@s$6D4uw|sHL!BYxS5@49XnG%CfCiLF%QVO7B9AWe^_c;L3Mv>EA<{*zt?ApUe{7Y znpNOxZMD#S{h~vAPKHh|DfZm%cV+94d?9SPaG5J#Q@%W{xXe%ORfDi|*o8^4r+4m9 z)WCM-1LHy$g?%O%5*6J_T|euF9saJ#apAkX+?cUA6|3WYoGFi=KX1*A5}_(HqvN85 z0juOvn9dzMh~~1*fjaXzMu^{!ZO_Pb?K;nSPI)eg^0dPPPHCozS(Jgn|q;o;xlG~vEt-Fcj-Hga`=oVj+N4U}yhr@8% zsRwxdR$YOKm?+*hP_i(-qIhviAAnA63&&TS$~=Gy2vL`nDg}OvW24OI=E(Q7DZ zIbOPSzAzBcg8f_W{Oul3Pz4ZKuZF@0(Jyw6gf^kU-h@;{-X(?ir_jVSRD%{IXBO&; zS3#V?yVNw56;JqU^!DC~Z2ewd?7qFtb(GcAsJ`>Jd39};?OFzN7RH}jey$nvYLBxM zVEdi&+#~P8l#P_Y@{l42?gOZf5xy|x|rA1-hMj>jQ_cUv}`{+IMcVZzbqm>DQ zXRq&^uE@JLT0J8_r8=(`C0|o~k?U(~It|5IakJd?4ZCA%Dh*{*9U5-cEEXi5$>B2) zz1`5ZAnx!v_vQ!O2S%%XrL-S4CrfIw3D8nZg+->VS)2Liwtjk=qW^N8fAH4oC@ubZ zhpojO9tNy8cS)uG-uCJQoAsKL9|hm~u0OFg>@D7QSGuilb1z;Yg%=-+^9#rthi7GE zl;UMC-9LU9nspq)fqOT%osbh`pE1(Y_kasizJvjSR+1H&WfK+8`R)GwpW}F*`?!zezK`a0eaGi>e$Mm#e!Z9a74VgIP8;}q zsIvm=6~q0O{H4ePS2GXAD$YHax-~?H#lobwPjQaxgx!@nWaZ|9iPGvE+N*0{WV=5$ zJEW1kDSsMvxx#L`BK@)c*|3B{=11+!uk_AJ~kW>>|$oy#Gm493i@m%r zEAABPTKe!Lo`s2Fbx|4>V0`>@!MU+wbMB(z!-psfg}d%#4{pdMI57`WTM#|ko5?T4 zc;H-r`8qFeb^2rNCJ=+NYl7Vbw7=5#?}zt-E{Yf-*SMwT;`RV>+cR9Ouebhwo?*7JvYq74Z>ycv1#<7PW z`MQt+c4NCnwio~IK*V$8r{es#>6UH7ry9Vyhk8^3+C+S$y5) zEeF%Ci{M~@e>3bjG`Qv>lndq#yyjWL?51$KT%DI_j-Gj(DM#695YPnUz^Zw9Gf(?iag)=FQt}xHO#by zc<0CEqTo702Qw5jj8Ok*-Eis!$^x8PeUe?=-Eoo!dtil+nxTaML5A)iy!-|aCrs~t z!QpBb+;WlqzB5OF;8=vSR}X9zpu9wQwSV>oc|eyM)LXaxwK)_71wI*w+VQ%K3S3MV z00n^Ja|xQO+6BUJ`Y`p@E$g(1yw1tvAEv!e((zvDkOi%dWpw8bN^&wPZAIr@_>zyw z=;&YvnYJ-`G3vSBO`6}FJ{2auvK^VFY1^L`rfY0+PQUJ9^%zz)j%40@oo@8~5UudD zO(jsTa7}sq_O!O(->w!^uJW)mKcnJm)px0+%;?!ZRTOFxVR0D>+UA~$Z}vv@6LY%bYUWJJ=Di9*jKj&y`ZV(cxDAgrFL4QMQRRcbpne|+wsy7|R zYlk>?&~C76f;O$e=a2(*M!^U&6Ao7J{`b9TVJ&MyzI5|;TbDe&KPdX~lvDY77Jih$ zIRaE5a?l-L`2IZv8DGHJWM@}>d5T)Vh7=)gz(g1s9wUbd04_zHd7p&+Z~pIhy8Z^~ z_QA8M(}E@W?=*diQVs{l0q#@pAIgN!e?yj|-A` z(z?ENaPT;%xPNNF0>9g=e;OvrW^J{I*T$zB9p3&>#+=gplj}g94~5*(-`^K`VuOWSrf)sm^2B0L**WFG8^An-)47v{xdb@ z1R5h-h+SMh=*liND*OJo+=GWEX$MX+WR|Y`KO2(br&#nSO9+Y2{lA-*vaXH_CVc*0?jy-g_{_m`w{Ch1gW(OYC^^9|v03ZW9Q~=P6$o264>xvNN5Yb=; zu$70V#AyeUTBb`}TyOS##ljS@y`(WYQ9_!lQ5E|tmJo%%>oS@$)xYh(vwF{x13gZb zXf?q>)SI$lR5j#D7Y4hc2yFoGvX~Ec^Mj0myl{%n3}JTI$yt>|zO;-ywC5ztew}tP zvqh<|t7zO6ZQS$9f7?GTUzs5P^qB&_E9@%6&&~tiuPh|&O{-H({O#AWiZm%vv0GaW z5I=8|P0|a|-K8$l=@Fyv9)QC7w^ZzRfpgEaf|;(#^w1avv`H&hiB1k1efY$&2P%0m zP+6z#ke%@RXFKEtL@H=e4IE!=%$H!tOL=p4p57NTSY<#F?o&STrz6uK(*r$EFPG-D zpw`OTYUTgIjAUxyLMJ*r@DR3^@}W%~^`hf^l3kba*QR}gbOB3Ze5(l&G~(+S=>p%w z>^NtbyybA2oaU3fzj9oN5>hN<6-SL9)#(0p9CnD4Q$6*0O~S{8*1=(QB2{v8jQ{Ay zUMBskk3af$&3~^PG+T`qJ*ugQiB)j zYN|dA^U}Qh#Izi~AARo_boR(qe)q0s*6 zwdtMm)s1K0XTPXXcQ5WTX!ttk4E7VnvoRLbYrJuZ z3tV>5nFqN|?zP_yvGK6Hu?k+hE$pb97;2izkE->r<~B8#$%(q*dL|p}GE|qN*wg#(1!< z0R{cv=kCTX!t;IZeJ|rrf~^QVQc_8=`Yf=j$MN+j^pj$tpqw4kmlNmii}zf=J($uo zn+&{?7n%DCTGhS3t5h?S6f-zztlnyS-nq0T11iKxN7Lz%8{APs4zB z8^=cc_VH8}UYAO|Mk$OW(=5Z`nNYZ%ufYz@K#ftgIPU z&oMC2^F52@1NkNt@2mA{YY2QyJU#0#ckuBD9}If&6CJhq;H)UrS~ ziFo$u9%y~KEPx$=_QqHf4%2_tzig#ZA1?EmMIWy2wPS@MN)ds+Q0kHW877RFJQ|eX zJ4xOBr)-6=ar|{4o0_5V^Ylaoh=}EKp|x{l-h5}!EM1${Q~SS<4V_vl{*gZ_|9g}v zl7ui&&!4ubu@nmSnZ;D$)hM*TNDls~U#QQ(s8om8Vm)ibYcg{5RA&HH4Md;v*(RouRQL761%uHf^DJkml z(j_lPNscR$)vFtWBKsF$ow$5mRhy{ICWqAl&M5wVsLZ7(EH^*4Lgf`QEy>YWU5iC@ z)+ttL3BqlG?Vm0iJ$~G}^85^Lz)SpUU$(+jlW-r&%NiR{gJ3~7zJ+foEE)9QpgPys za`^wXGSS}APn7@d3j>*4*D?0EWJ#1F0?;@~+S`qGj*MjFeG1~Yij*c==tkAqKlf9n zO?h!IO~LAiJvX;tptr{TCUdxF_M@{*(aIwCn$d+P{`6e@^|P%5`}k}PQylVa&qu!) zRz{{~$cvIu=W0>}U!a_1VP8$NKpA-7Bq*WW`rj|2B)sivMX;a$j2zGxYm}oA%!KivcZ}#WL z>!z&iLE=zq(3WER42tRzELxV~_yaMl$%} zg#b&T)~0qWtg3z&2#$)5<%opyGD{+PC%T9NRXeUdMD`VYiVp3{OkY#Y!`teYwszgC zI1Wr6P>Tq>*~dpQIl(&&&T6M}%s~#U(YPt6p^cpBwEW}M=jE9g_=#wAt>Pu?CpX_~ zXfTYZiM3qvx_a~#H^no}(viafbsH>SFgiUC1woG)$cbRwxHi{%2SHRVxFX|UAPoXW zF&h3u*^~i-cW}5Fe|rQnFB&TtpMZNe5c*ON!Km_&Dh7?~8lXSJA)IIO>FXeHFPZ-@ zNCP%MUjNf4&=C&+3AC)q*_~c003hMQDg&E1^nVp(5ptx2>rM44$zs0o2gz8l&+n7xzu0uu~g^9 z9?R}u2Ir^4r~5B4Uw35JD}FA}c@r*|6o={E#HdtRox);K}luK87)1}nZO}6gMmnC*lSZZw;+fBp#Y*oDPST4ULhGoLI{q#TfN9@bpV7`u+W79d=X#)E^xF+7xN~AMl?`t zxIwq`5;Q?qTO9tYox5YbJ2FA&KJXg1XdK z2}7fr*_uKmq3Ek8dC~xm+Hx4O8%+cg6)Q&7WK9QkGil(<0xPbe5Ej31IASLYZkIFr0-`0batnS zJ@m@zKdHqxx!2?oJ^e%D=NsK1b%J&+1(=*o1wR0V0vQRycomw-kH?Rc5zF9MtFNbX zqmiDz?Rm~$m~&ErEC$g$0>U4hn~1m7U0rk;<Y$AwT;D8O852cA+^@^8eV*+4e+u0XDcv~9$y10M=fO$RQ zNk^gz02^g9Ax};I!88k&{6*07fdJGoY+Rn+!k4K8Ojht~VIeg5t)_vAc@R@b$fkif zRa7mInX-d0}cWN8_NYUQVW6LC&7G!Z+&vjIa;v@I<6AYktPuj?2ELQF89+RT#v z*KlM!y3GokzyQjE=%eR0Ls(cKmmtNf_ZPUbGSeb{4Jqppz9=%!>wid1Z1`DoP%CpK6gxZV!HP32uPS%WX6f{Uw&rX7Kybw^3BDB4&l+~ zf&^A}=8+Uf)3LWN;XBjC3ojO$8}HFveQeT^*^>4|I!HVqcXgwyNI-@qO|wGYVL^lk zD@fcsP}&>RJjKNeiS3eG6d67<*wTAtr$xs`Dgz(;c^uJ})TytSnW4;kvHSyP?>Imx zE3>40pU#mYDV`1mNQ;(?(j5_&4=p~DtKvp0jST@Md7mCW9SEhi2B2P*3ZEe8Tdu*Z z69gh<>jSm}v#mXVlN7J9|4atWD|vHsMqtO>0@gf2s|0)IXpt&-od`=+?_sPV^U&3= z3GD1{H=t00+1JDq7`j7$)ti9zk1no`l27aXlg-jU#y69tZzdyBX7Dx8SRpf3P(ayo zC%=n6c|%c0a&kNYKL;~;L?4Zl1N5X{vB!kI z!@H=ZqN=Ri4cr_A!B_T}L2}n4^{sHueKP;PxIKoEq32w@mn0WO`4XYdd)3=3MW)Sn zERq<=>+KtOd@rYH*}i{;$ms1l3GG|IR~>F1xFdx-2V@)f>=d;U^s%!iG#jbN19Uho zZj$gb#ctDEQhj7`;pT!9&kF$wx;VCde96HTebrh&kwz)K3TqHRhIw97>3<|e1?7F z7c?%%z%q@*tiZnC0QOU0D->6z`xqxCYEtxD@90B2z#j<^`0DC317 z9VA-7ED{mjBj^h7!Qg{F1&Ij(u?V7MfQ+xor+ z-Cn?sfU%BQWZ+17!Uy95slMi|(aM{Ki7Q^!M!c@)VnONR&jMC(+`T&{%*N2n4JIZF z(=(Vjn_ln(nePhDfyd&$7wFNAi=W{Y78WLLoDz)5=1!{jLiK@S zI5?6zDl@eHq!h_pcwxrt4h`j&`gbFW!R41qBQKg;i^ex})!FD3*fbxL%eAheMfk{a zA-AExghJ8MWLuLi8H)sKIE=%C5#Nhe!E(sn=Pep9mhdXMc5AwT#GL(1{_`OtIHz6f z8~1&GMdswz4L(uA$V!c{YAt~!o1R9?HE*Va$syL^67@W-4eeOAcVA#A(ZL^#S8$>| z+gyYR&DD*el60_-yf&qQRRV!PBKUE*i<%c7=*^x&yA=ndPS7TVzBPh{fcXjy#s_Fl zL)E?G#eA1R%J!?4n+PrJZ&9~7pRK~Yan}oQJDR)bFE>FGKvaM6rPB!FvdAr9j;M3IlUL=fvB3Sq3$O8DtfY?)S=VL%>S zAS|Pd0Cs>`8iJ12vXvtGTGtd5EgaevsK&-6kfL&dCGkGUg%*~VwQ_>x3WNS{Iq2FQ zZgIW@1SJw%OD3=3;fky?0(*cFNcLT|IPESqB|*%Y>3p)^kZp@oAJZtJ;1&kgV62Zzn=T@@Y834wODMzqW3 zybV$hi^t1y6$XyT<(I2TZCM(9nR&OrFtr6@UAWNF8tcDG+-)6~EWyGof7l!v z9Eh>jBIzD26a6dFsNwT#k2J_sQLy+1g=8T61l9zUTQ7$gP}efhHo8NV$NXxFSfe1q zdHHd-NnUv_x#CUql#Eh1U;UG~naxs!8R>APK;r+jf2s_mk_@CpXXQgJdaAB(+%$xf5k{tKbHh ze)sI%@E%NQ5NI;oP6X7qA2L1afE9Ah33d$P(7fHx@wxwvt)FlW;$PdYlJ0A=*B<}I zJ@WqEmgoG9m6*Hjl~qAsN_iGU%&{97zd1eup&K%H0%Nf@f!3?Fk%Xe9k=w)oUX&mhR$s2uGs`*np_k_iad z;Iq+Lw>%i)OQRe8=;`ecCy8o{h`2Y%MsKY!-xCzpvyk#jx{?YG#-A316o2ICY1F;P z%Pt@wK)cBO3Xw1p?84wqB;gk$@@ufOyZ@-WQG*h$DID3ab91X$fV_H2dtYe~jAtkm zs32($fpuJ`BQXz(rY?}%0&KlNhhDP^$kvvR9^HV`020my_>H?|nTsp$KYj#Jw=Dt+ zjbx5~DcPOfp>XnzM9+VkDDc@i&g$;BC}Ih$@cKHCAB6jT=%hmQ>Hm(<&CM3Sb`}xI zSWVCf2-uKI2|L9lknsBB5ihos;{IafY=u4S!ZMPfsY7sSzYCwrMyZF7im zVr;!<+y>j1s73ME)r)>h9rv0_Di<>j*Y-sts72w-o){u+j=Oqkrwy;>`nqsJ!n)DW zq8R4faR6mQW1z)C>sz!++~D=FPjr19<1&vA0)#45G?XSD60gRcddBBCS?$nX>v-R6 zEf~5j1V@F2C&M*pfgqsZnFY7s8}%&E5kuO5q#!q><$=;e7SN|pzK62R0IQ1#XoApKFb5?)LUsuX z3L0oOfMpNhNzVb@^pAX|g|F}VlfiuL)^eL4!Bi^$LbFvwN9)290AIC(^F*zye~KPx z2Rk4#K#qzCkQX6$PTYg@cQ)XTLA-hyQQL#e{yH=Pz-FfB`4a~jfQ)H#>0!t?(J-?WO22=#^Ycsv2v*X8uA?=X<$7;Gnx=jFSOgjkW8T zVB@`TAZ@)6?}oH9@s9)G(wpG~qy%=^Rw@eO21W zYx`?`h;ZDwz9*q><-9$$<@ogQasHb(7GNF*o2U%p2i%^If!k^U7B6Jfi!Oy|RcJBh zR*J1mO{#jwJKB_MqZV|%=>vp9%~X|5Dnk2Ks19eEnltd(^~t&mQ=eAI8$35!LNOHO zvT+Iqo2g1!-(=#GrEO248{M$__R&>PgFS^&(c}Vg+a(N}4?^s9-J4otZ1i^CGZ92| z%A><^BuW@0pUJ4_QtzeZXDZF65MP2W$V=0AlQy4}wIdBwz1&nf)FDgq>Wh zDP#Gq10Fpl%!<1DP`~~`<sXdR0(pcfiN5y@A!Y+Yo}|i;`W~dEfkGe+|RM+ zBsTg9fw0{g|CspYX)DTdE#T%ryUWSo{khbqs3tb7J{J*Mujz;#j_aSqd?fqW<^~q@ z41ykaOWOo)lo5Tp8R!Dzzs5PfCfs*m44b+`72Sjo==)=D z;7;{HYYe;wV;dM!9PiT8X#rG`^iKhw(ijEyuc!9 z_W}4l$gRn^R-n$VppF}@xzyY&o%H%bu_(50_$_DJ9SLU};*QK*B3isi{zpse?FEL-9`u{OR^#g@6+s`HGdd2Swgkz zwk2M=g2xgc*!3;({$CEv!=r8^Ur9~R%TgC&?_Lpsc811emE%{b<#F2u1N{!q&2nZ+ zkP-sJ3~3N&X1?~80f{$aFNLSf9MWhEIv#=a9M0T}An)%1Ek5F#4GtP1$tHC>(kZ#;)FgTY`yohzAVb1yNFY z83xw@t$>lx5uSIFT0vMXR+g3w;K6|+Sp8znAXOJ=_`{$K`v(13gF$3>0tjFVKy5+w z898GTEfb=8Jd0z|9wRNqK$haDdPNfn|3lUx6bgK%;Jf_>y%&uS$FM(vJ!%ZRiI7}O zd-o|VvgUz<%o$vFQlF9ln0s9UdK)t!g@$$a9JWp77WvoEyM;2lXz4B;cTU1@06in1j6x{N$is8a~FZ^z9J*N#GVZM~yVbCvKs25VS%oUu(xe5AS z%Xd*)e}`S%dEctIx?y#8U5^f9|0Gmg8sn~Yzs_z!MA$G;{kK7nTkcDtti5WwyawgbMxDyg8%^>t3||4zM$@2TjQR!6koJpvru)XTq@zkJ3-Ekg_C zWi&=#?%v8z!DTO2>-$<*eV=z%+&P;MYN-nDIvVbWS^r*i zMEBfqkp+{suiMR&9zy*6)~HAK=e~=RyHzexBUc?r9va5)Bog1aGv%j%btV$DJPV*A zLz|ea6oBxI3L;RU=dSzq5Q%nn;SIx9&r%LuKD?@R;)4N7S@vBAHs7?FFJHbPa|^_i zqs;<$1mrHAkRTA@e$Q{k3iD|rZFEQ*amsX)UF?+fzysI`)NYjb{e_YP!KNUL9_VmS zrG484$yE(qEPiZ7jf(R){kWFqUanP6!Vv(s;#e*qI_?h&E-!7#bzEiTj@~JDmSQvk7ym- zK60-VeEan#&XF7uxqcjn>ZSq*k^zYHk_u(~OE%>da7bFms60*SqfT(6b z?mxgWOs_&A3K0e?K?u$grh6ze5c49ULom$j?Cy5_&ngN*Mt~H@6sui6fxovK22r;l zLJU0fSf~m(kOGko3xsu?t~`7&rzqrhTsgnKIsa@?#FZ&MgoG#20dd5^Cr9k~vFYih zyd!{hMFK<+M{7=nSE~n%D^hBYC1A+#XMTZn$6J6%(MkYnP++rOLQHHhuyaqUghK%! z%I=91f1RBLv09;9rX)jvq}r({nYS1n7@Sh=BYOJSpDylScl8vut6(PNsWo{R7DlR7 zmQLbw;m+K^)hDPOmpa}Jw_TUIF+Ej%zrvM!aS8b}BEy}7n%hq|?o47L1zc;8*v&c1 zm`kV@CGBOyW7i6JNW^JNQB2o`xThAZ2CLv1+0&CvV{-NsKJ}fG`mCwVg2l?Ml6bj^ zD`BNFdQk0gypes>Okur2T7p{Gm;BD?dZz6_D%8uCmZ%xkrxe1H3@9eDis#So`$k@f zzY%+#ef#%^!LgY&kLLWuDVM^MXBuee=e~gEP*fJ+HIPvw*wZ_JmZNPI92A5Y)1r(5 zAU%&B%=y@!*KWK(L}g$gYX?0eq98?X2^d0eZz4F47T;rHu(R>`(1U}#w3J+QSwlku z$!Z4E9|H%+1sH98WP1!gA0++pj3@DaB@8*n-#sZs0|e+i;r(gyZEky~Gd*d_k{^-Qb8 z+ImfPK{B2Xp^GAs>yVo_w)WQ$#!`^2auJm8C3Z8gLGeO7G`wwV##wM$1YqrRAQ6b^ z4wk)f1h63NI>B-ymeP>?Fma?mSHcXfAOvL#R>A^x4>0LLr#E=x99(cA&CPe90bg1> z2ZLzvT4Kpfg+h^HJLtcQIkx{jcy$A_T>Ea2W@jroq?++M&WZ>?790o=$5LQPy^DN6 zLP{<*aX;?2^KaXFcjdQ7^D^;5o-SxIZZZ{cKKNm+p~x7!09gjX1qI9iqIv;^E}Wwt zC;tAP=G0Cl4O7vcegN83M2 zt~haflYG^PZzcPoMj=&IOYFB<`-rD>@2Rx{?KQR1g%rg{Z3Y6n8d}opKb{%GZ@M#i;51 z4=y40GSD|6OAHXP+o7mBe9I3q3~10GFl}JQx+k5$9zO_vWKc!GD)=0hKY)_TT9|<2y~5*%0R(&&bl2gbNEkIM56b&?!0u9Y<{s7NePaa) zzC+xWP~$B^bR6_#A3|#niS+>qEdh-3K@gA)t1Oj}eF}%_0d!k;;Nv4|?lsUji*C#I zXr`NyjP%kNUA@}R&!>E6I(`akrl%KE?CtK47on~AJWI$AUe=ujWe=S)4Lu12KD$2Xvz_>_UgOt`{gU%;$}zPjcG6{E^msa@=xj>%VYQh*9yLvR8siTeY3cwPa65u1|o66$^9N2y?! zN2wA0ab4;rC_8}>AeG6`pLht=6Gmaag~%;Tc|h_zg|mo2=qd`laN9D~v(2%3UO6e>sy5bQr9pg@N6 zC@>lafbV1pWYNV zXqX>r9v!`ak8MU4W_y=>&^Kk#pgTJUx)x6HRVqi&%d1xu(@kb{7g$)WRs1-ucdJ*q zsyfIgO>5lkJ-Hg{bwU_0>MURh0#$-`PHRbmEf+E#ghzRU+Y@3DVJ{xb{&7cClp>|P zf?=TMDr)EF*E=s|aPIz1heHkmG=fP3*iEGbogY4hDjCMSTpAgm(MriYa9e%V;>KDE z4ToX+D4tmNHW(!VuLD5@)qbCV83Wu||KbC!#P`&N+ik&!SEEqXVSE?ucjH}JHB!L9 z2g6Bnfo>B#5rai?iCJR8SN2*Sk1^t98yjDLdZIzmFC@Ed>d9jet!!1n%gci!t?r|) z`vh$klXpWzwtuYr=~=2B>b6Nw{Mn zMFFe^`5DXxg|6P~k3qwAiItk5{V;1ArW=$Atzgp4` za@%rCjUKQ(QWz%d>ghdjJW%48&eKb_%~X2LE)sbcFTISrePwwipb%AK;-{;tkY~Zb zKPJR}LpPkOlJSAYcs*Z2;A2hkm;f@zcrr3&ew6Xhfkt7_T-1#M4)Am%#3QKd5H(J4 z67ave;Z*Db@BhvT^L}Lhv-2|m@YQgPzU`)xe()f=SM}`bVeWLJ8!H?z-Uv_NXZOeJ zrMHa3saqKtmeHZ*E|lOnhag_U7e)97p^O zUQMFxH4Gcw+PSxdG&}4b6|7Jxm{p1ECbsH0$ojp+?!7y@;v(u?$NQuGVHkF{@g7}x z>PB#+KrJ4%tRJT|b$||bbp-vRqL<+rCIi}KDtr@e#VN3&LIj6mF`$v?9p_qL<$ll( zT?}AE(cC@tk%8@r3f3#`N*~102E(3T(1;c+G;MUnfyoTbUAUbbM@Qhs8iR&%>;Crq zyBLVQfs@PhNj1tpp_zE8ZUg<{!o47My4EUMkZJs9r!JrZp%d*H(N!(JxDHXS?5ovd0|mQ zV>bpgKCc@aZQa%uV2(2xpqVu{~ zlD&+qokln7M;$ooV<8O=7<4d4k>5tCKwFx-;sG=)Y0ga{VIB#e0YVCOD?Rd{gu>j` zg;OVJdp(c?C(<_?+LEx1cEO+N0ysG6qm=O>bbSB?gaUPCLLf&?&*tfTZo_XOoOsXK zpjsUIcTtbcCxc5#<4oVT{Cw*XPj`hRGfJfrl!rF?Dr|vfG3>b6sYb-M=8OTkS@K3X zq(;8J{hFF8Lhe%O9EnO}!QyZ0C?z9pGv(WV3x$Cf@NFh<#DzSN&&S1gFp)W97Ya^S z$f252*hNqb5;ySLh6~-#O&Qk9;W!DGIW_D6V~QXA`jt{sJ( zk-_^Tp-BFY_8cxz>VBm%t#LDwY?0*d;$j*SIb}sfxHw|Toc)5Zp96~hxJgi-z)$OJwU7jBX)3oy)Z&aqVW5^e}eGcfRGpTudLY7 z=SVb%U(o=Z(6Aq1Nzo$u`hcb=@2qJ|0wF~Zjgtm8k5P|=8J|{rD&XA*ap4GZf z?3QbA{_BxO-LY{}lhUPE#JNGPIQI@rdP452-;>e$6NYn%Fvz%E7w3+7qYuuV4|A0P zxoj`Zv0mUjTpvubtmd||e)`NZUZt+WA#UjTzBe6xm*sa58C0|K&+lh$`%e4p@9zVv zk{*_7NA2JKe;TS&Y$Jr#R@A-D;=WN$a2m$D;sn{`)wZ?PmPg5D4Yez1W}Q2_#WIC$ zCFprjXyaF|-um@6s@Fx!V7+5y7x!XqZ%`Mj$zb?>#TSEF3AYSJEgyutZeq_%$))8_ zQ_6=uV(B%HlT?ohkge2-DiHEI`Vlkmf{1&oAQ$JlBq4Ux!sRR+%s@$oQA};^&(Btk z*;ZDb`2FD>df(wev9hw_cQ7X3a`HW`1*)@#uRnbTse$o2W$bzFiiy{C7Z(-Z{^7wv ztZWz)mi;De50COq`90~BqC&=_BA)r|$%x$_#=r&qo@2GIukoI^*>UKVkrsSO@HbcW z2}-0=8pM*AVbUfXRkz7lh%mr$crLB_@H!f8m-$L44m(VoM$b7w*i{>U)IIEI*`=*= z{B@CbwIbsU-lR^xM^7${;*`}V2dzD@@m9m~Ew;IbKZ<-02zY+jeKu*BP;RWW;k_Cw z;3u+RyoH43>BEeZ-fstkf`a1EBn{#kA!(`+oQI-|&o95`rdeKDf#O*KEMkVUXB_oI z(pi7rE6xARm$sulZ#@t>^-yqd@y6_k4WKlS(fS*(E$Ym0JFok`e;q;Dm)xz(#CaN9 zV_cXsoJU34+Heuo6G!<*2MPq=J7eYTkcg%qgfseD6tvJ5oJ!@Zukq};tHCq2!+@Lif0yhQ4qX)q z&|jh~9a2~8=m^s98rbnraN}bbAa@4&Rz>K6x8C*pCujbCfOe!mUIX&9%?<)Byah=C zH(L)hRJiy-$V*%^^wi?fLs3=#)V_hNBQqhzA&D|p-59Y85&Fxrr1&(_H`xO>WG0h_ zCiN#|+!FL29!mS@aMF+Zll&^srbEy2xlAU>udH8GRj*A{;;d_uc3wuWj$Su!_HfYl z_Q|bKzFNtu7I5>W5Jpo`|M^VZL=z-&4|H19%V%)E8q6~**)hnj^?87%Q^F}26O2YJ zOKt1f9Xh4)S(9adl{t#lq#n`+`XhHb0hS?=CZGrc0tiBt7Kmdx^yPCM3kDK zj6+qmf5L)YP12Ddsr0o?k4^OhHZC^|GeM>N?s}u23gNY+j|A0MbaHtxZ>t43u9$}~ za}rJ+{ZOxG)6rPmdpG>C9VRSN`ME_Y%42!#UVhEK0#x#6d?XZdi6~<&an{ir;D!6z`cZ=0Zcz(@*T#q&T!8`Li-l^sVomsxO|M%!)0ii!{HSFQR^bJYD4NvpCcm zoja4y*wkjY`_0Qqf0(gqpnhr{J6SZdwfHZZ%xbXc))v~5atTh|W;d*EuB3hm3rlBS^xMpB>NH98<)L+9a(|0XGl2i)X zNMTV?4wa^b$huI|u^#LF`%Np?d_!nd^ZlSX{xqhcTE_dB=Ys7uX-1zUP;>K(t@^`> z;Z%$h{$^;#z>5Vv3X=5U?_Y&)rn?o934OLl(W>&kPslFfYxekQIk^39e=5tcsI=66 zr#io|aID$Mbj=r+;#y1>_7jb%;4dbtvwu4I2AESVYlDmmYKDJvklnd3hzj$#PyHjI zsg6#YF7?mSLU%}Q)?h&Zosv=opNAS=^!Dox+tx31yNa6FL6fB97+(!4`&O%q(O%)< zm31E+;r;744j_K@`MwLw=&|rjUCZwq*@o;#DW5*Jn`6YVZou^%fL&r!wry)!#N&!C zF7u@bTrrFY^Q40Voy3*2c(G989JJjymj>Pk#WHeYEdL*bwA$)7Bss0DiX6*T!kDG= z=7n;dLRE9*ntgLIV5{rw1z=e3yYs(JKy(c5T4dnljJT{S77Gd8sZi(9AI*SH`y((Q z;ffB42)lmPN04K=k|JHi=p-~t}_uN0fR=GNzoim1@6O~9304r9GVqD(5`!xl+;VWnvSp&Hq#-N$Bs!6 zef$o*8zvDD=LFXE4?th!92k zafjztlK;9oKJ!()ut7wbz@(X}k;&&fQ?h)A$$Avol(}xm7_U9{0Zixh!C)gi>x>18 zQ!{`qK=M~edE04t{Da%Y9dHQ^`v6a_2$)<1sSF)DyB!#_en%SPj_>|@a8VKhD{r}z zkjE0nRI_d);_h!7&rUZ*LB|*zZ5vzL#*0Ry|D^sf49aEeuZsL^?>HHJqAq_;*2w5n z-Li@Mgf?K!qggY(I-h|nHwsi1C+Dp&Y=ZNzgSpH<)9!;cpQcS7YuRJrZi&Za9Bk?1|0f7u9VfY|NdohTkL>=w@jIanG`=V zj0bZjJvMrLK(->At&)w5IAQ=Z@1h~=QMbVmQ=fsei1--uk&Vp?rpfM@;uCI5Y?kVA zsjXdt&>o7Lma=6QhgMp4rA7vKqlaKAa#g?dWMh1T?+|&1%L&_RU1KDms7Bwi$xV%Bw#8)4yHH1Qo4 zT{Wxz3^+q;XoFw8FbZ-wE2u~zVE*$nd$25}`)m}89gIpNzlCXX#)J=2%3vNmbxJa= z3^mgO;L~@SY$FW){QhrnEf5Ge1V&6Rh$1%^(D$nV8ngzIv>(JPM_#*=1j8yI$!^}= z1}XY#Wgi_9ZPpX0Ds{v0Rv21TanLepEZ{om zZa_Bu+1?t;tTPM1jU!(Q&^^(hg%b91kdwm&(?!%Iuz#m+lc3|6cs;iC(jTybiKyuK zc4*hwLf3k1xRa9Wo37Fja2V|)za0ssss21v{3de%9=c)R3}1bc`QFkT{tB_*Th-G) zJitFlDL@QQu%wy&{gPQ_e_L^La<`wz#lxfbYX^k^6|XrqtPnFodPwnnsY7RRsdKe$ zwfTpvk*n0B%q&;hVuHzDp7B@o5Y-Gbq4@atE?B)b{$9I%vK;TdUXU!}ynuqC03S$s zd@`1w!=H&Y-dOP|IZ+E*@HRb{{!wa#ImjNJ+g)^Kne_gB ziUIs8J4%_{$JeEs|2=H}gdlQM1#aXf?eR4PRHLxLee>q`7gtQoKt_D;u&=w@xdZ3Q zEm(4)T`}F_&5vv&@Kea!PwO_s8_HduogDz;E1YYCaI9|s;qlF*`EMVE?7g`BY)dbY zAFlv7$6?~pzPF@sCOC3@nz}d+<;H`}gwohdi` z%en8mE*PwS747p zMtHE)3m;~F%8|yq2~P&(txLh=@^9Z}+MB_5C%C`AIK}V8JbUH*or*VEIM_MbVYd~( z^F`97(FF>>yLDH#lQO zE*s`tmBEf@b63)%iylYps{&yg%Q+f`ft4%>kZ1FgeYOAC)|jN*bitD%RT4CKA(O)v=rrrLJH}UNsN&hWFbKFekTx-4} zKp%s|T1#qcFJ7j@m3bB#W3#!*_h#tO$-!#Bz`R2J z)t<$6qru(wm+)Tl(cH2m0g>&uCzHm3rD*ityK+IKAB8AqkoO-8kJ$d;51`K7em1V| zO&U50(in?@HUOk6^iQ~Wct*0G^YAZ_G?kVTDaO6NRyIp6b&~Bl!2YHJ;#}yw7BJ3s zW1gv*m^=WqbNz8Vr5IOxQN!)6xc^j{mN@vG%*gx|+KBvocH(ULA(K zx4fYL4kn~v$jJ9@4Bd26jC&atMsWHQ=R@DOT?R!m$Tv{G{&D@<*!<#+*QYbwoU^|M zRP817Iwn{>ttQugzLpi6wimJ&x(Z){sA^b4JF$MwCjt;qX^4(t*c}qWlrwH80t4U^ ztf}A@f_G14V;hS^Jkmf<;i&ca)yFq6ELq!cW7;8hKR|x-zSUk|C5}-JjDLOo5bmpvtcM_G!cFC9F z){L5ArM!;TR-)s_Jcg6_r1xAob)HU7xU6k5hxl>|BW=P8r)CCvbfb|wxmz%GJC%7n zvai!-uYhOL$5T~h_=>8!I^1~!mg-1cqLcBNHh*96ZyT=uf@Unnwvaw|<7%caBAx<) zG()nHt73dPP&%QJqHbbh(pP@_6}VR4sk%Cp>O6i`o5xuQKsrQ_mXb~-q*FQtBm|^W1wmRu zq#Kk5C6$mAkZwuIZ$0PU`=9U3FmuM?yvKJx``LS~UoEcn#rMA3Hg_Y9a5%ZRK9-cg z)5#kp=ulaMXA{T#F{Yp}>t9;GB{x~r>bJWLr(%8&Wu6ol^)-3ESZdtrG!nm;YN)zX zy4L>-&RUe!UzKb+#W%IJ|7}(dA`<|hdM$AcK;~4y66zYeYty<^a9kLl4$CL`pYtjf zq)T_5>@CPuhKAVLQ~z^f`?)z;jRPb>UaE-_2y6=r3!lP7rgYZ<5%J?`*UPhNaGN7S zEeKxmhJt{di_092Qbga6h#&ML?&l;+scCI7W#!0hYLJfZL>0-l-Ek5L8pHZjNSGyK zy0pM#uxV7KYH7p!PC@yzS+TQxw5Jn0V^&UoqplqndEuKWrDE|qaco{PnS67cHhR?3 zUrvT9B7oRo=*?2zyq%~ohaN_nbpwy80}{Svp#_fndHrkL7h*qUdjA6BYp$;08m^v9Whus+R+hJb=&MFr^2KDG(iG`%By|*`E+uKzS-%^pI8$jRL{6zWLDy2uRcomYixj!;xNki0 zipiurd;j9n`-->VN3Zgo<_;LB2jwN|t^4;g?_Zr)b{-<#J#vgFCbI5<5>U{~WD6r0 z1!*{UnvdzO&R6csP-$~xN~jj_GtZ7d{pifR-VmU5p+5hTwEO6kZCXzu42Jq z1=)u+>o-&b0cChTrf+raS9S(;6;Rvle{P(OVMfbI64>v3M4(D%VkK zl&x2_**7HkCU2P60^G}-)+T;?vRB^F5Sc;)@*pv5 z#_^bq2-*3ldcZltMuhQ4E^v9GeFj4;Qcl2}bD;8u?k#my&c+*$7ud|Near z2TS?XRujPTeh@E?2p|K2+yVUNi%U1H?n(prd65{o(8l_P4iZ8K>)}2p0e-!}ni{(T z#rk;0AwwZ$runGagC)by~B@t@8{gL zBkZe*K^YW*~LhpFOL?#=-j`kEgMHaFVE>-zg-? z7V1hDp*2UP69^Nr%mFU@o_W9Zt1IAP#w&V|*#4HZ6UKIG<*Q~)=GGX!xHv?9R77=g z9+Em90SR6#hx6B+%gadfEY7_SAC5)V`eEKz5R7eLYH9;F}0V2k? zqim@u6`o_<%!=7R%wtVem424k2T2i3JGbK@Ui)a7lqux$rCVh(}{aHleSSk2oE8T#JfoM0?$2w6t&zAKSjQ%#_D}Wl*)UlV6m| z%h*Y^zH=V@wsb6^I6a%ufiWe~yk9t-?<`VDMlwY@dS$2eqU>9#&UCaN@(e_J-_3RD zt3N%Fe5^IoXAaEv*#aedLTru?B%W&-Sv~|gxP;;K)$fEU5Tow`iO3CLp{FcG% z)|HUu?;I5Cd~i71_85iO>`_wcB>Q;Qqh5U`&TSs{4~0DxMCEvgwOse-t|m-0dB9b%~RLp`6gWSq`-6gkr7j7!AJ9{A2qk}rJf7p&dGN!@x0*srm8>LPfuIfhq@!3 zAR<|B;v4JyoyB0D{cwQJ#bL^JOkUf#L0#uUR53*ncU})${z>XLU(~YXSRMbQPldn~ zS7a}A*rv7ChMi6=KmQl{@4Ms7UAyYa&nnbKFo5B)iDR0i?W=R_cUSH!9+dE2Iu9^? zr25w?MD9Nr6D$haSXcvD02s-lt|j*VT)zQL-~~}F<9ZE`Q=L(pc2(kSpKm9 zA}7Bi%HD!Z)yYtDTay;4EP1Fny0vwU5ijt?7_}r*M6E%3DtM5`&)$#P zb{b^dx}Ef2eW~2D{#dDLp-6yVxl)w-4dv7Fz;1L8U%gXBuJw(b1SKbnr*hJ`F6EQ5 z*DP5&D=G_v7qDwToEDnc3k=>Wha97|{d>>s4J#`GZST0eJl6PF*>@!>Ch(wgFfqfR zGS1SIvr4V3by|Ip#o&V$Tl#C8oSF28@7~fn*itO#svo;6=6RAE>pU*Wch6<(P}f_| z%^k@(b0JpnzbHLjX0CiQ@%_=(c}KCVL0)c$y1y3v5ohOBg9FJ*u5&+A{JNoo<^Ybg zDkSj(1OcYuYf+uq5l>5yu2Wgh_g-M983PXwexh9B`uwS30<)R9xy|?|TF9n&`x64$ zG2*+JxTZh5k>D4E%Xbm{WEL5M{;Ynp;v0pq^HbWrdQk2e0e*~NjtoSZ-^K!zzqap3 ztc^t<9nWx)IbD;-m7KzZZX1kg04w8xx7$2pAJoBvAkb}_7WHS;jgqj$+u2}NYIZ@+BV`Nh3exPUKbZj>vJp!0Q*8ZN#Q7pYCg(Bfu7nk{wNMIL zV)xBdN?&dcRVx)fWxSPKXiQncMSf76Bk1b}d_xvYh0cMvs`23Ij6N>Gx@ z<|%dFn(m#YcIrqn1soU!)CV&f+GZUk7^mz5Ys?4&Mfgbuf#zp8O48DblAyYubw(x@h08-F z-7lO_qVp(X^54d32OWx<3okFj!8V>iP2}4h5ebb(tCEsH*?sAkIN>eOr1MySW+0xJ zSe=VAF6XJncb)m@z%he+2UwG`&)iFkU(5A78NSV5di$f?6b1Tf`Y(iUq7glcn*FqPTjCS@9Nk6>$IJ2I+dNi)8^TSPiPX3O zFZAhQwT(Py+`qpQiY+SQL}~^=X@(~A#~;-En(#fuO{(B~{@yoc;*+sJnar&rbm-4+ z0>^`JPY}ZK94w(GzFPv9=lg1O-hYsJx+rNd7!QU`Spk3vy-6ZaNf7X$2L1qKt1_N* zFvdRCk1%Lfw;b~l3IjfkO+dq1u8Zv`m(`j&Z9Ynn`=c|1?pYbtO||L?b0y_bw{aEc zKeoJX>$&>B=2_J@6@RD;f5(js6s&Qk;Fhwx&sNXHx%+T9Tkma`Yl4#oH>bz56O7fL z_f)nMJT4kG9n~xXoZ*`O*)ValL`skj&}?OT%IBUgev{tUVJqZ z-1{lp4S4Tm|2VYtUZC31hpBCjwPLFhaGL!|P_BcrYi+uo9|b%lxp1R{;Y22J@V889 ziN#!L?ZWAq)8s&NbnZNxjRBl+Y@q(?0l9P!AObQP8br{=(D-haBh&-Zu3qkg-3ggr zDm3fGfPSVWDIrU z*1tz5pMruqscEsz8yNK+%P(jnMS7d92r9mIYvd_+t?dCw zTOWri5Jfp$#l@u6>hSx*pT7ddf$LbZ)@y}uktfhLf-vhpB@?L5R-zM#33n+k|;V(2Q)p^ce_3ujEL81cSiDXknX*B(wKU5D}Qs_clZ31 z`i-uMVpkH!+mt(mtI;@jn&NQYr5RO%2M-C+W^jz>#mYn3iX!H3n8+!z?Wt+JfjuC* zG|V7nN9$nyN%iDfS8V=qbPh#S#0eRfELM~`5fCkV(#AepH2ozb15Z=FGgE5HBE-$)n~ zixm{YeqtCTHFQs!3e|Xvqf|>PtIjCR?Dg6m{rLo!-VYIixCco*ygq+y3(s}X{Q}Xu zi;4F9yVtOE`g8DpkTj#YWervlVRffloVbu?EBIilR@oLi{}Aj zN8?rXvYDEc_wxumO&Ac`8$(*tM`g!S+WB7UYK%AsCw$agal^$z=@Clt&1uI!#OA)o zr(FqS8pX4G&E~z3Ejgc-_*yb%li@FM`0sLkqwQ=5rS$xzaGdH2;_6AVJ5{X?U3AJd zN<^r})QECU#R6PGxpwJaji92)j%RC`=sJ>Fnm#Yfp{|-mj0fNjv_#I}na0{#SE$%K z04A^d+sD=0*JoQb3y5A+Qc0dTluDgCflh;L7^H zEMnR&Gjs6ZYo^yM6x=~Ga2M6!mAb_kCWuQM=_i5?&v<`>j(YG#^UF(G&3fC1pAWkL69c!t z2n_i(2h}-V3NJucpBlGCfl!Rfi^wi3Dnc}MDJgQ6fd$W$ogL=}wu`NP4-B&T|Fz(A zY>6;?DQ}925xh%s9`~tS%`(e9IB127E|CPPQX!+G^}L9b-$yP#kS-JiG3joO832&-z$c^rmXZn_`>^SNBT~v-KQs@N8SrequH`k_S}ir?2~^I$Qv~@*rOi=`!BYY#GsedwZh*K+`NcF6MdO|MkX|%)jxu zr#9m;_yqSKF2ph1c8rA^C`4y7BIb`v4I%3}T93kO)7Iv`Au<{Ua^LJ*-s!MG-LP8y zh7TO|^QKu6N$nm{vLv}CutxetZexOr$=}|ri4mHrBB3Csa|XDe zc9N%E9smLOQ-mDb5VbO)lZ_%b2VLu~mx9DG&E!A^b;8z1w4mQTjJarT>KIinpy8BG z+x@1y9N?S$Do_%4I8Jci|5j;wsE_=;C^ zK7oTrXgfPi{<~td8P1;5I}P6D?;M07@sOOQAOugI<|{i7xsta?VbN7u_mVUR$5(U( z9j-#o3U2hLg6|$^bCebCW|mzyqNXPgSLFM^jt$6i3xDwXqt{U~3Cz=U1lp=r>N6;; zij2IH?_Y-Fm!CHu>m32i)anA|@A=BRN9X1}gaZ;6Ze8g&ekAg(dNRb<*>Q`7IZH>2Vp8<$aol?fbC4E~ zH2Gz|var}O`Q^CZif@BaH4{_BuE4t08m?|mP(DGDJ~Se$8pgYi)_XL>Tp2RiyJ-J5 zm7Zsl;dSnf9W%Mm;*lYup~z-dkt~)FoDw-Y`I^3=U&@q}$O#KCWfloY9H0MSrO7PK z{+s&5`SLJT*Uu4UZfow-w2>gL$omiX&!q;oB~ZBE-KDF3AKvrA{~XK4LoBbi*UxV` z&F33!s~aV&_A|AD7(d?;#RoguT3VkW?qQ}3=9jhJ-dwm(Y|3(6 z3OSY0J(3O}AnNq$NDL)2{qoM{;dICQlNrxH%R7!ERuyPsRMu+#z`r9qYJ{gLF2p=! zmm9=+EJ54JJlfk(6usd6?hfLSMkxxlk;~8adNh!IfFs7;{2V<81|B8--F+UV>K6~Q#HRuN2 zmrAGm%RjaF@>Q6geWzGBJ+is`t>OQv@bKxx75?Ge0cQIArMaqD|BHbW)}M1*cXyIg zQvUAy?(BgF_b>2f^BzKeIW$ur3HZy>+75LefMcNX>aA)=K(R2*Q6z5fs8W zECcKixqBff8yGV3)H+cTdmp@|j#*+WbsOSu-mLt1Ij1ebG}WY0oZrzA$Nlztq(Vji zbr`v9B43C_<}1C4>M+##TK$eipeHw_ao~}EtIggA1JeB%7A8aI6EX=E2GETMiYRKw zD=WVwHlhkmK#AS?Nf^z-#E4-XlogQbW)^$^Fq%9NBj7UvNkWu$fc zPcN>7KeY04D%O;?T;xyc0Rq6Y%gAEqJW?uirr?}2c@om1QEHTB$sO8uMQM9GGI`=P zhm`JB4NIqibi&-1LgaB;hk${D-{-&@J9lDXKpeCx2Gj3>e@O(cVYiQ~3^EO8uMbj#kpcJu3owB6MChmQbqF0i zid_lQG`tf4N6&+ML_Fm;vfJ7CEzHyctK}6#l`)473GsRt(YA1Aya-X5-?_)0RXPt(uzoAo{%enO!px3ysObWdpA5$^waR!XmexH1ld6TgTI4XKF$3 zbUjkL^rv_BhJ2)6jypA|JhxX^$6}E*%=-LY@w!R{^?Ijvj#)_~Y0xccWPlUW7a>AE znEb6EBoEDLQubBz!>{cG<^X1-uVgZP^4!r$W>X{v>xgOPr(a+^9F#oczSDBT7#bQ1 zViAD%S=HWuPvX#A1+#p{ajIW!B8Z&pHtCIpKPk&aP4d9$H~i?S*_Y4unl)497M5YT z@6|O7*5nT5sl!Y*dF}rcNmQA|a)?lVw~TwXC`TPOA^6p>Cb^aOlV_RQy^!A>i3SU5 zbXGrINOxz#xOr{WrIl0XF|4&yn#nXnIM5~wqs)?9EK(TJL<#>h#~@@A1cUxi5R!rK zlip^Pxj3{KOEQ%sCNd}yUM&;;c@(4%I)3R~DNQQ*N+ig|FSn)q-OWOz`e zTxVSKxS71aTks+wk!Z00N>ucx3W(p(WU?pFJKhsIdERj9_%MvInsE1(L{JK~oEjZ@ zkqh&iM<6JTkBY+Mq8WQg9j3au7cmvo+xa@=sxg`QI|m2D$95sP} z_Tnw`57=zb%D)5?hVw!M+n8@mHoM=+e7N|@@0W-+2_8;?8a)CscalGOqSmq>F&@!bcI($wM1~>sngF-Z<{nq=BN{2FK4@Z`u$b!qE;;1pW(3I(ZZlnhF3*> zv6iY0a}|*KoI4h9gT}qJnHtlwmH+g1(lQ(#CQ%hTS(q`U+@psSF>`{NW{i5JzdfkO zgLA;;5CxY*M`DiD5ayvQHbUZQas(U-U@zafRU_~D{OUN{@=dz3k`nmNZ|LjmyU%Kp zhJvZj1wuFWyXjy2^q#X9be;8Hap@NrcLqtK(65(1d!AzYwK=b%!k76WkismNV6<;PMw>O+?*dKHK- z{lA{25X^4!C(gTbSFHKU7R+;_|&L^TDX^-DkPGBA( zF&F;hOvQ&Ls<4R_?{tUazIS@5ID`?jtgds`O6MuFN_OdCrO7xfJ#MsTiebsT#jO}e z%!w-cG``~%*6ns8RJ_?~EaZFee46A!e=+qC!|m1wMI!3!Aew}xor_k~?G`Yw;2f`j z^a{DMGhn+Bbp$ksOaKupCV=1A>g7r+piwXjAq}+Q&s|;XH$F82bLT!#`OE%Mrep@9 z4Npt!Db&b828SE$6I~O1_)rE1BRK!@zbU8941bd412>p2C7$BLm~7Y%Hwo{%e#m;b z76=mm6wo_IfF`ps=486uTpj`UAQi0>ppiNIyVZ=nzu@?vcoNZ&+%ZYnn?z{}REHB;-ksyO zR8F1twv*dmmWiR0vD@o&7G&D)`y49S8U!D~T7}!aP^7_If+7KA9U@qO)R7J_93Wy; z5Hpk*Hy(W+heW{`5S0;!w?EjK4UheO3c^X`>;QfgL4py5E{JL~VSRX3#bq^w0U`H> z9Z(N+tT()$9PI2*LCkAERZ}{eshve|xpQ?H3peC{H~trtIs%%f$D{mG+P(S*_jpA_ zCR27iDqma$z>;%-E^$Qp&QfloYSH5UG&~d&ulZ}Rm%c^|%_=;{sSUb+xFi@bXV~{T zenzCGQjm-hR*JAcL?$o=&|Wk1m+Sn*l6#%Z51t#Xk_le)F5+@fg~@N0Yo%_L|H37A zCZTblI1RX> zf-l4d@@o*gAp-Qib8Ns0g#4=^2u^XVUrM=7avK!^AEEDtOh$c((EU%uJ`KMY0mipo zD9lXDy&5@LSVC{mc?G`Vw;Czdy@LY!->+al?uPVCN-)czkgz^N#@tT3j~h3EzMabd z`2%Jo&kEGiEVn~QN}#f#BTbEx;J0EVCmY$@>wZOlPhns5@BThu?1-=xp<9!#dp$&h zl%Es`ip&7DMZ$!wOLiUt&HZ??G+Zl#Js!I~TvP#N;BSSmY&2n7fho-2ZEU>o>F)jU zI8F%caLFf>rIyY@ap|ApbJZ8dnLlQhe6Tko^6{t%8yWdySgKyC1uBU=;`!Dum)%z06?wix~iQ~{D z!2FKu5rYvC5z`Pj0&y`Jc4f^qqYIUwBRzCFf8A-nuov<26$CugK%aas5%iYMpzRm| z#VojLdi(pIK~ZKu(;%?g^ii{bQSoucmEQv@dA=6*a`i}wpe5=(1_?j2*y;vJ^e73; z`Oge()iV+^U&v^RzJ)%%prP(_v51V8RZ>8COG}3zcKO!HYmpLC-_AYz0~-2>Nc--XeO4ub=V{6z~99+(f{Xh={HTOgM_?JC6p$ zlA12BW66Y%SS~;6|2Eh9;yy?VHJiok5U3p_(5pW`CbGH%M_kGk@JvmS=XMc$(P@!5$AZ@U=E8TlhX33wQn;SQ($>wcTu)GxciaDs4MW#ND zNUvrX!#eZgx>snsx|ynSr>2IfHBQXuenq*e-@6wzp?)-Z+Pdt}f~DEnyzpUl4u4qm z`8Q@;9NGcs_-sHrld;%h4uXvzxiMe(?8Zs-y5Y|$0UR`)aur5x`2P!>n2at{V$UuB{G3TgeB1~Ooi9nXyw1X`d zUbuoWwGnw_wf(5U{)6m7pDE-R+ z_Bskx37hjbSG%c2lAG)8jtOSl~y?oM$FSLVjd{b%i9o<<2vO~TMkB_ti`>(z3=ZO-hq77yxKtEqV#0{sR%*9 zU-FC*vw9aVF_6{>a813U=`|Odh9%lUB{B_u3c=z!D|)eX637t=>8l_2-*bOUN=+UA zmEo03Tz4pbK(IFZH=317I+{gp6|naCp=Xa!MIYf%&Wc7?r)dzg5?B}idicELTKErh z;rYAiXjK}C-69fvH+d*ARfQ?B{h}yAl~<8%hX3J4%UFn$YEi`J6lJAbLPEKRHj0W; z%+2v0(;?_GlCquM-Qyd@MxZf&@gzz#@cIT;Nlx|F8(#D4i`}sVqA97V-B9}}s;IE= z^H=`ba>55W21vSkdI$-|4hc^#@^W*lPR|H0XeA#$2~TCJZzYfrr}sX?=d(0Y>FSAp zoAd@gf!1^SE9ClaG$C5VIn8(<;StG0X>R3UpKRtm*!@ z?HS*O7Zt%k<~SIKbDg%Vu<#b#KwuCY`SGJ3WCUKTAHo%|KNttjM!cFZug&S>OZY`I za$1$!H|8IH9 zTY-m#n32I+{XOEqi1e zR6%XovQZ~+Vd*vdxaUM+4%^DCaC08C>LeyepNcR)dG4;=sc%H{Tq8>W7$u zT$#L^%$=)3N;MuY&WJaa`qLBNMiGC0wZ+ZJIHnH7g0R5tS*j85MuCU6F+#a064pbv zKsCsv1h7oa>BVk4!R2vvA^LAMGN$~nzF~aB z_IXK~crpX6vSq!c>STRu9O=jPyH@fkUoHi|VcX`_mTL1(PF{`5X=q>Bb?QQL57XQTV1|dIzRX09lQIT=|6BrOfVxDKC- z57)XtrA&z@R|IVe69JN?(Vfu~j=gsG%GAstjaL#Rz=)s&u#XQh8HxX6NSpRv$$$X1 z81O9+2Z9c|XmqQrKYMh_+x)^Y?6_y4hX3tVBL}dHiC_qDUVdYh@b+zAu4%?rG6$dwHP5y`drh z>tR)}jjqJu%JIN`ZKkMrXepogi!Do~W_R4_$mC>gH(Noz{&&55m#WgE<9$JI3x7;| zya8-xAeg6i1kKCyjD5_@_^SEZ5r|?YielMnccg4Fr%Lk+%!S@aX}of+cb}23cByN^ zDfwj+!H`*NsFZ8JdLSew7zAA;A50)~Y}^vW#>Pg*{~<&-IEkB17NRcqK?aOOs;i^x zsBL=*^h`4T}fUJ=j9H`)0LGE6_O1XI`DNA+aS8$1VoH^Cyz)Qx-sh zm-~pE3o#-;OEvBS!U+gkGpH+iDyE-mg?|x-K)WR9FEGm#paFWgc7y)$Q73usYf72* zUsghAr%=4^{?t_4>!No0**Izo0|*g|75=0wlOpk(H*cz(=H4d|ZzW*do{1YgSG&pv zK?|9QI7QSi;bBvNW!=CoI3T=;3$7113se&Aw-*+h34W+3nov~>y2bEaeXINa=n=y? zO}WD)NK^5{w>>xholYU{`D7aU5f3X?!@G)Eg-^@=uC~Pbfq$)V6z9j-!E4Lf#(MLq zgp@GfaURmf{=5DdPsr{R{5EcaJ(Eyv%zjWl{3 zY+z7yCW+m#!uEbRScGHJVHFI6=!hO^8Z^*NzqmFKFFwDz*n{p)}+V! z`n_O@9|X0(2?-|j7y zmVu?`5S>(Wr6l~bZ4Pt)W36QMz3*H=r){^8kgr$=?m%=MtuTPz%5Sv>F}=a;^i zvV4fkCDq;8$@}8c4}?o|zeON~>XU1Uaw^XQqTy&n77EP#)T%e=@|t$raF8Dr9>b@& zpw|T_%O5a!WW(5cU!C%!jz z@Co%>HB$iB&Y6B2c|P^0yLD@&K`4Ngi8m~@0FOnCMYXa5P&F5D=7S|0^6N!s{f~`R z#rHZySG2&S=FzBl#PYRxx#etIMq3*ZLpTfQdY;gw^xN1l%&c!0=Xf;B-BQ?8#BFv? zOL=;TJ+yS4*thMrl-a6H-B+`OU3SjSB{Pi@hS$Pu*X~$gqBaPI-2`Obx!kn79hgzI~;$a2HG?N z!yUL>RkgKmTBWPuc~fTXZE~SvJ(${0kRlxLuUKRkEi9Os z_#cjD9kT)Q4eKhroMw)QPtX79AQP#O*@&(+c;D$uY2pK6E7|VUXc~&|Pp* zJokp2RQMu;1QSCTZPJYhbRT(q1>&2tnAV0p8zYfQwXc=&pH!L3Nzt<0uZPMgCMc-Ao{wv{DZ#V`wnAyw>D`33!G9{3{w$Hvto$RWk30}3A4RH|I12gqsob8 z&MYBi(3C8LUrA(uU?54?7U94gbd=vOfRaau%now7T1a_Cm)=R^tR*B1mdC zmrhCHxEcBvvvOy|DhhLCA)%tmGJ+rEoViU+>2PmU6#w2ngneNV)!!bs+n5HVI4_%%hX2!uU^1@j-y%dw3OJ&SiM`M8uS!!<^ui5ZL@^K$ zb^%%jAt+B?jzb3-3!+e6K49i(R1-1NnVddcMjQKxLP7d< z9Uf#XNQ23~_>-E7N~gVG*prXLdk&iT1t@rO%F?SOzHLlWXW8b;oYP@Me8JaNe}<|3 zd^L$yHHw1fR#)3ta6{jjLPl7tkL$~pgWZJ3v)sIS5w{+7G`AD-PQOOJ*X41j@%l#B z)Qx$5_9^9tgna%vMHO=6V4g_rJ=y~j8;A;th%kHC0IsP8@Vi{i9PhsS)#2$V#MS7A zh$ivgXTEvU8-hiWCg?!bP3LQ0Oz*b;_wQ#t?mx@Rf@hQdHA>4bPRX6Fno@;}BhCi4 zzWpH)rCyjfyhZpkE;)I$sC5eF4Y`0>)Ov#Pfq!9<0h3fgmXQ+!pnZ>f~P zXeYzWj(Bn_qjukS0hX=Z=jyN+WCRX)1gw81lBFkoy}W;>!-hFf#c4dm3VnNER86PYK^|W zFN>&clq{CwnU6A^DL&oqTdPj!?(ANj3*g)w4nqY|8efY20}#&8EAR1In%!nir|Z1i zW?Bap>skx^f0)(RZ4MJ)VX5()?Smsd1uD@iBbqS3jAvUy^{AKoi{-iqEVY17n^eiU|`->zSEqkei> zLg=@avJ&f<){e(;5&D#dQL_7bjfGoB!fJn^QS}*tR<*~>Q->eP7mc2O_jPwFZ4See zNXhP<=r52Yu1Ty4pVE6W${()B_L`Z>1aRDMIU?fS^8Jv_m`W#ILtpsAMAL|GS6z!xxOcRZ(% zIdMDjirSl$?b!a!1%$i}5g=g?5S8Il`H2H^aiBS2u#L|Wj3@Jb&1ex~xQRu|rp##+ z*7JhUro0;yWAbT;6L+U`RuyN@3xmOjKB|0Z_ysp&7n-pMx~0Qv->(}}R!D~`v-0X( zjijd~Qgyf1QRTZ>+&j46cMXm9GhE+-j?+D_h?uOcQ10Igl5(nh7CLw-+~eE2`RwpX zxU)}E*BvyerRlW$?7HSB(=#N2i6vsIjyy%K%TDh-><^OAoOQm_TfE_1Pv7sad0fLa zn}M5X{^Bd6x09f@b{@cbfJA2Cz4$ZF{qcVMEh&3@?lHRdS33g1+cOP+2Txj2;|R_p zjQnF{B21SuXEP@PmM>_mO6|tW4ON$-?*C~I$X7xBKN#3+VpR62t^Q;r&65Rp-Swyk zR>$AxOtA?0M8m4xkCiy00GP(8vJA++1*qBmTxp@y zNd2?)?83{QQD=$V80~T8O$M~J|~o}H;J33CPo5Or}Rr8!1)F6EffI^(A9wzTo6%6 z;)crWc)7MazSnQgXs%IW#tL)meX1~y>r<=70Oe2!1wZlEVS|?6r7~y7pO;_Ki_~}$ zI0~;Zqx31CEqZ8QOUET9w(4Yyi(9>EsO;dcVWF16Hwb%KaDe*If@{5hV9VQYZ!O&K zvNwEly1e=8U_2ptcW*()z{i4E)7AT`jE%)lJlMn33J3EQ!>A9H|i0dRA?O6mzMt*!YuyuT@!W!Yh z;gY6u8XEv5Z@)PV)VbZjBPdwYIlhhuG?drwE!6@NS(|m~<@s-}^+rj_$)d!g-P3+umrJXwj!5aaFKU}_k*;3J}@R)$2Im#4Mg*FK0&+_hJH zi&iSBgqv`{H9Z^#5l6Xo!r;$neZvezEKFyoTK85^Z%{_?A`yZl zv4ad^ET4Lx`5ko=prnhQV~DyH#(ZC!2vNww=fG=$C>S1-7Z>ic(2gN=>-9n9$k-K& zsN`~)u)Av)$FH1rgCuj}yY78;6)E-}wuLw_jJ$;J2N?ZL`3QqN$Q~&Tex&5&WFTU` zTe=p@;{6u{Fswzxo0p#;y!mivBX`$p+iO~CeiGG$UP77(nR(3DW!wxk9a1b zZ&#i=zw?HV6gBg6f$wNlWjupH=+c`axROC5);N@emmP&(;)oHn{T_Ku(iag1KQfvw z15!;I{J3T@q0l@2?IBYpeb)b5z`SMT;vDoVkM6!d7ujjC@K!_KrN3~Sx#Qjyn-w7S z%*@PHlc)JmuYf1G0>nQp(7Ih8jEaq2DXAPVJFj0E*{ENM;NZ%cO6b&cCsttvP9x}K zxdvvDsXvDH@0)^20`q&Xg$T@QRYOEY_3Rl7gdKqP$c~YUD>9h-8!W{ZV^&n8^UfTB zl+pMng_NQDj=f7ub(cqz4?3%y%Bah`t(>%tjJ`t_+eL2^`t7?T3+>OoYG}sxqs%rJ zW#3Bm@ze`dzK6ktJv3^j<|oXQ)$C5$)t5JUy&b&Cr1@E)lVeyuZxX2(@4l6|ZG`5Q z4b>c}Z*+zd^h%nKa34#f*~*3nE$6~PX?p6wPvyS->hWW$7%Dpmfnrua5(irv*7dS z3^5vsQka9T+?TX!>RV`{#5z2)4UhMrN&iBcE=y=txF z%v)`BXlp>{-M+YYFQ5WsznIxE5-iD&o*a>jMHSb1S@U?l9}O3_`$peFi+=-n#5|BK z^f!E}s!B?I67WVgiVP-{r1%WN7d+p1a%BdprF5`g2!5OOgJUaDQiLsok z{b`L_R$H5N3XgG-Vh#qH1RsDH zBR|~V(*jFOXhoP%dKUZ!gyn^)Dx zCKax>iB;q3f`i&X_nKAejBzayyKm0(;x;^effaGOyd#OD=GXHy$UF_J17>} zCzOpIN{!3YqWZvz4}su~zpk+`GsEoj?^zfmpoxo1N_q%B3<`)DQcud2y0x32`}_C8 zRX{8dyRCzpT_+@>b_SR87&o-F^_zVJkQ@(WkVEqdc;WbLMwpRy89aOVhO-~o-gh+n z3jK0*WXZ3r$pm>7W|hU4K-3Am3YANRHZ~Mx(~3EixJRtaiCEeiZqL3B6CSOHyD0|$ zynAYKx+(ek))9umQ$0qDuOB!gKanpebIlZ9TDR}YOTF~uUb18jDcw%_O|2{)=tyB0 zVox$cT#_Ur(WOVqg-4i$o`4=k#kAG;Dg>^K!u_bf6Xvll-+8<5HD*CR3LW^IES-|c zFb@V0Ob{87h_$DGt1z54ysvJx^*(6h_B%p?(nn<88Kc4Bxd{C~;!QhjDPzZWYCa@m zOLs;qNPn)>ys(da0H_1nVBCT?Anr(WkIyBkYd@{9_w5eJedk-0sYcnoD{z_ytM9=R zF9&^J0Hg>g2+0z@8bqJ8N21!M1I_@o`nl!74=V_IKsZor$I#l%qsV`_|70I&^jv{~ zkJgUG?U8DmiV>A&f}wYcW)$GsBty+GFiy?R5~eXNu4PGj2$G-Ifr1@oa?Ffb=-eM( z-=Z1Qs}Iq=cL0LXXeMTk)iXSSe67XJzE?@jN%5He29`m$u#uFiCjtLhtTO!tGi0@L zrBI|E*xXEQs(ZiioLtS=4Y%O1FT1c%A+e$s%sOC*1xiS7$VFQlux-z}i=(MuP2R2< z8xX2l51=i=*1fzz;P~ACxxUDmW5!ET5}C6ral)}40Z=SjV*v}q2az?xNb(M(D6qmL zU)R!l5H101G=o{a+w<_yTk74rtF&rB@DPf{V|!W=Y^h*sLm*#xsb~dz5EAUW;paes zvetn(64hHZ%Bd-WbAzCZS%X~q9QWFgR}m6>*GYX}fnZR-8&#Nv2r9>oku$Tf^*t{V z1L9d1X8ls&T-5vea2aV%6ZFGikz|dizoJiCeKl25#M*F8xR2IQMXH_Re({>Dik%f> z)yL*<#xQaffVmLD7CtU^b0`u+isVR>3tA8z;s^TQ?CCnA(^k`)m~9X0f3#PzP*2zu zTYNnUdc8JBS8Le#t^hniW@cuqS{VX{U0S-HXG zwfbP%_h`stR^05}n-E=ihZqs^5;0I!a96@GWSYZ=TvT8AMb@oDuXV!!k2QEcl_W}+ zAclgAE6b!A*YxW=$<|{g=A#L&8TQCXj@QHE{r{(cGi;V5u*R%G9cBUvTzj%GelWSJ zz37wgamAfhw>G1&maFcSp_J~sUMtgn<&t}7ImIpJ^+VNTh>Lmts!p2 zh%x^Ai1j^ADW}P*2!H_-;4FN~$PNn2>^G3P`WOssYnI^dc%Y;8>BXWIB0Bm$EJ5LE z&!+ONtfa&OI(MMONTNF10H;M_BVpj9cZMtc^G3o~5H1)IP@3E>8r)k-ok%IW(U3a%~wCMO&ju^1HedaU1Ng=YS`}Q=$UPE)plw z%a)l7!%>lxC3u#!5N89oHxkeWbs!2ZjWD5N0+JRFx>bAiH;8f-IJesCeZA&E*+b5G-7Kg2Sti|=jBD+yQ#iBbe`YJ?j~6kU zdYKu{0q+UG!S|14Lit4&aA$AwuvhTir)$=8!AZ;yKybW~r-6Ek@Ep*d!5f?ce-~_e zqpw8rGB|fYIo;QudD%<;g3S4nm$rWXC*9lBov1&yb1f1`>Xn-K9tK@3+iE&Y6nBP~ zna^SJUddR>)fWiiK-jIc$C}M!)S{|2Oh`4jIaQ0yLZ{zb*q7M3`nv3&7o9nQQdA_! z^|(nboOD~X(dokJ`a}A0GQ12`He{`l(h{UIun|XfpY(&U^gBey>cKi0H@MA%h{kGb zvRWz{uM60*ku1C}M9BnEcL*dfaW&(qXKs#K2TC#Io&irMqmWQ)!F!)bO*M(++lDnm zA2r!vj?;hd3rMv3VN%P#_2M#QlmSf0L1cAcBF`CaFpMl^d~Bxn^sn$s=<~>Y4_@Dv zo|?IBNy6bm22n|;v4JshkVEP3Loe^B|8@RA(&`~6y!d_5C55Jk9Zo!vFI2H#FgFu4 z$Fe+O_P6!(+NY+706Gpn{UySb!{N_IvxV5_f@1!i)Lw|!THfgXlfRdVa~f{pdkp@@ z6L_V)A*u}W3#Ep9fiRl(m~sGp;LDSf6P?kVBF?j&I+FUrSNO5qUnXlDiu<4;3wC}E zmqcP_CjFY_!}R_$rGN7~3kpK$C;~&5*(oR&pcRG5(~vFEa#gzz(Kor81**U+cRPvX z$x>VBFT+~@0h!-!R5u(F%doJ^@C2R>q>CWz=>H{L1|m!O?M`btvTtB|wCzYr*e(kL zZtVm~TH&YMHd$SR8dpvlb`SrD)a#UU(qW3hf9iJgYn0Sp!zRu->(PGllM~Q&hRx*o zh}`2b-Ab-@lQmW*jqWJ3y!gySaZ!5ZcmpGW|BtY@jtXjv+I|5^K@>y~Dd|QDNePh@ zr8|`trBgz}0O=AWMY_8Y5F`ZY4(aZ0gl}%o``$6W`^UY1IK~-A&r$bYd#yF+e4gJ! z7}PZY#M&DduuqEJy<3nJ7^iXm#MBTkfZ-ZLdE!QM-U7s-&3*HwGz4(eqt;GSZf>)# zc^KOayk?YdV=eeMj*E9B&N55Pl8r%BiX4fqtIRN$eT{jYvIE+EG!yZDy%~@9&ck2z zhhr~e{sy2u{@V_CoMh0ar4tqwUUq?Y1=NhOI6WvIu&yK1C0J{>93Y8r^FlHJsBiXw ztQ~&i2_T!;vJ{a&#o|@{;|eq2<&&q(*!rcWkV=!JqWt}=RZZ=v@rA6|Y^BZFn~Y|i z(GM3lwWWB_s!!kQZOt+Ll6xp#E;daeS52HmYtlfl{U_tHh$yC6jkY%v1&%mpZg3ZJ zR4>zjtoL>>FAjS3d}+UY3Q!l2YW4;}djjRf)?koo<->=fCQKVDIwT&taeaNtn{{Va z$Utje8z&O!o58zVI7%7tX@@5_eSXs9u=%v6W~(n(7x4v8lWZn8&=>VffmHvud746%1;cWS?tto`4xpJ>Ev8e2I8Kies5~v4={v00d0MJ zKRr_4pA~oMX_Cm&d$p!Po0kEzI8Z~x_NGYlx!9k9y!L%=7}_H`ggyZVRx^OYvCG>r z+_;@kGj;avyARi5d~d)DX_J@g`Pte3bfz$hfnSJKJN!RVVL)vst;myCl}6TlaY;d^ZTCs>Cu(~ND5tG<^&ELqrOyGFTU<>uxvH}2=6!K zwQ7OG9yw%S0P<38BIrdy_DYJ4+x?7YENQaU7#T@2*R`h)-lI^+7G*HJiJxo@>pW|G z-9of@HgR{R$Er0vhIHsO?uqOhQXSH9~+C)-yr?T({hkF2{(>n>VI2DzjRyh_# zsDk(Xh1cu$Q4dBP9x`U7q3X}x(W2(P)n?onAMW$IgnIlygi&oAtr;u|AI%-C2SHmb z4YyJt`7-myLBnkIT*Ix#fgfCX|2Hgs%(%N4E+Jem8qs0iB_k_417b!Kz??ia8`4(uV_P)BobA|d5#DVEwwQdN2KD7zNxt;L%XeYpble=D~W;d>*Keco+hh=WJ z7yI>VHvqv&|HG?*KLyd@BBAy6JETcAz1;lj9`fy9I8z^N|F{Q-x3nM`@9XKYR|QDK zFdYK}5K4c;wz*n+eXQ677v{o(AdNLPHnwS_kG6pV7rRt&_gFZI9_J|oLTQKF=g+t0%*V4L9K+T=L|2u5R}R=o<=%)5IXeAX`++dC&C zdTQ%x%%WR&j!A=7O?@ZG3o>lx;G9JET3l(j*pB}{n0kAA`*|1&5~OEQ_OE&bKS>F2 zu(@(H7Li~UIQEs7PGQ0n2&Gf=S5CoDv&1(HGWWAA)oAWBz?AzY;QsUxQ}cTYEu|{k z6$ZF$e0N!~HD;`^Qp$r&07UN##SmdeKk2?cRGFZE*FhL=O@0J<*%{PNM9%_2|NCj9 zKko3d&VX(NL4G}gt|Ne}lTuUB)zsF$Zv~iJ;$Iiuzk!HiKq6@d-!j4g1#vc)%l305 z$E&_z8Qtvhuc4{KqI&4c>@|$oP5C7QFz)Bb_-+vW)gjgzN1TC+n5PCljk zoKu#r`QvbgAbpq5C>=Fxc0Nh7DzzBj6Ggh(;0Rf%SgFfKxC{sAnSI*?Z`%?ON{zu& zg-Eqfkb&~szo)jp*(;D3#|JJOuooZ|75u^@#u@&(KyFYVP=!3)#lHS<8wCZf2`M@i z(|L=~f<ec3T|f0iy^;i~3l7uvPYI+GHI@Ot zk?cW||4#@Am1FPMBKuH==*=3FufAFxk`WYW1+tv8t z%WT=RWG48WW*Fd>kMQrE!v~y0K)xQ}#4_nJ7lhai9--|*Ul7^vkmd+7KSx3S%z*6z zOi=IF?^X~h5brbw8Ia$v$0`_gZ(D^3h> zi!$Cq(v6yx^}MYsdQ;+9Sk6nq3E-2m`TgR5Qjo=cKij~1g!L7a4s|w6vZ>8F%q-3; zu1ai*UgZ)F@_o@9q<-v7F=>8YR(^18#lR)L4^0MS6XrDj6>3wOrSu;@9lts&C?H@C zTz+Pof2sg0G`?l6K2-*S#_tC1mnozvQoZIu>iho^E$>k2RN2u3 zU^xo@u>9bwSpkwOv@?Zf(ajAA8^@`Ive9!cF(MRUf$1EIu zRPF8nLxK>md`h`$O6gl+a;7io?%YbELP$dq5du3+H{s=82jaP7EEUc?*TC}hB zpz1-$z5r792|h#Lg-R5rQmp=spi|)5;NPM$MifD0& z((><3e&IAa?)JR68TqGw!wI%y1%SAwxQuKE;8}pC{R-$Nr#rL$AMoX${{>&B=~XvE+XQEe*GkkhpyFC_#*RJh`|c}( z>TkIT*AtV1N!i797`Hh8&5B#cwag zq$_J`uXrX9(SG<_^VC19SJa>UbM#47 zbk&#HcF}KN$s#_jnnGR=jF2H%X=A62k+FATI1^_RB0$p-Z({NV-`;?L`6`9l#jxy9 zRHv+Tqp(d1+CFsee)a?|DuK0gtMj#P*^x55J2S*49+ON(^WE_`O>v+(q+O)~G*Q@krurtVdOJCl!{=S2`Rd>dbhB{v24udTa zXX)nGU$|b*>Sr$TprlpaZe5R4yts&~WaVh(K8-9KAmeBn!M}&V^!d-Utp(&`e}jst z36?>Oh=(8!b+F7SeEG=CgMX(f`Xa#$1r-HFR^pNB%_<#L(Qj4 zCJtTumYrSyeFziLzmX)VMacF$@m5`9D5%U<7j0U_P*bz5(j~feOG$U}R#SMcvc%hH zqivB9jp|#66b~hGB|Y2bSn$cW>k74*^E1UPrcd^5N=-Q&hP(^x5__4 zjLmVM;U$09+NpRP(#-3M;IBX4 zcih|lb@e*O)W_uP4l2>mL(8?Jo5C>IMjSzaZo=P1yG92_eWU}DlV9x*Y1tIz`jSeJ zb4?2Pv^4Mv zymj9!<~vb~;3;4D~S{n*sPrM?wUt=S^V z<~IvR+Y?jM^v%ti@xJ&o5nq%AodSOyX~x7n8qC+zWMO@9<~F=50$Wr)yW;r%ykCdx zMjx&>&65N>iB)U7kk-eNlJs0RB4S9xc%vNG{kHCjNQV0jz7Q2)M!(x)m~1r8%dR~j zLb&ODz$wHV0d0g5kY5Y7dg!1E@q|8Sfww#|qIvn|SuHPIppfnNpH+!>qMII~m}X~Z zrMiE2burYSTC?X2j^zsK_>aDBF)VtY#*xwsrzUV7AdXyVx`{4p zH@T|Sc`Nea2gly6Ruy}2zbJB8QAUjYm=%}@TT@@bD+=SOREEnu7`>H%R0eQe1FKtE z?tfhm^+j)vT4iidR7CWO^^KT|{qM`oDG0jIyFf;K=i5Kmr|Q2#`!RdcUYk{H{QpvP z_jnX{yQX|QvzYQYnlXou7-9lW>xqwF3zsr!;2i5kvY9>`A60d4yRN1c{NJk$r+ZPP zX#+?`Ah8mhkaaxCui|62&vEFRd6*&+W=AesjeUBO=|IE30^hlfk^vxowMLY4>5HYlp&+u}Dp4 zWr!h6E8`G732u~#)dgYrLB{}b0f4hYQ`%h~>Su6l7((CpAB!S}D@_acfk=Hf^aCm? z|7NqP?9T?w8@z=}oMeWif^^w2KzqO>lD7=fHfOds;A%HgG3}ait@dvuaqoKr{r%W|*6>erC>^L_wyw`k7vzp_nFYl^ zk0#!_WwRgq{(c|CESR#bIFf%>9q2$^A$USg@}*F|z(_W4Xo5yKSnSe>S-FiZNSp!{ zA}w+y(}w}Qd_1>uM5tY4Q0z&deZpQ!v{o(fR2s&0nRZif^f5i72+{`3U zrJ5wLdx;>j7chqltfWS)del4Z5_!Sy^brhRw$P*EJKMwAZwAAK%h^T+EuzXZ{^j4^ z$@;cAkLi!S6yKy)RXiMHU*Vk~7!PQF`~Frt`e+WMNyJf!d?H9I8Low;<~0!MKnX(8 zYn%En@c`0wqH-_g`30Jxpkwl0oOS3!3)O&jP$8nFAfa!_1njehejBu+fKzT?ds&9A z6&1s8OM_hdPj(mLL3nbI=nTm^K;WP%1a8}95wy*GU=i`U7*OP8dMEz&Evnz9ciI}t zi!*h+2elP!=hRvjt7>aIUiu*1Fz`6Ig@7@0bDu`ASJqX>iC4+Bq`0J9JGgr|;=pn! zq3sn#NEn)(GWUL@|9)Nd70IW<-(a*}tv_nI$LKzIvkv|JUE=0D;%b8+FhD(6b(G)z zo0uxOT+zuiQ1cKGG{ZCbv)-s3_dO*Gc@tpdMVy&Rii%<|JVs!h@cYk0qig>wpGi9r zWar)F8SRAX72%!6zgdhyN@2}Bb%Z1U713|#=dTSM032WsZjxMPUGbQr2H=cA9t$)y z896wDp^>r+z#=X%qvcnxvTO*6-B4#?v3S_BfBcjAMro9p`rn2zg(=3GZN`g}W4^ar zU8lg~y^C}TpcM(LIBn1|IyxGVmm;wA`(M{T8BP=?y4>=NWYfbeetHV=GyoC$a%YDe znh!qaNzi7ZW_3GUA4ORBNZt(*3CSKb6)|hK+~og1*{b40BErd-+BQ7fZi{lMw^@8v z4_uOlh{xgD-Vdb>d^6%hAAi2U_2(V%9!asExo-&A!x!>$Kkb)8QJWHf;q-`nlik84 zMOiHUbzFPeQn+gOOJ>?712^5hd95}C+er_|@NL-t_UfYz=H@~~#1D>@ z83)qfVuG+bVKMXrtKiuuvfz8Igl!g1IDEJP0e3W>a;^^0{Mk7Zuplvw>3!tbA0fxBG+hyoeMB{7&Z_R z62h3SsQwq+gOK>Zx|2n+;*t`C$N*zPO2pE5dE|NN3%AuWBWXpV#OgiybX_+~#XUs- z0n0U*?KcFma6)z+l5PeWFuwsz3^>`K`c6=He(}^$2DuTieMu0>k!eXUSaBgm@CSrP zyjukbHQF@v$6xi7-v^}b;JXo(tj9&tYGFBfN*x4GBjGyy^L2lpD)0fiF=+{p-!sJN zlW;wG4Osv?Uubx!u&z!OPXx=Fjmab}e4wOBviy8XCie)Jlpr?Z>CTQ!QBkIRhSk5m zI1dl}uOQu!9QKXz;8(D24!E-cibGB-xVFUI8Gv)J)u* zcjW!HMI|`4z8Ckib5eRn^ff1%)y^& zYu|xbARf5kus}|+qf@v{ID#_iT`wl9_RS9guAAl6FQnURhc z*nD7LgqnQ#@1&?;cCBJYk!(0H9&xf|3I>Ig5!2K?pBi zu1`$If566>NM{0yFIv55A3JE9l3pp>&mRqjG#I~4K2~<8y7ZkU7>O_{CmDQxp zMmOjRNdr(C@iJnuvOshJ-C1{*0N;=?L%Ex?q4!&qgc|gNXB%~Bp@xQj!mCVx$fJMR zwCyK_i36I&RIN&zZ4<_(Cz{)RrJ5IM3Bz!52CA|Bvqz#(sY4zbsA72U1Fi$fj)DH_wM+Rl z_%HY1H$aXZx7Wu*m^s4+06%cC^s?zXxgR}uNiSEq`!V(F5wTWA$_!8@Ib8Y3Yxhj! zxdZKzgIx{s9JIhe1W*s&@}K95ttL+M)vO#ZcAH6h>pd@%WM`}_au z=xEa35E{kE6qWjyq@MRkDH157B>WZUvdtf_g;_}jP3y2;t>rA(L{u%nxx%2xxz{TG zJbALINdko&U%E%YJ8U>R!}if@xNkr;9GaN8DH;_MBMVj$n0z~|4F!Sx7!n#?oYmo1 zKt!W;>G{|QEA72QFAe^OnyUk8SPqJ%FI=^o#vo{U&AO)MpSWJeUwGw_6Nl9*j=(3T zx2)0FC!%g^@_Y3|DK^VD-ESIw7ow$k6hHX%`{2_wouQ@VFM6T`JxSfR#={NbAC>;| zwSXin9S{OdgCuP%{4};y{68+L?3pjEDAEU1$Nsp`u8`<0hbUXI0FxGSfFRpvVwD%V zj<8q!f(&59i1p#aH3304s*iKQA~-rVbDF$IH1exT!HZgVts<4WC(y=jJHVgfujt=O&JEoTcKp-qq|`d5s$%A3XunI z392hAK@&y@_hWG1(7*)?xt_q$bm{nS@NI#c-y&jgx1skVL8*L??Joac2{AWGIk+EQ z1}y{q;<01l3yGU=(^{}xj?^n(W^j+Y=}CxX3=QN-&nRARW2-&T$twMQYCsgQteG7X zu`eYnIlqzi-|}#bJ2(NC&Uy43f$K!#W81xTe^aD8h+h~_ToOz z!PpBP0!gJ%69~2Y!$w`E?@rJYwJ5BwXV*rLxE7I1D+ne3K@_1@GsWqF2bWS<7@C|+ zL&$EbTG2;`8w?kG1lfdmuaPD*SpH&P<-dSYJUNW^0X72CuQM}wgOE|1<}7e z@Lu1Btp2FxCL8^`0KjMevgZ$^8OCl{prd)5=8c^f9Ne(T91w2N?R}hu`q2qfL@?cz(}>i00_`y&JcoWI%|6 zVAy*O^1v{w;Jf0P*-vIlx{H=B^C7rWklo!M|MJ2)pvi1o>N@IMh33WS9hZNn61OQ{ zf}rCpuqv_uJ_~OG$s0t5YRL2gY&#C?y&+g4rxAx)wtslj7nF>Uhlzdf3MSq(w3sap z9i3_t;7SQF!P6V1Po{q|9uEb3Sp@>x>OPqGkt`($qtpaDB^+bwB~FtGcJ0iFJQ|5x zbpnrb-*wU4i1&pe9j4rfhEP5u0t(uGXeR+S2j+;0qTd#PA9M^Lhc*yL{p#v6g;Y6& ztR5d9FX)(&mbL?paXw9hAZzRSX3Cz7sq9U3^L@tSWyy6}1-wA7p=g^-maBFznQx2& zIO)@)?8Ca@aeklJBBGT{wJH9gg=Eb#tWsuXoVGhv^?daxmKAFp($V_F&4FSma{JFl z?Nqe2!O!h)8bpY_-vm?3MobD9+^aOct2;Je87*Yjky`JSjwo4vnG4yS*-tCRpMk_N zWir$op{78o>rixW5en2cd+F>gI5r z{=tN3t4B6EkBkmn9wPq?gUb&|YG|Sd*ggMT&)RT7@S%i>k|Bgz!9Bd&^EIqM2(xUu z+yc$v5Hn*T9ta8{dK}f};xFHmH*abQdf*1n2S1#9il9!0!B_8etA9uca`!_bm?n>| ztXbjdm>f7A9n0YM3=)%nwwf4~L&xbMaIeEBPTBy`S<{$uT=?B0#$9!e={>&TknPmS zC~?O`FB!%S-i)-_tjwOYuaye?PlLAO=WqV7{OqbrwD;CJec;!s$Ay4w6gcc+Ej>t| zpw-sU*8a$LhLMr)b*e7d<5uauZUc*qireFKAG7%e^0jhhxBi@rvu@ehVWh(+lLEhT zk1^RSl2i`|<4~$^B0?qrJx&QgD*kL%^D#iWF_2S{J2+@_rNtdnA6ZVGzPxAf*U@he z%h_19VCYO9(VyS_jwC-l5;sv%B$CuX)8R?ZwsAf*UbyaftkbL5K542Jsac0FhacCzmVVl=Vkfmd|h^;{s;k~f^ zx>Mn^!!;{%@3+7#cV9Vp5S-NFL)+6WL^zNb;Xp5jwaDkUZTNBW<-Ud`jK4v8849tsZeu>7OS z?JDZfmsj=hU2k0TSboM|ilJpAA4*%F`|$Lk=BOhTbr)Mcx2{frQxug6l;d z2vR`iSI%BV0AVnx?v59*Z|qe^_9L?F30u}31sn_4i< z*p!GqAaSDz0R9}(zz_5<3q1b@yvnco$Z6DquJsyRlBx*x6I6VQqiyd#@O_3V^mZt6 zS?_yxgGVfMLJ~Jw9XPx8ontgw*|bR}TF!@rKkwP>YXG#Xdxx#ixshG)65tyqE^qk5+~O7e8{7YS-!=5*$6Xf`bw zjEj68UsvZ4+2@TqZ)0obc1q!K^^HtS>>{)N4(%V^ABZ*@67DmzvuD2;1<$|}$bd%u zjCqFG4O=K%SN%TtEHCuTDLnUFq6n84iIEQx$$mM%2NF~9moJ&Yxc~9;OLjIA=sq{^8*!-4Vs(N8g9xAc^IPSLb$)Hw%&D?Dg@1K+Mkw}pCEs$8xnXE+TSon^q~KRzn`RT zB9JW7c;S@EVx_fnDYOfgUt)5>Tllo>hd{PZPo;y*9qL1JOoJvP*)$OnuDiQNZTG~Y zqgP1-YX~kd>bOTa(8!6jibT7Vlwsi zw|C7}!&uf$fdJwceaDbT>mh_9P2orcYR-k^01iaa?bx&gn|vZPp^3OeFkBG@#1H6LoXJ^y==GwgTS77#t_M@Ly z@V1k4kM(>u*e?dXM@ycYed-+mgoKQxQOHTNtZ_S(y z;kElm`K_wLMH*C8)&d*B30yH zKx=TuJO{*}z)A$J2F$BseSCZX$#_Ft*X^+QsAcpEA>pq#n_6r!F|or4w*wl%@Fgrk z;k^jD8a!7~A)z>MSolCu0-tuEI=lPBqXYc6?G>CGRC=UC!Z~IC_{83K4Vzk8;I9f% zou@ay>BPKOROxs?&~5P^!(2@b*rQ@mCb|~S>Lmp29w?#=M~tLYPe7od*nEommGK*2 zdgQMU9Op^tukVyKusdf+pEdaD+|f$e^FtbC$E6CU8SF92p^@* zutAkwe9cn6`16^QiNsr_r&~NP6^1|n{5zETu}|=|+xNX-9&Zn`l%45!*Aea2hg5GU zOd*2v9Bw&4RgHo+SZG25#R>zCxYw*h@zLp;(z=tN@EKl_G)0;Rg?}TcRyyD?`2kb+ zcaw7R@{53TG6BO=tY+X2*|lK4ETVw z1rfdW1c$d6ACkk4KubYBe7A|ba!Vwe*P7u~3tqT%@WA(TMAr;?gb2@S@QpQGx{z5G zm@%LEh`D`;du4D*8X-IT2{(kgEBkr@;?Vi;Ye2;IP_oTB2r3b!?F8I`iJ&eA3A7jU z2!j&djN5Ka1`!$2zu8|_ugH0jS^V_Gc@4#HZ6e?chaOEIFn}Qyn;HZbpnfl&$*8?J z+TVs^^v1iC_M1c)q%Ys2Oz^n(ww&IUp5)DBfn-Qm9F<+kJ8Lh=A9+3S!{6R`dtWw` zoJ6bOJn}!Je6==F$%?hS-O|#Uh5Xe&e{ZaOz@zP1Hqd@NYkwEAy8{`52(b74Hp|~! zS$=zNs*Kysu(d?UXkDS;JzeE-I;=%QMkgB$FkEJ3qaXIYXjUSmgDi@reU! z@Z;f%nkMrVKwgq4&KF$4SXx9t4;H)XFsnA0i87J`_K{( zE*MUA@&U-DcW1`8Fg4?q@+Pbt#5s%f2%$y%Xe+^ew+*R{kw7?2wlNVe%Mq=PD^!-> zig!Lmny`G74>apvS zcuA5YMpqKI{rgcq?;j`7{k^JtU#1^+_Iw>wZBb(K^hl!nZ%i0H0J&lu6-FhUv?s|K zffIo#;IAFPfw64&*X4iy@Xu1=$W2Yf7J6qs+XKw1gH|eI`LFVgJFOMf-{Av7NPIm2 zR)PDs$43-m34WMY}KmffkZL$6U^O^`F%#2pbrFCb5q^Y2*3rclrMj7XE3 zq4NvhE+p2UzdJuam*HFU`*}|+LrO#(4~?qtfEtqgco&E$Bt#zvjTjB5j6Ck75vS`- zL7`weT&A{raQ|ve=@Tj^Y5(6Jv}^a2ug}SF#oUK|ciEWyQD?GdYDDtsc2L?6rc0T* zxQNt9B0VM*#eMz$PmNi<= zVg)7}e&I?Jj2wYoTdj+&--Zp5UvBr7dx_-T+%KQd3n3=A`vjK_K6_>DJH?&pao}Eq zRp*!L8@r>coPYhrEguYXVMRBkj_}X7s#;W4XdWiTefXE$fWWsAjSw>W{P@dB=%0O& zN}=`SI&IngogQc(xj>Y%3rvjH=x+1Gbs{8l1kDJGbr*Kv2$=p#Nqr=yY6!{A{8PAZ zb}^!X_}utwzdE(WAjiyea!d$b2aO2gloSAU!h>$kyO~sJ*;fZw(D&&Nz1~VZ&iq6u z7u4T6XO`sh5<=G)1bBV&b_mVml0wXX@?5^E4WxHVt7}S( ztuY#qmt&Hoeqz>j zwMo`R()S=?&LxYaA%h`YWVamW#-HFHnPW1^71-tgs>&<3VH$)&lX8jfG9S+aWD->- zEED>_Blnwrh0p>GyIqfqBZP2f$Ym#pUL{)|2~>pVa$W}r)wLHhid+B1h``CWw^1=w z4El1AtJ6-9KVr4b)erhR;Vete89xB7fnCR9wms-9>_LA$Ld4^Ad%dK4)(%6o5JX2G z5dkFRae!E}J64baAL4cqt`5%`IZ8<0SUcI;9_b`)09+!Qj0r_u;nmutZo}}98_fTL zt)OxRu}_ZdGN<@EL|!vzaV6@uFJL zP&wX2&1+~9U`B(9bVjtE$+#B+ z1_dDo$ucHLII&WpBv8hTPhBJ|kR9D;Vhp11|9I@NgzQ>(d%{7r1;*;(`8^XY1R_F| z@OtnXiEA(~y5f4+k?u7jH|4qg7jZY1&n)`AF?{*P3K?*|>EF*tdxK$45lD-@uQS;F zx8RXLl{FQMC|hii%}V6Y5EeAHv|}lg?QngTShp9zgG;W^R!bH z4y1+p1UL7kAgQ#gG?g<7`*WCD1Log{%&O8GVqOYeb_{`6`x8(V&G3c?rX67y3cT~+ zYRyC!XlzbA4>yG#X0v!qA3M9cD%=%YRzoFV4yb1GXZ6qb^E&X&WUU@qV_D74d8PIG zRYTc36ZYqN8b$At*(6av)6_Chd-O4=Y;A=w$54NWWIn>*T6*J7Zn{}(47^5?~{NrZxWP1ybEY9Y)C3@yeg-W3zS( z3SB36s>MVR^mlChOz&@qV*n4W>R?ER6kaT4h%dxL-3N9i5-bg!PR=_z7u8B2J%KEa zU-s~Syz&FG5OmO3^Tq#VLpfFj>wV_RX^3+<8RF#OWj|);WI$e7n7nUUS=e;fc^qG?cYp$ zV*w2f49nE7#>$Sqi0c699LNf+(769WIOnRC{5M|oLS|#OIg-K_ooaKeIT>1Ua9y>q zb%T~W#B_9mJfIy`-TQ`~9UV!aQh~OF+;KpOBKz`teS6E}uU3nqYq+w{?(cT*N!bkf zZBkf;+z`hmumaD_^I!GdKy1atC;x@8+7bTi*7rT_KhDslN4Vmhb*5$7K&`VrEVBU- z^uWMC64R3$@7D)_c5CzCWf-;75@;(68P@+^9kPFY-k$opnJ!AhaBYuxfhkP=SB zNJtVsVv)yu=MAH7tVB5(nd#!+97c#KXQCz=`ar}-6hLSBCC&o%#Ug-m7h%-)l2`L} z32?9-=;(Or313PFGndXmk7*u$1Uhm{V6Bgoeu~boic$y&I6kQ8+`e$@v@+w0`NH#( zklVClx`Vr$PWUM@D^cHq1+B!f zsU==nRnA}{Bm|`4u70HlB=|2tLq^>V9j73;$V0hLxl@C!$3Y^P5DcmglH#0F052&Q7SLj$U!-ZOP|bxq=dO{SdAPxnScHwD5(MN8F=bQue=P!i}rtgnx7 z&}ivs_>#%|`mOSeN+6-@$FaPfo6MF(O(SPHS)}-nnlEYEr`X>Y#GY8JkK8iq7*|`c zQ(1D0@5_s`=o$}>C)f3=%jZDe(9rFsjj-s^mBaUI-zSJ?{GCfR)} zr%*BP2B63>yvQ%0?1Oh2>}YS;KC@d`P@KpTce5hHR=SW?lr9?I**Zm;r!Vp>L-aX2p%P8L!hF!&AdKZMoQszEj!nU-iDPVKT0 z9~&!QkyBzcZcre^J(?UV;?sgsTGnvu%xQ1{o$?Mhmzw<~ujv~`mp|&SI>uGz%mXdw z)9JGp;l&rN3O&OFx|#nkP)+Q9zpQF(Y&1Q}$;|v=RY7Gp1wCCc(5ADCnh)MB&^K}V?Y;eOA^nShwZrvv^3UDB5=oaGRx{;>h_%Vb zwdp-5^!lcH?fD1lg~(y!Ra94(va&i?NWvsOZx6H_nrpzTLn*Cc;jI|Oe5K3Jao7;> z{Gz#apb-0;&W8XkG;fUhA2rJwY*%_3a9d62bIckFrX|<24If4kRghI!D|du`gEu9@ zNs!PC;D8*%9sbKk+ZdKR($jfz*H<;)_jtAG~2dJ}KWkgT}b;Yh>03vA3=Foi}x%7?p(abWO-;yi2Ah zNjEPjc(h_FqSP%d^U^KO9Mqq^3;2)|@aAYq`L+CYSq9-0T;-MM2pLx&f{2;vn{Vgp z*PQa3wTiy(6J#Xw#hUX^qlE}LyrPKzI(Y3JJx=pR`ZtM}hvphf2jXV;XKhCcHC(QY zl?|$$e|N=L(_lASih9;*dh5#af$Qlt(w1LF3dZ}XFH=)ZrVHUbFIb!&qWT)&-nKG% z?{~S~O-kv<5pxNjqdH5Cx0+o%KH<|d<&1lNmn*3@ETxNAzCAQ_Rx^ zy6~^2QU(2}iH}W#vx1^p%729}ka+MGa}<1~uZE%(DyxN{0J6R&;3Q@k`IlV^P5 zAzVE_|D`OgIDhAe4t2S_cfKMMluPSQZV^jlckZASRFqQneDgpB5d_>>A0SoA|5jHS zAvilqmmG;03LJEdjL0`*=KfcQ+4HXrHXOaGTb*!S^YA@(<*kO%4S)jxX2UoX#nVP1!71MNM8UI5to zPUz25F)2ZB=<5zJh7gl&mtamB^T=@$e}ap9+RcF@`U#;rwJ^(NTyj%}>@{51j|IZ` z6~3;%amI4%U0WQU3?ABds^!_dD6FVamv4upUHy#d=JRf*2%kLrK?J1_T8{FPIC9ZFTw{Ru{~%vbFr_=+s#oT&y9W<=^~KKjpS1Jn7b5 zle8sCq9-9Gg$Z+7Oau2F3kpg^u9<(vNa98#?ZF@X^ZDD5E&6nJcH=;;8!Yb18phuD-zbX0f5tFe>7kVh zoQ|`u`&L~oXiVJ*5KT;d>tOu?vys9VplnWWd&R1ep9*XRUtKmEFp!{l$2!gMrz=)O zJrQc6jr-ZDc6srsfG_%fu^DE!E`Rv>{t)+Z++4wn$pV9V{0dN~zkM;&_yJMVf`LD& zc>@WoKo}vQbX|!}=%a=wzb z_jxB=gm0W#j^6sH%+0Ooe%EY%E}ANTa342Xh!F+<{xBimvSTWJCTn0=qIu(DsS4>D zA$bF7Pd1s{Y9%Xes+DzddzWk-n>OIBhc;;*P&;kdtDV52)4_CbHP<`Uw20SL8!UwmrWv~vXW{V7}L`|Z)d5S)NK@?b=zKCpK@e6 z#zXMNfl@{_XF1=#dBgtj8Ga`&(3gW-qSy3YcZyUHq61n7O+6Gh-k|&shM*iJ-b*c@ z*F@M4s%Zj?MREKC6dBZGjkk!_IkO+w4=yySXpodRjp$jeQcOM&8#tu@JB_!jscebq zplIsZ@lN=WqnnYyx4!u4jzP_SA`g}%pS>IPH?;4G81ccWy5Hf z%UTY`D^nRBctI8f_$iQ(*n^Em1?o%C!GWxn8n}wHP#0Z&%?C^s=)Mw%Hwc<_kt4B; zh@A!ow}EZO$gC;_i2H~c#Crpg*CEcNu7`V10d6J>AT>}jfIfA723(UrU-MR+eG!j? z=oARogyN3J%6iz+rn+kWvs&(?S{r@q!D4|KdG8Fa5TuGFGr?lY===+P9F9a_HtM^s zq!E+6S?^830^E`@QXbwJC_OB;{E69a;7;(H;PJ1z^*;PEJ#1KS$$6!rkPPZ{M_Jjh zFL?T{{}@-&yLXsH`Bm9N81?(6+)yJ&o}PZea?3`G*t8d{(&8Al!iS#+3fky@XJQzCsf?fk9Qx2*P1d{B40c{%^ z1i`wd2+4FsKr#8c+j|Ckc zB@M5E7eqY(7adavoI}vV2Kx8DQc0RT+-VWIC-flUU3kt7T!jw z=1*Wk7T9}NbLx>Rbx+GFh2dfOhdvLf&hLgVb4b%H|CEGUb(9~?8l_#$fNIZm_LldD zxdG5d6*>5*vD5QEW!P^k!qG3MyI($G^{}k$wxMb|&#@KO0|ozPJFdCuZeEQJ?LJi* z9Q;?WyJauh@E*wfXMHxk(gVp-h@b^x;L0jHp}7&~Q3rY7&_c-^ocoH%4dI8Sm4Xgb zhOeM8h$Pq`e70HGyP@D{T(!O&{jo%LeY-WK+IDHa3*+m%veNC)WG008@;}jp<)fCt z{Il4I)d&Dq$)SQwjM9{;9i-#V zWQyjXRiYs6p^UrMY=t$z7yN_7i-pbqP5BG8TGzF_(3uT@diILzqHek z>OkuWs!w72{8?U6PV4RitRzQM?$deWhmMY%!i>!RgvS(FmgHG6@OKxN_zjs!FjBc^ zI1%XQ@@kNhIDe$GvTn2^PK4a!wz{>)<=-P9LYT=UIBX}TibCWDg42RF(3G)gO7AbE zVWFm>*9_SGara*1vjvUzMbQA0X|dvh@5%ieB*-f@$Gwa;fNQBpkNUo##1^mk+P)`e>h3%;0+rTjxmdbX)7 zLGio*EphG$!#c;BxsJp@FH;g!Vp#J8;5y{B- zw*i%aTMoqH6!QEt?|HGl3iFbE@I1j?K5#f|{khz^TJ49dSc&tik&(oBN8kB_0bu;|JruoYypuJL#@y`#G8C%NG%AttIH5oQ9yBuO%BbI#E}^ zhK>T8^7S7OA<_Y`x?ixX4;ll4tj2CGh|dn#K&4q7Qua?MJ@DW}ou6=zh@AZQXI0H| zoZ(kg!E~^xRpTxjs)jqIKujCMp~k4;opoCb6S6yl{88rtt<5&%gJXL96O&snR^xwR zUn5U%%?w)2r%xnsA^%;Zs+2LQ%)#(3Q8gg!PL13H(jE&+xuRS-&uz0fb21z;T-)2# zdp`|E4NT@5vVPtkqIIu0h2{pZ4W$7Ph6sd_3eT!S$T$veT1YE|qiP-$XoQ02`eiT~ zU05rK;9tF6%u$tE^6bAQ6y5UrXW*Ce6am2q9FHeg6UVgGj6c!GUDFJ7wJ;hAUp#rI zSd=@FbBMPrR+v$Jr+GtaxG{M;jEFo?#RStq*+$qFt5!BxR%jprH39w(x}u_E*{Ztj z7^6{2qW08kp@r4NY;;+oL&jT0oC)ow}JZ;5@4l&J8f^l$r6h&+0HYl|*k zbc2MB+gM3|8z{?6{T@V3D=HOAo1kfp`uo&W-!a1Tfstd0YIH|ZA5KxbuG&M&KkC*} znRi;lsF#B^9-!lMM+`>GUZ32lt!Pa5m*w(EriyANVnC*UcL zL3&BsZ=jMP4!KE->fnQe9wRlz0Ep`3v!dh{zMS1@ok|nW45f!x5@x@q33DUrj04Z{ z7B+f@E+DuYsVu8?U8`MR&lTRo8SR)S=C2b_V*D|8F1Olby-qIGNW-0NRv6c{J2d3E z)1$}%s@3PzXC>_X!^7|bIz{s{NhT`JPI(kY)=I!$)_JqJnl&9=yj224^`j{FuaDGi z-QA!bVJ2Zuc$-6ay}a(6Z5WqXGl3^ylSoP>L=D;o7~Hi(M9^gV^CQHt6e%m%4tula zeNB|8{JIxgqgs)i&a7*SnZ8>U$SdfEc}B%_MNST?ZUn&uu}KG^qTUDtI|g1hc|wK2 zbqx2D2(}4^F!R{Y0X^w;bJ=7j$JzDh;&QmFRbwv=-$&cDf0QK0`xzEKk1L#W7fm%6 zyY-Y-Whe|aw?%>3s*gwVs-uB^mX*TG+=D_3&l@IOfRKU(f+(aSamF)^bgva4_t93g zLJ#SDeq<*EPTUUh8IujD6nHVWqaGZ0a6>&t(AU7r4Q?3+imTEEKeCf5{uVRA;Hdqs z&Tv^o5@9MX;ygsaV-?nV-{S}FjkwSryVrz&*JpE-p&D3Q$knP(LL3i3J(B|moD)Ql z*}{`uXua?pOy`6nD20nJaXL^j}!xy;|1 zFAQvJU##6m*(liWC~D{kJKv)BrAhqUW??V*#Jx2zN}K3j`{YvWewI!ox5G_3KgNRo#032 ze-D8CHp7-axr>R3oI0~z%j&yB+bV$}_6n54nN$k(YBs~T952;h>p6dM*6AAO@sHAJ zKkF(y%wQTI41P5^{Uo^!Nlf-_nz46Llw1zh!eXtIk7WS~KdMB))}m7u?@9 z=zfAsxCbt~PP)XKSdR?94OpXxC28y5If`X6Cbd!aebK7Wh1O@kofvS>Nc=zz`aStBPeb*|rbtT&VGpF2zA>bk8YFsU)`zbow z2GC%@7O{k$<~E@lCs+;v;c7Nk-~|HHDu=BXCMG5o9_ZOKe?0@PVoUHZlT^*vXAZd& zBPVOu+i&RCek@D5SUoRPwRFVCqx!*X`#RU)rk#s`ZwdR|BO2}@3b1MH#>{=R#wcm@ z6udIq-qe`9wDT=T)cRR{vUg`%R;itc)pPSl9hDWiBMo{SEQV{htJ`t3u<&ll27C*L zul=K*W&TX(@-H&xJF|XumGtH_`C!@s(R4V7ZV=F-+?LuBrEumT!gDMGaR(5^VOfcL zcz6iO);H<-2VwRGtY2i6R+`!hdkAxegt0 zO=2G-MUP+;OM_eHLXVz!p>5ep&(XOxM3t5mWq^>Z9{cq$Uz?QF$MthALt&$5hwhWq zBP;3IDGJOc7Z%ATPjrViteiiQNC&*WN@aR4UT`_toI#NG-y_2P9;6+LYHFP@TLDZu zIdG;5Ee7sgTtrH9Pha;CSdch4Yr!9#m{28VQvD~4LC`1DTpS!x2>0}GSeeagu7R^w ztHQgt%zk6c_Gc6wJUve9k_xi?Pad`2LY@~)T8}xoZc^PdP!N(eE(LVQ*lCSq|IDK8 zH)~jXWrQ-}+UcsHE&t8gZGoZ;Nf9Kk{(}C)tb>7OV z35(W_7t{Qrx0Ptx`r0adx}!G=AwqeItvCO@4Tv0J9gcA;U!EW<@{J}{B+J@T9m^UI zeRNj6qw7g0?~l}(>`tx_lpu!7ka_$NMox07YRXy~m-@kgiI!QZvVdaTi@+Nq2dxpm z_Rz2RwLCgZiqxg@JW7l#QXJw7X;2&a@_mFLUh>H-k7UfJ_pk{lXX6Ysp?U!;gQcga z#-$#0--3B##BYg+fu2|3@?yuMwTjd{mF_yedlhL~PqVfE2gJq*6}gPS;VWPY^gTE+ zzOB?`P3RL<|4)GQjq!k^}kYQ@YI!!b{w zS6W(%oU||Dy#s8EPT?s?r^gkemf>M zz&wkv2!6LUW1}@Ew^dGeZtA3J{si;r6BkBGX=EkYbm1!{BYb`(+quWwXjKdy88@M- zoU=jImfPczOin<5xbt4ttVeRDF#X6j!b9PE2XVFJlTv2k)!UQ3Wi7T56Ri;w5*Q^n ztedtReupa0EX2oXwiN5p+*VR9tJp%u(V%^80>6x~!!fHG0C zw%q1ULM2)E#$g|h@u4BPFGfllU$pwI;MwyalX^yKYD&kqaZ^$K*Z-!gl+dyYlRF*@ zFYrjdHEtN}^Ax=2LSy+S*_=1AsNK z@WBVxQ@zbg|Jz^AZo4O5dqHQ8{_Slovue#9`uw)c|W;?xjzP$omeofFXv(X-%(5UzIEW9QVDkF!_li!h99~ zh@D%2oqPI0Y*TTWq*{d=X&=U|m|4%UXc;>E4$mHiJ$(p!U#Rdu`enTxu-~93`n9Sx z;t4hNLsaAHPHAZ$E-Ce`l?NQ3t`5AwV0Y@3Jj>HaJDM@D4=(+I>tfGr_)Uq>lla#S zA~Apan<4-{L&&a3PmgdCz%(2<=>S9`uX9SKMOoh^jiV z>K0?x<>Jk3A&o0CwEooM0|wjbgnm?{We!;ILQehth!|UAa`(eF7~YnG*m^M0lTjba zB(y<8g5-SsVnx})yDpqlIm5s{vTgrbsiaOPmOdag&a8P(`;~K|&o-&Qyz5SjnW+6W zT->LPbdH}|BRnYLa9tFb)^W8SkQ_pjyUC1y>*>?q#+ct|@{S5W%~MJ>zM1U5p)UL8 z(9z(eNAi|E9+BA7%s|aBMK;pDr-5Zvj0}&(o}g^iGm3U|tPQ`YHL|*w`pml8dUHy$ zd3n}qbLK;J(e=<=!Ce=hMidtT(g5$RW_Fu? ztYs-!3)$ypz|Nk`Icm@e$sYi{W4wO;JV?89?y2mgLn|w}oHpv5G(UM$H@?pT7(#34#g8weN@A)epqCYGGQU zVfb97Rled}RZ?2#|lY_B)8!Tw) zqZ?1if>Q?jN7oLb_xskYs?=dt9|1n?pSoC|epbvZiO3VZy98Nq?J-s0ta}Vlhe}V1 zk))`l@5QlyT8k@p#OYqV&_BXmctY+iQvVcHr}^R-?XiS>>`+rK|7FABoKi=~$uarB zFQk=v&rLDr<|xB)TU%zA2~)p#Top&SH(4kdG-2iAqu-Snmk4^oAgl17A}rweb8ax% z(KarB8AD2_%|k6$Ztrau#@RwN(R@vL=h;NDV8@L(eus|EPQIzxa6>Cj!{^(~c`5vS zyl;L5bOyJ^w9QLMU~V{tKGWJ57?As}w0=GCZh&6hk98O%ezqeJqP6=}U$5xypA8pT z-rN)zSh>tRaRKY=S+$npV>fG)ag#oibp!-X)?&zsw;pZ(YI*dgHZ5u2Mz(r}>+{Q` z7TbZIRE2>x166#vO2x;n6dC5E)TOQ z`DTP(8aZvT0=`|w{MsWj=&8X)p8#u+WME(~g`IB$gAG}Xz)Y0_79${vbposF8ch4N z23ENhixEn`W_XlFx%H6#g}s0V&341Wj7D*L zkIashhq$W93%#;8Xv-f%`Gl0;@Vu@|5B;<)(1y8i{ya5*1Gtkaxv}f(sUzYjO4VF) zV;pQe**lX|mzUe0-}+2*sF3r+6FX?wj7MT?^J-H3-N?4UuAd?sCR0WqF(u`sjQc{m zN?#qzet45RQDqZXeuFx5 zQnYmX{B%TskLmjkd3T)*e#hO=hlfEa4#4~(1`ok97$OnqI}$=PC@YkR5H(>BR`>G) z_VjU&b*_N;2pTh8);Q!Y-~s59O1XswD4xNAgct!4?s_0g9SQ96bMosT^*Esk3;|PS z4W~#E=#Iz06h@FSIrXfQ?A>{AYNcaefexB4j67y8lv#Utu4%B@hs7(xZ2)tTH$Y8o zZ*LzOc4k>PKi--B=I~ls8HuJrymsK~ttKv)l9q<&zULQ!7?+SHaXG~y<`viO{=NoI zo7DVfni_ukzmE5PWc`6}J&|}pYHDi3v?dco4N$YN=D`ngL4OR2DW0k z=G&iJUcQ#HkQtlhnsPVqYHuub=S%A7$hy2_a0_f*N`IJTGDXp){WvUsy?dJ}-xj25 zY0*OqdrUzlT^$PccTiQT&=Y^>|ftdxnSn_|EJTv1%5b;Imj^3 zgNz3Lv(+?&=#PT%<)B~>i;TQEcHF_3uMZIde*o}q&WYj>Jfs1)?u@sv8;u@l;Ywe^e?CHG z^&u{24E*ICotHA}Cy%Dr9vk%`CyOq9I-K|qX?SAlHkL7m!p?Ta#RJ)s;+eQppM&5{Xk-qkEh* z-*JKtCLTRG_&GHKIYVesPyyC}XdNLeGGQbiD-Hc#&K3v)iRCBiD)#E&e*@?TiMBmF z2*%*v=q$pvR4X%ktDs4(iS8{xhAB+W%6i4ryMi(GHuv{kcS%`xQVG@^p%wbp+uVGm(GrL1A zg|^V9-xoO_UqcuAi~G6&?Kh7{q`HskiQQxdnp70X54CXF5$phh@hB-N30r9mB1J=V z|8&O7N_cP*Axl0GKTIr^LX)x!Vky_#_!83Qd$oaqfe^uV|682!GMrXfxw()kZ@d02 zb00R-5XR|wbm^2DX|^#^xbxZ6Ix0HC`qJ61a*Ojor%Ip*!ffEM=Zu;Cm$9`RXXLb1 zI(j98504WFj?8r@x(Ip)a88p`(X@3_0i9`98{d3h2?a7jv5mR)UqM%DUwOl@$K~%k zN2kQ}RY)wUtSsVzLhJ`vHy>I}+mFh_)TqY!Hwh@80!`D66FYCW4U723_89*Ni~3Mq zLt(V%1&4kmOIXf_@2@?lUhO!3&^5IqKKJemIyf3>48EGi!qcg!=u=&WC;DRl-qC7J zm|3&_p%M8qM)0w&DcXgN;{D5{fL5PLWp7bz(!Q3I2SUp1y^?i>pVGp=4^I(czdr0) zO^S3wHFmeL8T&;WuHUuSB9m|}l_!LZ7Ttz@|Mcra&`sRFb?d50&(+^TEAG(RS61a=RH@DkDSFH?8-3(C>RT~dHgjc@PxI3{eefe(TJ)zpt8com z?y={EO_rJ}_i!)Z@KebK*54MBKLL|n9G4{({LycVCn4w77I1*9Fo*^ZHkDIdZv93s zO=(TN!=2WnZe6?lgO8yExdEAlxmQ7JO8pCNuC=-wVtSMXns)w(*)&4SO23i&~~ ztzkz1r$6K|6_|~)TGZ@4x2WFY1Q8)JXoX3LdFf?wbS0x(BNZ+zmSSBGo0K#|^j3PZ zYBPn#(va&lGd`oezP;}}a&?pMk@wmX_$tsxrK5)>}_KW2;X(~U)D zcxlV-Y_>corOYU$7!}Hl%582oFZJFz&nE7;vKATnWN~?pnJI9ahdvA4kNBWR($G|r ziSI?ctZpE>W2M9PHtLQMIS5{+hlfepLJ-*;az1@_8gT&@}fp&N=-A?P5 z5W_A{bcWf&pM5C=ngO{#)0>=#t_qYvi0v2j`5?gZo8aCV+;SqSu(Q+r(drldvvX|Z zIw<=7C+suSQu866BqhlBiv73eZ8Gl!TdMYHv?oo0Jv$`Cu$9 ziY)G8za}mll#uRGhX)-YZVvfEXcJ-KUU`%m7rH;EGA-u{k;{di;o21~sKq%h^yEo6!dylGHNYApi|K@^yO`hk`RK*4TXAlHzmF}Cj&ZNdh1s6W zhMt;fdVQMs8E+N*Li^c05nLGk3xOw#^~01UziFliTF+@9g7QO75uu*p;s)+{eYz>l zWPzoOUW2piTk}^}`c$)N@U*vw2h|`e+G=x(AnN!#CPU+w4>-oPn&Qa``kchCi)%|b z8Fon3#mBQ?VTiH}FdnVa>#ZS)bOD5WHW9EKDRtOMe63o^m}Z%GOtH96x5=EJ>7lvU zSy{mzjk@*M46R`2qYMgfMRbMWb;ph$K&4U2RV3j@Oy<0rxi|LG#lMn~L1 z@MeREzYqYV4DVVxXL&JXU4n#uD{K-{b;2QQbkp=9*z@W4*%&c=P}BNJqK>cA0)^H2 zc2^#@nUBUIl#gKXIpEf<((rsgTZrCf3+Zx4XyVQ zUfWTbPL&z!@7psk|RY}*Enos)?>)sl%FrsvVr+(sk*r}EBp`_ zw25D-X82~)+?9QmTEZucSy2Qao(~Q(C`sJrc)$*caBINI%M~a@{4b zWSaQk?++_a(w`YBSpl^BkyahW?hM%upZ^_11Mvd~2uki?q+$Q`xLjPG zi0bqY7mf=h*GmeL5xsxS&__{QWppE9Q|igSskCb+te0uwm1H#%kxE5B?07ZqV)Lq& zx}uTxk+pNaZ|Qp7nx{ec6uVSBV8p~<)R^<4r>HHI#$z1)F2j<2Q{xoZNnVf!fk%DO zL<`|}909$g+bo+r1RTn#Ov?; znt`7#1aAW_WUY-TA3s@1N=YpM%yGVIBhNJA=^YuV@=rxB$}}sXYmz$&ZKE0gmRUEx zs6WMi_cmiDyGOrCUCtBY?XvXql!iDkTubE20sSv?-SqgC7|mE3BH+jVLIzt6%;313 zfa#Fp0!-V`-Q4Oy3MNBxDs|aEA1%UbO^1^u^JQhlGs6cIzW#BuGh_>BCV-;|bS$N8 zas6Cear?aDueLidFU$tVc;2olP*fkMRH(1suX*>e{!y6H_O?7ZQ~{`(?uV}tIT?Z; zXJBL`6?Ef<`s@ju^_KSaLqo41s=fnU`3U?i6bxsG%@2SZ`1DbLpL9VfvzYEc63sby zdbnoYZej{211;zeaO0NX6v?csOM*2C0_6n(w zDDmAbaG|Fk#A{{_T8|sV?16mXS@$9r z{58?Wc)8BK26dHA-#_ZAf$)kk-Pj;YFFAA^_S(6Q5lKXq+DSRA6&Cr?-U43^e^66; z;-R~iu!j@8;=M;^pMsp^!bXp^x2MU^qnY3^Kh49z4bFI1Nsnlmj|QqfvW%?44#IS4 zn`-x68S0$fuc`Vww)vU_t}K3?qV_(PPo!|CqrN=+j4MnFnf8`5{4kdo{ly6)Ec2sN z#1d`XpQ;q<(JOFl1wSRkf z8x05JRSMVRRBnpZu5j!o^lPpqaQv0mevb|pppKc}9x*G^*=f*g?V;Q~U!XqH3`mtD zeubvW7}52-DuY(e*!<&*dTT-@DTrzidltxxqkO&;e&r3s;*z@#tkX8n=sfu!Vt+r0Bj zf*$7loE%8i>%qhlG%_;!4TOdQo28DdtH~M`_@VDE$=eOlJOm51Ud<>X?`#qFB?d;c zhOEL5EogB$7F;3j=l9PvGdL!4%CGkcvAD$LJ*-K}ZqZCz;(`@r(>>ba;?@F3us(({HTG_C-!@Yp-;vvEdV`T|?pX zt&bpeRPL)XR3($@|4yr^fHL~NSGe=9<_GVBp+c;)avNzr}ZD~(AB2_)s|?jo$p z)atgwe`y4$+u#2*M*gXaz2Eh5Tr8~^2I6w=LE8{alPGgL3ypHEYj_|&BUC{tn3a@M6jAF@Md9eoGrinR{dOqpV_h~&c!WHG1X9}y=7z$4Q(r0OyI%i|IZzL#k%#V`GE6JAqXJ%+XCm0gB(kC*^&q;os z_lFI7q*c&Jr6->GeXP@RcNh4te@ASt5~1C}Xi`i7PznQ%eDvQ6Z7+VHrWbWU*y%i*%`Yp9ZjYq((-(yKWimqD z%hRZSeoz~)sH(T3)mb*kMH~0K)N1ij3f)p0}8=nV2qc;Ho7k>rHFA?!D@H9Vrinx3wGK;tB~pRj1>nw53T zOqJPg8)12`>Eo#9h%xyWn{1 z!5=wAZt-#TJSrvznV1woqDO-*pg!~=rg3wsG7?hLo*i7o|6Wv9`(|5FX+NO-65~|y zpHAgtMpzLR8TJv1ikp|{c~2rij#Lia3q2H7y{DqaI7c9eab+|oa7`92`_ckJDR{y! zwL{xes0I3o3XlEY=e@;x5Lb(uEn%2vQW{;6EMz-O)z~$>uX^orFp(IH3A}x-4C`ot zHqX74M!8T5(3zf*w6jvs3{PB`DPsGZV7+GW*yhQec^AK^*V?~zdEPH()n5<$A?yFz zH_(&F`JEoan>zzYmLl6qj>g1s1S!#W-UN|Xc7AsO!A1GEMQQJm{s7^1<^IM<=^S@(a(1? zqZfK#Djdt3cqRlFzhd)(LEr!IZx*=3#~VgD*v~a^tJ_1##NX(6Qd{D9@82~b2>pZI z>cxrX7oGB`P{JV3SD*argJ+8Z_vsnOUp|j0pK1=lWO}w*IKF#AyfdgaYIuFQr~CevJ2b=Ia@oVWI}JnX4OV^0NE~3F)E*o^xSyL{`t9&@6Dp6Ei&ad=UJA64IUdAbJ(GER&$aT`n~# zlhfb+7pviML{Y~?|2~P|NQG6x5$g{W5UqdJ6!2<`n(K2ZyC*zLVL4~z`rgMkWx8+? z{|F59JxxsocV^d)QMo9}=A8##{?aqwwqLh`fJq7B)Z-YTgmT$pry1|Vr z4|3ntT_q$*l%Y63`fzbV=!u7qB^(&LKQvr;+#$3uRqL1MQ5!hqf3x|L3atAHV0u)y z>lb?kw&$cfkZO{nl=}+?T^81}NRaW!8>8^gpUI9KC^zqbbbD~_S5LbrJ0IV_!81XA z3Du@oi`q=0>+!txd!dT;zY!pV1KruO$b`EOnjb+MH%<>H`?4|rn5aIh^1{7H!G5z8 z*XQ?d#NinKT)=`0WWr>PnN3SIU(H%V!G2&gsLWMC7y2VTc4wPPL&W=Gr&MF(lDY$Y zkjABq68>mi=_`Hr4T=1G+($KJVHO|hzI01J$x+iOG@lM+-G}6F&DAaN_4a-o!fJlQOB<$T2>} zt`b5cyRYa=E$1zo*&G{1dbA+!bB~X_mgOleAw22aSInUuh4+`faCNTIZWLMHK2Li+ zcXn~X4cWf4m-D~yDd0Haem`<^R+I3uJ!r1|b)roCO+TnysPH3ok+&AX&45->aCl^? z)g>T0@!IXG0xA!H)4fdK4hNm4JXJ}bxRMevytJ5MRB<1)VYw8j(Cs4`KX5MUm`^s6 zC0H!i{l&(5DBzLI`m&hT{og(2v;=xHMMX4YzR5nq0~&#dxE$3>?-@2mtTYx^K0-JF z%|t~F4YlPZa{Jfp2)fmA@s5n08}r|R(^ep9H(CbK+6*f&qZn$vxl44lb$ePzpe&Wn zOANErP3C$YZC%SBsoB~oju!4PgZMU8MxN*e z)w|gBP@#tgzuN6;&+~P#tEeE=8>SQyI?cX&AX)UQaezB)4|cVWA;yrCGN-EQGxX(V zoN6i)BN3VPGc7$uycV(CVM*gfyD3PWRiXz{zE!&_Lzvbeo`U5M!8z#|KG^YtxWc1$TOnywagwpPGkqG*SX1zSwEnx$ zdg8G9y!O4Wx9rtcbc_O;-gol5ctT4oH4;CYlqJEJxg9DP&@*zq5{Vj%;Ijk)g6A)kUj4F545cL>>xZZT**<~hLocY89}!4ino3lCDNI_N?Qf-=xb8 ze2HOK$TgO>lyZ+9!*wddpu*JT@q6p;gkL6>SL7Uu-VB;9pc~~`uzbTEdXIN))C<&@ zg*gsV+^XKhB{;_#q(4VmQanZvb`RZ4NDp{jRvr{C37e0xrQ8y0T*DSNJuSwq2~3-K zcSe67JB|3XML`)?HZR)?Jw@!*>zXr$j8FkzgTA{iT^<;<8MbVoWQalx6MudK@4l#r zb&&(8TEJ!O7C4>~RaI3zpCp?{vGW=1KD5lgIyZ0YtH<7ki-mj*K-Pk}2q?3`x$}|4 z!0KMwCxJSn4QZDs1`gK$2t{ofJRRXb1PKn$5(uO1pcgGg~jmYPl zn;kZtu#OIJnG@wyJ5Rjk)$9h|P}s?IbYf3hr%5L#1Qkl%R=j}oe)dC+rb`Xr?fvA7_DC`>2~301z+gSQsSJFCAG<+`8)Y%eLFd> z=hSz__{0%Jv(*$PN|_kwg+4<2)V2$rI}E-1Q)))(ql z7zLQ3%K1-TI9o|n;m7})@Q=~(;#Fr>{vJ<20P=lRaD4@r*YG)PK9g@!jwz#~L&-g4 zS-Y^Q@e+*@*O3)-9}C$i9Ipv%MSYkntAFAxaHA5&kJoUk)89yNy0TR?*QR733yH+w zneSVlOTyu;%S!)s4_PrdcUErZX0>kH_-$1*-Xxvq^$dCGPY(m&NK(~gqCgHfK(eyj z&MEQ!YzcPNx(?blGEy>F<kzI?UXE1<`W=(CYzt(OAh_#o|fi zO$`&&K={CZY3-iJN&{a+{d6C{FW*+NwnsaDR~D%s=+$Z%)Nog96EW<6kvA-kgKf7T zB5?u74Z4&`rxrHSjillf~2iA0 zN%>dko`q7HKPGRz_4Q8jYCTjdiy--`A9(RJu4!D-_u<7*(q%}gcTfwfms&t>0{2~h z$7d5qJNWz~OZwtwkHj+a?Rj4cxXeuz`}uB7HXRiY@u{nr&*&{>#B%H%kEnVax6Awj zdQVx@knf?9^;wUkZw)s$?cwHC&&;|c--~lK4V-)dz3PGvSD6mUCuxdP+==s@6d`4r zZ-TvgnZ%UN^gqzvn7CmlbH93xM^b9VTqA~Ao87zfgZZRGMDqb;EZ11?({YWbZ~ZQl z{oVteEHMFOR2ckTQ9K$Noaut}ER)|j=Jxg+e?b-R40eT6pik`q#Hs8F3C_zz-Y8_u zY`xI(q52d8JAGgt_Wm%|AtpX&R$W%CHjuWLQs$|Wg3AByE*R!Nf*-TFSr52J<2cR9 zn*H!sjwUUQ+9PO~w)udgw++iiL#<-f+)sW__{w>hV*=sPgVDRwN3p^StpS0!I08%M z^h!2hPcU-*dEq|0`{;dbPWp9EqGD9n+*{{Qv68peEIHaF*cA+H3!Oc#E-g{AP?B^( zcHNwH?oV$f5j}bn_vu{n&2b;|$)kb`jm>XnU8>Uk)$ISia!2W-hvbx5WyP6U59A`! zivJjRsgtgKUQHX}E+(gS>UeiaI@lJ@WHFgkSX=%b!!zMQ+VVM_=qw5UUwc{lv?||~ zoryA2Wr&{57k88sdb?GVyEB3d?z$oK)7LJ+%ea`5M=zzm5rT=JAA~%{O=I!UYo5KLi8ZWj@zQV z`e$`JtYMpx<$V&63dtea);-;uNpF{NjL~r(OEUv?KED8Ps*YD5dNhq?iW<80Q zr@(tjlc96&oW1Fa($*_sd=!g(>8mmEO`)#YV_FM%YIj}_4bS)9>?LK#bXU-ikWU>R z?MU%@1-FsY(GD#mHJS#=HpR5AInMUMPExLaefh|ltFbKYcRKWxiAdqz%^q1pr}d3* z-w%6s9~`fXMPr<0arC5xGzn=%sc6c$Fbc`Kwm4Lpv!MtSMQ}U`QH{i1{2EK2Yj2gJ zi8a}4n^NVAQB(TE_A`Csm(t&5X<9<-N1I+3A9Oa-swjh(GB9o}h9?rlap0wnkA~B+ zO%ku)c`9N%RccQ(V}o5f>n1v|+JE5WAn5z))5A&FU?UAUk_RJrzR89t<)^lnEd!xs zLl6k+fO$o9H4h#>chHFhhEJh(;}sd93p;SS-i+4k(W*-1Qr?=ccsH)@Y<65g@t^T$ zFnARqd3tDgxU}M~9R%AtGkr4rhco*s!AYUr5?V z@7y}dRg>iV{c?Y&9>4adny9oq5fG0Xk@SY!T5gT-^AuLzIj!WZ`k0{6T!bi&>#jUO zBd<;bhnhLq4vy9Zy3RMbd>1qKhMb$NhhE=tq5U^J2%ad^y9UySboR*iDI7q8+hqze z*v!5_fMF_;?i)%Jd=9=_v6olt_c`juVzg$}cSPPt9m+7QQ|Qyg^SQrV@j7|Kjn^%Q}UqPj^*c;RxbOGKnqo<|me*$lqxTA^)*;;Bz(#K%8&bX^I$?0Z~ zt#^C&NET{`Xz!j{A$te6Wx)^KWt#E!vCYJuI|>h5Cl=I z-xM=h?bl!V;*b)4h~`?t7dA4mf7cj({BELkYv)4bsnUIH7E);wZ?FAbrmu)Y1(Sdc z*qMvV%f9^Ou-}2Bj#`Fqi~Yc5%j1nL##Pssd;2ug#~D+V9zs;r0%4B_R<_9d#JpiPw_LtFduWvfzRW#Q)?B>cK-hUK>)>pjx1IBpGX;$ zzhU9wPr-GKyyf^*W8b<0qKyRZ8@LOA#N`S4mh8dSATCh0A6+hSMr+!0+);YHchE;2 zS7dcBE-nsC5&=+GBmpA7oWu=MV}F4_V+Bw!+JK8d5;0(uNcjX?P*e|vmyNAhvGvVK zGqZ)8k$}b^q0`nt;o?up=l|dr=nV*IX|X->6ndi`$l7f{)@#7wky20~#1}Y_`yPLr zI4{L?%&CnNz2F4R@87JS&%Gw%GloZm)0CU!v5_#E84IrLvI%(iWW0TSzdt&t<9ps5 zRY?~5Z@@LiniA;4ma?GtDDK>;crEe62Pd;KQ8RbrpN(+`Z^M~pNtrJ8y=i~)3dPOm zyKyjx`CfwSw7t9~ea8NzoQFH|2s3@8IA7@3u#I_0-gPa_}G5C91RK2frthTLuI~ zey;z+F6kP;Qlkcvijfi?<|dJV2?qjtC1``;u=7B^7}#RUIs3W@Vvu+UOM52EbDlm*bYQckU!?%C(KHPNd>e{g3;x@oQv{$MNm7^k?d)tEsN&lyeC&Yd#{ z=AK0c|N34|(dQU&38!w|8f$)~o6U_!`%_me5_J`{ta~sec>`uz9#r4J6n zR!I9s8!t2$_&Neq$YRJu2P$C$>>xTI>J1=cXw{Q(GyrcS;d6Kdym5Zy2f86@_>VOh z4^2!S41RW7bC2fo&vU1aW_tfr)qh?%Gx|MrO5Lo$Ci8EJXO09m!q0>X7jC1qJPnkE%bVpRr7D=7s-c34SicU?Ume%D82sv(+jK3vHmrb5)X=}e_k5e^}hUJ zHnd>n+{8-1Jf%hAM8OiNJIB>8mc?V=7s6)C2!x)pIT7c<{RXUj|zh(R@IZwWBjLQ7}nW5=4y8PV9zgsu6AFj` zua9p-+!~B1iygL95&Dw@rD8B*u|?#9p!2Lg9^SwuI@s>+BS_=c9m71sC3|_iEgQo7U}hLCAdI$9&gcn&}~dcisYX z-RkzpMdDnly)|Rj3jHs-#1>@v% zyAzc!jY%h1!pHckeKxL=Otd&%<^~%h^a0+s^>Bq{pNaz1rUe#0Vq)A)HZuDm8Q$EV zZ=kKltl$d!*(-Dz2j!T#cnDaR4+kJZJlc`N31w0{nSeCOLY4l-;imw312>v<(y@@T ziptm`$L`2s>ExE*lp}CfAa6+LF$rvgvB!JNI3pni%PI7Tq8Jq8G{C8V8UfM)QNZ!t zBb>tV=Miz|J)d5e6g83i^tG;#jV%SGE9j0ufvd+(_GZiqr=D|l?No|}_TtDo@*t1~{iT0%Lqb!_|KtnZb-Y>gjy)oF^vB0*(ia_3+zy+< z@}Fc39M6k!U4O{;-KZh$LjhoAQULGSiar0gw&c=)Mo;=YZ-HP`=N1przv8#n8Ywvzy}Sk279JK^}F z(s8rc?(WeoY51-2yxdl^c0kZn9?!VOyLX$E0+eoM{W7ti%NUiAGAuD%?41RbhVAdR z-Ytr!SOY9Q!!>Fk{>tU3M0tm@bv9FfGQL z5NW+3MJR6IjCRz+aC6(Yg;d~g>F!P>TU^-Z4|oMJ8*=I)KC=PzZ=Fd975lz4J2X%h z)q50o7FB)WdLuU_L)Ibc(Rzu!Q@?l!wy7`J&bjTAsD0GUO(``G2;FJ_1_XR<{H^`J zzJ6`@zF(?&dVZSJ8K294e}R@vDTgDq_4Zz)#v6Zy9o&GpUv!f<8h)Kq&3(&heytj? zmhvP1X!lym0=+S29gcDGRz@PO@Hy*RwS(Ag@no81?<9huoc_AqB%b5vKZ{Z?U;eNt=W?!VR0f(G32)1!-FkOCVAT4752fy4s z#JHJLbfQ37jhLBa#Miv3bIIn*;2MAdB7~p6|1W;`e}d8C{6RkX%vThGKm%N^z&=7} zIV&x01_;Nbs7}fF9^0;QtV9 z+`8+BTK$WF1tJBlsSnt1M+D}I-@5xT|myjCcRY>si|V)C!isAhxv_LR!Ycb=}K z_gqc6*dYHg%F{kn%NP4{&d#Cg$Iwj5%QL4pX!oNL@w1Rn^2K?4v^5a*&widlR$fXNbq6x-jo85kG< zjZkWMqjrNa`wI_ouLFBKNe}VR|NJ6ba;Ew&CY9-zz+_)%RbqiHauSX!y~3Zrbk_W;7o_kW zz1ZmJE_tDFvEC>kGY1d)kpy1pTF?)j))BU~7#iIz=YYoMS|PL9HAj#0{$v#YYrI*X z2K2*5K9?bG^Iq_e2HE17mnV=7OqAd>^Mn=XMZD7#5zr+U-wzKr$qB%8pS*_9LhuBm zKh3|(ZCtvVMP@w^(Ejq%e18FUIx+XbVd#S|nkS*`aRSJVT@Nv9`YMqW?-7(;^mE*8 zlInaeNN1-pzHE~Jl~_{ZY(VL6+}Q~9gh^2*$H@W(mFK{ZUE3bGa26&&l;U{K0c9Py zel#xMtx7gv9P9=#)d9ai^%t1n$MITQeOv+I3rX}ZRfk&_b-fN9Z;^*ZD+(a`7N80` z5P6sc+Mpl__fp8!4y0L;5)`@#cwWURAmtz#&k!@a=>}jnh69VeqwY<^kx`d#L>qlt zBPtq!sa)FQ6|C|5Q_hz#o^aex_#TNnvZYLtZp+@nm&JhBew`1c-MiL|8(8%Mk{No# zShm?`6*H0FjdD(i_}X2>wVgcNYZ2++jl}jr@a5Z@%l&{AJetPOp3JPzdxf8W)WGJ+ zbk@#yIj{&l1zbre^bm#IPl#|GB;l@Ll87|Q`+=BxywfO(KGYh|A5AAIVScLZ6=xtc zX4zfWjer!`2(*S8n0Ax^59!jmj5zcm21Z6Z#B%bt0keaS$J$L~en+DspnSR9L zsz$~)wVg*AprDg}_>cjCYGDx6_+V!(dpY*?>#x9z{WKA9u@o$C01}ROnG6;>+L=ON z1q+y8puEjU93isD3^J#Lz_msG2r=5D63IAu`vejf7t1SjJ6S9DDGlT;KW;8bo*Tb* ztsW8QNI4vM0y8mA9@y^H`vuc!>VuwPJ<||DsP1l8qA^OsKS(_!x4%`1z(kD-48wkLDhp)#?OM~|R zyvVXo=4LjrFVIrR6W1;i@ZBY$EO(B+VX9mn0s3c~p&NSmE?&Lto*y*^%3P0d?Io=g zhhyx}ez$L{VT_{E_f^)ABzpTA`Qlt`+_%J#Jv2Q};jiuN=7sWv}jiD5j18ua3PGg5Sge<$>UrHe$nQ^6tka3Oo z<_Y`*Yc7GrBh>D|_#zKhub`@Z&)imC<}m4Ea96T^#5bvLpT8}K_i8=ezU_v?GSgY} z?VeY${u5cHlFgl~Khd9(e-dn2uyG&M6{sP+J9=4%so@F6LU^*byXI2XhsKF5REp1_ z3lXLI%IuRDN3!hpV>?1j<*eG_+-DuPw(V6uSUK{tes4y0DZo5@)uoeFU6 zV#T`g#$tf~q|%eOFR$By#*Wy5F)*lTTOe7<`uK5V-}?Gm-_p}DTatqUxMnI-FIVC_ zj~@rl7nzIO=vC6g`<<$I!RM`$d-huQKon4S%H1bT|WmK!LR*m&KqPVO%! zssr`q&t{>lPvU;aGV3#H5pHXKiAh-?1>=DM9S}};aJR$^QLy}lonp(i_pwY~G@3IC z`x)mHJ9E@c+uQ+Z9Lz@hV*S;&Y5dZXKoD;fGU;3(CT4eB!18{B@vHuDCJiLa_^+(F zk8Z`1&fUUtSp6iDgV7F@QH1#R*0k?)`Pcci`^~{K_Ns*-4s}xZgKlSMo+~Hn#SsP) zvp(*~Gc67eEMLoi>OcBzbolPsSIxGRi)*K7h0gfA?z4Qj{gfUhe|)5G8T@Q?}*z@|NfHtz%#NJy$d}`2viH zrzkQGPWI`Uz#?rk1GZT-X=|IJ%2!zzw|KGKCta`2Ih0i!NxO3^rlb3Qv5g5{#jwB2Uz$LGy8E*WoiaS zmb9nF`wmBgaihhV&}MHu`}@K%*Gz)YE>jZDpv^{hp0;cw$|@*F_lZau>8u@P<363k zFf{6qb6At;M;{)v5OzDEnWQPh0-CI%{n4-W(oED;OXD(FfbOzoEXHdwOyEMf4f4Pm za|o6{Imxq9lAU%Tu;pT2_%_PHruofQw~yNJa`lMt+R=b9@B$N>F$=4%wda18%@^0c z!+w#$f`Y9x`cFziSWm;{w{!f=>a+$~uJ8kcxDbjKMoRLTnnci#B~OfljLtbPR19lR!5da3N_udu;)_p2Ur_g!;-OAdt*QE_c z!kclS55fGs_|$zWHvFnnUm5G2+X+B_wb1(7DULa8d})&#ewEs0L|OtV!XPQsCVSyc z;*srV8?BEGxB9qNT-{{^(c59jir^T{6lG5XbLF1*hZ;Me;xk(uwc}kA`9$%bmsp(S zg@qS!efAG;46d$sIUAjpvstQS(+{Wcw)Vk8zT(*4V%L1>p|W2-je&LQ>)7r#pKKOK z`Z7FuNHlEADi2*Xhh=c`I9K)0-X3gv zaM#bijT-eVoc#P$uT)@q?zX&}X2H4hH}xB~>`>C%-$V94o`!r=qia0%L$Re#SlPX+ zXg8*(4c2>P!cFL2Wm$gX)5-@e0T+jr4|yfBmTtyODcgsi7Maa8QG>-+!XejPUU1F7 zyv41+4Kqi2*9L}M6yFz>?=-}s31>JP*AHeeB82!s_A6>to$iWqlp>Us1C;s2@BEJo z&P6b-etOJ&)K)AN?L(iADWm>3EOqFEX+RawJRBB!pTdyq7s%$IgG9Js?5Liwa#lcW zy`P=kLUwb9^F9KYd;AjDEO_%F9P)kC)ZRmxLQAub>(^cAY9+Dp&PN39^_Zh>Q3!73 z;dI9N&Igxp$Boh~&c!51m4wyb;*!62ODL*JV`nXM6o)ya- zec&@Rb*`40XXf%)ntDQAtB=_}U5WEOHMbUYTQbjL|88Pya5mw+!t>raM znuZ4s1dyrFG}|8R&8{>2Wo*Pq#aKgaXv`RJ+j{GfCdYmAB{sF?4=6pYu{uLaaYO#t zVxaxD$%5|`yK#fp3qCGI<5E1zYHjz~s*+sKnagik*4GOtBm8FnY z8;(>sZFKkDDevBA5dN7K-Tvw}yOoLqrq!Jc%U4-6$6A2gqO2ZEm`6!EnipoG8WIic_N$w!pn?fM%?p=49Btk~K|*+$ zij%_tJ`3o-Uxu}kEt4#de|Z+8#k(2B1{E#XFrbJk^i%RW#76llwZD zhN@=o18>>clC?KN1igZRIBXzL38t3qe<{~c-&Aq2^yeKCVXbMhJ;YD4ko1~;M z`*4B5nq|!6>MGwa8v#&9&nXsp@xp=`ulr$=(BkT=rY0qM+L|9pB<8~M!};#KjxAN# z6~yEMZ`dc7?u3d7uSs^NLvy8~a!`}W&8DwA64#!PIj0S{~W&)8bc-b~)HIg^hiweH(p8Cuon$8OYEyok&NMu@lpWB(}^S zC1j4DO2!+NK0Uy;v)g`7QrG;*-*@bD`jlhZIj^QBx_9rxvS}WF?bPm_9@ACnhn2MH z#)L%`-{r{ku)NChNWpjPo{CMma6ixhZplgH_W4)z8W_fLmHyl|{fKivlxlFxkosWo z*pk^;<`I>wCa%X(S0J7Htj7=wbK26NL_qGrm#-7VJ3Ddhw%w|6L&HBL!>A&Lp0*m( zv*#04>d>rf$V3cunq#jFjU|3luC^>%Uq0tmXLy6B{7zA;Y|7_sLWwpT?^Nbh zj!C7=ug9#r(pPtsVfe1dwp$|FSI2zFb!wcn&h}gHR@cx{{JXA{J0!iIHyP7g9-~!H zso3x*yu2+9W8NXDc`13X=kR!CjWMcpD`P(aZ0`gvLgw$dZrg%9&DOY8YmcmHQYFFaMhB6V*&a zxJL%n{LAbm=jZIvB4Vx6tQGbxzSv9sJ9%kezL#=X(;gT46s=POLtC&S#P@uTA+TmDq z(B<{6dmnMX4cSwAZ}wnvhwR+6mgypgfr{dWr6+B&y^e&|FW%oao!M$Bix^LNs@U(c z6PA9Z=RywZ_7+ya;Y?5QQX!fgUgbS0zVAX9l4eHonm5mzwjk>h2`&G$ga1Cp30kHNP(y{-NK$fhz8LMgq}#U6CqLTG7&AUJ^RrF!oFzx9Sr&Cv$0hYT;=i zk_3VGI!;EouEzsAQs(=+G}iM1-Xo8+=k93_=LdXI)ohe9A1bmv;1@o#{9^en&isJD zolDlZBL3$FJYSFTeZ@PP(-6p|Jdkp4lR?SvQ8yl2?qK?Mx+PfL3+d!EnetsW zM6t1K7){8rn?IXNpsdzY(Tsz~^H^8=M*Q5+i)TH`Bl!W+5AHo=_|@D@=!u`z<2L*B zk{EjXKWCThKaglj%E|NjpAn33dRtp>wIxqyS-*XsO~=VZ`lcall$2Qry)?TgIV5MG zPvh{?aDXS?DBGT*xUEivosIEbc{HMt8npCY3^ss{L%zxZdU6A+k7lW9&9Ld`);OQ( zMCmm8B@FK_e!j@g13tdlbe@PL#{J!+3DB!}`_^=@PbHN(8l$M&m!j`r%J*pG$M$g4 zxVC#Z4HngsbzB{lDqC_o?Gny-)U*!HV&9}Zc4cqzVfT6oR_1vA*tyT0gebO>xUXO3 zI5;^gJx7A=RSR;94DG~O4`JBKrg$Qri-2!?JOSU<3j9Ows@;7SlMvjYhwG+rHtEq* zl5KwGR;{eJ!7V>iKYo1n;lSgzvAxvTRbgS_)JE&zzf~~Rv!o=%1v*Yh_wEmE+0THO zvM$HP#>(jE=oI9>y3Gh`RW1|*wQJ{G2TIRHCcdET*zSCev*r7Ju3lX?;tHL3*P$hc zcuN<{Jalw!P(00FSir$n?#`e;%>G&B`6Fy7+Z`OM_lA(#Lo3%6Bf5izMAThYirC8)i07+DQi486_4t1NQ-`8CF;@1%II zpMXJte_lVr>9Z<{11SWN)p!t=%+?zCT{e`t{{9!I{tY^Lh)okh^z74de+q8{ng4*k^*69tF&yDKIhIq< z|J|+X(C`JbT0^jL%C`zRxe;YLgrSQ#|77!i0j6xIN|16!h^>eYb>X8f6iCow!C2pqr<1Ga#Du3VtehY0){ASN0qNHmVJhK4D_3^dR?>r4d=E8A2mS$W1Fr=uh{WO)CYPaK0QGfE7FXbRN2MuKD zaLQmEn!!zwf>PGiZG(jh+zF8X0C(jZX5DXpH=#lPgnIoD;mzZDwW$Lm6ntdHQu4!@Mp-zT3GcPMwAz z?m2P{QXhsO_B9MsVRUUK#>RGtw71nTA1L6$ZJyY=z%-9R>uc_`^z@=V&Y0)VxgKBw zbM~pXGrJV1dqVr}r&D>pMs`|eru@h}bL3Gt_1wbjv%mjKG~N2OwKRl==eivq#CE)5 z2$W4iuj`pzC9J(XzAWHyG~dny(x=Iy%PNejx`ft~`(3b;hEUIQyDY zq&8g&ylb#Es6lb#pSGG7@RLECL?0+Etj0!xEq~-2X*^cZ4)?bsX5CNEt(ZRhZ4&se zq|nfk6XN6nR^kL5+k{K@AXB~y(t^s$%Cvpew$lH$BmZdFQAB=RaC%Z`th=xx@N9PeR6&2VVsPM4wWGende0xEW5)sz3G`lVaZsX_ApAQ^n zyM8^QqN0MaE#e|?PV35fJh;jkV4sK0yHkJvS8L}V$sG+()>m!w(NR$~4@?ry_iR6} zTKbP=@z0xf`HU6*2vr7xKi^(+XKC*v9W!8KflM0lM}mp3F;_tl%>dRo>+%q2UBOm_ zxh93^KJdD42qL7HNZfD{`UZ`S+|PRt@V7!tB`mFVUAB(7jGPz2?j8tpzu{s%f6Mn z0=g^6LIUbyaesy{DFLPnxLjUgWqsJ0CcAV*{s$HQy~C;NxiHG;AvAwkdAu!%I3fXC z{}wp?!T43fq;7xouOXyR7@e~pWas~xOwkYfkw7D4x{cEeXj z*~*H|dCZd+7#I0gU-iZRhh>_Udy}i#B@{8l{Ik~o{EweO$=6I3*Sp78o0HZqdHU(NiE zr6ntJ!ytxp>FU+935tzy9t~D_-Me=lq|+IBd1HV4Fk-y@vRX;>kmM7Vxw^@oTg{VG zt>Knnss-c5Mi+C$v+n0k&u8UM!ydcL&;Ke>*d6gy25FbhjKhaoB}yiHnqQpu5$_c+ zGwcEjD0tXS&CUAI=>qiS_)*spNAXSfW?)Fe*JjhdU;q4`8JRh|WCG(d00_K92&8Bj zSb2X~;9xL%F&y-KE^KFIXFmjwKlN-iSf!!h%Ltvp89!o0y^%6U;LpgcWwwWhZOm+o0>PazCx9c`6Vb-<|{E-=pwVl9Kyp29G_$fRU^xc6(=u7&l-j~_Q+ zdO}fXO$t$&2PGVWPXXr2CVPV`C$g)1o2OmbfY>JlZX}5L;F#C3a95_13FuXkkdkfy z%as%44T0Zw%x^msx(pnpmk z0?~OFD;UTMsnJumfKRSpRWA-SpYyeI4aRqu6Z zMHfCA1FEdl`gM;HaG^<64;L&~G`~}fE74;SR)AaqdJ8PhDbJeL_ z_{S$!&0^*Yp*KIR)}GZIx&Os+-!Y@=p@nl|%THD0AwL9SRJIk9p=Kd+5C44qb8qnb z#@-2!bJu|4)BT%ZNXN+nEMqMgc7+}Nq|$x6xD25dk_)>zjDPwMa-me=h(^z=qpdq6 z{`2!@Air;PXX;6VK?+1}szK+q$p%nd@Q1jFmDtkS`Vf*Z4-nP`b0ZsWPBw>(T;HDV z?s2E`y7tr^M=%FB;E9t=ym&(oB}46c8A+5Jb;E+Cf493N!p0e;Q=RD9YeU_N02#GPGn~04^86feoAPk)t4a-$Y06(N=W+hzKMB&tqcp z!FGY#>%bN)!0srD?EX;Oj>=~pl+W>b17Agw-!nUy`c6|Z^;LH;^i@Z}mkyD4_#Ke6 z`vL0kuA|3V`uh3>`}a|Y!Q^{KPF+x#=}b&$Z9!rFZWhgr#plcvi>_m>ObR^$rXK+O^UOnQ6_ z=6NQ%?GxSV2H+NijILx`U7af180;G3C>A^se};n{ zykFLV2n!3%mX9)fnf~8xU>+E+^hPF!SU~3YvYA4On0@Msce{4V?V(WnNu zZF)P7teO$S5IC84K_$GdQ>24=xaw+$2tqXmZ7g`RI1LRANHGOmZ!yo--lo#!_^mBx zdm{^)!SNd3SA4dYkeLstNiu*(4oblVJ!QH7M&mrErd_V$9|9?sKPdRh;7i~KMZY|H zyDh>z0;y+sgh+0tS+EcD^+0tN`2F@rUc<@E!ongj!b8YHgj^6uv8G9p2LDoF*IOtJ zVH(YnBAO#dFo*tdgou1-i4)Y_5b;X0bAseUsg=Kfn7ZH(4+?kqmw#SgzI~|$HFg!<%JZCu9gr7K^-#|No-5*F9-~dv8s^xL&*zV%8NOS zXOal%rB`7pabnFyB2o8~9RutXD2;ANvkH1ZB`x{I=_~~W1>eRuPMADDi>P>k=&w}V zR7`nn&WI=wXC8BOCyWWt!L)rv(L=+mLfMEEjBw!wETHc#dv+@dZ)!2 z-+{#kcn}~o7mT)nB_|!%pXZ?}mxp8(1(v}2P(3SbX;->AHl5n}W4YW475keF4Aa{t zd*z_Z+q{N2|AMaTE3QYXMi;4PAVT+7xN<6Ha;^?TsmW(Ej)I9Zo$*g_jv?jeE0e?l ziv$K+)7D>(_J;eAI8DHMc%DCk3oIjKYyyl|)eB7tAxATJI*A6aj$S}806wZhqP;)@ zV&n@Y1J>uwrv3{PkgX%ze*W@I*KE{+-}mT~`yHQ+G9OnKNH0pIm*_$?PC=lt4Ms4; zpdv(klM!hJBqfEtfkK!o*EEZ-rET2?_c9q8Pc9H(9tJ=6m$wz-N^sDR( z4uw@9a?1x*Fc_ATLAZyOOguz{P%sY*f#{G36}bB0t@m4Sp5D24@20*!U7N`MHKY~* zb`JiA9IiD<)OK2e@gS1g;J%KiXz*B%enaL7AW_9Zo+K# ze|`_Y##WIUhVb+iWcNQfEDhevSHJcZj=2~H`4@f`ND;-{dd?S4KNdL3?*Ly9%5M_H z`$ZW<_Q$>_ z2%d+d9@OOdevCba+Ue`VSnUkRl&->__zIOZV!|AJV8aNhlQ7XAM)Y5uQF{Qv#W2rBIc z3qs@8*XI|EF_C=$$N=;nKx#|6eCrTecj)``4g$y+c#7H?Iw_aGeO5qvUPE_F6cN2s zhFp9hqp%s`wJXRZlqGc- zBO@cf3K#>fO6kkGx}5_OO($a(qq$%=jr^)qiw$vvhweBI-6bGL{ad}rUkGLBRRA@Y zy2P>!w13OS!P!LjF*S+34V7Ub27C&2J?cw5{0exLs8Vjns zQ+R>C@87c^>OP>?3QJ)a9HgOIT2G{o2xLlhc>=@X&j2lfZB~2!zLq!~Cz+35AY0!s zoId~*6k(}@ls6gDt`&3yGMF+(^FL%FC>R+PU{)HAgw^Rd>vb3-i;j-Q5VNwf%G%e4 zY8duW49IB5B16_fAejn}1ByV=cF6Y;L=Z9_VuWA_V?nR@?Tz)u5Z&HWXD`@5MQa0h z@zH)p5m8s}&CzZF<-v$%BeU(cwFXjM8wlzrGcE9G7LZePJ0CaUr{l|lB zPn)OpxNoIDPIuZtYDap+V`0ZIC6R;G4hzDW|I6@_+9hW-0y7<963tYv{ z9yr5*O(Yb?u$8+~w&1ZT-@o4oXlch9jT{c$UAh0bLB0>0ivyfL#vpQ{0F8zQPHFK5 zWCnn^{Q$vVID6N1q11#khRUohp5LAf8lRa7P87m!o8t)F2`K@UzzewKr~h@y>FFG# zt->@FI4O-&bb#x?n9!SnL;QR0Wel#at{*f@!r=f2gWB{t@^xRw)gRF{wV9~Jqk=Ku z6T!QGAjkF(lUWb?xc zgC9Z1Wd^>#J1gY@0%btXfAgRc4nCiurD>4mG+%$+@^jwiB9N>lkFxs~5Cu>aps*As*Rj3{J6{fJmqBJBfEXRST52Z=te|B58sFxUn_ zw8#-SOynCi6M^@Op!0IrEfmE#B(0>4l=ip~XDDH83j%c~L@byVx`ddL;ZbeCIPfIQ z#hU;a2{+w=$ixe8Yk|cC^`_e=S8bdWO96(`*Ps6`Gryo9VCoWPb~Qr)3K{@;VAkx` zeXu5i!v}$TNv29!ed%>4;22^mo+{PT_oF3BMvI>2Y2#-E%WEE)s!M*<)j zMBMpzl%pg6H#!lhtU}_d`ryH7*z0UERE~G!wpjt=f|}jOD*{GWPjWyN9J<=S9h5&m z=#qq-&EKDiM7tmy&?Wo(j{ZOU4Jmbq2XiCk&L867VpW*XO#fc<+W_d2^m&2VVUS}q z1nRQ58);nTpW;DW9TnQ z2H;MB8G-dWC&D>`NeZ~QeE zh(9AMmdA3i9%`j(C`zHr0n)^=Fb{h^u&%?1Ca|ss5)(x89x*pAg{1G@`}dPj3Qhs4 zW!1G0%<=}l1Vsqlt;V{#klli|3847nk&R`Wo$|;^?FV^>nRt7=``V7@;f|2#0sfPx zV_VMmZ>2>mrh>08NK|8=$Us2WkM{b-?&|SAM1HHTHV80LFpU29iJE?g{M-ew@+-{D z^E&B5KVw0R0i1(1uzhuSJqexSa<0wH&m&kfIg3084aZ z`-ssch|(aw)6M1p)EPtGh~WOve=Bl}hf{79FdiEKhYKFIT+nfwW1#ljR6))$5F55a zWQ(CDB;RqaFa=g%TPiVyW;lZS`;>#W_Mj!{{pG{B9<=GwAQ1xfP2#K`NN?T7 zx!-g&!7TZZQ4$i$yrHXAJa=f)`Wm^M8c@;RZ4^C1b7^s}fK&`lH(U6qk}@yQP+jQD zWnyAN5}MQjOXy7jVsz%999An*k%F`QgP>Ml? z$q&z8RzU%o2=ZApeRq7#PxpcVI#lG{tIOH z1MUmuDL|Rmy|-eckx3io%TrUoXuY0-TJJBW{D(#^<)l z{F>J~92yR4WjSx&(AU=0t&P`Uz($%oOw6rVKZA*mrkEh$53n!&<;$4j;^OcxJIsnG zpm$aSSbF;#8T^Fo%F}f9&zaHwJ@G<0!Ic(O4UOi=#KiC4@4vhI5^Ptx&A1U`VEYm1 z?~rl43QPF@^(*2ok3xa(Ten~Ulx}ZeHipk-l@{`Q41b9l#`It85bBzK+98pLpPyeN z7gVAPigLIsDk}-8sly@bpMaspc%q950*+6zvsn?~996bpNGuWbPUV~_atxlF;_C5m z8~On9BOWjvApZo|3N9WV9`Xdht1beznjT~W8VkF7dlHP%DS&Vw*&NB4Q_%1Wg?0fn zx`-ix$x@{R0rW=LFGxpG;@Sm6XmPsb3c~#kSGW=bL?B^m%8ZQbU}9p@LcYn)&d%p` zBm@?wK>+30^oE9o)kE)Yz{~trx~jwS@T+-IVu^2OioBuR`F*uPH}~KW$b%Wr?p$3O zb2b5Rh8e&>wT-Q94J3?yx{r6$zp?{6 zfLbDj;EX>y6xIPSt`*aZ07H0G0)F9Cf|H;!fy9)fgAFA>91Y;sT|fo1sJG+ti&N-m zU^x43qe#GUo>oYx;w!S&a22X6v|1DYW<>R89+@$~MXXa`)A@BXcB8-k`$upeK`P4s z*WdV$w}bp!X>r!%6Oqk-UK!abhX32&@PE023bKNQ!son9d2o107bCZecfuU0f8S)p z0TN}&&|$W5bo?nuxvuMQ4r)U`0s|or7!*byybTW$&lekiS`hc-O=LNZSEJ!la0RX0 zX!7ddzJV;IptVK__<_A*hh%2;Uo5f8&) zU&hV-AqP|4xC=|xUF<(UhlhlK-h=OeIhz4BmsbS5mQfSrIbp25H@c~&~z&Xxc?i&orteYV!5> zr{U*O{rAtCFx0*C3VS1U*WE85fZ~v%zq8}hyWgLFuc!T}lV<7aVaMn96oa~ZcbG{~ zx4q8Y_9ggjjPYmBu%D3=`@`z|{YL)(Z~VXUJhGI;T8uc5e$89W61K&~MFK)XABcyu zMay8Uc*CF`r?jlBc3^hj;GWVfWl~=+3&2UtC;_bLLDfZ02*jP!b>! zq~okM#_!i=Pd6=yGK_Lc3G?H@yKiv9P02xBV+&U4IFk- zwFK|aj}HX^r3{A5zyLBllsCxhy@7Cw7+1q7q_tcIL9!Ok4x7V0$3pWyJXCw85)GH- zAPKbk>!7*b$|eZ67t~+?vT>sEp?1I5(E&fO zInYoLh%r$hA%h(i6_r;|;0tO9dz*8<{r&27o@Bo-e^acl`s8QT$E!P@4u=C}XK&A} zweK4=Z96~dfKaRv$Ak0*5L+RDTnM}rVSW~3dL3k%1T-`eAhk0ENqz$a4LxgXUHy1S ziooG%=38h;2s(^RNz%Wk~K17Mgm+g%zJTpt%lnXO%`(yz48J3PE;}&aiWqPqprucz?wz_ z*VEUJe;|Ch?l^CP-q5l#T6y#l1C5oaI5uuKt5$)C3PVzdM z(DD{_UB`qUKkaaSvEvOk-0B+iR|>R=(e$8vgU^@+1V3Md`gg*nF-!6CB)YsL&SI+} zgqR3e;}n!W($EL41*LFcSJr>|VgRS(q8*ZvfxT??4O~HMP$yKcdMpf7fk1Fd62BCs zx4ZuHEj()&7Ic=cmJK}UtFJ*9{T8Ils(2t$1YiT4Hj>2EcV&nBy7bwFMW zEq#eB`3Yc|>KhvRA}ajAJqm;c1ct%kP}III8AQz1ap=;vQqA>Fey9Q42N5(&bMsIn zc!E&!Pd59l#Rmx+hX)6FCT-M}qbATDC*!v>a+J9IJDMc_eNJbya{eO&M?OyU`M*ge zayS@}sOdiOJ$b4G5+7vSWxT{$j0mP{X<6>0UTJvuTko$?UC2jwuy|0F9C6h7TE@$8 zLEKB%AO})=n|T!qqOO98S@l}-8`9aTmw2ye%m}bKO45=9r!ZfU5O+)MU64q@Vf!V4 zhWdCwN%Ax85i9+R!J6hF8kF+$gfkDsb9y;cWk~zWd(conG8x6LT<_KRGTL)dmFYf8 z?0e{pUN=kjL(0kn+Wexv1x_^78#PHAq#OU&)N;VSNaaCS2l=8D!|H)z8?1z_&@oYBVW22bA-Sz>SP)~(yHA%Hgz6F{1 zl)_w%DnifKiL#gAI()i2PN5>BcW`m*Oj)OXEaB&OE3UMqF|+wqx>F^zJfJtE9%Avw zb-iLMplkmt3*Sc!<*FibhBLZIhcwu7eDNYCiurr1n5@o=UJf3oaGTW+XW?hwdQwp; zW=^hY;@tnq1sw%foTB=*=!Na-s{hkUVic|5#Jle<2@9AKM)W=Voaaq4Yu+iM{0vU- zyIBfGkcCv-!1sS}MPmc9aIM3643v?-E3?}p@*<6M{vkg1^)P~@E|JWny}2x^X)egK zrD(IddJ097R^^?Vrfg0wNUGE?P>z8DeeYzoD}^s?H$R*WPM{|%qTs)t>hSo2$rx== zJ&*EN`N$-S+&&YQ{n2T;bk!Q|R4FBV{LhkbmTR=2arD2xhg#gl^Ct_AO-8wfu^K%m z`Y4C~c7pzzSjgaMTuhHZNgNa;hbYm+$Kj1vb~QFEQ>t$b2@3Q*aAKw5xHV4yP0~7! zNh6n((_pbo)op6z*7FcNqNTVgqZs1wD{|K3fME|O?8LR!>UV?XF1v*JrlwbNYPTr-A;qCoI`Hn)y2e&S5+u`%p zVUYwg-IJsn*tvs3U2c`$`8A_0>0TQ)Djb8dg)~l=-)h;&b)Zn~H;JC!P|4`!Af?_N z5BRvLFXI)5y-IT75v#olVHoqhM|QShn~ayyi+VW%jh;p|^Qp~oQbln~P)w!SKS&iN z6=xrf9PQg3?CT3b!y#b)CZQBJqmeuF*3wo?XHPHHfA*9H^5g2?58J2_VxeZ)f+g~9 z34U^sc|hv?JRM&UYvAB4Z_1W$Si!n-{d;+04`Pes4)PV22RwFU`4_Rmaxg0e*TJj>*SAP7FJOCZlmD9Lzwhb z7zy}_x za&PKT+QWHZCU|hg`l~ZvJ&s0DbcbrZ-%vVqojTi=@k;1H=d zAPm`%w|;54NEsQYc7I0T>cWmVGm$vmRq@9ePO4J0BomG_q9`$0aq5uu!f1Y0%QTm+ zE^L(YOJdvyQBI+CJa~#PmsMYXYi?QfRb}ha$fyhzw4gc~n!J&YM^@xCnfFG9>Q-8@ za@O4@+3X!o;nG!J4qXzx!~P2dF^_CbndQ>Y0PBA7sMv`m9sSdz#{#;V3sd+$$~dmF zgl9g^n?vLX?kG{5!#B2tOK_x=$xd@3n^M_BrP2!GtWO>n@!fsb&CJ3kEw5z#&EdKN z+e}8EuUtyPYOu{O@9t4TRAkKJxmi=Rk>)OJgNyHAkzT!mx7{XM_hBn%)p}TN=40=d zmyN}$bTT?_-rxIpcCf@~3B=U2f@&87G;+^25aDjJ9gdRD1m@3_g0{n;VMRG9idOV0 zK?u=OAc-ZFw%*F(8B~*O*4ZM}@}FjYaZG7Wcqs1=m#-(RIjyhzs~f!8HYszwcKX~Y zjtcI#tiL0Os*W`0(io8sDtG6_5v$S7Q_SBcM7-fnBCk9rBO^f1SNBV;P<~GGW8z`< z66KBN>`krcByyDh?eacFL8klt{D&naH{W0HW?2f%pc<~!Gc?DS&X#rxS=J#*s9cb^ z{6g$XEZuebTkbxwB6o&8HPfU-YfYj=R@ZsQ0?FZ#JB4l4QAq^x+!rUDXWpPCI%A`> zR0j)2u0Lf{fM5H^X*m(i%uBauP5f*~FJS12d6_Isu`*lMr=v!g2sXq|n?7+8w`y`w zMbdPlU`+RIooc{K*1!O}I^&&)QVWZDdYaN{bPU$6TM-Xy9hs6J$R6<>U&3OoupD1n z%9hc(_ePmH^3@fi?UEunS=kx@CEN22W&A2Tbt1T*Ty?FSZ@e}4`TLJ$bI))>&lf(+ zn1#|QRrJeAqUg@~=()2+!Yw8r3FE9%A&Hi%U&UFZ`ok=P6 zIuwG`rXZWHGArSwckkCRmuI@g&pL zKAgqi)o1_M?d0+{H88r~gDkU&HQyAnY8x9H@25}Cjis#C_Vk=bMJ8mA48EAXPEU`~ zmLw`8E4yUTY;0|fA1o!&7=;Fdrr6L3XByLAm9|&>+T1+BjVJF6#n%CJ&x@QyJFPF$ z^)hS?J)RxND@^edLtQh?7`Yl4Iq#C|Iae1Zo8X)GFp~6~j`2gXfSNI4iF_L=6IAuQ z&*M$rVvfG)Lf-_j;KvD?m*5jNw;7CmiQ1*)ZEe{Xm&{l0C<_V-qTUt--;un+#frzm_=AlxiS|D}ePVyB0bzzZ`*LBWe_Yik17WZCCtOP&%eTqZfw@m7Ui$a9Zm zZEesnhd}i_ZXK$ek&~16f3jyXFfahPj*yC~)%jL^ zwSl1_8ftlY8Tk{dk+KGBF~0-Sm~mPSBM$QT1aoU%k#~g-HjJ-smydFFBpDw++A^`c z93z*~s}dbQtMu-!M7CPrAT>E^d}Sp9{(VhHR9S7Wl%*(BaDxF-tQYN-O9d6pLn50f z#e!lmG`4IOozJ?gfh5J9tmYf*2=;;kItzVlXjL zTpJlg>R&W8G#fj+TF`rh1Xcs|uAJ5V06sH~ZZ;~~!lFV62?-&j?w$SnI&m@2bBmeW zQR7P`V*$#ys6e1M{7ok&S3-oDBHMMUyYF~tmGbCHTk!(CTg!?O}~HG@Js^%vfnK&5hX>M^5A-is4*v3LtGk z4U-z4YgACW&a@?MP+`&4i&yN+LNfbwbGVpkV%k zYCwt+M$5@bj87wCB%kB&=O@M(O$XfjnI@bROfJ`8G1_X#*+ZZv*q}O-Zv&NnEikh* zvaVY}|J!zd%aWx|1w$^K2oT<)GAsu+3ssb*YYXou{B=5P&@_ z#}d=IgXO3G37RJQpdSjAlSiP$wcB=f9MmEnK|m{U(t&~sN+xj(LXFe@5@@=*x&Q)x z+yHtw!TIwy`>@q6!A9cY;dy&Mm)g6Nh!Vmqz|=xA(l`vs&GI3kp|*f`Yz05CynG=P zY>flKGz^l%3*lIWmjlEaqx88<+fmT^n-Rp-Q30HHVI5c!#YOMG0ARJcvt~H_$v!YC zIjEttF!I`vK(TBz<192d&-qy}NJvN^%MTL^>*K}~U*8n9EJ9M!Mnj`|0@#-D<_O8i znqI^chc+Pk%X?c(E%pw`R;aiP;Q=_p)j(d~!Sm-u_d&rWpYeaPqI_)-=9}_Ckot4o zcudAGUMl&X*u=zeAQICF3dXG{yGS+?vXcZ$p%-Z=u+pE-Cjva@3$RZ3Hw1tj;_mJa zb=!-_ytiVcWMm|-U8rkoQ(E9=VKS-Y!l!&I<@+-YxOs?WQ#+26SZSS!b5_|X1Ip)40z^jVBZbBH^ev=B6L7FeI3$t$cMogEO~Ax3QQbFkR*EFe5u6)sU&5%g}saI13Zov%ti78Tu; zl|5Y)S!4rPR2@KdgO4?>{d|2f@S_MNgM)&qq3ni$#)s5V5PN~}^Q5*`A~BU7lVF1i#eV(y z;*}ntS!ZX~rI|6KX$#o7z@FKKoz?*mL4k4W4qi=ahpiOOJ4=m1Q=jp1JwRdCA=IE? zXwkGUC*gZvd_uy+++6VNtmzHhH!;JPrj_nIrV|p@LO}F61w9$~X>|1{ zfJszxb%YagVI%YGY7MpEM};*1w1Woiz0%vD>k07zSm}>a-v-e=(#Tz4$xlTV1*|?F zeX=|LH*sg-G48NVfl=Uq_1nW31VVYNLu_ykLuA#E!k7PpQL_4h>K z7H8)A+mDD;A3uKllZ_ZY5Li~JfO{d=p$vf)46^k~KYfZR4`bw4I4#!_4Jwqw!^3B2 zZlJ`V4MRyuNgLgKn}nQB3&K1lvQxmc)7;b64Wdne{@WIJZUBs76jc1(U3izd%mb3R z{#er4?{LF`Bg1O-w>c2Vk)XooI8OqRDdd?jxAW2deMZJ941Vu&I z?2sK1N>x=g&v`{h$YZCyU!M>|z;lo5#dY0qAo0q@o`awT*9L#(HUT+1CbUt3ZPA^r zNeYo8hRthxSsQUErRE^efrG|Ic`}b=B&{#ZxayI{vpzKk%AR;t@DrJ#ANOKSo-@dI zn=Q$H4t;V8E>jeAq7lgCWj_oHp<`oXS87N+8>GK}9Uqn+Yn3vgmzNiyx9JPj z6UFSRFHBm%Qj*D3OxWD>BnfSReJo{RaZNkvK@fNp!Q#B9ar}TD781;Tuv;nDCj$}= z`R1mM2;?ZS*L9^~6#-vguH$FBNJ3!whJm6*M z`vV>Q~MUGfTBseFt3zWY}l^ zeIJp-Ig3L8uL-EBbCnR-ysrmSw%rQ*OSGQ7r+=L`;hONBs_TdRCILo zO&uVap#gR;Zej5O9y3;oGBZLBH~>zOTdtv@AqFh8`i6!M)J*-44+3bOP}w20A4H8F zxK1I+tOo2@c=Po@0A|s0?sLqAQ2)6b1UVs$xOU%{#k&%(g6J^ly zkc3O^cm20jm|f%n%{Z6=I39-?0$9;m;-c8>l@K&6hD)Oo57eU7uLTqo*vGIreYgjq z&GF6$9~c`*g%Z#sutrc}^0@}fT_tEHoFEpEFG3D_bLwI@HINOyPK}Ry1N-6IYw+5=9rxpY$vp!?%zrY7}$H+4mX#kvTNIkQ-0IlZ4jW%(wrIus4Cq zd2QRq@03iXgfyZ#NduJzks?G%Dyh(@nFgYXC?ySsMk%F1Drp{R(xiFPOrs{1q6sPA zaoO*;{{Qu@Z>{fL`+e54pKaHD|9;nX4##nv$4NAPuCA`6{w||w41_?eAJZq$I`lTH zI@hD%;1&_t2vrU~FEYWHG`FPkXk7TF_4lARNt_>IH~(7PVrQ~E#+K(O?JzWu-Srbi`MctT>~ z9?p!XO8i}o#3P!TTTtR!E?2x7@h*sqEAL;50raPh=UjoaC2lu(1W`XUSGvyd(n(&X z55qrnzenz!ThSH@CX@~Wy85t4WxH~q^d1orFU1#PzQ#RIcm)Jv)6y7G%eAQ}>b0&g z`&NK#yzHX3ceqHZ!_8oAyDj}|B#7TvP1X`d_QZ+AAB#tMnO@-LNIU)D1$*m-K*w{` z{Nn3z{flSAr_UQ0L^n*IKf0tM5^9X2IfIT5ovY=X&Yn9m+x5^!?njN0G{3wN_civ| z!nJM@HWF_V!w;d~8mSKUAb*iz8o< zm#1SE%B`oTw=n;sojQ84fGqDKj5G{TQgrR!=w`1+&};vti)n&<>LQj0czm~R9mMt0 z7g?<|}(p&U9=!B|i;mKXL0) zDmVCaiaRd5$>q!8_31XdMD+9ANq>s0)TZ*}{J$qBC?X=&GaUpPdL*}o?SEL_uI&jU zF_YI_w2DPh9n8$mdK}?Slw9fxZy1)GM_YNEm#MR}bL`L3xvW$cufrI;fQe62lGg6C z_pICNraGV04(DB;U|vN{4JD5D@Vw-35ai=Dr%#{usJwh-sU|`zE7jiKzJF-Q19B$K z362#ssD!Hnldi0;{^$<($Tm*B-?c!+hTT$rry7}=C1ldLk0|#xG`#sVPk{^rFVXhb z57SS@w~M>&5H0^t4tW&=!$ZrfdOH9&AlCtNLPwqkGWWMjxLo~aE}tVtGj3-bZ|hC? zoSEIyR$J@a;rwVMEp=*g@=;XOrQJnmy?Xng`=?L@1QK-9{|d#hPF5kg0_eJIXRXKd zLVYbDGcd41H;%5Z8`3utMA`)aDE8m8gmm#tZleH*U`JS5^bCE}H?Qo5Ea+AH;xJgw zz1NOoQU?#S4L21t3_)^Iil$3K5)xa8yhBGg=c_|v5uT&-@3~!gz7UxtUEhBF#*Hd; zbLd13W{Okur*=DNHy8bwnNh$jb$0G&2wBM!g#5@HO_QpU9F7y zz)huRV)pgV5Iw^ExH-pWqr^H_V2Hhkc>0zKAjc3C5*lnUUYp)toa_pmW)w07dp>@<_JK~BWZH4w zjIYIyxy)~Ew*2kp^nGx({)G!xN9EP6WI{VM;ZGrS;bn|l{0W7ss;XEdt;)5{gJG@d z@vM|WXcgN6s={ucbLc)d(x9_cgKULZcHiGWJE@o&rz%nHp}JDqSDI_6*X_{g5Lqp) zVLo!>Qh^A>d2n_qLp_^yk)%uX(*0k)&_vIza#>oK>MU}5+n$l4m#d7AP{TP^@yXnB zxG6Myj&>5d=s`I;-}pqnV^vH|ug~u*QcBag#<6_bX2*flmlEsD6)iUu(DMIQIexqZ zOM8f(2-Yk!oEydO0HYvPX!5Z~$~Zujo&5Y>kg^FPi{f)jibOreh*7Z0(ZVxzr!ahg z?BNa7n%*ao}zF+U1y$i3br=U8i{FFKFc~jM`Z|r1V@t!Fx#jQzt1u7z}h@;6VJ zffcX@nYA|absx&E6315!|Loh^xjHx6pVPQJVMJuDA zCQFh;_TTz*GeYi0!0ZqVmrg;dy&XdvW$&D3iKq&7Cs7CygPFIVAU?_NJ=fYDi#an| z(eA6K0B1qKir1P64ERd#yLG^2p#F@SwiRx`2&b4q38kn^Y=c=tL@CaMm~o|_ZgY0q zBcuxQk5D~)=UhJxEmAsG3t^&w{@}rbuHMFW=uxk2OwwjYmT2+t({(4$si~=nt;251 zS{j1603Nm@3WM9{jO6hE9)*Nleze`0nwHMp!$ZmcNksV!N-Z&y50BWQ*f}^k2d1X# z51K?5Xa2e$5I{RqOk;nYV6(!^JckbTF&b{t*aguLb^IPNW)c1S_@Lat?En;30jF)o z->&a~y!$REbNBDdO4TQu555{aj|M0MB%-Fh1=syBDWbOh&IJ+wR<9&URV@OOr z;h|%V(9u}ZR=nD!I&ncZAju46k*l|X6fe^{&STj$(7d_flMv(;3I8biotl(dRpF?Y z@y1w);-7nzo~ABPQ|Krrlj$>30O#nxb)<&?`r1XY&HwQVXql`}y#8wM=vawq-Xnz} zKGhIvZKR}SXIEg_^KnFkAC4h(Z=3{&HG2wF?CYc~25G(JT^4rk-@gr1Bf*nV-V3=k zLb1z1SEi$1`w;jXfj;(M?foLRB~xMlcpD6++@K?SI4xGUif^M2SCiclx})5W6;iY_ zJt~+cj&_u^Pvd}eW|*Gz&yBTMT`xw5tYhr{;eP&J&Dsu_|1S6a9l%+q?Yf9k9WS(PtrSnVHjh@?T!?`Cv)Z$&} zTnlWFG;i8TObVvOgqirF|5kyFgvQhxFf7_(4q!G)(L=}C%fy0oxIM?d3=ZCf%@+5r zUF$FwGYG_cC+FTE@x=HI_!W;U&oouB_0VMR)59lUo9pM*5<@`xYPeYcUOG~YGbC>YL(axyYYEtuU z%qNvNR$%_m)0sf3A~tgXnV@rF)KJ15E=QcDEBcH-sL1`8U|bS;5y40~rS~>$oB2|` z+2iV4lt+-;DjFKTD9u%!3q`U-Ekm|h}XD~K?6Ec;OsWC8uC)kyeO_Y)2F^9>xZJRef z9J}jMoIUO%@Bo1ShHC`&Z%Nnj`|#m7&~UbEZ$01nWmtL=SSPeLQSaMrLR4VrSb~u& zaZ~;3ReUZ<4LA|FC)V#@=L8<-y~d&C0f>mW?%|JtiaXzwMU&7cr6_y}5FKW0{4%ES z#Y$2AZWFy2=R&=k5mzN$hYrsXWq~rp^?pCpAE-#oh(cRvncXeO@R$+}PZ0A@dFf|z zRPWOQ_?$oj|M`0&6~tB54Z+KIuCyd4pojBj*g44! zY9voaBT!7Tn)X0YOpvI`hdWMtJUTCZPw~#rpEq%;*i$&#PzC0Fp5eF}z6o2jh8So)#68mGlzO@)8j`0^d<9pf;RnoH^ zT`UGQQv{bM>fPtwe8I~nyZ!A(0-vEf%b8I&X4GjLE)Ugx<76g*!mCtS* z0^Ai9^&emG`}16XWi->?-Ln23A!$H&@(DV{XeSShj9mWs#2zDZQr$|biV$rD?lq6* z7Xj?LT{a*Ozy9Do#lux+SW$l<+|YU^#h{5prxT^y`gdFKYHm->dm4%5;LGg5N8uH; zeHk0OkGe1xS3%i@LDl{lQ_~;R0$wQ6(d!v!ErJ~3MAfO>F>(Mio%=DYFCQDAIt-=+ zn&;SHM{dvX0w(5c_mP-#!+G3Y-+JFd03)<0Qb@@QByNV2DUqA7LQ_2*uco2pMIXk1DC=R!BGhA~2#A0X->?Al z3y+|qqm$^)@ww%AUMH*MbRfnqbZXj@|BtJM;QySA(cSR1=yd;ST)%@{yh zNc`YCd=;y=i{O@H6(svV0v2&iMZqB(s&LG3zHrkxdQELGBR%~NlEmcH6p1EARQMvq z5(O+f;nt>Yk8cIj6)2wvT=GH(7%1PUy2ZVOI`ylGexg2=Px<>TYx`;lb)yLSh^eEAag zh05c<<^(tE)kX>N^_DTmbr3GEd0bR>bv*#7h!0^}g@yf4PJ5-MiXyUA-C((FinUE- zXewyeS$v5Y;#TmLGxTv}q(5ZJF=S5Y+O;o;!03bBF(1}+a7i@jG&Jy^Q0wmQjsf(le0#_HtxQZtPrmS~|9(UzDE|gd zNybkWUrzxmqY8U@PmVYUZh*omA>(zqAjzwu7vH%T7RvumOh+<=Liy}|Qw?J#AI~T9 zZ3+zKd4hI2@-N?sXVQfKIDU;|lhH41$b1|gz8*Iqwm1~M6sjF6L?cM8?gP64DiebV zTYmN}j|^GRxRmzxZb!q%@6_1+Ixmk!Bk`k2wIcpFMiL03ib(Jzh<^s^b;{q9E9~sS z{=Ah()|tQW#^Nr>D;WTcQ_qF{yjInO-B zhXx+T`Kic_9jK#3HT`2R-IPjnh8{qn2LDH)g3(vhVEEERS)FCm!<<^U5MHgPsIjWx zD^3S;Qa^&(4{sy%0R(@RU+pVKnMREWi`9?^Wv5<~C?`agHBP=LV=OFx* z@%7ej*q{WSPu%sVZC+lUW?m64hne2XcOn8>3A+NLrZ`PMl*=b^uU1v1}vOvlEMUiCqg<~Kx@_h2i&Ln1J7eGd`|@pT%2L!1U=)UZUGq? zR($F9qybk@A=X$7gkG%@RYAHz>D~2uZ}!r8pk&~}Y$pDcl>NszekLX+%2vjX!o#?2=0NEN{}4KqUBnOzY94fZo7b1LK!|+GG zF#0?Edjzl^>&g@W_iXl2nhG!7d)Jx(Ir}ebbul!8_I(J0r^EbFmlkFQl-|L*Z~*vcPOThrKBh2pXqX&b%5scDDvsz7xG zgH>XS8Mw+1KwL)B?beMubu=|SG2-d{MOo?0R;!$OpELZ{PPp`FPjeor1vGI{)A?hQnWA*+2{Px2~?P zvA*I%xPs>lEvj(?U*I!6@vz{GtbUA ze^DGw1=&{!nVvuXfp(sJz4+15n?~jsfTajdeS4#w>>ob43)@^_BejDpf0sS&cjwL> z^PZeN7@b>#&xNE#m|g-1WNqJrKZ*JKJ7l;7*aSAzKRYuRZ*qD*jN9(!VEH-_#7E6y zDiNgGx?MgGjQnAJCX(%$dMxB`c+7nh1}O)6+Z)i~u3Lq|oS z;6WTM0_!j7B4%O7=^Zlq3~;X9Y5P}lN-?mFKm+ALn2KH4E+FK9$L%kfvCxu_jT=}0 zbO%~Vw7`d7sQ2S%5yj|IHyHFsIH;)cgI)ZYIXL{F-N8g~6APVk+=-3aSr0QaGf8N+ zFB+nd>~Q4Bk(~grz&#o62oDVkGJpXZ7|;@UPNg_J(|l5T4P1l}=c8qu?wj!LCWTf* zW_tQz@H(5=*d9Rj!Xf8KhyF=noVpqb5r*ZPhd1)Fvaqaup?)7vC~%&IKbzY0$XZI; z=?wqiNgp4dQi2WYK7(U}5z1ed{a5t!C4Gn_@6S)W*C1{X4a`QZ)Se@CR>F0}Xl92J zGB>eeC}7rlEWX(rClrt|nXAvb$PV@v#!PoHRT-(uyL-!eR00?urZ!4vA@Urwe`%NN z*-%4K1l@jSTpQN_e7O-|v?7?1(xF?k_510IRISV=Y39(A0tRHvaY<4}f`{dm56}l9 zah2_;udlCoq2xV-Y0V)_dKr$wtb?7*F5RBYtPnkZP-9>bqZI#LkxS5;By|PSclw?vRGiLGg)?skWk5f*2G-u!5y)ys}qw&dyFNy|+W0;eSu_0!h`_?xAVF&R=ldc|x z@66WXmEXHTcy&3Yr>7HM8$}9L|7`|-JmQ!8*8Fcp05rRani@(%Mq+b6A5a7fH|1n! zw^`ZxB+0^#0MftO69-SmYY1Z*5wh%0sJeCw-@j^R=8ev70BJi9W4zql+|qWU`gx4N zo9~?B-)H_wBXJ7?k#})%@!?#nDG48OrN8R9!G-I_d)z zS(vAXt)wr+oCDC6Ma-T9N&s^(Ppk-i)xx}?T*5EdUG3yaC18SCPZ}`_ScVQi9iA`n z`iT8xl8W8}R7yIW4#}t>81sk@9D6@N3|vs4^`}==RM;_Tcrw-i}&xP`_*g%_^chfiOS`hq3Dy@pZ^Bt-W@or=w#Vcv$C9 z2_CH~F*?9)(441cuIwulBbNj(wXy+EfD^1h^@)Sj0OqNQl?_oXIn?hpt=ZD>+Z3At z`tdm<+P{~l5e{BcsFu@D9-uVhyE3Y`e`Aq95wK;d&7!l;mEk<$`qDXGgl{D6Mvf0s zHO}Wz6F@i$(axD!2BrJ7N_8!0N?0j%_)q_pi}v{uC)Cs~f3eRm&2^g_J5$--9zs>J z(t+zVf}hsb`%)#|2%@V7r1l2KOXz-IMn{*pH?w<-FXqbJfAGKvU*vL=&B(~eUflEH zKZQ0nHc!zYhzkWLeVP~Q=~$fZGec~W>A7xeYk;23qUk3@d0}v$*|+y;U38D+ey;;L zLqkJPfkx~De)h)6z?9erZCXncQS3wvf`A!mx;#U0sRQRC-9uAC&{bR{T3`9yyM2iV z&(jUtM0A#&${)c%XyqX+=q_1i`G+pvz=V4%Dwc-6p0YBJuHxIb{d-4IFrF%Oo|o>K z|Nh-@1+Xcb&7vvZnjvhZc6(fDRX^oq{p^RX@7U?t8{%`n`q;D<+*oDod0j+sEe#Qs zbjGD{cS>$LJKHxX;VGIyf1MQ~4_QE_{jdr@*xt&hiY=D4S21pFLVoB7!gSlO--u(i z`G*{?T{D3d-;Rd81q*FBWHMD=>VhWV)uU(SKU!uF9DKC8f2dO|*L&~TC8?>at3rKO`!TQmAk?~u(zX*pc66TDf%a)dKq3tYm$t}$} z#|~vWDgS7D{wc6f#o`sE%|iD?+8ko}FDX>W1g96A17ny;PrgF~N5>-OK6^`Y9<}J7 z`HoA1!om`rQYn#;)MEPA1HqQf`WdcF1u>G{V-KS_Pl%4~bmujLHH8S7^BsM}`_j@o zk;U+#Ly1*ku%}Go`l}7=*VAPbb`Ro59>dBxx*Tk_;3fWCtzQ#h6i%`dj8Fa9Y~EtW zP9A^~R3@bjCjk+Y*@J<8b**?-mf#od7M?mO=vItz*C*%j8Rmq7o8cF#U+5g%diUGR zKp8m1y!L(v#DOxzgnp!oypj|CIJiI(CWLQ8LNn8@ch&A&C^DoUlC*ZgXB581IOEnE zECAC(cQww@8SwQkdz8B17yTQS2X3!$5N1(s3Yivn9!OD2iw(SRo{<{4YaBq~yV(Xg z-C9hP1Fs%}od9j&BL82)L?mh^Wd59TJA;l{aQc&N$dJs_{Fko zp3lwCA4O#Y(ewFbhQl9D0;`||@qY%iau2#34{Nf{o7h<@20! zIs-W-aI0eh5*${KhxQJSfc_y1In<1nB5(BW%Khl25+5^h)JP19Wr~Uwg;U=71WUB2VQx83X!wL+%^y0gq;&rGdoG4YB^{GKDZCjeMAn^LwyXzeiBtzmY+ z1Db|@U?+EDV`C5ZDnVR72!y8!DYMfFW%R$_s$0@Z~$K198`MEC8k&Vc}K} zoUrw*8MuAj?7K&~HxV5toF_G%mg3+cOb0@%rAwXcScr)G3rsQj(}q)mTZmL4g1HsP zrM+OK=|F$eftj6Nh}Nq?H%9ui6Lo1rvEWDzMJ|%>Of!srU~$>7Q^5ITkh9p*HpT;y~R?bawE$2 z2zOwHl{m-JpQ$^b?F1^&A@=?Ufo28NBKohLxCLps9qh*0Y(kluNG(I28v4D2${t;i zZRD-~tE&)1lv&?x6D)M<%d*&6dviOxxd29l0yQoy}UzbCHiYlgS98!L5D><~! z(vFiXKpwlCaMVZiqp0FL2JhXwmxsb`eic9MR`hyMf0)y+JqWX~g?FM%KCp)vv0V0o zR+Zo1;JP+g2jooxyXqMno$>XN z9wrxP>Dl7uhY7`mfZAN-=7MpA($?0^L^FXm;{V;wuaa?{xvFnlMoJlWB&eqdOlSe) z@dgIzpgeyIvOk>tssG+bg1VsPpPfSp(h=Uw&!0ah&op5qR~#~Q;@uZGlaKr+uT8jBx_Hgte|6uCP2=S+Yb z1(}mXWit$g>B4U`lU=t-a7HptUC3dRvc6Bg6jSiN%XR&{&-Ah_S3_BbTm2LWz&`;l zbRQ8;!?bbRhYueN68@qX zOjVZ8BE1Hx)QnchwT6P0!8qL<4n9Yxeb>*{;?0sIxR~DCf889rD=OkEJ3Qd;Ne!%{ zYaFYOyju-mlDh#c{v0Pit?~T2F<5;P;}I5YzJLL!0GL^{=-cpmU)%m~$0dM^W$57V z5^o>CGX%xM+)LN+#_b30lPyeGKmj~w9g)&Ns05baWb$=)nbrg8Z%$;Gq1hq+oO0l+ z!Wzl{jGD!!r;fAO~jcVweIM8pO_cv~Z z_hX@S$}xP}f7iC{3_C(H)bkeiXDnAM9r!>ZCh^Z#larMlL{}@(6)g;(9?};k?4Bpz z8QLtA>Zm>|5g{%5#daU-D9J|oyIisI7QMJ+(aKn7I*;RIXK8-rc0@QV)Sv|Ayqp5~ zLwu5|wuy^eU|`<5el;qAGBuZ*H*IOdli%Rd6P72EXp?0YFJMeg8c}>;Z0tkgz!E6@ zKYf3fcgW#f64EeNc{-LgO>LI(Qlh5)c3E?zd*>Mw1tQ~d{h6j68;no0_3z4(sN6DQ z<^)G{nh@aE8Q5i}wG#cppIcVWdx;9{(4k`G>7#2Y?&(IT_>3uHNTQ~rTeof72TI}; zviNTOMY8IRB8EXfvr$~$`=IUXAcU)Hc#BE$Kv*Q6&y9=ov;dPoG&*{2s&l%pxRGK2 znCsG%&Dcqi(0xcMM#M0gBmv!?`zT_d#$u(=Q7X}}`1tznvyJ;rRur(WpN(1Pxsq-tVMXA}_;(J2l6 zqm$grJ@>dCP5~zu2bg4pH=Ufl0wp=?9fgvWWsn+;sHfVc-y{Kst%Ga3bk!V?39X?i z-6f}Tb+WV%1+zG6$&~ir(BfP470HiI=b{p!K9(4T3SUg|#UpM%~ z=)$^L_w!vA-yH633>)ig>BD(+%BRiw%G~t-ayCoou|j{v$ja)2gPb{wmN{GZ@QHX0 z5?@gGFwe$)z?cJu+SaG+?zKKuMNvs99Y~?0tbo88XntcwCipc1Ly}Q{ zkkyw>ArXNzfs98j`_mt3HIAMvEF<`GNJypHUu1ULiSxUeeZRxAExi?j{fN?5 zNqJUF4dq9S^pHrRp<<_JlW0J`maNUH_ICSi1hp(T*EITU6j8?Q%N23n9(Xm`;11$xrE3!5FCPJbv&*IluAgAD( z<~(#)W9CH}vzN*5E@H5=_tuh)V>hhG*rlx9)j#xESOP6$f!DR1+hnIW>X+D>9^eL* z@FOs%84$vNmLjGd(MTk{`-Yl+)7GsOYNF6?0twr1a#IV$;9X8RN81819II?y>gp6& z6wexXYQE-=*GFkwx2uZ@ebUvUUzG;wT2&+$CsyUbB2GWwKE9?0&b$?fD>B*KKG$E6 zxgf+0LeZFA>|$cE>CO^2PviuM2>99l$t7}TAsSvq0bYBMtQuv_dK9Jx4vuogrXSwW z{<9_H7}|Mt#wjxt-K#6QugPnWJ`($N#{zWCWNsq0W465C#nF zhnw!TjKQ~vU=l&R9i*$l%l1R+EqEzL@iHV*B#ad-EWU-B2o-{H=r~do`f{_TMYkdU z{hbrk6e6bV>~n^Am#jO}y*|7yabwx?@30sDpWk2W$2~6R|9^IW>Z8BUoH^53eESfz z@ozdxj{WEC*U?R@?mTNf$ySbnN+|fjS=)=3z_AmH#?Eob4#;e%3wly7 zSV8*%J0Y!CYKtS$&?1l+!q>G;#dBOEaM@;^1cP{zGaVVaix7B=FW}+reat_APib-j z+tjGQD;xv4OX&yFbqqG@!B^fuPi%+QytAigwtZj87CI;ET+6lxDP!cg4 zmg9uF9I^5HEj7!}1(GBi_Xc6}&sL&n`pDewts|^_>sC=))G<2L+N%*dpjo*P96;q| zD28#2oa}sv6##?Q8R-*8>KMdG!Ckuyhu&d1Yw)dD2_JWePkRqF))KI-$80|@<{Y#W zHHqOJ&4s?ujCEspU#zZ}z0jEa{B|4k*;ofgMIpG6Ii}HizCJNzffMrkzP`Tb=?iDu zS9%hEeS-%p8W$bsP- zCJN3(J~Z8Q%FbGxjyhYnUM9D<;Q9u*5*Q@l=-HsYXaD1J$YsHrurwHFrl_#rz*@G| z3ggRv0Yc#3tUhAF{ya-e2fa5)!zxD$cs zx2QpH{U|C1%_D|J&~8HUI*lQ& zh`_+u>#j+d*JfrWw8^wY&^Ml^r)a@@;st?(wsQ`|`P>LfU|)^PG>#haPLXrNs6Nhz z3fLRM%HromRxw?I%mKXu&WwKx_c@4D8X5wY--LBTGHA3z~t(=oI<=PYLGbLx9fa9|)=lN19qlW2P>w$7Gd zv<*QkDlc7#+H1PLCu{;JD`s+OKv2^|{)y)9fYT4pvr;h!T>C`B_Nn?DL=3}Y;s!ZD zobxYTMQH|(-y*2k4nzfpMM_81$nZ3b+XHRNH?gyC0KJ$ga$2P-WaVeUp`5S94l?e9dL zNgHA@?WeHQ9V)Q4NESTwG+>0dV@Id=azM1Sk0}=iAM|gE*0~H>y6jZMG&m6a^e9a0 zg$@Px*#kB7Sa#7!As>@ZTkeY3$8?x<9))6FGx2Q)q!-fr%Lt4LYY?4SX}W4KrQ{hM z;}Zqzi#|p_{s}RhfW;~XtkSw??{lF{*$D>^NWPo}4x_k}-cw0NPmt|s?ejlbFDw!Q z5{zVE;hA5>f0qtHRC9LPn>Bn*6=X+~4JTgy@s}{MSmj_$-%&!W+K0}f4nWZhb6Q5< z3hieflGY;x;b}+>>|$Iy19Cbe6+GEjw|YG-%*zpA-9_KL_mWU>4B!fUDtC232^|JS zB}0}<^Q9v&GKapax3`xzxiKjf)iLH3VnMo)KL}kXl2=y;IzYnZjxBd3JB!@HL*K;8 zTHU_04=^8@b0M;JKt-r5;R}HIyyV_B5)LnaPCfAVhcxsErdIlaIYV7L2=&b51kR99 z?u`drBxaF~C@%Dbe#87T%(AmJFk}rFXSaOi>t7f1UXthsG#dnAXLq*|u6cy_FflJb z{Oxfq=5PP3L77HT?;iV#n?R^A!h-*vvw-`Idq;*>@C)OUw|~!Ri_s>pB%_M`Djf1W zB7%-Ge31j^19)IY?fBboh#PjmFaLm$BhCC=Kr1X%@IYiQL66Lz@7^p?nwy!~IzhBa z3CuyzTjB6u`Qe*VBXPBt1Hi4Z-UGC$8_~uYpMD+O%qz^|B|c`h+2;t}&*j`=%OvaC zvu=lakPl@%T-{JEucfD#)H&tV-9fzI(E=b&VWx*X%EBF&&9Agu?Nx}47yGyZumIFt zPH$fukU2FO9HT?7KdJ@rGnX)_3As@k!!%GWPoSEYrk}H7I$k}w4VZ+s%XaZNI1U>C zMgx%`jRs&|*9=wl@l&Tv5=d_cadt*ZRK4MC%((&^@S0lD#iRrz6l2R`N`bHU&Ru=> zBJ69YR8`{@LBu{`CLihBv8YfWHN1KAW=T&^kNJrsC!aihx(*Q##s!Fg$e|g9Z{R`z zrM=(p6=)35lNt7%m`(VrPf7ZH(t!QiENULl?bpbvk5HUmE^z2=5bZF?d zp3TZ|V?}p?0Xfr4f3ct;`Hxt1(&!-Be`UQ&7J@9m0_EV{h~5=VA{1s9Q{nSc(^-50 zxd7iC0zAxzmf|Yb6=V&7ifg3rP~PCOsWO^SN2r%MQkBprUWqyARQ zqUR+IjrXi4WZ+DQxo{YwA`gN>1RNU#r7Njii>m0H1(>F&t$luc5Rhvl6l8+Gh#=0-;OSI3C0h`dsIf z3CO!~AK3)!9-JSNOgqKzahx~ii^FQtx88nc#CRM45^PEu{H-CZg=dpX1ZEoB!tHlq zJ_Hv6A>!S;;*{*dOojFnV!l=xA0SaWG{yKQ7mqn zY>memg&s@C>N7m@yd9`kfD=U=bkj{v?k7wk)N=jA4}!>+e){+wgEJiW6(QZ*v71N_u{E6ujWTCX8i^Kyddmsror;21`DARl73H3+QXb6M9-&f z57i5qfTvl#npIqOLL->w8d?Ig@_ERN`>`caLfc#t0;Rgd4j1%f5z=;wsn@ZHnT*Y4 zTBu(BmgKDV8^Sl4d7Ik+7c+}qkc|r(u2o6M+btE|Zuj z3FF;p6bWAo(rFMOB4l8d5bBYs(|6$UNHEzjMjbzYK{8g#3i)+2P`9y;)E&h80?&LC zIDAE9sm^8vs(w@hCHQyMi*LC}T=~QT7<%YWXn43~Mx)+lv=GznjZl470(a;dM~sQc zEtQ&+gu()tzh~qMlJ8sr3)W53W4%33CVNGXz7}8CSsk4ZA<9(lNB~x!JHy%0`!$sL zAs-r{Rt$av9U)=Ivn<$4+~_6W?!Uk%};d&$kyuI z5X3Nt;#Zns3{*BOuU0``gh`2yNpG55IbtEjgC6lv=k|@JJ8A%#8o;rT-!~qUMiH{* z;a{{c5sYI%CRs9cUi-Y1&_OK2xP>%zE1-(C_&^)mnc~ zLxZ5a?CQm|WlLO#yCtT+0^O}(a)kk&t94CuU?v=BgNaiWgnGL#oaTO9(%#KMW6vNPM>+eOBo~iF5XvePovjAN+3j*{-$dsfl z25F4FlyAaCz5O(bl%Z2WQ-xGPhnnKjF=6E3Jc?NgB6>qhCt(ox*n&xsKtFS1o8Z?{ zS0rWCE*yB!59-Z`UPidKmjKpm0x|T@O6U7`@$C4z`{ z24pA^&n3SZQuAy4nrR6HOa}`jdTfSKM&3lPB~u%L5Bx z(IJ$Ioyz|^Kh|6pwxiL2*s6hjQ3>tAMq&uCJI5h=ZZT43;e=+<>|;Ah=$9?LY7opp z`Y#>Z_~geuZ|{HNWiYF|^+oW+TUR9m0E4$+ZhG0iwLVAQk6%R3p#;<}wPhY>&Krey zYuJnP!LZ;cq#e=2r%F+?9Ph zsKp?#P&2w~$bxK!XA5#MHaa@G{o`G!Y=}((Cj)!1rQH*#5+5dM6;eIK3fv8Nd)GLL zR7UBY+-%`t`#@}=M%5(Bgq%@}9V$L1aoaFq2l; z83JQW6?AJ*E=EQk#0F)U&ozPUfID7WTsq@~(fm4dBWZw1ffCkeAI&Jd4hT67hJr>nQigV*d2ogRBo9Z>+bck zu;rXOcuc4RmiG|5ynJs87AdP>XShqXX9f^7=@e~bH3H_I%iGu%pBj$&16S;3!~9aR zZ9cff^lHn;$IE}Eq|uPxYjh+oL%S(l7PZYLBR0anU|et=ECfj-A}F{P*!SSIbdl2- zy2ofS0+vk5b}9oLR@l@WCd0ufn;NjMVtBZqhK&0IbWebS=}}nNeYhVG2ZkqK%Ei#1 z1Gr?wU=QwrbyvncHyaEf@Uw<}Gesry&n&1A)3UHtg0s|iU;oTov1kH7{OH{C6}zL+fZN4ctZwXovkDQE~yrpJ+2 zM@!eppWhS(cf0Zs_l3#Go*3jTWu56G%mI-c; zO?-|*wG?zwK>EiF+76`kT&q9C~McFrJx~s_q~$+T{$o89aExr9JuL@4@G)ZOa{%RB|gBiS3@(i zHLzz>d-x8~$6n>q(Q&5xg|W95M+7VP<-1#24^3dU~;;RXzo$H57Vt(%)m=`sL2!7Nh^ubJ!Ew%OpFSg z%jC7LxB`)&$>vLE&TJy19}p~ryhXKIhSKAM8tg|lU8GlV&j{g3B3QSW|F43Rb$+#0Y4<|zhu&7)yfu8{O zUbS0ILqi6eCg;fM+4xlrZ;K4TAv3@wDdKl8fqP+Gc-0Q}S`Pv_p!?iIM#CTzKD(hy zh)v#EqStAZlh{83#+v*CvB0ycS0#y~&ta#EKMV^?OmJBF%PHJH_)lyW((&q*^?P7T z(vp0m_0P=2v!gCyNI?uiHLVrStCCTVue8jc?Q*{a2|`UC0SflrzsCoyRVfJATD2%F zJ%ToJ*9|9?g|X8jp*)h3+3%lyL~TW;7bCIf`OYrIeGgdoUACq5O~PYls7M+?XVr-y zn$M6=@SO|m(VfSho3eBG0xt~KxXS?2x3;fOa9~QOB{C{vh_lj%Lo@$i8tNj5nJw;7 zJ5KlHGLW21qyS{z2i$KtI&)hn?i%cY=qaP>19`ZHLLd{!5H|d?kd$WN|8+{is<(Jj z$r++KgnuLu0#Xc2qhFjm8du%C zEorM0v{ru?A*I;lvCE7pQ+JY8fsal6q5g0OR6u<@vP(F|#hlvT>%DY^{zW)BJ%f!|~ z;SWl9zQMbDFyW{TYdpzz2r>Hi)5w>vdU&W)&F+!5CW9 z&L2V$b+sAhKiy#jpUkzxpX1;*G=vt!BmvV4SAIO!p|^p#=j5vagV~2Cks-+Zp7?Ru0<;?gbHG3Oe&i(oQEHZU=*Sk zc!-<(ZpXJS&yTd>RQ*D1p3+=ghm*g#f*?d6o*fd2$4-0&3893yV7$4`E@(V(JyZ8>*+yE4h`ZL5q^(z=jGz7COZGRzyc`z$x zhdvK|)r)DdL}rXi=Ul()bfSzbV=P!gsr1<-ofTZl2CSy6Q=9J2K56Ewv}Vm3qIevn ztse3n(P;^uIf3Q@IjCzK-9b2}22|^x%Rf`SC4@|@m#a2vkHevWxf$VuXDrum&*dZA=BCRI0ezJ7!8k#;!3hPZHCfOzTbRU*txe=tqt=3TcFUcNK} ztam_J>Au}PFE287%K!It-yIc*SRXBC%%*#77KerCgYd4w(=pmbpO%gF8dg&{?;XM6b} z3nb?0a)oY7j<6!a_*mw9yQsCz6?=VuGI=oh92x7*PHxz62UOl`ki(JNi3(h?P3rGx3So_+Zv(q8FsV$2y%L{~ryR!PYsQLmNA*YlG&w-dIv*FG zXw&+*N*2KlbD(Ak07UEZ)6-2*3RL0`t&Yy>SwQX=yEdlYuotRKsFysSJ$t5gphP_9 zfv!TRjm03wiMKNBr9|US!#_ftOua{ENwxrK&I*x(lMhaG{mZRex!T}b=!@r4RQ9R~ z{dhCB)j`v51UU*Wz5-ahjU3YM1dzoI4Fd^-_vDF2kH}#$DdN#^XDyWh1n(GR6+*Q} zFjZV&wGHMDOR95ob4M_q@$3~kA!45D-g4*Wp6jPr9SGTs<{ev`MJL}u>5BY&zc~8h z;J0stnAr@>o=RP|e*~Hv)9-~As`1F~n7!_&tCPoAN2aquFJ>5s$t|P@6nEkht1H0&)hCr)E7 zCSp^!WJ^nn^d1iZ)$nrGwJlG;>^D#sk)H?S3*B0bA93;Uc+{-gWMo2wk$~LD=sFQr z;9!!G@RgONtE4&t`cwt3Sq#Dx0+G}6!q6bb!3D=z2^IwC>ws_TojYH~QFAqj@82?n z4;B%RAVfjqU@3o&8q6tqPvDqf-q7_6mbn9>uk3?txSoNRLpiAQLOn>2`$15PYpUe+ z;5#jAPMPY(hEu~a1yLiTl~BcNo_)N2Rdwaq*~%E}2?S)hSmL32FYYW*$0v*xW|b7eN!8&lgQKrNIH| z5bza{{V(j#w#DTi%yU^3k!u8!4~6^a?l}AF7+nW0Z2tS`9vAUE=UVMz2LyJwJ3i0H-Rm6*b(xO-NtxQNkjX>h1E# zXd6&IHiutYs2m#(8EuF0nS%&3NqERawjs?yRMEzX_QMxGL>zE>vhPt~w)_XE?2Ce@ z0`wQScFq(J9dBJ9p~+b$7U7@)2_=CcxAW{h0M`a$q7(^`W*-P@>Dnf!)@xxe{!nH`3L#__8G8m+-|#16G!DeC zy~wqcdoWQ!^ek8Viw-ryWM8&>`;Ia(7^%i-=RZMLI0z;6WR{HMIVsN!c~{a9qmZ_} zh~k?o0)KtYohNogV{|~BJRkc+mm{jO8J_`sxQ4?mj z0oz}N^2p0`u3o)5!fF8CU<6J+psbaiA-|vWw%~)w;5Q(^UN}>biOGC34oS-`P+mIu zCAoqKX2IXdxnWG3620a`oXTs_4M&nr&6ZutX^X_90o|>=@aDsd3qK%rRHsf@3eH*gb2g%6f0a{$vPq~RQsjBzeFY#%DQ!E)CR?49yp;A-=5Y7XL_iQ z(5fF$HZ77kQHZ#cIg8Bz=I#DBe=klqj%fr$vauZQ#$uRhOM3=HqQw0+`kPz&$8#L}^yjA6^md1!7Es^)Na z2&<7qo#FvfotQ=7-%BUTOrqJ}9-E{O5q=jH6XeQJ#zInYa>$3V5VlZb)>IdAl3l<~ z5SZ?)x2cBdvCT!!^FrvyD%F;jmf#ro=wX_0L+!dT91OdHVm*fzM!~6F>N!%{T_L*= z$HTDRm-D1^Q?Rt~(#}{k&E_p}KGj2ew*pejcmE5#UtL|jL=B|qcC;w6J6@dMa-pgj z;+|>?3!|ygIFx^xf1#*~`0^GCOPm(-_7uK#T*140da!6FeEH@cOiVy&U>$+;h*%HM z##&}$zCQkHSh(K;QxrsT{3Toa@FO#^r$l$&Ih!lf;lF1hyW^q~kRb{MzzqIi1QT~$ z5W+M74kYwWtEbR=f97WU9;EuD2Nu$pjn|EDI&NVzi9n;)V2Emp*=|Kd&vF=0E{H|V ziDzRe%InO{%tkH$jt&mKXYCJ|86Bp%28cKzc?lG3U6JHbRr(zVBLd^d%8Rbsz%3^t zN+a%H0r5x1Oygd_x*sEa^amC9vBDm?fxZMOxN8H1GRMGWrKj9F{_CO|hw{;*WMk~) zMEi3InmmRIB$ceMi%)9CRTQoQDqOw+I~53J41eUE+68(Rncpm-Hs>Ym2f)JUrf6=4 z8>NHEoB60qmw`h8x^&b9*05&}Jsfk1@C(=$rhFek_Pwj|aY%?4EFxhSkCu%U59&Ck zEv=3xYYLJ7Nm6nvSQ~7EP{Jx8;96Zra-g|Dp2Nfsu`(cz%VuT{+tq4J_5#}E!tw#= z*8%z|KxrXB+;0GH0s|mgP>`VnmGN}K%r8PG;4}|lanL)(;5R9cUVsrf90~B>)1q9k zWt6%38H}tAVR=ANx$SXl<(FCJ8;iPa=%I02Ns}^pz(KS=ny@liS3a{TSUk~!Zpw$2 zQ)21J>}`PY`3w8aOlx=IVSeYJPnVFa5n|r5B^o3-9Lf?~obhowF-lJ_YrqduOB|7M z!DkMY`#ls}3)ihUTxU!%cTYN-XE*yW=>X-9$--%_dLNu1)JR?I>!HD8ed>aWBb)md zQS1;1pYrJO<2yi9V=+X4BZv6r1&`xyk-uI)><7~(?RXy_CsL)0gL~s4SBnKAO3*i| zpyz=S^R37h53p(!W_v-6jP<>>RcG+1;EmN#v_gi{9yt8LTzr?vbHcmecQixQO#ngE zbtOaspB>|K3)@)3u-Z!idoj{mL!P+Ps;v23-_Stt9SBSSj1%w5oMzvb_Z+&s?<0jw zx~fi?c^OJyx0CdFoRJ!8vnxeA`T9m@NZp8~S@hgutaT#~hBVhheg~QDMPuR6ZSm^^A%(_Il9!fFu^Lp8zSTcSxU<&d8J(zAg9{4({2!_ z8URX>bh9$r3C5Hxu>uW1<=%QU7v2T1(OiA$#nX@ERng3$o+Hk6#03$R8!`Sx-nx2b z+rmq1S(awljj6Lc81i-OW-6d%BO8JHM@OrAZ2=)|#UUfN4n?!?xr zFP_c=p6hr0{~szzN~MIBgoK3BIt?<4QV5}K6rxE(l$0b?vZ94j35946nMFg|nvz6Y zO1uB_?)!WEe~Al0t;lBdspgET2lXRTco~@Y+0dY~Ycs()W>Y7=^T4C|DjL+dXRJHw9QHvegL zrtUne2Mcuayv3S`#n+973@q;0vExHeLAW6tMoV_`a0)A}G?K}X=$}|wkZ?<*n(2VH z^5J*S*aqsoKr`?Q+>m;PZtp=`e?B%cCQ=Kgfb%?RZia=}bGBNQ*TrM;ll&}CDm$)>Sa=*zoZ7hlA`NuCT`!E&MhUowXYH;^MH+ zUIUM?5xR8e4J%{w`-LUF4djalHP}84XhHxa##^G+&bekgb@DC@krGLk0L2pSIC9oc zY>dMoh?e9)kw#vB@Au2*T)fYYhxqr0o4*!lEt8Mi=3`_`CE+UKS))Dw_x z3tLB*Nv|Y@IXm{_`>ND7C|;<=T@)1$#2b*B_*U5v!S=t{5{14|L>{sI(Ot+_l}eza zq&F1$ez^67;5)P_Z=YV+J=hQ;ZiThU-^5oB?!WCt>-g0ZK?+($|(<@;zcyPD~F_=DaMG|uI zHp6_!?j6!aR_|y>Hyc`lREYA^{*B#2s#n-QZ0qmWCn#eyqvleQJXR9dc==bm;(wCE z4zIzRUogG<=7o^!Wp=;tppi&)07cG!MN>l6o(JPWEKzUa2o1zqDe0v0L;Is8M7S za3+!Y74sir8`;-A*!Q;3^w+U>NUW9rd6r2Q{AE;!2UyH%v3nE(T0Br~5i$`GAusST zL}cPx;2D!lDU|xa5UClg!9b|)ucT04^`H?631TTpeleJ zXM3!EcvD45Nl4~77x&a)^cVJf6_;2@$be_LSa$!`*ctdoN>Nl;%%Yc}XZ~5A|{J=Fy0GL9_-o3lp;K4R;f>DNr(YR6w+Aukq0@I-8 zqh=kR8tC^D92j^!C!k?sw5q4wg&84#?`F-x?ZIy$`sfp3*96I0yR=?eZayTr<2V6l zevcOY6pUj~SZO4{D@PY$9lOm~2OFq^e3~y-TVF5ke-z+f3u^B8^pF4=g;5>=n5DIC zdi(72#Xx)p{AU0zGf~?Q-}M1PsI99jcK0|m0w{O5RPg)9qX;4)p&=(b&FVW*DPDR7fG6MMx}eMP^0ccS}-QoEPJ2ee~cR8g9tY45u`5lkWLW3uQLz zh(gG<63vJV%(=um_DgcLfoGyO(iCJP=s1M7Wv`|SLM8}p@y|4}*`C7`e)}{*jhWL5 z{V!XkuC)66mwW%rNKBOsyCKxmQ)QQimi$oBlnVayYmG==$7hilmVnm?C27>BlLrZ^ zdCh1v)i0;!&JULZPyl4TdMr^Esv$_1HYvMXD&qQl%=CS;khgp9;E=2&{xD8M+!^Bu z)2b3gtz`7J7F3hsEkK*lxX$H~YqoSce*F70!5>jt<&2m)Y>P`NPLNNiO7s05C9Ig* z+}hgk_~ktrI#;n3^;|#kB_XkTN%qFdM<3f@WwGGqKCx|2*l_lE*a>ie;BFCP;QQ_` zabR^KDO|qfrx61z0Y{z11`7Hew%Z&mxJF7a&d{sgqg)kf^OdU%3v7x)I)B~qz}ENCoVOE$J-O_kzkh$D^O0S|-_rYA&wP7tIBn;G*qb5sS_JGr zpgG>kfG81?Grgu$-|LMA;~y82#ejgpr;IaWW2dKkYEwJy035Ueh-;sb9WF0w6oPA= z$J@r$DF*#$JiN#Jw3c1=ppBxL5vdZI*DAH11_uQ_xO}ft-<|w15D9l~K5@{f8HPxe zsat?tjIk^F8X)PueD!LCVkGMcXB|M6u5N7Xm?>rX-&#F1qyG|;PRO)xU(AIMV(+^> zCvmWAzO0G&nH}_^UKidIMU7>@J*jfPq0kDtJrV6L@h6hFZO_sgd!)HoYQ@%ariH(g zp=NXEK3kd4dyfr_i+#Nq1L@Byv6PUN&2E#=dHv}~Fg~0Wiz=+Gx868-78ZxesNutc zBR`NRj1Yo=Wh{I^50*2k`^1Ums!Mh|580@d^5OBBjpC{Tq^)1s=}_d7&j%ViuK-KD zVRg~BGODHs6EBZu;mlCP1KVu^S<+Qnp_MYX@35&W`cL1@2ODQ&(okPt3(X|a_CBJ3 zkBm4nj#cD|OFr(>qd_J_2p^q5o0OJ4Ex4@EQ=G{Dy-^^w0W-4JG&C59Mp#3}6qg7z627Mtv@i?j`(*>ICiyH3G*+*u3_7juVOy)TGN^ zeFyIvF(PqraF>4l6qunmYTn4c%;~sAJroN77zWbX-wwvb@jxjLmn0Kx!Jisp#w1AE zY${g0xp)1@;K#bUKUudzgxcI%BLD<+5cj_)6Z=H(LBF?=PGSHx7l5ulZq+STppZW8 z5%$5D*-|oZt(4Esqse)Lc~|<+?y%x|u^x1oe7|wC8jO^?E5)W zx6o!3U%%c7{~V{t_3GN|YI(@%folmy8C`klnVyA!a=Kj$Ea%9kgM|Pkntl4($ zy!Q<&$Q-d2`G1#A?u#WNuu!G2nSvx-t*f_H1XdXvTq0dKW5pk^TUxLU2tKAJoykMz z(JrMW`2#b(>+y4E&NSXl&QWJJ6M?Nu;e=L73KCfsrpN~%Y^+!l$Q+*j{=-xLZe*+T z+}$_w1HOPP@HqG9lAf+^rCRv`hEfhHEs{ue5q8==_xJAEBhVosEkM{(!toW1)7}nV zh`LvvZ{G=34%$}cOeyVmsY&d$^Onv!3ECE7qBd`)MQ3G$!nB!NBF)uLhP~xrh{hEe zoon#>pl=`hi#74|*3vm|mEQAy^|9&b!p$Nw)jWRf%+nk`XW!i68QGE9+1XAmDTyj4 z@+)Z_I)SowHW`VP1%s21X0dVfDZXd%!JX96KetC~g!mdPb+cL2%QLnK$*@==;#740 zZL5u17ucy25zDXh;$K4ByR^brI=%Hg$Tt{|(PN(PyX*G%kcV~nXE1A8zJB{Nun6c1 z!1p$b?q%Hygi^|}`XUUJT;8r$J{*RxBSccz=r`%@kz6U*kz^2twKLljDBc?&U%!^!JRcEy+R*unmn=~rq;>QB2OFj=8p=y4M0-OneoL#~(0BOll+Rzs)%aXROIogWNMX)tD?UKe{AC=+jJ&2!L zM7tf|vAQ0;h_J2l&Pl9i-k!E+WLJ2t_@_BB6>J_7+wOePeO1T)UG~`Sv1cV!e)@R1cGfd{*YK;W2A)3GZbH;1*=5f>{GN_)e(xV1 z{Z?zy!^by2f86oE|E*|v`y}aqt1h-aUa{E?_8D4g;+dZnMb5k@sgFCP3H*|FwMB#A zj^#;;kC|rt^D>VD7|*Fy7V#e776b9n!qjdtn&FHXiFVg%{yCB%`-Dogt$r<7vSjZ3 z&6O3%vT)TdKa;Hlg+i{bJ z#jYX9_@Lr~gi=0lm8T6HND;L_1I$YWLbVYFX=t~V6<=c&&M?lTsqLy&r`s^<33Mb)VV;XKa*d^t;z*Qbs6GE27NQ&TYBwh>=H73 z#0U`&C}wAg=8#77kYmbVD?u0rz7g&A+7An(XV}myo1@N4_JT%py{1AVVvV?y>-i%Z z%!?Z*daCNcXdN_Dm0(vFi-LXbAIn}jAL~FnxEjF;5tHgr`$wQlfB&?Ocb+*@ z*H~$YzW$-PTPwQ_1d7}W1)J%%GF`-`h~O^K4x-_dG?a|;9cTdHb8EA`rNt96F#9bhDJt3yjqQUnVE|+ zHWnqD(}WH-xwPi94CY1t!7FU?N*^OMEC$BLrHtmhJ!{n4NVuDX%x3iMPoQEV@1G0& z%>$#~3kdKQI;z=UanZnzrMoV13_-qc{G30_4xhql8(ecT8_*Yvh{avJ6 zm}Nf#oSCY}7$js(?=IMvvWrc-QXZlCA%m^5hngt5t8Y}g`}oLl4RXBNNdDUtSLQ4; z=QfoaGQod&*`Vo%l+7oNP>Ku#-xgP`Ic#t(S!8s+st?2G(7?e37SS0(o!R-7UcLE= zg_4Z-%LrP*{9m*2lGbvtprvPKfd>@vr{-z>HG>&q6#!bY79MX!8J-q(!ACadi zi7P3M+PYP}!(iQzn+<}X_J4TiPJUTy9f+V89{hJ-KE8kQzn$}D*z7Pgs9hm21g=v) zhl6)Ku5sYf%T7Dgr`!JdGk9{y`|9+LG@BxMy6p5@D9rqY2~lvmNmC~|x>$dggcfCU&kVj!tFjdA(8xrVa03XItJ-j+Mg|A?Gf*MS@Uj`8^1+&s_6XDe5o z(6yZ%KKTt+t8HHo`C|%`<^1vu$SNpIL@EFF?c0w{cXdH$n`-p3)#I%N{)5Qwm5V7*D{)vCt_dT*_Mv48!x^HsBy&Q@d}bdvhP`!5nxY_ZTy z@*1e167;4Yr|;0%VN!P9m2#Zr>U%9cfqoaLEVOUw)K*HBgxbNkrCkJ#_J1ate*OP z1$w8YgEnJk{6}11g<=j6!b^22?a<$`nAR zKEh(u^h;H>9+tl4SwcbrR^Hgo*|++=9P2oK-LFYQ+G)hypuH8Z#@Q)jf+vgB7Xvms zcpL7P{VM>0kFqCDotlV5*54~;9X*M1s{>)HMb+NYv9#Gk-;bys7_T-y)y%FtjdI%m z+~sABWRC4~f8KPhp+jPYVixk7NG{4ZVH0a8mxiRv$+e!y%L_$}H#;G0{}FF2d}0(9 z6CE)&c!s=}X5uO>`uM4CA^^fJvd{)EICF6+6ZV~>jvZmzDP1xHr@GFZ$pTPi=1KmU z7@kWLde1Ri$SvFQ3~Zx^*1_z>Lj8>^hyxXtl%zsD-;qVJvVmJ&C-!G}-!*paa8xKx zP4+`)3tu&nx!nd>Mio**3Tu}NUB~0I0hy2Mpx17($xUR>^G=r{#t^~uBFZDG@RcNo zNX{rU8mOU51PM-)UB{qU%AwACE^02M`KEbul9rmV5^|x4kU|4vLjdMJN_2iab z`B>85ZCVF>K>qh!CB}V5!gR2RfnS(wwBu(pZq{W4AEg zQ6RJ;Dv6}gReCc|%8MzD8yH|Yj5L+FwQqS6AOHMbEBKNXudZE&j1I?vqYm*6{gTqk z>>vgPA>kFsuUN9eaWO>kNFJLk98mI~WJh%)qi&?4q*r=m{1hyd(axbyE(XsGzxR54 z^VRkj&Y#!0ws>ajj-GWe+Yz#}MJ9~<>X9+y8pjI}Bn9d0FBZu3aXs+&%8Fvhyh!*8H4(hV)WXRM|h^SznlvDD#p{6{cl{f9+oF%T2BW98L<=`)`Km4dIZojePb+wyJbz+~7cw4wz_*qBz`|WfRFA=9^3oSPG z+RO7wt=1^G=qE<<*W6Q9cnI)VOr9rA*2gbjj3+6qk_a8Kh=1gchC?wZ+SWzZ=#Gxb zad!St4TT*_)!1hf%mXWEcYT_+yTf~7Zs2v)(F=>yT&%t5ArW}o%0sNLPk6#;1t@*{ z=>#K%{F|*X=z|{ko_xCY$kl;fO;xEIFMGH<_H3Tx22t97Th~8grGmV|X!WlKxp{dL zXlP#N{R*BqPkqQHytHi&9Xd2R-}3K{53%<4_LG#i840Rrz#yf@*j>W^6!ZHBsLAO3 zG6OaD=YNmrQ&G2_mt3!3{zd)b39Wg#&g8qfjU4`O@+=A8T*M7h*zg(9gvmXWX1TFO zBbxD4!P(ilD$T>DLvb!4j?_~`Ok!x0X{^;J#!()$Ut;Z~dViBu1$%&4oDx!557bwt z=6<@8FCu_w(a|v3)}bCi&~&@NrR`X=>eZhVi@UE)+%@nDlW4|uPx~3Yv z!y-NN{QAFrAwLD!4#v|d@bOR3mcpVUX;4J5J`lK6#23uooo+{8*hc7AOmN-`;e7Ry zSx?ILi@zKtf^I^r0zEi!WY!T?80wJveVwl_;kHvKuD zRVUF7$TCsW{v39lVC9T-O_1Yau0z+Rg`f;_9p>J-O8P?%agewV4$#4P z`4YT7X4kGsbKA$8vU3`NMt2Jp^X0}{)0Ew&abU?NiK>!lv*F7yHBXK}8F{Mx6 zeaFV}(ORBC%d_5{+Y{KV9&A4d{Xj(Ly5(%ufrFo1uGAK?BC(|0Zj=sSmn}ie2~tgr^r=B9PaFUr`}B3kG)$ayg2p3X`r`DT%r3G_zOrlJBE*^t9tQm*PTz;^9{My>lDj}}jc$LH8Imswtfh=7Tzk%RFp=yNCV z$-7?Z%XvOM$svUb`Nn2hx)|131xf%kJ*Nd9fFcY~&=}S`XhQOgp3V z4+YX-dB|tUQxHl7c9`DU^FJx6c#pVL?04+~l`lp$qokx16*1Cp$KJi2_<`XVyjsRA zSg;_=4!2rFEO6+bpL{z!i@gju@~oYrs}rkR3Cp;}%}ijNfFI_euV&nqXzHpk zfB036l9N4Zrztg>NN#(ni-WcvZOJ_{#37Z`33Qthu^@10x4Boh z1p`95+y3U*i6t$$4mpfyIVVmO15l`xAD?@vFF%%}++CBZen6+zfA=EV29f?Zi0SBT z{rb9H2ozN#b6Gk2*lQatHWmLB(S^THk;620Y5%xw=gU8HQ(AxV&x)9JZe{eR>ZgZn zdWjJMBq65%t1HjKH#PTnpJyaMuSiV?nf#`>(IX^!tmvZrw*)x$<=dLb4X5K7>+4+*g>X?l^+YKI@0$7OclRU1W=B>$v{6XXpLW@e*)b-salj-#Z6X`~%tvbI~EMH!|b)r3} zgCTKGfWLewPIggSYB^% zZ`UIY){Q@SwTcAtV&}M9A zrcL_h(eybSIAFv+G(XcP2ghr&W)AnnKc_3yWeZSyS@z3D3_RfP8@Ykd5?4JR=6<#10lT zLb(8?VIv(dY|CsfFQ#D=AQcAwc_e?x^tyROs~b!>F!*y`2Z{BZYM3XxI+;!T2MjUc zcJDQ7jC(*GLJYW)Fr|*9RW0RWC&7G1c|v0XPiAO0m%9i)OWqv~0YelUK?P3`vUA*3 z+t}0Ekq$bZvY)v6HZpf~9(=SfNk;SN?<65)%RpK=neNcgo={e(xx+BWN1PjaSAl&a z`-DLXMSUyUGC__|(*;2cpCPJXs;fut+u|b8vB+5NMc;7X(ahnUSvNhA{^!-r{o6Ui z>PRgA{Q2X2F=!OAsaE}1g|$*|?#13>Rs^?)^$=@qBq;+wR{U>-L^!hmT1D>kg~24r zu;I3dK3OOfYHLl`R!l>P1WrRGm57XM2)(Xi`q!Uxeha)RP|FLEUCXm-biPhOHa9Vm zpmGwx@2;ez`2+|EQWFkaNZm_Jbiz0zLUVu(NZ65yW17g(`-F&g z==)N7F*k%Zg0#~4-f_lWemJ4mEH7`*gz=4dxqti?FF)}a(iG@3s&N)p}z!`zdCbbR*S3hdk_q~KhM4pxz z6>Tpj457Fc#I19{#3QM;Z7;k8%jqtmyP2UnKG9bDAp3$??s??X3de<*@hqiM{-WT? zvj3uNy_VxMT~8@;@phizblykiN%@xC>~#AG5#O~0|3xiUK!NE) zD5Ha&UCidJVe)W!N8p93-<8!b&p>6sfHPg>-Mv0AF-qBV_N8^R2M4@ypPSa1u)8C? zDE0)>_@5(HddcVltKELt*K?Ij*LYm2r1+>jqKn9Q{ra`e{lEP$gvkB*spYu#y^7Aa z54-P#w}k54cTqwFl_DH8Thp|U9yzk*C|w2mN0V4Z-$d{YF zEjl_~VoO6Xq+lK1QzH4ASf<%sVy)5b)_xJ9fr{)f9`9+G-KK-#9Hxmsg1?_HAi`4P zII;m8C~G~X>EH!vv_Oryvn(1CM4X{5pP`3uuW>;mjd6}zt97wO79IHW>);-WiomZH z+>g=ASFM_HWFd9Sftgb?-a3QZ*PdSM4l)X5KX1Ai0>dVyhebb?B%KYiS?cQwY4O#8O2;_|ZELLb&^c^VHrMlXY=f&kqC zeXDwi;Frz!PwQoP!ozpYt{#k-CH3#g{QONgV9#vR5nD$^nhb|^+2>a_63yVB9Nz$6 zl?^GQ4!v6++R#BW_r?B!FXz?#8934~J-h7Y+E>?9aDWNTc#GUw;rQps6+!Ro_UUOK$oD|IQmJ#25bEiCC@5jTTKDVIM3E1Kq5OcN28X-{|BY6*7vW-QEoYGK%=h!V z{$PQJ$0k&8z*d4nr7xZn_65Lt-=A}!KtiugX;A4~2v;qT{}MMt3avEvAy7~XI&HDb zRFqy=!m6K@-Bib~X>LnqxFuu_`QnOy`D4fz2xQACRq;O7*dqP?|q&*IvplDMpq zgjejaPG@Ek|FTYppAi&*_SOkB?R6_2jzc;a!Y@FGenb@=c<$FaS}P_IK$t=p-|kI0 zd3kA%zEGrEDh7N%OKF?#>5JyHg3b!)K_5`j@G4w;_6;?*t5AH-0%Sd zHyR<(q!rze>qjE7Q28TP3ZaQ*j%+Wa>wR?UR%mY(+lfVnC{4_UG;P|=K~e3IDoZ4@ z^m}q$!j}uQ0+VEyUcF`>n)ee(pI9EWNjgDVOU~&Zbt}vMa;wTjmfHI)FSGqlnuc(0eEc5t_|p1*V7J{J?y zRYc`s;#2LF7hCFf>uOK-r1b-uRuZJ#sZ-&i;mGC=XxTLky{YMzc8--qwQ{zy459cdI2iO@nb+BhC5C)rndX0fy!f=b zMMF2dSZeq6L!Y-_;p*KG5z%3+Qq^$t5Unn8CinV(ZMqw&l(L^#g9i^Ds5XAtjT(g) z>{dN!4Z<@Tolx0v^=jv~3EKi)8EC#lJ1Jt=Cer}V9j&fkT3R$B^x`*_*9ou zknImtclNkD{ZMxKCzCsU$LKw7j((?ZQBl6%q9t;-AsPTJI3kZiV=P_-8tW$ zzA%U7M;$YBXgMhc>cGtmlqIw;*4eA@WC<;)2zB6001S(@%>e!SpU*d61*h)MYlR}( zrRgH-I0FP5JoVQ-o2%ch{@$#~aXhT}fos|8=2Ze*3TX;Kja^Tf&1GGDh`IU(E}93v z!-XLLU2Uf8rG?a(KD~Q0LpWw%iX6?l_Fn8*2;pT_b*r`)v^00 zY3STtbdPQvo(y^#(lK4`EMoV0C&b$HB9d!*l+0pbvk(tSthGLQOvnlv3kH!NzH0UA z)1d}>8*esQL8d?1!SA@5`M)j!)g%U)*>jXG(@o6z=I3M3YVco{N2n! zJ;q_GOK&H(3Lx9i-BKVJbq4o>S*9cC4}rYo?RLSqXw5<|Q@;A^m9McE#4sVE!Q}Rz zc(hi z3sap&=TG@t`)PHGwjBQ;cA$mqv^AxXnXq(e&bI8^<$8&-j_TKSpW$->RYM9;x=4g;11m{L>yFz0fIahyB|m;_1c~fsOOGFTG*7i zOTro3gkGBFGu*m?eo`!S1H>~OXz&EW38iyq#$snz*P;gxc6By>h47FrpJP|0z8tGD z2q0*jKz6tdcz%hyyWFkVI%=6fFqjBwWPOFv7=%t}W{4D)qgM;V69o<%gDfr#NHJMRC#%vi3M7ihO6A)JnCbt{`g%(Scj{uxWD3B#%*r~xk4>H?Q| z%U8Aflcx^+oRXCxBvXv!EpmEE;aM9uZoKEaqr+YhDgYTAAiT^VioDE18)$F8GxWY* zVx{cycU3;`t*OU%zHFsP% zI1_{bB42#)Av-lc7~6#;^jw=f%v%~HR-*RFN!R4wIF3J`{^547AK0EYivfA31B@B#chw0_p)BM@nD5i{8xO59K0hjG_xZQu&?mHO= z%-*!@zWGxlNfJ!kiRrxSkmaGKFT9r=ir>WoVsGyV8h1C;B3_tq7zBl-h@7jqK_-YU z-LCG`R3?9xwq0a}Q@jTSTm;&yBj?o=Y!@kb8=jFkmVNv7l~}vIef9FCpnC<6jCz`N zTN|a?$Qx~t@>H;c?G@%z=c)Gu`p1Pq- z7h*7c_@UO=fO&H6cmPq)h5vrJ(9*l3z%BD9F)R)@pic9xT`9ADMRrGxO z`HWf(mdcj552vU4i6u1nfj5@AiAg}HB3MTH#Qu?rNm%Idm>9dH*x0;Pljxw&c9Q)O z=GyKx2j~E}X;aotld6v6)>|cZsNxR@L%O@l=G0gmP+Js~RaLE5eKil!@0aAI-7Ugv zd#{HkbyP#+sUZpbuV56ipgMlS0Jyx*pbc5kj0tF@UQ4a8nd0;P$CpKAd5fwC4%&X^ z%qaBawQt`>(WnTAtOUG7kSTOlcY@?`H<1N~?mDcX9uzj^6| zKPzsncm_HpiS%m1_W^X6XS6+bjcH<-QG-kfhmTy9F7yzac#DOUM5HRGzptdOGM9zD z%X@Iz^V6D{(k56apI!{^D9LYq&Z|3l>{C2BDB~|Pi%;%!IhU!AZe#YqPaoI<44|!$ zqVaexC}NL<(EH{dhL=23CxNRS4p49j=>K`?Lb&#?UpF%?xQ8WNEB>1V-Q`b|wL*!Bnag=4(5Y|1~f4tbv z`p3v5$?wM711(3kdpK6VU&UoKluSLBb)CNckH1E=wDp@!1^vwBi#CdH#ByCfi9ls( zbd~#FHo^)+I^U(`7hFB-Tps$wRF~!V7`0dYSWEZl0 z&jOYM&HRS2OxLkz>W>5WMmV?}s)QsEAXrOtH|f-~fQ7A;47!gtwnolkFhaj^c=G4` z3If%X`yMraYu6XSDFa|J@=zvoA&l=++(e|AanpR@p=Hp!XX*9V=REWUGcCI|F4bRH zSQu}G80o))Ovsk-%TM?qWj0}*dT~ifTiWz8KuD<_<1*+r^_YYwBvRZ2wkFYTjNKy1 z9^)ZD<;YcfrE1+}`4POZViK4&OFreLiu1#^p>)}fhrM}L8DIpt~=jYdn4{qe5Ak7m=Es$EzvE(jIpLP7}%rE(MV=$jpau2fyS;5Eyq72*fOfVBuZ= z!P~>a+KENdl0s_JQsOehuxSJt2((f&;;x5be7!fX7XoGg)c(t~cVAzyI{j5cgPC^s z>K7SQE3+;2pH?$w3^JAv_3a0_E3~w=E7lB*z%V3M7@J(xh@-SqVU@3{FpAszjBSn` z272c7?Z*D;w|14?xf3_1tDTY{o~z9beHAV29-LV*P*Szc8-z@IMc%=@Prow<2n`&~ zqxbfg^g!--d}I{uLsL|JdWd+tXwa*9coUp+=1B=jw0K|euaBM>hpO|+he89`A|5%vX#s%8HSPn)ic&qZ|Ojmx3s=XrZuX_e|&u_R%z2{YEkGOdCv zd0q=OuYTxR7^fI+r6eFine`*xxT?^{Y)nc{2AMTkc+$3eJ0e|ZeV2|L^v^$J`!Ts7 zCnu%d6C%;LZ3fAnWfJiZy3K1Brz3yL?~xL0KAp|Tz!vSg6Tm3=dFOE@iNzYMtcwJ^ z)mhz;%&iXAEeQem5keceXC&(FR?_|OtNaA)LwqvLZ@%(K3*%911zfCL=SOUa3|oZ5f3 zMmxuN%G+(j$KQj-F>A3n29fK!=P3GwEHRV0+-)h{Zo1n*MR^4jYkZk<+9EFgZ5&^B z6pp(HouzRTOQ6MUh)%-8GST+kw;+{SOo!f5KgeH1S{>wp)Ez4o&cO6L!7 zn^sRMt(!(cmX%>?ed5beqeqK$a|tJ04-gyzWfS$9D<5CqluL!Iv+NCLcC_ohcA#_R z@M^D(8e<*UH6;R(U=)SbwTf~h2sxAMQ+-@ixT;L5IyFlVv#?_-t6t-E{$}x@D{G%s zY$~Mn2l*hovJEGE6Wr!lrJZ4>A|H3tgwzRR0WLNJzKEq)^%e2AIpj4DBegW_0ndU(rh}OraZ> z`*P*OZ5UYIly;^w{>&22MkpZ&(u~w@g14Ym-+l58sDRk;?cNWdzDDJM9%K0H91Twz zP>0$t$CoHpf)ty<9CF=tK|dl`JN#}eKXZPlyr;Y_m9aX1)yPzjGy{^yu`cl8N!xb& zyic~uSTVY71iJJ>1OtZbkOYzxZ-LlIjx0%EXU0(xhskT*r=|XrmE~ ziK~rc|DR>dBZvjG^5{*Ey6YO>#44uzv`b#n2VOb?cXC`ib@wl&w$OMOkoqAQHXvUi z5Bj&4&v-(Jl5E?kgUbL)+ay-P_aVG$V-<9$Er~!Q*_dM)r7Q*d2`^Q(@tNrO85CJ7 zo3t(a&X{IHv+LKdtJG?YoBPVjcGL9*1GKa@ud|Yqm7S+iz_T;xn&P-AYD0&%@7!QZ zkd^^sP=z>`Ra5(Sx43on9B?j(BT@jBI!Ag4Dp9x;EY)H6nu_w1@oT=zwG|!fptr?! zpI!$e3$*TZxJs~!YzUSt1t=DQ3ldld<)2qy*doB|3KSssMYO0Qy4^Z`HH!6&ck12c z=2HVP6@%Y}b&+k3oli3%^n$Xv+^zi`e*E}>Kf&CzDq!WxL#>T`0)u~4bUfLdX(A%b zl_Jw^w@=A89y+vG$Or=0Z%xYy`o5f(kvKkyMDUA@Jc&Opc+!U6&OK0BR9P9hZSO24 zu-f`{o#RT3hYZ>9mA9#5%1c$1@$1e{_!gPG&6oAzHzztcbjPA5pz**7md~#8qu13` zzCXYS4n6f->^6jqDrc&~O1vE-zF0^sudsf?b(6X!3j~6{BiLouA#e&|TNV}>LJUpY z?;6;pC7x3$q#Iy&Cj4*0z{1UNJ7U_(_m5o$wDnq(dl3@(iUcl+9uHd=(ub7sD(!JK@6WT<0vGp%3mDH!o|HN&$2RT1uJg0CDd9Q97T9 za}Ra?(D*)ck8zo^1OD8xDeUK4J?6(~G%dmaK(i?&z@1V?rlY0JGlKtnH|8N$#HWl>eL7e-rl!J5i2G7xrEKLjZO-j)z=JxHpQEfigJ@|=}I01x*f{Az?JT`&xl%}-T{)?5u2Mg`XY?>T@wpJi?8yfXW-F47&%EZ3ksF&@$LXr+1PDu%gc^PAC_+MYZ zq{_MM4}%O$1EV151WNqx^Kqq-$j}5l0!!bxw4G7^*z!6)op!)0{-@grL095&hJ)Ze z1Q_yLgu)QHS71N>O;kH>!igjpbm`uG6CDo0L;If|MiF3OXn2{zBLZ7kai&ZnglG{j zDD1x(&UHgj6Y72`a2m9=g-n(>Bb6pko*bAKSzf2&v3w|!Xvw#4p9WCXk4%n@Tfm%{ zpZKfc*+yIG$kHNKt}4f>ap6svI(1v;Z2P_s<;*1?TU#fhTU1$grh{gzJWme7>%b`l zi;m$+UH!vc_uC5VAD4re?nIIlk55d>7>l6NwgD%El-F%2uAN6`{O#G_KjFZ&M{l>4 zHXU%~{MzvhoomM76|v4>0es^ zci+@Q6Seo>{CN;$*-|6QeRqJK04J;|3Eef{3*M7Dwt7&Oj zmM3)0k7iWIDpkk-e`6^ebioEDcX!e>J@pGZ$jTluSgW%kMIq?DpR5E}C+gZo+OLHr zTZd|BZ*^P!itaJCt75dqFZ+Fbk`f^=_`M24Jx%=~&&>RC@LT0JL%%O%tVd}Uj= z`p;m*LY@bJWBhpmPG zirAC>U1YrsRV3g5fKvgB9NN(k%}2=LzlOM0^Qb{>xI>9$HJEUR%>vy7VU~Z?O>7+_ z+*zWz?=!#$1Bv0JAzzNFD@AryR4iVt@6=ww<$LZ*%!cTvru9~;zDXr`-gZCwVr)aU2T&ev@w`lO-YLe(f7Km?E(^p`lP4b()*6FQ`Mm zkya_BJ%p6$U%Ne$hp|v&!H@IPxa`I8oN9dke-4=qnwHX0&BIRfkU+qY+AY2aVqogeDPqVEM0Em{&M zl~`%PW2^iAokZ^QLEr7eo#IiI&p&_Ss+&{nv(x?_@2XSH+6O*DGSq;2uUY-xXR zg}W|&Hl&Q;{-Nb5x^^upH<$>ysFzdj&Odb0NNDK(Jfd|3d%xk1J@}C8R;ZikZbt@ZxxH^RQXYeol^v_@*4Ag`{il+*^J*V6 z$^6&=%?{|%KnL7+!|Y<%vq@y;Sx}X_;f@sNElv~d3?dnwHRl4?uFceZ88S(uXiL`t zLjOmDl)c>;3O}0Zub_2cUQUl@Y5&EG_351N2KI$>;P@6~Im7~>BD#~9pVrw9JmiMq zFRH_B!rq$RHQk0fB}sjUJ`-ji(!2`)*>5vjdLil}^nv;txO?2CnRC%1_Zz?NU?m7J z@<8{JjRSv$Q^3!z`f%fPc;@smt3m_S-i()Rt0|Z!#(Wh+%MCj>91GHWiJ3S)Y5uvh zJ4tR~it&8@1Mn&g;a3To2% zqd_m_Z-z1&EALtQ4aqcp;tqYH3hd_hQ#YB>=li%p!-p&3^Q+ox*fvzE7HJi%Pr=q` z&f`Bl;NuTG8-M4${3;~SqIZuaLZJBzm5+6FPr8e2|K%<;4#4J0k)>On?TLv=z4NJ} zXu*k3xF26&Ma#Ay3o3Ow=)2MEgt4b0B(n{hmxT^cS6}+!>DWH5(}$CSDg@rM6Eyc~ zez5i&aPCigd1aF&j|vtg)(qTvC+z&lIe2VoWtS_=CjmA&B4_iHxU5pmwag1%i`0hw%uXQI-dbBw1E-JuZpQpeAgE#wdd;xxn&ofq4b~&D z55C0yn=d$i%QLtsba7{g+uYU``dSmUl*;rq*=Nt46GRxDe)S>myMTeAw5{=~L-N-D z{R0qqsFKF`_91W8bXZ6J*r7_MAYI2v%Sc4ZmrC_TqQG?0J@4e_)nm#w)e${qWi&zB z)PjuOE#P*-qeA0TO6FB0UhPi;Npxgp7|~ZdvPlHV!+BZ61;krV@6FE3=lH?^o=a;BPYT zHv_6(Y^E7EZk#aMBKZ*B9*Jh?b9xz_F(V|x7Crl{|NJaTN6gMlwgR`Kn-CFYbZml& ztfuyt}4q(ueqYoBy1Y#O9AOl1g+xZX;eIL>h z18ze(3gAv`>ihE+u&ZShz{fwUd-v{D`&W$YEfVbnqx-jfNE=|HkKcOX62QxZwLs&J#1K%f7pByP39g@-TB=i3nIaDBbu(-B{8nSF(hO)s~B zOyR=W2>L?@7_ey9zUio#Dl^ib<&YKmEWn!rlnN(Jv|8PRL9I zFIr!1ZWijpv!gu@+mv8(RdV<8QpRB+mRVuwc;Jg`S@6@Z%Tgbr|kn zVI3tGw@F?V+G(~}V?4lecEAE&^{Aew^L`v8kgsj~_KPaBd~DX-uV(SRSMKZ29jrit z)u&7{dHcNo7Oy+Xxf{bxcI`;H^_hmFlNj@niR56e3cD#3iX7-#vPal^Fp;-ZWnX}i zSRBQmIRLi+8#^vTpzhecJ5q#LBPGToX~qT@1l1~QG(CKuUF-v)El7$OwAcJsyg6d^ z8MYw?d*KW~`hz8Yud)v>f(ZN*9|-b}{u&x>De%v^|ia?$CqE zA~%b)ZqOTdgfZfJK+q+Or}8RBn~vQlTZ&1C*~Wsx0BJ0Y2B?^NgR5^wo$^j)En+g{ z^b%fg3r@Sr@ZU^LLOad6@@;gmoS|Ej|KLa!UtUmd=uuby`8A5%6_&fpZ+-%tv|v-H zeV8EU_jJJ@9l^JrLJJ;$^te|4&-%X+$29sevjv{|;qdh5L(UwxoW!KWN>l6+06Z=G zxZ?s5qGRVDRzOc*w)zO~rvTh9{{A{M?C##STEeF0?=N!saIcy#VST`X%VF{wah?D1 zq!9Y)7gO34avGH@FTX53*%b9Fa;K1TYbB21ohpUHE%0vPp|hc9l(5TXwHiJMfcqmk8UI7wx(&3_gDA zbG!EK5AY2Eh^22;WL7A_b3=CNWTGiV;2xn?azD`|_QivR_@)fpDw_TQrJKXmlOin% zf=O&!%gW9ckvA|$by(Jge)<1oc)X1V5KoDhfJ6~-qBHH{*qStbe&|DtU_@dIJ3o(| zo9_A~jc=on@}((;P)@~{hXt^eH8v&|1GzP9K^m_F#O7g(XXu)RLqI$(np8|9jmp>f z_RTr%pNN0E%+&>mEEtzuavaOPl>q#aPpuQYUZwek2fs@MuI4Ygzpe9l6YX9Pm*{kn zZCUxno1uv&abhBRp&dJRNP6~6Tz=edLWiJn{r2tKr#y6l{D8RRU%>|!k-QfI)KyiF zy{WM(xhWh%9q>&>R|Tc&c19HZSfMA?q=#)t(WviG1q>6FR71ry>^tRd47pSWJEz(t z8CmrIo)SgZlBl|GQ_7?@(!*?xw!{p++&!|?8C&o@-`m|Or)hsz1_YGyCRsoEK%H`C zeX{A6_sN@?y(;SybX27^t&ZVGe%&k#9Fwyv*6g#zyMbeJ8rD^v$tttj%{&REpW}`R6|TKP8ul zqN<eM>!mU)7pfADh~uz(BSHTB7n@f6m3Aj4lf-W`Iv5#suLc zOx4!Y)5zW6NKy`4k=>cHTTYUfnRx5iS@SvaKmp-o+imMTYQlsp?TEK?=sKg8u0igy z{n!+BudeDQin7-B`y`#>JiaCjcEq$Qe=B>m`Uh_>H#htDZ}qLdrH|tKMLG-YYDa0fZIQTZ0CD;I5b_<=#c8O^vvVsKM&%FtzN$nA2}#ajlpF1E3Fy4 z?r!IIT@lw3&nz)HB_?H(MX){npvvUp#36I1CRlYlYOJ+xU46pcXkTM%m(%CY>5d$H zq^ZRA)~E_{QrmD@0~n{B!Z@tXXU%)ccC7!Co2isrOq^JJ`}Xv7p)hlp@0J5n!iL8o z0Hwk@CKe2>bBcHRz{buk6dl0~5kFu`fCkd#E0bQgkIUMH?_hp!-8ZLzua#qyngLM6 zhHflnhyLkbgDvX-_OtHD_5SYp6V_fVJ0;H{0-P^n)(o4ln+>Gcrf^|JSxJJs z93Q9Pc$by%6d8%esJ%L(dEg^)f7!F5qQXsM==~nNX$;cxv-dGpK}$z+WD03?!V?_( z;p;?03)p#6Hk~ytgLWP{A!E|kOWM>*r&|gRX^~ugma*}b^^lvljd6hp-febYT-kt0 zm5=w!R#i-odC(@VWM*6p@c`L%$0{N>b3p60GBu?0eNLY_6TjQ*ILt_t=23zawC|so zvVRQ%KtAinX1b$_^HZ+`98_f>6;AKsHuli6rLSYTd_=snU(!UiBYj$XRL-wHZCF@$ z`QS45)5o2UOO{8RE- z*qK3*8xS3kA<4F4LmJ`a8*H^pkO)F0Sd=?DeSHjwDRN3*+bW?b6ZjxW{vkValzMj# zS9(jRJ>C6v*ukHq+=$_wb}>;wQ$wQH)I_GDEuiPN-5m!lX-eKo zHvlvqi8^ck&|%Tqby40={&d#&9(Jldk26v+h3qZt-9%BoD#W-#QqzNoKve6@3| zO7oCLz^c2$BOYVcv7I$*O5*rw7uA6T*tn;uFkABz<`p`wiVF#XR^@uSRnJd7qf#@O zLKId11O5-MeQ?rG#adfqMvcuvNA!IZKp>$Hua`sRP&U#!-M({!m{7~Veq?}jP}GV$ zGvv%RhhoYh4Sapzd%B&rpicg4(5crLM4ME}TK4Vxanx!FN2Rdgxs>wN*^kaX7oHap z-62Lq&;}thv9;Y{{nQ_|vn4pESfYWXbsm+^x=(rSQlSkITTb|{MZyjrn#fCul%{bN zXr#zp0c{ome78zM-bYrX>T4VdFyAeVk5!6c{xeVT6u?*UEo=Ee8 z-Vz&v2yrYT4jpcxkZ-LqkjuIaJi6dhGlnPRB8cAXUcp;HP-&atyR*B5hS?5>nagiq z-!T6T8xpgoKN8X3Kb8O_%c~BPg((qva7meeWw%~AUkX}pNKR4UPh43{P;F)>i8!y# z(?wK4|9ESbv_^!V+lskTTI8;X_v(x#f9tg>HCY*1i>k>$#}-m4{CoPR63+S9HN786m;U^ zu5^HwXJ!1R9G>%#j>>R`>Gn)uNYOS_+=*IZZ4sK&Ci-2;_(UW~f(ghJW*8ASIBjly zz`8Bl_wnRD7;}_4;iBLsZx21U4jembK3h&yFY2tvX~+Mu5_gMnmlaRs0fhM$|l5Ke%R@>i)X5>l>apKrKYF=q} z+Yy{TdkY|FcC5m}0j45BY}MMQxtqsPN%~AJL;ELbLcaDx)x61Ct9a4GGB61ZtM#f~ z89Uy_gADCcNC)iR3&kDg@;Q#2>ueU=d=4qq9O%a%R=75?%Pm0djh>;s;^3a7m^;v3 z()vx=10?_^x&UYzSi7Cg@vdJGHXkvp{o>~#*SneIi0&@Cww|QoINjklX`o9PJ={p$ zocebicFH>Vvw~5t0pVThgPnZvQ^W3?0s|)j=EA~Lu=i`nL(GcTUJ!)-XCp>1 zgfvj7B|6*o;WPQ-@o{k(mKWO4@~v`S^c#0$)VZ{2jI!NNDH4_IkxIZm|0ie}vZwM6 zoa8n(OuaqHfdBCU>uO&hRS#7jOw-{7VqAtKC;U*ic}Cs6u!1 zV9-Y(^B%fYxv>wbL3a<}bO?v2xP3e_f>ESVqJAbKLK{jM@Qj;Jq6=3kI%7kc^DAfQ zTt~-?Z;T!Y^08CaAC+0)bH^AT8cT(4=~+*RPUe;icdqTc7lUW`Y!EZK1M8hL@tROZ zNq<&M>ca;l$s|x-QUUnSKo~-Vu&|v$o7B9MSD>qPL1&1D^zXTjI3?8LGAE8h(?{79 z0O;UUaRP35sDwiRaZmNQ#-Q+J1-?##di)@YW~;o%sU4Pc`LcwBM3#<{CB$Y}^3Miw zr5i}7pLmZFkup&oenT9NG%csBt1sp-0NO>pTUPQSYun!+7O3Wmbjvr|MD@v%!_yFnergpet;NF%L~x}e$o*S+%+?p%0Y&nsd! z=o}2~Btj6l(TLwD%ivy&d%27W1jvQ#D70Aw<;~GiD5TIRgq(Zv3fTY%A> zz6n4FRb2F?9U@2jn*j+>j)g<{jT>SEuFEN=kfdHm&2*X}HPOkDFj>46sl z&>$KGbzqi^ih!e$E6*mCcQ!DrxH+K=w!66;Od>WyP74G8!C;iBlEJZC^wP<_@w)4( zfBxCyS4qj;K!z(-qBFGdUN%6I0txvkz?9U$(uiZQf%7=SvMudWl2Lr!$G@?CGf{DJiOBn?F{#JCWg9{u8JHsC9sFVl2GUbNR0<(R+l=1={#VNpMhq2>rn zPfxdKfVT8@x8M8GIR&h3(0-4J{vdWLC8=5i)NF z;<|dDLlOkUd{s=@f60uEkD{3}M>qQuZrat0AgnLonWA4fU&UL zu^$PicQ$8^>cLy*?B_!%2UsG4#YF{XydkJAlN14Z?&_}zF&0(gjzDk7T;RBwqz-wQ~D|+N9J3cHg1voPl2|T)cKSK7?EQ;vZY<#1a=FI4rhd z+lU6D z>68fW0HjxvI}!e&gYTbQEbMfPY6fcF;@3nO2Ou_+HaH&MAK%0=c%!`hA=Wp9Vp)@@ zI-(u`Eg6}a1-VlTq#e)a^2*h0vd-ZyplbJ^N0WrJ87m#0G4_^HPeERN_&;yhUKM<^ zBwdm_f<~#Nr!P`J|JYp=lP1wvsIasY=$P|@?tnNHU(?_=8yoGZzE>=KJ+8iw8`jGz2G!u_j(z$O9Ac4hUJG;SFX%|^x)PbIg^0Y{9ofJ^)QE0@n9ca35Zj) z$-l86w_Y_&u)v&nAaLjY0efhkkn zd(HS4t(Y$YNHww;S=T%*1%+R^r_s(CZeL=Ww943?Lr#`~JCRBivN8b>1Yfd6}V z{R7o2$W(Hw;A=-MUWU|wpiNRb;B655N0a}m0xnxN4D}MD2y7qm7s#=2!VU2QK`^R5 zicr@W4PCr=k&^5Tj^!T|a^8eaGZi&_dwaZN=R@b>FM!9La4a(d5t`ADk_9tL+ruy7 zL6!2eCrX7)!CZ@<2yQ0u?2!|uzaMsvZ?K#T#03yMLq;Is=k#FVmq?-nc28m<`XTr{Y|mSd6sv}G@%JRdk0;|m)L3$@%m4sBDaZ{vo-;U7;oU^-Ym zemD)hTI||P{8W_NhY1Kj-Bq^;nE7PC9~{JXsp7gh$t4mS21S{1x!ZB-w4jY12-rKUPsprUgj)Pm#l zB8Lftfk>=gouG*Bj-NRw<5j@-gRYz1zJ1hqYFOao^#}HvN!NJ{;vum7gRr!vO{^mD znv@4dKtFKeBX1i?gW4J~=O0Xk`7L%@kfaXjVo_3C_{^aIKuBr2BQqwwU&?DG0LHwrGftJp?|(dR#yH`#lkW(nizl0-h9s1D##zeyUoYihcJ!O zQOSSA@(_GO1JR8^n=g_h4VY1O3l$X*IbkM_a6lszbtW812=hqBMGsUx0#zvbhfKV5 z8a2GvFocHUz-8sJ>~qss@-JRWLWQM+KMlqZ>jrSp$Q%1@mSHny!Z-$>@u0-r0&gx- zCBVts=D*_8dc0T3H>2>EL+5)N%6LY%ozqc`I*i&%NXs@yd#^Y)l6>@6V(8dvt5_vi zE^(*B5qKLi2%UU5vtAb?NhI-i z0J+w~Gq$SS4~I+ffqor3A+&L|XfVst)T&j1n7n_Ks(VZ|?cBLtAkbx%-ln|%08609 zf|0E2NQ`??x|auw+-iw4srk@xJWJS0ye)d^0$zXZ^IrI;=zq(p=f#V-A?>;R{%n?}B^RLp1g}q9;&&96Ab1aOpyjf4N@IhBoAB*zdqe9De4^ z92gXFi!w3bFRX(?VXx9K5W_r8b0K~6s2dDKqnz7tLIbxH-ZKuK1^fa6?S-dVO)M+Q zk#F(^!wt}nX2U1L2!x+8;(Ktw*Y^uCd{+D3p29PLD==_2NpXsNK?*qckf*T#qG+Cr z1`BDm_V@Rn!w^$+$An~pAt$pBBM^GTT`0PuTua={3Go>=zLl?j&luXQq?m0hcc=`H zj@o$yFMnA+;mp@OD)Ye)P~+TI3}OjK|J{c^_V$5(X5pbi383B0_SW~`j+aVUG@td2 zJD2K~fb>_K{N&(*JUYU-0mE$=RKG!v!Nhx+%oM-HGn1c|NNe9hmwsx2uh+oQVQpR z58|O)UTeSrPQ+lLsj>NR;(9C=4$dLSZe>s20>Gw`j=L(IHWluJP~#Ho~QS&5hQS2X&G z2PT%=_WzbO^5lH(S`yo!i+dCY`h3{WfHzU5C4$h1dkH*i+qQ9bl85GYlz$ajcQ`6u z<|j?KL;yR^EK(?-r!RGH*V*4=Dr)w*pGYOsjE6HottC3bZroBRItjNK*3vSo*{sIdmuomTTpU^3Z-y2lzvLs7S6K|qQm88Mao`i@WzPqCW-P<&@yot56|4L&W(huA&i6Rz z9S~)DOc@L_@Jt;D7VFrT>$Bc}G90-Ur3yHpEqCv}yk%dz)U>}B#tbFdRUX=#8=JoN zp!94HQ^sXvcWiiTiSuL|@QK|oO3*M>dG6QMli{@hU64hwK~=|pmRR_LmNx^&GPxfk z<8_v=SW)=WV(_ntO2eb~ZiScueLiavT zub8sZXz{2ZoGx~Ql^nvl2zH7_I@Jc`-cv}~r|YQm^O4F{cF_eRW1@Z|fqcfJaL0Me z6i>PJ>n{*q4KUbn|1r4j>-!H3zY&v?((@X^430!3>^o3Mpp}`K)~zmll{|s$6wUUKJ00an@TDwXT4`J_|4MGx6s(PVo_l*1Y1kwZMD#u#};dC)Y^;tx7omTmhot^l_mg-lgmz>$P@-&50@>>uzj8Kx*G zC=?@+exM>lzu@VLVq1CQZu{#skop7#>Q0CET(YDdWnF$@jd!qP5No2}2?eZv#go&M zY#0}7-ckXI`Z4R&Vg!RCO0dn%VfhIaSJ(bAm`g^yus3%5l|k=$LD{j09k-(E)#rAw z9ZE`{0NaJcR={ALJ{yqE6%qnkJ*Nfjk2VURkqH3X9ZQ}ahCzO^s(H|djKb5a(YS)< z8$k2mv36;u)VoV~?f5w$W=1}uq|T~3RPU?|-(s#D51U6_-`ruq^px;sd;5Sm9Ij!| ztm;U_%Sf#UFm^`gM-W*Z9zEi~e8?J9JR!itgpuE%1D(lHYOizHP7aU{!Eq~DD*XeVE-E0#vwlLN*yjCARu?i_$RbKh zSene@A~ZYxG*nvgficB|_HeE@hw}> z@?lT_hvcPL?5AGA0N~ z6M2Rz64lkY;#OF>NZu)-SIC9>k#1-2DmS?F;<#Ln!6a)H@WvRmHPdbs-F>j;D$ zf^4dMBA)?B;K9^l+7Hd-N>+4;#Itw{27#kB9)A^+jFGG|#?^(K1ficc9m%4Q#mOWn zllnMeDwH}ANY1jnAB1JekgAqrQ6MPKRr4&8w4aOA?Jz$I!M03f!P5odID$X|nxuTi zfC^AQq4UEp-##{_i1+~g6!a?OQI=i)OuQ?8G*34Wt?Cb?2MPp#MzL0W{O5#hj|~^y z;!r0-ldyR|wuun9BJ*5-{Cxu6Gpu~sF@7&eRLOcCTS*Uh!=YriJC}7I-e>u%A>!4? zrd~9u4rm=K_Sq925cYVgMNe&y<7U5o_@kf$^|O*jrEnR-9JEF#=9BY&K`}`3$n^r0 zSD;5&SXddTYvnK0Sv0A{N}~&CKJqJcQvi6igs+oNGHwT)QHme{<00`?P^uC{a?W)a zu}c1v!`dl-ravf6r&-%%Ez%W{@zxG`!Y*YsjV`i+m8h(j)C41MWJ`-C)Chx zelFSLp8alJ-6pBE<#X!pXNq;Znx&=ucRdgqDrr)S`zvY7_n&LgJ9G3mL*OkQz`Wc=HAqYA zwA@03x*D!fO4tK8N)+2IYz55PD6tqK$0RxM{>mlrgjRp=_o;A4HaRr4mm+6+r7YKAP6Ut>&Q`f%zunrN$*h9 z!7MWzbf@CiLe;gU|5uW}^mj}3NgdN12zt+{dkNN_xmtqp5>Z@7)?#u9?&2$}bqmRw zpNao(eEc}~7S8-dSmf7zUh4%~3&S&Z977Frs}JQH<9hc-a+1MJtib9}t`-XiRu*P@ z4RA?i+ybgF0PYtYS%J^2Uf^@#k_*fTOkG~}5k_td4V+)=83pflDbaCI`|)8`5lNI0 zkvuX1ww(XcQ=%3g#)`In`MwjQlPE@dhO5=+u>~e&^aTPQJq^j^(?MLjc#w93WOD%VP6^q@Zxk$`AxH0g8>X4F$b!2&j)HikDra z$tjdQ2tIPe3;g-E@a1;f;RraUapY4JDk~M~1Yn@cXsyKa=EwrG&Bps#b9juu>M!JDi!ldj#f3p_*g7PMaq8mZ zx3I1t2+>QyAT7cWlOFbidvdOeS_lUx2ob*TU?Skjf&IyFdG*}P^&r7Wn`mU?hL9m9 zwBZU4)i`x^Z(LTb42P|%`Z-PX9D7sGTBAu1iOss}_G{bFtSoP~+u&j5K`QR6{4 z4H%k`S=P=uq!yCZGEcPQnFk@(5)}9@QVBgky8k2H@?<+_ak=3LslwRt8@f3JI2j;2 zL2~RN-W%z4%Z^qr6A*aN*_q_6w7A(2<2~ym`J$G-DNsRUHUAGyE!Xg-`hj0$T=6|94(te!Tm|eE#?&4}h@c}`s z1#r>n!--dDmW+3{3&1Gc1ZF?vWn{wRH+_PF0X^wm3mzXlf?9mVkrH4&#JsFhM^{Ar zC!{ltUybGfRY8|7sis!e5w{ifq&8}?;R(pA`1@6x-=dq_CA9(3#x-b*`M%HK&q;Ad z*jcA=^Wy#quBbSgfA2alEjsw#>`)1sz$t;@1Gcu|@N9&hs{mpCP^$9oLXf)7b%$5v z#R9vshgTbtaO)pP!uF@V?f_JTupFa9_6x=7EMrFb@bKQB--XXLFnT#0=*(*7F))sc zvC|&{*0yqdvK-+ckRH&CV!v;rd{wV8Mwk#8?4*v}>lpY-3G<61!P<$fUvFR3Q~{BX zH#CI?zdA9Jl`^@YU&^z5bO%>`2L29qwvm9JgG*6*Yd=_>A=`1yVHwGb#%0Zzkky2< zrA+B3TlOdSLK$FY*C2+B_1WVJC#GIAXg!gEF|ADe+cmgpHUmpMSCQ(jj zC$3#$!=`YXytuVr6oUhJzYiW*LybH#Gj`PP*O56hvECJKd~Ya`LKA91?EKAS`GAq9 z&=^v#^{^LW3#sPWTovOwwA}l%8n7VT%BxrHx*uq<@ehO){6^9@Z{8&215-DyJ<^~` z3L|HT+3H<_)&hEll*)TaNqpe;n*8>x$Xf(11i_@qf{JW1Z1KDZO-eyRM;PV^o=v=w zrcJ`lxoG6g*oi(xf#Cu-J2b~iw_?DA$vaf?;=X=}sdfA<^TC0~ zz>?!3wK7ysqTO~vPZzgU-m~)}GTT)S19PR*uXq+EBo4G%Tp<`uKI?N*so0jXx~YPo z90TB+MkjY;93ViNeyH1*E8qLZfH@3!fBc4g&?Th1aLGRaaFbkpXvK(O;*(CE`O-fJ zbkTwh>9Hq&{|ph95PPHcr52tSf;a~2V5r9s?lUDKxxE&Ne~nx{Z7(}l}Sy>Mnm~k z^WXtuXT=huFV~4=yz8$5q)H2i@SBuGBg|q2)Q;nT1m{>kVc38qe#Ag7Jl7gvF9)Da z0jVw1@c}+1TzuKUZar=Z$TWEfEn9X5jp$0LPvVK3f0@N+jlL`lAl>x<$O&>5+Hm|_ z?{8cUQNrt%pmNv8`)b3iFp86a)P&Oeup-GOxc=ybUgj2vQ%Jvo_^pmcDWu8cy$8bU zu>Jj2=CrW{DDH1ynjUdr^d&Dw-2i(EHE(?+0LxD0k&gR)o>W0@LOpJAiN_Xtw@5RHXg9vSkJ9RT1~DsflPCVWTj|F ztVbH`g0S?)11to25cj)V26KTn;ra5zaVF)$v#@69>o^`X;yaS3pP=cBfHpZ3i?Q^` z3|=DD0#rLvi3oL8JLvrqogG{MyB5}qZ1wU!wz(F&icZRTz+7+*zJ+ssau04}U}nyB zTNPqaSpEeK(87dXoo!V-%Vw+Q`Z0H9|6&GAxzVsGygi9wmv|m^YizJ>o;KY_Ko+$t zl!TTY+zVw7rN2{wi&~e-l)a6CN#Nnwh9tiw!&2?dJXCfBA%i?kEWmx`2xo@^A)zY| zn&bL|rYRdzb!dTNA3kw$clWRg9ho4B>VL)0BH*80J5g++89@Gl1nEQ ze8&J5HKNs^jM;03=vZU&UYOJ@nH|kwN z8glrAq3dZtQAhQp!G-bG_EH5F=FHvw8190XhmV?kXD7_N(3L$3SF;nuv5)P^!~dK+AZeohX@KD z51W;V^HN&saS34-)_0%UUKl8jPrW{ZJIcCZV6~ZqR!?{LS*ZlUhY%g1`pecGO$?F0 z)rlM2SXk+71oLdjZ3S*t*$f3}i@5&P(beU`m5qD>njr}R!b3nX@fR*|1Zgo29y~}! z53pnes)0G|8yU&xn+g0gu#u2;uuf?!YPc~ zh65r)j3*XH3Ly>Wz=2sh`f>})aI-RM3LpLTzBO;hS0S|C4o)% zlu%F_Tu`csLv{YRMx4iMFNk|7&nu1;N1ZkiU`DuYupw*{h zpQQ}hS`;4$a4YG&{bB|PCT|#E;45DA=l6^f%pLKGi7bdwqk2iBh<{5C>cMc_aXeDRxMB}F(2#j-CoNx`%-lTwHxf5HqztB;M$ZuwOV+yn6NVwYUUP!5~&EXYK;Fq@dG%Vv8-clKXGrO<8%GGJLXt0boH|@^f;82QLPbAhVLb zLi*W{BZ?w(ntys|;KHIfPh8g_asnOt@CX8!vq-Q$T>u%+7nI9y(e=oRwm27)hZ=n5 zw`B%q3*MQgYncV1k%1@q0mK)qZZbw`z$z_itZjFqhdwkNWFth8B8i;5uJ^-pal#<( zR9eYhmZpQli9yqSo|mokNBXXuAs!xlC@ctQDA^aUCB`ogs~19(LaM5&Fm7kbnRe_D z8XF)tB$;TULfk&y3QWJxe*AP5w+4WZ`1YLfuUvt+nxP5;-EBQQc#V@F12n<1d_G}j zzJQVty{_4m5&h;|hGye+BeA^upgM{%vKP9y(Qe+Vz}kNJRJ1*dqZT68FA@iv#LAUM zU6+R!LXM)hX;b*M$9abv2cB-@&Gp^m7()10 z=*~28_K6h6DB!58!_9lh%eXAq-Z1h4Qd_Xpo|g%9a;8qruShl~Cmq<6qvmE@Pgii^ zTT}Ee@H$Y8s{WT?Qb1>$78UPHr79Nt!>@vkcdtTDegbt?C_ulxub9Y93yeX7ZBC{e z{CC6EX}fIW)VSz)+Qn5EP-R#~h8TQ9dneZ;NPuP{!DgrCkEnZ)$~c4t zPaJ{w1SD)&SlE2F2bpeBQBj!c6NE#~L*b>A_PRKE50+r|xRgWEQvEiAC=(8iIqI+h zRdtoW#*(wuS!X(@8Oo_hkUsG&r&*XE^bsV6ltgIMVVrdW0UG}m@sw>t67EYO`9u*xKH>7_Lkbb-BBdS4)w!m+29}^*X0ppFv4|H)s`hojlMeoqOc|XQ&l;SrnUb-|46ngvx77ZMH^K~-KBz+T(Xulp99c9O9AAQYLOx*g=89?LX z>;KWBu=u<{xF?8}b$t3qRN}kkC4}mWty*Q)wR?D>Uf?w$)LHmH`6ZjNYv$%`m4Meu z2U_zVjKGI=h5z|EB%2uUUuT?Lrry0Wk&A zW`R|^8MeA$G!2B%(z5~hBIYC|`+y2<9EhNUd9f+G%GPSYhXX(8Y1XzR3^a+C@<+Im z+C0;1I-Ypl*e89TK3yw5xVX6ibr29$L9UE5XU>dcIyw-W^Wg~^Qn>RFK_j@}83qex z#QU=~*A9K<5)g=hm^=fNf2Dd@#=P+PgfT(QLlin1b*zoac}vddudkKU#vK4Xp08J^ zBlSF$zMd*K@>rzU#A(=bKd^Zz<+rkcK$mxM1!?7(gZvYdl+^wB&8Na1kkhG_<<*Xr zNZ=+&RZ?;eG@>3wcKw*`L&*qC{ ztQ?O#ZQCvis*|Gk@wCgHgFN};4DQ>bC?P`w0y02^$l+o751a|VC7ysIgwT0Cl-Vl9 zVA&kbXC1`%FNXa971Y+Q%YP4?cX&B|-^WsM10>T)Db^tcHntDxXSn6dLPh;2-mxKC zFrTZ{BiIA$9al-7!-XguDro(2BX`;WVH4ZLs-sDf zMOsZKu3S6!n5I96hux{hVKE=ZHlbb)mjBRXd>S;D5Kv3?<*`h3S(q-(XwozI8<2$= zu@S3({)wB(pFmHayuJq;J{l>CXjcmK0~KXt?&AR?HU(Xkyz2A$s>;$h$LeObZ$(WN zj1)Z7t^`&~&votXQ1L#Wu>+EyzlYUoR*&Gj#i-~pCjlvQRBPkaEN!e48DPrLK+PBe zK_pj;7Rzt}G~j_4Yqy0CA$(5AL@L#@?R4f%(WL-uEgPml;5C(E^*4V5aWb2Y&J~Wa zFu;+5s_tfk)2Qm^K%R;h7dIuUNwd>1Ye382-#=*u3IA}h0d$1>B3F6=+o;x(^0Tjw zbS_g-+bovHfOD3LSp3~1R6_VIvlan0P0qVgQJk1&qW_(O!6jJwC@m3ou~}j5+D$$h zv$)*)(5QPfEz&mUrDzObDT}#GV$JrHG-2%`LoNQ8Z7WutfHfq_f2W)2Qb% zIDCfCsu45|MW3%1yS8bZ@~R*+Wr)e@poHp_kg?mo9eW&s2A4-exjIPI38Ic`=~A0P zdr6?XAk3BzC3M9l>s z3*~O$Gppl?-}v{n2CzXc>Nfz&H*5YzU+)e{C~%QtOqxPqHpQ^eaUrtakAVXx#042J z%6py&!Aamb>dAEoHk2b6#nDk>dA;%(B7Ggsn|W0n`7A=u8>uM|YHM{-p7H|f1mU8R zYHkF2o#o`olT>ZuoECyOxa@9V2Oic+)abAzv$;4k=vib4jZV5Q&q@lw{SCWbAmSWk zqbuOd4ZxBP2KHap*)Zs1^e>;LZn?#Bpbp>#aJ-+E-PLyFIof@S;(`8x6_^kUgHnzF z71pB-f_=ExCw3>EJP<>=?7(|6Gwe#;gRsPCdI1bLSvATu!VzgjP?w&@Gbx;1$91R` zeqpS*Je88VXsHpKh+9k%H?7yA<^{A_5%W78Uy#1NxY6N-3o!3;apDj-W0Xk-Ko1Y$ z70L%11RY!#S`zkbDwagEY0P@xDuQk zIN+t4Jk=g%oUFicU;BSKSXxYCaS5Ut^Y2^8K+KLlbS>VV$cb7%js;e6k_R;hnu_=~R?cDoq8iQ>plEWYs4#Q*T*HYz}X zZFaZ|XglB;P@41FwcEQcFD?kwUeE@?l|D4Uyt_BiOF(e|EphVEeHg__vUP;hgtXEx z;Fy!$mkx;oy%NH{U;733>eOBOLvxg&Pb%_UU9enl>}M>n{Q)u@T3aVyGtEy%E5`!Q zEZ*JO4f$vP-`7GXhc$;UXO{mKDi&P6A?W!BQm=#o8Ns-z3ggXFAv)sF!nOl2RI@WM zqmf&vx2)2rYMWg4@-z3cWrZ%AS*;#qIPhWyBS|MhKH@6$n6SwpP+dNR_QNjdG)&$#|!4_Dty%U}h9p&<$q~Df2=dnG~>8cklkl zEHUng;qPsRlNCj7%Ja`eMoyN`TIQB~RQT-eD&v*8_gzogUw6;*ALT8;knuftfv~0! z+_Ha9eA?U*A}=2Yb&SQ-7KK5|&!~5J3br~lmea(2LrF|`|k2S5I&Cbd~O_=Rd zMYsto3o8eQ>N(HnZPyfDp0;y4DT+p%Vhpy2WtOE#w_ENw`rot~bWB$*bw&XK;gL~E z_V|R!wZcDWc+(>K8v%kJ?FdEgy1N)nyeqQFhA)o*&MYnx>G0{82QCy`Oa=E)1 zOagYk-|Tc*b8$5$VW8HgTGPg5j*o+~vYML0k$U;PN`p;Lo;+_lTKM?754`)BS#Oko z0`Y;Q8~;m%wG;yAXEaB@k(x0{eQSfMT9cELD)Za;{tsDuD1s<*N3E9pogqILc&tyh zEGh4tkTkY#-5q0vo2wJQCs$?I+1k4Q5-gIRe)SwB3{pjckOBXo?rh<{V=e2I@U^~u zI`Y)!T~8%Atx9!6r8v#&YU8gu=xGh>Zd63#2Y^4M`N47?jZs=_b5M z(yrVM+x{*z$7AC^kKgtG#*X@dh~)fU)MWq3eQ#J?N|_E9ciaJ~2tvY%S2k{lsCT}K zka_!fxyj%6+hZ}FwEY@1pOT%KU#!D zp^_6t2k`^;CZl6k& zp&&<`${F~^7+L^bP^+LTr^*xo?e8l{5&jD??0enB&{fe$_}y(eM#yDuk?nF~{fMAF z){Ht`5A<6Eb?(u!h2tF-`TTk(w>MJYIj?Ec@s zjgM<1wd4SXktFRy#<(z!9b}c>zH^5#uLwN}Z7``o(x^`KJa{M)si3h9k^={qSD+@x z`3qE-ff9C`YyXbMz?+ny?gVs`B)rI#D&Se>nqV#!v6P3dX4Tk~6r0=_mzyu4b_cC^A2jwz?yq2O`24z?_hUabUbv4FX{p448pI@R+ZeqDXFNf-E*lTJA?P zg|l`pv?$qg-E}(K<`*08M7^PQZq18It-}g9D70-JD#>^X}8}ckn_lnE?SF|Jh4gb#`!k}mn54bHfSYK9&(2C_r)71at^d$wdDYqN7(l?>dWU=2Z z+ykioAAoRge!aE#gYG4G>IM)dDw}=;1@j9`1!`{KniM_ZXVf(xPAdrekLB)3VKYH( zY%6aaeJ|$kGzh$}dKzAV7JGTlR@r|1bV~aJdbWu_#yDe9LgCXto}Zu8o|oxT4}3F{ ze0io?YwzZjWmgq;yv!V9<>fNA(qb_;TXICN{vabZtI_;jo>+L`d~+6FZ*8+5S}#|! zvnS^+E4><${O_R_l{cd2E`;WIJ(W+$nfhFLyL9zulS@Y@4o|!vd9+TJ59G_yxuvNG zI80nGpnid3f>hSHeioM7EM2kopBu9z)%{d8<#=l2o=+7+A{0S-q^cXE%=l>x7D+o$ z8ZO(He*S!6g~p|QMB1S-N&B@rag0w%SzLwtjOLLqK|3!)o9#6*%lhMuE^EHG-4WBUC}w6 zd}rA@{&8ev?}NO?3mx|`{;OhPc@;%_L`MEtx6cqQCJi{mxiNZm29f3{!xeQbU1gSJ zL`1nR*!k3pX+ax(8n!`#BxeWC&w$_=I6cKpWeD^*nHe-ma4HOb9Zw7ALm z$Panp+M?`e7pAnvO)&HDulGBUaIgM+y~s07=-v^I5c$F>+;}ky+<;I($WcraC=BN? z3E4{tCU4)pV@Wlcw{+?F?mduJl044L&Q8{jL$9N>zOD{=$O~}@e=C<$P^brIOgT^y zdd?U78s&*cM90^MEB;$~%cDnipl4~8mz=)Ad(e;(=+W?^Reu>3>xQP4)?-n?ODMG+u@*&HH^o?goyv%I`F1K?kkTk12OH2`YeJSM z{CaWi;v}8SKoe=tiD!oA$Xfz;8I8Yskr^I2*ZtWEOiIP5mst1f*Dsw7UnDLiQH;V? z;1DN8UtIDVU_;yA&TbyRR^Pw?tVu;LbEz0BR-Hbyh5$jY@(ZWxf?22JRixT=_B=uL zioh1@$jC=4ab6(Y3j9XsZh-}D0Q*R>5Z7(W$DY5b|h?Z z{5h;}YATUi=6&@;Ai!$ETTz~&oEaAW=hbcRIru=pEvi_#&Q^5T#6gOu@9V1u#&7N6 zQe9NtYkc0ZqJL1`mR-W|npsn`Lgg&V3Ahdb$|xL`aUH5t{2u4hcgp;;`^%E$IRDpt zQ4M-AB=xPRLqyL8w6wa-}8$c(7G4^FV&BV6s)0jW;<5l0`)zSlm&%_ z^_=zp=AY3t{TalDuSBZbblZK*ml7K7>lC#PvU5t5%*AesYjXA7(jJE~A3&{QfgT@2jls(pn8 zy}%c9-EnweD06^-l!Q6(XmOk@Pzh_JN_#R5VjX8IW)JPyxJKm+b{}HZvYv!BufSNl z>B$i^Z`LtUk8-QSOrfR|@Ts_9bGxSkb(1qBOBX61$9%acp>npeue;-nrccGIzWHTw z0rEl<54_*s4>)9~uOITBoz+7KM4@>)OFBTJ29aYNs6Vsnwe(niUH9_^BT+PHoXd^G zcP(u4pA5J885a3#4j(YXLppW-{CTW<|Lu5(MZ!x`&0#Ns#i<}Q;PczqjSo?}>c>Qm z*8Y7-fz`T-DL38SaglC39U7`WQqf==sD6>;Y4`s8mY!X;IdDkoAn?Fio~`S}5A*Bk zWfguHZ6b9|Wo4Sp7L7o>sk^h^d!X0runT|^VBf3TT&RF0=wL$4f=h@dh+0`$*?quD z?L%S)u*);N!l|3-YpzEx(KvMId{zVa4F_^3`DNh!BEdL<xYWd0nQ^PDwy=YJ zrd-!?Y9B&Tc429>iS@Q^a~NZrcjIxVO$xcMUy-T^(g<=U9mpT_D$>?tx(me?JB5Yo zt-$v|MRjoy<#Pz2ghVs~7~o?SsKAj_ zafY8SbsJD*+&X2y8@0*{M*_AjR|(R|Y@0DVdzoVIHyIgmt>d_J7$rB!DWQwvUB}zr z=&FJOTz#bQzUgvS=3c+I3!%K;j)$6UkuR3gvOQ28v1vm$_OyyvT3UurOphL2b5x(3 z>m@rjzy9O@7=qSA%-v>*`&ymtiFq(inTk)GUv#{$M4pR z9(y76QUq^MkW95dhO=YqICU*Sq#ZwSDgrKN^bnk?Bg}`;?tZ~9Vx+nKnAsS@?t*UK zJeY9r+Zf0NLm!`#7fvtKe3^D$tJh~P5W-QjP z^v~HR=J|gb&1TrQJNAFFrHD_|(yIG=FFcccxyF&nw1XALX0Vu+VhXAPnT~q4C$~HV zi_d75Q&ut7^Ps|%w8!Y&VQl2*A1^6!kc`Z`c8SrYrSHB)a>N$h`8YZSNhzt*2aorx3GnAu z3!dKWZ)KI#7$2^C;uUH-gNyer|I%tHD3C=o+eD-x41t)q9;OG4rVgayDfP6KM=V_d&*NYAH2=;Z9Tr5LQ!y0%)_5_`*d z^JYq^WtlgxrmM?lEUuB6f>Qu9M+ndBJk@oD2cJh5)HgqZeIyKBPM|_#++Vbx=jV?f zVhA%p^?<8C4ZzrU$<_QDj{7Aj4t%}fDuU$?*Z$cTPK&u-asbCcLkZG?X%|yB*Ge>J zuPgb2#rmK&_8ZN=QpXwbhy;csBO_^lRx|+HTbOGRarP{Cv2LdS4Uj>+yjt52%$X>V zWmUXzs&ny2-P*R@HF|zp!OQkM8TESmb`U^2bA?V&q@B%e*+lll#6%qnTh^01H!`$* zO&1nSzM2>V6{fUk5kpJgDyetn##ig(0oI1$&>H#)$qz1Ymo@!CI*N?W(C zS+`a@-!kiNR@MWTN?ZXKKNfh(KTR3yw|CxB@8;H)%AzX~^!bgAL04z2$I0DR)>fGY ztrZW!O^Em&-z~iM+X}c%+HQLOBetq-{e0bb86_45+0z61mZcscHDmiWWoIp(d40<{ zY%osa-YucCPcEAAE)Mk6Jk~E5sJ^E)9J|rzU*{FKqbnA5e){xf^VWvcAL_?X3Y(aA z&tV;YBii3}P2%01&))BUF8v|0?%bHbR>lc`YNgh^$(6g{;qgPHM{G$3o6YVgI*%(3 z%J8jQyv|ZQq5sZ|!3T#Ue{6QvnkqJw-7x!g_QKVhj@ITu?Qa6+GuA+!k#KR{NGV&@ z;e|~z0ZfdXBOf9!SQj1Yu0tQOWyKa|#^FUGr*$+J8I1VL3m5&q^mX(@@s#ehA#sOO z0lm=~H9Dta@+-HmkvcJ*F%YpL&TIa&xEKC!6u(Z?#F$HdySDlD-MLR>`4rXm$DBGb zGkkSQR<5>D^e}^=(p~-_KStQMTlL-{wiBE}EDWJf?Q@Q{n`?M=ySiq)O~1lm+zDN% zUwrT0mh+#&V#V+%8TXUN^UX#o1`ZEDak%YZzpqVs@K!hk$Dut>E;c$&@2~R84=6)V zw{_94H}`|`e%y(>Ds7%0SAKK>qtDq=DrRyl|EB1h9rgUbrxZM|Xp|Zn+&Qe_C86Y~ z*IH}HV9cMMd^i}+dQtu3pYnLt^{Q6|V{ky$`-x!@K=}Aoq@qDd%d-+RVm2ci9Y0_2?@$ z+`e8tkMXXqRL|ey?341KfA%|n`2_7QPejBT(UY-T&L@Mm)i^vPo3nFzs%DeC^piyP3-#9-Me&!jK(3B`=ec!C;7aWwj2MES1;dIc^YZusuM;RYgFwz)5PWwpQ>Zxw%YY z!~I4F;x~!mS($y%lVO<2@P7F+u6@7MkcnY#yqrbThG$yez@xiPmiNo^gT4p-8e&+7 zUkNy#sy^;RXRm|e`0j9tOf=dCSDgobDx_Jr83Jzy9r}y`o{i!%xn+ z8OLUCehLtD-n4ACOWkwzyK`ef)%HCF!}R;yJ+;m&&MV#9Xf~%is{Q8Jr{jjp7H`&V z(3|e`71_Hj-6Mf^-(Di;yAV9jC31tTT_W%A_@_6ssByOjHVvDJ%@iF3(lS84NqY*Cf`y8$6p@1!tmm@Vg6fO^j z77A|6uMGxh;DQE{@rh#svx=N3dK|`WG{(uAtLSgf(g3zc->|$c^Ge?f{1`~qDGC2^ z3H)QkX`mR(16g}yhAc0LgLukumx<)tu%p+a+*NA{m>yX{>UM-U0cg?H*PokcocnVT zeTVQoCZRd>F5?xvhnXjlUfPCin>B0qitfhela-YP=JZqJ7NS*DP<4{E^P(?uc~G{V zem?|JEYfr=R)S4ohkz8>Xnhani|U!1$EU`G{{73H{@-7|at&d|kbsZKqi~(iu&?l$ zi#93mU{AN#=iX|Nft{V5D8tcLvK$zCPDY2fh%=+`;~?l}cu;7sInc+D;x@YIy@Z!{ zczwrmRH_U#H{vNR3)gf%+$O_oCYyvKZ^;Sa%2PkfMc#K;p4pOUvMo)7=3-C3zsXery#(ZSzg zjchp#Z73<>w#c{KQ-z+6f&VCZaw4xwt+WaW1;E7^1h@VeyV=U1=})Hsh&u-SbR)nU z`5)K5mRVo54Z=`^5eFL!i(r6g8n@Kuhv)>h z6Eyqlm;8o#2VsxN?yTGKHbj7XwnYoy`Z-5kT7d{N@I0OLnW-qw`Q#Q#p+ zhS9=wcpvFl^@zW>bh zzvQt0hyUR`2Qj1J&0DsJFc1Z`KU<@_o;`bZcYpuUWMIDb=}C_N9wMwxWmG%P%be6P zs0Ls0BDYCRd5@MpiT)OF9n&PsY$y`jz}}i6ICW%OK<)+vdqx0eug4U{aW5i8uOYx6;efAVJ;cA^Bu%c6di)(F(GBz|3###-!?N>39N1C%6~g zc*YPfuChOQ-p$3~YeM)MZZmeJ;lOG68{T`?2aktK;p4&x0G=EVh#%d5z1QA_KlJTf z+WPqGAtUaV?%+vAM@Aljc%kBax%E~YFNUUsK%@-E0FuCpj?0_vvo!zhQdsA;o~z2m zeg!ecQwXL$^W=~Hw~@)cV?lII_pIQ5J$V1)Q#ottQaPk|BX{@PwQ;%=o^bo;zu=en zv7`>LZAbs`z;OHbbNCtzzg_lATk}qf2dZrCm!b0M>nHJ-CYk^JJMlwGe!c=g>Xi^) zQlVDp>q%OJ@oZnjNfa!1tE!{`tokUa{<~UzVF<8)7mUHei?VH$W|pRHPU?O4eQB z)EizH=8*+6W+tK7vV}wG1~C<}TG5&f$?i$mTT8<{ghU)(UQ21e5lEJTzpG?RS$u_y z{=JWWB9Cw2*UwO{7C>dE-u1jr`*JdI1+25l;t={hGx&Of_K^Y0uFE4Y?=Ndq;3TKb zDCp<2!W$Cg@VAGDPvWfbrA#)vxnNUlX=?|9tk=Lx=VuskgI9vZQcW0Rd*ptBuPZIc z!2hR)^QDRp*zs@9f|fCB+meB{AZ4vW-3$8;NlXAfv_BI;M85HBJw~WCFU3H+bcndm zJkA`pI(RUl6_hNh(hmZH zK@v#01rFm#hX{IZb)GGXBM6)VfNK@>KGq^S9VSXoL9Qj85b9|weF@>STEFB}o4hH& zmt$f<(V8m{JQTDWJKs(wAZ=9)7*XIZ@?xD^a}?@WVnkAgkp{vD_hA*x0%@@`Oz_!x z#ht%v=2`i6=DZ*UcIr;Nm8}z=9wGHPXxa~Om&tg-=FXu>%6{ErNb`jz;VRDDV{iFk zw!UR=YAKI>9^<%c0CrF5_lhIghMYJd;n!cYh4wE&`f(H@>KA!)g}I!C{hT{k&4T%? z7`P8`9h1YH>*HY2mv986X5iOQory5Wz981GcLMHRDEJoFu3sMqH6x7qOtRXg1PQ?MRKgV>2^kp~?2E9AAJ81XwrvkC1#zw6M-gPEZvqqF^Yi(#N(wCtse+o^s02S>|N>(Q>_b~)wy3UlC@w<+q%$x~#E2v#>+`McM zCZ>Hk9j^zi3ZT1h$LZe;IC0c^AxO#)-hz^N(tmcoNGAi%-g9eNNIU-A7Zb!BmSw;N zTn>C+SX7YSuanN--8ilb~wh&_A{qel-uvt!W)mC%Ez$HoNQKKO29Mdstj zYvM3OF~MY4@aoW`)3e7i=#t0mMC&LGO2pA<`@=6C7Su1Y>@D1WckGw|cW@BbhQ6DR z$xtvLZHYh?abT}w89Q)983Zp7CLnZ z2i}}nFM%l(nHueS3DY@k#$-H8H?|!DLo6;O#oVPH^`!*XX^>}EPSi<*hRo7hAebiN zl9JO9RFZN=033@V;=$YdVLRuA|bOy}?yS{hDWCK?)4hrSi}B`0kTIC^DxylC?tCMz%Qu%T7X z{=89ES@{&?vOADBWW($2E}V8_5~yn}VLVw|{? zLlP#}a`cX^63=IyKP{-k`aBf86_X=dFqMdIy=9n#*c1gT!B8Gxe#}m^Z>w!=F7dZ!BWhZ2nKaf2gA6i~Y3s_^+A`NA ztOB$=Qh`0Ww9L{)2$5zp_yHfALzTl2w^JgMQJBus(D>Pfg>iti+G<(`q@ktt{Ek0M zzl|mIwcojOHXg_)`RiSz1$w>Y{Ens&jyoH=d|029cCSf8zG>?0eAVOvvks`IL?FHn zJz4%`ZzcJnVp@eW%XyvgmTpiDLLQdF{xw}@n&aR zAOe|LS%MkAGk1npc(O5#qQVc18Zd{0A)9BZR;zd6K_yqNOP8|O`y;(2CMM4J;@K|z z3ymq5*_2~RC=TI=L#WddoC5F<58jWB;UjhWk`-J9VeUTCm*9QC5*kGewC^JVB@SA> zsjKC7p3&^82L)h*g@r}i)(`lmNxaX<^&OuKWL*k&hG*n0U*gH_#KITwS#tUT#L$$kgDcH>> zEc!wq0N5ww`iKBNcoO?+*9j#s+qQkgF3mv+ImmM`HOQb8?+Wi;A?2nu+)lAwDj=) zNgduXw2rhBd;BXIBTvJR;9G`?DS2IOIP~|6)*ZWfFS&|sh__~N^8=}*=iWq}CXm3& z#FYIkx)%RkKk(@6U?*N1Yd|t~23D!%sEWxV;Bup5Mj4AQchMfljn@Vt?%5vreTG1? zvrdnt+;y_wKpk)lS!W>CB{0R(Q;~(vfzRpF9KNKXn3PZCTLZNR9<%D0glNv%FS)d= zcFmd-mFJE*#71{GhF3L)R1W6Y-{+T!IF!)#ZghI&7;2r~h3|q2X9q+)VCm`E`Hy&z z&jE5bvt24L$|=>x1M(j->)v9|IlA6(XdK>Puht9B*;hihS9mF`4f}X@&1?{w*TVeZ z-4pq}kDXPOP1>){(b9q;t4CCFY$6NTN82HWLJr%KU+X7Ajd5y!ACO0KQ2;PT_GDh~ zZbUy(be-z_*mqUwFIf6vZ5`9vhWxl4gfsFarOCVBhi7K0tDS{ZA0j5i!DY$oO{7@| z8%4%)$KDD=o+|x5Nwn(wFvQ24ULGM8Y=i#2_jz*-&#kFx5?)uw`o74Y&3zgrXP)d} z*;`lyz>bD?hkC(iC5M0fYlg_o+~~X~u8!@(U34(kVRDPFIj}wW|4D(!!fo(b>vH+) zT|gj%-5L&S)Uyhu813}I>_8Z$MDcXpy6}c%@zLy{5L-0qZO$R-2D$|g5;i8yhc^=Z z>sw%Y|HAqOs?84_HEa9&U?mj^r8t7fYXa%OR;y4t6_;`@8Sti+pbb2?*A2d1B_IT! zgyoTns_MO4J-ShHJ)Jsoj{)s~`n7B(JRQ^LXTxxtuHVG?9HV zv$XZjJlGi-VU;#WdhDo=?fy?j>>~F8Dh+}yQkL5ivfiZaqrj?|eTQp8N9uG-TUp8j zogpQ_P=SA<`dLUzug*qCtIeK$qhXoOOK!;^*aLXSdrW~eL>bLiAVA@40E`Ja{IH_hp33X+1kiU?ggT1F7|EUI$I zfzt)M5VDWkfHk)8_p8trX6r84p99wb*l9H3YA`icvxby*(^KASEo8cK^i@{-ks~Lr zUIu;-sFS+2hG6r;%|sbRv{r}BfJ0$4v@}(QLhWf1qDU@3`RIpCQR5*{5!vnT9g`Z# z5%V`PPk)%BuRjAm1pIAjyowuXSomuMWH#yMoe__*g5vvP9I>mT)%nK0i`BcDiL+GkX_3ynT|va z1)6-d6-O^ov+JS{K)ZCMWIlK@K>)^Hqr9P2OJsBu^jb7!30+5h92}l;6phx;-GQrO z>}&D7l3ymo1?>Z<)B^Es8R7;l>a)foHa0)?d|Pd3!`9=tbc&> z1OCR*t|L(&4<~mqtA(2eZ`7s7Uum&=#kUg5Y`^SRlMDOhl&u+K-3t3n&+ap_!J zt(}W3#GzZo7@FM*>FSL+lH<;8ZttN`8&bP~kbCtO4V!W_iX81D{P< zcFNGk!g|HH>&*wy4af&0KK9zXhYAGRGGH}VGV1Kv>X#uL zmLFJhL^9n_f4-N;bZeM_D$%Fx8l2Le%UdRGnuC=d^024B3sCVH8Y^6zRO_ z5y~qfP;~6Bn+fxxIp@}7;hh%pKPfHpd~ZX1d)#y-C7zu6;acbiltTRlp3;YfG#vX$3u#>KLp)$hTs#l%%$*+3&&NVixR$5uxoD>f>~=&M_kb=RD$ zB|`-}FK;!Rf1u>H&C%5kgi*u?ahluI9fmb!AlxM^pkw-ASh78}YXRbEF2l&{?YS)- z;br`)A^yW?-i)(RQ4+8NngVKXFeqj5gPP-A{LS9iX2egHm)A_(G^bGTt=6x}MZYvA z0A$-_PmZ5o0haElfQnR+i8Q$x77M605bLo&uPk_DTJ=9f#zqddX;C5Nx zdn3Ab8R%iy#nRg%nEN-wNwy|oTyte@7PK9KARdyHT*ZDoG~oZr;zieO>AQ2G_kxCW zu#b9!+8yYyLdaGS1UqtbHXQk2Ndq=KvLumVFyL20TWhjg?X|VER>Gmn!pbUSx}Db; zOticZ2;(-*A+pZJk86wO?s4TA=<9bEi%4As(Td705Y2Am)*^B?p)c$1?w&08-L($Y zOaSOPG-0Au6=)2ZTO=^z5W`Ml%6xPXaB@)vzq9zV_h(8eD6AbJmHY;nWASq+)M(4y zMBE-LTm;FL6_2;O9R^@XHa!SdjrHbh#Lh3bv zQJY@z;48$03d;;^I_kdh__iQ#)&8+hjmiVh25&KH_89|gGlwU_n}^e}t>NmnDBzLk zv$fDMJfb;Tr{RfaG(ytdU2JR$xF&)tl znaKoW2M8QOVDQ&c^kqbcij&mP2``7e8vo3L!J#7UrpC#&0Ig4`6iCT0G(9kn8+w3u zqb~<=9NCB?N*Oyj{Yaxb6xynw11c_deVV^94b5c^E?PWp1QkWytry)o!b@Z;*Y^MB znueUBD^;TB)vW@ngX1(jMMq5zlt_H%o_KnMYTO$+{AjIQuDd|}t=`N~Oc zU2cyMw-WVkGbXfHI@oS5_!HF!%ug5$(j}q7L2XF_N6b047WyzT8KO!Rzob+_GwIBY z133T;3qB7EXvG@(2pInaBR9=}{I(R9kUh}SxePqw^{;?f7_B9Zn7d1++qZ|8T(5fI zQ~uoP1!}{icoL2A)2Ya%Pg7Y-b^wI7W)BZ`>9h;R1Ej_kzJ;zv&CA<+0^Z;$plzng zO&5rQ$apUmFK-We&WOwM*B#@=i~;pQ~K8i)BSK5 z_i*sRsHjP6-riVL#IKROo|=(|jk7$=n>1ioCxN%Z#L4M;Hz5ijTTg8>a=kSC$>PNd z=JyCS1c%aKWVDGOs=+JuBqk<01T>Ml#R`Z-nM1o!Rz_wvn#Fh8D}W-*hu+Jv}h69B0OZki*5`BE?iD#2b2CQF$f3cQQSg9V#~^J>snO+oas)rQ0EYKZp` zJMiUCYQ`QXa{Tv?_PwNL^?!lyrlk;neE;Dw?Lg7pqoc3U|H%{w4fgvFAF6`d@vk4A z-2Q*}A6`?Zc!j;cVE=|=%?iMB`~$4waU#12LywAF6pEmKaxKn|9(pnGV5Z}G9UYyl z_reR#S2AjO$B(%}I%8`3!yCYAv}OXx2S}YNJv}{WUqtM@nnmMt1&DGmKMnxU$tj=ZitfcHnaiLB^oJ3lzkKKSBiJ=@}v@@B5ZvTP0R%1Y_r3F>bgEytCVy-Z-)R zJtg6Y4O8&2hIx=t;1&g%#F2~7+{yAZW{XTK4YO55K{5RxWOWeNml6kLU}UQF@o-${ zYwED*Ix&;v7xZB+$o0^ifq&QG>=vMqVHn}nX7)X_1O`c z53Oehsm}t1Ijp3}E*3IWYNTd~2l8ERZJJXs*x-w)%}n6*WCn=c!%YZ_>NCqBx_OF2 zgI~>kOl@dB3QOg6bw74BS9Ws z7g|$@zs!lQVyuq^cU1|hWCs;Z|SIeq-_e0?P5 zXsA_PqN-w;6EW}GT6EX|Xr5x>7ud{^X(811UdR^Y%?(i>?eL=E1ldp0lmomSGO<~6 zV`142OsYkHy=!dxMg=ZO$I$t-0JJE^9RgOuetHNo3+Y-65-?i4h~G{p1Ha&6luu(A z$P{K!pwbdB+(;1y?dt|aK;H*qP~a|qN+}pvXQ0qaeH$_iB#s=Sfc;(gFgt+W(!^X; zV=ajD53u~v9j0t8$jY={4E>n_>hufQU8w7tN!9r>>n8^3eB>6p$|?DO+IPvoR~DcG zV-%agZselR!&;cytTUh+SFD975c3C|wt$=NKK#C+P!HG^;<;`)F4QydW>6RJj-s{2 zC_>~GI*CIcl{EB%9xiY19cVVfPn03u6GI4D>aUMMqi^1yj##NmPo|Rh8)=}7Q<2Kc zEY}^%&;Ct1@FeEez#X%5!)E8KN*mo5|GW-C7TNFnRS z^yiG;_;<8kxTnq~eFt6xctQWDgbWP~Y0ZJ13#FwmC?_$1 zahOo+5)bZ;5mw2MWt_)uC?&scWANHlk)SNkFHQi&hDAd1qwmi*gD+%x(4Zbbrh40N zXg|7eg~CQs1ffQdgF7*s!-WQ5rSLwIj)KnFAlOa&jA!ati*aCW}AZ}_> zk};}hvScPG$-rPH-sha{*`p_X7W$SjA~uuxFqv=w|GCI+ z1c5Ff`ZN;GZldYo1wh=oo7f38*b)2zRdkt2B;8EucUT9u8nUx(gUc`m0XfkT%O!Tb z(vgl#9i58*c!HKFVrntg-B|@%t?k{p6yTHFu>{O>UfUhEr2M`y~QmLJM7MgU9& z5GyKjyD&9U)6uC?6{ **O ZMP precisa estar dentro do polígono de suporte** (a envoltória convexa dos pés em contato). + +Se o planejador pede um ZMP fora do pé, o pé real não consegue gerar essa força e o robô tomba em torno da borda do pé. Na prática: + +- **Apoio simples:** o ZMP fica sob o pé de apoio. +- **Apoio duplo:** o ZMP pode estar em qualquer lugar entre os dois pés, e é nessa fase que ele "viaja" de um pé para o outro. + +Invertendo a equação do LIPM, dá para recuperar o ZMP a partir de uma trajetória do CoM: $p = x - \ddot{x}/\omega^2$. É isso que [`tools/plot_trajectories.py`](../tools/plot_trajectories.py) faz para desenhar o ZMP. + +## 3. Gerador analítico de caminhada (MuJoCo) + +Arquivo: [`mujoco_sim/analytical_walking/trajectory_generator.py`](../mujoco_sim/analytical_walking/trajectory_generator.py) + +Aqui o problema é invertido: **primeiro** escolhe-se a trajetória do ZMP e **depois** calcula-se o CoM que a realiza, resolvendo a EDO do LIPM em forma fechada. + +### Fases de um passo + +Cada passo dura $T$ e tem três fases, com $t_b = T \cdot \text{ds\_ratio}/2$ e $t_e = T - t_b$: + +``` +|-- apoio duplo --|---------- apoio simples ----------|-- apoio duplo --| +0 tb te T +ZMP: início → pé de apoio ZMP parado no pé de apoio pé de apoio → fim +``` + +O ZMP de referência é linear por partes: sai da posição inicial do torso, vai até o pé de apoio, fica lá durante o apoio simples e segue para a posição final do torso. + +### Solução fechada + +Para um ZMP linear por partes, a solução de $\ddot{x} = \omega^2(x - p)$ é + +$$ +x(t) = p(t) + c_1 e^{\omega t} + c_2 e^{-\omega t} - \text{(termo de correção da rampa do ZMP)} +$$ + +As constantes $c_1, c_2$ são escolhidas para que $x(0)$ seja a pose inicial do torso e $x(T)$, a pose final (`c1`, `c2` no código). O resultado é um CoM que balança suavemente na direção do pé de apoio. Veja o gráfico gerado por `tools/plot_trajectories.py`. + +> **Detalhe didático:** a solução casa a *posição* do CoM entre passos, mas não a *velocidade*. Na troca de passo a aceleração dá um salto, e o ZMP recuperado tem picos nesses instantes. Um gerador "de verdade" impõe também continuidade de velocidade, que é uma das coisas que o MPC resolve naturalmente. + +### Pé em balanço + +- **Horizontal:** blend cosseno $h(\phi)$ de 1 → 0 entre a posição inicial e a final do pé. +- **Vertical:** bump cosseno $v(\phi)$ com altura máxima `z_step`. Perto do fim do apoio simples ele vira uma descida linear, para o pé pousar com velocidade vertical controlada. + +### Planejamento de passos + +`select_next_poses` converte o comando de velocidade $(v_x, v_y, \omega_z)$ num deslocamento do torso de $(v_x, v_y, \omega_z)\cdot T$, no referencial do próprio torso. O pé em balanço pousa a `y_sep` para o lado do novo torso e meia passada à frente. + +### Máquina de estados + +[`walking_engine.py`](../mujoco_sim/analytical_walking/walking_engine.py) alterna pé de apoio e pé de balanço a cada passo e controla as transições `IDLE → WALKING ⇄ IDLE_MARCH → STOPPING → IDLE`. + +## 4. MPC do CoM (PyBullet) + +Arquivo: [`pybullet_sim/mpc_planner.py`](../pybullet_sim/mpc_planner.py) + +Em vez de fixar o ZMP e resolver o CoM analiticamente, o **Model Predictive Control** deixa o otimizador escolher o ZMP a cada instante, dentro de limites. Para cada eixo (x e y separadamente), num horizonte de $N$ passos de $\Delta t$: + +$$ +\begin{aligned} +\min_{x,\,v,\,p}\quad & \sum_{k=0}^{N-1} W_{com}\,(x_k - x^{ref}_k)^2 + W_{zmp}\,(p_k - p^{ref}_k)^2 + W_{vel}\,v_k^2 \\ +\text{s.a.}\quad & x_{k+1} = x_k + v_k\,\Delta t \\ +& v_{k+1} = v_k + \omega^2 (x_k - p_k)\,\Delta t \\ +& p^{min}_k \le p_k \le p^{max}_k \\ +& x_0 = x_{atual},\; v_0 = v_{atual} +\end{aligned} +$$ + +- As duas primeiras restrições são o LIPM discretizado por Euler. +- A terceira garante que **o ZMP fica dentro do pé** (o polígono de suporte vira uma caixa de ±`FOOT_MARGIN` em torno do pé). É ela que dá garantia de equilíbrio *no modelo*. +- É um QP convexo, resolvido pelo OSQP via `cvxpy`. O problema é montado uma vez com `cp.Parameter` e só os valores mudam a cada ciclo. +- Só o primeiro estado previsto é usado e o problema é resolvido de novo no ciclo seguinte (*receding horizon*). + +As referências (`get_references_x/y`) e os limites do ZMP vêm de um plano de passos fixo: período `T_STEP`, pés alternados e passada `cmd_vx * T_STEP`. + +> **Importante:** neste código o MPC roda em **malha aberta**. O estado $(x_0, v_0)$ passado ao MPC é a própria previsão do ciclo anterior, não uma medição do simulador. O MPC funciona, portanto, como um **gerador de trajetória**, e quem estabiliza o robô são os controladores PD das juntas. Fechar a malha (estimar o CoM real e realimentar) é o passo natural seguinte. + +## 5. Cinemática inversa analítica da perna + +Arquivos: [`mujoco_sim/kinematics/ik_solver.py`](../mujoco_sim/kinematics/ik_solver.py) (OP3, 6 DoF completos) e [`pybullet_sim/kinematics.py`](../pybullet_sim/kinematics.py) (Aurea, versão simplificada com pé paralelo ao chão e `hip_yaw = 0`). + +A perna tem a cadeia `hip_yaw (z) → hip_roll (x) → hip_pitch (y) → knee (y) → ank_pitch (y) → ank_roll (x)`. Como os três eixos do quadril se cruzam (aproximadamente) num ponto e os dois do tornozelo também, o problema tem solução fechada. É o método do livro de Kajita et al.: + +1. **Ponto do tornozelo:** a partir do alvo da sola, sobe-se `D_ANKLE_SOLE` no referencial do pé. +2. **Vetor tornozelo → quadril** $r$, expresso no referencial do pé. Sua norma $C$ é a distância entre quadril e tornozelo. +3. **Joelho** pela lei dos cossenos: $\cos q_{knee} = \dfrac{C^2 - L_1^2 - L_2^2}{2 L_1 L_2}$. Se $C > L_1 + L_2$, o alvo está fora de alcance. +4. **Tornozelo roll:** $q_{ar} = \operatorname{atan2}(r_y, r_z)$. +5. **Tornozelo pitch:** ângulo de $r$ no plano sagital, menos o ângulo $\alpha$ que a coxa faz com a linha quadril–tornozelo ($\sin\alpha = \frac{L_1}{C}\sin q_{knee}$). +6. **Quadril:** a rotação que sobra, $R = R_{body}^\top R_{foot} R_x(-q_{ar}) R_y(-(q_{knee}+q_{ap}))$, é decomposta em $R_z(q_{hy}) R_x(q_{hr}) R_y(q_{hp})$. + +O IK devolve ângulos numa convenção geométrica. A tabela `JOINT_SIGNS` em [`config.py`](../mujoco_sim/config.py) converte para o sentido real de cada eixo no MJCF do OP3. + +O teste [`tests/test_mujoco_sim.py`](../tests/test_mujoco_sim.py) confere o IK contra a cinemática direta do MuJoCo: aplica os ângulos e mede onde o tornozelo foi parar. + +## 6. Do alvo cinemático ao torque + +Nenhum dos dois simuladores recebe torques calculados pelo planejador. O laço é sempre: + +``` +gerador/MPC → poses desejadas (torso + pés) → IK → ângulos → controlador PD de posição → torque → física +``` + +No MuJoCo os atuadores são `position` com $\tau = K_p (q_{des} - q) - K_v \dot{q}$, saturado em `ACTUATOR_FORCE_RANGE` ([`config.py`](../mujoco_sim/config.py)). No PyBullet é o `POSITION_CONTROL` do motor de cada junta. + +Com `PHYSICS = False`, o MuJoCo só faz cinemática: o torso é teleportado para a pose planejada e as juntas são escritas diretamente. Serve para depurar o gerador e o IK isolados da dinâmica. + +--- + +## Limitações conhecidas + +Estes pontos são intencionais (simplificações de estudo) ou problemas já identificados. São bons exercícios. + +| Onde | Limitação | +|------|-----------| +| PyBullet | **Relógio do MPC ≠ relógio da física.** `int(DT_MPC / DT_SIM) = int(4,8) = 4`, então cada ciclo avança a trajetória 0,020 s, mas a física só 0,0167 s: a caminhada roda 1,2× mais rápido que o planejado. Os ganhos atuais foram ajustados assim; com relógios consistentes o robô cai até o planejador ser reajustado. Detalhes em [`main.py`](../pybullet_sim/main.py). | +| PyBullet | MPC em malha aberta (seção 4). | +| MuJoCo | O IK ignora o deslocamento dos eixos de roll do quadril e do tornozelo (−24, ∓19 mm no OP3). Com o balanço lateral da marcha padrão, o tornozelo erra o alvo em ~4–5 mm. | +| MuJoCo | Velocidade do CoM descontínua entre passos (seção 3). | +| Ambos | O CoM é aproximado pela posição do torso/pelve; a massa real das pernas e dos braços não entra no modelo. | + +## Referências + +- S. Kajita, H. Hirukawa, K. Harada, K. Yokoi. *Introduction to Humanoid Robotics*. Springer, 2014. LIPM, ZMP, preview control e o IK analítico da seção 5. +- S. Kajita et al. "Biped walking pattern generation by using preview control of zero-moment point". *ICRA*, 2003. +- S. Kajita et al. "The 3D Linear Inverted Pendulum Mode: a simple modeling for a biped walking pattern generation". *IROS*, 2001. +- P.-B. Wieber. "Trajectory free linear model predictive control for stable walking in the presence of strong perturbations". *Humanoids*, 2006. A formulação de MPC com restrição de ZMP. +- M. Vukobratović, B. Borovac. "Zero-Moment Point — thirty five years of its life". *International Journal of Humanoid Robotics*, 2004. +- [MuJoCo Menagerie: Robotis OP3](https://github.com/google-deepmind/mujoco_menagerie/tree/main/robotis_op3), o modelo usado na simulação MuJoCo. +- [robot_descriptions.py](https://github.com/robot-descriptions/robot_descriptions.py), que baixa o modelo automaticamente. diff --git a/tools/plot_trajectories.py b/tools/plot_trajectories.py new file mode 100644 index 0000000..01fb13e --- /dev/null +++ b/tools/plot_trajectories.py @@ -0,0 +1,110 @@ +""" +Plot what the analytical walking engine (mujoco_sim) generates, without +running a simulator: CoM, ZMP reference, footsteps and swing-foot height. + + python tools/plot_trajectories.py # walk forward + python tools/plot_trajectories.py --vx 0 --vy 0.03 # sidestep + python tools/plot_trajectories.py --save walk.png + +The ZMP shown is recovered from the CoM through the LIPM equation +zmp = com - com_ddot / lambda^2, with com_ddot from finite differences. +Each step's LIPM solution matches the CoM *position* at the step boundaries but +not its velocity, so the recovered ZMP spikes at step transitions; the axes are +limited to the feet/CoM range so those spikes do not hide the rest. +""" + +import argparse +import os +import sys + +import matplotlib.pyplot as plt +import numpy as np + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) + +from mujoco_sim.analytical_walking.walking_engine import WalkingEngine # noqa: E402 +from mujoco_sim.config import WALK_PARAMS # noqa: E402 + + +def simulate(vx: float, vy: float, wz: float, duration: float, dt: float) -> dict: + engine = WalkingEngine(**WALK_PARAMS) + engine.set_command(vx=vx, vy=vy, wz=wz) + rows = [engine.step(dt) for _ in range(int(duration / dt))] + t = np.arange(len(rows)) * dt + com = np.array([r.body_pos[:2] for r in rows]) + left = np.array([r.left_sole for r in rows]) + right = np.array([r.right_sole for r in rows]) + + lam2 = engine.g / engine.z_com + com_ddot = np.gradient(np.gradient(com, dt, axis=0), dt, axis=0) + zmp = com - com_ddot / lam2 + return {"t": t, "com": com, "zmp": zmp, "left": left, "right": right} + + +def _limits(d: dict, axis: int) -> tuple[float, float]: + """Axis range covering the feet and CoM (not the ZMP spikes), with margin.""" + values = np.concatenate([d["left"][:, axis], d["right"][:, axis], d["com"][:, axis]]) + lo, hi = values.min(), values.max() + margin = 0.1 * (hi - lo) + 0.01 + return lo - margin, hi + margin + + +def main() -> None: + parser = argparse.ArgumentParser(description="Plot analytical walking trajectories") + parser.add_argument("--vx", type=float, default=0.05, help="forward speed [m/s]") + parser.add_argument("--vy", type=float, default=0.0, help="lateral speed [m/s]") + parser.add_argument("--wz", type=float, default=0.0, help="turn rate [rad/s]") + parser.add_argument("--duration", type=float, default=3.0, help="seconds to plot") + parser.add_argument("--save", metavar="FILE", help="save the figure instead of showing it") + args = parser.parse_args() + + d = simulate(args.vx, args.vy, args.wz, args.duration, dt=0.002) + t = d["t"] + + fig, axes = plt.subplots(2, 2, figsize=(12, 7), constrained_layout=True) + fig.suptitle(f"Analytical walking — vx={args.vx} m/s, vy={args.vy} m/s, wz={args.wz} rad/s") + + for ax, i, label in ((axes[0, 0], 0, "x"), (axes[1, 0], 1, "y")): + ax.plot(t, d["left"][:, i], color="tab:blue", lw=1, label="left foot") + ax.plot(t, d["right"][:, i], color="tab:red", lw=1, label="right foot") + ax.plot(t, d["zmp"][:, i], color="tab:gray", ls="--", lw=1, label="ZMP (LIPM)") + ax.plot(t, d["com"][:, i], color="black", lw=2, label="CoM") + ax.set_ylabel(f"{label} [m]") + ax.set_ylim(*_limits(d, i)) + ax.grid(alpha=0.3) + axes[0, 0].set_title("Horizontal trajectories over time") + axes[1, 0].set_xlabel("time [s]") + axes[0, 0].legend(loc="upper left", fontsize=8) + + ax = axes[0, 1] + ax.plot(t, d["left"][:, 2] * 1000, color="tab:blue", label="left foot") + ax.plot(t, d["right"][:, 2] * 1000, color="tab:red", label="right foot") + ax.set_title("Swing-foot height") + ax.set_xlabel("time [s]") + ax.set_ylabel("z [mm]") + ax.grid(alpha=0.3) + ax.legend(fontsize=8) + + ax = axes[1, 1] + ax.plot(d["com"][:, 0], d["com"][:, 1], color="black", lw=2, label="CoM") + ax.plot(d["zmp"][:, 0], d["zmp"][:, 1], color="tab:gray", ls="--", lw=1, label="ZMP (LIPM)") + for key, color in (("left", "tab:blue"), ("right", "tab:red")): + on_ground = d[key][:, 2] < 1e-6 + ax.scatter(d[key][on_ground, 0], d[key][on_ground, 1], s=4, color=color, label=f"{key} foot (stance)") + ax.set_title("Top view") + ax.set_xlabel("x [m]") + ax.set_ylabel("y [m]") + ax.set_xlim(*_limits(d, 0)) + ax.set_ylim(*_limits(d, 1)) + ax.grid(alpha=0.3) + ax.legend(fontsize=8) + + if args.save: + fig.savefig(args.save, dpi=120) + print(f"Saved {args.save}") + else: + plt.show() + + +if __name__ == "__main__": + main()