From 17b962d1b893b41c8fbd43f0152d7f9781a8e2b4 Mon Sep 17 00:00:00 2001 From: <> Date: Wed, 26 Jul 2023 22:17:19 +0000 Subject: [PATCH] Deployed 178e8d2 with MkDocs version: 1.4.3 --- .nojekyll | 0 404.html | 1064 +++ api/api/index.html | 5049 +++++++++++++ .../index.html | 1711 +++++ api/paginator/index.html | 2304 ++++++ api/search_modes/index.html | 1341 ++++ assets/_mkdocstrings.css | 64 + assets/images/favicon.png | Bin 0 -> 1870 bytes assets/javascripts/bundle.220ee61c.min.js | 29 + assets/javascripts/bundle.220ee61c.min.js.map | 8 + assets/javascripts/lunr/min/lunr.ar.min.js | 1 + assets/javascripts/lunr/min/lunr.da.min.js | 18 + assets/javascripts/lunr/min/lunr.de.min.js | 18 + assets/javascripts/lunr/min/lunr.du.min.js | 18 + assets/javascripts/lunr/min/lunr.es.min.js | 18 + assets/javascripts/lunr/min/lunr.fi.min.js | 18 + assets/javascripts/lunr/min/lunr.fr.min.js | 18 + assets/javascripts/lunr/min/lunr.hi.min.js | 1 + assets/javascripts/lunr/min/lunr.hu.min.js | 18 + assets/javascripts/lunr/min/lunr.hy.min.js | 1 + assets/javascripts/lunr/min/lunr.it.min.js | 18 + assets/javascripts/lunr/min/lunr.ja.min.js | 1 + assets/javascripts/lunr/min/lunr.jp.min.js | 1 + assets/javascripts/lunr/min/lunr.kn.min.js | 1 + assets/javascripts/lunr/min/lunr.ko.min.js | 1 + assets/javascripts/lunr/min/lunr.multi.min.js | 1 + assets/javascripts/lunr/min/lunr.nl.min.js | 18 + assets/javascripts/lunr/min/lunr.no.min.js | 18 + assets/javascripts/lunr/min/lunr.pt.min.js | 18 + assets/javascripts/lunr/min/lunr.ro.min.js | 18 + assets/javascripts/lunr/min/lunr.ru.min.js | 18 + assets/javascripts/lunr/min/lunr.sa.min.js | 1 + .../lunr/min/lunr.stemmer.support.min.js | 1 + assets/javascripts/lunr/min/lunr.sv.min.js | 18 + assets/javascripts/lunr/min/lunr.ta.min.js | 1 + assets/javascripts/lunr/min/lunr.te.min.js | 1 + assets/javascripts/lunr/min/lunr.th.min.js | 1 + assets/javascripts/lunr/min/lunr.tr.min.js | 18 + assets/javascripts/lunr/min/lunr.vi.min.js | 1 + assets/javascripts/lunr/min/lunr.zh.min.js | 1 + assets/javascripts/lunr/tinyseg.js | 206 + assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.74e28a9f.min.js | 42 + .../workers/search.74e28a9f.min.js.map | 8 + assets/stylesheets/main.eebd395e.min.css | 1 + assets/stylesheets/main.eebd395e.min.css.map | 1 + assets/stylesheets/palette.ecc896b0.min.css | 1 + .../stylesheets/palette.ecc896b0.min.css.map | 1 + examples/simulation/index.html | 1645 ++++ examples/synthesis/index.html | 1519 ++++ exceptions/api_exceptions/index.html | 2351 ++++++ exceptions/node_exceptions/index.html | 3674 +++++++++ extra.css | 3 + faq/index.html | 1208 +++ .../CRIPT_full_logo_colored_transparent.png | Bin 0 -> 30023 bytes images/cript_token_page.png | Bin 0 -> 72761 bytes images/favicon.ico | Bin 0 -> 15406 bytes index.html | 1192 +++ nodes/primary_nodes/base_node/index.html | 1097 +++ nodes/primary_nodes/collection/index.html | 2505 ++++++ nodes/primary_nodes/computation/index.html | 3271 ++++++++ .../computation_process/index.html | 3809 ++++++++++ nodes/primary_nodes/data/index.html | 2999 ++++++++ nodes/primary_nodes/experiment/index.html | 3065 ++++++++ nodes/primary_nodes/inventory/index.html | 1973 +++++ nodes/primary_nodes/material/index.html | 3166 ++++++++ nodes/primary_nodes/process/index.html | 3852 ++++++++++ nodes/primary_nodes/project/index.html | 2191 ++++++ nodes/primary_nodes/reference/index.html | 4254 +++++++++++ nodes/primary_nodes/software/index.html | 2109 ++++++ nodes/subobjects/algorithm/index.html | 2365 ++++++ nodes/subobjects/citation/index.html | 2191 ++++++ .../computational_forcefield/index.html | 3594 +++++++++ nodes/subobjects/condition/index.html | 4396 +++++++++++ nodes/subobjects/equipment/index.html | 2925 +++++++ nodes/subobjects/identifier/index.html | 1097 +++ nodes/subobjects/ingredient/index.html | 2422 ++++++ nodes/subobjects/parameter/index.html | 2216 ++++++ nodes/subobjects/property/index.html | 4790 ++++++++++++ nodes/subobjects/quantity/index.html | 2773 +++++++ .../software_configuration/index.html | 2518 +++++++ nodes/supporting_nodes/file/index.html | 3171 ++++++++ nodes/supporting_nodes/group/index.html | 1097 +++ nodes/supporting_nodes/user/index.html | 2125 ++++++ objects.inv | Bin 0 -> 2220 bytes search/search_index.json | 1 + sitemap.xml | 3 + sitemap.xml.gz | Bin 0 -> 127 bytes tutorial/cript_installation_guide/index.html | 1250 +++ tutorial/how_to_get_api_token/index.html | 1161 +++ utility_functions/index.html | 1691 +++++ 91 files changed, 106492 insertions(+) create mode 100644 .nojekyll create mode 100644 404.html create mode 100644 api/api/index.html create mode 100644 api/controlled_vocabulary_categories/index.html create mode 100644 api/paginator/index.html create mode 100644 api/search_modes/index.html create mode 100644 assets/_mkdocstrings.css create mode 100644 assets/images/favicon.png create mode 100644 assets/javascripts/bundle.220ee61c.min.js create mode 100644 assets/javascripts/bundle.220ee61c.min.js.map create mode 100644 assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hy.min.js create mode 100644 assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 assets/javascripts/lunr/min/lunr.kn.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sa.min.js create mode 100644 assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 assets/javascripts/lunr/min/lunr.te.min.js create mode 100644 assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 assets/javascripts/lunr/tinyseg.js create mode 100644 assets/javascripts/lunr/wordcut.js create mode 100644 assets/javascripts/workers/search.74e28a9f.min.js create mode 100644 assets/javascripts/workers/search.74e28a9f.min.js.map create mode 100644 assets/stylesheets/main.eebd395e.min.css create mode 100644 assets/stylesheets/main.eebd395e.min.css.map create mode 100644 assets/stylesheets/palette.ecc896b0.min.css create mode 100644 assets/stylesheets/palette.ecc896b0.min.css.map create mode 100644 examples/simulation/index.html create mode 100644 examples/synthesis/index.html create mode 100644 exceptions/api_exceptions/index.html create mode 100644 exceptions/node_exceptions/index.html create mode 100644 extra.css create mode 100644 faq/index.html create mode 100644 images/CRIPT_full_logo_colored_transparent.png create mode 100644 images/cript_token_page.png create mode 100644 images/favicon.ico create mode 100644 index.html create mode 100644 nodes/primary_nodes/base_node/index.html create mode 100644 nodes/primary_nodes/collection/index.html create mode 100644 nodes/primary_nodes/computation/index.html create mode 100644 nodes/primary_nodes/computation_process/index.html create mode 100644 nodes/primary_nodes/data/index.html create mode 100644 nodes/primary_nodes/experiment/index.html create mode 100644 nodes/primary_nodes/inventory/index.html create mode 100644 nodes/primary_nodes/material/index.html create mode 100644 nodes/primary_nodes/process/index.html create mode 100644 nodes/primary_nodes/project/index.html create mode 100644 nodes/primary_nodes/reference/index.html create mode 100644 nodes/primary_nodes/software/index.html create mode 100644 nodes/subobjects/algorithm/index.html create mode 100644 nodes/subobjects/citation/index.html create mode 100644 nodes/subobjects/computational_forcefield/index.html create mode 100644 nodes/subobjects/condition/index.html create mode 100644 nodes/subobjects/equipment/index.html create mode 100644 nodes/subobjects/identifier/index.html create mode 100644 nodes/subobjects/ingredient/index.html create mode 100644 nodes/subobjects/parameter/index.html create mode 100644 nodes/subobjects/property/index.html create mode 100644 nodes/subobjects/quantity/index.html create mode 100644 nodes/subobjects/software_configuration/index.html create mode 100644 nodes/supporting_nodes/file/index.html create mode 100644 nodes/supporting_nodes/group/index.html create mode 100644 nodes/supporting_nodes/user/index.html create mode 100644 objects.inv create mode 100644 search/search_index.json create mode 100644 sitemap.xml create mode 100644 sitemap.xml.gz create mode 100644 tutorial/cript_installation_guide/index.html create mode 100644 tutorial/how_to_get_api_token/index.html create mode 100644 utility_functions/index.html diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/404.html b/404.html new file mode 100644 index 000000000..1c7e433fc --- /dev/null +++ b/404.html @@ -0,0 +1,1064 @@ + + + +
+ + + + + + + + + + + + + +API
+
+
+¶API Client class to communicate with the CRIPT API
+ + + +Attributes:
+Name | +Type | +Description | +
---|---|---|
verbose |
+
+ bool
+ |
+
+
+
+ A boolean flag that controls whether verbose logging is enabled or not. +When When |
+
src/cript/api/api.py
53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + 65 + 66 + 67 + 68 + 69 + 70 + 71 + 72 + 73 + 74 + 75 + 76 + 77 + 78 + 79 + 80 + 81 + 82 + 83 + 84 + 85 + 86 + 87 + 88 + 89 + 90 + 91 + 92 + 93 + 94 + 95 + 96 + 97 + 98 + 99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +760 +761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 +829 +830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +842 +843 +844 +845 +846 +847 +848 +849 +850 +851 +852 +853 +854 +855 +856 +857 +858 +859 +860 +861 +862 +863 +864 +865 +866 +867 +868 +869 +870 +871 +872 +873 +874 +875 +876 +877 +878 +879 +880 +881 +882 +883 +884 +885 +886 +887 +888 +889 +890 +891 +892 +893 +894 +895 |
|
host
+
+
+ property
+
+
+¶schema
+
+
+ property
+
+
+¶Access the CRIPT Database Schema that is associated with this API connection. +The CRIPT Database Schema is used to validate a node's JSON so that it is compatible with the CRIPT API.
+__init__(host=None, api_token=None, storage_token=None, config_file_path='')
+
+¶Initialize CRIPT API client with host and token. +Additionally, you can use a config.json file and specify the file path.
+api client context manager
+It is necessary to use a with
context manager for the API
Examples:
+with cript.API('https://criptapp.org', 'secret_token') as api:
+ # node creation, api.save(), etc.
+
Token Security
+It is highly recommended that you store your API tokens in a safe location and read it into your code +Hard-coding API tokens directly into the code can pose security risks, +as the token might be exposed if the code is shared or stored in a version control system. +Anyone that has access to your tokens can impersonate you on the CRIPT platform
+Another great way to keep sensitive information secure is by using +environment variables. +Sensitive information can be securely stored in environment variables and loaded into the code using +os.getenv().
+import os
+
+# securely load sensitive data into the script
+cript_host = os.getenv("cript_host")
+cript_api_token = os.getenv("cript_api_token")
+cript_storage_token = os.getenv("cript_storage_token")
+
+with cript.API(host=cript_host, api_token=cript_api_token, storage_token=cript_storage_token) as api:
+ # write your script
+ pass
+
None
¶Alternatively you can configure your system to have an environment variable of
+CRIPT_TOKEN
for the API token and CRIPT_STORAGE_TOKEN
for the storage token, then
+initialize cript.API
api_token
and storage_token
with None
.
The CRIPT Python SDK will try to read the API Token and Storage token from your system's environment variables.
+with cript.API(host=cript_host, api_token=None, storage_token=None) as api:
+ # write your script
+ pass
+
config.json
+
my_script.py
+
from pathlib import Path
+
+# create a file path object of where the config file is
+config_file_path = Path(__file__) / Path('./config.json')
+
+with cript.API(config_file_path=config_file_path) as api:
+ # node creation, api.save(), etc.
+
Parameters:
+Name | +Type | +Description | +Default | +
---|---|---|---|
host |
+
+ (str, None)
+ |
+
+
+
+ CRIPT host for the Python SDK to connect to such as |
+
+ None
+ |
+
api_token |
+
+ (str, None)
+ |
+
+
+
+ CRIPT API Token used to connect to CRIPT and upload all data with the exception to file upload that needs
+a different token.
+You can find your personal token on the cript website at User > Security Settings.
+The user icon is in the top right.
+If |
+
+ None
+ |
+
storage_token |
+
+ Union[str, None]
+ |
+
+
+
+ This token is used to upload local files to CRIPT cloud storage when needed + |
+
+ None
+ |
+
config_file_path |
+
+ Union[str, Path]
+ |
+
+
+
+ the file path to the config.json file where the token and host can be found + |
+
+ ''
+ |
+
host=None
and token=None
+ then the Python SDK will grab the host from the users environment variable of "CRIPT_HOST"
+ and "CRIPT_TOKEN"
Warns:
+Type | +Description | +
---|---|
+ UserWarning
+ |
+
+
+
+ If |
+
Raises:
+Type | +Description | +
---|---|
+ CRIPTConnectionError
+ |
+
+
+
+ If it cannot connect to CRIPT with the provided host and token a CRIPTConnectionError is thrown. + |
+
Returns:
+Type | +Description | +
---|---|
+ None
+ |
+
+
+
+ Instantiate a new CRIPT API object + |
+
src/cript/api/api.py
98 + 99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 |
|
connect()
+
+¶Connect this API globally as the current active access point.
+It is not necessary to call this function manually if a context manager is used.
+A context manager is preferred where possible.
+Jupyter notebooks are a use case where this connection can be handled manually.
+If this function is called manually, the API.disconnect
function has to be called later.
For manual connection: nested API object are discouraged.
+ +src/cript/api/api.py
disconnect()
+
+¶Disconnect this API from the active access point.
+It is not necessary to call this function manually if a context manager is used.
+A context manager is preferred where possible.
+Jupyter notebooks are a use case where this connection can be handled manually.
+This function has to be called manually if the API.connect
function has to be called before.
For manual connection: nested API object are discouraged.
+ +src/cript/api/api.py
download_file(file_source, destination_path='.')
+
+¶Download a file from CRIPT Cloud Storage (AWS S3) and save it to the specified path.
+If the object_name
does not starts with http
then the program assumes the file is in AWS S3 storage,
+and attempts to retrieve it via
+boto3 client.
If the object_name
starts with http
then the program knows that
+it is a file stored on the web. The program makes a simple
+GET request to get the file,
+then writes the contents of it to the specified destination.
++Note: The current version of the program is designed to download files from the web in a straightforward +manner. However, please be aware that the program may encounter limitations when dealing with URLs that +require JavaScript or a session to be enabled. In such cases, the download method may fail.
+We acknowledge these limitations and plan to enhance the method in future versions to ensure compatibility +with a wider range of web file URLs. Our goal is to develop a robust solution capable of handling any and +all web file URLs.
+
Parameters:
+Name | +Type | +Description | +Default | +
---|---|---|---|
file_source |
+
+ str
+ |
+
+
+
+ object_name: within AWS S3 the extension e.g. "my_file_name.txt
+the file is then searched within "Data/{file_name}" and saved to local storage
+URL file source: In case of the file source is a URL then it is the file source URL
+ starting with "https://"
+ example: |
+ + required + | +
destination_path |
+
+ str
+ |
+
+
+
+ please provide a path with file name of where you would like the file to be saved +on local storage. +++ |
+
+ '.'
+ |
+
Examples:
+from pathlib import Path
+
+desktop_path = (Path(__file__).parent / "cript_downloads" / "my_downloaded_file.txt").resolve()
+cript_api.download_file(file_url=my_file_source, destination_path=desktop_path)
+
Raises:
+Type | +Description | +
---|---|
+ FileNotFoundError
+ |
+
+
+
+ In case the file could not be found because the file does not exist or the path given is incorrect + |
+
Returns:
+Type | +Description | +
---|---|
+ None
+ |
+
+
+
+ Simply downloads the file + |
+
src/cript/api/api.py
761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 |
|
get_vocab_by_category(category)
+
+¶get the CRIPT controlled vocabulary by category
+ + + +Parameters:
+Name | +Type | +Description | +Default | +
---|---|---|---|
category |
+
+ ControlledVocabularyCategories
+ |
+
+
+
+ category of + |
+ + required + | +
Returns:
+Type | +Description | +
---|---|
+ List[dict]
+ |
+
+
+
+ list of JSON containing the controlled vocabulary + |
+
src/cript/api/api.py
save(project)
+
+¶This method takes a project node, serializes the class into JSON +and then sends the JSON to be saved to the API. +It takes Project node because everything is connected to the Project node, +and it can be used to send either a POST or PATCH request to API
+ + + +Parameters:
+Name | +Type | +Description | +Default | +
---|---|---|---|
project |
+
+ Project
+ |
+
+
+
+ the Project Node that the user wants to save + |
+ + required + | +
Raises:
+Type | +Description | +
---|---|
+ CRIPTAPISaveError
+ |
+
+
+
+ If the API responds with anything other than an HTTP of |
+
Returns:
+Type | +Description | +
---|---|
+ A set of extra saved node UUIDs.
+ |
+
+
+
+ Just sends a |
+
src/cript/api/api.py
search(node_type, search_mode, value_to_search)
+
+¶This method is used to perform search on the CRIPT platform.
+ + + +Examples:
+# search by node type
+materials_paginator = cript_api.search(
+ node_type=cript.Material,
+ search_mode=cript.SearchModes.NODE_TYPE,
+ value_to_search=None,
+)
+
Parameters:
+Name | +Type | +Description | +Default | +
---|---|---|---|
node_type |
+
+ PrimaryBaseNode
+ |
+
+
+
+ Type of node that you are searching for. + |
+ + required + | +
search_mode |
+
+ SearchModes
+ |
+
+
+
+ Type of search you want to do. You can search by name, |
+ + required + | +
value_to_search |
+
+ Union[str, None]
+ |
+
+
+
+ What you are searching for can be either a value, and if you are only searching for
+a |
+ + required + | +
Returns:
+Type | +Description | +
---|---|
+ Paginator
+ |
+
+
+
+ paginator object for the user to use to flip through pages of search results + |
+
src/cript/api/api.py
830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +842 +843 +844 +845 +846 +847 +848 +849 +850 +851 +852 +853 +854 +855 +856 +857 +858 +859 +860 +861 +862 +863 +864 +865 +866 +867 +868 +869 +870 +871 +872 +873 +874 +875 +876 +877 +878 +879 +880 +881 +882 +883 +884 +885 +886 +887 +888 +889 +890 +891 +892 +893 +894 +895 |
|
upload_file(file_path)
+
+¶uploads a file to AWS S3 bucket and returns a URL of the uploaded file in AWS S3 +The URL is has no expiration time limit and is available forever
+original_name_uuid4.extension
document_42926a201a624fdba0fd6271defc9e88.txt
Parameters:
+Name | +Type | +Description | +Default | +
---|---|---|---|
file_path |
+
+ Union[Path, str]
+ |
+
+
+
+ file path as str or Path object. Path Object is recommended + |
+ + required + | +
Examples:
+import cript
+
+api = cript.API(host, token)
+
+# programmatically create the absolute path of your file, so the program always works correctly
+my_file_path = (Path(__file__) / Path('../upload_files/my_file.txt')).resolve()
+
+my_file_s3_url = api.upload_file(absolute_file_path=my_file_path)
+
Raises:
+Type | +Description | +
---|---|
+ FileNotFoundError
+ |
+
+
+
+ In case the CRIPT Python SDK cannot find the file on your computer because the file does not exist +or the path to it is incorrect it raises +FileNotFoundError + |
+
Returns:
+Name | Type | +Description | +
---|---|---|
object_name |
+ str
+ |
+
+
+
+ object_name of the AWS S3 uploaded file to be put into the File node source attribute + |
+
src/cript/api/api.py
688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 |
|
+ Bases: Enum
All available CRIPT controlled vocabulary categories
+Controlled vocabulary categories are used to classify data.
+ + + +Attributes:
+Name | +Type | +Description | +
---|---|---|
ALGORITHM_KEY |
+
+ str
+ |
+
+
+
+ Algorithm key. + |
+
ALGORITHM_TYPE |
+
+ str
+ |
+
+
+
+ Algorithm type. + |
+
BUILDING_BLOCK |
+
+ str
+ |
+
+
+
+ Building block. + |
+
CITATION_TYPE |
+
+ str
+ |
+
+
+
+ Citation type. + |
+
COMPUTATION_TYPE |
+
+ str
+ |
+
+
+
+ Computation type. + |
+
COMPUTATIONAL_FORCEFIELD_KEY |
+
+ str
+ |
+
+
+
+ Computational forcefield key. + |
+
COMPUTATIONAL_PROCESS_PROPERTY_KEY |
+
+ str
+ |
+
+
+
+ Computational process property key. + |
+
COMPUTATIONAL_PROCESS_TYPE |
+
+ str
+ |
+
+
+
+ Computational process type. + |
+
CONDITION_KEY |
+
+ str
+ |
+
+
+
+ Condition key. + |
+
DATA_LICENSE |
+
+ str
+ |
+
+
+
+ Data license. + |
+
DATA_TYPE |
+
+ str
+ |
+
+
+
+ Data type. + |
+
EQUIPMENT_KEY |
+
+ str
+ |
+
+
+
+ Equipment key. + |
+
FILE_TYPE |
+
+ str
+ |
+
+
+
+ File type. + |
+
INGREDIENT_KEYWORD |
+
+ str
+ |
+
+
+
+ Ingredient keyword. + |
+
MATERIAL_IDENTIFIER_KEY |
+
+ str
+ |
+
+
+
+ Material identifier key. + |
+
MATERIAL_KEYWORD |
+
+ str
+ |
+
+
+
+ Material keyword. + |
+
MATERIAL_PROPERTY_KEY |
+
+ str
+ |
+
+
+
+ Material property key. + |
+
PARAMETER_KEY |
+
+ str
+ |
+
+
+
+ Parameter key. + |
+
PROCESS_KEYWORD |
+
+ str
+ |
+
+
+
+ Process keyword. + |
+
PROCESS_PROPERTY_KEY |
+
+ str
+ |
+
+
+
+ Process property key. + |
+
PROCESS_TYPE |
+
+ str
+ |
+
+
+
+ Process type. + |
+
PROPERTY_METHOD |
+
+ str
+ |
+
+
+
+ Property method. + |
+
QUANTITY_KEY |
+
+ str
+ |
+
+
+
+ Quantity key. + |
+
REFERENCE_TYPE |
+
+ str
+ |
+
+
+
+ Reference type. + |
+
SET_TYPE |
+
+ str
+ |
+
+
+
+ Set type. + |
+
UNCERTAINTY_TYPE |
+
+ str
+ |
+
+
+
+ Uncertainty type. + |
+
Examples:
+algorithm_vocabulary = api.get_vocabulary_by_category(
+ ControlledVocabularyCategories.ALGORITHM_KEY
+ )
+
src/cript/api/vocabulary_categories.py
4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 |
|
Paginator
+
+
+¶Paginator is used to flip through different pages of data that the API returns when searching
+When conducting any kind of search the API returns pages of data and each page contains 10 results. +This is equivalent to conducting a Google search when Google returns a limited number of links on the first page +and all other results are on the next pages.
+Using the Paginator object, the user can simply and easily flip through the pages of data the API provides.
+Do not create paginator objects
+Please note that you are not required or advised to create a paginator object, and instead the +Python SDK API object will create a paginator for you, return it, and let you simply use it
+src/cript/api/paginator.py
8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + 65 + 66 + 67 + 68 + 69 + 70 + 71 + 72 + 73 + 74 + 75 + 76 + 77 + 78 + 79 + 80 + 81 + 82 + 83 + 84 + 85 + 86 + 87 + 88 + 89 + 90 + 91 + 92 + 93 + 94 + 95 + 96 + 97 + 98 + 99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 |
|
current_page_number: int
+
+
+ property
+ writable
+
+
+¶get the current page number that you are on.
+Setting the page will take you to that specific page of results
+ + + +Examples:
+ + + + +Returns:
+Type | +Description | +
---|---|
+ current page number: int
+ |
+
+
+
+ the current page number of the data + |
+
__init__(http_headers, api_endpoint, query=None, current_page_number=0)
+
+¶create a paginator
+Parameters:
+Name | +Type | +Description | +Default | +
---|---|---|---|
http_headers |
+
+ dict
+ |
+
+
+
+ get already created http headers from API and just use them in paginator + |
+ + required + | +
api_endpoint |
+
+ str
+ |
+
+
+
+ api endpoint to send the search requests to +it already contains what node the user is looking for + |
+ + required + | +
current_page_number |
+
+ int
+ |
+
+
+
+ page number to start from. Keep track of current page for user to flip back and forth between pages of data + |
+
+ 0
+ |
+
query |
+
+ Optional[str]
+ |
+
+
+
+ the value the user is searching for + |
+
+ None
+ |
+
Returns:
+Type | +Description | +
---|---|
+ None
+ |
+
+
+
+ instantiate a paginator + |
+
src/cript/api/paginator.py
fetch_page_from_api()
+
+¶Raises:
+Type | +Description | +
---|---|
+ InvalidSearchRequest
+ |
+
+
+
+ In case the API responds with an error + |
+
Returns:
+Type | +Description | +
---|---|
+ current page results: List[dict]
+ |
+
+
+
+ makes a request to the API and gets a page of data + |
+
src/cript/api/paginator.py
next_page()
+
+¶flip to the next page of data.
+ + + +Examples:
+ + + +previous_page()
+
+¶flip to the next page of data.
+ + + +Examples:
+ + + +
+ Bases: Enum
Available search modes to use with the CRIPT API search
+ + + +Attributes:
+Name | +Type | +Description | +
---|---|---|
NODE_TYPE |
+
+ str
+ |
+
+
+
+ Search by node type. + |
+
EXACT_NAME |
+
+ str
+ |
+
+
+
+ Search by exact node name. + |
+
CONTAINS_NAME |
+
+ str
+ |
+
+
+
+ Search by node name containing a given string. + |
+
UUID |
+
+ str
+ |
+
+
+
+ Search by node UUID. + |
+
Examples:
+# search by node type
+materials_paginator = cript_api.search(
+ node_type=cript.Material,
+ search_mode=cript.SearchModes.NODE_TYPE,
+ value_to_search=None,
+)
+
src/cript/api/valid_search_modes.py
Abstract
+This tutorial guides you through an example material synthesis workflow using the +CRIPT Python SDK.
+Before you start, be sure the cript python package is installed.
+ +To connect to CRIPT, you must enter a host
and an API Token
. For most users, host
will be https://criptapp.org
.
Keep API Token Secure
+To ensure security, avoid storing sensitive information like tokens directly in your code. +Instead, use environment variables. +Storing tokens in code shared on platforms like GitHub can lead to security incidents. +Anyone that possesses your token can impersonate you on the CRIPT platform. +Consider alternative methods for loading tokens with the CRIPT API Client. +In case your token is exposed be sure to immediately generate a new token to revoke the access of the old one +and keep the new token safe.
+import cript
+
+with cript.API(host="http://development.api.mycriptapp.org/", api_token="123456", storage_token="987654") as api:
+ pass
+
Note
+You may notice, that we are not executing any code inside the context manager block. +If you were to write a python script, compared to a jupyter notebook, you would add all the following code inside that block. +Here in a jupyter notebook, we need to connect manually. We just have to remember to disconnect at the end.
+api = cript.API(host="http://development.api.mycriptapp.org/", api_token=None, storage_token="123456")
+api = api.connect()
+
All data uploaded to CRIPT must be associated with a Project
node.
+Project
can be thought of as an overarching research goal.
+For example, finding a replacement for an existing material from a sustainable feedstock.
# create a new project in the CRIPT database
+project = cript.Project(name="My simulation project.")
+
For this project, you can create multiple collections, which represent a set of experiments. +For example, you can create a collection for a specific manuscript, +or you can create a collection for initial screening of candidates and one for later refinements etc.
+So, let's create a collection node and add it to the project.
+collection = cript.Collection(name="Initial simulation screening")
+# We add this collection to the project as a list.
+project.collection += [collection]
+
Viewing CRIPT JSON
+Note, that if you are interested into the inner workings of CRIPT, +you can obtain a JSON representation of your data graph at any time to see what is being sent to the API.
+The Collection node holds a series of +Experiment nodes nodes.
+And we can add this experiment to the collection of the project.
+experiment = cript.Experiment(name="Simulation for the first candidate")
+collection.experiment += [experiment]
+
Software
nodes refer to software that you use during your simulation experiment.
+In general Software
nodes can be shared between project, and it is encouraged to do so if the software you are using is already present in the CRIPT project use it.
If They are not, you can create them as follows:
+python = cript.Software(name="python", version="3.9")
+rdkit = cript.Software(name="rdkit", version="2020.9")
+stage = cript.Software(name="stage", source="https://doi.org/10.1021/jp505332p", version="N/A")
+packmol = cript.Software(name="Packmol", source="http://m3g.iqm.unicamp.br/packmol", version="N/A")
+openmm = cript.Software(name="openmm", version="7.5")
+
Generally, provide as much information about the software as possible this helps to make your results reproducible. +Even a software is not publicly available, like an in-house code, we encourage you to specify them in CRIPT. +If a version is not available, consider using git-hashes.
+Now that we have our Software
nodes, we can create
+SoftwareConfiguration
nodes. SoftwareConfigurations
nodes are designed to let you specify details, about which algorithms from the software package you are using and log parameters for these algorithms.
The SoftwareConfigurations
are then used for constructing our Computation
node, which describe the actual computation you are performing.
We can also attach Algorithm
nodes to a SoftwareConfiguration
+node. The Algorithm
nodes may contain nested Parameter
nodes, as shown in the example below.
# create some software configuration nodes
+python_config = cript.SoftwareConfiguration(software=python)
+rdkit_config = cript.SoftwareConfiguration(software=rdkit)
+stage_config = cript.SoftwareConfiguration(software=stage)
+
+# create a software configuration node with a child Algorithm node
+openmm_config = cript.SoftwareConfiguration(
+ software=openmm,
+ algorithm=[
+ cript.Algorithm(
+ key="energy_minimization",
+ type="initialization",
+ ),
+ ],
+)
+packmol_config = cript.SoftwareConfiguration(software=packmol)
+
Algorithm keys
+The allowed Algorithm
keys are listed under algorithm keys in the CRIPT controlled vocabulary.
Parameter keys
+The allowed Parameter
keys are listed under parameter keys in the CRIPT controlled vocabulary.
Now that we've created some SoftwareConfiguration
nodes, we can used them to build full Computation
nodes.
+In some cases, we may also want to add Condition
nodes to our computation, to specify the conditions at which the computation was carried out. An example of this is shown below.
# Create a ComputationNode
+# This block of code represents the computation involved in generating forces.
+# It also details the initial placement of molecules within a simulation box.
+init = cript.Computation(
+ name="Initial snapshot and force-field generation",
+ type="initialization",
+ software_configuration=[
+ python_config,
+ rdkit_config,
+ stage_config,
+ packmol_config,
+ openmm_config,
+ ],
+)
+
+# Initiate the simulation equilibration using a separate node.
+# The equilibration process is governed by specific conditions and a set equilibration time.
+# Given this is an NPT (Number of particles, Pressure, Temperature) simulation, conditions such as the number of chains, temperature, and pressure are specified.
+equilibration = cript.Computation(
+ name="Equilibrate data prior to measurement",
+ type="MD",
+ software_configuration=[python_config, openmm_config],
+ condition=[
+ cript.Condition(key="time_duration", type="value", value=100.0, unit="ns"),
+ cript.Condition(key="temperature", type="value", value=450.0, unit="K"),
+ cript.Condition(key="pressure", type="value", value=1.0, unit="bar"),
+ cript.Condition(key="number", type="value", value=31),
+ ],
+ prerequisite_computation=init,
+)
+
+# This section involves the actual data measurement.
+# Note that we use the previously computed data as a prerequisite. Additionally, we incorporate the input data at a later stage.
+bulk = cript.Computation(
+ name="Bulk simulation for measurement",
+ type="MD",
+ software_configuration=[python_config, openmm_config],
+ condition=[
+ cript.Condition(key="time_duration", type="value", value=50.0, unit="ns"),
+ cript.Condition(key="temperature", type="value", value=450.0, unit="K"),
+ cript.Condition(key="pressure", type="value", value=1.0, unit="bar"),
+ cript.Condition(key="number", type="value", value=31),
+ ],
+ prerequisite_computation=equilibration,
+)
+
+# The following step involves analyzing the data from the measurement run to ascertain a specific property.
+ana = cript.Computation(
+ name="Density analysis",
+ type="analysis",
+ software_configuration=[python_config],
+ prerequisite_computation=bulk,
+)
+
+# Add all these computations to the experiment.
+experiment.computation += [init, equilibration, bulk, ana]
+
Computation types
+The allowed Computation
types are listed under computation types in the CRIPT controlled vocabulary.
Condition keys
+The allowed Condition
keys are listed under condition keys in the CRIPT controlled vocabulary.
New we'd like to upload files associated with our simulation. First, we'll instantiate our File nodes under a specific project.
+packing_file = cript.File("Initial simulation box snapshot with roughly packed molecules", type="computation_snapshot", source="path/to/local/file")
+forcefield_file = cript.File(name="Forcefield definition file", type="data", source="path/to/local/file")
+snap_file = cript.File("Bulk measurement initial system snap shot", type="computation_snapshot", source="path/to/local/file")
+final_file = cript.File("Final snapshot of the system at the end the simulations", type="computation_snapshot", source="path/to/local/file")
+
Note
+The source field should point to any file on your local filesystem.
+Info
+Depending on the file size, there could be a delay while the checksum is generated.
+Note, that we haven't uploaded the files to CRIPT yet, this is automatically performed, when the project is uploaded via api.save(project)
.
Next, we'll create a Data
node which helps organize our File
nodes and links back to our Computation
objects.
packing_data = cript.Data(
+ name="Loosely packed chains",
+ type="computation_config",
+ file=[packing_file],
+ computation=[init],
+ notes="PDB file without topology describing an initial system.",
+)
+
+forcefield_data = cript.Data(
+ name="OpenMM forcefield",
+ type="computation_forcefield",
+ file=[forcefield_file],
+ computation=[init],
+ notes="Full forcefield definition and topology.",
+)
+
+equilibration_snap = cript.Data(
+ name="Equilibrated simulation snapshot",
+ type="computation_config",
+ file=[snap_file],
+ computation=[equilibration],
+)
+
+final_data = cript.Data(
+ name="Logged volume during simulation",
+ type="computation_trajectory",
+ file=[final_file],
+ computation=[bulk],
+)
+
Data types
+The allowed Data
types are listed under the data types in the CRIPT controlled vocabulary.
Next, we'll link these Data
nodes to the appropriate Computation
nodes.
# Observe how this step also forms a continuous graph, enabling data to flow from one computation to the next.
+# The sequence initiates with the computation process and culminates with the determination of the material property.
+init.output_data = [packing_data, forcefield_data]
+equilibration.input_data = [packing_data, forcefield_data]
+equilibration.output_data = [equilibration_snap]
+ana.input_data = [final_data]
+bulk.output_data = [final_data]
+
Finally, we'll create a virtual material and link it to the Computation
nodes that we've built.
Next, let's add some Identifiers
nodes to the material to make it easier to identify and search.
names = cript.Identifier(
+ key="names",
+ value=["poly(styrene)", "poly(vinylbenzene)"],
+)
+
+bigsmiles = cript.Identifier(
+ key="bigsmiles",
+ value="[H]{[>][<]C(C[>])c1ccccc1[<]}C(C)CC",
+)
+
+chem_repeat = cript.Identifier(
+ key="chem_repeat",
+ value="C8H8",
+)
+
+polystyrene.add_identifier(names)
+polystyrene.add_identifier(chem_repeat)
+polystyrene.add_identifier(bigsmiles)
+
Identifier keys
+The allowed Identifiers
keys are listed in the material identifier keys in the CRIPT controlled vocabulary.
Let's also add some Property
nodes to the Material
, which represent its physical or virtual (in the case of a simulated material) properties.
phase = cript.Property(key="phase", value="solid")
+color = cript.Property(key="color", value="white")
+
+polystyrene.add_property(phase)
+polystyrene.add_property(color)
+
Material property keys
+The allowed material Property
keys are listed in the material property keys in the CRIPT controlled vocabulary.
identifiers = [{"names": ["poly(styrene)", "poly(vinylbenzene)"]}]
+identifiers += [{"bigsmiles": "[H]{[>][<]C(C[>])c1ccccc1[<]}C(C)CC"}]
+identifiers += [{"chem_repeat": ["C8H8"]}]
+
+polystyrene = cript.Material(name="virtual polystyrene", identifiers=identifiers)
+
Finally, we'll create a ComputationalForcefield
node and link it to the Material.
forcefield = cript.ComputationalForcefield(
+ key="opls_aa",
+ building_block="atom",
+ source="Custom determination via STAGE",
+ data=[forcefield_data],
+)
+
+polystyrene.computational_forcefield = forcefield
+
Computational forcefield keys
+The allowed ComputationalForcefield
keys are listed under the computational forcefield keys in the CRIPT controlled vocabulary.
Now we can save the project to CRIPT (and upload the files) or inspect the JSON output
+# Before we can save it, we should add all the orphaned nodes to the experiments.
+# It is important to do this for every experiment separately, but here we only have one.
+cript.add_orphaned_nodes_to_project(project, active_experiment=experiment)
+project.validate()
+
+# api.save(project)
+print(project.get_json(indent=2).json)
+
+# Let's not forget to close the API connection after everything is done.
+api.disconnect()
+
You made it! We hope this tutorial has been helpful.
+Please let us know how you think it could be improved. +Feel free to reach out to us on our CRIPT Python SDK GitHub. +We'd love your inputs and contributions!
+ + + + + + +Abstract
+This tutorial guides you through an example material synthesis workflow using the +CRIPT Python SDK.
+Before you start, be sure the cript python package is installed.
+ +To connect to CRIPT, you must enter a host
and an API Token
. For most users, host
will be https://criptapp.org
.
Keep API Token Secure
+To ensure security, avoid storing sensitive information like tokens directly in your code. +Instead, use environment variables. +Storing tokens in code shared on platforms like GitHub can lead to security incidents. +Anyone that possesses your token can impersonate you on the CRIPT platform. +Consider alternative methods for loading tokens with the CRIPT API Client. +In case your token is exposed be sure to immediately generate a new token to revoke the access of the old one +and keep the new token safe.
+import cript
+
+with cript.API(host="http://development.api.mycriptapp.org/", api_token="123456", storage_token="987654") as api:
+ pass
+
Note
+You may notice, that we are not executing any code inside the context manager block. +If you were to write a python script, compared to a jupyter notebook, you would add all the following code inside that block. +Here in a jupyter notebook, we need to connect manually. We just have to remember to disconnect at the end.
+api = cript.API(host="http://development.api.mycriptapp.org/", api_token=None, storage_token="123456")
+api = api.connect()
+
All data uploaded to CRIPT must be associated with a project node. +Project can be thought of as an overarching research goal. +For example, finding a replacement for an existing material from a sustainable feedstock.
+ +For this project, you can create multiple collections, which represent a set of experiments. +For example, you can create a collection for a specific manuscript, +or you can create a collection for initial screening of candidates and one for later refinements etc.
+So, let's create a collection node and add it to the project.
+collection = cript.Collection(name="Initial screening")
+# We add this collection to the project as a list.
+project.collection += [collection]
+
Viewing CRIPT JSON
+Note, that if you are interested into the inner workings of CRIPT, +you can obtain a JSON representation of your data graph at any time to see what is being sent to the API.
+The collection node holds a series of +Experiment nodes nodes.
+And we can add this experiment to the collection of the project.
+experiment = cript.Experiment(name="Anionic Polymerization of Styrene with SecBuLi")
+collection.experiment += [experiment]
+
An Inventory contains materials, +that are well known and usually not of polymeric nature. +They are for example the chemical you buy commercially and use as input into your synthesis.
+For this we create this inventory by adding the Material we need one by one.
+# create a list of identifiers as dictionaries to
+# identify your material to the community and your team
+my_solution_material_identifiers = [
+ {"chemical_id": "598-30-1"}
+]
+
+solution = cript.Material(
+ name="SecBuLi solution 1.4M cHex",
+ identifiers=my_solution_material_identifiers
+)
+
These materials are simple, notice how we use the SMILES notation here as an identifier for the material. +Similarly, we can create more initial materials.
+toluene = cript.Material(name="toluene", identifiers=[{"smiles": "Cc1ccccc1"}, {"pubchem_id": 1140}])
+styrene = cript.Material(name="styrene", identifiers=[{"smiles": "c1ccccc1C=C"}, {"inchi": "InChI=1S/C8H8/c1-2-8-6-4-3-5-7-8/h2-7H,1H2"}])
+butanol = cript.Material(name="1-butanol", identifiers=[{"smiles": "OCCCC"}, {"inchi_key": "InChIKey=LRHPLDYGYMQRHN-UHFFFAOYSA-N"}])
+methanol = cript.Material(name="methanol", identifiers=[{"smiles": "CO"}, {"names": ["Butan-1-ol", "Butyric alcohol", "Methylolpropane", "n-Butan-1-ol", "methanol"]}])
+
Now that we defined those materials, we can combine them into an inventory +for easy access and sharing between experiments/projects.
+inventory = cript.Inventory(
+ name="Common chemicals for poly-styrene synthesis",
+ material=[solution, toluene, styrene, butanol, methanol],
+)
+collection.inventory += [inventory]
+
A Process is a step in an experiment. +You decide how many Process are required for your experiment, +so you can list details for your experiment as fine-grained as desired. +Here we use just one step to describe the entire synthesis.
+process = cript.Process(
+ name="Anionic of Synthesis Poly-Styrene",
+ type="multistep",
+ description="In an argon filled glove box, a round bottom flask was filled with 216 ml of dried toluene. The "
+ "solution of secBuLi (3 ml, 3.9 mmol) was added next, followed by styrene (22.3 g, 176 mmol) to "
+ "initiate the polymerization. The reaction mixture immediately turned orange. After 30 min, "
+ "the reaction was quenched with the addition of 3 ml of methanol. The polymer was isolated by "
+ "precipitation in methanol 3 times and dried under vacuum.",
+)
+experiment.process += [process]
+
From a chemistry standpoint, most experimental processes, regardless of whether they are carried out in the lab +or simulated using computer code, consist of input ingredients that are transformed in some way. +Let's add ingredients to the Process that we just created. +For this we use the materials from the inventory. +Next, define Quantities nodes indicating the amount of each +Ingredient that we will use in the Process.
+initiator_qty = cript.Quantity(key="volume", value=1.7e-8, unit="m**3")
+solvent_qty = cript.Quantity(key="volume", value=1e-4, unit="m**3")
+monomer_qty = cript.Quantity(key="mass", value=0.455e-3, unit="kg")
+quench_qty = cript.Quantity(key="volume", value=5e-3, unit="m**3")
+workup_qty = cript.Quantity(key="volume", value=0.1, unit="m**3")
+
Now we can create an Ingredient +node for each ingredient using the Material +and quantities attributes.
+initiator = cript.Ingredient(
+ keyword=["initiator"], material=solution, quantity=[initiator_qty]
+)
+
+solvent = cript.Ingredient(
+ keyword=["solvent"], material=toluene, quantity=[solvent_qty]
+)
+
+monomer = cript.Ingredient(
+ keyword=["monomer"], material=styrene, quantity=[monomer_qty]
+)
+
+quench = cript.Ingredient(
+ keyword=["quench"], material=butanol, quantity=[quench_qty]
+)
+
+workup = cript.Ingredient(
+ keyword=["workup"], material=methanol, quantity=[workup_qty]
+)
+
Finally, we can add the Ingredient
nodes to the Process
node.
Its possible that our Process
was carried out under specific physical conditions. We can codify this by adding
+Condition nodes to the process.
temp = cript.Condition(key="temperature", type="value", value=25, unit="celsius")
+time = cript.Condition(key="time_duration", type="value", value=60, unit="min")
+process.condition = [temp, time]
+
We may also want to associate our process with certain properties. We can do this by adding +Property nodes to the process.
+yield_mass = cript.Property(key="yield_mass", type="number", value=47e-5, unit="kilogram", method="scale")
+process.property += [yield_mass]
+
Along with input Ingredients, our Process +may also produce product materials.
+First, let's create the Material
+that will serve as our product. We give the material a name
attribute and add it to our
+[Project]((../../nodes/primary_nodes/project).
polystyrene = cript.Material(name="polystyrene", identifiers=[])
+project.material += [polystyrene]
+
Let's add some Identifiers
to the material to make it easier to identify and search.
# create a name identifier
+polystyrene.identifiers += [{"names": ["poly(styrene)", "poly(vinylbenzene)"]}]
+
+# create a BigSMILES identifier
+polystyrene.identifiers += [{"bigsmiles": "[H]{[>][<]C(C[>])c1ccccc1[<]}C(C)CC"}]
+# create a chemical repeat unit identifier
+polystyrene.identifiers += [{"chem_repeat": ["C8H8"]}]
+
Next, we'll add some Property nodes to the +Material , which represent its physical or virtual +(in the case of a simulated material) properties.
+# create a phase property
+phase = cript.Property(key="phase", value="solid", type="none", unit=None)
+# create a color property
+color = cript.Property(key="color", value="white", type="none", unit=None)
+
+# add the properties to the material
+polystyrene.property += [phase, color]
+
Congratulations! You've just created a process that represents the polymerization reaction of Polystyrene, starting with a set of input ingredients in various quantities, and ending with a new polymer with specific identifiers and physical properties.
+Now we can save the project to CRIPT via the api object.
+ + + + + + + + +APIError
+
+
+¶
+ Bases: CRIPTException
This is a generic error made to display API errors to the user to troubleshoot.
+Please keep in mind that the CRIPT Python SDK turns the Project +node into a giant JSON and sends that to the API to be processed. If there are any errors while processing +the giant JSON generated by the CRIPT Python SDK, then the API will return an error about the http request +and the JSON sent to it. Therefore, the error shown might be an error within the JSON and not particular +within the Python code that was created
+The best way to trouble shoot this is to figure out what the API error means and figure out where +in the Python SDK this error occurred and what have been the reason under the hood.
+ +src/cript/api/exceptions.py
CRIPTAPIRequiredError
+
+
+¶
+ Bases: CRIPTException
Exception to be raised when the API object is requested, but no cript.API object exists yet.
+The CRIPT Python SDK relies on a cript.API object for creation, validation, and modification of nodes. +The cript.API object may be explicitly called by the user to perform operations to the API, or +implicitly called by the Python SDK under the hood to perform some sort of validation.
+To fix this error please instantiate an api object
+import cript
+
+my_host = "https://criptapp.org"
+my_token = "123456" # To use your token securely, please consider using environment variables
+
+my_api = cript.API(host=my_host, token=my_token)
+
src/cript/api/exceptions.py
CRIPTAPISaveError
+
+
+¶
+ Bases: CRIPTException
CRIPTAPISaveError is raised when the API responds with a http status code that is anything other than 200. +The status code and API response is shown to the user to help them debug the issue.
+This error is more of a case by case basis, but the best way to approach it to understand that the +CRIPT Python SDK sent an HTTP POST request with a giant JSON in the request body +to the CRIPT API. The API then read that request, and it responded with some sort of error either +to the that JSON or how the request was sent.
+ +src/cript/api/exceptions.py
CRIPTConnectionError
+
+
+¶
+ Bases: CRIPTException
Raised when the cript.API object cannot connect to CRIPT with the given host and token
+The best way to fix this error is to check that your host and token are written and used correctly within +the cript.API object. This error could also be shown if the API is unresponsive and the cript.API object +just cannot successfully connect to it.
+ +src/cript/api/exceptions.py
FileDownloadError
+
+
+¶
+ Bases: CRIPTException
This error is raised when the API wants to download a file from an AWS S3 URL
+via the cript.API.download_file()
method, but the status is something other than 200.
src/cript/api/exceptions.py
InvalidHostError
+
+
+¶
+ Bases: CRIPTException
Exception is raised when the host given to the API is invalid
+This is a simple error to fix, simply put http://
or preferably https://
in front of your domain
+when passing in the host to the cript.API class such as https://criptapp.org
Currently, the only web protocol that is supported with the CRIPT Python SDK is HTTP
.
import cript
+
+my_valid_host = "https://criptapp.org"
+my_token = "123456" # To use your token securely, please consider using environment variables
+
+my_api = cript.API(host=my_valid_host, token=my_token)
+
Please consider always using HTTPS
+as that is a secure protocol and avoid using HTTP
as it is insecure.
+The CRIPT Python SDK will give a warning in the terminal when it detects a host with HTTP
src/cript/api/exceptions.py
InvalidVocabulary
+
+
+¶
+ Bases: CRIPTException
Raised when the CRIPT controlled vocabulary is invalid
+ +src/cript/api/exceptions.py
InvalidVocabularyCategory
+
+
+¶
+ Bases: CRIPTException
Raised when the CRIPT controlled vocabulary category is unknown +and gives the user a list of all valid vocabulary categories
+ +src/cript/api/exceptions.py
CRIPTAttributeModificationError
+
+
+¶
+ Bases: CRIPTException
Exception that is thrown when a node attribute is modified, that wasn't intended to be modified.
+ +src/cript/nodes/exceptions.py
CRIPTDeserializationUIDError
+
+
+¶
+ Bases: CRIPTException
This exception is raised when converting a node from JSON to Python class fails, +because a node is specified with its UID only, but not part of the data graph elsewhere.
+Invalid JSON that cannot be deserialized to a CRIPT Python SDK Node
+{
+"node": ["Algorithm"],
+"key": "mc_barostat",
+"type": "barostat",
+"parameter": {"node": ["Parameter"], "uid": "uid-string"}
+}
+
Valid JSON that can be deserialized to a CRIPT Python SDK Node
+{
+"node": ["Algorithm"],
+"key": "mc_barostat",
+"type": "barostat",
+"parameter": {"node": ["Parameter"], "uid": "uid-string",
+ "key": "update_frequency", "value":1, "unit": "1/second"}
+}
+
Specify the full node instead. This error might appear if you try to partially load previously generated JSON.
+ +src/cript/nodes/exceptions.py
CRIPTJsonDeserializationError
+
+
+¶
+ Bases: CRIPTException
This exception is raised when converting a node from JSON to Python class fails.
+This process fails when the attributes within the JSON does not match the node's class
+attributes within the JsonAttributes
of that specific node
Invalid JSON that cannot be deserialized to a CRIPT Python SDK Node
+ +Valid JSON that can be deserialized to a CRIPT Python SDK Node
+ +src/cript/nodes/exceptions.py
CRIPTJsonNodeError
+
+
+¶
+ Bases: CRIPTJsonDeserializationError
This exception is raised if a node
attribute is present in JSON,
+but the list has more or less than exactly one type of node type.
++Note: It is expected that there is only a single node type per JSON object.
+
Valid JSON representation of a Material node
+ +{
+ "node": [
+ "Material",
+ "Property"
+ ],
+ "name": "Whey protein isolate",
+ "uid": "_:Whey protein isolate"
+},
+
Debugging skills are most helpful here as there is no one-size-fits-all approach.
+It is best to identify whether the invalid JSON was created in the Python SDK +or if the invalid JSON was given from the API.
+If the Python SDK created invalid JSON during serialization, then it is helpful to track down and +identify the point where the invalid JSON was started.
+You may consider, inspecting the python objects to see if the node type are written incorrectly in python +and the issue is only being caught during serialization or if the Python node is written correctly +and the issue is created during serialization.
+If the problem is with the Python SDK or API, it is best to leave an issue or create a discussion within the +Python SDK GitHub repository for one of the members of the +CRIPT team to look into any issues that there could have been.
+ +src/cript/nodes/exceptions.py
133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 |
|
CRIPTJsonSerializationError
+
+
+¶
+ Bases: CRIPTException
This Exception is raised if serialization of node from JSON to Python Object fails.
+src/cript/nodes/exceptions.py
CRIPTNodeSchemaError
+
+
+¶
+ Bases: CRIPTException
This error is raised when the CRIPT json database schema +validation fails for a node.
+Please keep in mind that the CRIPT Python SDK converts all the Python nodes inside the
+Project into a giant JSON
+and sends an HTTP POST
or PATCH
request to the API to be processed.
However, before a request is sent to the API, the JSON is validated against API database schema +via the JSON Schema library, +and if the database schema validation fails for whatever reason this error is shown.
+The easiest way to troubleshoot this is to examine the JSON that the SDK created via printing out the +Project node's JSON and checking the place that the schema validation +says failed
+src/cript/nodes/exceptions.py
CRIPTOrphanedComputationError
+
+
+¶
+ Bases: CRIPTOrphanedExperimentError
CRIPTOrphanedExperimentError, but specific for orphaned Computation node that should be +listed in one of the experiments.
+Handle this error by adding the orphaned node into one the parent project's experiments
+Computation
attribute.
src/cript/nodes/exceptions.py
__init__(orphaned_node)
+
+¶CRIPTOrphanedComputationalProcessError
+
+
+¶
+ Bases: CRIPTOrphanedExperimentError
CRIPTOrphanedExperimentError, but specific for orphaned ComputationalProcess +node that should be listed in one of the experiments.
+Handle this error by adding the orphaned node into one the parent project's experiments
+ComputationalProcess
attribute.
src/cript/nodes/exceptions.py
__init__(orphaned_node)
+
+¶CRIPTOrphanedDataError
+
+
+¶
+ Bases: CRIPTOrphanedExperimentError
CRIPTOrphanedExperimentError, but specific for orphaned Data node that should be listed in one of the experiments.
+Handle this error by adding the orphaned node into one the parent project's experiments data
attribute.
src/cript/nodes/exceptions.py
CRIPTOrphanedExperimentError
+
+
+¶
+ Bases: CRIPTOrphanedNodesError
CRIPTOrphanedNodesError, but specific for orphaned nodes that should be listed in one of the experiments.
+Handle this error by adding the orphaned node into one the parent project's experiments.
+ +src/cript/nodes/exceptions.py
CRIPTOrphanedMaterialError
+
+
+¶
+ Bases: CRIPTOrphanedNodesError
CRIPTOrphanedNodesError, but specific for orphaned materials.
+Handle this error by adding the orphaned materials into the parent project or its inventories.
+ +src/cript/nodes/exceptions.py
__init__(orphaned_node)
+
+¶CRIPTOrphanedNodesError
+
+
+¶
+ Bases: CRIPTException
, ABC
This error is raised when a child node is not attached to the
+appropriate parent node. For example, all material nodes used
+within a project must belong to the project inventory or are explicitly listed as material of that project.
+If there is a material node that is used within a project but not a part of the
+inventory and the validation code finds it then it raises an CRIPTOrphanedNodeError
Fixing this is simple and easy, just take the node that CRIPT Python SDK +found a problem with and associate it with the appropriate parent via
+ + +src/cript/nodes/exceptions.py
CRIPTOrphanedProcessError
+
+
+¶
+ Bases: CRIPTOrphanedExperimentError
CRIPTOrphanedExperimentError, but specific for orphaned Process node that should be +listed in one of the experiments.
+Handle this error by adding the orphaned node into one the parent project's experiments
+process
attribute.
src/cript/nodes/exceptions.py
__init__(orphaned_node)
+
+¶get_orphaned_experiment_exception(orphaned_node)
+
+¶Return the correct specific Exception based in the orphaned node type for nodes not correctly listed in experiment.
+ +src/cript/nodes/exceptions.py
Q: Where can I find more information about the CRIPT data model?
+A: Please feel free to review the +CRIPT data model document +and the CRIPT research paper
+Q: What does this error mean?
+A: Please visit the Exceptions documentation
+Q: Where do I report an issue that I encountered?
+A: Please feel free to report issues to our GitHub repository. +We are always looking for ways to improve and create software that is a joy to use!
+Q: Where can I find more CRIPT examples?
+A: Please visit CRIPT Scripts where there are many CRIPT examples ranging from CRIPT graphs drawn out from research papers, Python scripts, TypeScript scripts, and more!
+Q: Where can I find more example code?
+A: We have written a lot of tests for our software, and if needed, those tests can be referred to as example code to work with the Python SDK software. The Python SDK tests are located within the GitHub repository/tests, and there they are broken down to different kinds of tests
+Q: How can I contribute to this project?
+A: We would love to have you contribute. +Please read theGitHub repository wiki +to understand more and get started. Feel free to contribute to any bugs you find, any issues within the +GitHub repository, or any features you want.
+Q: This repository is awesome, how can I build a plugin to add to it?
+A: We have built this code with plugins in mind! Please visit the +CRIPT Python SDK GitHub repository Wiki +tab for developer documentation.
+Q: I have this question that is not covered anywhere, where can I ask it?
+A: Please visit the CRIPT Python SDK repository +and ask your question within the +discussions tab Q/A section
+Q: Where is the best place where I can contact the CRIPT Python SDK team for questions or support?
+A: We would love to hear from you! Please visit our CRIPT Python SDK Repository GitHub Discussions to easily send us questions. +Our repository's issue page is also another good way to let us know about any issues or suggestions you might have. +A GitHub account is required.
+Q: How can I report security issues?
+A: Please visit the CRIPT Python SDK GitHub repository security tab for any security issues.
+Q: Besides the user documentation are there any developer documentation that I can read through on how +the code is written to get a better grasp of it?
+A: You bet! There are documentation for developers within the +CRIPT Python SDK Wiki. +There you will find documentation on everything from how our code is structure, +how we aim to write our documentation, CI/CD, and more.
+We try to also have type hinting, comments, and docstrings for all the code that we work on so it is clear and easy for anyone reading it to easily understand.
+if all else fails, contact us on our GitHub Repository.
+ + + + + + +~GZCfFjIm zQ4H2#kPm)AaV^d&(7C9IP5{1sfb>sD1f=hSut>GNgwbR90SF0;nGylpH0EZIZ(xzu zp1W;^Ct)$SDei(MX0`xFOu=i82e4F^EJLC0OfW-zVxllY>bubbv9K>QFXXQ5Y&RoC z{>k$So&@{<$Lcru8g|Bqh!U QVN;FDKE1%BLtLa=l=|SDrzUQJsvP&hJ21JA|pHov!|ETi) zwW>Pm>toOFZV&+qDAnix4O=o33 `SQ}Hu z%JfV3XZWX322cbVH=V55UDS^z!&w3EjR}jWDr`4CR?(@03~Z!+k15Q}frln7)otN0 zJM`-S=FHH}8!smpCS{yodVfI{nFj**8eI8aXWf+&u6AXj_VN5~+4u};zzuwcMN_Py zw7TqBleVUo%8Vd$o9B z@6DkaJ9gG0_Ty+~@OJ K zwe%rz;=beQW3!8M$#zjovX9GmRnQ+!!6h!Im~*R7hu3k^Fiyd4{Z#fNL=6Ebg74n< zF3eU!eEg`F{9#EF%iaDQbk~@a3Yt7$vO0exDtI=UUf3(&f?W8!MO1#%3$H7dB*<-7 zo?P6wM#|{sHSudT$)2myr+Z>#O>%JgIP6B_%U|GD-vjF6-SxKGLhj#jO(*jC$YiAx ziJjcbMr>JpEPcN@j8ywMT>kBDlwI~ibO8vM+wsD!>bhu?=3jXU)nY24bSaqM4bfL8 zMoBMPnzuj48{iOsM l>bs<^ zTxmnb7J@{rA;p_Im-!C&qZat@+7Jt!_H;$ilKNTiqX}fAkSNS#lF@NntVz?*kDo~~ zga*gYH@oY(!yI{-ec2E~>xvP7=1=U}L=kOuk7<))EL&v3S+;gdb{!5s5rD`Hq2Qpo zsuT}N!wCDlj%){i^0ZX?PlDN%=5)QEgeX(STcuuQyAJy5LilvvOM;uJV%`=w%+MU? zV46zTh(5-;{JGi#!7xG>fwxTuRa{AT7Yw9l!9+@mdy+)H2EL<__#^sGwTHixCUoxt zO5C^?kHP(7h0?1=L*Tft>+rhCx72b)g2jaVMJya=4}5C7w%WbY!Zv&s1u|tg<4vOV zg1-I6Hs>nc5jc3T0?qE>Vw;7|en~Q;qs8R4;l|uo+(<48Vg7Ewf?>z>VD~rB?{*s( zYC~RK%zLhD)L(gf>%8D&bevMuN;@Gc=i|fu 5UMa2@kt zvn@=b(wd4pR 1b*xN~QB@uDJW1k@%k9O7II%FFYI^s@uL@r8J}%$fUb?1KQYT4F zBR#^On#8)1a3mmAry|E+bMko!{c#x-e(4#~Wjy_c%pROzHUf2?7$*TfbRA6xkTkJ@ zIim7GBIpU%+a`;>ve%Xc|7kN%hw=H57D+Zgww<8O#6&F-TmT~?c?i{MGQ>2)JSOn0 zobPZ~Jt2OXJqCHn29nbaqWc(2uJ}*i51}S-guwSd?-7(--B!DTvX6JJAW<~xclu?u zL36)OlElAfMNtlET?};S>?9B&r?kq~pQ?tnhG@ev@38er_@yzZqPE_bB`@adwuZTH zh&})sq_vwux#Q!Sx3lzHw1ti-8RQV4@bC3SpvAFNAXkOqWa9rw1vLmccmd(Af4S$* z3Enq3cs@lH>YgIl< |26e&6~#Msc>etnROIT)ssQk} z1TXXAwr23 g-_Z!Y)6lng&BUK+~SiECh*oHQ3N+n1tud|XqWrvQ$7aPhGxxg74wub$y{3-1U z05-%w>qiedEGQSfCe?8!)nug95Ue|Fi2NvAKvOW^_nglQ0BU1VHiM!F&nHuFr*a;K z;-q`fOAE3gY+b(6^!;FPgf_kTkyp>zu#eFRa8tzSuFCOAc! A1I%_UB ;An2RkYv3noj|Ga3;h 2V5`{zmkh{0_6J3OE4hsK;AT=+h8X5dj7O9ecreZbiO74p!9$0j!IE zCd=D*8$E&C`mc?u?Ct#GT%S1rd`mzPjqur|Ks_evuIGy?`?bo2fon;5_S5yA?8 zng9`!e)LlH4f|f ^j%K-(jvt{$nwyNxW=pX9(L_;&q&;i^(0q eV z>7Drq_Y3*5e $FSk#wi4p9V4W5no0!lTOZ)#Eeb5oes z`%Q@2#$sS`Ga*s(_M8J04`_?~Yq2HB1OO5A*is{=bL$!~>EY4s8J=d*6qfhcHILY- zr`G?Y!C~AlM5y@mlfVgEsF)wo))AIV^0r|ALF!0a^ctB-j>YU?CZn{zb}LeK)2nHf ziR$kw2Xta(3=C+q^7vS*O^%-7!G=VN;+if$1(H`QG4w^#r33KDySt+PL`}zYmK<$& z$6q?U%H>74gJKEgCWxU^c@X}W4p= nDaY=^8kUK; z#J?Td%a}#FCXw+J()Cu@BG8oDxy~vUO%kYq5B?Knco`O6%Fjs0GcbNpmZIK73`^&F zcGxKESu{Bns``5Y$L1Bxeyx1T+zaA0jT5 {af}2`20*4M%w=&!p-1Nj99Vk`_mbZBQH$g2bj}4$nFNjPY!`3+nf~g z)p`wiZPWY{6*@W5#pu??U8}yZKb@k0$&mP#op>ty@Xu`o&zD`eWM^F+;ZWGjc4*#( zff ~7zV?mJ_Q4yNm{^xY-B0Uzqpz^o7Q!i{)WF05gZ(yrFR8%g7pNpNuw z!xd6H?wvJakNNgF@zm}i4GPQGciO{q{GM9$U@u)N=hM!Eve!hl&^V0qIkiOB{*NCX zZ=qCQCpWbgr 5SC*bQ2(GgGM zMEmKPNjSSq`9dl8b;7EOkDIbcU!V- 00s4)6X1Qf5REskM<61Cl3P{ z8D?|$+=X`X{HHM4iK4Z4TSt{X%)WzWrgSBuJ_wSdF7KxgHVcGwX)FyaNM+<6mOtNJ zwxGG~?BgejLZb{FdM6J #C8KIeDl-Kvc7#4si=f93Poq*bDVVM2D=H1yCa56H|QOEH^GS_MjCEz7W0 z#43} a JwG3 zXZMKOYIiYA=mjY4OSe1Dw09l}SEg&`Hzn@L$W1!VzRSea|CpQ-OJ0#|>3A)%H6Rj4 zh> (h;Yjn1&NQOxrw`&mz@z7A7cEbNh0Woyl14(w?fy}L2U ztpeHawcz``gvTuj)^swtVA$l_XF!8DUg5F1%p`JHJh}FCtFGX4f#}XRlm#ry+{5{f zdx6j7a1X={ngdcRP{pBiW=!7*PMk~lT3mNb?_K_bcKeJvTUnHZ 0pLM}xf>#)>cAW|_@nNuRo9=Yz8t;!#Ft26 zT6Xi_bUud`E6NKvsjd|5-d*(1zU0Nu{}BCL_m%uMhNqX0fT5;-izPyg2m45l0j~qH zNc^?#8h#}hS*izgB -4Jg*|bq0`x-c)DS zqCGLj+|lCE19o6*)S#nNzoC8t&H};1=}4~Ufj-ahbPk7)WBk3%*;OzVeM1(s1sn)_ zG6R?;g$gFRBqA=}S$6+3^=*u*RWAhY>YM_P Rv)8i3hRLy%K zHB&}zvt6HlOin_;?q !d$@b4w)-g znO|z*-WGN_xY2wJ-PP35_Hhs9goDu}iwQ3kes}Pd<@uw`K`+jO?depT_fvF0euX%9 zz!NcThYx~vi0mGsSR5f%JO~>ygIu#m=44@=q83mUgrYiaRjNQpzj;~}I238wo-7dr z%ca2PTJWG*N?$VZK&s*dLT1g=s=?-!+=wX^$nygM^^?4)*7&EV+W|=1Qxf*jQ*SaP z1-8k8A -x z@L{`QCKqp$Aw9n!(Oo YP^iRYLc5gZP)oZ5?3qI{hJ$xoV~| z!|2%HjPh>22LnPU=ex--hyafzRG8BY!zbrx*fbCKjl{k!(VHyrr!eHTB*8-@*QyK~ z)0BfAvb?uAIRrUvt4rn#g7r`3ZB#|*n_&DXB2~6leM#D8nAOeVaPxPx|4w%m^J70k zq}&~hY=P!|;g1L? )~*ltPg0xJ>s6}eC-2oUZPt5PbsO;#eE86X#O w+_O|_*N52TARR=TyUBZI+RoDZP 99bIeBrC5C&U-vgALgdSR_Y)?zPRrm*0QlY_|e=~_t(rwAKn6^qrJQ) z8ed&!RvvSm02`OmU+=>C#3A~LsM>qzOh1}7;OtkQV{eyJ ~u8+$`xR+#SGC+F|gk_21ww0)eXG`B)AvO(C_8%3(Hz2A+>ar- +!a AArL@1vbaPTf+^g%XBb(;35yGYlo3FFze zuO(|J{>T8C yctk^7sj78>EAa_HT rnoDAdUBO|b3XaIh{J(-?~WDZK14^!azgrUZQ~(>(szt3B6= zOX@9I{gFzV(fkyv;&J5GQ1awY4{lb>f)U}^NWo&wyQN$RjrH%Ix@qJEy-B*-)6A}) z@JOaBy}3HKndY~a8h@4$#uiqFn&s~qtKP>|KF4D255Rq6oEuKyg{ds!ZrR2AdgMGs zOUNyC?6?F&*ZG019W~`}9iK5zc^)hGq~3=k;Hn!tvY)Ii4^=vldL;zL!+fd&inJ!~TSGY=KO54HSK!4o_ef zX_uC7?0y1Y`_UxmOFe3H_wct4Fk1KpE;rDd>?YA`z;?K|ETftC#)kwLXN6w>@YGg` zwA>eS`Fko}ILx#5s{k;|#>7>6&AdDkz`C#6*-@7(&-j#`;QG&9wYlAYT=WDwQW$5$ z!}8Y8`>t+J9tObsHTD?74!!^eqEaU>V7KFvRWfn?&%gqdcQh(}!~4MlOk=|!Tk=?# z+VoEYlSuVUf(;!&;1p?xUoN*^nKCOFuAfSvQ6otpZTzQUpzZ*v8pzlWe-T&jc&K1e z=JT7tOfMEj{!biX@hMb(|I4yA0(b$|q@?B8l&~svugmXqeXY 11L^8UA!fiA72AYIh?E01#M>tz z=evorcon$4$u$fEpDAsq%PHav+G4E%Nj5MA5DLBQ5wAGQ!Tm=KlstpyKYTP&7dF5I z>VKOBZ2li)Mu(K>BTnEH%oK?YezvNY@}-Mc12N1~!U!Rya;!eNW1<2ArdILVz$vGl z8DB+vdh9cEB(aDCiC~@q)mVkH?-@p @uSeN1F z5*4CBgJ)En3srTGZ|owHXKKp;W H7KaQ+mkw2dsQ8Eo9@m7q?Al zfo}t!U}A?#B}+%e&7ZmM{bw*?b7D&W042;;ze&mJs??7!^ig1lz2WvI)(|4JPDQHj zw|MPu>uF7SfVTTb!qh ;kM2;eq5_R@;MR>YQX_{ zkVJ>~O%-lwbr4ngVZ!vPwnD4vZH~t#$K}u@h$L?uYAXG=yw*W?Qn3A1o!=LSrpONd z$6!QRe?s=}ti;LKUlD`cwNLXfAg*9{!w#W;Ft6LGo`<$C$2sQP4dy)bz!orLYZ-cQ z=&^8H*e`d0=?%xZHUbPlHzf?u(!y}u@2bhkK7N&%3unfX+&z_>P?>%RtqQs}l9O`T zwXxgkrd^2F6UmsmsQmIjzj=An+_#}J|0Ha{xP{A>njgJfTC&F?4sw+TfqKCx9>Lhe zX@A)LVSuTa(~FoNu#Q9aN3PCy1)hEyV1jhuWr@VX8e! pAo;c`1Cmf1XoB2ZpnEL>y~GIGaT9)Uvq}79AHv$0qx%$i<*kr zJXK_I#k!|TwbY*F$W$39{ud+Lr|WwCTF>TA55Sd`7hJHjtTG9CUJ31(${Tt6KvTV_ ztn_0lD}Ar|u71wX<N%;tWG{%3$z6JKx)1-T%1oZXZIGBcR+!&1R5hmQ zipLm)n-NGVPAn36i#pi=lD+(CFYB&KOYC66@!a|~y-b>a)wk*sL%q!!y>z#eMgZ_M zw?v_7&^jgbncJKn%v%M 8CUv?EsW&oE#m z7>SBytWJvwK^ D)?`usj05 zq|1D;& x&~sB38;X%ik&5rAkcyiAa4N^q{x1iE3#@=g zNeL+Wmk9~32y(aKi#rjovdJPrBc_7jpSQ@D_6#=cf2#`AY0A)iMjpBM5iGR>k11Zf zHI^;y?{C<5wdxySa%PW5UJ`2nz%_Ub*+nMAz-D*#@g>efrlWSyRlpf|y-?G^|9MKM z@15$0`z9QH|NaN@4KLUEA%~#Pt|ip(lV)CuIG}=m_3!Fv-X#ZiVo;S|`L!DFbQF&h zJ#=c!yRnW_5V*-Xfd^;_bAzAV*n&>1cH`S}uOT^KUT7Xv;+{W8fJnDVfP6*lWVe+? zPd&a+51uCwU3jpRU_hNgu>Ie%Axg4y$dhfZAdx$txv1B5%pZ!|k^yh9wCS>NOysPr zeTVX)LizMs)Q5_@=jYTnjf0>xk-g%!c!olL;7sg{j|qViFbs^LLZ96@85q)EI$mY1 zAjprI9<$To`CG9eMxDk^HJ0oTt1>1*Ktwv|`k9t)0gI4c$QwGjZ3F9E+bf7(g~Mzz zadDMjP&bH`hddmUl7FIg%WnF@xez};y%=&tsw`>ydzDojR14f7TiDg?n$E3DZbuFt z$G5pkZF8FBt{|vtqW16srwAmf8ojP1MoW=WplqI2bC&a{WZ?7;<2g03NZXstuQVZ| zco!`p?6>!NMjmv2Ef8d1@Lc`c+DPt@h`(lR(x~r-wuOp8?F)(wi&`9ZtD&Y1nrMYz zUl2!u!27OVkwzlPD2Gpr&Yp9s#tQkNf(IrgI}qUF)dW@%C ;)eCl w)vdLQU8i=x1!MM0d+?pJDY`pqmOF-o5y3_azVFP}dr% ua=7?(-FEMFsvPB9 z&6m-gul;0gw;W3UND2B5m7ltdh7`m-jv8RX5`c3@Xx^ELqC4cq^QS7G`6uU=H1t>l z^Y${FUts`pOo|v_V)~mKz7}OLOLET)^e4@`y{AxBf1b)3hl`dDaHTh+-qits6ZcPS z*fQS^{GP-&Uf|oug^@Em K qUN V(QpyB};&@(N`YZYmUNw68Y zYmBZOS)&91+b}a#IA7ktv#w`CeqhNgUVAdq=!IA7F;Teu5ucARNYfNNw-2Z+uI2pV zlhVMQh~_?6Y7W@?*wv*A04o+}ZKB WZ`)w0Q7Vn7^VI0Pu8C?`NdAJZ&`5?Wf*-=#@5)F6!2#d;CFi}IPUvO zZ!_En8_sYgN&1yl_{wY!0xX*BzqIE~_}!EI$(hqWk&DK8;$k@W3=deGZAqpT6tr}) z8($9z=>M2P4sJIZ=UlWAZ_}Z1D1Gwo?^u$wLHqROIaPeXU-0}T%e^?%=9|{i?LW3S zE>)?n*z&Yx9KhDfROMTBfKedK3$tm>uQsq(;o$w3{zj2$bDv}+850JqxU|4dz6Pc7 zpY_;iJ0DCN4>oW82xkqF-;4tQY56{CzF1h(2D;_b4~*j=1pxpYv 0(swN^$^1f|q7yNgN>?cw0Ps*k@3yA(qde;d^5!27 zfG3Ni8a}Q4C$XqEm;3k;vu+NmrMlq0wsQ;YJAibEb@q`;Gy4=)zeYKZ#sQpCf6TIB z-}_0ozh1xe1buh-9~&srt~rT*Zykn7qhi(!X*ZXA<7f<$s!Lo@ouKuJZ-}z9Kvx7P z(Aa{fS9R$ilcr|vTGX01GdR?)acsChIcdhIYWza$1~C7|%NH8G$vz|tA(tJa%5phJ zGGN*Fz}ejOWMd$8{UA44r>Pw58IszC$RvocIh5kp16(NgZSH4@U;W7%zsM= H+S07#{}$E zp0!LEWJ_S#iq8%;CkIREnXWwV09jz-wenM?PxwDK`#Fy{pfA`qsc2Q8YR|6}*u3f- zwho8@T?2TTKE_Z9`;W#-% w#%bDwk*jO-;?d9Rw1f9A+vbEWb RN&}l zJ9ipUUP_NQ^a3rwXNY-aGu!!og3piP>aY)xRv^g8 e|gCy3zJUA*a) zBH#cQRNq wb+D34lt_7rtsypgj zpjN9=P4eoIwXBU;Heg7)Qh?7GGxbt }X-8-nW(}94%jL_iRJy9ie!dv)(OVTqql+ zguA?o4T?69_1^KS^*sDFIMS_h;qn$G=tj?kN7%$^4{s)bqr{2!gcD&uJD`+-=B!<^ z0J2Jc8P67>4qMP&&P);f*A1UsMlha0R-vb@yK^~Q{lzCJ!^!@?jYR<+hw=L$S54hZ zzMU7C-ton{xYdJw@^bp9DnMH1{?|Hl=p#cpu=9Z>zaMI96Oewa_O;Fmy8fJ@7|W=> z<6E8XqC rf1O=3oz6OA zD1p zxxBUA<4U(c08Cn0VuTL%eDjS`MZo>Z|1uwCv)Y^f<_*@lfdZ)b84nu}Pgkp_Kw!;D zfXTPVD4Vse-#^Rgy(-CpX&ubFD#@qU*TToI=>fCUfJDYi79GjuE@7apKRLtXzN5VF z%KvU!c6^VP4no0l12aq2H=A8nMk0*YApetol+D)LQu?Uv2=Hf_8qksHbx`z3JYY*| zhWeYEi#UD#mj!rawHtzH4e`sfP%h2RD|1!Scr{`r4Zw$O0_Lda?X)Pf(r)9ZWQv zhLsMVRa!Fzp!oqF!fkv7sgeWy?_PW$(r>(ALE&VDFA#Y+-q_#74$r^FPNz+S&rkFK z2T+^J_vbdcf`C6W{30M2a_zcjCV0d2sWxU$hjQ2Iha-G232f8EIGwp-|HJB8Jk03d zLAR(e5y0^r+WE8LJZd1`R7Cg@4)8}4vqwrnacFwHGxXvE$4A@^xr+-z6PEbkXBG4> z+n8??(`Au8eUBgf0G} UDQuNR-w?;QO&hk!R5f0F=f+J1Ga4K#tbQy%54zS9xqoe~xbdH{i70_Jy|{9^k*y zx+|ROzz6`z4QCCTcmROB1O96E|J^5JYg)!uM~j+pv2~^TzG(B19ZR%KqGnH<#0E)_ zHhz~e2ZifOzXS&{#r>x-cJ0sdpXo8)*;HmR9{!3yc zW1f=APs{NdUEWJRu{ktN-E>^&WJw-~8%%09^|IgpLbBCMUnaEWs#d6>WCfj!>BGk7 z9&TLNi{d|YE#fuN`yk=jnGiBpF31@5HfMa0X(L<1G%dauXp^*w9bnW@pDw#L9gO=n z!Q@Lc-v1R{KGxzfM)8i$>8a9FQl;=vfjGQSyF^K3Jl+H{a6c|5*A5bGiaf&VF#qmX zsrlXV!iLuL;is46HL$^|s@%60?SVuBsY$O^+J-ZONg7_w9sOm_VW_jjvitQ~KK!Kq zYBxx1hcR8P>ffp0=(U8RK z*9YrQ##71VBYT3=9%a84Q{QUb&mVcTZFiAcR`lh!i+j1%#HHugvGdKce`C9*dPllM z`0}sl`G2w?{EI$!R@sv=;E#@P8}gm-K1ABR`(p5L=AJ6FV`ZMTr>6&^8G?fm+oF0P zeftQCB;&c4RMBBT)f>ASd>(%%AgITULxd(nWh%z>4MEt;8rQ@aSRq*;CmQwg^k{!e zugELJU{`E`?axBbmeu8h+fBc0Suz(wQ0NYpfbUNwncWv>RN8cpU%Zybn89Z+*$Jjj zU7v8HckgwZVLIF5rhfkW?C&5u)cd=1t2>_lBg0>ziK$JE&Fp5jufIfD5dzQ-E7f{@ z`obm(l8W=V=YcKA*-9p42@~-g66%AEQNT<;8>EKA;e?cBC~gXiya7U%P}g=L_&BN8 z&;T4R%%D*gdiEC+koMrFQ fI{(z^f6d`D9$=|g!diCma`1;^Uo>wcjLr2z4zDeb4+FA1*1EkdV~og5Q7C1 zq@)`bxXr3op?^#6;3``iUpfdP)E0l#?L2>0M@w&(qUPbZ<)8a&vd>p8L1@!we|^-I z<80tr#r?Z^TeF{0s1J)pr9Fz#IvYzLw#8r{c!KjoFCF1;HrU0iq*(uEIQKpZx;-&C zAapz5=iT@nVi|Blu%;G>ZgN{L_xnrnY={PlM3Uj+z_r)L{W70knvxRBm@3H2W3phm z;uuqWK{XCh7j@!ySW0WCn^cv%nV-_(7Pq#|7Aq?&nD6o>sbQb}ch1Dm929{ 7Dl1)x0el$2O@6_HIvc z4w(dhtuK?9he%Bi37)TcB7|SxI%ZE~p;i5LGCph|YM=gn7a>HOGD*Td(5~U
y%Jq(VvVSBg?y`7HS!HsbVnR}vx+HIl8<8}GHpmD~ z{PeWpmbl!=se%_k4B!Ln$E3UnDX3dfhdzTp0K0X95l3>RUjF10BPCCzr+tLmiEe)W z W5mvLGR`NDWeXbrIQKFQ}U*MyNIr< zYyxf)hDXW2Jge>{Fgms6v?bbQ_bEG!T_>)aDH$n5@Y^v;)u@v0R$Vt ~~9ii3_Pq=$^(oU5r{s`DAD& zS-HI9P^@9>#A5*R?!-ztS+sFewHax>LhL&;;othOs7q@5C#Np(bW85EgRSt@tW=0Y ze&C+Ps *v`$O0h5PJcO=;i}VT`m^Y@obZcc2*IxbD!xl&q zDXA-60iUi^=hW8t%R9d_*^3AV1m0%}5;%id2?wAU1hA>amy6hD#B{sqCAHbyBeZ&* z9R-WWkNms}k&SzYnfA}o^*N~jF99+B&bDKmsCrb%qY%YIi{l{m6Ab>q0`VLzzOY#* zX3MNmq}@BOZ1Ph1=h7S@?FLxP8VugLqcK#>4Y#n{*s<4&!dC%i;S_W{@H-O#^T81? zI+y2=bfOI)dRCa;3NY7Zpr$xZlC*qF@mUhgAM^=~aJc=f#|UcVR17lMX}nNjnY^BF z!hkVBL2!bykmE5m7!)^+OBYT9f7Lcv1KYn^_()=@(cEmr$`i6@5R|T3$x$c|#PNV( z09!_3@qw%MMZN_DavEzNMK1aL=e!bsG#R3sew9?OYT55b1J+1C|R-EmaL}JAMOG)kXJx2uj$J@fVjusn z gOCdihsfYng?+yIfvEK)9 zYM(8}N)F}7-r*hgsYklsAjC|Kspkpq#3(ebIz(WWVt9a9#t`87(&{42 zG+cBL;)9#Bv*@8%8zF)k8l9}?(T~o2ryh_jO_L?8GS&MK@@t;46zd3hHNQuazjckN zTs+&| f(bn=5B2iFey8&=ClW@tPlUiV- zlLdh&fjkE=Vj 5EalSDo3Eql@X$@i?^t5Ct%w|d;v>WWSdw6$DC6iQ` fnH&v-1Ob2#{3%R3GRg2RfhoPy%-*VISk3G4{)34!x;exA+DU9Ju+|39cqmp zsGtvHys#@lBl%U`I;yL57&4zkDns~N*Hg+H`*oxS<@vciP_BxkxFNK;mGKawg=G1j zeO_cJsCnjyVP6nT_cPaW1!=Oa&v&w`914^QDL_eyr+=y`MinXG)m?%Ca%y=*V3J@> z`i@rM!axz0N(P;fjZPYo&W!M&74o=^3drZ@#lya%1@yt=1rd{q6Wz8lnS?WiG+2Oy zWk~XC7OGel5zfgBn}plu;({5{CuO%}TEL~UXP9Niv(rgJs@S*Mvq)?f)vZClAU}?9 zV4oVfl3VLMF~yxwxT=$w2LMCBW^^OYpdU?$|GzjpJJ$ju#bqOun}Qkk!5-*9Ct+xG z>reNcJk_D5-mxD?V|-Ic6AsQY >@u2!Q$h;-1SY&%&{vZq(cO0r-S`ncn<{QVg`>BB z8tJrLSGF96in>GbVny1a;;vd<_Kb 1K;QSr&-4dVnQ6ck{ls<|=wB>>Rck5fQ`1X6S^>HUqy z6Mm}sb&8y1RViRUA7ZF%h#2P5IF7ld%0aTP (33_ep0^eR4p ikM40aa%qE_-7)qPCsWJUf zQOJ>@O3x?f0S#aXl2(=xi!vbs !PW9}?tKDLNai zWf}=_LMXDU2^UF|w~jk9^+*xJJT>Ml1PjrW@O=Bi*t}d>l8ijXtq3{b#jlPkVD7P_ zIRLX8Ud|+3O-LuL2oB>RL|gI>8y6a^!Oy#96{o?t*(BBu4oOLt#%C3q$wqT>?B89W zzyqp23^`k7+K)MfqkFMrT))d!*FlKa-6i%~>sh_evzEv;lYMGTKU7STN#bA(56Jg; zz%X_bz=#)O8V+Qx1HeNFL!x?2n&W&GjF0I6T8fQAK&H#kGK9mUrr#+S4TkUMTSy66 zZ)ts7zwN?!I)yx=5*Lv)1sISv>n`B!MF5ZNz?ZtUF?AFnBwG>E4mb-Inh3$hU}3IF zGlXZhLJ2Q$N}VG)Dze3^b~Qg*{ABS1VC# %v69|8b=!KjmEfT1UkTD wR4LxwU%#tVpx96|`1XzUw>{_^7u zXmJX@LXwlvI6t=c@>55U5(S^2e=z9l{f$p8EfD60PvK}LAs^w_K(fdV5EwS=E;#@8 zF#uq35l+ChF$u|K0_yH?@Jf>pRSzBCNc) 5{`Tl)`X#)*bOpec{KS@RTpe>5jKH#>J}S&bDgg?v zJ}D#DX*ypv&S|9Wm-Be#BLKj~i}+-C7g(&=7?dxXvj(^>{`Z>zfQ1D(!A?W6C zcNSi4cRphldB*x@9*lMhBRMh(r&P%$m?yX>WG)BadjkL-`SQ;_27nUvfMEo&w$BZf z4X$5VxPTRIU%rgrdvGkgf^X0#{pk<@(fKApN$zt{@nN{^Ll6DF>Bde8of{v6^0`O} z+VAi;7*@{KCfJY4svXq;Mm}eCzNR(@#!t1!@|5$IezWAl)w8(6^LsCFX-o%abi41u zodMIHM@5i!)xT(Au3S|o&Y$0qLY_KJ@>%=MCJK|J4^9<7R2Y8;clbWQr7=qxXf{>L zMYH`f`l9o)zzRbAfFQ*@(<;L+eZhe`!>+lbnT1QPPv-1DCDp~wOINYN)vN#c@Dq3x znsuf&`hGpw*EG7K`^x{Z5=c7Ood;MkzGloFU2^{A8dkV=?ZPN9CU;cJ=Z5&hAgTyV zNSv(|g-N6w^?w75v4r~Yz1J>bg&Q|6;fXC@1jAvlA9FP@ ZofmP37x%rsGn6b0@9PNnczf0N z3^P`lO{=mu=hb#<+>QB*>)^3N{;5C~_S&gba!KY1d2jVH{`q~Z@b=rA4>*q{>;c0F za~m<3q;P=zHo*MjV;GA(|6lRJ&M;ygbwN^mG+gGWZ+~NAyXDK0?WKSl{+3Xq!Tyr; zlLMtcFvV03GiDC_GQfy$>0ga^&H~KY?$X!~jqv7CFf+1Glt8*w|8X00_1RpLg+KrV zoX#w!uzM3$k=_{+$9c4xOJ&m5?KuLoQW!Je!LAgleYvTN@BIfD@pr$wxFhNg^>Fv< zIP&>DpMtrkj^>6}VdufZ>`waZ^42>qgSq-Qzl}$AI=KmwzaDIU U(v6n@=B3B+#k&eg zPM?LNrwNRBPCq(4*=jbWp`krIdFRVID=vg4@6Ks}xd`l%ssxKrktQV{V4N2L<_wp{ z(#XiuP7%xisu`--f+-8v7J@{kbwQ~{6qr<;r69cq{n{_2^Kt>&>g zF!$KeI#nABFH6Et>vY0}G4t{}Z|QV0k3Ks)TS)!NlLv${4Zs{rZ6)-esxW5Gy}M*H z_~It37)Jw<_6E9a5_7Ui>_NipoRIg;)Fsr1ox+&;;SVm|RY*E3RB?~lqKe6kdKcoT zBOoJAHtt+@5n{96Hx^GL#6{<$=zSE+sf7yj%Wbc}h!AHS?XSFC0wdg`05iWV% KZ3d`e2J(M zYK*c)U9BRqG!)6UoJ2g@BZ;KMgeU5Bq2MIEeLM4Z_jc!gkoWmhhZT2c=DqiwH#2V{ z#EBztar<>t^Lpju2o})kwSdH5pc+|>Yv5q^??AZfc99qYgS=F7-7P|p6IDM?NM=bi z%#-x|0dbNKAsT$hC3?=xUoP3aF;$EfGk(5%7f9huUC_G*!oQynAnCpX4<{u)MM{aJ ziQyVkXr`pf?wC|UHN~=Y?Sa>Q*Z+WqovNo^Mhwst5N=xE2jO23MwiKnF)%dD(CEYC z {)FC-2%WtNzbXAT~WHOaW!~^}%d20fM3HqTNvYf{nRA7l-#E-Px zL!v^%Nz8m6WZg_Hp)mJPAYAxlp_X}q1O9A&S>u!W&N;47sZ}HHRjD7cNmt7Fp2v$% zGe@>*xE_a5v~ThgPk?%tWGcvfN0V 7f zx8t>dgiW1k7??htxd;yvATP*Z6|jb5dqR{$erDjF^xA$=U)Zq%W7sFCo-_297bO=T zGbW1}Z+qD#jA{Gsb)}5|8-zQWWOoWx+=7P^q?B`WlW%-$bab?2Vz|bn)+jFuRa@~n zK-wO+uY%%CQLz-A=iHw3@8RL?>EM;w cqd?#ucV2C(g=*p@4&;q&AAVo*K!YD znH^5ZgJF!d6?+_Olq_g@*_AT>E_WA;c#fY#7hwwC%e^}V!je^em|vSRE`xBvW$bD0 zV@dS15ghpqD##5eIfHS6N|nkr5Uze4s|6$w4^-Iz=i%j@c_7$gt~^QgoSPH$X9n)c z71s8#0%Pzu50)&LA lLl$ZHTUjpSrG82jnebt*EDf4pfs_g7Mp#TO0hGyVl(k2Tl@N!ez zCV8^hO?Xtd<>`BGyz$2D+$y)^DnEmZNeRYMV(R?)JMeNVMCZTCEOMDaUh;mCLB3*< zJgcr>aT#8&ET~;3&p4am>tafwaMJ9QWX Yr0o zU=RvH$DZYQH8tzZX~vJ!Oqpn*(R`fvsyk);7AXI5yEY4;hG!veW$a#5y9fnWXUV=i zEmn!0$XZnHF|bdbmaJ2JQ=%@|4hR<|Oh8vCnAVXOPzwjYrsS{wXxA>f@hPvLs49lf z^m! _; z-1u84`0o3tCK>d^NLe>|cUBo6Tjkj3DtVflGywh3*?)$D@6V7A<^4X5;AXK4AbeZG z6uS=oc3K;=-TQBpF6lq#G>y+=aT&e}S%J{w@B_+WmY+pR*Z;HdFgHykwT>|uM*AM` z!NdFSD#!Ri!7O@&*ybcp@|s!zd`ocwMMP9`=HtX8w34xh>or?ISz!U2K#0pjo)4f) zAN85X*ZOnDVfH1l;eu9UD8d~4_Z0eIhEs AzP%Zvv8w6$g@AfsYi08-jiBQy$=iMi2L7`(@-Aaq_ za994SQ|_MwO5CK8ZQ%ij&4=;yJCT%}`8Z+us^GIvJQ<4x#I1~b1^oHJGXY~@fj7be zH$(&&ieM?Ce()mHly@%K P)Z4PCl4Nhnm7m#X)c#Xg~KA0{NK}|T@oEn7|Ls>HY?C0-(=_A@-$NcmPs+p z!OJ{36x;ZeF@29EW|Wg}q~|Ru Vl*O3r+o_*nJua;z4RKm?db z{v#;)@jL>;8DWGWV)Ns}Vmi|xmftWEh>u07{mV8nCWFW&2O+r`ARi&b=G-gUWZ)*} zhwJn_>Oda8$aB!Zr<$+9%WWd0W9Ftt9DPKZ-+(wWY-kqqZ+vSmybmS+k7~5_1uN`9 z>Skj8Lnyf%u=6XZJGPl+i?jk&%`xrAUxAVj7ElW-QoER3Vbm?3Hk#xOcgF3Q!u0ab zNoO>SQ19_$RjX#=@@@qHOb3L{_{~6r6<*1ij}s)MUo&n23B(Nl=67eANWzW6<(NFz z-H-zOKFsL|{;lrA5P9wHsbt;K=Tc7O5w?od?LcdMLhKkgoEsU=AUN~yMH{Z>aS)JA zq*f}A=top&v&^nQ2@!_|#F1%3yW{6Dgsx3)-1Jk%{8rZtcaXZF(9XXFCGX865YwR* zNo)CDg0#k=Vc`QPS)9|jZ=P}OfvFTstw790mk&H;v;g}A%poI8|Kr(h+tn7If**KyZmzpU>@oQQ$zwI03z_^sFv== zvx9=!*u@Lc87X4Wsee-Q2B%4wM&CVu4j+;wRqoM>HD&D#hM4{_y%$}{^*eF-#O0ga zW~O2m*-pV&K&;9*qwWfx1T>rj%M>EOI6>K15v_JM(AoEM29FaTsCkj!TYb5!_vt zEGab8n7%$#&N{IP&rX=&9-}y-((k`{E; H#Gj? y%J$BpL WRG=uZ0u48`ZE{*_j5O F(nkV^||*R-K+ zn4%?(sa`BfdeNp;$deeZx(TKL18^o3!SNK9Bshe4?C{YT zQnD2^3L9 y;FhM&l(5CTF3n=?pz$Vz_;Wq1^Q-_7O zIz=X)V#afmaI@bMsAA+8;>U4bkpdh7gZ+6Jp}oPUjR9 HetpV mwgy3W?a|{7q1+LFJm>HEX?k)81X%FjNBaK$5W`L z^7Y0X;76)ll?DuJdE?_k$ff z-NCiVnd&-QU5e;ZfE!!5gz;-{q26=_3U?B1yv!ND|H1M|v_qoSv|o$fuF{5q3yqDA z6U*>NgtCA~NzFE50SS*-9fmJo7kE%4_LY==iu@1=ty DrN+f0^YQk!@lK2rqqro^a1%G c?~m*TV~_2UEd5 zbQ{|6(0S3LLBhYBsaf-60ie8b=BiOCp*HTd_L_N6C1*BH(0St)sCY6?3rGM(giIR0 zB=9LhbmT^|P_Y|Iw(ndAvqu#Jt#)8*QM#2~(x~}PxMyEh2K69+Y_v?YaS9QJYcWK5 z)~ FufI(tjoWJfoYq`~0MS%~L7E~Ur`>Feh}AS&EU=WlP{ z0~HTj_XgU9S*rCOJdq||Wd#kQN$bq}kOZ(H*2M!VIn!~X$h?&i3rKhpX~Ir{ {NCgO;mZpC_5rLxLouV#TrC(s;05?s9C@mRLjVB{CwG;c6M*P%rku z%h2#DLzxHQ*Gx)wDa<_GX(;L0Kxc0$TR#USyZ8l-B8?GA1eY>D1YzG3FwVATpyV?x zcVSiou!5W>>fMUk87B^EK-9mOG5t`{%$p9ilX=mLfK2+QW`-Y-c|Zyc#6V~?6(=6w zqtftO!dt+8tpy|iye3hNcnKUScg;+BGR!`lr&@byA(S{8LQ_+5uPbz#T~|BqyD(89 zN-!>t*~;YAbfn=rBaJJ2%QyMExo4H#2UZ!(<#J?VZc>Bruet~R$bY~($3ivF!XG*5 zdnwr=0lkz-g6DHEf<-LGAK5~C= Ryp(R`e+eap7}-s9mdz|vYk0zk=vi}DEcBwrAy#wscIrtS<7 z3* YXd5|$C-wkk1I3KVG6ugeF)a;k1GpeTPz={6;fp*=+#Qz)_>zb>PL{zT zgs>Y}Q~Qqe_mcZ|ZlnWvz(omPes_n@isWhOo@sZ2kutpk9GmWg!R&`VyIwcZ#Y!I4 z^gIm(cC-oR8R^=dvxZzm{rDT%GMf&y$zK~mvHZ4=XI+l`XJ{C;E4j1g {3w8$2LWZn3#Ll?wvoKMJ zn(WLZgBZGIxPFlzu6#fjFM^OMBPw~n;glsA4Q1~AAdEv_b*FOBeYU$5qhVa(WykL; z1>;uICY+6mE}$Gi$_zaL6VNz12nFeF8>{oEGDW%lT-UqWUimpeK VT2i3?kSoc^inX^lk zGNx_957G* RzF*F7jE|d)mG$nzUn$Xg8NH^|W8$wKs-ML}m zUx4@{Snwmdn5C@DIHD9g_YHH?GDBPFvtvPWd*+_cd+xbhS72=0zG%Hgx4<%zLUXHT zMV0qpyw04aMeiD;_Y;SKaxtc$@*p+Qx&|z;25{)K4;2~NDd7sr^8%GmLB)g26;-&L zI`91p?h-{PRL~9FZ!UwA4+)x07Ck4PL#c#LAH@Vy5E)4%lK+~q>`EhCwl~%j<8ls` zS{V~u4YgfOQVjajjov=x^=0W=K>A(+(dUYcClb%rv-UX_qTq)*9R4-H%+B@NdAcf- zu}y$kp4Q8RH>ouz =>0?uL1LVF4wW}E z*U$iuHGm^yoH|eu)6Rn^*@i&DISo#ElQ|GsZ lbL(04LvTL zepR&kh&`xSp061T$Nww$B0}$Y5cz;-#@rCwm9Armp(sn)kjU+m#CdrV@QlCfXdAi< z(h#GVHAKOQ2_K{J#~|LSmh=pFA5|tpu_qwjjp=2=epCwM+QO@9PE$|eXZJTWgz5yV zsfVxq%OG;S_wla = B<`wCAH^$!r)W1YPp(SA-Lmc`B=7$N z+OFAFw(EC?;?^Ps#*xblm4Lta#~jeM?1P`8hX&3uh)7yx=4Ky~!ShDp!>pnQP-QZ` z{D=Z$ZwPvsaEsc#mI|-Nq7oRV&^!_k@k28UGf>ER!@c+Mqj(W1J-$ML@l=`)Y(pJo zbZnS0n>Ke(S2OvHE`e8F)~&i46c379)=V@oYv>rET;t9A36jjj;Pw UW91#Iwl4_o+CbB mCiC^@vinN5D;+)<;bNx# z4qkhb)~ZSJ((0oaWi+rVE=6>#YxRF&EE_%y&32t^WxKKgli1~xH+kC-dJ8j}qOogR zesNIAI56Xvoh39V#pDk=XVcn55XSL;G$x|Zr0JNZDZY?sU` r|~~PWD?6 +PEg}OI6gSq7X}dS8$BHlJ8c18l$pq> z44^OwP@hsap632T!quN|KjRPavw9@RyYYu;acE)*hGDDJ)Y8BKRLCfB1(JSZd` g9PqTV7SWps08x!wSoue^K7loBP3uV+Kx<=kJ;9>Rehd zV%nbcaU{We(to~XTUD9FIu3rEUjqPN&k4kMndnL#XST#A`)FUI=w#dSfm$nTA9bx^v1{K#`BaiRJ6x-hDAI(`^Y} zk_|*q;$%q&KsR}vC9Yn3X3oKXYq!R>Yo72SRo|>t^0ZT@flYt8X!zqcjcpt2un`*F zi4(;fc;bkEwRUh@_c~|+UI #wv`&nDCh93&CLZ(WO>DOJOFKBJyYo3silW z@Ebfz0S+U4nyCom0$2^@5vPy9@AmHJ#gXHR1NgVMXI3UQf#3>O@*fPrL`NSDd)WWL zYe+EUWQBN#VP@G&E}KIxLK4bECcURzq9x>%tH1)|zd#`}^DlJZg@q6qw}*CznHZ0{ z)OzaaYPEj!_j6s>xVpMty{A%lDeo;A7N4$>r$vj5@Gac+?T|FcAo0{rJ4&++m8jGA zV1V?ZhZ{Anjc&)ps&wu O{0ww6=xHd@! zu=K4+uA)i_=7KcVMrDji0^}`a{wPp%)KhCc-u&u0WYA&caGapU!xnU~o+JTw%O3=5 zNgTiujdCTn2wUTj8mn3=^i(f1v>YNpt&GvX7C|nObXE?i06LM$VZ5I_O=R@|jj<)M z6n|7&{(+4ed)CVct$W9#%mbx-R2zl`r|T%tj|{hoKS0{i=1OBp`hZdzS5~D2b75^% z{+PUy++}POi~dr`XQE^a-u#NWWYA&cxOKAqpI#ZhF0iy!NxwV#PK7NT6-aDVa!8F8 zEfsn?l=&DiMI+!Z&?{qH_B_FQB9lH=h}QlI@p}icV{Cnf$%yGIY{Q`VuSo!7Y*rU3 zOGgk#`g#eGSx{_eRfa+4boJLy*AQ7Q6UiOc35x1?0*%c3$k2}k8u2 albqIf*e|jP03H& z=AIgL!KA=ap{GNUdEgQOp;{T=>Q2^wl9Ow-wC&DC)Yb>IKz2M)KJ5MrTQL#j6}VM! z?mOD?+0uHMh#HGyEbGJqog}N0NvH61?I)rX#-X4TUG0k;dA({q9ZW>DtLmbBVJ{It z>FlJBx>3eN+howsnjLFjF=S7Z$UfuH)`e{LLTh;Q3+h~NG=p9uEyk8~Vs&X7U1ICh zWTP+0N@7#<)AqTiM%}bPo>0@F_`Sd-0 b EKaAZn|WPqM!)eiF)J62!+3_Of-%vKoX|hNcFx2oTQI5jUo5@nVtDg+ z!sTW&s0ZaYrmHl-GckKNVQ!L5fnIBg%^!u>9*5Le{Zes>bE{9{Po{1W@G`qHwuN1^ z{Wx<#1j(xq>kTshf^RpnXbPq1B)ob z;V$Yne&fL)qq95v2$d3OMH|~=@@qeHOlIRkF9W>FpSJCfg+c2P%!&?omeQao*XeMf z6;S%?*jjJoPLEx`jDqpAC(2Q-MaMx x%t5xacV5%&!_q4k89_y zBBwxeYOIaP!$cbs{qoYL>!!*vQd2VR@y|Tm^1oT?!s#3TpUi^W1^e%za-GUVb-wuy z(W3OX^t lDZ*Ze7&pA?Ec*P 8cdX-aq`Xyd|)<)?Gy{-tK1R5 z%9t$YSRgToi0FVlOSolBd#LsP!@D!y@a`D4 yb6H^F@Fkurj6_XFk4Tz_W~rhz{JD zf4AoIEMwYJfBMAWj**LbfbTS(CPq1DON#(j#&pXdi}*atn26{A8DtTUXBpG}h!QdB zNqymc^9AtEiI2CD;`j1K04rls@pqw#h-!qt3r+i^f3aNdK0yW<-Xj9|1|BWqvlK)` zb;V~XXwQrUdB{A<8v(40Ng*+xr63}z4Vm*<3feE>Tyo9qAa4Y)GA4y2JePuqs5T_w zxfHZ-f{6%pBC`N_NF0EbF=;cT4 ZS67ytm@z@uz}3BOe% zBB~4VM Uf_X;TgkV{@5+vV{{grP4Xn WocRC% literal 0 HcmV?d00001 diff --git a/images/cript_token_page.png b/images/cript_token_page.png new file mode 100644 index 0000000000000000000000000000000000000000..a7d86d57f14f9aac37ab09f223e4db5c9a590b27 GIT binary patch literal 72761 zcmZ5{1z1!~`!}G7fP_-gQqmyZU6Rrr(%s!C3ewUIOG|f`bS}GegMhHRbi 0s;bt>|04y1O${I1cXNgPmvz(JleNMd8kmG z-s-p_AYk_V`XCNCelvTxiRh{-BaTopMgl`XBwmq~6jS$F*qsxx#W5!Ne$)}|phW2q zg=x_^0o3U=%t?0`kLHe-`gmp}coR&+{aW|%<5R6*E{1x3skjXHaoL+kct$}VgRwHN zkmQ;fvo|;d2ZTV*OODskx3?!)54|BEcr0rRi=hAd7L YOu`%guOiAeP8C&Hg9 zX#Jo2|Nb4_jtO4vyd8za{C B(lhF%Ox?PF}r$ zPG$4aX~5O{V*j(FKG ~ofEL2& z&}3X*(N6DnVH{wBhkoKP+)wE@X8_nwh>c2BAU*}R6b3^R>A`iv`t4kopy=gxPP|ZO zgwDvdXCX7(Jc^!az-0Sss^HCUPtkIp=JmaiM#b;{1fcY-#%6d46% ztuvFk6y27O6BO}r0x7r!9qkJ1s-SWI?&)eMsL%CGkYKfybq((W3zeL(%G;10|KwX* zA;Zx6_VW6$apDW>Z~VbCCUcAGr2_k9{zJ>!l$FruK5{HKbLPmW{XWI_T@oHeL#U3^ zgv7tu^6L^b92JVce`UyI@tVf?Ek%a7SL`!Jaed0RgiY#hR#3D)-=YJDMm O=Mf;OY;6Rfl3*3k8L! zF(1INez|iBLnW#G%Tv-Us1V!AUKo5YS-;;R*n`1CZS*+z`%v>Z?2_0gqeIypF;lKU zfPbuZ6Ed_>M7%@ezV%C;{uF?c`~gpEbfkZl?69}tm`sE2#+&7PR&h!ErfENQiY2q3 z4YLle^F;GA(gdc$du5&O70g0l_?i3t#Z-GXOduUcYc{uIJx|SmWu(uvft9)~j?^%9 zBSE2zH$ImYO2vf_{`@YY>piPpfR9f_XZk&0?C{^d5fIdOsOa5&2YLf{m%Wv7Jy!;f zI 7#%7XP+=K_YwV zo1?BrpfaIryIsMz?8R4My!8PLCqDx<%p9Fg_<6Ev4}GwTT<70`3C3s7auEFYwy#yg zB4IvTz;q*z%eSjHz~9#5_ux;>-Q?M&jJpD~tBN0MuGKKoi~8;|R2nePScSa@ogMp# zR0<^FkP{K{Q<|C0eKQVIab2O;SjHi-Khy9W!(XkF6XD!zF@D>mLh5^_Y!>gbFi*N> zD{-Bi-@dtayi~SgiUoMF=bSJ(JDz+V5OjBQr`30jrI5}Y1@plTA9d`wv;F_*nQgPL zV-ER2hr?Hp4tl}&IaI^TyBrj|u~91}Q=y%A!iRM2=mpo;St{vbeHP~BygoJlXn7`% zJNXue=e{_i^`OCjM%nwo?v7N+c`|_lw_JoV(;!}u(R}Q!VzsciB1dEQ zDS*K_&S}BvbUPscXt5KH$Cy$9*=uZk+~~W1`Od_2<7$ib#eXsasLVlmWXMTMpiO_| z Fo7H4!I?}Vr1;?7?mtZLcSR{??hG5bq<7Ko>70~L;U-v15lJC#7bpyO!i z;lj@V%{5azq+~G+-JcvnW#Mvl$x2>Ptk{6FyEJkB*n!Hh6kZxZx1|XfPFhKg&=N(1 zdZSU=;dw^Ytc-BGiRyj%2qe G|iy)-~PVmb+|0yy3YJsK~>l2zRi;jtn3kh0Y ziGxFQ(rTXPE_{^f%nc||QGbue+(8cR_|W*-C?ZmJkGZd4$aT5V>3Rl1DCwlEy1>lv zYDV=@AENV#L_#{%YzL>u#_l+J=$*q^3N3Dg1lFLbkca;O!g2t)TSfY0)9#f)1Xk;g z@a{N5W_qb*OG>+MJb%D(1gLXn=Oh(H5q=bIL??=3ITS~O5{`B55QK@@=h-MP8T8~| zqJu)?$n34S$&b P7R&R&}?rOeTaer*}FXafAsW$VaSC^)_QgVn3j)_JMgD_~`l^qb9 z4>{*0vkUt$cnaW;3-IPLx9S4Sci7!!fV)huMV!rMb2o}@&ky`c8O$Z%4R|Okh7M!% z*JoUyv*0lE*!U^e&?HIr{w6}ZeqwRO0D!Ondf2SWTmVnqSl^~<*txcQ%cYC10v0lK z?1R_w&mjy9MN$#hty2LN6_w@HtkH!_mdbnWQ0^`$I$|12Gy=wd6pP%STQ)YU*FDJN z5h2ZoJjHFcf-uePF}V`Z-n-Z8btqQ#psdY$%ZLH V=c>uC` zKi1RE94WM~OPVzCp%bmtZ4QUMsqEly-Du7IvDzV&1@kFX%JgZf+s#4ZQRI2Il6R(% zEu0DS(X7yi&u5DSG9k8H_c0lD_)X-?D$GjgeRR8V^oW6RW{Rl2<-eKZx&ISI{TkvA zV|cn_Q6 8X7p4M&eCT1UIU?B^Iq`?Tc77v<3wma4*Qb1#{n{ zBGWwDfYWyOy^PAlGX5QFR$6JI4wo$Y@M@ZVhj)$#EM=FEVrb(Ykbbh8GuYK{AJ6D9 zc7CWEBh=}CVJYP7?DOEmgeyM-ZR>tEDeze{#(?8|C`CPM2R@6(4!H?1Xecc%`(inV zx)(XZhJnP5q~~ePt`IgXqT2Z@uSae?jLjZrx9hhk4+1v>6o~Oi=filx@$Za%ca#h} zzG>dT6ISyn)!Ay4D;BtBdNB+Zj$jJXK*KMNiTBolb!N#9GCrb7)3}UO)v{n}bsst& zGF4~}3v@~H8BqnvCKIrbTCd>1-WpXJ2x`J7PWmFG3qFbX9s 2eGv(C^ zMJlEBLim8-l9G~Cl&DE(qJIMLUjt}qG(R8Llu`MyVktjtUOHtv_NogASd2j Aot)jPgPcX`GfAM)4nTTg#Oqqv%atfOgdsKj;*NA<7?8?2x3}qpS@{- z;CN}?s*o13Un9ywX^&x#Tbg1z_k~l?YnU~mLeV(v_@CV17r_AH2Nw)09r<`65K(yg z-UpvqwJU?nFP4FW%W9kfr;M=69w4P84bAATTTP)?-!kM$PN^5zmOD43n_EgDSBtOd ztuvh$AY@w_d)srBtR&4AlJdN8Pc5M`G=pV+#~C-QD55v*b|vk5*rFk{;lTAs3Kw9Y z?@%kX_ B9U|1xZ73!iAAy zxrr5q#rCvW+NzfFB6nhtRW9qMz1`5Fk8J&Tn|XiEtVDDrk=?sz%z6U!=CGz8W5g_) zdls(CZP5YwI9SQECexfc(?C0=TB&8LVd3J+{Lg1DL9bQAm^e8T4DYW>51<2CSYS60 z*U1Jd7S;hAVR`14zCM5c{3-YslYbX*l)tqlD^YYq5#*VOxs`m{tOA66oU-7V;zz^< z%UCrZhx(x#O}=T!4<)eVPu%ldp|qw%2$n44&v6ad2!q-dUm|1Lg}ozR)vQ5VkxpZ= zX2bVXPFK( Z+OYa(H9q4Lvw5bSFl;!lx(w+LDm!M9b%MPql0HH9s%r+SHhFy~I=8B9Q|n zBSD7cr%R+=QJKpw9=RzyB13m(?;O{A!$xCh85l|+kLp0asW8q~&jYc)JZiQVQVs_Q zH0(D+e!f(t;(6DoS*!3yUXUC0w;00}9{6jI7OVEOLE(c&1othlw7b!0<)UL`tH1KC zuc_ZcT=gOZD m%f&Gnn;$uWFPOVooF?QE8o~?*n9&zhoA08vD>dlo8vT& zk=Lvh+z}U^E#lq{)SG4x>f?ZzCJBy703mznj;s+HBHxk6^BXxz^IRjsxsC&DDZC zlruE~RmZh6< *nMx~<%%S_~)99~d;x^dMgCrn-&>_cJz7{!J+50A2&XM!BEG z57}VNTllh7!`>i%&&E8qA))E*@Yy(w-<5i{K7>AB%>c8WGK%xR_G!GT#laWcKit zL<0xjLqs}m_ak;XB4~7~VT?WONuLI{Fk*p(`8xexq;N7rs=4})LRvrT9Q5#~Et93D zO~$O*EO^vMSkxI=Gva2m)Mqpqwo6^Cj625=6oHMz5%}%G{#R~=5#u{2u$l+f#w3uM zlX0@Kr9%z=wg s0F@n~` zFgM@;8G!uDerI73X-H@&he2f5d-1 |WJDpCk>o}@>_LpiI?YQ<)}*!R}MexAtS*kJYwVD<8$HO`@N zr@HL`uQ0fm`;GSSNn*Hd>XTS5wI7}4y|a-cGQjxDuLT@ad`br4UVGIitHF0ukr*!) zej1i4aLO)i;l@x*+YTP4&~&xu=smM`V@b((r=8%D0%8#J_+0+@dUu<>C*t%SJvc;A zgxU=JBZrNR?Lyf0`na@nsWY%~Z*1B%5v4(aJn;qB{pnE~n?yDtd@8Wgmw8uxrQymn z3^HP}BiE26(sZW3)^Z}1zJt|2Ln>P#&{Wi51({#EG}@DRO75|~-ar^-(V|O^3SMj+ zm@N3q#r>cykRkRf8u8b!$ex9JmCt6;ILSRkMEKT0D(h_dtqR5JxyYuvYVL9;#hj+& z(Haqk80hGxFD*_FzZwTWIrrTPygu`7F}g -;^IhzS>BZh-*HSQ`Pu1#mJ*TbrF8EQ7i+dIp0T#jk=fK`i9M_HcGEF5 zm1cyNxUsz8GleLJL-NZub)%=?$wEcfIa?_IUvA^jbyr@%cU*O^)_Mtly1+(O!d%rK zp4F0f4_e7Yk@6;UBDt^3BWb-}IcPnozwT&7k6^6Q5qnI{COcgi+l!u$^g{WF(y;gX z?et^hK2D@UmAAMmZF%d6Szgq+udO1FY}yu-a7xQnd~fDMH*{y`0X8w zaLjC;Z3*>Zoxv8BTVEc!0m*sWE`?`DCs*EkNu$`no}7HxdhW+l*HD`zVD@gT-FP^& zc+E?FZBN>Cyf<1{`zq=9tv-_7Ax4mLCHdmrRa5^p+?uL;5Q3tEFkpT4qY@b(`+XpV zX#72jf$r5xb0y1H*1vthKwat9Xwoqd;(5Qd6LK8kn`~5O PtT-8Q`bU4}@uKl4`T8emnhPd%$a3X)}yhXY-X4u^y$Fu(Cr6p-cr0pNq}; z4;Zes3{O0bj&1Db=O}BcEXe^{l_g|cHg0N@^e(r!3$`}fwpbt|HG|0t#I!QnZzaPq z+zv|$4T9wNyS*{1L4Rd3F$c%<>8^nq)9M+4e&gSQT~9DsUp83YlwQBi&(h{Iy+|S0 z4!P1QxN_NOed3ozS1B;O6aIx$@Og-F2}|-sw>6L33WckZz|M?z<(P~BO(%VSO1JA1 zo=T_FY9YN<(hRn~)xFFB)} 8`ihg`Rw1MRxitJAE)jm zHnQ;Ct=VnlS4$X}ytD?BwMxC`e;(_n?e23?Z~Y-{Hn$Pdx#EMg#(@crAV?N|lT%i< z?s#!OU$N&tkw5eywEq;w@s}9^P?a$7FTJrezP;3nW*HXIVJj#uJDjVL9T3TCZ=|yf zkY;Th5Ob-O(Bjglk MGt8o`kA}DR-XtRzWR^!_ z$CF%Tzj-{r9Jk_TRcol&?7KOa$vE(ibZLSE8_yc+f?hKUoeEL6?#@o>o#ozl=mO$h zlq2x9yiY&kRX1Gz!}~nP)d%VBtiD~?Np!40{uq_%-unCxGVm*u1N88DZpYNBGTLyw zASNInVWH<$)pz=4w)^38v%6!#nf#ZpcuD3=xTO6qnsHf~A=^$2l$Mi^!%fRc2pFm~ z!|G+*G;sACUc|CbPE&Yg+T{lyZI3bv-JJMbWUV`6;n}ClTQ0S(obL7Ijf7iDzfF2M zEhmipS~6E+1zM E6VcIX5;-@BqwUAYgXQJbZ@A0g>BG>Gagc z+=`0>l(QjuxD3(_#db|*JA$u ?-8-gHXGgz(fbMco<9@x zQOMH2oSr|~0~-csOKYpvJpRi&@@G&9umZ`|_2I7W5`hN|WuID%!_p@=A`H(*G5~aG z1EibI0z!pb9 ~Ks98r}Cjd@~C~g{Wdx+YC}jbuy-J!_G@) zI1>Za5~bxvM}>zjD!A@9(+h#6!+8cSWfraxnjc_OJw+LFsJbL^UZzxBz+iRrDK3 z3YV+LBC6S>v&wkx_dwDJ{KTa;@9VIJ*&jvE `NgYi+yglVG>y;*yeFtHQN67#eC)Qd`Ke7R^p^S>+8%xTF?&;)JggjG38Ct2vn2 z(tcXU*1lxlRMBZvl7iaRkz^GdnwoheFzlByHQ!&?CZ}D*3=uC(55YOZA6sPy-`eTf z@0>#(2*x6_diI#uw#0TpiE1%pF$3eJ+1IEEv$1&lO0hsTOYO1t) AiD zxI~Mg`(A>tV)LVEIp$}Vp}L%LheV|+y%Rj+xWZ9yiv9hwEj_Jtk@>pq0?ELPE~vg$ z3`>8)2l8xTj|~_ybg^;&84)KPwjK5B*I9yasl_3bbMoQQ(P#XryKw>CduQVh?4A98 zw62(ezpdhDUSWA(_N nnOy-4dm@vb_(;OPL0h14 zDJSshB$6%7S*n{&G$w6j3fi7JeWQYm-08>*4ns9s-o$saiT7vysgv3JBS5)}ok*FQ zD8+0|9kw2O_Q3AIw3F4cP4G=9WR5Q}f=n%g z>h;0p8vKz( y(?xWvhK#~$6bbBE#^UH* O53;@ z>SDsJYl1w`z#{Ep@hbXlP4*@ofd}LVokr&ylf#J`do#auiq}CO@xT1@CZfI7JB%`N z10kTq!*VOtGAlWC-oVmw$pDxP#w12wAb&q+{27e~O@*pE*yE|bawSdJ!1ukcN#S0M z?8?;y`4Iq>72Gm9ekM02=nNH=@0atJtrHh(Ju${!WC5X;N=5;15D;X>R+Oq6%d6^Z z>evOG8w$8IcCI_Lk}E_~j~;;(` vb9@AzJMv^;y zg2}z`Od)7o$5eFk(Tx*h^(b1oue&wzbUnN4o!cCXC@HovNJY?vUu`b_{Fy++GL~fI z`fe-m?oxyQ*!lis4(i1+dxljzdmKoWk?9?Y@CU~IWwoO-fM>UNC!gWPtzzAd_BPfI zcE#xI0qkUt9oQ-I-w@J95{svW%jP}dbLK^}C;c>zKP?BV^-;bqP;7doN>njLnnP1O zz8Ah|c(d8#d$7vFDxB8rvH!uQL|2O`2(x|h?hSh!D9MPFlpssU|8C&924kuA16VT+ z_x=wo`X1 fKoB&p+$Esjg-#;qPi%NU?_!J<|NfUY0bunXSS}>6FR%5*=~f;4bNL-FzPU7IWH;9T7N75&+z zC~d`=^vFtthdr*1FaD3dYMWHjqZ}aiI$AzoA#?3NzK4n&7 JsG^yVZV0i=afZ|C01X>|+D9q&2E49UVh1L>;UNo_L4#MPeT^1p!&|UM!v5 zV?jFoLr(&~23+t(AoBa&pHpEHmrdK)yT){MFdnRQfVS>_&yU%X=mB(`nFniMWPE9f z1m<1B$||eLsZ}aZkVL$V)(0O4j9{E$B#M8d?#pMpy_8OA`SOC4QnbjpB@TDNb623Q zI{ij*=P zt`}c=-%zJ7{>DGP5;&{BuiMyItRxq3YN&TtEN*+xAUX?TR6>EOHdka!X~lKdnJ^UN z;?wla)Z1e13wfLE)Tao$uDl;Za <7@PV+?~~9Ff*4XX%g^oDosOdCfBD+_13pK&c~>HK?9~oP6}aRFf=|)@Y*bB zHTaPykK+Q#S+i!TqEg%7mhf*T|J2GLzl7fX+}n@Ms>pdeYOu3UV%3hfP8rY-yksOx z@gM!si49Q87V&TQYp(3Pm*&3sJ;I>2C(zVqnLvWyt%!C!`m?5XkNQu*@TUs8c?d1< zsyumG!+Mkvq`mKH?VqAz-&yc!Ep_o~i$_WW^CzmKnHcRgSQmgOEx%^vpCzS3*MGvL zf2tdjf2`)JiL0=YQju%fToDX6dI09c8*+R1wcnXM#CTL_*Fyq&JtvXnS-;oNx!8r& zQV}><+bQ&`-AiS1LnKGMfP0dfXUWPWa-w5dB*uK`uBc M$*&mOp{^Hx?32nLC*@5>%yy8OLSMYz4ahGk^(5F1r*St4Y{AnF) z+qKO02~;OIV01lYeSlq}QGze+Mdmq0_V+k4$G{4sYQmGMG4x-HY~L4_CLM^>mNOSW z5I2Wv^LcL+2!{gCe9DR4gs}Kq-4k h};CCsW{GYeQg45nJbXxw_6}Gm<{9JnVwG&f*vf! zxJ~z)mmzB__lXNhm{S$ `~$>W!D~Uql}wP2-AUSSi9-m|Iw>pL zyB&0Ooyi b=b;beS<_X&;5CyMAgyBjFo{e_x(P3k%o0KfR zDl&d4-FhFg>L-Db_HS;uo485j-v?(T3$2&)NgB|E&G7m|WO6(?>ZsEp`kO9<0&e~y zbDiW O z+a?WY1=NYal2moV%&&OV#Tj@+lK%81DkWo?u9M;F&?vBTs*lR2VF8)zeK#};_6Qrx z@~z8X`j85(8%743`?CyWpADXH!m9mO;wAqrMwaR)j_thiCk~zsldh5Uft>UObeNV_ z78%;zQ!K=~Mmugf!~WXPKXitOl%X zR}Lj1KbQ3PgUHA(9G6yB4;mNUI$N#NGEWw9z-@eCU;Zfyc#b4dW7R8}d#6lk2}utP zlz*IHTX>RTTT!>?gLujt-MRttk&3+7B0&A6B@en;{c3((N6NSmGCVPutlyBH;~q9P zE#BuaDM`x-GK_;X?1iqeKXXCy5fK}KEH}QrhrE;dcbIN29_9@VjMe*NrKie;7W7>5 z#+FvClJXRiFlB^0w);~*hed}=x1mRMLWl+(EzP_)=YM)d0DO@t(c#_P-M1@(6j )G-qxBY@# z_wE?KQ0Z2l_S6W?Lbyoc7ekBt!D`{DS?4 8uH)5# zH03|#D)II$yWzpucAnKwG3JmCU4x_`oIZ_-Jn$JQk)xyiqKChTK1(ti;{agfiGOw> z%$aoH4lUa5qUJa69()6e0BVmD$-sq6*4s9#(_BxM4#a~T+UySyFjURzSv0 @rR%Z-S+w2M|=F_Q5Qs--Z z0&qZX^P9q#e5IH^>#E6M4uXPHyF!w3*P?cUQ$Hn?KXxD}4}ZGZAWYL;1H_{ABCsWA z3DhO>^=oDr&-$US)l0P|EBlodX_h>dt2<$`n9ta%U&0bn$v =L3iLeqQBQVPJ8QT&Jxzgv znvuk{3e>Kp&JK_`0VJtIl>i$bdb_jyGrNm(iA|mrcGSf-++Q?9+~9PE nmC9%G&!z1|L=zFIS+SzEz4i6t1z-JyWlR=ZHle?o4V zVd;PgVy*go8~99)MB%#&9?SOl%8c`aYz}mid^?>f?gQ@(o(sXT%Io0PwGG=N<8~&s z04*pdDLMsh{2P?= Rr6r(HGlp?nE8K4~f|n<~!ZVu$Ilk;CCb%nFpWZII1c;yCH0#i3 EkH3>R-UIjfyUE ziT|AFS&&^ hQXC-mbWxJEMK3G*ShRXw`Qw3H#zUke%t zL3eA*sE2poJOQVB!Ef?}jjx6cNrNJkZHsQz2I|r|hDI+6@_gd+CN5Hcjw(jgubyD5 zfs*P2oequoQ1 60|A#3eAP@vy$N<*5S)(( g8h zQ&lSz-;wQ>LnV9n9kMQG4QV+k(-aJOT{;>4FYI2s7}# 1!ql>q-}uQL*tws6PmsG#{G?w<6@t-ViC2^`hEQd zDiJGf=UoB<(utkxu*A^cJpSK45d_i9P85vfpi436=L-og0rLEl_i1;bMe|6Iwi-Ap zm8_Gn-sU4y;c;t0+*}yYbYIGll9McVC}D{8V`gUd`(2;YwN)33@rDSC{q(8bSs0q9 z6VVARvB$VaoeObxDB+|UesY9!u2=s__v<8=?O3IIk}zUC!}2dw70m^xX!rEA0ZE!A zB&U)Ay*n)BzGMpT_KwMR3*Dq8sg5Fnd<5}hh1OvR%slJZ7+0L4 {)ti%ISawL!=t)*?&?=nCo)c+AWb%#tX#&q6Y=f@(zwz`J4RgU2p9!qce? z|LFg3Bhcw3^}1%2e+W9fAwOJE^teSxe&Km_#$B1I4<@L>99?6dCLKKZD1y-Ml{y4i zAqAaLbNo;tn2j$}Q-ai)HA~l`@)NC5nWKn+(Twnbtk)k=*TUR8-!f>cnN>zm1{;8q zvoE%M; @srNxNaFO!*Fq86os)% zPWacf?LUw2Mviwblpu|$Y|bRqF-`T@TwsC3(iFA4EpHH8(tLTPud+9-Tc22+#mzys zo1cP%U!hl_bxB7)A3r@oyU5+ t_t?Ny zC3!0uX`ADJB<_v_L=d9H)|O0Jk^HbDU&3ET6AqZrH)50I{^-f@h2P|@Vq?@3^Yl)L zrZMs?jp-T2V16ds EewnCu$AqGqW zy7#*6*S41Z=h_t=;p56o8$#2nO2RIdzGo|rgsrhlXIVfwB}^D&(k0<}ae7c@;PU-G zQ#8i&<+o&YBBKF#==|A?jl$PTE?+0Czw{>^x2crRoS&a7p1coSMFoHU_!K<$nnb2t zk+;MlumZh3`R+$n+eV-1zs}-8SU@K~mucLa5t}j)NPQ9@`cJHKe~3rB2H5q63=RD7 zbxL96SA4B#S>qnwpS*c?93#HFZ&6Uct*RZRY~>v)fIFMV*>ZWHWSOqb?X4Bbcg%hT zv#|ZH87*TeSfr{?jJj(0R?o6BDTCS{83#-N@P9V{1ui1k@Z5Gxn08t(?@+|nbCwf$ zd+QI^_;l~u_@QCMoDFgLimZDiE2#hS@0oCYdbL6QBT~WWrK|VHh4*Ud+#(WdI-mRo zx304g)5sl_*qE#LP9dAl%ts5Y4ArxFz}F7