From 51685b48b1494dba7e1b57df28ccf15437a43f2c Mon Sep 17 00:00:00 2001 From: Stefan Krawczyk Date: Sun, 10 Dec 2023 21:07:07 -0800 Subject: [PATCH] Adds prompt and flow to convert code to Hamilton This prompt and flow will likely change over time, but this is a first stab at sharing something that others can use to translate code with into Hamilton. --- .../dagworks/translate_to_hamilton/README.md | 52 +++ .../translate_to_hamilton/__init__.py | 329 ++++++++++++++++++ .../dagworks/translate_to_hamilton/dag.png | Bin 0 -> 46590 bytes .../translate_to_hamilton/requirements.txt | 1 + .../dagworks/translate_to_hamilton/tags.json | 7 + .../dagworks/translate_to_hamilton/test.ipynb | 168 +++++++++ .../dagworks/translate_to_hamilton/test.py | 44 +++ .../translate_to_hamilton/valid_configs.jsonl | 1 + .../dagworks-translate_to_hamilton.ipynb | 294 ++++++++++++++++ 9 files changed, 896 insertions(+) create mode 100644 contrib/hamilton/contrib/dagworks/translate_to_hamilton/README.md create mode 100644 contrib/hamilton/contrib/dagworks/translate_to_hamilton/__init__.py create mode 100644 contrib/hamilton/contrib/dagworks/translate_to_hamilton/dag.png create mode 100644 contrib/hamilton/contrib/dagworks/translate_to_hamilton/requirements.txt create mode 100644 contrib/hamilton/contrib/dagworks/translate_to_hamilton/tags.json create mode 100644 contrib/hamilton/contrib/dagworks/translate_to_hamilton/test.ipynb create mode 100644 contrib/hamilton/contrib/dagworks/translate_to_hamilton/test.py create mode 100644 contrib/hamilton/contrib/dagworks/translate_to_hamilton/valid_configs.jsonl create mode 100644 examples/contrib/notebooks/dagworks-translate_to_hamilton.ipynb diff --git a/contrib/hamilton/contrib/dagworks/translate_to_hamilton/README.md b/contrib/hamilton/contrib/dagworks/translate_to_hamilton/README.md new file mode 100644 index 000000000..15199d6ce --- /dev/null +++ b/contrib/hamilton/contrib/dagworks/translate_to_hamilton/README.md @@ -0,0 +1,52 @@ +# Purpose of this module + +The purpose of this module is to provide you with a dataflow to translate +your existing procedural code into code written in the Hamilton style. + +```python +# Make sure you have an API key in your environment, e.g. os.environ["OPENAI_API_KEY"] +# import translate_to_hamilton via the means that you want. See above code. + +from hamilton import driver + +dr = ( + driver.Builder() + .with_config({}) + .with_modules(translate_to_hamilton) + .build() +) + +user_code = "a = b + c" # replace with your code here +result = dr.execute( + ["code_segments", "translated_code_response"], # request these as outputs + inputs={"user_code": user_code, "model_name": "gpt-4-1106-preview"} +) +print(result["code_segments"]) +``` +For a jupyter notebook example, see [this link](https://github.com/dagWorks-Inc/hamilton/tree/main/examples/contrib/notebooks/dagworks-translate_to_hamilton). + +## What you should modify + +The prompt used here is fairly generic. If you are going to be doing larger scale refactoring +of code, you should modify the prompt to be more specific to your use case. + +If you're going to use a different model than `gpt-4-1106-preview`, you will likely need to modify the prompt. + +# Configuration Options +There is no configuration required for this module. + +# Limitations + +The current prompt was designed for the `gpt-4-1106-preview` model. If you are using a different model, +you will likely need to modify the prompt. Otherwise, we're still tweaking the prompts. We expect to add to +the prompt based on the model you're using underneath. Please reach out and give use feedback. + +This module uses litellm to enable you to easily change the underlying model provider. +See https://docs.litellm.ai/ for documentation on the models supported. + +You need to include the respective LLM provider's API_KEY in your environment. +e.g. OPENAI_API_KEY for openai, COHERE_API_KEY for cohere, etc. and should be +accessible from your code by doing `os.environ["OPENAI_API_KEY"]`, `os.environ["COHERE_API_KEY"]`, etc. + +The code does not check the context length, so it may fail if the context passed is too long. If that's +the case, translate smaller chunks of code with `without_driver=True` as an input. diff --git a/contrib/hamilton/contrib/dagworks/translate_to_hamilton/__init__.py b/contrib/hamilton/contrib/dagworks/translate_to_hamilton/__init__.py new file mode 100644 index 000000000..5da444007 --- /dev/null +++ b/contrib/hamilton/contrib/dagworks/translate_to_hamilton/__init__.py @@ -0,0 +1,329 @@ +import logging + +logger = logging.getLogger(__name__) + +from hamilton import contrib + +with contrib.catch_import_errors(__name__, __file__, logger): + import litellm + + +def system_prompt() -> str: + """Base system prompt for translating code to Hamilton.""" + return '''You created the Hamilton micro-orchestration framework in Python while you were at Stitch Fix. Therefore you are the world renowned expert on it, and enjoy helping others get started with the framework. Here's the documentation for it - https://hamilton.dagworks.io/en/latest/. + +The framework you invented is a cute programming paradigm where users write declarative functions that express a dataflow. The user does not need to expressly connect components in the dataflow like with other frameworks, instead the name of the function declares an output one can request, with the function input arguments declaring what is required to compute the output. So function names become nouns. The framework then orchestrates calling the right function in the right order based on the directed acyclic graph is constructs from the function names and function input arguments. + +Effectively, any place you have variable assignment, you can replace with a Hamilton function. + +Example 1: The following Pandas procedural code + +```python +# my_script.py +data = load_data() # loads a dataframe +data['avg_3wk_spend'] = data['spend'].rolling(3).mean() +data['spend_per_signup'] = data['spend']/data['signups'] +spend_mean = data['spend'].mean() +data['spend_zero_mean'] = data['spend'] - spend_mean +spend_std_dev = data['spend'].std() +data['spend_zero_mean_unit_variance'] = data['spend_zero_mean']/spend_std_dev +print(data.to_string()) +``` + +would be represented in Hamilton as two files: +```python +# functions.py +from hamilton.function_modifiers import extract_columns + +@extract_columns("spend", "signups") +def data() -> pd.DataFrame: + return _load_data() # _ means an internal helper function + +def avg_3wk_spend(spend: pd.Series) -> pd.Series: + """Rolling 3 day average spend.""" + return spend.rolling(3).mean() + + +def spend_per_signup(spend: pd.Series, signups: pd.Series) -> pd.Series: + """The cost per signup in relation to spend.""" + return spend / signups + + +def spend_mean(spend: pd.Series) -> float: + """Shows function creating a scalar. In this case it computes the mean of the entire column.""" + return spend.mean() + + +def spend_zero_mean(spend: pd.Series, spend_mean: float) -> pd.Series: + """Shows function that takes a scalar. In this case to zero mean spend.""" + return spend - spend_mean + + +def spend_std_dev(spend: pd.Series) -> float: + """Function that computes the standard deviation of the spend column.""" + return spend.std() + + +def spend_zero_mean_unit_variance(spend_zero_mean: pd.Series, spend_std_dev: float) -> pd.Series: + """Function showing one way to make spend have zero mean and unit variance.""" + return spend_zero_mean / spend_std_dev + +``` + +```python +# run.py +from hamilton import driver +import functions +dr = driver.Driver({}, functions) # by default it creates a pandas dataframe from the results requested. For execute to return a dictionary we need to pass in an adapter argument +outputs = ["spend", "signups", "avg_3wk_spend", "spend_per_signup", "spend_zero_mean", "spend_zero_mean_unit_variance"] +result = dr.execute( + outputs, + inputs={} # no inputs required because the dataflow is self-contained. +) +print(result.to_string()) +``` + +Example 2: The following procedural code + +```python +from my_pacakge import SomeClient +client = SomeClient() + +content = load_content() +system_prompt = "you are an awesome portuguese translator" +user_prompt = f"can you please translate this into portuguese for me? {content}" + +response = client.chat.completions.create( + model="gpt-4-1106-preview", + messages=[ + { + "role": "system", + "content": system_prompt, + }, + { + "role": "user", + "content": user_prompt, + }, + ], + temperature=1, + max_tokens=4095, + top_p=1, + frequency_penalty=0, + presence_penalty=0 + ) +print(response) +``` +Would be written as: + +```python +# functions.py +import pandas as pd +from my_package import SomeClient + +# Assuming you have an internal function that loads the content +def _load_content() -> str: + """Internal helper function to load the content.""" + return load_content() + +def system_prompt() -> str: + """Generates the system prompt.""" + return "you are an awesome portuguese translator" + +def user_prompt(content: str) -> str: + """Prepares the user's prompt.""" + return f"can you please translate this into portuguese for me? {content}" + +def client() -> SomeClient: + return SomeClient() + +def response(client: SomeClient, system_prompt: str, user_prompt: str) -> dict: + """Calls SomeClient's API to get a response.""" + response = client.chat.completions.create( + model="gpt-4-1106-preview", + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt}, + ], + temperature=1, + max_tokens=4095, + top_p=1, + frequency_penalty=0, + presence_penalty=0 + ) + return response +``` + +Now, the runner file: + +```python +# run.py +from hamilton import base, driver +import functions + +# Instantiate a Driver - the DefaultAdapter causes execute to return a dictionary +dr = driver.Driver({}, functions, adapter=base.DefaultAdapter()) + +# Specify the final output we want from the Driver, which is the response from SomeClient's API +output = ["response"] + +# Initialize inputs if required, in this case, we should load the content externally +inputs = {"content": functions._load_content()} + +# Execute the data flow to produce the output -- will be a dictionary because of the DefaultAdapter +result = dr.execute(output, inputs=inputs) + +print(result['response']) # The result is a dictionary with keys as the output names we specified earlier +``` + +Example 3: Handling if constructs. +```python +def total_marketing_spend(business_line: str, + tv_spend: pd.Series, + radio_spend: pd.Series, + fb_spend: pd.Series) -> pd.Series: + """Total marketing spend.""" + if business_line == 'womens': + return tv_spend + radio_spend + fb_spend + elif business_line == 'mens': + return radio_spend + fb_spend + elif business_line == 'kids': + return fb_spend + else: + raise ValueError(f'Unknown business_line {business_line}') +``` +Could be rewritten as three separate functions to help make the code clearer and simpler to test. It relies +on a Hamilton decorator `@config` to do so: +```python +from hamilton.function_modifiers import config + +@config.when(business_line='kids') +def total_marketing_spend__kids(fb_spend: pd.Series) -> pd.Series: + """Total marketing spend for kids.""" + return fb_spend + + +@config.when(business_line='mens') +def total_marketing_spend__mens(business_line: str, + radio_spend: pd.Series, + fb_spend: pd.Series) -> pd.Series: + """Total marketing spend for mens.""" + return radio_spend + fb_spend + + +@config.when(business_line='womens') +def total_marketing_spend__womens(business_line: str, + tv_spend: pd.Series, + radio_spend: pd.Series, + fb_spend: pd.Series) -> pd.Series: + """Total marketing spend for womens.""" + return tv_spend + radio_spend + fb_spend +``` + +People love using Hamilton because: + - all code is unit testable + - all code is documentation friendly + - one can also visualize the dataflow created via `dr.display_all_functions()`. + +Your task is to help people translate their procedural code into the Hamilton equivalent. If you are unsure + of you translation, and need to ask clarifying questions, please say so. Otherwise have fun! +''' + + +def user_prompt( + user_code: str, special_instructions: str = None, without_driver: bool = False +) -> str: + """Prompt for user code. + + :param user_code: the python code you want to translate + :param special_instructions: special instructions on interpreting the code. E.g. help the LLM to focus. + :param without_driver: do you want the driver code as well? Defaults to False. + :return: filled in prompt. + """ + base = f"""Translate the following procedural code into Hamilton: + + ```python + {user_code} + ``` + """ + if special_instructions: + base += special_instructions + "\n" + if without_driver: + base += "Please only provide the functions and skip providing the driver code.\n" + + return base + + +def translated_response( + system_prompt: str, user_prompt: str, model_name: str = "gpt-4-1106-preview" +) -> object: + """Translate user code into Hamilton. + + See https://docs.litellm.ai/docs/completion/input for litellm.completion docs. + """ + response = litellm.completion( + model=model_name, + messages=[ + { + "role": "system", + "content": system_prompt, + }, + { + "role": "user", + "content": user_prompt, + }, + ], + temperature=1, + top_p=1, + ) + return response + + +def translated_code_response(translated_response: object) -> str: + """Pulls out the code content from the response""" + return translated_response["choices"][0]["message"]["content"] + + +def code_segments(translated_code_response: str) -> list[str]: + """Extracts the code segments from the response. + + :param translated_code_response: + :return: + """ + segments = [] + segment = "" + in_segment = False + for line in translated_code_response.split("\n"): + if not in_segment and line.startswith("```python"): + in_segment = True + continue + elif in_segment and line.startswith("```"): + in_segment = False + segments.append(segment) + segment = "" + elif in_segment: + segment += line + "\n" + else: + continue + if in_segment: + segments.append("***ERROR*** unable to parse last code segment.") + logger.warning( + f"Unable to parse the following response properly:\n{translated_code_response}" + ) + return segments + + +if __name__ == "__main__": + import __init__ as translate_to_hamilton + + from hamilton import driver + + dr = driver.Builder().with_config({}).with_modules(translate_to_hamilton).build() + # create the DAG image + dr.display_all_functions("dag", {"format": "png", "view": True}, orient="TB") + + # user_code = "a = b + c" # replace with your code here + # result = dr.execute( + # ["code_segments", "translated_code_response"], # request these as outputs + # inputs={"user_code": user_code, "model_name": "gpt-4-1106-preview"} + # ) + # print(result["code_segments"]) diff --git a/contrib/hamilton/contrib/dagworks/translate_to_hamilton/dag.png b/contrib/hamilton/contrib/dagworks/translate_to_hamilton/dag.png new file mode 100644 index 0000000000000000000000000000000000000000..cbacb949afb1ff966feb8d4f9fb37f7f4fc9e09c GIT binary patch literal 46590 zcmbrm1yok+8ZP=HN=SpGAkrcwseptu0)ip}0)ljhAf1AMG)PKGh;&M~q#_{Q-QCh~ z-`}8BJN8&(Eo%OAe)Id@_o;V(P^bI z;5+R+=QXu5LJ5{OIW|324#{fI!^M#xG$dhVF8KH;EtLv;*&n%csoH}D$P=R1-T zm}+A9#BxgJ5-mQo0c`%Q&1$;zCjn^vYI^--dUErnfyroCPdSavG2UInC!kdezuOzL zci!01{1(L_+VgG(PyW4mXZ*J@JwM-5*5A`}ceN zk{>AGM`Hd7sS?4+A4xoUnkx18x4+(bN(Vo|2v?5GBtZUhJM-gyZpkKJLx7U?IOYFfefS>orII5(Y}4*%15k%E}HE8jZqZeSLagT&kZAWStaL{}(sR zaG;fpM=jj^{XLeM)>Ngfk&mzMGq-HTgDm&7A#?->2S*S&cSaYN4}R}+f2yZ)j~{36 zc}Kvr4`eBS4U3Fyb|8Zv>D(aGTkpJvj(5xF>sO}9m-4VC-Q(kweP0#ZQY8cF^Oc40 zohtvU^lhpDdCe+2OxvZdmJ*}xBi#4+h=0Ehw*P(|VTNgXZ6Uu01{MUdcSW3> zoG>sjL=QJ7dNLGlIyySurll3j)f5xE^6=q99YaIko-}E_?j)h3y>1as_cKR*BO|$i z$!{io_+(`MOo~aTa;xF^CJVB?_;g;!Rmu+4lZ^ zWL2%{>guXmnK}858#fS$fPet&H*dt;-G#Zhxor+sRhrw{=x*OeWMpJ$3SnVk9UUFD z>&jqc-7iv3hAZ&Qu+jL0ImpW9dPRYx|K`amE#jD-q~Oa==}yO6zfbeENNo>6jhq{JxkoRAi8nF3ijaI#O(5GpDvnVFgXx!m{edB(($ znwy)4#m9HBxx_2o`Pa>swziUq!}dpr%F4=`pO0bQqH#?_))^UZuKV}VmEPXnIh^t! zRcE?!eP?xm#n{*wc^(j+9Wfkw^X=iZ3$wGc>6VJmUJDTr$jlT~Q;Tih!0#nn_NSmpA8BuI_be;pgS~r%`(@;G z#*ex=#8PFf*Z@mRObmH09bH`XX1>4wJvi99GuKw6+j1S7oa^e7Cr^}=lp092UA;=p zhQ4=q<0T~}$;iq!DvF%b_a=$n4$BmheY^Y{rNprFyD!eo>8-6$^%BEJj~=019(Q|$ zB`06LnuC>@JI+^z$GQg(%}P%%kuAVe>uHz$bt!Z0YfVi8XX|i=LVWC}PZZ^v>gsWw z3H%@9AA57Q{_N;bhpGziLI)A+<%JT@ZG?NeS-nMuh>nSAi;7AYLPtk8G%;Z&ad_)8 z&}SJL8Chu3m+_%_V|DeX`{~C0Pf6m}ktOI_t1*S4n9QDzrDe!U-&eh!R7t|24?#iA zzf;7+KYnbQAeW)&%26%ACnfd!{#|T%bTn1m54SYBv$J#Il{ajYx%nR-W#r^?99$w` zB^x%z%3m88v}jdJ(MxYpj}QqS#KAe=-nPyXUWRTG_Q}}9B*o+Mf;XRqoxSz*Bk$kk zR@%AR^+~6x9_J^#@K%-Q_Q~Its!~%4*g03IYGlJveV`PQ3v}Cv67=7OsN)1(!%GFN>V{6&)3hdk(Of0M>*mR-o?K12Cbf3cV z^0p5|OceX=6C&GNXg0R_@^QKvxWQv0%irTA~&aVg-uZD&OG^kwyEEZ4JxH%BpA~B_+k$-d;*l@*3iE zdiqtl7XV*lkTxS&V-3^7BKdrKOel`74ea z{o>-H{nxMNT=fziXt;03cXoD=J;3hmTe!5Bu-nOWf0i<>0U;3)r|Xe589DhQWo4p} zkdOy&=Ka+)?puyG9PP}#hAq|Tjjp%1*vZT25n+DQL~UzyD?>3k#A2)r1(6{i_m1Co z5n&wb76gxVW?bapHd*6T(we5g%*ihow=V!Ye92IV- zd>0p|RtQl;L&k)}#PF!7Yueh{%gf8taPMUNZ|Dc}wE`<`m#$=1DP6)ACF9h+3Oh*0 z%8Enf7c?$wTU&29GiT@L;mOINbkZR@{h3cI9Cv7i&$h*p?+11kXHZbkJ-t@!%$%IL z<_|<<_bn--IkZDQf4-fcM?fbfs-S>#>((u$=g%dSl){Gl(IPDWdAut7jQ~N1&D%mk zRM5R7W7xG2HJ8H3ZQE5y@^f_bllse-0c^=!2G>y=9?Qw8%gSOZKYjW-O)8j>xx{8p z3eA&&iHW`XKhBO}+fYu6SR7FzDIu*B8Y3iS;Q8HG}dEdGp)in1ws-ezuO z)V9CBulDjKh3e+T`Ki3PxHpOppofp9V=_(QcJ){|I0gtcO-)MGMYlgii_-ufSUETp zvjvR0li&wWo`eLY(xxA$9)_-$4l5@|;9q}_$PN*1X=$6o@4^J$K%c?RV1qFp#l^t_5tNIle7Z){k z_1Du4USz_q51>k;F)%RDGckE{`a-dDbaoEt=eTnRfk@BDc+&wpVtRS`&RbTzTMrRR zZ&f$PuAri#AOHZMD;*ddl&vc39BxhdWn?fSKEp!kEs8?vxDR z^f6s@CVU7DAe}%GwpqGclMD0n{%02(xq;c6Rnb01FEX7D7i?_nBE7AgC*z?N5tMVap&8@F|uz z%}&0nDl5-jaBP%?`|jnR=PShXl@8`@2}|jxrKVnilLiM};?=8LQ!_kwB_$;RXgB`) zg$>=v)sGGd#{i0#=37lvy$$x*f8nw-E19VH-FWo?rJ=DA1)$;EXqw>Z4cioSTuL2Y zR!e($X0@Swt(IsFb#m!C|TLMP_E|DYn0E`xu z-MxEP4JtSb8=FCpik3)!o!hB%i_2^?CYonwqM)kzM3RupFyO)AA0L^>5oJ1!SBkje z?^_unh5=Ew!)E%4DCDR+63ZQj3s;);r;qUUTEh+?%Q>egJ=xUj^oolk$HK%6&kkYD zFJfkAH>`HpGHmh-)mgo)-YofYxxBUp2>3MUe5Z|C{>}})VYMMuYisMoovNy;L3I%> zu445e^oBBvu@^=+gL-&PgHu|PPI!2EC80P%C$3DBbl93)c*1$50jl`S;-c@A`)P3e z&oo%;SP>6lfX+d%2zD#I*QBJR9@s7h$!H+(2?^gpHIa~(o=XcAb}n!PP??gSAMzKa z)H`h5f}MWPq?bNdvyu=_PI!2@TB&K2Ol4-~ojc6TuZIg?D=8~`!+ucHASNaTcsb{8 zm1{wl3eWBDN_hSKaFHH*G%F?&F;Z%xy znTNi$W%}e;rlPt!El^U`T&*4#z|#_28G~H3&W{L&gM1OkZJiH`#pc-uoxgr9FV`d( z7u_e?U+F`K&9S|=C;t5T4g2+BFHXHyQE6#Embv-)t9p8RtlD*yP;H^1wspqeuViMH z3L-=C67>uV!v`qZFgAuf-;UeUD9CaREl{UDj0P~S^OmDVxn(faZ(14}6mFv~33++! zKbsSQ0IY5RW{%~zWgIOv^8?<1gwD_+95%-vy?MhG&8`&yH{JvnCLtkN{+)tq*!k)G zSh-cx)D$(clmrGgRoKh{^6EYqQ5%zzkS(43nN=V#E3ch7zo+vm)lOoJ}Ip(Z$H09K@l)ipgk9( z?cNC083mD_pFjM~R1@OXyun3Qd*67A|aruq@m|wrWbqY4_^OmoIp;S@Gl_B_SXoA>ri|Q|arV z*MRYl0R2z%+v3(Nx+-|o$fSHrCtD-wYB|6PAKvNU0Or0RNKgCa(CHHY$G0PiU@H_9 z6vC*zot;PZ?&tcHI5ceCr>;F$uU!KmIS^a2i;hjXK$C_Mf4`ONPcBwtk9PSC!0PSo z&o<_d?>zlji)=@)YMe}S*Z~nm+0gson@~P<#0Tj>qM;c7aqpz}yLVU47TKg^pAyv6 z)Y@>~yVjvt-m7BhhTpIpNGdWB;7!1sDHjylf-^&GMGl(w>{TQjN-bGSOC5lz0&!ap7XNv zoRa4|`DA_}=Y8Y2(h17t)m1SSm8h+I7%z=e?az*EBBP^qzx!a8Mvv5ch(H^fnVakL z5C0(rvJbmv1wvbUN`U$k%a4jM-x9Y|`)^iLx1c&&^}Ey2(gJh!1P;V!TA%(i4gJvO zV4VDUWXxPiL%|WQpDlb9857kwVnIKZ!o%EoV9U+M7QBVue)+9E9`i-6nwMnYh1-6| ze=(QwV&K5Dr(5+_`@^OI3vR&C|E*!IvL61az#n;8=jEd_Kqt9^IOu$Xf~ z+cuoG$%7p06&?>|MQB4hmxICW9bL20RKryS)kD79B+AMR9Le#;N1L~IQzUOa1^ggk zW5apq0(%b>py|!ckik5SJu>q8q8cG~A0A#RL3^M4eD`9jEs!;?%!_%s?~Dg{8(PTd3&bmwZ8sU1qFo%0s^z` z;dHbHu5NBK8yjq8IRD{%ErNRC&Cty5K~0E_kJsCtZX5<$HGHkocJY^2eZ2?>NStPa ztialAI&#_!{;ZDxS`QIA0Uu~Z8_2&}t`$kjNy<{m4ek$aZ1 zA@bTQ2Hod-w%bhG=i_0LnydUANqU_%qVl(f(UtvN{c!@9Qqn7A(k);13b_h8(LSWg z+mY>NKQetMsC!YRG|=UMWo>$Ch42iX>T2p{d51uV6kvS!wUQ`^>yS}sEM5^l%GJhm z?d`at9da|I!$@*!thaf9vi7(V^Dptx(Yc-U(CHljJ?un*w_?DEISqc^2;aJ^sHo_A zd46~V`*&t#1(S}BZg_ZD2eg39+}!z}yn_zt;oARA;-7JIaf!hO%-5{^&URXVc`5>= zQFeVa88B^HO${aVC1j^(e^rBlSRO5987;HuIIjm5u9=vd%Zg3;03Q&tH)v>8b}R3+ zJKR0e=QGDytp6B>glteE~ycKX^=Zr~x0L)1N^Vq%ew#xHVoIjP76+ z80E36NU=FlgZo1Lz;Cdrxw?YjEv|M5wO6i@;%|>;W3n<6KatQ|4OQ2)tq&}<>)$yg z(S;vxqxgozV~`#eIVMC0Om{QUak8T^W-R{Z<^rt4%_}|j1XRLwjr%sItt$D6F82%O zI&A2|!cP#;__oi^u^;B({TK93lz|U?>$84=m%ny-`sc zqS-VUdyTdxYvYoVZs`RTfwpXDW!1fZa4<607Fx*#pZqVvZ_TNup}{RDCs$+<`)|p> zELifHy}iA>qGBk}Je!WqsrqiXNiqa<5~2T>I&foTan;5N`lRxU7lx>4*!U}Y=O+i) z2tl_K9=#w=uszT`wQ8NCSd?GB{Au9%?gI}>*#xKtW*%as2!&np@)qKrx zN9vD;+IBWJuR-Lqs=EvR#sBd;pF5DDr!C6bfk``^N=~` zT~SVk?(F&D1Lnoa8V5oYv~SC)x=t#B+`3m^PMmCj4}@jA0jn;mc0zKB`Nc(AFiiei z)|jX-iOR{$YykZjUb}x#Q0~^E>;kk2|IkorQBj1vfcrj}co zM6L6|ci=!3Q&EwT6=P@JiGoZ!!i&4A|fNNB9=!=z77lA zym_<80B|EHRJkRcvL6{A-MKLfycyxSzi+2mZI4q|SBF&ifLoI3-C<<~)9-6L*k~>Y z(RpvB2w@Ehc-$fbZ#0}MO1z=rPzE1v1WMT39pKP7u5o8YJsB{1Cfj57@SKSXp);y* z7E0q#@PllDTQb!>DP6TQ#bB(uV7Q8%uk4a#PieN|B?WMx%&e>rtT{KJ9Y3;S&ta6P3<1`K7^9zydUZJoH*m4;B8lqN*x@-qh04@>zLa zR+jpjKr7j+I#)~tt#n~F2r*YO1;n1kBqUH~&1L5#emZeEMMd?L;C@nFT`jJti06k# zjfS9nV2wabFD;=X+Pk_IN1r`?N^s2i*YJsviI6GL40PE`oiPO#K3*TOsfSaID7I%yvJFPB(g|K+UPYIKd+2c-0Kn-d~>T2+|R#V%y6- zbXkfnkH7!QU8NmgUN$azJ8=I2fE?7!Dxtaz4p7qO(SjE`17GHR`TBJzM*%!55b8L9BvZHy8()Q`d9dI5yhnujVT1PIBtBtEY?MTHvEt?!Fm4B~lk0lZM2 zQ(fEW@Nh5KtaVLc6ge{`Gd7?0rJrXL<;?p$D0;ipgg4i%ht6R7wN!SXQ`kW-KQK=& zD5t8hRsp?=X4y^1aa+_}{b1>Bx#a|+{bHKRAn)H5v;D1)QYn^!~ovndo{g);dW0A6pf>#B*iEvA=cRjE(E-(m%Kj|D8@WxrQMQVn|P^nQE>^IWDNiw9@V14udNQ^a%k*OLJ@MEEpVW z)%I*iRs<}r;m41xNw@N=*RNkE6SQYVs#dYFWLMGA^#E-i&3{zXF*9RoIU+ui&s*WR3Om-urGml_jJ2q*+mplxE?{l`cQ()>Wqx zq@7d#LFGOPc{w)`?l8MG#?&XibgTLguwRxpe(p&|WPaLz@lsg(7kY#B{E)?i zx6=*?&$@8Vx{KG`RU;}B2O}L>BCH$h1Upl;?J^3F+9$1I^Z8?G)z|Y?m=w>W8XZS z)KmCBvQ$LXCK%nwgPEC~oeGOQP9<#B@$=_*c!t+TMyf`Hd%{HkRHy%Jj1?9a&*p1I zltM9}f!z+Seqm{;q#y1qDb2^xg{7rAz_x{DW%&nLI8d7-qRm2nb#`*Po!Dhv{08sh zUyiQwlG;SA3n{$*w&;&bJw9apCVf?LEton(e z(u=1AERf9M@8{)B$yYxm_p4ctk&&yyBKEo~U#*@JRrVy$;AZdfcGHo`2yJBhGXEzi zW`22jY*s^-aB9By^bo*_6$6D|S63H|iYsPjYZM*#TOHcsLAt{SS(2RFuwVU2?;&1R zJ;`xy_h=3OW#qS@;9yR><=+m`z_ucmcVq{Ou3o(=(_ar};K{)n`sY<1V?CzFpzbYC z)qAA<8A$*Gu>V16Ydrk~^1Y|i;bR~W#8z6NBT9DrIG@w3wm07qK2sj@1l@L(b#IZ< zq_l_ko&?9&szU|!I~*K-qLQ~u{=h3>D=GT|?ZXewbHBRO=vK(#mz*3qht&?u$Nlq~ z2b~04&-yGTeZqW|-M)R!FyWSIV)@nmK1rA=Wk&Ahr~UgS_#>Ys6uN?`MUuAgF=#Rc zRK9>)9n^nXxF&c1NBc{uRaCpR(crCn`WYbm-Q(ez7sT$p$`e>Ej7f790raX}NJxT& zlFDUnGuVvFEE$S&r`hsE^|A1edDsR=;1q#6#)AT0Gk&l6bCF<)7oe<%Sdn-H_D1Mt zW)BFTK6DbnBifq%k>;V#^^~7R0q1X43R3~Z&xHj8e*uCnhdZ64S*Z0*sb;GC8!Q*O zPsLtR6iIac{COP;|2!QDC~sx}jke=?Fi$;RhDOZN)-{<#bM-yjSqeuza-Mwn$uAtg zKP+=(bTmLdRT^|K?Z(E&J1p$qXPo7wE{Cn|BW4mY={o|F*2oh8#v<*J?2|C{^7692XeVd6l5u;f#TIUjB*Jo{IfFraE~lVwHpy1@NYB*tr_k-B z?ZCe(VnlH<7cRB%RW~Y&+*n=+IMvPyrZaB*5HUFtw!Z6&OCA7L>mC&@&71e|`!Xe9 zw?NN7lUk8eRVDQ}vmq>aRoC!NQF_j4F->Zvyt4A9EzX_@RdAP~;n9{y)8|YXZ8@Ks z50eBwVTMgDE!UtZT70`eGV+`EEk(8Ei1rwRn)B+@Zsu)egmC)yp&!2fF|RlyV0}9^ zmHo3y&L}mmqKw?YEKw;O{5hx;&xQj1{TslXy_r{^k@3ac$l_@D0*vM9A#IPA^V35! zHX;H7FC={j*Y#QHM6Y&tRcvhRZ2sJ&rZuzyJ6DuAYO zSweZ{Y|PAAk_z3(U<>QZZ!)XS5cg<3JKnpQCn9@xemJF8 zqv+UgZ~wu_^6|Oqy4QK1ZBd;6-aVz&G_2iG?f*&h>qy6wJ+)Z3iHgo`dpudL{j$@- zMZX1T5z^KG?~Bjs0&@?~_mQeL_2P?(*XF$_dB}uM(NmS{EIW5a@Dwkd{QF%Tl%8o~ z8!cTt0JaNRImciNu)(3M=Gkb0%m_sNg_#zlyI8fVz6~C9rU=(gEl3AC4KnFrBbqPX z#PgUuOKa^JKIcszZ7pQn8tnpPZ0Ocwt?Zs?*4wMCXo7L|3RJDzNRw)75@{FC&du!r zicJ@mnHQ{)`U34-NK43dUMG~pP`;^++9L=~_#PDrLT7)ur!*g!VWusYriI0Z08!$i z(o}0J8XU?}@Lc`>oqOBLtEh-|JDhO%H9npIv9`f4a-@7v^j|nj?SZy2gw zkGbOEzqxsM^alqAS$TLO0Sbiwd1Grk0_IT{(zS(@;s`9<2!tPK2DiX7kA;rbH#ler z(Xd>&dsbT7cN!utf6Nz`mYAL`fjPu_`?jaLnpz>;atWx(#qc-9VA1CX0a7%Zs;ljv z+qU-r%gO*EP`VI6FidtoVO|?9qT{}Q-w1>fLug@yp;OTCW5K~kZ2+LxV%VM3d2kSqsD3P)SN` zTwD?OnOUpAS^tS1;@rZc>7=Ul&@bpAECttr8{j&uSR2xVTl^K*1mPuhaEmIqKxN5= zoTofErorUgzHF~*V)-9DxQF;PHI>W~15XdsKMsV})3d$q5lEM+fjSop!AFgHcOlhH zSbq3Y#vv7)WmW<_%;^ z;DlGcIZ)`I-0X%W19Hg)p5&h%Y3ibDSS0sINlDQ>og5uy9=e+i=Dr5iNDmPUq1Ukr z8#+=WU^8mq-H{=XY6+R%uYcuQT97Xe%@erhV;)bIL*q;EPR?K^?`K? zZWpxy7y-GSiuOCiaMjzj3NA=-0?P_xzH_G#&^fHtfAXQK`Txs&sDOX~D+9xOmYCSs zcCfB1(qyeXPyjgxXJuz2;|-STP%O;G%JO7%`@%j@Aj+&~#9pYY|F9@Qrf+(B!cETr z=#nEyNJ*IpnGk&D!}p@$Qfx8wPe0mWo2?#pZh)Mw4>EySe{vA;A7CW5VwID+t}EQ?}C|rgM=g%_TxC0 zmX_A9M8TL-Eg=5@P9Z=F3|SJiEwHX30OJqwFY5&$LJ&;!g|-U5@C~>I=v8g8oO-{Z z{NyKm`NF_wG3okRwH7c!p$B0qQXu~%WM=HIl%*hb z!Uu^Z1b*D9+~C~Pn{X@;4WI;}Jnwwtu`$XEQiqtN1xxcmG>B50mj_f$<+ZNI zgrWoD?o%&bzPyg0{SlShmc|Di-MB9UCngi>I>>{_>{F!6knqcR2MbkT6^I5{V{^BI zr_EK*hjLidUH6v>An*Elua$=&BIqz)f5(|n3?nNJ9CI_Xv1y{oXxKL#9C#3&R;zR6 z2U3E}C4h!73vlZQB0!bJ1JX8=&)x231B?R)4wB2y=a9+eV+a%CA=5mVM2yzjw4lBb znOT93KV0ilKD-Ow4&WyYC>F>dEJTF~fZBi?otmCLe~zA-luj-p3pM9+vG0cmA3uJq zyc{yZHq$jUL__dCX?9-tNsO3&r7GL5`_MCc9vKCza6cE|;^MMi{!L@IIZh5Gn1~tq zYU{GwPInhNbk`;ywrIq!+FXEdWu}=7QAUvPzW4X<*D{h>EDwEz-1P>9w_Hv#-qUYwsew`LW3l~N_U=zM{iJa zI&`S;q$GAOUw{w9vSO>;zPu{~Iiy#wc(uQk8vrvEZIK$QO(AiEdE)Zt{vuPUC z@9mu(0+v1G!y>aG(AA1;oR79&C)(1g*g~EVu!_z3$y*R$0`jy=3~>-TkRLw?AuvAr z*JCq0g(ml|sE898+Ju8W5A+yhi}7nCpvh1Fy#D{8gZPTx76p_663+C-M)2^k+C#@} z1ah1K;ORZkJRb1#r+_gKE9k%uCfhyRMfv9DW*yiCNBez>Ahm1GU2B7LC!X^n&w9E6 zCE!(`5Cw@z&~m)usZqS!$^IjdHGp>D6BEA& zBU=Zey0l-L>8Y9=4*m~hF5~T;OgRfqkL^bE!f)T2VJQ&^P_T91PL+lZ#(+stcQlIy zONPv<10nwo^&3=m#uxUG8iGt^E9|LC1Ell_m>&g!%mP6|6PZPX^Z=9=-G!e~pqn$* zLC)N{4TI-wO%`Sw?1OjpufV%sA1lZE$f)og6x+%LfMIHtZ@#pX=KPl^(m(!hQ6%Vp zh&h{&dXSMWfwy;G`PBgET43prQx%_AN7+-9?$`$p0fV!dZzq7WBu!7FtfGR< zup)QErr5<{Pgud%nq@c#LRwz<%sput`ifq>vja8Ye;}jdAhqcj8~a1OY#JVp)vz)) z#*mhlP7ru|y|S{>|HB6_c$2w#d1mHYKv^NvCJrWnxrIefint#|N3Hug1tumY%qsZ4 ze}C=jl}Cv-hDJtoAQwUTeqUP38*^j;U*Es4s-GRux}U$3^Jo4C5roh_B-G_8ym&!! zOT-;pP_SB{KqEpQn_=Y;+=Ys_Yb4KAR3HQ#dia=vo+`Ae|HdBFwdt7|F9=Wj3xz6; zy6wAxx%Fm_8~8!OLnlH|wF!s1ySk(xy9dYEe<6cl{U^;>3VM>{YZMbQAf2pNTj2{K*C zc<@Gt9|%*Bkoa!ZU)}J;|&8i{ZTVk!9dnV67eFdgF^xU~8oz zV^4qMD_-#RjsMKf{dcb6?{(&J-W*^sBL^4ocR2(O;Ecm4;s0hh5E;5c1I+fX4}n>V z|EM1@mxi2b`S2 zAP}2bEJJ$c5rD?LwJhO*uP4v72M<6UHDwdWpiv-n=>R%LhA2vgUpn4MrGDk@f*jz`{;H%^WjCl`$him@ z5tIP6^=U8$Ad|`S(!b*(7zEOPXOaH@&q)4fdH#F0@Be<( z^8es6UmD2tT+ttW`UVtXON?xZ$b%qX}O_{ z6qb~D1HuO|g+M^$1c87OwRs3rSCoSG*m|u&Gi)#5*ClR2s80W9Z!h7GA3xqkn+#+V zBW*_&l>w;$h_hjsP{0%!tulo3m7YG$dRq$;!9yV-W?AI8sj27(ig0ECvXCR{uc`yA znC$jPolZ&u_5%-<-Rj5o1OUo_{h#*b8$wkCCSo&Gh`h;pZO}j46AB8ffHHMi; zWW@(=|IqaaA7H7gAM*Lzc@64NZ?pNq@R#uMJQ0kCDn%e?b!9w5GJk~6&gza=*q}o8 zxe?~)o^nV5Zv#co*BXP!`D(yk6dKtPHu5AAymZSx^q&A3-S*Uv%1D>tB<;W<7b<$ zy7~=Glj%V=$~;<;?hXr^B$x+pO0>n)C9j<)CeFZ|8)zqwZsTuLB}pIySCAH&J9Ilm zhBzZ5qh9w{;e4|@FuqGnn^Ab1T3s1=C+=s0~w@l*8LZRj#gymTP-fmeD>>{ll^{ z(>>QCjI)z9ToAH+w+bZ^gxx6mzIKaRzPiPT_UnE1q5hqm%xx+XlXq$c3|*H~1nXL) z0Y7)&z|1@5rr|?3S|OE(w-N-Nnh=13uP^>*9XcEh*yYDH$294l+JG{E?Zw5kW!NFtLMf z@k>B&C$wHPEsA7{hTs;QYMr^+0_%-&a?OB{#f{A&2&m=0ETzC2vU)VtNWDjhYC;|{ zt5;6Zy#vC&!gU7IV4cT5y#^VnAne-iHFWJ-?B&XxSO?=!us(QR*2CpQ9W*p5TofDU3rO$3toKO} z*cL2fJw=0{T^oQ{__HuZZ$e)3YNtI$=fln5YiniGX`vIxaJg`F2cqo(M~C&PdTKxw zR*}LJ6VkJr=Qqr;MNk+GW$mA^qT7e+-5S({)KVZs+)+~x6HrjcW#zRAmTE4Y(grD3d zhMYF{R0@8tzEUcqI26kkaIZNncHUHZM)w={s!rWrTaY{ZGiJrLwN||qom#MVZ-03& z<<}`~I-`Z4{rBx|*BwCrFc}1&ieW+sA@vk(`*qRK$@AYMRf2G3{x3$xSC?ke3eRJ- z^+zE;hY%0QoZHZP5Qd#v^BDV{3`)GrISIeVB1;dY$jO z+*hYLWM2moqTkyFL^;JuKM1nd2ED>G5vCJ$bX5n-?_u$pCTe#I#;9P#73Q&@ubqGx z1;eMKRwtWrMq+yD3KEYzk9Vks>6pN~UbS9*c z$%Rf_Wxh^-&@5a#B)Aah?j|Tj?idb^g298)tytj?fC1D8MBug|9orJ!rc}RG>J&A7xB1XA9UQS+54J;v@|Nbybz=Mk$c`7L5T6( z#DjkKk%|@-LSKNb6L>&acsNbTA50TgSQ(7Or;O0|+D}vCF)^I*c2qg&R+~8y4e13W zVnd!!IQZx(w2#FK&}o80Lox|=Z*e`_qT6kEm$)?&n)+P~oHcu6*9ENFTRuF(kNZE7 z!})`00=lJ^J%{Md?jKTveQkv0Y_jnY`|8X$2CM65hu+Ine)L>l>Zac;xqV;ST>XoZ z|9DJ>wz};jvTiQw#_?V>8W%j9jzg;v zPrd4LR7BYY!8JEJJ;;;kO@*4-xOQ_u!?}j|wqYCq8-{adkKY*fi|S>8NQmCjCt}Cr zb!G%IgnI55`tCAytHMZ>eS5b-gjsg^{xHrHaX-B+Ulqp_MFHHli5LO*7y`ZDrP7KC zM;4^dwkCYDKMA0P>^AgUMT;qDiKkxlMr^*GNUekv#(e%|UvJO*p#&%E!%x2sDjPab zL%|C5+CvQg+4M^j^pKJ=y}lo;F-LUS&H23pPlP=#w8Mx_>DfL-o`QhRx%x&UVq&GevQ7+M~0S#q)0*Iq3wz1FbCPcpa=aXF--k~}W6Ijdd~y@FYL z_)5&US0e0{3cmL!6-d~CS81W z@tm%hkfzX!zFIcFn?IpMP;lYXh*zzXVUp21a^V#%gl-z`hbn@uBJa~9y*|Z^Ior8W zmAVTZ#?!LPpFik4;<;YcQvM1e@K!H6z4zoAmciY_(vBE^?91|~*~ig?$cofgIZ8C`r8?9u}p?=P6(iyD24GQ7X>j6D%c zraWmoM|GKZn?YE;{ypD>b6kjW1EzL{>kiK4VN*fB#V#4bhLe+WWG-rD9KQ2HN>V-jdCW1iZ!G zV@oP9C*(l-Vx`g3I-JhkreX&o!!Z>T1!A{_wE&HpJDLGz;SEG+*pSY)wJH=Hlk&0Ho zs`dJ2a}{r6QZl=zz31G;^V{IJU-?_FZvE%ZCFxmKZk1}@;b&gSHxXx&PtlKib9S+Z z)|D)bnLdHOH`gm^u3r1@OEqr9ezy=unEj9T!OhKr`hBWIT*TvOm;K9d0aEse&Dy*~ zDl+V_PXURE=WgW=OLzej9!Z$SOWRw;%!ex}hFzB@TfB;fZr}lji7hXGPuTM> zYgQfF^z>-fQ#?*<8|ytd_G*@twq2hVwm+}&N;^Fa!}kc>xLNmAGs-l>K_jG*C zkA=H(kIS~ZVCfaPcVnwBQ-Q}&j&*e@p?ha*(x2*DV*Vef>#qK>Qx%EY< z@0gudT{cEIFIMA21VR(5ve|0wMR-EOuW+GPN9$vWmUW&)bwMiwUYxZ8+jI8=kxQ_V z>>fR2)Xm*VL|`99aWGL-RC-OPlk`$r*YzR7!(~HXG{+a)cpxlU)irV9)P{2!d#(PZ zsMr~sZd%&W@fbr!H^oEy%Xl}I=Xp=woAM$aRC(YTox6H{OET4{zr>X}?J*fCif^%P zYiZVZLNn9ZJbtTD{mLZf!JO5EexE}}Z^ps;`R}#rjXyi3{Z8iWiCeT0QT?fgEpS4< zL*0?(rBL7Aq$lZg^_!p9zb;T678z^&slYysF)~Xn?swp$+*kLK#j8EO`fEJxN)$jz z$I<$oZHxC)HSbAs3f*wwyd3UDUGS1de_N@sP5yh$D-A6Jo&C1#Ec_1yighnE2VHkC zkqMy7FGEn96h^;QZx!>Em0`y}h$5%+!}s70*>S5@uCXJhZ7PUoM=9TkQLx9n4~Yd^mX9ceKAy#B`w(daN;`d3!NAKRi%c>sUTAJajs{ z!)7Wl1=~JLvNPa-;)oF5y8pSzVK&%$eMRL==Tdc_EQO`NUa6_JCO?ShMW0^Z(WAWB z!?X0!YRYrOZ0)=l-XCeOAtK*xy|lio=HGne*cYm?;Ts%RZ^`{$Q?uArWIVn+^-zwk zu&9*#eC|@MIz;~bO_qAceT%WAxT)Old5)tveM1F45ebn#u&rQsbsSYDKXh()IkS%Y zI0m(;uc(%oR8GyfJj()sK z@&lIvv+9>mS--25VLP#-B{1*_L_Fp-Ro+kT$W>oi=*q*ab@YPV8n@d{IXx2tfn#rI zFY#SN$F!sKbc$fjO1toBTa*f74q5mxYrDHT#FbVpErd6z-lr*}T(2hX+~+e2b`F0@ z{IgiMB|tjZLlItvY%(iPaM3~GFQNJ;lox@rsC~}%bpm_hRgQl=(vfA=cJ?7(Oqau6 z;=!JQ>(nM#bx10-4F(G|200>>?N+!+q0-);@ISx(<)^CrVMS)p z0|8>0Lw{;pH~u+`l0~!2?rkiG;*#>O&^Y2I7cJ0k-u(GIS3WKvirSe9uPI^t+I4>A z&;Ji|Zyi-t*S3#tQ9woEK~SW1Qz9kZU{KNu(t^_6-HIrUfYK=fA|>4*p-6X0H=FK` z-@Tsq`_4DMKhGFvoO2w*cRVjH_FikQIq!Mj*L_{r{AhL^%<6c`jYFz{1Vj`Fpd{>OnQt*w{4{)>FmN;@OeAW71 ztz)fSbQ2?J6*KO%%Ip2ML*0vCIAhVBgiuB)g+6A>c`(tiQ*$WiUg@;u^vZKSaw3E3 z(b$n#;`>_B@7p!E>!Uxotwa}hK@f>de=JwfUR4~&OgWV@J1{zs+*{b$I~B7a+h8=y zP`+E$oKr5og|~Y|9LD^aI*=4)f(-e&kUx2=Lq)JZPa|rP z@oGUj=}*auDUZ3#27;(P52kVtK_A{|*C{LsN<9ofSa`RXC$(pKB_$*{Kr*{Dn5Tg0 zNdu}50jLSgB3IXe+ys~=Vm_q&UD@BX|2xo26~S_juQY1|ZTDG5;Gef7cqG=PAGtaspZ66A19~p|;d|`O;;pnL!=cyt?g4fvFcTa@tELJX@j9W!(3T5cruOczp*&e4*F5r|Cepe^L@P@VWc4)gvHN zzGxCfpQly+7&t_bJEs9&7;5j{p7vNyOsr8BT6I&^`{OeSPU9DlcaaPhK+yBI0Yebe z0a>?zwm`O;KhTT?=a*z~cmQt}4j^4qP*M(;T2ULcMq(qXK4i3z8SHs_^sLZOPTT9$ z0j}V;oxKcoHO^#C={N|JXkJDFLyL%*xDkfNWL#*KG#k{H$iHM7dwdtFg#pU7}05W&M7-)5w$tdH&TuKxe_gz9zXt_1gUjYG zlBcq=`q2IYD}j&T$iNPZK`9JJF;(I^&UG?IG#7a>p&pmfMzMu z?+UG|#lA|6NRpw@K>;ttc&vgOOgs?LsWJ|Oo%D8kSv&rA45fCY$z$gei8FZZQK78P`8W+N+;Cra3_zyr}bOHiLxiecon%J`T zN?+Bzl?C1wqf&ZTnT;MSoQ4Pmk*C1qVNkI|h6U<{D+`tn}56ZGn_ny54Y#Sn=?vXjD zz|~6o0nx$8WUbA3=*N^Wo(gh9Qi(Gd zjLCiP>_-q1ppjCx;GGWP41D51E=&^3V-Wx>v`WWSX1<~j(sNVZoxiq)dR zinTCwp+y?#&_?hXNSO(f%%uZf6z~X2?5{<@bmMSbQG?#lZDCg~psgGnA9Gs#E|4~#0Q2}FE`H<4NF2N* z;E^=BtyfC|e7WX=Vb6jkj|T#Ogp~(Dna^#H85%MfU?)e+suc~B7DOr3E`sXCwZM-} z*KZC7k{J;wZ=o5B*rk~mLlAUDDP}&WL)9EDF~O#`^XHJ-m(?S{&u`Wn9MV39hu6b4 z-U8zy*9%^lP(^y0x{88k5?-BJf&LZPP_UC>VY=@P8(~~M@yU6DKrh8%Gc5*F4A(m$ zff9@#jNlEYC&msp|N7+%`Zf;O`x0H37*PSYSXvm8+#jbr_Px!ae;Kxd+N_%^ul0YO zRg{(eFzvtp{u*gLEE1F0wb|K5;I_&6YQU2lcYnb~lmbABL)wX<@#n8epRaTGZx!=D_(q=mh-+)`5+^edE~JSYO~)Ep%u2Jx}-?mipMoW*pgo><7M= z|B#c*fClp$t9zWC9Y4tj}*lU)acDV5T800$Ky3b`>8y zZ3es%r*mt%o^2F?Eseh##&!#CS6zA4saqrq#)mm$%)KHCD`*Wc_z zh^1l>(%WN6l7S#^zcRF$*b6R3hI_7UVKJxeQm~9oV2oHaiptjA&nbBtZzv<{cb3RW zd#y5w!=Odq?go1lk{X4%PnRzw7TRBGriJ7UvV{Bx;N9VdKk? ze_ZYfc;yU&^$~PfKd0&WMO(nklT8#l%*EXAYt+3lZME5d+{^E|n4FgCrytv)vpkQ% zJU!f+W1)O98wNRC;FXQ-U(w-@S@tC&L(t(S0$$>@4Bt54=m#Fe*_@QwvunqBPHX(3 z3;4_&ESc3ry4k@@5g&hG-f{`jay)OAEY*K(r-azftpoxMpG0FxU+jOi#0zcljtu10jn{>R zDNQt#TNB>6F`e&Z4M2^VX*n+%!u(6o^M`X94$jMm8UBI6DGm#3M_~~ZY1^t|+5T6# zn{5R>)mkyBlF^5|_sK-ZZ+U0nTsP3)cf5p~i2Z7@k#S|X!r#q5hIY3J+#h%n>m@Dy$thF2vnqK#IkNJD3EZj3r#y`Ww0PlOxivZ_ zWt-OuXra^5rTDmWqOuk1oKTOku`1dKi$^}yK5yo-`ZYzU^+r&%5B~_ zmuwf$?|BX7r9%y0c26mv0|gb%rNgAJh8IJRvtQD>VNTiwp}VMHZT$l#nGnZK)9|$P z(A#Z))>eDum^3r5_Al}Le5LnfSo-r*Ik~x&&D1{gt}d?5@?HihdHMA8O3%ecTl=#B zzv5>p^y=S@n|nqtSN;ajgV2E{9#X{dvXak-0Bj60#;Mv#;b}sWRV|F`4b09?H}J+2E_qk`TvWJD^}EYo31w|<|5dsC z>ZJt!_UZlft^N|>c>eidxZs6pf4;tZ^TL1KMx@*|9G^5AHL0Du#^ID$4t}^idiFQI>)PRJq29J%cEt>sqT@O2RedfbQ zrfys*C6W=>7V--VQgD_j%#`Audl^B%dN?iu7m@AZF3{^o@M5(Bc#wLrCdz5v1u* zAFxo0H<}(C9Th;v054XYC9W?=Gb%1WU#pTL``o3~gAOWich-*r4h8FH>8=jTHsj~Y zB{@2@oNrGQo~4<-fRwk;f*e9>8f-`VCtxu94meH*W#-^=23$U)_eo|hEJ>8D=RjS2 z=j+=Im6m<)i*E)*1OxG_UTjfRH(Fy^U3A_vszJfUtgcQ z+xb^Df(g(7S2--3kG;>xfRDt!bm@W1*6cM=tC5mb4=-bC2Ny_DK?wRGBBEFLT*PpH zuefTLb$!Za{~yu!$}i(uc@%_qxuUGZIJV>ZAu_3$*HuPb7IC>&JJSTW57C^qWf*R`C*7wMN{t_$V*-#T=wbNy??dE(%zkS=&Po?F2~fgFL%vL1l{{-! zA<061om%8t(y7~Jd?7Q;J>4{2G|j>GV3Lc=!G`&mJY+5##(jQrOi>2Qj$%~$txGqF z*;H0)SnypS(Yym`hXGjAAr%GGGg(qZh2IogCBXRM=UDY!8PRA1CR+gd@?RLyeXhDdh?@U_CU1=BCU-24NedKmw&s;UnVP9OXB_Y-zSC5~ zh&5U4LgQ9y@wyJG#6vU-D1xALLs;$iRdd^*l!_7Ys0iS4xp%+M_NQtfiBcOhGgGx` z&j#e+{_$kE$zph?xGlA=7|gY-+ZK@+f*plgne|;J(Nn)MO`iE7M>;P-x0Bm=>GDX0 z_5}*D$dHH}BpN|Qq+V)?s0H-kuX3)|a8EuhiBd}B#j7N&ZET|Fvd(`DL_C-K-H#iw zo-*~^>hz4FaXXs?s39G6Mf{1te3+^_TFP?Y>(HbSEMsdbWjmDVis@~m;D0AiDY#jO zORXj#UjmuPSIDP!mIp&wUshN{B}q}4_GpXerYZkd_#6-_cgBls@vdLnq~#E5>j|U% z@N1%?c|}#nu!&%z&~#2(!)TlD`97!DnL988+KZxa3p^Ug0_Q$ft!}KnlwCQ$c7173 z5B$4_X6ug8{pt@3G{=uMFOO=?{5Kas?-#t7b|^lj0)nUII<-yP|43pf9I8$2l-ars&zG%|qk+_n^K!8p9f7COf|i$5 z*>E)lBK0E{{m9+fEjVV9mLD~A)KgpR9oOw7y3=nYgbFtAq~ZhbUQo#JO+1l&Mxba{r~ ziS^^HRl(l$rd}@7{`YS?SR+H|L+`MQB+h)mFK}9>-!nh;{bFf}fDT|$G+|0T0ohU= zux^k!MAsUb`XtE{P~RB9E7dIw^QlrueE4Q}1qHpdT3ybScHGWfM2@qv#&c(UAcEZxM(%0>Ux}c=am4 zE+$i-=Gn=DAxj1_dpo!rfx}GWH<>T-qF#w0u{0gbwZPm{0XLao*rmFDyndolWQ;he zq|q3Z!%)n3CUt^$LM8xXK)x98Fb+?JELOj8WNb$vLEsnQASEVq^*R4iX;oYK~fZ^2KIFkuU%nC^gW(Az=V)rCkfW)jnP24x{J; z`wqkh7D62|hTy~wvV}4Dt*vU%hJXyW4b;a90HX1G92L3R2fR>(?`l9`Gyt&$8<2@fx+-+CIj>0Dgu{FThri5;9HC zfA$KPUo0`-DG!H=f{7FST0^Ks<$=16)F6Pu(latP!*JN9jU-@`aTzCfV1+Y5MUema z^ZggPpkr|d*vS3ta6#zoUv%P*n#lxy0iGm*!UYfs9IY> z0yY$e>2PxkaC6NDum%t<1^^$w!RHkc7f@OnW7N?6@(I|qqR5Mu5r(G%7ys=x&Q8Sm z3y^52#D2gan@2q}x&S198A+mMTjZwlVbT@C3okJru7?}j+}L;pqXUaD+@eqY;TAby z-7d&E8~lzdx8-8FQXnxykYMokg6C@Hfcrm0?qBE&z`h8chM?jw5qh)LIYEAI4!0FL zTbqClD>)AyAP;!}y+pS)ewe({F&qZQ!v%E6+hS+$hPTCG0kRzEb^y?EsDg z_fZ%h6qNy}BBW^gV1bQ5u%L>}T6+@V-ue*^XEDY6gAVsVf)StrfHu5_1PjSq$jQ0W zMgM>}_z}$0_Sk)BCm?jo;rM%A$chB0yRM@n1R~6m2v_qyM2XppKnHG%;|+D1@Q4Cn z1c^xBRr4${YAWrGN#Qj*P8`CDFLzue1Cba1CuXawmoFo3>??nZ13}rg4RTbB z2p6G!XslF?^)aFH2YgC!dI^DfGrCWx4E8Rw`{xbALqiV$Mm-k!GXl2&0;Ei-N`lmG z;DouqPnuX`R;=~(0O1F`z*$k`#%Ms+hHP}mt z+yjC=+~EgcdCN!^e}gPyV!K-mgryK?RK+!LsjFf)PXn~d9Zbh{AdTo1T@QpoF{ zf`ra^w2U1BKD10Pn(Uvv$kR@M!23Ihv;k(EJ$0-fDGdB&iPSJ?Y)r4NS{%Ku?gc<- zYxltwcp2c5oVNUGYH3LtHcUrHvXCu#xD>ig2nR1bJiN!l5V*;(1Tb)FiEoeptVjj; zLAe9)1dP>#L~$~3U8Vufp*?;^0Tm)P1daO+Ope407jeExRenHbAdtbwUVOa(z+?e5 z3-P|*Xs!lKI=nLh)G7o7p>AhC#zD-i0D$x6-rn!#W=7mz^$#HJhIAn*SKI>jjv~vk zzi%_MJ6Ag{5*(=m%1;CsZCi>mx;ze`;;no@P7^_hfZGuT=LJEE1*h2P;(sj=ftCca zJEt=UAfOZ^S?YYB9bhBk0z*FMNq1=0{3nEzQAp8+A461bZUWq{2^R5FY3YjfpSd4- z42NI>96(xNdoRI(6cjHwK1kTYA)pGXd-cPfHVaUrMxuY2kR zw6K1G=r2o9cK)H;_$wsi2+~KYEmB%q$^gO_pSp)>(w~u51VXg`MWWL-`~zM75jLQw z3sB~oPt=U^Z8e&EdlopVYJc9k6Q6)Rtmnd+S6~gpZGD!M@BxY#xnvA(@SBBWDs;UtC zAczhe#RU+T{lUylc#Fq20_22{L}8D+Z_CL@=3lKn*}`M1-U-i?VuExOX7D`hGc)iv z6`=N*CDP^j`N)^g#BEt1&XKS20WEq+09kW$Z(kdpP`$^fH4c31i`Tn9 z$1ZfJOdWsqR)IZN7;^;V`ZcSy1yfnr@3iK1Q@!QJ zq?y;VF#5v~v-}%v+W8^xbCQx}26I*Wr*x|gA-*iu_;96m1ZXJM! z@NdlYP{$t-%?Gn}2nzZTk6?&;VY^x@1z9 zqOB%pR^`Ib;>>XQ$YM{1-%U-Z3Xlz~B}(SSSO1d+;8>N4p2zzAxd(ClHHHs8EDcbv z$`S-**l{B8J#=3EA&+38lx$ow*DQ_o<(&8p83;5JGzCJWN)!WQ{XWNnLVyQ?Df!`d zz*yDNIy{mxq;mvL*1fDW1p=f>AcfWS1+|KLi8+5~)j`1Tr$o)(01$en{@z^9)8unn zi%*;J)5nA9rD6<|dbG+R0>3(`(4LquYha~0r0f>UZ`h8QDaC0uk<9Bc7;R=)rlv1F zyt9QWDRJa-t)i?w-HSs2-hV%2v1_WK&)!NOa~oa$IOlyZpX<);C2|IrFeD8mc4v?7 zxyht1@LnocG5xs#2u%C~0t)PbAn2){ zZ+#;y8$O$sK)}(O8sKCsWJs-9RO;*f*Q{D&Z&Hlaen9vl6^gC79>N|wm+_2hES~RKyg{?i~79qo@s@^i#3)PAviM|8^-uzT_MU{mC^oKk8hy8qY7&Q`cBKd^!@ zWb;DQ1>mLF4s=EVGqO$FO2xBt-je}+m;h~;S@uF5$_~bLn)Lm3eCmf5q4ZU|-3zi5 zT_5m_6nYEvcr~2XI!e#?FQkyX9h$TQPhR~2){`KBj|hoKFFeFty_vb%{f^{!P=?mr zcU0SL{&$=;l9>}l{1z3Wzbbwu*{$_mK`n7xbd(KKE0sLIi3*PhSKB=l^3=|M*ZA%d z3Spz18nQTh_L%{I%ieJMfT8nr@8)DDi)i6a>VBCg9We@^OP4RlO4s5}SW8OI&$dgw znpA3By~qZiJOR;Gs>^W)m+|;gO0CZ^rIDRojN8iZT};bo84{C`8B)^g`%eCEEmKIz#sJgPNMRT-Dhj?e`EEIXBWo+p@y zA}y4WiWl#2bYAThtY6@pvx|1pUNO3#Zf>y(M|;uJ-wzirOt+nT{-A-O8t)^^L}B?c zEBd@AO(9HZ{@in@#gDO2cAdmxtbKHy=W86p##d^{o}hK`6Hl+{q6rHn>bj;#hi%>y z^9_D8)7RyMj=^`%BQOItG%IA3N6rPdNd!tY2k*lTTH^*Np#uRFO~B1BZit0uAe?2U zGa=dYd@IyHuI!i3Q^!fyKJdG6{2lzWv7CM6PK5$oezKRkv;YXl0(%(TpLg>nHpllF zet#maIi0--yAkwRCX4q)rUT__d%af3M+4k;j>pd2;@jJIA2hKt?{MS@IOvW4m21}Qk&ar@f)G)GO9||iGm*;F{V2^{3zw@#DRv*rL;iI+TDO_ z1PY~9IXRRx8C4$uxn~iuuRD(p&BSJAroFUKp9@WOA_C`iD8b(zIv2Uei#(C+I`w^o zBDQCB@AP=Us~~agda{0TG)S&8s>Nv z^P*@>akp_JT*Uw!P@S67_d|>KfG*v+guTtE_$S8l6lbeVCJ(sY&YcdTvWbGeT6|Wn z!dCTJZE**&nA6q#lENry-O) zwM=EaO1`Tc1TXoz#~mjd?e%WC4I)11W%>U_6qc{FcV%LYq>f_M1uUI;%;CWp!FlRz zxl)hl{;NlS4R|ywE6+2TUe=&Xb#Y z9!{T21%={HTID4mJm3pCWLw(-d6~~EseHA15{V86*GI143*mX6vr|J?t)=fQwqaC@ zz-|Qyov%+-@ud7}#m()BMZGE;chr5FG@hlHI^0*zy72X83ebcvl#!A*46N5)>{+ui zAI{fI-)kPto?ol<`z4mo#78g-4N{(*zUuJetBdU3ztkc#E! zx+*Z}73$?HE2|tHD-;xz>g-;uW;F-z0J&5$(-@-*2+3R@7k5`R&HnZm`yv`WCR?oPFR|sKE0HZp>lYkBmr`c+JA57IZqC zN3tmpGCk0wsrZu0%Q@UY=e(NP|D)SufqiHQnVOJoneVx_zqVAd+kYvBq@mHj#@dX$ zDui{HKl=Si`1dDMo4LX)lv9boV4hPMm5YD5cKQjIuz3vm$64L&0mIyg4}P<(OS_0m zwQd^nV?RGNMo9DFkBYQ!Ec{=;&rps1fAY_>Zt%tr=SHmf+)jorE{tt9>{FZ{Kg9of zgAfW9bUj9I+qSr3E>PBUPrPf}RH=ngcB8@(3s>n)f>nsIZeG z-BOP`jJU{LUbi`GIAOx(NdJd=@+vYg3wKz^OViESbIhFCAs;r|vVcEfN^3@C@w3r0Gfp(PLKS@mbDl94}V1lV1Vm`!g3bz2i;(mX2d!SXdT&jw~#_*>r@q8yF zC{2~TJG1pJ+Gi6xE24a8-CdY~Vq4ijT4-ACbflr@c&qL6zQ=9xvTo`ZXR8%!Bu9D( zY!P%r1E1Z=y>0p$30NN(?nSyFMn*&m0UIIK9ezgtRq_FACZ69#XRrsc7q|)HgA8Y8 zz`|s3n-oj_fWxj&=HRS&f@>oEbE|Wq4jX(PrrYas@Q6?|^sm||dEY|cC?1al1f^7N zxVWGxP|1q*i~c4qC${x9Lh{V8J3G^UWt&3J$o*AC-%yE>m9|F(QJgAEs(K~9d*GHgXp>&eY|#M=$KZdFaI)h? zl+n7sU`0qJ?#u<1<`0fb`1k~-3(pk*GGkONzhC0$=yd8*rOB4KxXJaQ z$`MQGN7~B7)FUA(&kS)|h39}u%<@0ut-l~8EnBr*m{fa8<_)T(U+}q&6BSP{7)4di zAKPH2iw<3d1Y+Ze)+;{EHkPpi@psk z5^<<}`z%JU{a;j}t%lVfezWQT5{C&jy^=c4Se>U7=DNU|Kroy4QqZ{flo67MH|L@( z>QILEv^$+j-vbC>HlIQ8S!#`-(P;=A1Yuesy1HjgS&COb0wfIQG&6F;5Kn*-k>Uci z2_O_OaaJ;!j}JVw0?NW;V_Sx2Br}JTU*B4-_WL)JrtK{E$h3iKAbyKq7Tz>c1QZ9-AR+2rxV$@=Ho!_@{fMB2{p>n+7yR;xq3y##NJQt`|{JO`Io zy^$ksBo8#Z8^u?)%}bBJ_(b~ojl5dX^(2Zh)Pd(z2CT;fv*=7=2h9miH`0 zo%-ifr~p$<-q8^~SY42nLYViniA zaQoPvLHcvnLjctdU49KhwsNrxiUk*s4tK4<&nrL4EB~7dfO<8sm~ucV@N?fQiK{q$ z6}W8*XS|6ZFhW5|YCN(`dKBv71At0wNjVVlB0YDgpxi^jrzB5&8$=YJy+REFMWF6^ z)uQsfUUg(ozn8GNvg7EGqsGb!nmn0csE;60$X2BSM?Qq`ik<|4I10ML0#APW&wv(H zF-?UM^t2wG4o$$0IP7I$oP8c$@cH{IBdesfZ7B8Y^WG^A_ z4bT4V4-z&%Uy{9}3&N?ZcH2ptm9tajx0oJ9@T|7QmcmW+Sq)6*VjdaE6WIs{QJsDm z-BpJjAA+IlWJSVNVuT|^>S<|YgroaEg2dB_VENB6O1n!>JWiFk#epCrXq9hIXMRKUEU^!AX94l1#quBw z$sMH{Es5^%G=Lj9toIJsg@njr*Qkk`Wgv;v^+h_)0amqi^c>T<$7!@id?c*(pmYVI z9s#g-fEp1|44Ih7wrhrM%{+?S%Y7Hw{qp_w0>ghM5-uP>Y` zup4z8Bj05KQCKY#tYkT$`QWL2#$ETu2!T7li@nz|nmhOS1K!!vK#PE%(T*7Z)FfmA zW5zZjg`zoKse9CUr{a`&Vp47!r=OyZY?!uiM8?O-BA;w=LB+4e4$+dw^5lwOP}TWT zD?~JPfp9=)x+ULP`kQwVgz85RsISfvYMFg zV=U@g?np?gbsOIA_T|1D0YJDQ0ci&(d!8Px3~w_K-i?axT4!X%O(?Xd4Wqb8O7&at zi4cFuxFS8*MK$ zgjP%vXjdYLbOPxXOC($ovyFisj>z!}C-~5a*VX%Cj<;_A-CdG{2oF@0a06KP!=~W5MbODKd|T~CCjO*W<4x$G zM$4>)N#D;0Jw-Kf8$>pK5N2N4EOkzIvYLluqQTIu)Lx7*5NTSsLXUQ%S3z_WX!6kI zipG2N2(lZbdwy@OB`PCKk_L{Nehh6XM$M9JWc&~!gwQg-cXOELz<+1eFm%7lMRFezqnZb^RS@)knIJu8lj>%FXxmp6GFDehveS-aMG#-wAFiV znv(LnA3T80Q1csQbod0)ANV7T2q3_tP0!5CZQ}5|a^@GboLE6HB)VbO;E=YD$1m+c z(14su%O5QzwnhFD$l1*g!O#xG3wIy~Yj6Q(KTu-~@zZaForoc{aZ+m=n-tKT0BSX{ z8&mh{=h)aw@mN27rho5-9nK$xZGAl!qEkj|B9vuFVmT`U$EYJ@bYQb%(m`(yBzH#D z+<4z`e<{!8TW~kWa2v_c>4k(RIvtwbT@?;yH%;Kzkvc=@Asqh%iXX0O-rLu%Q3(yb z^%tY9?MhQ148wgu7K|`PmIw1L!oef0FunfmqDA;iX+tRw7l66z>!02i;Dh1cp+oZ$ z8YNO8F#XWF@1gzu29`sR1P$~lf7nwHHZvf%+juQAGZXlCF~BNCJU9_{D@F<(PB5e{ z;O!@6`Rc)K#F7u{xBER}v`vtl(vp(~OF%Cks4AC1(8j>QaU-=G7#dq2;SetWkdSmx zf;s1>WyQvIWmm5qj{HE4^@|4Mf4nx`HIWugnd zPq2VW;4BUp?{@SM7!m@OJ6t$1181d_+S3v~^o{*PBO@ng0ImGCSnj~YQyu-T1Ak(B zN1zuGHiI8`2qwXX!%vwjt>Wg_Y>o{MW`3TsuCR31>ElA z(JLgIMX`d`iDgXnga7SE;&QVW5Iu9Ioe{7;>IKjDujJ@>wl7Mdx4a>RYFoHCo}xWg zaZ+G--DP{8TOge8!Av@^dw&P3=j-yz zR|Gw}_C;8M)(6CRzu@pp=k*!37d>LO(Hx&oZnImkl6#H(0lI2%2o1ihWSCVnvSOcF&fM^}Fb-*YyC7U;G1i6BL5R zmx8Y~S6&E{{G&4(^=KyJHl?^&LG$R9ql|$|n>-0kMA9LnwqF$4GlhH2p((@l&RjZ{eFajZIDD zl;RYA$Ug&7WrKOv!2TK`ySGtFJ)#hVRhF9Sck$UXqWnjX-Gy>pL&E)@*gEXPp=j-K zI1Gf6)4D(k%GdF-(2_L%BW}CU+C;=w#4CW0BEeFydu6ob@c6o>u=tejI1WQa;Rm`5 zzad_Dx#sq-7Vt~5wecSgp80GtYc)=a?eDcJ*j+iW6C6)-w_r6@FV1PYxX0nJqll9J zoM&8$z4*GB5xpmuAtGMzY$GPZT&v<^undVUr@-GuKJ}Kck{4pMaAbl1P^Ot`-pNAs zH@{`EcOTTNgZwrWb-#RBJ94qLy>a)^!de-t*--4lgryc91J~@d$3oy`0y+^^o`&V} zh<69hthl&g)=7c~x_V#u9S-hHsj^Nu#gxnzIgVI3WO&J#4SWvsnEG98{X9Hkoj;Ui z#!1s~tyG=613fH`+*xyz$HeSVO?4!O(8{qf&+(cjLS3G>x0q<0*<+J5%k6ZAv`O4jI46r#7E1E?-k2cC|li)La7bvCD-lN8rdzq zdbMk?67AisP@Rsl=k3J*j#iFM-s$hZ4(gR`J8qjMtU8y!{Nn6X$~3f4Ip3f(3ZbeW zqag`Bto;)QCzuM^&Xu@;khiwI&E^(+_?}9bx$W*WHoE=^OK+5q&poX+Tq)tDe~3t% zA#FL(8zY;x{H=Djc*?gaxm>Td z>}I<>iU*9O%B^;XT`*vZ)bT%KatvSd9M)T6H&-HGMGEReYUQ4_J@IvE?=)9 zZ)GH(rQsY$a(_)%YH$zp`?oodo$;VfkaHG|4x4&lsFewFH(i#L%mzP)hka*fX}?&C zJ+NrAh5?#yucJYkx0tM!XfA6X2Ny3FIO>##o*QGmnr{leAX++P-o{ zq_b&)<F%`#yf6U^D8kBaxqh}|2uaucUEGyb>%G+8A3@D?dB|yS^#^_DSP3^;DCjWa_eDT%uG6z> zwvOhwIZyw>M?s^@-$Sq@EIhugD)0Aeo;Vu^yP4{Iod*WH^zboBL(xGx>pW(o#?Z)u zgTP*Q9Wb_y?~ruXb`wdlT&rc#{3I}8lq%&H+SERl@LI{R@MMzI(DJ~i!1KigHDN1! zh%zIedjDE<`gyJM4&*Y?96yFIL?$}?elt)7a%n7gcbSXUlX;>;)dmrAGf^srtEKAv z9cYOzLWn}T95GVI?Mm|U@=hoBzq8xjQcTg1yR)m@DM%xV{j>D(Qi6{v>!q~Hd3v;v z&{!v!6L+}{scDUk8V@Zo%2Nzk@nhX`#oTb&cAEwQ8O0B)<6F066CbWh$hqXZvHh(X zAoU(~*`BZ=&u+IXwGb{^X0w#Y(01#qanb=pk_!(@FHq+j^goAZKk%4)e$~~5@P<9U z_iu%gwB6Rc3yKxG2CRwK^_SV0)|Xn-;vUc-(R-KIbZ?`&R)qPWSq(qCs~c=supcRFC|2A|+SFS0SRztv=CwpV2VBH1#xSKGXW&(3lC zUU62AhAP~y#W;x&o*Vn*ww3j^lt;Q+6oH8DD6W-eNq2@lX;xF7Qtg|_Il}?ZH-k$pHAIuPLi2S5LhM$d^^3s77k>@ zirVxUFO+4NsJM6fyh|OHNJ*8>Y4>|fJ%=kGS!;cMOL zkXvR!`dg8fq$U|4)>0aHA3tE}CUN3coA3CNbeNd0x0vmrj_j6X0fG0~u%c9$K4F5B= zOR`%BJnmYZVPPU?R;ID&iK5%f%F6z7d72muc*cF6Vb;BLlbcXbmm}?b##4WSRff@Z zchxgeNxYUtCw|JIQB2~7Bi%azIn9lotOO)7{_LPM*DinOEm}$hLFGLio=e(7<@w<- zZoASKbC=fT@rEWfd70CZsWCy8Jc8A;z{pkYAF{Y7xrcpRL>lHDHr@577%3iHuTm|>~p|Bvp@;4i#0Ge!i#F zVXJT48$hRpk6&-jvpur)D?8>~c==Xm6Kq^*EiaNmefQ9GSO9Va6mM#(TIB}ron6-k z35BA3#*E^TMAGb+suFv?$-NARF2D7sXLS8Ts_I%?I9ZbVos072W{mL63Jn+d0`+kz zfmSOaF7v6NimInr_&SwW&z5<7SjuCtk)=dJ zJ;4FHl^!ghmGKIHENndW#FqxPwqIGt5)OK)^xPg=*+sFHKYC~Fe{1` z^|7J3G0}hXRStPSMj+d@y`8v*rm*MD#_NvYuryx z(XB!wgE=LPK9!QJ60)Zq;WK?H7gvV&1P34IJ@3l8d@6F*vo-89TRu|axWwz1dwTvs za!rV~@t69U_ffy=yDNc%Y9Wf%8xr6INp4&|zyyH_%)jKFrfPFeDt9J>-#mfiW$Mg% zG^I7tVvA#bI0w(64_#TPjCNKIiFc$=J$rt%-UZR6uC49#=R}W#$-+G5)*`z9%m8&{ z2E<+`+pZ_CwzhT5|5PNYDN?s#wV*biZ*Y=rATzK+VHB2a$Ps5DWQCso2_ea+HdeZj zXO+sBKcBrDeBge!xun^Me}WNVOIxCYo<)Uv=aOAUr+LK@N~}?nFzFtLc1D59e*&} zfcek1bvHOiZB@?%GrSr(7_R?&%2U{M*f-_lzG15grU*|!=}Bd*jvjc9R1bzXxtoP1 zLdbOc-9tt9Z+I3j8`{L;8s&{_uYZ~MQ|@cLTp=MR214PSleNyb>dt-lIp>O20VqfX z`woKQbOli}`**Sic_R_EukPyVS%&jYB-9o^7AtrUrHkL_;DLC_SP>c>^TY{pFYp^; zgm?&l1k(`r543KEGGeJAw(<$N+RrZm?g{=iUkSoJ{Be|qMiv;r$Y=iBKK+0GmpP2v zPu7RZ>{Ghg#b|Mr;LDr656aXG-8sUaR63DCQD_TAFQ7ks9tU?Ao12w$)teUYp~HR3 zZv$x7INmrG1bPPyxZlOYLRKvM##>Czn>N_RUgqMw(f;EN#qc&S3fXwlwvAkty=Y>1 z3~&2i*(_S`kAfU`Hg95p-E1bQtgM2pN*ulQ19|$VG$O2#ug7#2Y&Tq>CTJQdy@n*N zr=6lxOY`#UQ+myDwW!Ta8`#C46+R-mKbY+^Y;L&Sl7>`{1Ueoe(Tn$>p2-S_*CPhQ zM;i?Fn2pdYwp63`g|KuM)W-T~!nlRz4flCs)EMq{z2m?ApYon3z|MmD2Yv=&laK00 zRN@nf&FFKP+2*LuAFKa_#RFUbA4P<_cflGytC*boSMxBbw^d{wZXu~R6mt*}dKUdN zm_?no6j3OJ()B;E*%(+E6E^4DbH4GDA`4D5_i1kS2kB8)vA8h(wY4TlbD)5MkwRHd z|MW!N?{{t7vST2?dBqX4IOODgw_Kdo=-&}Z-Hfn^E}kOL#teUOPsARsvSpkRagGx? zQ}~CR==#@44(}FjGfmI6#y6**m{s3k`CcUthn_P%^5!(5DuJuy^22NFSI-G2a);Z3 zc1JS7Gm)ed+jgt#0!p4@R6(Tk?ZH9hg*$ge5+Un;o++{F#{5#O(Gi)3r9mtGzA#|v zZh6j_0ubC^gk!0G$wWc?v0G9^sAC0fM9wFdf$XaMJrkyNj0}v`Y~*9{>T<~99Re5Z zW;FPq##P%ql?7Vpn*lb5WIu%~H;8Wd`3Im+S`79qC$LWJ?iYnqc`~76WRaCJ+Eb&d z6S6weFiYqYBc%HPm1}8v;DmX!|EK|FZReB$0S5tHFnS+2^-dk-sfIpe#gQ8@sj_p_ z_{}0X3A*%C=4?}+j@QDdzuvp|TJ?oLNYa>8=)LM5W9H*(JO!ts*wn|*H(HzBjm2IO zHgCd0H2=utWO-v|COG7-`X3rqODKAOH+^B?fy`-`04A|ROQT1$|SBm{hdpU*Gi z1J=g=KCeIk-)(roz3k+c@11#%-Esu+!OMBML*rI*+EJ>M{_U&(U3fB% zWDroa#R*0k6lN4Sc85wd5r#2aJMYUI22s|1dzK2iuC#8O#z&Z1Tr_E4jfWdzT~?M) zb5Ct3@V&w!S}!bq`rwU6kVh$QUXhnC7}1bYN>W6JeDfFb1_u`H zIWaj=`$;pA+Tp3J$id`Ak@FKfXe$MW(BaF&Jk$>dA!&D^5nHaL*v69J`nGunueFT> zw?L%d=m&l}-}bCX4y$^jue9`A{i;NZe~Yua^TkkrB*{8#&&vUgzG*?n{Za0yi!tW> z#&s$yIbcCpZ*M#hNc0;G2Tjbdp5#!q0}v8UWR3sOftngIT?4D9kKLPM%rJ@tKf#y= zG2s{ONa~E~&9Jd`O#fC&a43Yg`-U1HLon4f;wTg8l3@`4bKOCa`- z7obl1`5-w0JYNzt`>?HiUBnxs)&K#(UGSoUFM~3k${S2sLDqIy%)|QNFSkF5%>i-u8$|IRf7PL@YXF5T51E};W5JTwPeVn=PzPsLA>#q05V=WfTB%b8t{LcRE z{eAoUw)|m!P1v?zxMe7?g5T!mBA=SfG&2|XNA^3F>Sv{=BMq`uzSJp7Me!3?E#WXE z{#G!-^Drip2L^gi27ovVd?B8ICe=w-!j?w`BcbKbJzu9OQf*S~YynY~0_#Oc?f}@0jbXF9bR6c4C81pfh2JY=mcfuY! ziq`~y7QOHS1jOE|Thma+gObM-4ylKv0+e&$Hw3;}>+Kt$g{cDd`EFrO4(*pFslf0U zfv6a{<^#5INNB1FI0RC6CSW+QSwavO=`htS&f|V7n3aL@DHqs{STYAfsg%QH3DDO8 zylOr?_|}mJM4AfHF*rp=xZ^o02tbl{fmDgaQDU5-i@ZmuNjOgE0z6mvaXugC=INOM zdZ>7PkT z1R+f+76JoK3;eWT4%#(%Bd4L#M{-$)BTHBEkCr9qi?l>b=#fPX5NQ^K)b-NcPxRAa ztAdl2hWShw!lAm12%a4CYLU0Rr_-Rp&xc#`uC_D3%#$5mT!hf_`suc0)wU0{J7Z15 zHa^^o*WZV1gW%rwzJc%oxW zidl_vfQZf)?$-?Rg02HnptG%lB&5mDr|B~ERp zCW5KQhK73tC#DIek_SPo2=3vLz??Y~0x@(h<>9mQ z`+2yy&@YZ^Ej7gq!n)-KRsoFJR$6M<6@MA+28&=rjLw|ob@Z4$FaD8fUgB6PZH=ZK< zWvs#eIm!os+Zd(>f~AqoFX=q`3~&SJT`cPEQIH}RxlU_3W3&(RYoncEy>KK7*Av|# z-MF{9>fgo#yJSusGi$BeS`8`kCpFRYiZ)WtjRjaUccMBKFk?t6T<2$E^sTc&enJ7Vr$eOgUUVWVbV*R#A2}v0HSYib$qFHt zS9SOqFB|WO>(*e9MRSr=Bd3?rE>!!+%cops4G9FympP}w{G#-FGb=;?t1w`!KUg)8plt9=&h^h3x+Q+Y_0WwIYckoR5!R~y z4*(o69__@OOq5IhBA0dCwB(=^%_q@b+3Q+Y!JWCjMFf2~ZuXLk34|f92%pP*RWY=M zmgZ0f)BN5LyIWfps)3w$hAF zL(1mc&jAyDoV3~AuDXLU9OhGH44HSlMD^YAUZv+hxAtA;Jp9yeX8vhK1T>P#h)=d% zoyZcBpvEU9Nsm@(vUSC;yWT61dQINwo*wpHalMO8O$QnS+GyXN{JyfB`#Fc$S^chy z;93*VI#kI_p!lNl+D=c=coZvL6z)IdI)*Hm&5al^^$I8TbbfEhQgZ)syR!XNx8wVA z!8l7bvr=BFm%@p-6K&v9ackS+uRYJrwz%;T_&Kn`N80@;_2ZmDFoN7NAb?`ojR@n( z4JR(vr6mOFacXgj6;!QjWedL0lYau0JNfCW<`)N`k`n0Mimi5XP-avqtnAIq8?K)X zT({C4xCKa5Yw9V1kmZYx(yslM6V5|*2c*1Ef6!wMfLHl6b4w;Gy})^i5ru%??qv&C z7E0BshO;zKQ1K{o6tvIKGKEE{b@fuPfEATgs0&So!1GA7d(C;Zn?wPVM}9*-96+N= zopL0%Ma=_*xGzyQF+Xlwx4V*%iFY;S@-K5~Ui7ivTdkYRGe{l9Zy_&u*`UXG8(0*e zqt^VUE&bRIUrPuc7GhkJ}ap*`>Q=zfr4xP(Y^^p3gjh!eJe0Z@rnN)R&^I&n@ZywBuA)a12F{9s8LFW zmn*%}?51O%cqs$ibVF*Bie8@~$m=V(0P5GbPTE8IG0(Y6I_MAEMs~k;*<`TUP&Vw) zpi&6`kwb9zF7vB)AZ8gq`jIgXAjGyiFSWFUP<#ABcER}DxH7^xZQdjq)m(%l`9;D?A_k@atgJk3zjLHF$6i2Q-Xi8oEw4lOL{-0w>`$rKw) zAiR=nwl^F}gN=I!-m&vxj8ZwBEGzC66A6qt-Z_$qa*u4eTW_>YB%9?c%zN#q_yb#N zHZGH)mgJf0D=RDThW$cj-l5!*21B%L9I}?4{u5o-oS~VLH3ZHAuKL@n-gMn$zu0!h z?e$Q56)!!x>bm7h(~9NkSl>VCwk1yVj~dkN3bmniy=Vy+-y#3igDsZ=3#8%*;gP~~ zx-6d0O215BQUR4{^POaszA86XhG1t=0D#!^xGk%P<+Rx?E(;F5i!f`iy_%pdP(a6O zL>(!Mu(Tr(tIbu+@_z&FwB54=f*66fhbBO9$_y-J{NUPh090Naxp0ZYp7L!^6tCsNsM3Mj);wz zaLF~V&K$?LrDg^gXj=23-j!}*h>dTj`pUT>Y8LtVV+EC(-pyMsIlnVInW**%_F~`7 zia4L0(WO}5H({3x{i-v>*DWO5MhZMQNVSP=OCi+(DmPiIf-M|8B^__9_)wbXC-W>+ z%#KERdY}!^3(ei19<5Zb%Tmct&|21$uX_xdCM`OaADclg0kBpC@Vjce+3ZcfzX9y+ zYiQiyxAFON06XY}hofNK`$G3V+nKPk4Vg)5A#@rnt;i9cCA&s|5*|6y}RyP z6Ay_^PCJke39Iv*J`c8B&54CjWPHj(lAX=iBI9dvA=N6aQVTamH z(9ju?NwqrtUnfYa)ngw%s0hFVPpe&VM*7897Ks3Of0_8oaHg7@Am_Idu*Was@^L{g zrF*bj>tnH1UB&Ez0V^>#5~Qj+Bd}Y$E#J&O#8;gK=$kzB{I*qAQ0UaaPO#5d+c{0^ zq>Sj*e?sVO7Igqr3uOjr!wrm5^qfK=7zHHcfY@u+5^75V?B@Lv-#QSp6EutqF%lHY zkQnmy)YA6DZ@bu8s}!o2w!@oiq}HYK*AN?G=kbriMNW-H8C*Md)Z!d_)o;yTWk>In zl{GXB3$OkVgYFGvo(9(m%Q-wX!Nj;c;0W%^dosM;Lznvxy{kFC+Ieun-hUz8hxK8G z(kpk-CD+1T$?96rrv>Z8DEoXT^FWF?&)ssjCB~(~Larq-YO5b)IDV6`wOVbt3RpcYteH=7H-pGEi6)E8ru3l|LD}^KyO1S_~R^ z>A}vi&CRS3*8%3M_+C{(ok;WBdO%NRHzqu|n0J|qPYqPLiMvShfz^AmT|wNetM^s@ znaWC*q=^BYwPTQU2igVNk`)^t9MfRaJv65U4n5EBhU& z+A@Z7d49XXa80#P;hx8L{Ft_9v!cHf(#UXot<1N%VThmW*P4O>rQ*|i(8ugUOQtX7 z(r{P$(PLjTp_E$=fu*9l)|HF=qM#enqNYs;J$82L4fOr#X966Ypar<(|0kOMf5I-F z1ou~6MZ#T~z6&%BF$Hg3`Qs>ut0{tAI>2V%a*r@|{G0|tjmk^d|Ap6I zF++24Fu#P;j%sWU-C)QzEtW@#JPijbNgH;0^XkZdq4;(>7!FrHNet+Z3Pk~4M(uE& zhJ{eoMYTzcbzNUP3)xSObul(`@&E_%??awrKyDF^R8EvWzgjd%@P`j?{DOkS)?D6R z*~(06awcOhm*kAWe51kiX^(x*0s;YYPQPofY_h$V+B@Mky=gJLK5{-yUK=go_7jYa z|73^d-}BsW3j>!%r07bsq3Xag>+iJaiGqcEccoR;PlM~D^HvYi{JjgeCsS{Gx@XZB z3laKnbyX2M8?yVKhvQ#`Hz6%w92Mi=`sM1;S{7LU6etkjMFw?KeCS;p}wmVssD+D6rp6;8dj;UsO$f3=peHm@6K41IzB?#dJ zAqhzg)1HytPpNeLVODW~!}r&pUrCZ`c`E0PmM5aY+g4B3r%krL2PFW}lUg028M?Tttd?eW>iB~#SSWmqXN z!M<=rUgX);V}a8HJ3=)!#R-RI%S@m@N-|~d*w_oEGc!gN5bBqfB(8nV#GT)dTyn-F zC9il;UFgZEcpgU;_v?>a=8%Q_Jzsba#!d%i{u}OJNI7kpgLyrNmpZD1&{ixCh8CIJ z#ZSYKZ&HZHV)f$XFtUE3BdEevA_*FD@>YXH9C^0S%*H8SsL$F3bTM2!j-YW_$ZTb< ztDuGLJ8b~DO23>sF*AX&sC`wI$#k3Eq&2M@1NQ#8QpA<{E6cbK`T{ZsQNRKN1^RsB z`4tr8^Co#{F$9_UQCOEy7_^am3EFgz0sP9>2Hw=sV^9tERkPQ|*N55a7wNMn%Bl=a zB9$DicSH%W_!mdw7EjJ;O+Sojwk^0&xt5tff?XZg+wryS(!*jK*Lp^Q$5?=+;#Q70 zQXTls!6TCI!~aSQ%zX8bJF&;m(C}yd4e3Z!U5tcPtmBmz_2^f5;alZCl8@cSTMqG` zt6zZlHg=n!y3Ar%+`k+h$dP6PclhDne4{LU4fDmZpr3CDc0nf97&fo1noht8P@C1h zOerb9ICMO}^EZ@$W13oBpLMb+z6-ZH03xAsiJvx0J&y1zmk=AY;&dV?1h_a%$aGXO z9UZs?nVm-UA8u{kI0hOAANr6tumj5%0KaoZffD3OU%`t_C`@7S4L~M}*>KFD_(d_7 znl+KrH$VSGQ9fLkPyB=l`}Ij4kD#Do_s#A_G1~-!2$uL4*4{ejdabX@i35D2W9Ai##97Yhd$3y}DNbo3<3GVVK-74yu zQvCKieqpE~{R-0|#oLJE>}dqb(JG_h6yw2%&V6}Ir;&X@+hdFk8k!oW&PRpLQ%X{n zm^PMMi`y5$q=*-=>=(e&ehWeR{-M1e;H=}&m_-g;WKlbDRyGD`!~P_PR{k9^muea} zn9Mac>xjCdoiY+x0henB1Jz#VA%vZF*>Vx~d{!c@95sf@+Any0f>@{IDjk9}V1q;d z2y_gyOF)9o?7cE)u{&V(2q{itv@`bgpHM=9thpgQP4H^kDD%n^Y4dx3rQI*kbAO{N z-6i{CZY;0`0yiNVFe1ZC4l22E1#cGew|~5!(_bJqzWM6w^!R=4HxvxwB6cUpiU(dnh2X{G<#wqiw zVH7ph float: + """Adds b and c to get a.""" + return b + c +``` + +Then in `run.py`, you'd set up the Hamilton driver and execute the data flow: + +```python +from hamilton import driver +import functions + +dr = driver.Driver(config={}, module=functions) +result = dr.execute(["a"], inputs={"b": 1, "c": 2}) # assuming you're providing b=1, c=2 as inputs +print(result['a']) # This will print 3, the result of addition +``` + +This Hamilton setup assumes that `b` and `c` are provided to the framework as inputs. If `b` and `c` were to be computed by other functions within the Hamilton framework or came from some form of data loading functions, those functions would need to be defined in `functions.py` as well, with appropriate signatures. +''' + expected = [ + "def a(b: float, c: float) -> float:\n" + ' """Adds b and c to get a."""\n' + " return b + c\n", + "from hamilton import driver\n" + "import functions\n" + "\n" + "dr = driver.Driver(config={}, module=functions)\n" + 'result = dr.execute(["a"], inputs={"b": 1, "c": 2}) # assuming you\'re ' + "providing b=1, c=2 as inputs\n" + "print(result['a']) # This will print 3, the result of addition\n", + ] + actual = translate_to_hamilton.code_segments(response) + assert actual == expected diff --git a/contrib/hamilton/contrib/dagworks/translate_to_hamilton/valid_configs.jsonl b/contrib/hamilton/contrib/dagworks/translate_to_hamilton/valid_configs.jsonl new file mode 100644 index 000000000..b8a6704f8 --- /dev/null +++ b/contrib/hamilton/contrib/dagworks/translate_to_hamilton/valid_configs.jsonl @@ -0,0 +1 @@ +{"description": "Default", "name": "default", "config": {}} diff --git a/examples/contrib/notebooks/dagworks-translate_to_hamilton.ipynb b/examples/contrib/notebooks/dagworks-translate_to_hamilton.ipynb new file mode 100644 index 000000000..5ae1af5ad --- /dev/null +++ b/examples/contrib/notebooks/dagworks-translate_to_hamilton.ipynb @@ -0,0 +1,294 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "initial_id", + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from hamilton import driver, dataflows" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# import module -- pick one\n", + "# translate_to_hamilton = dataflows.import_module('translate_to_hamilton')\n", + "# from hamilton.contrib.dagworks import translate_to_hamilton" + ], + "metadata": { + "collapsed": false + }, + "id": "2e5cf49a8f5f4c09" + }, + { + "cell_type": "code", + "execution_count": 6, + "outputs": [], + "source": [ + "# find some code to change.\n", + "def my_func(b, c):\n", + " \"\"\"Pretend this is a big function and we want to conver the body of it to hamilton.\"\"\"\n", + " a = b + c\n", + " d = some_func(a)\n", + " e = d + 1\n", + " return e" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-12-11T04:53:48.501891Z", + "start_time": "2023-12-11T04:53:48.230975Z" + } + }, + "id": "8a56177f67c84a6a" + }, + { + "cell_type": "code", + "execution_count": 7, + "outputs": [], + "source": [ + "# get the code\n", + "import inspect\n", + "user_code = inspect.getsource(my_func)" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-12-11T04:56:15.313971Z", + "start_time": "2023-12-11T04:56:14.930079Z" + } + }, + "id": "68d94fe011259977" + }, + { + "cell_type": "code", + "execution_count": 8, + "outputs": [ + { + "data": { + "text/plain": "'def my_func(b, c):\\n \"\"\"Pretend this is a big function.\"\"\"\\n a = b + c\\n d = some_func(a)\\n e = d + 1\\n return e\\n'" + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "user_code" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-12-11T04:56:40.058551Z", + "start_time": "2023-12-11T04:56:39.312301Z" + } + }, + "id": "3b4ea454b23a9878" + }, + { + "cell_type": "code", + "execution_count": 9, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Note: Hamilton collects completely anonymous data about usage. This will help us improve Hamilton over time. See https://github.com/dagworks-inc/hamilton#usage-analytics--data-privacy for details.\n" + ] + } + ], + "source": [ + "# create a driver\n", + "dr = (\n", + " driver.Builder()\n", + " .with_config({})\n", + " .with_modules(translate_to_hamilton)\n", + " .build()\n", + ")" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-12-11T04:59:14.902769Z", + "start_time": "2023-12-11T04:59:14.408612Z" + } + }, + "id": "993d821780a4ae76" + }, + { + "cell_type": "code", + "execution_count": 10, + "outputs": [], + "source": [ + "result = dr.execute(\n", + " [\"code_segments\", \"translated_code_response\"], # request these as outputs\n", + " inputs={\"user_code\": user_code, \"model_name\": \"gpt-4-1106-preview\"}\n", + ")" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-12-11T04:59:55.748817Z", + "start_time": "2023-12-11T04:59:19.193779Z" + } + }, + "id": "446ea53bd3406d1f" + }, + { + "cell_type": "code", + "execution_count": 14, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# functions.py\n", + "def a(b: float, c: float) -> float:\n", + " \"\"\"Computes the sum of b and c.\"\"\"\n", + " return b + c\n", + "\n", + "def d(a: float) -> float:\n", + " \"\"\"Applies some_func to a.\"\"\"\n", + " return some_func(a)\n", + "\n", + "def e(d: float) -> float:\n", + " \"\"\"Increments d by 1.\"\"\"\n", + " return d + 1\n", + "\n", + "# run.py\n", + "from hamilton import driver\n", + "import functions\n", + "\n", + "# Instantiate a Driver\n", + "dr = driver.Driver(config={}, module=functions)\n", + "\n", + "# Specify the final output we want from the Driver\n", + "output = [\"e\"]\n", + "\n", + "# Suppose we know the values of `b` and `c` we want to use\n", + "inputs = {\"b\": 1, \"c\": 2}\n", + "\n", + "# Execute the data flow to produce the output\n", + "result = dr.execute(output, inputs=inputs)\n", + "\n", + "print(result['e']) # The result is a dictionary with keys as the output names we specified earlier\n" + ] + } + ], + "source": [ + "for code in result[\"code_segments\"]:\n", + " print(code)" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-12-11T05:01:49.575797Z", + "start_time": "2023-12-11T05:01:49.570792Z" + } + }, + "id": "3c47292eb6f24a22" + }, + { + "cell_type": "code", + "execution_count": 12, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "In Hamilton, each part of the procedural function `my_func` that assigns a value to a variable can be refactored into its own Hamilton function. Here's how you might represent this code with separate functions:\n", + "\n", + "```python\n", + "# functions.py\n", + "def a(b: float, c: float) -> float:\n", + " \"\"\"Computes the sum of b and c.\"\"\"\n", + " return b + c\n", + "\n", + "def d(a: float) -> float:\n", + " \"\"\"Applies some_func to a.\"\"\"\n", + " return some_func(a)\n", + "\n", + "def e(d: float) -> float:\n", + " \"\"\"Increments d by 1.\"\"\"\n", + " return d + 1\n", + "```\n", + "\n", + "In this example, `some_func` is assumed to be an existing function that takes a single argument and returns a single result. In actual implementation, `some_func` would be defined elsewhere, or you would need to create a Hamilton function that correctly replicates its functionality.\n", + "\n", + "Now, you'd want to set up the driver script (`run.py`) to execute these functions:\n", + "\n", + "```python\n", + "# run.py\n", + "from hamilton import driver\n", + "import functions\n", + "\n", + "# Instantiate a Driver\n", + "dr = driver.Driver(config={}, module=functions)\n", + "\n", + "# Specify the final output we want from the Driver\n", + "output = [\"e\"]\n", + "\n", + "# Suppose we know the values of `b` and `c` we want to use\n", + "inputs = {\"b\": 1, \"c\": 2}\n", + "\n", + "# Execute the data flow to produce the output\n", + "result = dr.execute(output, inputs=inputs)\n", + "\n", + "print(result['e']) # The result is a dictionary with keys as the output names we specified earlier\n", + "```\n", + "\n", + "Please note that we directly put the values for `b` and `c` in the `inputs` dictionary as an example. In an actual Hamilton application, these might come from other data sources or computations within the directed acyclic graph (DAG).\n" + ] + } + ], + "source": [ + "print(result[\"translated_code_response\"])" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-12-11T05:00:11.575020Z", + "start_time": "2023-12-11T05:00:11.539851Z" + } + }, + "id": "94355a6dd80f5138" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false + }, + "id": "49720b418f9a7971" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}