Repository: google-research/talk-like-a-graph Branch: main Commit: 36af51e19ef7 Files: 20 Total size: 340.3 KB Directory structure: gitextract_nuchrmnk/ ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── __init__.py ├── talk_like_a_graph/ │ ├── README.md │ ├── __init__.py │ ├── graph_generators.py │ ├── graph_generators_runner.py │ ├── graph_generators_test.py │ ├── graph_metrics.py │ ├── graph_metrics_test.py │ ├── graph_tasks.py │ ├── graph_tasks_generator.py │ ├── graph_tasks_utils.py │ ├── graph_text_encoders.py │ ├── graph_text_encoders_test.py │ └── name_dictionaries.py └── tutorial/ ├── KDD-Tutorial-1-Talk-Like-a-Graph.ipynb ├── KDD-Tutorial-2-Let-Your-Graph-Do-The-Talking.ipynb └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: CONTRIBUTING.md ================================================ # How to Contribute ## Contributor License Agreement Contributions to this project must be accompanied by a Contributor License Agreement. You (or your employer) retain the copyright to your contribution, this simply gives us permission to use and redistribute your contributions as part of the project. Head over to to see your current agreements on file or to sign a new one. You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different project), you probably don't need to do it again. ## Code reviews All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests. ## Community Guidelines This project follows [Google's Open Source Community Guidelines](https://opensource.google/conduct/). ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Encoding Graphs and Structured Data in Language Models This repository contains code for [Talk like a Graph: Encoding Graphs for Large Language Models](https://arxiv.org/abs/2310.04560) and [Let Your Graph Do the Talking: Encoding Structured Data for LLMs](https://arxiv.org/abs/2402.05862). ## Cite us If you use this package for published work, please cite the following. For the "Talk like a Graph" project: ``` @inproceedigs{fatemi2024talk, title={Talk like a Graph: Encoding Graphs for Large Language Models}, author={Bahare Fatemi and Jonathan Halcrow and Bryan Perozzi}, booktitle={International Conference on Learning Representations (ICLR)}, year={2024} } ``` For Graph Token (a.k.a "Let Your Graph Do the Talking"): ``` @misc{perozzi2024letgraphtalkingencoding, title={Let Your Graph Do the Talking: Encoding Structured Data for LLMs}, author={Bryan Perozzi and Bahare Fatemi and Dustin Zelle and Anton Tsitsulin and Mehran Kazemi and Rami Al-Rfou and Jonathan Halcrow}, year={2024}, eprint={2402.05862}, archivePrefix={arXiv}, primaryClass={cs.LG}, url={https://arxiv.org/abs/2402.05862}, } ``` ## Disclaimer This is not an official Google product. ================================================ FILE: __init__.py ================================================ ================================================ FILE: talk_like_a_graph/README.md ================================================ # Using Large Language Models to Solve Graph Problems This repository contains the code to generate graph reasoning problems with different graph generator algorithms and graph encoding methods, as well as different prompting techniques. The graph tasks are `edge existence`, `node degree`, `node count`, `edge count`, `connected nodes`, `disconnected nodes`, `cycle check`, `reachability`, `shortest path`, `maximum flow`, `node classification`, and `triangle counting`. The datasets used here are proposed in our paper: [Talk like a Graph: Encoding Graphs for Large Language Models](https://arxiv.org/abs/2310.04560). ### Generating graphs ```sh ./graphqa/graph_generator.sh ``` ### Generating files for tasks ```sh ./graphqa/task_generator.sh ``` ## Contact us For questions or comments about the implementation, please contact baharef@google.com. ## Cite us If you use this package for published work, please cite the following: ``` @inproceedigs{fatemi2024talk, title={Talk like a Graph: Encoding Graphs for Large Language Models}, author={Bahare Fatemi and Jonathan Halcrow and Bryan Perozzi}, booktitle={International Conference on Learning Representations (ICLR)}, year={2024} } ``` ## Disclaimer This is not an official Google product. # Placeholder for internal data notes. ================================================ FILE: talk_like_a_graph/__init__.py ================================================ ================================================ FILE: talk_like_a_graph/graph_generators.py ================================================ r"""Random graph generation.""" import random import networkx as nx import numpy as np _NUMBER_OF_NODES_RANGE = { "small": np.arange(5, 10), "medium": np.arange(10, 15), "large": np.arange(15, 20), } _NUMBER_OF_COMMUNITIES_RANGE = { "small": np.arange(2, 4), "medium": np.arange(2, 8), "large": np.arange(2, 10), } def generate_graphs( number_of_graphs: int, algorithm: str, directed: bool, random_seed: int = 1234, er_min_sparsity: float = 0.0, er_max_sparsity: float = 1.0, ) -> list[nx.Graph]: """Generating multiple graphs using the provided algorithms. Args: number_of_graphs: number of graphs to generate algorithm: the random graph generator algorithm directed: whether to generate directed or undirected graphs. random_seed: the random seed to generate graphs with. er_min_sparsity: minimum sparsity of er graphs. er_max_sparsity: maximum sparsity of er graphs. Returns: generated_graphs: a list of nx graphs. Raises: NotImplementedError: if the algorithm is not yet implemented. """ random.seed(random_seed) np.random.seed(random_seed) generated_graphs = [] graph_sizes = random.choices( list(_NUMBER_OF_NODES_RANGE.keys()), k=number_of_graphs ) random_state = np.random.RandomState(random_seed) if algorithm == "er": for i in range(number_of_graphs): sparsity = random.uniform(er_min_sparsity, er_max_sparsity) number_of_nodes = random.choice(_NUMBER_OF_NODES_RANGE[graph_sizes[i]]) generated_graphs.append( nx.erdos_renyi_graph( number_of_nodes, sparsity, seed=random_state, directed=directed ) ) elif algorithm == "ba": for i in range(number_of_graphs): number_of_nodes = random.choice(_NUMBER_OF_NODES_RANGE[graph_sizes[i]]) m = random.randint(1, number_of_nodes - 1) generated_graph = nx.barabasi_albert_graph( number_of_nodes, m, seed=random_state ) if directed: generated_graphs.append(randomize_directions(generated_graph)) else: generated_graphs.append(generated_graph) elif algorithm == "sbm": for i in range(number_of_graphs): number_of_nodes = random.choice(_NUMBER_OF_NODES_RANGE[graph_sizes[i]]) number_of_communities = random.choice( _NUMBER_OF_COMMUNITIES_RANGE[graph_sizes[i]] ) # sizes forms number of nodes in communities. sizes = [] for _ in range(number_of_communities - 1): sizes.append( random.randint( 1, max( 1, number_of_nodes - sum(sizes) - (number_of_communities - 1), ), ) ) sizes.append(number_of_nodes - sum(sizes)) # p forms probabilities of communities connecting each other. p = np.random.uniform(size=(number_of_communities, number_of_communities)) if random.uniform(0, 1) < 0.5: p = np.maximum(p, p.transpose()) else: p = np.minimum(p, p.transpose()) sbm_graph = nx.stochastic_block_model( sizes, p, seed=random_state, directed=directed ) # sbm graph generator automatically adds dictionary attributes. sbm_graph = remove_graph_data(sbm_graph) generated_graphs.append(sbm_graph) elif algorithm == "sfn": for i in range(number_of_graphs): number_of_nodes = random.choice(_NUMBER_OF_NODES_RANGE[graph_sizes[i]]) generated_graph = nx.scale_free_graph(number_of_nodes, seed=random_state) # sfn graphs are by defaukt directed. if not directed: generated_graphs.append(remove_directions(generated_graph)) else: generated_graphs.append(generated_graph) elif algorithm == "complete": for i in range(number_of_graphs): number_of_nodes = random.choice(_NUMBER_OF_NODES_RANGE[graph_sizes[i]]) create_using = nx.DiGraph if directed else nx.Graph generated_graphs.append( nx.complete_graph(number_of_nodes, create_using=create_using) ) elif algorithm == "star": for i in range(number_of_graphs): number_of_nodes = random.choice(_NUMBER_OF_NODES_RANGE[graph_sizes[i]]) # number_of_nodes for star is the input + a center node. generated_graph = nx.star_graph(number_of_nodes - 1) if directed: generated_graphs.append(randomize_directions(generated_graph)) else: generated_graphs.append(generated_graph) elif algorithm == "path": for i in range(number_of_graphs): number_of_nodes = random.choice(_NUMBER_OF_NODES_RANGE[graph_sizes[i]]) create_using = nx.DiGraph if directed else nx.Graph generated_graphs.append( nx.path_graph(number_of_nodes, create_using=create_using) ) else: raise NotImplementedError() return generated_graphs def remove_graph_data(graph: nx.Graph) -> nx.Graph: # GraphML writer does not support dictionary data for nodes or graphs. for ind in range((graph.number_of_nodes())): graph.nodes[ind].pop("block", None) graph_data_keys = list(graph.graph.keys()) for _, node in enumerate(graph_data_keys): graph.graph.pop(node, None) return graph def randomize_directions(graph: nx.Graph) -> nx.DiGraph: # Converting the undirected graph to a directed graph. directed_graph = graph.to_directed() # For each edge, randomly choose a direction. edges = list(graph.edges()) for u, v in edges: if random.random() < 0.5: directed_graph.remove_edge(u, v) else: directed_graph.remove_edge(v, u) return directed_graph def remove_directions(graph: nx.Graph) -> nx.Graph: # Converting the direted graph to an undirected one by removing directions. undirected_graph = nx.Graph() undirected_graph.add_nodes_from(graph.nodes()) # Add edges between nodes, ignoring directions. for u, v in graph.edges(): undirected_graph.add_edge(u, v) return undirected_graph ================================================ FILE: talk_like_a_graph/graph_generators_runner.py ================================================ r"""Random graph generation. This code generates random graph using different algorithms. # Placeholder for Google-internal comments. """ from collections.abc import Sequence import os from absl import app from absl import flags import networkx as nx # Internal import. from . import graph_generators _ALGORITHM = flags.DEFINE_string( "algorithm", None, "The graph generating algorithm to use.", required=True, ) _NUMBER_OF_GRAPHS = flags.DEFINE_integer( "number_of_graphs", None, "The number of graphs to generate.", required=True, ) _DIRECTED = flags.DEFINE_bool( "directed", False, "Whether to generate directed graphs." ) _OUTPUT_PATH = flags.DEFINE_string( "output_path", None, "The output path to write the graphs.", required=True ) _SPLIT = flags.DEFINE_string( "split", None, "The dataset split to generate.", required=True ) _MIN_SPARSITY = flags.DEFINE_float("min_sparsity", 0.0, "The minimum sparsity.") _MAX_SPARSITY = flags.DEFINE_float("max_sparsity", 1.0, "The maximum sparsity.") def write_graphs(graphs: list[nx.Graph], output_dir: str) -> None: if not os.path.isdir(output_dir): os.makedirs(output_dir) for ind, graph in enumerate(graphs): nx.write_graphml( graph, os.Open( os.path.join(output_dir, str(ind) + ".graphml"), "wb", ), ) def main(argv: Sequence[str]) -> None: if len(argv) > 1: raise app.UsageError("Too many command-line arguments.") if _SPLIT.value == "train": random_seed = 9876 elif _SPLIT.value == "test": random_seed = 1234 elif _SPLIT.value == "validation": random_seed = 5432 else: raise NotImplementedError() generated_graphs = graph_generators.generate_graphs( number_of_graphs=_NUMBER_OF_GRAPHS.value, algorithm=_ALGORITHM.value, directed=_DIRECTED.value, random_seed=random_seed, er_min_sparsity=_MIN_SPARSITY.value, er_max_sparsity=_MAX_SPARSITY.value, ) write_graphs( graphs=generated_graphs, output_dir=os.path.join( _OUTPUT_PATH.value, "directed" if _DIRECTED.value else "undirected", _ALGORITHM.value, _SPLIT.value, ), ) if __name__ == "__main__": app.run(main) ================================================ FILE: talk_like_a_graph/graph_generators_test.py ================================================ from absl.testing import parameterized from . import graph_generators from absl.testing import absltest class GraphGenerationTest(absltest.TestCase, parameterized.TestCase): @parameterized.named_parameters( dict( testcase_name='er_undirected_1', algorithm='er', directed=False, k=1, ), dict( testcase_name='er_directed_1', algorithm='er', directed=True, k=1, ), dict( testcase_name='ba_undirected_5', algorithm='ba', directed=False, k=5, ), dict( testcase_name='ba_directed_5', algorithm='ba', directed=True, k=5, ), ) def test_number_of_graphs(self, algorithm, directed, k): generated_graph = graph_generators.generate_graphs(k, algorithm, directed) self.assertLen(generated_graph, k) @parameterized.named_parameters( dict( testcase_name='er_undirected', algorithm='er', directed=False, ), dict( testcase_name='er_directed', algorithm='er', directed=True, ), dict( testcase_name='ba_undirected', algorithm='ba', directed=False, ), dict( testcase_name='ba_directed', algorithm='ba', directed=True, ), dict( testcase_name='sbm_undirected', algorithm='sbm', directed=False, ), dict( testcase_name='sbm_directed', algorithm='sbm', directed=True, ), dict( testcase_name='sfn_undirected', algorithm='sfn', directed=False, ), dict( testcase_name='sfn_directed', algorithm='sfn', directed=True, ), dict( testcase_name='complete_undirected', algorithm='complete', directed=False, ), dict( testcase_name='complete_directed', algorithm='complete', directed=True, ), dict( testcase_name='star_undirected', algorithm='star', directed=False, ), dict( testcase_name='star_directed', algorithm='star', directed=True, ), dict( testcase_name='path_undirected', algorithm='path', directed=False, ), dict( testcase_name='path_directed', algorithm='path', directed=True, ), ) def test_directions(self, algorithm, directed): generated_graph = graph_generators.generate_graphs(1, algorithm, directed) self.assertEqual(generated_graph[0].is_directed(), directed) if __name__ == '__main__': googletest.main() ================================================ FILE: talk_like_a_graph/graph_metrics.py ================================================ """Metrics for seqio tasks over graph data. This module contains definitions of metric_fns to be used for scoring graph tasks from nlgraph and graphqa. """ from typing import Mapping, Sequence def yes_no_accuracy( targets: Sequence[str], predictions: Sequence[str] ) -> Mapping[str, float]: """Assesses the accuracy of LLM outputs on Yes/No tasks. Targets must contain either the word 'yes' or the word 'no' but not both. Predictions are binarized by checking for 'yes' or 'no' in the first line. Args: targets: The expected output strings. predictions: The LLM outputs. Returns: Returns a dict of the following metrics: yes_no_accuracy: The % where the target and prediction match. yes_no_ambiguous: The % where the prediction contained yes and no yes_no_indeterminate: The % where the prediction contained neither yes nor no Raises: ValueError: If a target string contains 'yes' and 'no' """ num_correct = 0 num_ambiguous = 0 num_indeterminate = 0 for target, prediction in zip(targets, predictions): normalized_target = target.lower() binarized_target = 'yes' in normalized_target print(binarized_target) if binarized_target and 'no' in normalized_target: raise ValueError(f'Ambiguous target string, {target}') if not binarized_target and 'no' not in normalized_target: raise ValueError(f'Indeterminate target string, {target}') normalized_prediction = prediction.splitlines() if not normalized_prediction: normalized_prediction = '' else: normalized_prediction = normalized_prediction[0] normalized_prediction = normalized_prediction.lower() binarized_prediction = 'yes' in normalized_prediction.lower() print(normalized_prediction) if binarized_prediction and 'no' in normalized_prediction: num_ambiguous += 1 continue if not binarized_prediction and 'no' not in normalized_prediction: num_indeterminate += 1 continue if binarized_prediction == binarized_target: num_correct += 1 return { 'yes_no_accuracy': num_correct / len(targets), 'yes_no_ambiguous': num_ambiguous / len(targets), 'yes_no_indeterminate': num_indeterminate / len(targets), } ================================================ FILE: talk_like_a_graph/graph_metrics_test.py ================================================ from . import graph_metrics from absl.testing import absltest class GraphTasksTest(absltest.TestCase): def test_yes_no_correct(self): result = graph_metrics.yes_no_accuracy( targets=["Yes", "The answer is yes.", "No", "The answer is no.", "No"], predictions=[ "yes", "Yes\nNo", "No\nYes", "That's gonna be no from me.", "yes", ], ) self.assertEqual(result["yes_no_ambiguous"], 0) self.assertEqual(result["yes_no_indeterminate"], 0) self.assertAlmostEqual(result["yes_no_accuracy"], 0.8) def test_yes_no_ambiguous(self): result = graph_metrics.yes_no_accuracy( targets=["Yes", "The answer is yes.", "No", "The answer is no.", "No"], predictions=[ "yes", "Yes No", "No Yes", "That's gonna be no from me.", "yes", ], ) self.assertEqual(result["yes_no_ambiguous"], 0.4) self.assertEqual(result["yes_no_indeterminate"], 0) self.assertAlmostEqual(result["yes_no_accuracy"], 0.4) def test_yes_no_indeterminate(self): result = graph_metrics.yes_no_accuracy( targets=["Yes", "The answer is yes.", "No", "The answer is no.", "No"], predictions=[ "yes", "\n No", "", "That's gonna be no from me.", "yes", ], ) self.assertEqual(result["yes_no_ambiguous"], 0) self.assertEqual(result["yes_no_indeterminate"], 0.4) self.assertAlmostEqual(result["yes_no_accuracy"], 0.4) def test_yes_no_accuracy_raises_on_ambiguous_target(self): with self.assertRaises(ValueError): graph_metrics.yes_no_accuracy( targets=["Yes but maybe no"], predictions=["yes"], ) def test_yes_no_accuracy_raises_on_indeterminate_target(self): with self.assertRaises(ValueError): graph_metrics.yes_no_accuracy( targets=["Hmm?"], predictions=["yes"], ) if __name__ == "__main__": googletest.main() ================================================ FILE: talk_like_a_graph/graph_tasks.py ================================================ """The graph tasks to be tried with LLMs.""" import random import networkx as nx import numpy as np from . import graph_text_encoders class GraphTask: """The parent class for all the graph tasks.""" def __init__(self): self.name = 'default' self.maximum_nnodes_cot_graph = 10 def prepare_examples_dict( self, graphs: list[nx.Graph], generator_algorithms: list[str], encoding_method: str, ) -> dict[int, dict[str, str | list[int]]]: raise NotImplementedError() def create_few_shot_example( self, graph: nx.Graph, encoding_method: str, cot: bool ): raise NotImplementedError() class CycleCheck(GraphTask): """The graph task to check if there is at least one cycle or not.""" def __init__(self): super().__init__() self.name = 'cycle_check' self._task_description = 'Q: Is there a cycle in this graph?\nA: ' def prepare_examples_dict( self, graphs: list[nx.Graph], generator_algorithms: list[str], encoding_method: str, ) -> dict[int, dict[str, str | list[int]]]: examples_dict = {} for ind, graph in enumerate(graphs): question = ( graph_text_encoders.encode_graph(graph, encoding_method) + self._task_description ) try: nx.find_cycle(graph) answer = 'Yes, there is a cycle.' except nx.NetworkXNoCycle: answer = 'No, there is no cycle.' examples_dict[ind] = { 'question': question, 'answer': answer, 'nnodes': str(len(graph.nodes())), 'nedges': str(len(graph.edges())), 'task_description': self._task_description, 'graph': graph, 'algorithm': generator_algorithms[ind], 'node_ids': [], } return examples_dict def create_few_shot_example( self, graph: nx.Graph, encoding_method: str, cot: bool ) -> str: """Create a few shot example w or w/o cot for the graph graph.""" name_dict = graph_text_encoders.get_tlag_node_encoder( graph, encoding_method ) question = ( graph_text_encoders.encode_graph(graph, encoding_method) + self._task_description ) try: cycle = nx.find_cycle(graph) cycle_text = '' answer = 'Yes, there is a cycle. ' if cot: for pair in cycle: cycle_text += ( name_dict[pair[0]] + ' is connected to ' + name_dict[pair[1]] + ', ' ) cycle_cot = 'The cycle is: %s.' % cycle_text[:-2] answer += cycle_cot except nx.NetworkXNoCycle: answer = 'No, there is no cycle.' return question + answer def choose_few_shot_examples( self, few_shots_dict: dict[tuple[str, str], list[str]], encoding_method: str, k: int = 2, ) -> str: """Choose few shot examples for each algorithm.""" pos_cycle_algorithms = ['er', 'ba', 'sbm', 'sfn', 'complete'] neg_cycle_algorithms = ['star', 'path'] few_shots_str = '' # choose k-1 shots for pos algorithms and one negative. positive_algorithms = random.choices(pos_cycle_algorithms, k=k - 1) for positive_algorithm in positive_algorithms: example_list = few_shots_dict[(positive_algorithm, encoding_method)] few_shots_str += 'Example: ' + random.choice(example_list) + '\n' negative_algorithm = random.choice(neg_cycle_algorithms) example_list = few_shots_dict[(negative_algorithm, encoding_method)] few_shots_str += 'Example: ' + random.choice(example_list) + '\n' return few_shots_str class EdgeExistence(GraphTask): """The graph task to check if an edge exist in a graph or not.""" def __init__(self): super().__init__() self.name = 'edge_existence' def prepare_examples_dict( self, graphs: list[nx.Graph], generator_algorithms: list[str], encoding_method: str, ) -> dict[int, dict[str, str | list[int]]]: examples_dict = {} name_dict = graph_text_encoders.get_tlag_node_encoder(None, encoding_method) for ind, graph in enumerate(graphs): source, target = random.sample(list(graph.nodes()), k=2) question = graph_text_encoders.encode_graph(graph, encoding_method) task_description = 'Q: Is node %s connected to node %s?\nA: ' % ( name_dict[source], name_dict[target], ) question += task_description if ((source, target) in graph.edges()) or ( (target, source) in graph.edges() ): answer = 'Yes.' else: answer = 'No.' examples_dict[ind] = { 'question': question, 'answer': answer, 'nnodes': str(len(graph.nodes())), 'nedges': str(len(graph.edges())), 'task_description': task_description, 'graph': graph, 'algorithm': generator_algorithms[ind], 'node_ids': [source, target], } return examples_dict def create_few_shot_example( self, graph: nx.Graph, encoding_method: str, cot: bool ) -> str: name_dict = graph_text_encoders.get_tlag_node_encoder( graph, encoding_method ) source, target = random.sample(list(graph.nodes()), k=2) question = graph_text_encoders.encode_graph(graph, encoding_method) question += 'Q: Is node %s connected to node %s?\nA: ' % ( name_dict[source], name_dict[target], ) if ((source, target) in graph.edges()) or ( (target, source) in graph.edges() ): answer = 'Yes.' if cot: answer += ( ' Because, there is an edge from %s to %s in the graph description.' % (name_dict[source], name_dict[target]) ) else: answer = 'No.' if cot: answer += ( ' Because, there is no edge from %s to %s in the graph description.' % (name_dict[source], name_dict[target]) ) return question + answer class NodeCount(GraphTask): """The graph task for finding number of nodes in a graph.""" def __init__(self): super().__init__() self.name = 'node_count' self._task_description = 'Q: How many nodes are in this graph?\nA: ' def prepare_examples_dict( self, graphs: list[nx.Graph], generator_algorithms: list[str], encoding_method: str, ) -> dict[int, dict[str, str | list[int]]]: examples_dict = {} for ind, graph in enumerate(graphs): question = graph_text_encoders.encode_graph(graph, encoding_method) question += self._task_description answer = ' %d.' % len(graph.nodes()) examples_dict[ind] = { 'question': question, 'answer': answer, 'nnodes': str(len(graph.nodes())), 'nedges': str(len(graph.edges())), 'task_description': self._task_description, 'graph': graph, 'algorithm': generator_algorithms[ind], 'node_ids': [], } return examples_dict def get_nodes_string(self, name_dict: dict[int, str], nnodes: int) -> str: node_string = '' for i in range(nnodes - 1): node_string += name_dict[i] + ', ' node_string += 'and ' + name_dict[nnodes - 1] return node_string def create_few_shot_example( self, graph: nx.Graph, encoding_method: str, cot: bool ) -> str: name_dict = graph_text_encoders.get_tlag_node_encoder( graph, encoding_method ) question = graph_text_encoders.encode_graph(graph, encoding_method) question += self._task_description answer = '%d.' % len(graph.nodes()) if cot: answer += ' The nodes are %s.' % self.get_nodes_string( name_dict, len(graph.nodes()) ) return question + answer class NodeDegree(GraphTask): """The graph task for finding degree of a node in a graph.""" def __init__(self): super().__init__() self.name = 'node_degree' def prepare_examples_dict( self, graphs: list[nx.Graph], generator_algorithms: list[str], encoding_method: str, ) -> dict[int, dict[str, str | list[int]]]: examples_dict = {} name_dict = graph_text_encoders.get_tlag_node_encoder(None, encoding_method) for ind, graph in enumerate(graphs): question = graph_text_encoders.encode_graph(graph, encoding_method) source_node = random.sample(list(graph.nodes()), k=1)[0] task_description = ( 'Q: What is the degree of node %s?\nA: ' % name_dict[source_node] ) question += task_description answer = '%d.' % graph.degree[source_node] examples_dict[ind] = { 'question': question, 'answer': answer, 'nnodes': str(len(graph.nodes())), 'nedges': str(len(graph.edges())), 'task_description': task_description, 'graph': graph, 'algorithm': generator_algorithms[ind], 'node_ids': [source_node], } return examples_dict def get_edge_string( self, name_dict: dict[int, str], graph: nx.Graph, source_node: int ) -> str: """Gets a string identifying the edges a given node is connected to.""" edge_string = '' target_edges = graph.edges(source_node) target_nodes = [] for edge in target_edges: target_nodes.append(edge[1]) if target_nodes: for i in range(len(target_nodes) - 1): edge_string += name_dict[target_nodes[i]] + ', ' edge_string += 'and ' + name_dict[target_nodes[-1]] else: edge_string = 'no nodes' return edge_string def create_few_shot_example( self, graph: nx.Graph, encoding_method: str, cot: bool ) -> str: name_dict = graph_text_encoders.get_tlag_node_encoder( graph, encoding_method ) question = graph_text_encoders.encode_graph(graph, encoding_method) source_node = random.sample(list(graph.nodes()), k=1)[0] question += ( 'Q: What is the degree of node %s?\nA: ' % name_dict[source_node] ) answer = '%d.' % graph.degree[source_node] if cot: answer += ' This is because %s is connected to %s.' % ( name_dict[source_node], self.get_edge_string(name_dict, graph, source_node), ) return question + answer class EdgeCount(GraphTask): """The graph task for finding number of edges in a graph.""" def __init__(self): super().__init__() self.name = 'edge_count' self._task_description = 'Q: How many edges are in this graph?\nA: ' def prepare_examples_dict( self, graphs: list[nx.Graph], generator_algorithms: list[str], encoding_method: str, ) -> dict[int, dict[str, str | list[int]]]: examples_dict = {} for ind, graph in enumerate(graphs): question = graph_text_encoders.encode_graph(graph, encoding_method) question += self._task_description answer = ' %d.' % len(graph.edges()) examples_dict[ind] = { 'question': question, 'answer': answer, 'nnodes': str(len(graph.nodes())), 'nedges': str(len(graph.edges())), 'task_description': self._task_description, 'graph': graph, 'algorithm': generator_algorithms[ind], 'node_ids': [], } return examples_dict def get_edges_string( self, name_dict: dict[int, str], edges: list[tuple[int, int]] ) -> str: edges_string = '' for edge in edges: edges_string += ( '(' + name_dict[edge[0]] + ', ' + name_dict[edge[1]] + '), ' ) return edges_string.strip()[:-1] def create_few_shot_example( self, graph: nx.Graph, encoding_method: str, cot: bool ) -> str: name_dict = graph_text_encoders.get_tlag_node_encoder( graph, encoding_method ) question = graph_text_encoders.encode_graph(graph, encoding_method) question += self._task_description answer = '%d.' % len(graph.edges()) if cot: answer += ' The edges are %s.' % self.get_edges_string( name_dict, list(graph.edges()) ) return question + answer class ConnectedNodes(GraphTask): """The graph task for finding connected nodes to a given node in a graph.""" def __init__(self): super().__init__() self.name = 'connected_nodes' def prepare_examples_dict( self, graphs: list[nx.Graph], generator_algorithms: list[str], encoding_method: str, ) -> dict[int, dict[str, str | list[int]]]: examples_dict = {} name_dict = graph_text_encoders.get_tlag_node_encoder(None, encoding_method) for ind, graph in enumerate(graphs): question = graph_text_encoders.encode_graph(graph, encoding_method) source_node = random.sample(list(graph.nodes()), k=1)[0] task_description = ( 'Q: List all the nodes connected to %s in alphabetical order.\nA: ' % name_dict[source_node] ) question += task_description outgoing_edges = list(graph.edges(source_node)) if outgoing_edges: answer = self.get_connected_nodes(outgoing_edges, name_dict) + '.' else: answer = ' No nodes.' examples_dict[ind] = { 'question': question, 'answer': answer, 'nnodes': str(len(graph.nodes())), 'nedges': str(len(graph.edges())), 'task_description': task_description, 'graph': graph, 'algorithm': generator_algorithms[ind], 'node_ids': [source_node], } return examples_dict def get_connected_nodes( self, edges: list[tuple[int, int]], name_dict: dict[int, str] ) -> str: """Gets a string including all the nodes that are connected to source.""" connected_nodes = [] for edge in edges: connected_nodes.append(name_dict[edge[1]]) connected_nodes_string = '' if connected_nodes: try: int(connected_nodes[0]) connected_nodes_string = ', '.join(map(str, connected_nodes)) except ValueError: # Check if these are not integers, sort connected_nodes_string = ', '.join(map(str, sorted(connected_nodes))) return connected_nodes_string def create_few_shot_example( self, graph: nx.Graph, encoding_method: str, cot: bool ) -> str: name_dict = graph_text_encoders.get_tlag_node_encoder( graph, encoding_method ) question = graph_text_encoders.encode_graph(graph, encoding_method) source_node = random.sample(list(graph.nodes()), k=1)[0] question += ( 'Q: List all the nodes connected to %s in alphabetical order.\nA: ' % name_dict[source_node] ) outgoing_edges = list(graph.edges(source_node)) answer = '' if outgoing_edges: answer = self.get_connected_nodes(outgoing_edges, name_dict) + '.' if cot: answer += ' This is because there is an edge from %s to %s.' % ( name_dict[source_node], answer, ) else: answer = 'No nodes.' if cot: answer += ( ' This is because %s is not connected to any node.' % name_dict[source_node] ) return question + answer class DisconnectedNodes(GraphTask): """The task for finding disconnected nodes for a given node in a graph.""" def __init__(self): super().__init__() self.name = 'disconnected_nodes' def prepare_examples_dict( self, graphs: list[nx.Graph], generator_algorithms: list[str], encoding_method: str, ) -> dict[int, dict[str, str | list[int]]]: examples_dict = {} name_dict = graph_text_encoders.get_tlag_node_encoder(None, encoding_method) for ind, graph in enumerate(graphs): question = graph_text_encoders.encode_graph(graph, encoding_method) source_node = random.sample(list(graph.nodes()), k=1)[0] task_description = ( 'Q: List all the nodes that are not connected to %s in alphabetical' ' order.\nA: ' % name_dict[source_node] ) question += task_description outgoing_edges = list(graph.edges(source_node)) answer = self.get_disconnected_nodes( source_node, outgoing_edges, name_dict, list(graph.nodes()) ) if not answer: answer = 'No nodes' answer += '.' examples_dict[ind] = { 'question': question, 'answer': answer, 'nnodes': str(len(graph.nodes())), 'nedges': str(len(graph.edges())), 'task_description': task_description, 'graph': graph, 'algorithm': generator_algorithms[ind], 'node_ids': [source_node], } return examples_dict def get_disconnected_nodes( self, source: int, edges: list[tuple[int, int]], name_dict: dict[int, str], all_nodes: list[int], ) -> str: """Gets a string with all the nodes that are not connected to source.""" for edge in edges: if edge[1] in all_nodes: all_nodes.remove(edge[1]) if source in all_nodes: all_nodes.remove(source) all_nodes_names = [] for node in all_nodes: all_nodes_names.append(name_dict[node]) # sorted operation should be different for integers vs strings. if all_nodes_names: try: int(all_nodes_names[0]) for ind, value in enumerate(all_nodes_names): all_nodes_names[ind] = int(value) all_nodes_names = sorted(all_nodes_names) for ind, value in enumerate(all_nodes_names): all_nodes_names[ind] = str(value) except ValueError: pass return ', '.join(map(str, sorted(all_nodes_names))) def create_few_shot_example( self, graph: nx.Graph, encoding_method: str, cot: bool ) -> str: name_dict = graph_text_encoders.get_tlag_node_encoder( graph, encoding_method ) question = graph_text_encoders.encode_graph(graph, encoding_method) source_node = random.sample(list(graph.nodes()), k=1)[0] question += ( 'Q: List all the nodes that are not connected to %s in alphabetical' ' order.\nA: ' % name_dict[source_node] ) outgoing_edges = list(graph.edges(source_node)) answer = '' disconnected_nodes_string = self.get_disconnected_nodes( source_node, outgoing_edges, name_dict, list(graph.nodes()) ) if outgoing_edges: if not disconnected_nodes_string: disconnected_nodes_string = 'No nodes' answer = disconnected_nodes_string + '.' if cot: answer += ' This is because there is not an edge from %s to %s.' % ( name_dict[source_node], answer, ) else: answer = ' No nodes.' if cot: answer += ( ' This is because %s is connected to all nodes.' % name_dict[source_node] ) return question + answer class Reachability(GraphTask): """The graph task to check if there is a path from a source to target.""" def __init__(self): super().__init__() self.name = 'reachability' def prepare_examples_dict( self, graphs: list[nx.Graph], generator_algorithms: list[str], encoding_method: str, ) -> dict[int, dict[str, str | list[int]]]: examples_dict = {} name_dict = graph_text_encoders.get_tlag_node_encoder(None, encoding_method) for ind, graph in enumerate(graphs): source, target = random.sample(list(graph.nodes()), k=2) question = graph_text_encoders.encode_graph(graph, encoding_method) task_description = 'Q: Is there a path from node %s to node %s?\nA: ' % ( name_dict[source], name_dict[target], ) question += task_description if nx.has_path(graph, source, target): answer = 'Yes.' else: answer = 'No.' examples_dict[ind] = { 'question': question, 'answer': answer, 'nnodes': str(len(graph.nodes())), 'nedges': str(len(graph.edges())), 'task_description': task_description, 'graph': graph, 'algorithm': generator_algorithms[ind], 'node_ids': [source, target], } return examples_dict def create_few_shot_example( self, graph: nx.Graph, encoding_method: str, cot: bool ) -> str: name_dict = graph_text_encoders.get_tlag_node_encoder( graph, encoding_method ) source, target = random.sample(list(graph.nodes()), k=2) question = graph_text_encoders.encode_graph(graph, encoding_method) question += 'Q: Is there a path from node %s to node %s?\nA: ' % ( name_dict[source], name_dict[target], ) if nx.has_path(graph, source, target): answer = 'Yes.' if cot: path = nx.shortest_path(graph, source, target) explanation = ' Because' for i in range(len(path) - 1): # The only edge or the non-last edges in the path. if len(path) == 2 or i < len(path) - 2: sep = ',' # The last edge in a path with more than one edge. else: sep = ', and' explanation += '%s there is an edge from node %d to node %d' % ( sep, path[i], path[i + 1], ) explanation += ' .' answer += explanation else: answer = 'No.' if cot: answer += ( ' Because, there is no path connecting node %s to node %s based on' ' the graph description.' % (name_dict[source], name_dict[target]) ) return question + answer class ShortestPath(GraphTask): """The graph task to check if there is a path from a source to target.""" def __init__(self): super().__init__() self.name = 'shortest_path' def prepare_examples_dict( self, graphs: list[nx.Graph], generator_algorithms: list[str], encoding_method: str, ) -> dict[int, dict[str, str | list[int]]]: examples_dict = {} name_dict = graph_text_encoders.get_tlag_node_encoder(None, encoding_method) for ind, graph in enumerate(graphs): source, target = random.sample(list(graph.nodes()), k=2) question = graph_text_encoders.encode_graph(graph, encoding_method) task_description = ( 'Q: What is the length of the shortest path from node %s to node' ' %s?\nA: ' % ( name_dict[source], name_dict[target], ) ) question += task_description try: path = nx.shortest_path(graph, source, target) answer = str(len(path) - 1) + '.' except nx.NetworkXNoPath: answer = 'There is no path from node %s to node %s.' % ( name_dict[source], name_dict[target], ) examples_dict[ind] = { 'question': question, 'answer': answer, 'nnodes': str(len(graph.nodes())), 'nedges': str(len(graph.edges())), 'task_description': task_description, 'graph': graph, 'algorithm': generator_algorithms[ind], 'node_ids': [source, target], } return examples_dict def create_few_shot_example( self, graph: nx.Graph, encoding_method: str, cot: bool ) -> str: name_dict = graph_text_encoders.get_tlag_node_encoder( graph, encoding_method ) source, target = random.sample(list(graph.nodes()), k=2) question = graph_text_encoders.encode_graph(graph, encoding_method) question += ( 'Q: What is the length of the shortest path from node %s to node' ' %s?\nA: ' % ( name_dict[source], name_dict[target], ) ) if nx.has_path(graph, source, target): path = nx.shortest_path(graph, source, target) answer = str(len(path) - 1) + '.' if cot: explanation = ' Because' for i in range(len(path) - 1): # The only edge or the non-last edges in the path. if len(path) == 2 or i < len(path) - 2: sep = ',' # The last edge in a path with more than one edge. else: sep = ', and' explanation += '%s there is an edge from node %d to node %d' % ( sep, path[i], path[i + 1], ) explanation += ' .' answer += explanation else: answer = 'There is no path from node %s to node %s.' % ( name_dict[source], name_dict[target], ) if cot: answer += ( ' Because, there is no path connecting node %s to node %s based on' ' the graph description.' % (name_dict[source], name_dict[target]) ) return question + answer class TriangleCounting(GraphTask): """The graph task to count the number of triangles in a graph.""" def __init__(self): super().__init__() self.name = 'triangle_counting' self._task_description = 'Q: How many triangles are in this graph?\nA: ' def prepare_examples_dict( self, graphs: list[nx.Graph], generator_algorithms: list[str], encoding_method: str, ) -> dict[int, dict[str, str | list[int]]]: examples_dict = {} for ind, graph in enumerate(graphs): question = ( graph_text_encoders.encode_graph(graph, encoding_method) + self._task_description ) ntriangles = int(np.sum(list(nx.triangles(graph).values())) / 3) answer = '%i.' % ntriangles examples_dict[ind] = { 'question': question, 'answer': answer, 'nnodes': str(len(graph.nodes())), 'nedges': str(len(graph.edges())), 'task_description': self._task_description, 'graph': graph, 'algorithm': generator_algorithms[ind], 'node_ids': [], } return examples_dict def create_few_shot_example( self, graph: nx.Graph, encoding_method: str, cot: bool ) -> str: """Create a few shot example w or w/o cot for the graph graph.""" name_dict = graph_text_encoders.get_tlag_node_encoder( graph, encoding_method ) question = ( graph_text_encoders.encode_graph(graph, encoding_method) + self._task_description ) triangles_dict = nx.triangles(graph) ntriangles = int(np.sum(list(triangles_dict.values())) / 3) if ntriangles > 0: answer = '%i.' % ntriangles if cot: ntriangles_cot = '' for key, value in triangles_dict.items(): if value > 0: if value == 1: ntriangles_cot += ( 'There is %i triangle including node %s as a vertex.\n' % (value, name_dict[key]) ) else: ntriangles_cot += ( 'There are %i triangles including node %s as a vertex.\n' % (value, name_dict[key]) ) ntriangles_cot += ( 'Summing the number of triangles for all nodes and dividing them by' ' three gives us %i triangles in total.' % ntriangles ) answer += ntriangles_cot else: answer = '0.' if cot: ntriangles_cot = 'No three nodes form a triangle of edges.' answer += ntriangles_cot return question + answer class MaximumFlow(GraphTask): """The graph task to compute the maximum flow from a source to a target.""" def __init__(self): super().__init__() self.name = 'maximum_flow' def prepare_examples_dict( self, graphs: list[nx.Graph], generator_algorithms: list[str], encoding_method: str, ) -> dict[int, dict[str, str | list[int]]]: examples_dict = {} name_dict = graph_text_encoders.get_tlag_node_encoder(None, encoding_method) for ind, graph in enumerate(graphs): graph = add_edge_weight(graph) source, target = random.sample(list(graph.nodes()), k=2) question = graph_text_encoders.encode_graph(graph, encoding_method) task_description = ( 'Q: What is the maximum capacity of the flow from node %s to node' ' %s?\nA: ' % (name_dict[source], name_dict[target]) ) question += task_description maximum_flow_value = nx.maximum_flow( graph, source, target, capacity='weight' )[0] answer = str(maximum_flow_value) + '.' examples_dict[ind] = { 'question': question, 'answer': answer, 'nnodes': str(len(graph.nodes())), 'nedges': str(len(graph.edges())), 'task_description': task_description, 'graph': graph, 'algorithm': generator_algorithms[ind], 'node_ids': [source, target], } return examples_dict def create_few_shot_example( self, graph: nx.Graph, encoding_method: str, cot: bool ) -> str: graph = add_edge_weight(graph) name_dict = graph_text_encoders.get_tlag_node_encoder( graph, encoding_method ) source, target = random.sample(list(graph.nodes()), k=2) question = graph_text_encoders.encode_graph(graph, encoding_method) question += ( 'Q: What is the maximum capacity of the flow from node %s to' ' node %s?\nA: ' % (name_dict[source], name_dict[target]) ) flow_value, flow_dict = nx.maximum_flow( graph, source, target, capacity='weight' ) answer = str(flow_value) + '.' if flow_value > 0: if cot: explanation = ' This is because of the following edges: ' for edge, capacity in flow_dict.items(): for key, value in capacity.items(): if value > 0: explanation += ( 'the edge from node %i to node %i with capacity %i, ' % ( edge, key, value, ) ) explanation = explanation.strip()[:-1] + '.' answer += explanation else: if cot: answer += ( ' Because, there is no path connecting node %s to node %s based on' ' the graph description.' % (name_dict[source], name_dict[target]) ) return question + answer def has_edge_weights(graph): for _, _, data in graph.edges(data=True): if 'weight' not in data: return False return True def add_edge_weight(graph): if has_edge_weights(graph): return graph else: for edge in graph.edges(): graph[edge[0]][edge[1]]['weight'] = random.randint(1, 10) return graph class NodeClassification(GraphTask): """The graph task to classify a given node in the graph.""" def __init__(self): super().__init__() self.name = 'node_classification' self.classes = [ 'soccer', 'baseball', 'tennis', 'golf', 'football', 'surfing', ] def prepare_examples_dict( self, graphs: list[nx.Graph], generator_algorithms: list[str], encoding_method: str, ) -> dict[int, dict[str, str | list[int]]]: classes = random.sample(list(self.classes), k=2) examples_dict = {} name_dict = graph_text_encoders.get_tlag_node_encoder(None, encoding_method) for ind, graph in enumerate(graphs): question = graph_text_encoders.encode_graph(graph, encoding_method) nnodes = len(graph.nodes()) # Sampling nnodes // 2 + 1 nodes. sampled_nodes = random.sample( list(graph.nodes(data=True)), k=nnodes // 2 + 1 ) # Adding the class of half of the nodes. for node_data in sampled_nodes[:-1]: node_class = classes[node_data[1]['block']] question += ( 'Node ' + name_dict[node_data[0]] + ' likes ' + node_class + '.\n' ) # Reserving the last sampled node for the question. task_description = 'Q: Does node %s like %s or %s?\nA: ' % ( name_dict[sampled_nodes[-1][0]], classes[0], classes[1], ) question += task_description answer = classes[sampled_nodes[-1][1]['block']] examples_dict[ind] = { 'question': question, 'answer': answer, 'nnodes': str(nnodes), 'nedges': str(len(graph.edges())), 'task_description': task_description, 'graph': graph, 'algorithm': generator_algorithms[ind], # id of the last samples node 'node_ids': [sampled_nodes[-1][0]], } return examples_dict def create_few_shot_example( self, graph: nx.Graph, encoding_method: str, cot: bool ) -> str: classes = random.sample(list(self.classes), k=2) name_dict = graph_text_encoders.get_tlag_node_encoder( graph, encoding_method ) question = graph_text_encoders.encode_graph(graph, encoding_method) nnodes = len(graph.nodes()) sampled_nodes = random.sample( list(graph.nodes(data=True)), k=nnodes // 2 + 1 ) for node_data in sampled_nodes[:-1]: node_class = classes[node_data[1]['block']] question += ( 'Node ' + name_dict[node_data[0]] + ' likes ' + node_class + '.\n' ) task_description = 'Q: Does node %s like %s or %s?\nA: ' % ( name_dict[sampled_nodes[-1][0]], classes[0], classes[1], ) question += task_description answer = classes[sampled_nodes[-1][1]['block']] if cot: explanation = ( ' This is because most of the nodes that are connected to node %s' ' likes %s.' % (sampled_nodes[-1][0], classes[sampled_nodes[-1][1]['block']]) ) answer += explanation return question + answer ================================================ FILE: talk_like_a_graph/graph_tasks_generator.py ================================================ r"""The graph tasks to be tried with LLMs.. This code loads graphs and creates graph tasks and output them as tf examples in a recordio file in the task directory provided. # Placeholder for Google-internal comments. """ from collections.abc import Sequence import os import random from absl import app from absl import flags import networkx as nx import numpy as np from . import graph_tasks from . import graph_tasks_utils as utils _TASK_DIR = flags.DEFINE_string( 'task_dir', None, 'The directory to write tasks.', required=True ) _GRAPHS_DIR = flags.DEFINE_string( 'graphs_dir', None, 'The directory containing the graphs.', required=True ) _RANDOM_SEED = flags.DEFINE_integer( 'random_seed', None, 'The random seed to use for task generation.', required=True, ) def zero_shot( task: graph_tasks.GraphTask, graphs: list[nx.Graph], algorithms: list[str], text_encoders: list[str], cot: bool, random_seed: int, split: str, ) -> None: """Creating zero-shot or zero-cot examples for the given task. Args: task: the corresponding graph task. graphs: the list of graphs to use for the task. algorithms: the algorithm used to generate the graphs. text_encoders: the encoders to use in the tasks. cot: whether to apply cot or not. random_seed: the random seed to use in the process. split: whether we are creating a train or test split. """ random.seed(random_seed) zero_shot_examples = utils.create_zero_shot_task( task, graphs, algorithms, text_encoders, cot=cot ) file_name = task.name + ('_zero_cot_' if cot else '_zero_shot_') file_name += split + '.recordio' utils.write_examples( zero_shot_examples, os.path.join(_TASK_DIR.value, file_name), ) def few_shot( task: graph_tasks.GraphTask, graphs: list[nx.Graph], few_shot_graphs: list[nx.Graph], algorithms: list[str], text_encoders: list[str], cot: bool, bag: bool, random_seed: int, ) -> None: """Creating few-shot, cot, or cot-bag examples for the given task. Args: task: the corresponding graph task. graphs: the list of graphs to use for the task. few_shot_graphs: the list of graphs to generate few shot examples for. algorithms: the algorithm used to generate the graphs. text_encoders: the encoders to use in the tasks. cot: whether to apply cot or not. bag: whether to apply build-a-graph method or not. random_seed: the random seed to use in the process. """ random.seed(random_seed) few_shot_examples = utils.create_few_shot_task( task, graphs, algorithms, few_shot_graphs, text_encoders, cot=cot, bag=bag, random_seed=random_seed, ) file_name = task.name if cot and bag: file_name += '_few_shot_cot_bag_test.recordio' elif cot: file_name += '_few_shot_cot_test.recordio' else: file_name += '_few_shot_test.recordio' utils.write_examples( few_shot_examples, os.path.join(_TASK_DIR.value, file_name), ) def generate_random_sbm_graph(random_state: np.random.RandomState): # Sampling a small number as the probability of the two nodes in different # communities being connected. small_number = random.uniform(0, 0.05) # Sampling a large number as probability of the nodes in one community # being connected. large_number = random.uniform(0.6, 0.8) number_of_nodes = random.choice(np.arange(5, 20)) sizes = [number_of_nodes // 2, number_of_nodes // 2] probs = [[large_number, small_number], [small_number, large_number]] return nx.stochastic_block_model(sizes, probs, seed=random_state) def main(argv: Sequence[str]) -> None: if len(argv) > 1: raise app.UsageError('Too many command-line arguments.') algorithms = ['er'] directions = ['undirected'] text_encoders = ['adjacency'] # Loading the graphs. graphs = [] generator_algorithms = [] for algorithm in algorithms: for direction in directions: loaded_graphs = utils.load_graphs( _GRAPHS_DIR.value, algorithm, 'train', direction, ) graphs += loaded_graphs generator_algorithms += [algorithm] * len(loaded_graphs) # Defining a task on the graphs task = graph_tasks.ShortestPath() if isinstance(task, graph_tasks.NodeClassification): # The node classification task requires SBM graphs. As it's not possible to # write graphs with data (e.g., blocks data as in SBM graphs), we regenerate # graphs. random_state = np.random.RandomState(_RANDOM_SEED.value) print('Generating sbm graphs') graphs = [ generate_random_sbm_graph(random_state) for _ in range(len(graphs)) ] zero_shot( task, graphs, generator_algorithms, text_encoders, cot=False, random_seed=_RANDOM_SEED.value, split='test', ) zero_shot( task, graphs, generator_algorithms, text_encoders, cot=True, random_seed=_RANDOM_SEED.value, split='test', ) # Loading few-shot graphs. few_shot_graphs = [] for algorithm in algorithms: for direction in directions: few_shot_graphs += utils.load_graphs( _GRAPHS_DIR.value, algorithm, 'train', direction, ) if isinstance(task, graph_tasks.NodeClassification): # The node classification task requires SBM graphs. As it's not possible to # write graphs with data (e.g., blocks data as in SBM graphs), we regenerate # graphs. random_state = np.random.RandomState(_RANDOM_SEED.value + 1) print('Generating few shot sbm graphs') few_shot_graphs = [ generate_random_sbm_graph(random_state) for _ in range(len(few_shot_graphs)) ] few_shot( task, graphs, few_shot_graphs, generator_algorithms, text_encoders, cot=False, bag=False, random_seed=_RANDOM_SEED.value, ) few_shot( task, graphs, few_shot_graphs, generator_algorithms, text_encoders, cot=True, bag=False, random_seed=_RANDOM_SEED.value, ) few_shot( task, graphs, few_shot_graphs, generator_algorithms, text_encoders, cot=True, bag=True, random_seed=_RANDOM_SEED.value, ) if __name__ == '__main__': app.run(main) ================================================ FILE: talk_like_a_graph/graph_tasks_utils.py ================================================ """The graph tasks to be tried with LLMs.""" import os import random import networkx as nx import numpy as np import seqio import tensorflow as tf import tensorflow_gnn as tfgnn # Google-internal import(s). # Internal import. from . import graph_tasks from tensorflow.core.example import example_pb2 from tensorflow.core.example import feature_pb2 def laplacian_pos_embedding(graph: nx.Graph, units: int = 4) -> nx.Graph: """Adds the laplacian positional encoding.""" m = nx.normalized_laplacian_matrix( graph, nodelist=sorted(graph.nodes), weight=None ).astype(np.float32) u, _, _ = np.linalg.svd(m.todense(), compute_uv=True) if units > u.shape[1]: u = np.pad(u, ((0, 0), (0, units - u.shape[1]))) nx.set_node_attributes( graph, dict(zip(sorted(graph.nodes), u[:, :units])), name='lpe' ) return graph def to_tfgnn(graph: nx.Graph, node_ids: list[int]) -> tfgnn.GraphTensor: """Convert a given nx graph to a tfgnn graph.""" if graph.edges(data=True): s, t, w = zip(*[ (s, t, (d['weight'] if d and 'weight' in d else None)) for s, t, d in graph.edges(data=True) ]) else: s, t, w = (), (), () # tfgnn assumes graphs are directed. Adding the rev edges for an undirected # graph. if not graph.is_directed(): s, t, w = s + t, t + s, w + w graph = laplacian_pos_embedding(graph, units=4) features = set(k for n in graph.nodes for k in graph.nodes[n].keys()) # pylint: disable=g-complex-comprehension node_features = { f: tf.convert_to_tensor([graph.nodes[n][f] for n in graph.nodes]) for f in features } # If all edges have a non-trivial weight, then we record the weights. if all(w): edge_features = {'weights': tf.convert_to_tensor(w, dtype=tf.int32)} gt = tfgnn.homogeneous( tf.convert_to_tensor(s, dtype=tf.int32), tf.convert_to_tensor(t, dtype=tf.int32), node_features=node_features, edge_features=edge_features, ) else: gt = tfgnn.homogeneous( tf.convert_to_tensor(s, dtype=tf.int32), tf.convert_to_tensor(t, dtype=tf.int32), node_features=node_features, ) if not node_ids: # No node is mentioned in the task description. return gt node_sets = { **gt.node_sets, '_readout': tfgnn.NodeSet.from_fields(sizes=[1]), } if len(node_ids) == 1: # Tasks requiring only one node id e.g., computing node degree. edge_sets = { **gt.edge_sets, '_readout/node': tfgnn.EdgeSet.from_fields( sizes=[1], adjacency=tfgnn.Adjacency.from_indices( source=('nodes', node_ids), target=('_readout', [0]), ), ), } elif len(node_ids) == 2: # Tasks requiring two nodes e.g., shortest path from one node to the other. edge_sets = { **gt.edge_sets, '_readout/source': tfgnn.EdgeSet.from_fields( sizes=[1], adjacency=tfgnn.Adjacency.from_indices( source=('nodes', node_ids[:1]), target=('_readout', [0]), ), ), '_readout/target': tfgnn.EdgeSet.from_fields( sizes=[1], adjacency=tfgnn.Adjacency.from_indices( source=('nodes', node_ids[1:]), target=('_readout', [0]), ), ), } else: # Raising an error if more than two nodes are mentiones. raise ValueError(f'Invalid number of integers: {len(node_ids)}') return tfgnn.GraphTensor.from_pieces( context=gt.context, node_sets=node_sets, edge_sets=edge_sets ) def create_example_feature( key: int, question: str, answer: str, algorithm: str, encoding_method: str, nnodes: str, nedges: str, task_description: str, graph: nx.Graph, node_ids: list[int], ) -> example_pb2.Example: """Create a tensorflow example from a datapoint.""" key_feature = feature_pb2.Feature( bytes_list=tf.train.BytesList(value=[str(key).encode()]) ) question_feature = feature_pb2.Feature( bytes_list=tf.train.BytesList(value=[question.encode()]) ) answer_feature = feature_pb2.Feature( bytes_list=tf.train.BytesList(value=[answer.encode()]) ) algorithm_feature = feature_pb2.Feature( bytes_list=tf.train.BytesList(value=[algorithm.encode()]) ) encoding_method_feature = feature_pb2.Feature( bytes_list=tf.train.BytesList(value=[encoding_method.encode()]) ) nnodes_feature = feature_pb2.Feature( bytes_list=tf.train.BytesList(value=[nnodes.encode()]) ) nedges_feature = feature_pb2.Feature( bytes_list=tf.train.BytesList(value=[nedges.encode()]) ) task_description_feature = feature_pb2.Feature( bytes_list=tf.train.BytesList(value=[task_description.encode()]) ) gt = to_tfgnn(graph, node_ids) graph_feature = feature_pb2.Feature( bytes_list=tf.train.BytesList( value=[tfgnn.write_example(gt).SerializeToString()] ) ) directed_feature = feature_pb2.Feature( bytes_list=tf.train.BytesList(value=[str(graph.is_directed()).encode()]) ) example_feats = tf.train.Features( feature={ 'id': key_feature, 'question': question_feature, 'answer': answer_feature, 'algorithm': algorithm_feature, 'text_encoding': encoding_method_feature, 'nnodes': nnodes_feature, 'nedges': nedges_feature, 'task_description': task_description_feature, 'graph': graph_feature, 'directed': directed_feature, } ) return example_pb2.Example(features=example_feats) def load_graphs( base_path: str, algorithm: str, split: str, direction: str, max_nnodes: int = 20, ) -> list[nx.Graph]: """Load a list of graphs from a given algorithm and split.""" graphs_path = os.path.join( base_path, direction, algorithm, split, ) loaded_graphs = [] all_files = gfile.ListDir(graphs_path) for file in all_files: if file.endswith('.graphml'): path = os.path.join(graphs_path, file) graph = nx.read_graphml(os.Open(path, 'rb'), node_type=int) if graph.number_of_nodes() <= max_nnodes: loaded_graphs.append(graph) return loaded_graphs def prepare_examples( examples_dict: dict[int, dict[str, str | list[int]]], encoding_method: str, ) -> list[example_pb2.Example]: """Create a list of tf.train.Example from a dict of examples.""" examples = [] for key, value in examples_dict.items(): ( question, answer, nnodes, nedges, task_description, graph, algorithm, node_ids, ) = ( value['question'], value['answer'], value['nnodes'], value['nedges'], value['task_description'], value['graph'], value['algorithm'], value['node_ids'], ) examples.append( create_example_feature( key, question, answer, algorithm, encoding_method, nnodes, nedges, task_description, graph, node_ids, ) ) return examples def create_zero_shot_task( task: graph_tasks.GraphTask, graphs: list[nx.Graph], generator_algorithms: list[str], text_encoders: list[str], cot: bool = False, ) -> list[example_pb2.Example]: """Create a recordio file with zero-shot examples for the task.""" examples = [] for encoding_method in text_encoders: examples_dict = task.prepare_examples_dict( graphs, generator_algorithms, encoding_method ) if cot: for key in examples_dict.keys(): examples_dict[key]['question'] += "Let's think step by step. " examples += prepare_examples(examples_dict, encoding_method) return examples def write_examples(examples: list[example_pb2.Example], output_path: str): with recordio.RecordWriter(output_path) as output_file: for example in examples: output_file.WriteRecord(example.SerializeToString()) def prepare_few_shots( task: graph_tasks.GraphTask, graphs: list[nx.Graph], text_encoders: list[str], cot: bool, ) -> dict[str, list[str]]: """Create a dict of few-shot examples with their cot for the task.""" few_shots_examples_dict = {} for encoding_method in text_encoders: if encoding_method not in few_shots_examples_dict: few_shots_examples_dict[(encoding_method)] = [] for graph in graphs: few_shots_examples_dict[(encoding_method)].append( task.create_few_shot_example(graph, encoding_method, cot) ) return few_shots_examples_dict def choose_few_shot_examples( few_shots_dict: dict[str, list[str]], encoding_method: str, k: int = 2, ) -> str: """Choose few shot examples for each algorithm.""" few_shots_str = '' for _ in range(k): example_list = few_shots_dict[encoding_method] few_shots_str += 'Example: ' + random.choice(example_list) + '\n' return few_shots_str def create_few_shot_task( task: graph_tasks.GraphTask, graphs: list[nx.Graph], generator_algorithms: list[str], few_shots_graphs: list[nx.Graph], text_encoders: list[str], cot: bool, bag: bool, random_seed: int, ) -> list[example_pb2.Example]: """Create a recordio file with few-shot examples for the task.""" # LINT.IfChange vocab_path = None # LINT.ThenChange(//research/graph/llm/graphqa/copy.bara.sky) # Loading the palm tokenizer to calculate number of tokens in the sequence. sp_vocab = seqio.SentencePieceVocabulary(vocab_path) number_of_tokens = {} examples = [] print('prepare few shot task', 'cot', cot, 'bag', bag) few_shots_examples_dict = prepare_few_shots( task, few_shots_graphs, text_encoders, cot, ) for encoding_method in text_encoders: random.seed(random_seed) examples_dict = task.prepare_examples_dict( graphs, generator_algorithms, encoding_method ) for key in examples_dict.keys(): few_shots_examples = choose_few_shot_examples( few_shots_examples_dict, encoding_method, ) examples_dict[key]['question'] = ( few_shots_examples + 'Example: ' + examples_dict[key]['question'] ) if bag: examples_dict[key]['question'] = examples_dict[key]['question'].replace( '\nQ: ', "\nLet's construct the graph with the nodes and edges first.\nQ: ", ) # pytype: disable=attribute-error if encoding_method not in number_of_tokens: number_of_tokens[encoding_method] = [] number_of_tokens[encoding_method].append( len(sp_vocab.encode(examples_dict[key]['question'])) ) examples += prepare_examples(examples_dict, encoding_method) # Printing maximum number of tokens in the sequence. for key, value in number_of_tokens.items(): print(key, np.max(value)) return examples ================================================ FILE: talk_like_a_graph/graph_text_encoders.py ================================================ """Library for encoding graphs in text.""" import networkx as nx from . import name_dictionaries def create_node_string(name_dict, nnodes: int) -> str: node_string = "" sorted_keys = list(sorted(name_dict.keys())) for i in sorted_keys[: nnodes - 1]: node_string += name_dict[i] + ", " node_string += "and " + name_dict[sorted_keys[nnodes - 1]] return node_string def nx_encoder(graph: nx.Graph, _: dict[int, str], edge_type="id") -> str: """Encoding a graph as entries of an adjacency matrix.""" if graph.is_directed(): output = ( "In a directed graph, (s,p,o) means that there is an edge from node s" " to node o of type p. " ) else: output = ( "In an undirected graph, (s,p,o) means that node s and node o are" " connected with an undirected edge of type p. " ) name_dict = {x: str(x) for x in graph.nodes()} nodes_string = create_node_string(name_dict, nnodes=len(graph.nodes())) output += "G describes a graph among nodes %s.\n" % nodes_string if graph.edges(): output += "The edges in G are: " for i, j in graph.edges(): edge_type = graph.get_edge_data(i, j)[edge_type] if edge_type is None: edge_type = "linked" output += "(%s, %s, %s) " % (name_dict[i], edge_type, name_dict[j]) return output.strip() + ".\n" def adjacency_encoder(graph: nx.Graph, name_dict: dict[int, str]) -> str: """Encoding a graph as entries of an adjacency matrix.""" if graph.is_directed(): output = ( "In a directed graph, (i,j) means that there is an edge from node i to" " node j. " ) else: output = ( "In an undirected graph, (i,j) means that node i and node j are" " connected with an undirected edge. " ) nodes_string = create_node_string(name_dict, len(graph.nodes())) output += "G describes a graph among nodes %s.\n" % nodes_string if graph.edges(): output += "The edges in G are: " for i, j in graph.edges(): output += "(%s, %s) " % (name_dict[i], name_dict[j]) return output.strip() + ".\n" def friendship_encoder(graph: nx.Graph, name_dict: dict[int, str]) -> str: """Encoding a graph as a friendship graph.""" if graph.is_directed(): raise ValueError("Friendship encoder is not defined for directed graphs.") nodes_string = create_node_string(name_dict, len(graph.nodes())) output = ( "G describes a friendship graph among nodes %s.\n" % nodes_string.strip() ) if graph.edges(): output += "We have the following edges in G:\n" for i, j in graph.edges(): output += "%s and %s are friends.\n" % (name_dict[i], name_dict[j]) return output def coauthorship_encoder(graph: nx.Graph, name_dict: dict[int, str]) -> str: """Encoding a graph as a coauthorship graph.""" if graph.is_directed(): raise ValueError("Coauthorship encoder is not defined for directed graphs.") nodes_string = create_node_string(name_dict, len(graph.nodes())) output = ( "G describes a coauthorship graph among nodes %s.\n" % nodes_string.strip() ) if graph.edges(): output += "In this coauthorship graph:\n" for i, j in graph.edges(): output += "%s and %s wrote a paper together.\n" % ( name_dict[i], name_dict[j], ) return output.strip() + ".\n" def incident_encoder(graph: nx.Graph, name_dict: dict[int, str]) -> str: """Encoding a graph with its incident lists.""" nodes_string = create_node_string(name_dict, len(graph.nodes())) output = "G describes a graph among nodes %s.\n" % nodes_string if graph.edges(): output += "In this graph:\n" for source_node in graph.nodes(): target_nodes = graph.neighbors(source_node) target_nodes_str = "" nedges = 0 for target_node in target_nodes: target_nodes_str += name_dict[target_node] + ", " nedges += 1 if nedges > 1: output += "Node %s is connected to nodes %s.\n" % ( source_node, target_nodes_str[:-2], ) elif nedges == 1: output += "Node %d is connected to node %s.\n" % ( source_node, target_nodes_str[:-2], ) return output def social_network_encoder(graph: nx.Graph, name_dict: dict[int, str]) -> str: """Encoding a graph as a social network graph.""" if graph.is_directed(): raise ValueError( "Social network encoder is not defined for directed graphs." ) nodes_string = create_node_string(name_dict, len(graph.nodes())) output = ( "G describes a social network graph among nodes %s.\n" % nodes_string.strip() ) if graph.edges(): output += "We have the following edges in G:\n" for i, j in graph.edges(): output += "%s and %s are connected.\n" % (name_dict[i], name_dict[j]) return output def expert_encoder(graph: nx.Graph, name_dict: dict[int, str]) -> str: nodes_string = create_node_string(name_dict, len(graph.nodes())) output = ( "You are a graph analyst and you have been given a graph G among nodes" " %s.\n" % nodes_string.strip() ) output += "G has the following undirected edges:\n" if graph.edges() else "" for i, j in graph.edges(): output += "%s -> %s\n" % (name_dict[i], name_dict[j]) return output def nodes_to_text(graph, encoding_type): """Get dictionary converting node ids to text.""" if encoding_type == "integer": return name_dictionaries.create_name_dict(graph, "integer", nnodes=1000) elif encoding_type == "popular": return name_dictionaries.create_name_dict(graph, "popular") elif encoding_type == "alphabet": return name_dictionaries.create_name_dict(graph, "alphabet") elif encoding_type == "got": return name_dictionaries.create_name_dict(graph, "got") elif encoding_type == "south_park": return name_dictionaries.create_name_dict(graph, "south_park") elif encoding_type == "politician": return name_dictionaries.create_name_dict(graph, "politician") elif encoding_type == "random": return name_dictionaries.create_name_dict( graph, "random_integer", nnodes=1000 ) elif encoding_type == "nx_node_name": return name_dictionaries.create_name_dict(graph, "nx_node_name") else: raise ValueError("Unknown encoding type: %s" % encoding_type) def get_tlag_node_encoder(graph, encoder_name): """Find the node encoder used in the 'Talk Like a Graph' paper.""" if encoder_name == "adjacency": return nodes_to_text(graph, "integer") elif encoder_name == "incident": return nodes_to_text(graph, "integer") elif encoder_name == "friendship": return nodes_to_text(graph, "popular") elif encoder_name == "south_park": return nodes_to_text(graph, "south_park") elif encoder_name == "got": return nodes_to_text(graph, "got") elif encoder_name == "politician": return nodes_to_text(graph, "politician") elif encoder_name == "social_network": return nodes_to_text(graph, "popular") elif encoder_name == "expert": return nodes_to_text(graph, "expert") elif encoder_name == "coauthorship": return nodes_to_text(graph, "popular") elif encoder_name == "random": return nodes_to_text(graph, "random") elif encoder_name == "nx_node_name": return nodes_to_text(graph, "nx_node_name") else: raise ValueError("Unknown graph encoder strategy: %s" % encoder_name) # A dictionary from edge encoder name to the corresponding function. EDGE_ENCODER_FN = { "adjacency": adjacency_encoder, "incident": incident_encoder, "friendship": friendship_encoder, "south_park": friendship_encoder, "got": friendship_encoder, "politician": social_network_encoder, "social_network": social_network_encoder, "expert": expert_encoder, "coauthorship": coauthorship_encoder, "random": adjacency_encoder, "nx_edge_encoder": nx_encoder, } def with_ids(graph: nx.Graph, node_encoder: str) -> nx.Graph: nx.set_node_attributes(graph, nodes_to_text(graph, node_encoder), name="id") return graph def encode_graph( graph: nx.Graph, graph_encoder=None, node_encoder=None, edge_encoder=None ) -> str: r"""Encodes a graph as text. This relies on choosing: a node_encoder and an edge_encoder: or a graph_encoder (a predefined pair of node and edge encoding strategies). Note that graph_encoders may assume that the graph has some properties (e.g. integer keys). Example usage: .. code-block:: python ``` # Use a predefined graph encoder from the paper. >>> G = nx.karate_club_graph() >>> encode_graph(G, graph_encoder="adjacency") 'In an undirected graph, (i,j) means that node i and node j are connected with an undirected edge. G describes a graph among nodes 0, 1, 2, 3, 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, and 33.\nThe edges in G are: (0, 1) (0, 2) (0, 3) ...' # Use the node's name in the graph as the node identifier. >>> G = nx.les_miserables_graph() >>> encode_graph(G, node_encoder="nx_node_name", edge_encoder="friendship") 'G describes a friendship graph among nodes Anzelma, Babet, Bahorel, Bamatabois, BaronessT, Blacheville, Bossuet, Boulatruelle, Brevet, ... We have the following edges in G: Napoleon and Myriel are friends. Myriel and MlleBaptistine are friends...' # Use the `id` feature from the edges to describe the edge type. >>> G = nx.karate_club_graph() >>> encode_graph(G, node_encoder="nx_node_name", edge_encoder="nx_edge_id") 'In an undirected graph, (s,p,o) means that node s and node o are connected with an undirected edge of type p. G describes a graph among nodes 0, 1, 2, 3, 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, and 33. The edges in G are: (0, linked, 1) (0, linked, 2) (0, linked, 3) ...' ``` Args: graph: the graph to be encoded. graph_encoder: the name of the graph encoder to use. node_encoder: the name of the node encoder to use. edge_encoder: the name of the edge encoder to use. Returns: The encoded graph as a string. """ # Check that only one of graph_encoder or (node_encoder, edge_encoder) is set. if graph_encoder and (node_encoder or edge_encoder): raise ValueError( "Only one of graph_encoder or (node_encoder, edge_encoder) can be set." ) if graph_encoder: if isinstance(graph_encoder, str): node_encoder_dict = get_tlag_node_encoder(graph, graph_encoder) return EDGE_ENCODER_FN[graph_encoder](graph, node_encoder_dict) else: return graph_encoder(graph) else: node_encoder_dict = nodes_to_text(graph, node_encoder) return EDGE_ENCODER_FN[edge_encoder](graph, node_encoder_dict) ================================================ FILE: talk_like_a_graph/graph_text_encoders_test.py ================================================ """Testing for graph_text_encoders.py.""" from absl.testing import parameterized import networkx as nx from . import graph_text_encoders from absl.testing import absltest _G = nx.Graph() _G.add_node(0) _G.add_node(1) _G.add_node(2) _G.add_node(3) _G.add_edge(0, 1) _G.add_edge(1, 2) _G.add_edge(2, 3) _G.add_edge(3, 0) class GraphTextEncodersTest(absltest.TestCase, parameterized.TestCase): @parameterized.named_parameters( dict( testcase_name='adjacency_integer', encoding_method='adjacency', expected_result=( 'In an undirected graph, (i,j) means that node i and node j are' ' connected with an undirected edge. G describes a graph among' ' nodes 0, 1, 2, and 3.\nThe edges in G are: (0, 1) (0, 3) (1, 2)' ' (2, 3).\n' ), ), dict( testcase_name='incident_integer', encoding_method='incident', expected_result=( 'G describes a graph among nodes 0, 1, 2, and 3.\nIn this' ' graph:\nNode 0 is connected to nodes 1, 3.\nNode 1 is connected' ' to nodes 0, 2.\nNode 2 is connected to nodes 1, 3.\nNode 3 is' ' connected to nodes 2, 0.\n' ), ), dict( testcase_name='friendship_per_line_popular', encoding_method='friendship', expected_result=( 'G describes a friendship graph among nodes James, Robert, John,' ' and Michael.\nWe have the following edges in G:\nJames and' ' Robert are friends.\nJames and Michael are friends.\nRobert and' ' John are friends.\nJohn and Michael are friends.\n' ), ), dict( testcase_name='social_network_politician', encoding_method='politician', expected_result=( 'G describes a social network graph among nodes Barack, Jimmy,' ' Arnold, and Bernie.\nWe have the following edges in' ' G:\nBarack and Jimmy are connected.\nBarack and Bernie are' ' connected.\nJimmy and Arnold are connected.\nArnold and' ' Bernie are connected.\n' ), ), ) def test_encoders(self, encoding_method, expected_result): self.assertEqual( graph_text_encoders.encode_graph(_G, encoding_method), expected_result, ) if __name__ == '__main__': googletest.main() ================================================ FILE: talk_like_a_graph/name_dictionaries.py ================================================ """Creates a dictionary mapping integers to node names.""" import random _RANDOM_SEED = 1234 random.seed(_RANDOM_SEED) _INTEGER_NAMES = [str(x) for x in range(10000)] _POPULAR_NAMES = [ "James", "Robert", "John", "Michael", "David", "Mary", "Patricia", "Jennifer", "Linda", "Elizabeth", "William", "Richard", "Joseph", "Thomas", "Christopher", "Barbara", "Susan", "Jessica", "Sarah", "Karen", "Daniel", "Lisa", "Matthew", "Nancy", "Anthony", "Betty", "Mark", "Margaret", "Donald", "Sandra", "Steven", "Ashley", "Paul", "Kimberly", "Andrew", "Emily", "Joshua", "Donna", "Kenneth", "Michelle", "Kevin", "Carol", "Brian", "Amanda", "George", "Melissa", "Edward", "Deborah", "Ronald", "Stephanie", "Timothy", "Rebecca", "Jason", "Sharon", "Jeffrey", "Laura", "Ryan", "Cynthia", "Jacob", "Dorothy", "Gary", "Olivia", "Nicholas", "Emma", "Eric", "Sophia", "Jonathan", "Ava", "Stephen", "Isabella", "Scott", "Mia", "Justin", "Abigail", "Brandon", "Madison", "Frank", "Chloe", "Benjamin", "Victoria", "Samuel", "Lauren", "Gregory", "Hannah", "Alexander", "Grace", "Frank", "Alexis", "Raymond", "Alice", "Patrick", "Samantha", "Jack", "Natalie", "Dennis", "Anna", "Jerry", "Taylor", "Tyler", "Kayla", "Henry", "Hailey", "Douglas", "Jasmine", "Peter", "Nicole", "Adam", "Amy", "Nathan", "Christina", "Zachary", "Andrea", "Jose", "Leah", "Walter", "Angelina", "Harold", "Valerie", "Kyle", "Veronica", "Ethan", "Carl", "Arthur", "Roger", "Noah", ] _SOUTH_PARK_NAMES = [ "Eric", "Kenny", "Kyle", "Stan", "Tolkien", "Heidi", "Bebe", "Liane", "Sharon", "Linda", "Gerald", "Veronica", "Michael", "Jimbo", "Herbert", "Malcolm", "Gary", "Steve", "Chris", "Wendy", ] _GOT_NAMES = [ "Ned", "Cat", "Daenerys", "Jon", "Bran", "Sansa", "Arya", "Cersei", "Jaime", "Petyr", "Robert", "Jorah", "Viserys", "Joffrey", "Maester", "Theon", "Rodrik", "Lysa", "Stannis", "Osha", ] _POLITICIAN_NAMES = [ "Barack", "Jimmy", "Arnold", "Bernie", "Bill", "Kamala", "Hillary", "Elizabeth", "John", "Ben", "Joe", "Alexandria", "George", "Nancy", "Pete", "Madeleine", "Elijah", "Gabrielle", "Al", ] _ALPHABET_NAMES = [ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "AA", "BB", "CC", "DD", "EE", "FF", "GG", "HH", "II", "JJ", "KK", "LL", "MM", "NN", "OO", "PP", "QQ", "RR", "SS", "TT", "UU", "VV", "WW", "XX", "YY", "ZZ", ] def create_name_dict(graph, name: str, nnodes: int = 20) -> dict[int, str]: """The runner function to map integers to node names. Args: graph: the graph to be encoded. name: name of the approach for mapping. nnodes: optionally provide nnodes in the graph to be encoded. Returns: A dictionary from integers to strings. """ if name == "alphabet": names_list = _ALPHABET_NAMES elif name == "integer": names_list = _INTEGER_NAMES elif name == "random_integer": names_list = [] for _ in range(nnodes): names_list.append(str(random.randint(0, 1000000))) elif name == "popular": names_list = _POPULAR_NAMES elif name == "south_park": names_list = _SOUTH_PARK_NAMES elif name == "got": names_list = _GOT_NAMES elif name == "politician": names_list = _POLITICIAN_NAMES elif name == "nx_node_name": return {x: str(x) for x in graph.nodes()} else: raise ValueError(f"Unknown approach: {name}") name_dict = {} for ind, value in enumerate(names_list): name_dict[ind] = value return name_dict ================================================ FILE: tutorial/KDD-Tutorial-1-Talk-Like-a-Graph.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "id": "EMeYjNCilDE_" }, "source": [ "This is a noteboook that illustrates how to encode structured data for use in Large Language Models. It is Part 1 of a two part tutorial from **KDD'24**.\n", "\n", "If you find this tutorial useful or want to know more, please consider our publication: \n", "[**Talk like a Graph: Encoding Graphs for Large Language Models**](https://openreview.net/pdf?id=IuXR1CCrSi)\n", "\n", "```\n", "@inproceedings{\n", "fatemi2024talk,\n", " title={Talk like a Graph: Encoding Graphs for Large Language Models},\n", " author={Bahare Fatemi and Jonathan Halcrow and Bryan Perozzi},\n", " booktitle={The Twelfth International Conference on Learning Representations},\n", " year={2024},\n", " url={https://openreview.net/forum?id=IuXR1CCrSi}\n", "}\n", "```\n", "\n", "\n", "\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "5Pt0yWFcnIdy" }, "source": [ "## Tutorial Part I: Text Encoding of Graph Information\n", "\n", "This first notebook focuses on using text serialization methods from **Talk Like a Graph** to perform graph reasoning tasks with off-the-shelf LLMs. This notebook focuses on using static **Gemini** models (which have free API quota), but the techniques (and code) here are generally applicable to any LLM.\n", "\n", "\n", "Notebook Outline:\n", "\n", "1. Setup (Install Dependencies, get Google Cloud API key)\n", "1. Dataset creation\n", "1. Graph-to-Text conversion\n", "1. Evaluation\n", "1. Exercise: *Graph Encoding Challenge*\n", "1. Exercise: DBLP Dataset\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "SAgPqF6SANGs" }, "source": [ "# Setup" ] }, { "cell_type": "markdown", "metadata": { "id": "N6cnEIIlXhCj" }, "source": [ "## Get Google AI Studio Key\n", "\n", "Go to [Google AI Studio](https://aistudio.google.com/app/u/1/apikey) and click on the `Create API Key` button. Follow prompts in dialog to setup an API key with a new Google Cloud Project.\n", "\n", "Then add the API key to your colab secrets (using the side panel 🔑) as in the picture below:\n", "\n", "![colab_secrets.png](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAwwAAALmCAYAAADv1xMrAAAAAXNSR0IArs4c6QAAIABJREFUeF7snQWUXUX29Su4Q3AnBHfJ4O4+uLsPZHC34O4SGJzBfXCXBCc4DAT4A8E1WHDNt34132mqb19/93a/fr3PWlmEvHvrVu2ys49U9Ro5cuRIJxECQkAICAEhIASEgBAQAkKgxyPwf//3f26WWWZph0MvEYYePy4EgBAQAkJACAgBISAEhIAQ8AiIMGggCAEhIASEgBAQAkJACAgBIZCIgAiDBocQEAJCQAgIASEgBISAEBACIgwaA0JACAgBISAEhIAQEAJCQAgUR0AehuKY6Q0hIASEgBAQAkJACAgBIdBjEBBh6DFdrYYKASEgBISAEBACQkAICIHiCIgwFMdMbwgBISAEhIAQEAJCQAgIgR6DgAhDj+lqNVQICAEhIASEgBAQAkJACBRHQIShOGZ6QwgIASEgBISAEBACQkAI9BgERBh6TFeroUJACAgBISAEhIAQEAJCoDgCIgzFMdMbQkAICAEhIASEgBAQAkKgxyAgwtBjuloNFQJCQAgIASEgBISAEBACxREQYSiOmd4QAkJACAgBISAEhIAQEAI9BgERhh7T1WqoEBACQkAICAEhIASEgBAojoAIQ3HM9IYQEAJCQAgIASEgBISAEOgxCIgw9JiuVkOFgBAQAkJACAgBISAEhEBxBEQYimOmN4SAEBACQkAICAEhIASEQI9BQIShx3S1GioEhIAQEAJCQAgIASEgBIojIMJQHDO9IQSEgBAQAkJACAgBISAEegwCIgw9pqvVUCEgBISAEBACQkAICAEhUBwBEYbimOkNISAEhIAQEAJCQAgIASHQYxAQYegxXa2GCgEhIASEgBAQAkJACAiB4giIMBTHTG8IASEgBISAEBACQkAICIEeg4AIQ4/pajVUCAgBISAEhIAQEALNj8DIkSPd3Xff7QYNGuSGDx9ee4V79erlJp98crfaaqu5pZdeuvbvdccPiDB0x15TnYWAEBACQkAICAEh0KII3HHHHe7mm2/uktZtv/32boklluiSb9tHP/vsM09gIDKNyKeffuqmnHLKRopoe1eEoRIYVYgQEAJCQAgIASEgBIRAFQjsueeebsSIEVUUVbiMPn36uAEDBhR+r6oX3nnnHXfqqae6fv36uW233bY0aXj11Vfd2Wef7ZZffnm30UYbNVw9EYaGIVQBQkAICAEhIASEgBAQAlUg8Msvv7hddtmlraj+/fu7SSaZpHDRTz75pLv//vsLvzfuuON6Rbsr5P3333cnnnii++mnn/znl1xyyVKkwcjCr7/+6stZc8013XrrrddQk0QYGoJPLwsBISAEhIAQEAJCQAhUhUCUMBx77LFuqqmmKlz8Pffc466//vrC74099thu4MCBhd+r4gWIwimnnOKGDRvWVlxR0hAlC2OMMYbbbbfd3FxzzdVQFUUYGoJPLwsBISAEhIAQEAJCQAhUhUBPJgxg2AhpqIssUK/aCcPgwYMdLI9YtDnmmMNtvvnmrnfv3lWNK5UjBISAEBACQkAICAEh0CII9HTCkEQaSMTebrvtEnMa6iQLtROGRx991F166aXthjBupcMPP9zhIkE+//xz9/rrr1cyzMcZZxz3t7/9rZKyVIgQEAJCQAgIASEgBIRA5yIgwvA/vOM8DUmkoW6yUDthIMv8ww8/7DDS/vnPf7oFF1zQ/ztJKRdeeGElo5Gjo4477rhKylIhQkAICAEhIASEgBAQAp2LgAjDX3jnIQ2dQRZqJwz77LOP+/rrrzuMNI6JWmqppUQYOncO6mtCQAgIASEgBISAEGhqBKoiDCjSQ4YMydXWb7/91r388sv+2a5Meo6rbBppeO211/yJTnYaUlUJznH1qDWH4aKLLnJPPPFEu++OOuqojox3LqRA6KCbbropV4dmPTTZZJM5vBddKXTaAw884B5//HHHxRuESfXt29cfaTXzzDN3ZdX0bSEgBISAEBACQkAINDUCVRGGIo0kNP6kk05qSsJApeJIwzzzzOPeeOONTiEL1KFWwvDdd9+50047zb333nu+E0YbbTSf9LzMMssU6cdu8+x///tft/vuu7t33323Q51HGWUUt80227iDDz7Y8XeJEBACQkAICAEhIASEQHsERBjiR0QcabAn6/Qs2DdqJQx85M8///RJzZAHLOxlLt/oDpOJyzbWWWcd98033/jqcp03bf3xxx/9H5Mtt9zSHXnkkd2hSQ3XEU/S0KFDfTlXXnllw+WpACEgBISAEBACQqC1ERBhSO5fSAO5uh999FG7h/bee28399xz1zowaicMtda+iQqns2655RZfo5VWWsmfBDX11FO7P/74w919993uwAMPbCMOhGAtsMACTVT7eqqy6aabuqefftoXzlXnEiEgBISAEBACQkAIpCEgwpCMDpEs5Cz89ttv7R7KOnK1ihEnwlAFis75U5/wLpBH8dhjj7nRRx+9Xcm33nqrv71viy22cJtssombcMIJK/py8xYjwtC8faOaCQEhIASEgBBoRgREGOJ7JYks2NN1k4baCQMhSXyEkKSZZpqpJS9to42zzDKLGzlypJt99tndXXfd1aG3eQbJyl/48ssv3fPPP+/dTRNMMIGbccYZc3sjvv/+e/fMM8/4d8kXmWaaafy9FGT8h0I9cWshxL3x7M8//+yJDona3JWx/PLLd2jDCy+84N5++233ww8/eO9Jv3793MQTT9zhOQvBghgxwBH7L38fa6yxOuAwfPhw99RTT/l7OcYcc0w366yzehJGkrxECAgBISAEhIAQ6BkIiDB07GdOfDrrrLPaPAsYpXfYYQd/MfKwYcPaXqiTNNRKGFBgTz/99LbG0EBi+JdccsmWG/XLLbecT+6GEHA61LLLLluojSjwxx9/vLvhhhsckyUUyAh3WjAQ4oTnTz75ZHfttde2y5fg2YkmmshxjC2nR5FXgXA3xtJLL+3/TijVfPPN55Oxw5i4MISIE5+OPvpo9+abb7b7PGRjww039O8aKaEu3OidJnhbyO5HeJ6yr7vuOh++FQr3atDuVVddtRCWelgICAEhIASEgBDongiIMLTvtziysNtuu/mchTz3NFQ1CmolDBdffLE/XjQUrNnHHHNM27Gqr7zyivvPf/5TSXtIMu7fv38lZRUt5Oqrr3aHHnqofw3SsMEGG3hFfbbZZsssCmJFqJKdAWyKPgPByAOW9lNPPdX9/e9/b1cenhtOngot+HEfXGuttdyZZ57pfwoJw2qrreaPgY3GwxlhoG/233//NmUe0jfeeOO1u1+DQYvCD2koShh22WUXd++997bhxnG7I0aMaCM+kBzqzbG0EiEgBISAEBACQqC1ERBh+Kt/08iCPdVZpKFWwtDTLm5Dsb7xxhvbzWSs7eutt563xBNiFCf77bdf210UKPYHHHBAW8I0CvtRRx3lIBXjjjuudz8RamSC58BCoKabbjq31157+TAkJtygQYO8h8dChEiUWWONNdoRBisHIsK3UdAJidp33319+BGKOmXhqYDorbLKKj5MCG/EYYcd5r+BbL311j7Rm9Cr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lQiIMFSJpsoSAkJACAgBISAEhIAQEAIthoAIQ4t1qJojBISAEBACQkAICAEhIASqRECEoUo0VZYQEAJCQAgIASEgBISAEGgxBEQYWqxD1RwhIASEgBAQAkJACAgBIVAlAiIMVaKpsoSAEBACQkAICAEhIASEQIshIMLQYh2q5ggBISAEhIAQEAJCQAgIgSoREGGoEk2VJQSEgBAQAkJACAgBISAEWgwBEYYW61A1RwgIASEgBISAEBACQkAIVImACEOVaKosISAEhIAQEAJCQAgIASHQYgiIMLRYh6o5QkAICAEhIASEgBAQAkKgSgREGKpEU2UJASEgBISAEBACQkAICIEWQ0CEocU6VM0RAkJACAgBISAEhIAQEAJVIiDCUCWaKksICAEhIASEgBAQAkJACLQYAiIMLdahao4QEAJCQAgIASEgBISAEKgSARGGKtFUWUJACAgBISAEhIAQEAJCoMUQEGFosQ5Vc4SAEBACQkAICAEhIASEQJUIiDBUiabKEgJCQAgIASEgBISAEBACLYaACEOLdaiaIwSEgBAQAkJACAgBISAEqkSgywjDp59+6l5//XX3zTffuB9//NGNO+64buKJJ3Zzzjmnm2SSSapso8oSAkJACAgBISAEhIAQEAJCoCQCnUoYRo4c6Z599ll36623uo8//jixyn369HHrrruum2eeeUo2S68JASEgBISAEBACQkAICAEhUAUCnUYYfvjhB3feeee51157LXe9F154Ybfddtu5McYYI/c7elAICAEhIASEgBAQAkJACAiB6hDoFMLw7bffuhNOOMF99tln7Wreu3dvN8MMM7hxxhnHff/99+7dd991I0aMaPfMjDPO6Pbbbz831lhjVddqlSQEhIAQEAJCQAgIASEgBIRALgRqJwy//fabJwvDhg1rq1Dfvn3dRhtt5GadddZ2lSRk6ZVXXnHXX399u5Cl+eef3+22226uV69euRqlh4SAEBACQkAICAEhIASEgBCoBoHaCcN//vMfd/vtt7fVdplllnFbbrmlG2WUURJb8Ouvv7rzzz/fvfDCC23PbL311o53m10Ivfruu+8c3pMxxxyzy6tLrsipp57qBg8e7BPMJ510UgcBow+WWGKJLq+fKtB8CBxyyCGeuB999NFuvvnmq6yCb775ptt3333dLLPM4sekxLmvvvrKTTDBBG600UYTHAECGI+++OILN/nkkwuXHoRAFfNhgw02cOgQGB7zRCaQV3nUUUf5fZH/NqNQx7POOss9//zzvm1TTz21W3TRRd0uu+ziozRMfv75Z2+MZT25+eabm7Eppeuk/aM0dJW9WCthQHHef//93S+//OIrXMRT8Pvvv3vPxDvvvOPfnWiiidyJJ57oRh999MoaX1VBX3/9tbvkkkvcDTfc4D7//HNf7Kijjur69evn+vfv75ZaaqmqPlWonC+//NKtueaaPhSMhXO66abzoV+cUHXAAQe4nXbaqVB5erhnILDpppu6p59+2l1xxRWVkkoMAOuvv76be+653W233dYzwExpJSSeHC1yta655poej0cIwMknn+xz3vbYYw//R9L6CFQ1H2affXavVP/3v//14c5Z8tBDD7kddtjBLb744u7KK6/MerzTf3/qqae8ge+PP/5wE044oZtiiik8mUbvuOWWW9y8887bVidOnGR9Rf9AuWsl0f7R9b1ZK2EYNGiQu/zyy30rSVxG4WfA55UPPvjAHXHEEQ5rE7Lnnnu2mxx5y6nzOUKttt12W/f+++97r8IiiyziPQsAawneBx54YJco56eccoo799xz3d/+9jd34YUXtmEPgSC8S9a7OkdG9y27JxMGrFh4PwibhFTXKffff7/beeed/ZrGxi/5C4FjjjnGG2EwarB+SsojwBhDBg4c2NSerKrmQ6sRBjwmeBbQMw4++GBPBpA33njDr1OhEbUVCEPSeBVhKL8GVPVmrYQBF9qLL77o67rYYou5HXfcsXC9TzrpJH9fA7L88su7LbbYonAZdb1AfgYWfEBkUh955JFu7LHHbvvcPffc461jeEvwPiy44IJ1VSW23M0339w9+eSTnjSsuuqqnfptfaz7ItCTCQPWvM0228x7QzvDpY9RhDDBcN3oviOnuppjTcUIQ7hFWvhqdV9s3ZJQKpGhQ4c2RZhsGtJVzIdWIgx//vmnm2222bx34bnnnvNGyTRpBcKQNF5FGLp+jaqVMBAL/cknn/hW4npfcsklC7eY0AWzvuFq23vvvQuXUdcLl156qY/zRrm48cYbYze2448/3lv3V1llFe9iTxJIRdE4ZhYRNtOkZPC11167LYkcL0NRoXyzZhR9N+/zfIP6xykFLJZIZykMWXjmbVPac3jLaFcRXHme9/K+U7Qd0T7ISxiK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9EuvXr1yFZdVzzyEIWsORCuSNX6KzL9cjdRDQkAICAEhIAS6IQIiDA10GgoMytA111zjzjjjDHfLLbe44cOHu/HGG8+tvvrq7rDDDnPjjjtuhy8QDnPZZZe5oUOHul9++cVNN910Pixmp512cmOPPbZ//s4773RnnXWW++mnn9yHH37oxhprLP8cSii/hXL33Xe7Cy64wJeH0jXbbLM5lPBoqI0RhvPOO8+9/vrrPhb8k08+8XH6K664ojvwwAPdlFNO2aG+n376qTv33HPdvffe67744gtfR5KT//nPf7oFF1wwFsEXX3zR8R2s6CNGjHATTzyxW2GFFdyee+7ppppqqnbvJBGGJ554wh111FFujDHGcKeddpqbeeaZ/XsQhOuuu879+9//9kmwKH2GIR4DsEqS448/3g0aNMh9+eWX7quvvnK9e/d2k046qW8HvyEWkkTdL7zwwlz9Cu7gee211/p+QFkGy7XXXtvttttubbkQEAW+S5/St9NMM43/jb7htzg5/fTT3T333OPWXHNNX1YofJck7R9//NHngMw444z+5yJ9Bo5XX32192RtvfXW7cpnnOyxxx5+TDEes4R6nHPOOR4z6gBhIEGZ9q233nodXoeIXXzxxb4/P/jgA0+2F1poIbfXXnu5BRZYoN3zaYQh7xygQMYL7eXP22+/7fuKsbXVVlt5DJjTReZfFib6XQgIASEgBIRAd0dAhKGBHjTFknAhFCuUTpSP559/3v93lVVW8UpzKJxQdOWVV3qldokllvDKIooxCuw888zjrrrqKk84HnroIXfJJZd4ZZtET/5t3nnn9QoYCp4JJ8acf/75/neUeBQwFF2ICEpXqGAaYUCRRfGn3uOPP7574YUX3LfffuumnXZad+utt3ol2uSNN97wihTPzzLLLD7e/qOPPnLPPvus92ScdNJJHYjJbbfd5vbdd19PXhZeeGFPEGjDW2+95cumjWBnEkcYwJDv0g4UULA0OeKII9zll1/uydiSSy7p/xkMv/vuO58MC4FL8jgMHDjQPfnkk1455Q8K+wwzzODmnntuT5gQ61cU3WHDhmX2KwRm11139YSK9lmdHnvsMff11197DFBOwQuS9c0333g86Ns555zTTTTRRG7DDTf05CJOIDjbbbedJyCPP/54O4s9bdl8883d9NNP74kQUrTPyKcAl/79+ztO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CJFJmMNQB/AhYeAIVo5ilQgBISAEhIAQEAJCQAgIASHQOAK1EAZyEHbYYYe2PIVDDz3U9e3bt/HaJpRAvgPHtCJ2b0NtH1PBQkAICAEhIASEgBAQAkKgByFQC2EAPzwMeBqQuLsTqsKY41k5kYmL3ZD+/fu7fv36VVW8yhECQkAICAEhIASEgBAQAj0agdoIw6BBg9zll1/uweW251122cXNO++8lYLNfQ/nnHOOGzZsmC+3T58+7sADD4xNoq70wypMCAgBISAEhIAQEAJCQAj0EARqIwzgx2Vrt9xyi/vxxx89nFNOOaUbMGCAG2ussRqG94YbbnD33HNPW9jT/PPP77bddls3/vjjN1y2ChACQkAICAEhIASEgBAQAkLgfwjUShj4ADc7Dx061OcYcAfD6quv7kYfffSG8efI1S+++MJNOumkPj9i8sknb7hMFSAEhIAQEAJCQAgIASEgBIRAewRqJwwCXAgIASEgBISAEBACQkAICIHui4AIQ/ftO9VcCAgBISAEhIAQEAJCQAjUjoAIQ+0Q6wNCQAgIASEgBISAEBACQqD7IiDC0H37TjUXAkJACAgBISAEhIAQEAK1IyDCUDvE+oAQEAJCQAgIASEgBISAEOi+CIgwdN++U82FgBAQAkJACAgBISAEhEDtCIgw1A6xPiAEhIAQEAJCQAgIASEgBLovAiIM3bfvVHMhIASEgBAQAkJACAgBIVA7AiIMtUOsDwgBISAEhIAQEAJCQAgIge6LgAhD9+071VwICAEhIASEgBAQAkJACNSOgAhDhRB/9dVX7tdff3WTTjqpG2200SosWUUJASHQHRD47bff3Pfff+969+7dHaqrOgoBISAEhIAQyIWACEMumJIf+vrrr93ZZ5/tbrvtNgdhQEYZZRS38MILux122MEtv/zyDX5BrwsBIdBdENhggw3ciy++6K644gq32GKLdZdqq55CQAgIASEgBFIREGFoYIB8+OGHbqONNnKffvqptyguuOCCboIJJnDvvfeee+GFF9zIkSPdTjvt5A488MAGvtJ1r7755pvu1FNPdX379nUHHHBA11VEX25KBG6//XZ3xx13uFVXXdWtu+66TVnHPJVijDPWd999dzfXXHPleSXxmTXXXNO99tpr7pJLLnHLLrtsQ2XpZSEgBISAEBACzYKACEMDPbHNNtu4Rx55xK200kru9NNPd+OMM05baRCGrbfe2ocnXHjhhW6FFVZo4Etd8+pTTz3lNttsMzf//PO7m2++uWsqoa82LQJnnnmm488//vEPt//++zdtPbMqtummm7qnn37aXXrppW6ZZZbJejz19x9//NHhdZxmmmkaKkcvCwEhIASEgBBoJgREGEr2BorBvPPO6/788083aNAgN/3003co6YILLnAnnHCCtzRicYwT3kcIY8ojf/zxh3+2V69eeR53v//+e2I+Rda3qyIMxHWPPvrouepL+2hbXjwoNK0deHkoM29OCc+OOuqoueqa9lAa7tH3yrS56Djgm0X7IWucVUEYiuBEX9LXRfsnC6sihKEIhmnjg3bQnqJtCcskX2qMMcbIPVaL4ld07lCRIv2Ztf5EG1a0vbmB0YNCQAgIASGQiYAIQyZE8Q8MHz7c5ykgzz33XGyS4+uvv+49D5NPPrk7+uij2xV09913OwjF0KFDvUI722yzue233z42tOPbb791559/vs+TIPwJRa5fv35ut912c4svvnhbuXxvjz328ESG0IhjjjnGvf32226fffZx/fv3b3su69t33nmnO+uss9xPP/3kCLsaa6yx3HTTTeeVbn5LEvs+uBCKdeKJJ3oyBbmacsopvceFf4+Sne+++863j/AWvofMPPPMDg/OJpts0u5z11xzjbvssst8OdQHDN944w1f5txzz+1DpxZddFH3zDPPuDPOOMP3DYoG9ef72267bYfvowRefPHF7rrrrnMffPCBJzcLLbSQ22uvvdwCCyyQe4TgbaIdL730km/zFFNM4dZaay235557tvM+UWCRNh966KG+PfTJs88+69v/zjvveGWTOPkBAwa4mWaaqcM4KNoPv/zyiye2N954o3v//fd9eYTobLXVVm699dZrK//444/3/frll1/6vB3C8Uj0JySP37KEMTlw4EA3ePBgb40fb7zxPKlmnM4wwwwdXif0iTYzV6gjfUkIFGNg7LHHbvd8EawgCtSfMcdYxyuAl5Bxx28mTzzxhPcSDhkyxD9H2CGeiP32289NO+207b5PWBPj8bTTTmsLb7r++uv9+Npxxx39N1gT8ECiMDPvKWe55ZbLgs3/zvg85ZRTPHYjRozwc5N+5rvgb2JzEVxXWWUV/w5jh7WGuYVXKC6MjN+p77XXXuvxhgAwd9dee22/3oReVPtWkXHPO1nrTwhE3vbmAk8PCQEhIASEQGkERBhKQof1bemll3YfffSRV6iOOOKI3CWhSKNYoigtssgi3vJLSATKEEoqG7MJxATlBSULLwYK7P9j7zzApCi2NnzIIJKTgCiSBBHRa1ZMmDNerjl7zVkvKOacc06Yc46YMCfMWUEFQVGQnDPs/v9ba429w4TuCTuzO995Hh5gprq66u2amfqqzjk1adIkVx5jB2PPPfd0/2YSMnDgQDd5Q2TwY8/khuBrLxjC3Putt95yE0cmJN9//71rJyKECer999+ftJ/+/rQTVywm8X369LEpU6bYDz/84K5jcsuEzBuTTuJAxo4da6uttpoTQjNnznQTUtpPu5lIerv55pvdZIyJGuxxl6KP1A8rVlyZoDNBYsLOhAyRxQQKY0J52GGHxepbuHCh+z+7KUyMmHRR/ssvv3TCAQ6bbrpp2mdLuUsuucSJGEQczJig8ax69uzpJmG8hkXts3d949kTUNu3b19r0qSJfffdd44V7X7jjTdik7lMnsO8efPcOOZaxg9CBNHz4YcfGowYg/QPY7I/YsQIN3nlD5NgJvoItnTxOggfeHM/+sNYYWJKDAF9QhCuscYaMd6Ml4ceeshNjHkOTFiZwMOQsfXwww/HuHJRFFbHH3+848cYZ6xz3+bNm7vPExNkDDclxD7PFZGAOGJsINjghIiHv7fddtvN1cfzXm+99dzLt99+u1155ZXuM8Q4pc/wGj16tBMrfK6effZZxy+V8Rnh802bKUtsEe3gftTBd4pPsuDHAJ8BRFH79u2dUEAIcl/snHPOcQLaG99pxx57rL322muun/369XNvffDBB07YIUweeeSRSrt/UcY9dYX5/vHtidLftB9QFRABERABEciKgARDFvj4YeUHlh9aJrrsELCix+QmmTEZZwLfuXNnNxHq0KGDK8qElokKk7PXX3/dTZ4xJsysyO29995ux8C7MLCqx/2YIPPvVq1axQQD1w0YMMBNjlu2bBlrStR7R3VJ8pMUbki2GCZaDRo0cPcfOnSoXXrppa5fb775ZqxN5513nssow2rnVVddFZuMMCmjzxgruz5NpRcMTBy5zu8AsPILVyayGDsTF154YcwVyU/84M0EyBttom3bbbed25Hwz45VdvzyESZvv/12StcRJrxMFGkTuxQIBIwJN+OD58Pq8hlnnOFe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0NO1IiACIiACIiACIiACIlDDCUgw1PAHrO6JgAiIgAiIgAiIgAiIQDYEJBiyoadrRUAEREAEREAEREAERKCGE5BgqOEPWN0TAREQAREQAREQAREQgWwISDBkQ0/XioAIiIAIiIAIiIAIiEANJyDBUMMfsLonAiIgAiIgAiIgAiIgAtkQkGDIhp6uFQEREAEREAEREAEREIEaTqBKBEP54vm2+Kc3bdFPb1rZjN+tbMFMq9WwidVt29Ma9NzW6vfYyqx2nRqOWt0TAREQAREQAREQAREQgepHIL+CobzM5n801Oa9dqmVzZ2SlE6dNt2sye5XWIM1dqh+BNViERABERABERABERABEajBBPImGMoXzbVZD//XFn3/Ujh8tWpZ4/7/sxV3PFe7DeGIqZQIiIAIiIAIiIAIiIAI5J1AfgRD2VKbcfuutnj0e/90oHYdq99tc6u32sZWe4UWVjZ/hi0Z+7Et/uUds/KyWLlGGx9mTfe8Ke8d1w1EQAREQAREQAREQAREQATSE8iLYJjz/BCb/+4/k/56q65vTfe53eq267lci5b+NdJmPXiQLZ34Y+y9pvveYY3WPyB961VCBERABERABERABERABEQgrwRyLhiWTRltU69Y16xsqWt4/e5bWvMjnrZadRsm7Uj5glk2/cb+tnTSqJx2tu1lk6xWgxVzWqcqEwEREAEREAEREAEREIFSIpBzwTDn2UE2//3bHMPajVtaqzO+dS5I3pb88bUtmzra6rZfs9KOA2Jh2tUbmS1bkjP+Egw5Q6mKREAEREAEREAEREAESpRAzgXD1It727Lp4xxOApgbb3t6BdryMhcEvfDLJ2KoV9jiBGuy++Wx/8965Ahb+PkjOXsUEgw5Q6mKREAEREAEREAEREAESpRATgUD5y1MHtImhrLV/z6yuh37uv/P/+AOm/PMqcthbn7Y49ZgzV3c66ReXTZjfMaPgjMeZt63f+x6CYaMUepCERABERABERABERABEXAEcioYlk3/zaZevEYMbZuLxju3JGzmvfvYou9eXA77Cv2Otib/viYnj2Pp5J9t2uXrSDDkhKYqEQEREAEREAEREAEREIEcC4ayOZNsynldYlxbn/uz1Wne0f1/9mNH24JPH1yOeePtzrAVdzg7J89CgiEnGFWJCIiACIiACIiACIiACMQI5HSHwcqW2aTTWsYyJDU/4hlr0Gt7d7Mlv39h02/cyijjjQxGrQZ9bHVareZeWjTy9VAHvTVce6DV777F3/V+bgs+ecD9m2xLC79+Kla/XJI00kVABERABERABERABEQgOwK5FQxmNuOW7W3xmA9cqxqu/R9rdtD9sRYuHvWGzRl2ri2bOsbqtu9tTQZcafVWWS/2/vTrN3fCIp01GXCFrbD58a7Ywi8ec8HUiUyCIR1JvS8CIiACIiACIiACIiACqQnkXDCQUpXUqs5q1bIWRw+L7QakasqCEffY7CdPCPW8JBhCYVIhERABERABERABERABEciaQM4FQ/nShTbt0rVs2cw/XeM4gwHXpHqrbpC0sYtHDbeZd+9l5csWuzJ12nS3+l02qVR+0Y+vGjESWDLBUKthU2vYd4/YdU0GXme16jbIGpIqEAEREAEREAEREAEREIFSJZBzwQBIYhFmDh3ozl7AmLSvsPlx1mjTI61Oi04x1stm/mEL3r/N5r17Uyy2oVajZtbq1A+sTqt/gqe5YMYtO9jiMe+nFAx123a3VkO+LtVnqX6LgAiIgAiIgAiIgAiIQM4J5EUw0ErnmvTcYLPy8kqNrtN8Zau1QnMrnz/TEAxBq1WvkTU75BFr0Gu75ToaFAzUUXvFivMeyubPiB0UJ8GQ8/GhCkVABERABERABERABEqcQN4EA1wXfvOsS6davmhuWszsKDQ/9FGr22HNhGWDgiFZZRIMaTEvV+C0006zUaNGuddPPvlk69+/f/RK8nBFsbYrD11VlSIgAiIgAiIgAiJQ1ATyKhjcDsDcKTZv+JW28MvHrWzetOVgcE5Do40OsxW2ON5Is5rMilUwXHnllfb444+7Zjdv3tyef/55W3HF5fux/fbb29SpU125iy++2HbccceiGBj//ve/7euvK9y46Mt//vMftasoCKgRIiACIiACIiACIlAcBPIuGGLdLC+zJX98bWUzxlvZgplOHNRt18vqrtTLZVNKZ4t+GGZlsyamLIarE6lcq9LOO+88e/DBfw6kO+SQQ+zcc89drgkbbLBBTDBce+21NmDAgIybecstt9iIESPc9Uz4+ZOpSTBkSk7XiYAIiIAIiIAIiEBpEKg6wVBDecYLhjp16rhdhjXWWKNSj3MpGE488UR76aWXXP0nnXSS+5OpSTBkSk7XiYAIiIAIiIAIiEBpEJBgyPI5xwsGqltnnXXsqaeeslqBnZOwgqG8vNzKysoM4ZHMogqGpUuXWt26dRNWl0wwLFu2LGUbgpWFaXP8zam/du3alRgFyxSrkMlyuOhyERABERABERABEah2BCQYsnxkiQQDVV522WW29957x2pPJRgWL15s9913nz399NM2btw4Y4K/8sor20477WRHHXWUi43AXnvtNcOd6a+//rI5c+a411q2bGmtWrWy/fbbzw4++ODY/d577z0bOnSoi0+YO3euK0dAM7sRHTt2jJULTszPPvtsFwD98ssv24IFC6xTp0520EEH2aGHHrrcxD5sm4N4afddd91lr776qk2aNMnV2bNnT9trr71c+4MiKZlgoF+XXnqpIVKwI4880gYOHJjlU9TlIiACIiACIiACIiACyQhIMGQ5NoKCoVmzZjZr1ixXY4sWLeyNN95wf2PJBAMTfyb6PvA4vjkIh0cffdRN8gmuPuOMMxK2+LjjjrP//e9/7r0bb7zRrr/++oTlaM9jjz1m3bt3d+8HJ+b169c3hEC8HXbYYYaY8Balzf6aH3/80Yjv8IHf8ffYfPPNnZioV6/ecu3ywdg///yzC8pGAGG77rqr62dwJyfLx6nLRUAEREAEREAEREAE4ghIMGQ5JIKCYauttnK1vf322+5vVs4vv/xy9+9kgoH0obgvYUzYyZ7UoEEDtwo/e/Zs9/raa6/tdh9Gjhxpb731lg0bNsx++ukn997GG2/s/qy//vq24YYbGivwTMy9bbHFFtalSxd799137ddff3Uv9+3b15599tnlJua8sOWWW7ryBFVzP4wJ+YsvvhiLy4jSZq5FhOy88842ZswYV1/btm1t2223tWnTptnrr7/uXLAwdguGDBmyXLsQDPRjjz32sAkTJrj36S/B5jCTiYAIiIAIiIAIiIAI5I+ABEOWbOMFw/nnn2+kUF24cKGbaD/55JP2r3/9K6FgmDJlipvs+wnz3XffbV50sJrOJBtff+zhhx92ZbFUMQz77ruvffLJJ65ccGcAFyMm6X7CTdA0gdnBHQZcj8455xx3LZN83mNnAPvvf/9rZ511lmXSZgTOCSec4Opp0qSJc61aaaWV3P8feeSR2O5Fw4YN7fPPP7cVVlihUrsuuugix/Hbb7911yBoEFneVSvLR6jLRUAEREAEREAEREAEUhCQYMhyeMQLBib9N998s4s1wHr16mUvvPCCm+x7dxyfVvWZZ56xQYMGuXJdu3a14cOHV2oN8Qv+tWOPPTZWNplgmDdvnts98AKEWIRVVlklVicTfjI4YZwFQdxAquBi+nLJJZe48uyQ4MqUSZuDOxL777+/IQC8IYioe8aMGe6lBx54wPr161epXexITJ482b1PvAa7LcF+ZfkIdbkIiIAIiIAIiIAIiIAEQ/7GQCLBsGTJEtthhx1s7Nix7sas2t92223LCQbOU7jmmmtcmW222cbuvPPOSg294oor7I477nCv7bbbbrG4hGSCAfXH7kYYO+WUU9yqfyrBgFsUQgVjVZ+YjEzafOCBB9qHH37o6iEWgp2PoOFq9M0337iXCGjeZ599KrUrWLZPnz5OtKTKIhWm/yojAiIgAiIgAiIgAiIQjoB2GMJxSloqkWCgMBNkJsoYJz+z6j9//nz3f7/DQMAuAcrYLrvsEvu3v9l1111nN910k/tvGMHw3Xff2e677x6qRz5IOpVgYHeDXQ6sc+fOLn4ikzYjAD799FNXD7sL7DIEjWxSn332mXspnWCgzODBg+2YY44J1U8VEgEREAEREAEREAERyI6ABEN2/CyZYKDa4E5A8DZeMBCX4GMGOLsBV5ugkfXIByf7GIL4eoMHt02cONE23XTTWBVc265du4Q9RMTwJ5VgwAXpzDPPdNcTeM3KfiZtZieDOAbs6KOPNlyUgkabaTtGpqStt956uexNuE+RehYjkxJB2D169Mjy6elyERABERABERABERCBdAQkGNIRSvN+KsGA3z2uRj4NqK/KCwYCm3FdwgiQxgXIpzslQxIB0N63H5cm726ESGDCjAVjG/g/6Un/+OMP9158OlRe4zom5AQWY0HBgEBh58Ebk3uyGGHsErD6n0mbiUsgGBwjPeybb74Zy25EkDPZpDDcjNiJIPVrsF2IlsMPP9ztdviYjjXXXNOJKbkmZTmAdbkIiIAIiIAIiIAIpCEgwZDlEEklGKj63nvvrRTky2teMPDvYFYjgnuZGJNW9aGHHjIeDsYBargD+ckxAcv33HOPe49zGrhmtdVWs80228wFDfvJOe9vt9127sA2XKKIQaAeRAUBzdQXnJizcs9Oxuqrr+7Sqj7xxBMxOj4YOZM2I364ZzBNLOcpzJw508Vt+NcHDBgQCxZPtPPBwW/0xwswH4eR5SPU5SIgAjkgcMMNN7jFAIy4JLKuyUSglAj8/vvv7uBTYv74rZKJQE0iIMGQ5dNMJxjIAkRcgU9PGi8Y+ILZc889XbrSRMZOAJN1UrN645wHJvZB4x7EPHACMhNpMjMlM05X5hA4UpwGJ+YIlUWLFi13GWczeIHCm5m0mZ0Bdi84xTqRESOBS5Y/6C6Zq1TQJapu3bou6xOZqDI1RAs7Lt5oY3xQNu/RNnZYMATcc889l+ktdZ0IVBkBvif8oZBkFvMujvENoEzwO4WManwGoxixRd6tkkUM784YpQ6VFYFcEMDt1YvXTTbZJBYLmIu6k9XBby/utSxsYSQsIZW5TARqCgEJhiyfZDrBQPVfffWVO6GYLxQsuMPA//Hfv+yyy5z7jz9pmdV/vuj40WXFP96uuuoqtzrvz2ngx/3qq692xbgP5xsQD8Dk3huTcdpBbEXjxo3dy8GJOcKEegmexmgDQuTCCy+MuTD5ujJp8xdffOEOsvvyyy9jLBo1auQCuvmC92Ihvl3+pGffN1yYqAvjLAkm74iHTAyXr3XXXTd2KQKNnRh/ToR/IyhUmHi98847mdxO14hAlRIg7ofPrzcWG1ZdddWE3ye4PfrPPWe5tGzZMlJbJRgi4VLhPBGYM2eOS9XtF7/4HWPHvHXr1jm54/HHH+92xzGEtV+w4rd4rbXWMs48wnwCj5zcVJWIQBEQkGAogofgm8AXzfjx450IwNWIHYBURtYlyuNKxCQgkT8/qx2c/9C0aVMXPxDG5586+dKlDVyXyqK2mbqYpCM4fLsLeVpzvGCgfcSK+MmTBEMRfUDUlMgE4g9aTJTWmEqJpSI+CeNUdVwpo5oEQ1RiKp8PAsHDQH39Pg4uF/dDjPgzlbjXRhttFKsWl18Wl3ARZhGskL9tueir6hCBIAEJBo2HkiaQSDAABBcsXLEyEQycw4EYimKIxNq1a7vg93jDjSvsDkqqeqK0R2VrDgHSGLPCinGAJBOaoJEkgRgjb+xUJnJHIg6K3ctkiw75FAwa1zVnPOa7J+yis4sdNDLqkVQkjDHW+B7m+ziRpRIMYeqnDJ8jPk9hFvCCdUa5Dm8FfocS/aaEbafKiUCQgASDxkNJE0gmGHA7eu2111wAOpbKJYkfGN7Hf3vUqFGGYGjevLkLQj/55JPdapM3hAjxI9gRRxzh6mc3g9VdfqCY0HFWBS5RnMHx1FNP2aRJk5xLGP6wuMBRd9DYHsf9jAxYPj0trlqcA8KPp6y0CTDezjjjDAcB4Yk7X3D38v7777cLLrjAvc945EwUUi5jfD7wxeazgLBgrHfo0MF23nlnd/CjL0fZZIKBmKA///zT1cfYZsLljSxvY8aMcf89/fTTXYIGbxrXpT1uM+n9uHHjYmOIccoKP69hxPWRXS+RzZo1y30PE7DMWGWS3bVrV+OMoIMPPthN7HGNJS7i119/jbkCswuPWy2utqRGp6yPYYgf60zgcRHkd4I2sRDE9TvttJPLABj8XmengoNbMbIl7rjjju7+fDb5DHbr1s193sjCGDQ+S5zt9P777zu3KdpN5kUWAA455JDQC0+ZsNc1NZ+ABEPNf8bqYQoC8YKhWbNmxo8HFjzjIplgwE+WCZFfwY2/FbEiTNiYwGN86d9+++3u34iSYIyJvxb3MkRGojgJtr/ZBveG+xjCIFE9lDnggAMq+bBrMJQeAcYzk3SELIYQZcLvLXgSO65Jt956q3uLMUU6ZT8BiifHRITJjxcNyQQDO3V+fA4dOrSSKKAdI0eOdFUHY7s0rktvnOaix9dcc43dcsstriqC+BHAfjwzmWfBJd5If84498Ii/n2yHVEHacdJspHIHnzwQRfwzCKRF8fBsY6LL/f3CQji60A4PProo85tGCMu79RTT3X/RvjgAuXjG/21iAHORurTp497iV2Vgw46KHZAbPw9+O0gTjHsbnUunofqqFkEJBhq1vNUbyISiBcMrLTyh+1ifmzYxmYCn0ww8KPgsycRtM2OAqu3iALvE07wOmlysaBg4P8ciLfeeuvZDz/8sJzo4HXe54cguMXOai+TNbangwHgBPWxEsXKEitlPiA+3r0qIiIVrwEEyFrEqiUWTF9MimKC/r2YCJ73Erymd+/eLn0zY4vT3r2oZlJDECiWK8GgcV0DBlwBusC4YcI+YcIEd3d2Z1n99+KY7+ePP/54OXdRhAXJADDK8B1Kqm++Q/kdwHDT43v9p59+ct/txA9iZDgkax6fKSb9yQQD8Qy0B2PXg3v435dgunEEOLsbQcHANQgJYuv4/L300ksx8cD3P7sbGAlKfMISdkbYVUCok/QAwYJdcsklLpW7TAQyISDBkAk1XVNjCMQLBlaQOOGa1R7MB4AmEwzknicLFkbueb60MbaO+dLGWNHhh4YfgqBgQBBwL+8ryw+CP3uDHx62r7mGiT+iw6feJX0u9+HHjxOwMXYyyLLVvn179//geRy4MuFWIitdArhjIGYxJkUckMgKJZMiP+Fnp4Bx693wjjnmmFjGF1yafLY2digYg1hQDOdKMGhcl+44zabnH330kdtRxXDp/PDDD933J247uBFhQUHM/8eOHRtLq8338CuvvBI7PJXvzKBbEGcXYaliGBIJhvjEA9SDmxHGohKCxi/u8DuDW2pQMLRq1cpl7mP3G2MHhZ0UjN0Fv+vBIpKvB7dDn3WQBSP6hZFCnANZZSKQCQEJhkyo6ZoaQyCRYMBViC/W6dOnx76g+fc555zj/p8srarPWjVv3jwXdxA8Nfv77793cQhBwTBw4ECXxtYb5f0Xe9AdivdZSeJUbIwVI34YScVL7AK2yy67xFaa+D/b4v5k8DZt2hhpMmWlS4Cxuf7668cEAIcyIlhxs/BnM8SPxyCtadOmufgYXPBYjfVuHsEJS64Eg8Z16Y7TbHoeHMu4iZIRDMPV7eabb3b/RjyQjtwbC0OkRsU468jvAvB/vsP9OGchxk+0owoG3IYGDRrk7kFcBGcSBY34Bf8aMT2UDQoG0rYOGzYsdglluQbj/CK/c8jiFq58GEkMiFngM+9TqGfDVteKAAQSCoZyf2CAGIlADSeQSDAwCeKHg21kjNUqTq1lIoPFCwZWRFlxZUKf7KMTRjBwPgbbzVi8YAieCM5J3viqsjLMCnE6YyWZlX0Tgh8AACAASURBVCxly0hHqma/T5Cyn3gw+WGCxeSHzwDGjlYwWxKvES/DCqVfoY0nRBCpPyQyV4JB47pmj8N89A5BzFj2rkJPPvlk7HwddndxAcL4LuT7mlV7jB1i/mCs9LN7ls6iCobgjkC8YOFe7GL4HWDOJMLlL5VgQCDgHhj/W4SrKp8dv8vg+8tnlMUAFp2U5jXd09X7qQhIMGh8lDSBZIKBiT+BcLhoYO3atXMrTvFf0mTNYPLlv6Tx9e7SpYvzCQ+m8YsqGBAP3oWEeyYSDEHf23QPkQ961BR+6erU+9WLAC5rfpUU94WLL7445jbHIW3sQgXHCL7RfjWW15koIZ7xi/YHJ+ZDMGhcV69xVQytDS7w0J74M4y8Dz/vBc8iYaHHiwRcSr2rT6o+RRUMCAAyF2HsBPt/+3sE25CNYKA+gqp9lqSgcOC9vn37ulg8drplIpAJAQmGTKjpmhpDIJlgoIPx/qW+08EdBn4AfvzxR/dWMAA0Prd9PgQDaShZSfM/RBxOlMziT66uMQ9QHQlNgCwruCj4yRMZkbyoJVOST61KhXwumBj5SYd3YeK94OQsjGDAX/u3335z7QyTJUnjOvQjVcG/CQQXVNJB6dmzZ2xnNhjrxe4au2zeGPveLZU4NB8TEFUwBOPfSL1KYHPQgq5UiGVcpDLZYQjWSTID4pRY0OLz6pMa8Nny7kzpOOl9EYgnIMGgMVHSBFIJBsAE/akTCQYCQf2XMecgsMOA8cHyMQT8P6pgwH3klFNOiT2bRDsMwYkbJ3LjbuLT8nEhqfhYcYrP1V3SD7zEOx/M1hJEEXTh4HWycvkzPAiG/vbbb2PFSSHp01OGEQy4g+AWgvn4G/7NhAwxgbjGfFpVjesSH6QRu8/4wX/fu4OSPjTe9YaxRhC0N1w/SXXN9zKr+hi+/pThuxQjVocJPLbhhhvGEmHwb5+AAsHRr1+/WL2Jgp5ZeEKcY7iFItLZ4cPIkMRnwLsF+qDsqIKBtN58fjBikxAG3ojDIMsTtuuuu8ZcsCJiVnERUAyDxkBpE0gnGPCJZcIdzEUf3GHgx8Kn8SNQmsPYcF1im3v06NExuGRSIstFqqDnYAxDGMFAACo/Nr5t+OWS65s0f2T/YGWL9JcczEXmDZkIcKATYyRo5Hnn9WCMC8GTTMK8EYzJeQqkbcTn26eCDK7WJothCI5rUv9SF5mYiH0IBuN7waBxrXEahQAuOLj9YMHsSPF1BLPQEZPmk1ggGBAOGAHGZJ5jDBJX4IXBkCFD7Mgjj4xNukmDjfG9ysFr7DogApKlVQ0u+LRt29bFIPAZIN22z4zH9zbxCbj/RRUM/Abx+eQwOE535neG3yO+/2m7F0u4JPrYvCiMVVYEIKAdBo2DkiaQTjAAhxUhJjnegoKBFHnktk5nbA1zGFtYwUDwmj+4h7oT7TDwOjsITACDPrrxbeGeOvE53RMqjfdZaWUFlqxH3nBRCK5I+tcRv4zbVMbkhyBSLJlgYPVz//33T1gNrh5McrDgwW0a16UxHnPRy+DBgMHsSPF1B4UFMTuMW5/ymhTYXgTHX8cuGi55DRs2dG8l2nXmNepIJhiI++HMBi9A4u9BXAG7FWRqwqIKBq7hfAi+65MZ2fIQ6cTjyUQgEwISDJlQ0zU1hkAYwUBn+SHyJy/HZ0nii57ANX+YFb6u+KHyBe53GQgwZeUq14KBtuEfTnpWcnX700BZLebHB9Gh3YUaM1xz0hHciXAr8ubdM+IrZ3eN8cpOlY9lYBWVFUpWW70LCGkeSReZTDBQL9mWqMtPypggIVT4fODKFy8YNK5z8qhrfCVkpiP7jzfc2fykO77zZPoKumfy/cwpztiYMWPcwg87bX6sM0Y5/Izx7k8zpyzxASSk8OlMec0vyiQTDJQhLTHCguQD/nua3QTOMiH+zJ9zkqlg4Dqy5rG77V0AeY2dDPrBrrVi2Wr8RyKvHZRgyCteVV4qBPiRwZeWrWwmT4XISLRw4UKXh5u28MPQvHnzUsGvfuaRAGOaFVImHojlTI2dBCZtnJ7Lbps/IC5dfRrX6Qjp/VwRYKeW73HcenARSjVGWWzCFYjv2WDsWLq2LFiwIPY9zenQ8Rmd0l0f5n3i1yZPnuzcYPktKMTvUZh2qkz1IiDBkOPnxY8hK29hP6CUZYLH1mgYoyyrx/504HTX8CMdtm7aTv18WYYxVkkoq/z+YWipjAiIgAiIgAiIgAhUTwISDDl4bqw0ECDFwSmsTjDpJpCQg2DYBgxuZ3I73meL/vHHH3dbh35FmEwKBAjGrwzj6kL2BLYbOcGXCTqr2PhM4r8eL07Y5me7FRca0sI1atTIBWXhF7/uuutW6jH3JvCKtpDNAdHA/dkmxW+fTBJBY+vW53meOXOmuzduCmx5crJkWHGSA+yqQgREQAREQAREQAREoAoISDBkCZmteg74CmbRCVbJZJq8y140MEE/5phjnL95ImPLn/L+JEq2Fal/3LhxCcvjg0naNL/jgEhAGPgTL4MXMbknsJDUat5oC0InkTH55+AmgsowUi1ywnCiunmfYEr8+SUashxUulwEREAEREAEREAEioiABEOWD4P0aD74iRz8/J+Vd9K8+SDY4IFed911lwt8wpjAk5KNvM/PP/+8C6bCyGjjsx0ETz0lmJac5gQOstvAbgBGjmVW+MnAQOo47o+R8pCdAg4W85lMCOR67733jCwRwTzTZFA499xznVAhIPKVV15xdeD/+MEHHzhBsvvuu7u0ihi7G9wTwURudZ+lh8AxMvrIREAEREAEREAEREAEagYBCYYsnyMr9AQxYWeccUYs0wGZCsicgzFpx+0HI2MNefqxiy66KJZu8JtvvjGOpsc4QIaDkthVIJcyxoSdSbw/8AUXqCuuuMK9Ry5+0nuSH50/2FprreVOePSr/bgX+VNdzz77bJf1J9hGdg7OP/98dy3ChTzV3nBvQkhwb59B4osvvoidfHnPPffEBAbtJdezTAREQAREQAREQAREoGYQkGDI4XMktzmp08gqwuo9rkJYnz593A4Ch2kFBQCHvwSzMCAAvLsP4uPZZ5916TkxUsUhALwhOnz97du3d5P04A7ABRdcYAMHDoyVJ68zQgHzpz0+88wzNmjQIPcaIuWkk05y4oP4iETGQU5k4cE233xzF7Ow/vrru2tlIiACIiACIiACIiACNZOABEMOnisBzKyykzIwkXHwCwem4BZELn6MFfvPPvss5d2DOwYEULMjkMo4Ep4g53TGLge51ZcsWeLiIziFOGi4PvXv39+5VwVzQxPrQHyE32XgGtyq6B/ihHzY9evXT3d7vS8CIiACIiACIiACIlCNCEgwZPmwLr/8chcY7CfPZCPC7x/fftx2MC8Ygieesivgj2tP1gRcmrxIwF3pmmuuSdla7pMsIDl4IZmSnnzySfcSuyG0/9FHH10ucBsxwD1322232OWcwOqzJAWFAwX69u3rhAhxEjIREAEREAEREAEREIGaQUCCIYvnSDpVBIKfOHN8PKv8GO5DnBAZFAykLSV1KsYx899//32l8xTYHfB1sQNB3IOPK8AF6L777ou1lnJ+N4E4BXYFyGaEUMHOOeccFyCdyNgFIOg5aJwHQTpWRMxjjz3mUqxiBGR/+umny+0cEOfA62+++abrK7sV2Omnn+5OcJWJgAiIgAiIgAiIgAjUDAISDFk8R9KMktEII20qgcreyDR03nnnVRIMTPJZ3SfLEYYgICAaI6MS4oOJNyv7I0eOtFGjRsVW94kTYDLPBB4LZjjacMMN3Q7B4MGDXUpWjNV+dhGCKU7Z8eB6HzhN9iYfk0D8RJcuXdy17DogfObNm+f+jyggbSwpWTHeQxh4I5aCTE2Yj4/IAqsuFQEREAEREAEREAERKCICEgxZPAwm2wQCeyMTEav8pB4l/sALA9KbkgYVu/TSS23o0KHu3xyQxmo8uwO48viUpdRBTASGOxA7EVivXr1cDAQTerIkkUYVGzJkiB155JFOZFDe71Jw6Nqee+7pRALihh2Q1q1bG8HO7dq1cwHVCA2sX79+Lj1q27Zt3YFvHCBH2lZOckZo0BfaxcnRvEbaVwK4ETrc37tXEXztd1ayQKtLRUAEREAEREAEREAEioSABEOWD+KII45wK/CpjEm4PweBGAN2Jdg9SGTNmjVzuwR+tZ+ToDnzwIuP+GuIW0AI4OKEITzY2fBnNMSXZyeEU50RHxwKR1s4nTqZIRxOPvlk9zbpVf35EInKc5YDwd2IEZkIiIAIiIAIiIAIiEDNICDBkOVzRAAwiWai7lf2cflhlZ1Vf2IDsOHDh8fSlXLIGdeQNtUHKeOGtNlmm7nD0zp37lypVWPGjDEORHv//fdj9yCwmIPTuI8/Rdpf9NFHHzn3IbIf+fuTvpUdAcpzmrS3qVOn2lVXXWXDhg2rFDDdo0cPO+644yqdCs017JQQiI2Q8UbdtOWEE05wAd8yERABERABERABERCBmkNAgiFHzxI3IQKOmTwHJ+SpqidegcPZcPNZeeWVrUmTJilbg9BgNwCXoE6dOlU6wyHRhZz4zLkQlO/YsaM1atQoaf2LFy92ddMmysaLkPgLERrsULAjgkhA8MhEQAREQAREQAREQARqHgEJhpr3TNUjERABERABERABERABEcgZAQmGnKFURSIgAiIgAiIgAiIgAiJQ8whIMNS8Z6oeiYAIiIAIiIAIiIAIiEDOCEgw5AylKhIBERABERABERABERCBmkdAgqHmPVP1SAREQAREQAREQAREQARyRiBvgoHsPI888ohxAzLwpLM+ffq4tJzBk4nTXaP3RUAEREAEREAEREAEREAE8ksgL4Jh7ty5dvbZZyc9bCxZlyQa8vuwVbsIiIAIiIAIiIAIiIAIRCWQF8Hw1ltv2UMPPRS1La68RENG2HSRCIiACIiACIiACIiACOSFQF4EwxNPPGGvvvqqa3Dv3r1t4MCBKRtP+VGjRsXKtG7dOu3BYfEVcihZr169bIcddsi7W1PLli3z8jBUqQiIgAiIgAiIgAiIgAhUBYHp06eHvk1eBMPjjz9ur732mmvEBhtsYEcffXTKBhHjcP3111cSDaF7EFdwzTXXtFNOOcVq1aqVaRW6TgREQAREQAREQAREQARE4G8CRSEYaEsuRcNpp51mPXv21EMWAREQAREQAREQAREQARHIkkDRCAYvGkaMGGHl5eWRu8WOxqRJk9x1BxxwgPXv3z9yHbpABERABERABERABERABESgMoGiEgzZPJwrrrjCfvrpJ1fFvvvua9tuu2021elaERABERABERABERABERABM3dMQvfu3SuxqFWeyRJ/oIqoMQy5eBISDLmgqDpEQAREQAREQAREQAREQDsMGgMiIAIiIAIiIAIiIAIiIAIhCWiHISQoFRMBERABERABERABERCBUiQgwVCKT119FgEREAEREAEREAEREIGQBCQYQoJSMREQAREQAREQAREQAREoRQISDKX41NVnERABERABERABERABEQhJQIIhJCgVEwEREAEREAEREAEREIFSJCDBUIpPXX0WAREQAREQAREQAREQgZAEJBhCglIxERABERABERABERABEShFAhIMNeCpv/vjXPtk9Dz7YfxC+3XyIps8a4nNW1RuZeXleeld7Vq1rHGDWta2WT3r0raB9e7U0Dbs1ti2WGPFvNxPlYqACIiACIiACIiACBSOgARD4dhndefPx8y3x0bMsBc/n2lzFpZlVVeuLm7SsLbtul5z22fjFrZe1xVyVW3e6mHwy0SgOhPo3r17yuZrjFfnp6u2QyDdGBclERCBqiEgwVA1nHN2F4TCTa9OseHfzc5ZnfmoaNs+Te2EHdoUtXBINPjzwUJ1ikA+CIQZv2HK5KNtqlMEckFA4zcXFFWHCOSGgARDbjhWSS3nPTnR7npzapXcK1c3OWLr1nbBnu1zVV1O69GPUU5xqrIqJhBm/IYpU8XN1u1EIDQBjd/QqFRQBPJOQIIh74izv8HIPxfaKff/Yd/+viD7ygpQw1qrNLLrDl7ZenVsWIC7J7+lfoyK6nGoMREJhBm/YcpEvK2Ki0CVEdD4rTLUGd1oyZIlNnnyZFu2bFnk62vVqmWtW7e2Ro0aRb5WFxSGQF4Ew7PPPmsvvvii61Hbtm1tjTXWyHvvvvrqK5s1a5a7z0EHHWRbbrll3u9ZFTcgoPnIO38rmjiFTPtMfMOdR65aVIHR+jHK9GnqumIgEGb8hilTDH1RG0QgEQGN3+IdF8zx+LN06dKMG4lo2GSTTdycrV69ehnXowurhkBeBMPXX39tN954Y9X0IMFdzj//fFtllVUKdv9c3RixsP9N4/KW7ShMO1dtXd+269vUNure2O0QrNS8rjWsV9sWLimzv2YuNXY/Pv5lnr3+zWz7berilFWSXenhEzoXjWjQ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uOKZc9Acrlic04AFz6rw7cX9DJEjEwEREAEREAERKH4CEgxF/IwKIRh81iLcdL67qpdbLU5mPpMS7z9yYmfbco2KgFpOFCa9p59UM0E9oF9La7liHXfeQLxgwLXn0mf/snvfmeZcefyk+eoDO7qzD5jY5kswcC+yGd3y2hRjRd8bIohUskdu3Wo5l6KLnp5otw2f6ibE315V2eXo+c9n2jFDK+I/Rly8unNpCmPc+4KnJtob389x/fXGidL0nUxMQcM1ifMbHv5whnH+hTd2Sgbv1m65gHMyOp3/5ER75ZvZsfo5SI1diCG7rxQL+g4jGLgXcQwciPfl2PkxkUJ9/96guQsCxx3LG+Lr2KHjbdhX/2SBIrtWGwmGMENDZURABERABESg4AQkGAr+CJI3oBCCIZc4mATPWVBmq7Spn/IcAn9PVs5/m7LYiRTvg5/L9qSrC3cb4jBarFjXOraoV5CTiBEC46ctttq1a9nKLeu506NTGav8MJu/uMzaNauXdhIOY54Log0xw65CNgaviTOWWqP6tWzlVvVTZm3iFGhiNSiX6lyKbNqja0VABERABERABHJPQIIh90xzVmN1Fww5A6GKREAEREAEREAEREAECkZAgqFg6HVjERABERABERABERABESh+AhIMxf+M1EIREAEREAEREAEREAERKBgBCYaCodeNRUAEREAEREAEREAERKD4CUgwFP8zUgtFQAREQAREQAREQAREoGAEJBgKhl43FgEREAEREAEREAEREIHiJyDBUPzPSC0UAREQAREQAREQAREQgYIRkGAoGHrdWAREQAREQAREQAREQASKn4AEQ/E/I7VQBERABERABERABERABApGQIKhYOh1YxEQAREQAREQAREQAREofgISDMX/jNRCERABERABERABERABESgYAQmGgqHXjUVABERABERABERABESg+AlIMBT/M1ILRUAEREAEREAEREAERKBgBCQYCoZeNxYBERABERABERABERCB4icgwVD8z0gtFAF6EoRDAAAgAElEQVQREAEREAEREAEREIGCEZBgKBh63VgEREAEREAEREAEREAEip+ABEPxPyO1UAREQAREQAREQAREQAQKRkCCoWDodWMREAEREAEREAEREAERKH4CEgzF/4zUQhEQAREQAREQAREQAREoGAEJhoKh141FQAREQAREQAREQAREoPgJSDAU/zNSC0VABERABERABERABESgYAQkGAqGXjcWAREQAREQAREQAREQgeInIMFQ/M9ILRQBERABERABERABERCBghGQYCgYet1YBERABERABERABERABIqfgARD8T8jtVAEREAEREAEREAEREAECkZAgqFg6HVjERABERABERABERABESh+AhIMxf+M1EIREAEREAEREAEREAERKBgBCYaCodeNRUAEREAEREAEREAERKD4CUgwFP8zUgtFQAREQAREQAREQAREoGAEJBgKhl43FgEREAEREAEREAEREIHiJyDBUPzPSC0UAREQAREQAREQAREQgYIRkGAoGHrdWAREQAREQAREQAREQASKn4AEQ/E/I7VQBERABERABERABERABApGQIKhYOh1YxEQAREQAREQAREQAREofgISDMX/jNRCERABERABERABERABESgYAQmGgqHXjUVABERABERABERABESg+AlIMBT/M1ILRUAEREAEREAEREAERKBgBCQYCoZeNxYBERABERABERABERCB4icgwVD8z0gtFAERqIYEysvNxk1ZZLMXlKVsfc8ODa1BvVo2ZfZSmzBjSdKydWqbrdmpUTUkoSaLgAiIgAhUdwISDNX9Car9IiACRUdg9oJltv+N4+yLsfPTtu2983tYt5Ua2G3Dp9pFT09MWn6FBrVt9A2909anAiIgAiIgAiKQawISDLkmqvpEQARKnsA1L02ya16aHIqDBEMoTCokAiIgAiJQQAISDAWEn+mtl5WVO/eFBvVqW4vGdTKtpkZehxvIzPnLbMHiMsemUf3aRdvPkX8utBnzltkmPRpn1cZc1ZNVI3J48YE3j3Pju3enhnbNgSvnsOaqq+qYoePt+c9nWp9VGtmV+3dMeeNkLkn3vTPNHh8xI3atdhiq7vnpTiIgAiIgApUJSDBUoxHx8lez7a43p9qXY+fbkmXlruVtm9a1XddrZifv2NZaNalbjXqT26aOn7bYrh022V77ZrbNnLfMVV67llnPjg3t4C1a2f79Wrr/F4vhqrLblWMMgXPuwPZ29LatM2patvV889sC2+/Gse7eAzdsYRfu1T5pO7a7ZLT9OX3xcu/Xr1vL2jStZ+t3XcEO2rylrd6hYaUyd781za4dNsm9dseRq1i/1VdM2dd/DRllf81c4up7fnDXjLgU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REAESg0BCYZSc6l1oiIgAiIgAiIgAiIgAiIQn4AEQ3xm2kMEREAEREAEREAEREAESg0BCYZSc6l1oiIgAiIgAiIgAiIgAiIQn4AEQ3xm2kMEREAEREAEREAEREAESg0BCYZSc6l1oiIgAiIgAiIgAiIgAiIQn4AEQ3xm2kMEREAEREAEREAEREAESg0BCYZSc6l1oiIgAiIgAiIgAiIgAiIQn4AEQ3xm2kMEREAEREAEREAEREAESg0BCYZSc6l1oiIgAiIgAiIgAiIgAiIQn4AEQ3xm2kMEREAEREAEREAEREAESg0BCYZSc6l1oiKQHQI33XST/fjjj3bllVdat27dsnOQtdTqwoULrWzZsla1atW1dMS1d5jrr7/efv75Z7vmmmts6623XnsHzvKR8vLybMaMGdawYcMsH0nNi4AIiEDpJSDBkMFr/91339k555xjjRo1sg8//NDKlCmTwdbVlAjkJoGTTz7ZvvrqK3v00Udtzz33zM1ORujV7NmzbZdddrFy5cpZ//79rVatWhH2ir7Jm2++abfcckuRHRAotWvXts6dO9sRRxxhO+ywQ/RGY2x54okn2oABA+zJJ5+03XbbLcaeub3pnXfeaY888oidd9557o9MBERABEQg8wQkGDLIlI/V+++/71p88cUXs/bhz2CX1ZQIFCFw991328iRI+3cc891Tmwq21AEA6PU3bt3d4IBx7pu3bqpTj3W71966SW7+uqrrXr16rbRRhsV7Ltq1SqbMGGCLVmyxP3s2GOPtRtuuCFW235jrhvXr02bNnbZZZcVamNDFQxEuJ5++mk79dRTrU+fPmlx004iIAIiIALFE5BgyNAdMn/+fJeOwWghH/4DDzzQ7rvvvgy1rmZEYO0R6NWrl/3www/2zDPPWI8ePVIeeEMRDJzorFmzXGQw02KBtr1g2HXXXe2pp54qxHX16tX2zjvvuHShxYsX24033mjHHHNMSvbhDQYNGmRHH320bbHFFvbWW2+VCsGA4Bo/fry1bNnSvX9lIiACIiACmScgwZAhpi+88IJde+21blT2tddes7lz5zqnq0aNGpGOsHLlSitfvnykbXEuyNtlJDSK8UGNum3ctpcvX24VK1aM0o2MbBPnXNI9IMfAaYzqfHAtuH4VKlSIfMg415tG42xPf7iOya55KoZrWzCk6k9kqIEN497HcY9Bn7k/4qQdFicY/PGfffZZF13YeOON7dNPP43bLYsrGOKeRzrXKtvXIiqkFStWxHpGo7ar7URABESgNBCQYMjQVSaiMHToUJf7/Morr9hjjz2WcpSQtAe2+/33392oIrUPBxxwgJ1//vlFii756L788svuz+jRo50D2a5dOzv++OPdiGLYceHjyCjmq6++6tIdcGa32WYbu+CCC2zLLbcsdNY4mGz33HPPGTcExyJl4pBDDrHTTz/dKleuXGh72rvrrrvs66+/NiIr/H7bbbd1YmmrrbYqlui9995rn3zyie2///6u3iNoOCOHH364Y0E+fOvWrd2vp06dag8//LBzoEgbqVKliovmnH322UWO9+uvv7q0hE033dSlZoSN67Rs2TJ79913C87L/4xrQeEuBbzbbbedIQKLM5yzBx980BWSIpzq1KnjcsMvuugia9y4cZFdp0+fbv/73//c+c+cOdPlrffs2TPp9lHvj6uuusp++uknu+6669x5vffee+7+ID3FG/fmAw88YN9//73j26xZMzv00EPtjDPOsEqVKrnNEArk8U+cONFFydiG4l9SWfhdMksWYaCmB+cXQXnPPfe4+zVOf0466SSbPHmyXXHFFUUiHcOHD3fPCXUGffv2dc573Ps4fD5Lly61gw46yAl3apC8eb7wGzx4sOHUjxkzxgmy7bff3kUF2rZtm/JNEkUw8GwR1eF8/vrrL3fNojwvRx11lOPAdeP68UzyDAfPxackPfHEE+6ZIoL0zz//pDyPOM8fgyW8d0455RR3//C880zyTtlkk03skksuMSIsxRmpVL/99pu7n+EbNM7vP//5j/sRx+L+pCbj9ddfT3ifcg9yvjzT7FuzZk3Hl340b968oOl58+a5VDB48dyTNiYTAREQARFYQ0CCIQN3w4gRI2zfffd1zusbb7zhHLW9997bunTp4hy4REbO7c033+w+UBQ58oHCGZk2bZp16NDBfQz9R4uP7VlnneUcZpxMPqI4qN75O+yww4zCP284Pv/973/daCOOK/3io//LL7844cCxd9xxx4Lt+TA///zzVq1aNdtpp53cz/nQLliwwM2mggDyo9U4GByPCApOObnSOE84pGyD011cQSWCir7Rr4EDBxYSOpwPaRgtWrRwwgvDaUIUIRQYdZhg6T8AACAASURBVCWnftKkSY4VTtUdd9zh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SUtavny5S00K1o1IMETCp41EQAREQAREQAREQAQySSBR3QIOK6kxNWrUKFGB8+67725jxoxxUYYrrrgiUrfPPfdc++CDD1xaElGGdW0HHnigDR061Hr16mU333xzrO4MGTLEDjroILfPe++9FzktiWuyYMECVzeCYPMpSYiHqKYIQ1RS2k4EREAEREAEREAERCApgUTpSEQWEAvUHNStW7dE9CQY0hMMQJ89e7ZVqlTJiQZfBC3BUKLbUTuLgAiIgAiIgAiIgAjEJZAsHYkIA3n0DRo0iNzka6+9Zn/88YdLuzn55JPdfhIM6QuGGTNmuPoRIgw+LUmCIfLtqA1FQAREQAREQAREQAQyQSCRYCC6QP48EYY4MxUlSiWSYEhfMEyfPt1FGKgjIcpA/YIEQybuerUhAiIgAiIgAiIgAiIQmUBQMFDszP99wS0RhsaNG0dua0MQDBQmU6+AUKJmAVsXNQwcd+rUqS7C4AvPqWOIsxaGahgi37raUAREQAREQAREQAREIBmB4gTDkiVLrEmTJpHhbQiCgcJsZjViRiSKlNelYJgyZYpVqVJFgiHyHagNRUAEREAEREAEREAEMk4gLBiIMviCZwRD06ZNIx9TgqEoqnRnSaKlyZMnO8HgC59JSVKEIfLtqA1FQAREQAREQAREQAQyQSA8pWpQMLCqcbNmzSIfRoIhs4Jh0qRJxqrXEgyRb0FtKAIiIAIiIAIiIAIikGkCEgyFieZSSpIEQ6bvdrUnAiIgAiIgAiIgAiIQm4AEQ2rBQBH0woULrVGjRpFXavatBlOS3n//fevcuXPkayTBEBmVNhQBERABERABERABEcgWAQmG1IKhJOxLUsMgwVAS8tpXBERABERABERABEQgIwQkGFILhldeecVYEyGR9ezZ082olMwkGDJym669RphLWCYCIiACIiACIiACIrCGQCrB0LZt28i4NvSi50Qgbr755oL1GhL9viSCYfTo0UWKnqtXrx75emgdhsio1mwowZAGNO0iAiIgAiIgAiKwQROQYCh8eYsrepZg2KAfhfyTk2AoBRdZpygCIiACIiACIhCLQLYFw/7772/Dhg2zjh072pZbbhmpb99++62NHz/eevToYc8880ykfTK1USLB4Nv+5Zdf7Pjjjzemm/WmCEOmyOdIOxIMOXIh1A0REAEREAEREIGcIZBtwXDX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3y1z0ZAt+ta16pvEc9ITXb0ZM2bYNddcYx9++KHNmTPHWrVqZaeddpqdffbZtvfee9vs2bNdOk/YRo0aZbfccotLcZk1a5b7SHbq1MlOOOEEt3+ijzrO41NPPWVPP/20DR061DmS9erVs549e9oVV1xhFCUnMlKJ7r77bvvzzz+NB7VHjx7u2BQ233TTTXbGGWdY7969k96ccQTDN998Y3feead99913tmjRIqtWrZrtuOOOLid/++23T/8BSLDnTz/95I41YMAAmzdvnivI7tKlixv9Z/QxkaXDPdhOHMHw8ccf2/3332+///67zZ0717Hfdttt3bXiGoTNC4brrrvOdtttN7v88svthx9+cNeZa8t+RxxxREYYrlyw2gbtMqNIWxtfU9MaHVQ0Jakkz9Yf/51t839fYV3frm+z+y+10XcscJMklCljVnu7itbh9tpWa+uiwhJRMuGphTbx6UW2YOhKY3ChQr2yVr9nZWt7RY2Ez++c75fbn2fPcefAucwbvNz+umKe8XP2L1+rjHW6u441PaZqwblPeW2xjb59gS2buspWzF5tlZqWswq1CzvF237cwCo2jOcoJ7pQZ555prumPMObb755oU14T/As7rDDDvbggw8W2f3TTz91dQA///yzSxfk2acN2kxWW7RgwQL37L/++us2duxYw1Gm+PjII4+0iy66yD2fYaPe4JlnnrEbbrjB9ttvP3vllVeM9J+RI0c6sVC3bl0jZXHjjTcutOvbb7/tnsfhw4e7e71bt26ujRdeeCFpDQPvPvrHvpMmTTJEPEKA+/zKK69M2D/ed7xXR4wY4QZMwv04+OCD7eqrry5yXukcKyMPmxoRAREoRECCIcs3RK4Ihp8PmWmM0pOKNOfbZVauWlmrs0PRj33He2pbtYCQmNx3sf1x0mxreXp15xxQ/1CuShmr3rmC5S03Wzp1pW37SQOr3nGNE4/TT4rAwuErrM6Olazhfvk1EdM/WGJzvltu1dqXt+0HNHJOAIZT07/9FKu/e2VbMn6lNdinso1/eJFVblHOam5RwfU7b5nZ9I+XuPoKHJiS2LJly9zHneLgzTbbzA444ABbvHix+8Aee+yx9vDDD7sPNB+3oPHB50PMx/w///mPbb311k5ssN8///xjhx9+uPvIhu3EE0+0V1991dq0aWM48bVr1zacZpwBPvyffPKJ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skVAgiFbZNWuCIiACKx/BCQYsnzNJBiyDLgEzVOMTGoSkQbyaalbYPpM1kDIFWNqRmZRYuEwCgOZ55yFwJKtdZAr/VY/1n8CTJ268M8V1uayGla9Q/SF1Nb/M9cZiIAIiIAIhAlIMGT5npBgyDJgNS8CIiACIiACIiACIpBVAhIMWcVrBVPwZfkwal4EREAEREAEREAEREAEskJAgiErWNc0qghDlgGreREQAREQAREQAREQgawSkGDIKl5FGLKMV82LgAiIgAiIgAiIgAhkmYAEQ5YBK8KQZcBqXgREQAREQAREQAREIKsEJBiyilcRhizjVfMiIAIiIAIiIAIiIAJZJiDBkGXAijBkGXAxzc+dO9ctwlavXj238JksNwmMGjXKrTHRunVra9CgQbGdXDJlii2ZOtXKlCtndTbbLDdPSL1abwjEuZ9YG4VpjllAccstt7QKFTTV7HpzodVRERCBEhOQYCgxwuIbkGDIMuBimn///fftiCOOsMMPP9xeeOGFddcRHTkpgaFDh1r37t2tUaNGNmjQoJTrS/z18MM25LbbrEqTJrbfDz+IrAiUiEDc++mqq66yu+++2y2qeOedd5bo2NpZBERABNYnAhIMWb5aEgxZBrwOBMPtt99urLQ8cODAdXdyOXLkr7/+2o4//njHonnz5rF6tXz5crdI3ogRI+yLL76ItGDe6BdftF+vuMJqbbKJ7fH557GOp43NBp54olVv1co2v+66lDgG9OpleatWWeNddrFNzjyzyPbfHHOMlata1XZ44omUbeXqBnHvp1WrVjmB+8svv9i7775re+65Z66emvolAiIgAhklIMGQUZxFG5NgyDLgdSAY9t9/f/v777/tr7/+WncnlyNHvuOOO+zaa691Tn/Lli1j9erWW2+1G264wU499VS7//77I+078f33bdBZZ1n9bbe1Xd54I9I+2iifAM7/u506WbP99rNt7rknJZY3W7d2+5SvWtX2GzTIKtSuXWift9u1s/I1a9oBv/ySsq1c3SCd++mPP/5w4rZZs2bGv+N8RHOVg/olAiIgAqkIxHnX4SNtvPHGhZosk0dipywpAQkGM0blsHLlyqV1p6xcudLKli3r/sSxqClJ3ML0sXz58pGax1GoXr16WoIh7rEidSjDGzHyX7FixUitHnnkkS7aElcwzJw50zp27GjwYN/69etHOt60AQPsm2OPtaZ77mk7PPlkpH1KstHqFSusbBq56unu5xz7lSvNypa1MhHudxx6jJqOVDZ3+HD7Yq+9rOXhh0cWDLS7evly63zhhdbx/PPjC4a8PCc6ykR8ttz5s33gfPJWr47Egn3jck/3fjrllFPsxRdfNETv+SEuqa6Dfi8CIiAC6yMBCYYsX7VcEQxPPfWUPfzww3bmmWfaySefXOiscZa33XZb5yR+//33RYh8+umn9sBpJGBUAAAb+0lEQVQDD7iCv/nz57si4s0339y1te+++yYkuGDBApfr+/rrr9vYsWOdY0hRKw7mRRddZNWqVSuy3//+9z975pln3KjzfvvtZ6+88oqR/kPhMmKhbt261q9fvyKqdciQIUZuMTnwnEuHDh3swgsvdEWJyWoYZs2a5fr39ttv26RJk2zFihVuxJDtr7zyyoT969mzpyvOxcGl7bB6Pvjgg+3qq68ucl7pHCvd2/L333+3a665xr777jt3Tlwn2GyyySZ2yCGHuOv8yCOPFGn+m2++cTnZ7Ldo0SJ3/jvuuKP16dMnYarQpZde6q7FmDFj3GrmsAgWgW600Ub2zjvvJD2N2267za6//no77bTT7L777ot8unN++836HXigtTzsMNvm3nuL3Q/Hc/QLL9jY11+3BX//batXrrTKDRpYg+22s03OOMNqdeiQcP9FEybYsHvvtalffWXLZs1yzm7tTp2s7fHHW6sjjkh6zAnvvGOjnn/e5v7xh61avtwdi0hIh3POcfuHbdRTT9k/r7xinS+7zJrusYeNf/ttG/7gg7Zg9GjnIFesXdt2efNNq9GmTaFdVyxcaH898ohNfO89o688W9VbtLCNDj7YnRcRgaAtHDvWvu/d21YsWmSLJ02yCjVrWtXGjQtts2mfPtZk990L/YwIQ+0uXWzp9Om2askS23fQICtfpUrBNskiDMvnzLERDz9skz76yBWo48RTc7LRgQdapwsuKNI/Up+qbbSRNd9/f/vpggts+bx5LgWq0/nn269XXWVjX3vNKlSvblvdcos1C79v8vLsn759bcxLL9m84cPdsSrVrWtNeva0ThdeaFWbNSv2HolzPwUbGjZsmHXt2tWl4fE+SHcwJPKNrw1FQAREYB0TkGDI8gXIFcHgHTTSR3ACg8YIfo0aNaxSpUrGzEJBw+E/4YQTrEmTJs7hbNiwoeEAM6qMEHjwwQetd+/ehR2UhQsN55pw/VZbbWX77LOPc/g/+ugjJzpwWj/77DN3vKD17dvXTjrpJDv99NNt6623du1WqVLFOnfubIx6T5061T755BM3Mu2NGXZwbBEyBxxwgNsPAYDY4LivvfZakaLnxYsX26677ur6R2rBLrvsYjD44IMPbPjw4bbbbrvZhx9+WOTOOPbYY42RcfL2eXB23nnnQtuQz0wxZNDSPVY6tyWOC31auHChO/du3brZ+PHjDa4IqJtuusn22muvIo78Sy+95NKCEIzHHHOMc/7hyggqooPfH3TQQYW6hJCjbsELjJ122sldK2/cL4899ljS09huu+0McfPxxx87/lFt4T//2Cc9etjGJ59sm197bbG7UetAjnrtzp2tUY8ezulcNHGiTfzgA7PVq63Hq686hzho80aMsP7/LxpXzJ1rzfbc0+p27WrLZs+2Ce++65zfdiefbFskOO5v11xjo5591irVr2/N9trLOeXzR460KV9+aeUqVLAdnn3WGu20U6FjIRB+PO88a3fCCVZniy2cs1yucmWr2aGD5S1fbkumT7cefftazfbtC/ZbuWiRfXXYYTZv2DA3SxSOcZkyZWxyv3425/ffre6WW1qP11+3coEIESLhlz59bOmMGTZ32DCr3KiR1Q6Jpfann24Nd9yxUP8QDIiV1r162e833GBb3HCDtTvxxIJtEgmGlUuW2FeHHOL6V2/rrV2bREwmf/654wGDnV9+udBxPt5xR8dr5cKF1nTvvfNZT5tmHc4806YOGGD1ttzSCb8KNWrYAb/+Wiji8/NllznRVaVxY2t+4IFWuX59mzV4sE3m/VKvnu369tuuZiOZxbmfwm1sscUWLso4YMAA22abbaLewtpOBERABNZLAhIMWb5s67tg4EPIR3H06NGFprzEQWdqQabBJCqB0+KN0WeEBNEEIht+9I3R/6OPPtqJjRtvvNEuvvjiQvSJFCAmOOa4ceOcc0sUADGTzBAYOMSkBZAe4A3Hf4cddnCj3+FZkhAT//3vf93vKbb1fWfbTTfd1AmO/v37O4c7bNOmTbNWrVpZixYtIqUkleRYcW9NhB0CKTyDC9EDIjY4/wiJt956q6BpzqdTp04uMoPjs1lgqlLEXY8ePaxmzZquZiNRVIgIBhGgOClJRGkYmeXlM2XKlCLCsbjzXjFv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b3kogfRsOH/+fKs5SaF6CpBT3D4pmYX+i0vNSfpApP4hJzUlQ2IQmESB/FaPHTvWeqJkW19qpQwCBAhMi4CAofhOCxiKgU1PgAABAgQIECBQKiBgKOXt7h/1WfwxpidAgAABAgQIECBQIiBgKGH936QyDMXApidAgAABAgQIECgVEDCU8sowFPOangABAgQIECBAoFhAwFAMLMNQDGx6AgQIECBAgACBUgEBQymvDEMxr+kJECBAgAABAgSKBQQMxcAyDMXApidAgAABAgQIECgVEDCU8sowFPOangABAgQIECBAoFhAwFAMLMNQDGx6AgQIECBAgACBUgEBQymvDEMxr+kJECBAgAABAgSKBQQMxcAyDMXApidAgAABAgQIECgVEDCU8sowFPOangABAgQIECBAoFhAwFAMLMNQDGx6AgQIECBAgACBUgEBQymvDEMxr+kJECBAgAABAgSKBQQMxcAyDMXApidAgAABAgQIECgVEDCU8sowFPOangABAgQIECBAoFhAwFAMLMNQDGx6AgQIECBAgACBUgEBQymvDEMxr+kJECBAgAABAgSKBQQMxcAyDMXApidAgAABAgQIECgVEDCU8sowFPOangABAgQIECBAoFhAwFAMLMNQDGx6AgQIECBAgACBUgEBQymvDEMxr+kJECBAgAABAgSKBQQMxcAyDMXApidAgAABAgQIECgVEDCU8sowFPOangABAgQIECBAoFhAwFAMLMNQDGx6AgQIECBAgACBUgEBQymvDEMxr+kJECBAgAABAgSKBQQMxcAyDMXApidAgAABAgQIECgVEDCU8sowFPOangABAgQIECBAoFhAwFAMLMNQDGx6AgQIECBAgACBUgEBQymvDEMxr+kJECBAgAABAgSKBQQMxcAyDMXApidAgAABAgQIECgVEDCU8sowFPOangABAgQIECBAoFhAwFAMLMNQDGx6AgQIECBAgACBUgEBQymvDEMxr+kJECBAgAABAgSKBQQMxcAyDMXApidAgAABAgQIECgVEDCU8sowFPOangABAgQIECBAoFhAwFAMbHoCBAgQIECAAAEC0yJw9erVbufOnWsu92d37ty5My0ArpMAAQIECBAgQIAAgeECAga/DgIECBAgQIAAAQIEhgoIGPw4CBAgQIAAAQIECBAQMPgNECBAgAABAgQIECAwuoAMw+hm3kGAAAECBAgQIEBgagQEDFNzq10oAQIECBAgQIAAgdEFBAyjm3kHAQIECBAgQIAAgakREDBMza12oQQIECBAgAABAgRGFxAwjG7mHQQIECBAgAABAgSmRkDAMDW32oUSIECAAAECBAgQGF1AwDC6mXcQIECAAAECBAgQmBoBAcPU3GoXSoAAAQIECBAgQGB0gfUChv8CTYFddj+MC2AAAAAASUVORK5CYII=)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "KUTLOFaZM4lj" }, "outputs": [], "source": [ "# @title Load API Key\n", "from google.colab import userdata\n", "API_KEY = userdata.get('gemini')" ] }, { "cell_type": "markdown", "metadata": { "id": "YFxTwdqup_z5" }, "source": [ "## Install Dependencies\n", "\n", "*This will take a minute or two...*" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Qdm_qp_EQJe8" }, "outputs": [], "source": [ "%%capture\n", "!pip install xmltodict\n", "!pip install networkx ipysigma" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "MVTY3dUIMsvo" }, "outputs": [], "source": [ "from collections import OrderedDict\n", "from dataclasses import dataclass\n", "from functools import cache\n", "import itertools\n", "from typing import Callable\n", "import networkx as nx\n", "import requests\n", "import xmltodict\n", "from ipysigma import Sigma\n", "import math\n", "import base64\n", "import json" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "hHpmncOLgBMw" }, "outputs": [], "source": [ "from google.colab import output\n", "output.enable_custom_widget_manager()" ] }, { "cell_type": "markdown", "metadata": { "id": "-VvPogUCwLUC" }, "source": [ "### DBLP to graph API" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "f1Sj1xEmi1fa" }, "outputs": [], "source": [ "@dataclass(frozen=True)\n", "class Author:\n", " id: str\n", " name: str\n", "\n", "@dataclass(frozen=True)\n", "class Paper:\n", " id: str\n", " title: str\n", " date: str\n", " author_ids: list[str]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "mL2uT670P6I_" }, "outputs": [], "source": [ "@cache\n", "def _load_page(author_id: str) -\u003e OrderedDict:\n", " data = requests.get(f'https://dblp.org/pid/{author_id}.xml')\n", " return xmltodict.parse(data.text)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "k3B1xBosM5iK" }, "outputs": [], "source": [ "def clean_name(name: str) -\u003e str:\n", " return ''.join([x for x in name if str.isalpha(x) or x in (' ', '-')])\n", "\n", "def read_authors(xmldata: OrderedDict, author_pid: str) -\u003e list[Author]:\n", " authors = [Author(id=author_pid, name=xmldata['dblpperson']['@name'])]\n", " for item in xmldata['dblpperson']['coauthors']['co']:\n", " na = item['na']\n", " if isinstance(na, list): na = na[0]\n", " authors.append(Author(id=na['@pid'], name=clean_name(na['#text'])))\n", " return authors\n", "\n", "def read_papers(xmldata: OrderedDict) -\u003e list[Paper]:\n", " papers = []\n", " for article in xmldata['dblpperson']['r']:\n", " if not isinstance(article, dict):\n", " print('???', article)\n", " continue\n", " [article_key] = article.keys()\n", " article = article[article_key]\n", " if 'author' not in article:\n", " print('!!!', article)\n", " continue\n", " if isinstance(article['author'], list):\n", " author_ids = [x['@pid'] for x in article['author']]\n", " else:\n", " author_ids = [article['author']['@pid']]\n", " papers.append(Paper(id=article['@key'], title=article['title'], date=article['@mdate'], author_ids=author_ids))\n", " return papers" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "47h3IJBbBBkj" }, "outputs": [], "source": [ "def dblp_data(author_id: str) -\u003e [list[Author], list[Paper]]:\n", " xmldata = _load_page(author_id)\n", " return (read_authors(xmldata, author_pid), read_papers(xmldata))\n", "\n", "\n", "def _expand(id2author: list[str]) -\u003e list[str]:\n", " result = []\n", " for author_id in id2author:\n", " authors, _ = dblp_data(author_id)\n", " result.extend([x.id for x in authors])\n", " return list(set(result))\n", "\n", "\n", "def coauthorship_graph(\n", " authors: dict[str, Author], papers: dict[str, Paper]\n", ") -\u003e nx.Graph:\n", " g = nx.Graph()\n", " for a in authors.keys():\n", " g.add_node(a, name=authors[a].name, id=authors[a].id)\n", " # g.add_nodes_from(authors.keys())\n", " for paper in papers.values():\n", " g.add_edges_from(itertools.combinations(paper.author_ids, 2))\n", " return g\n", "\n", "\n", "def author_paper_graph(\n", " authors: dict[str, Author], papers: dict[str, Paper]\n", ") -\u003e nx.Graph:\n", " g = nx.Graph()\n", " g.add_nodes_from(authors.keys())\n", " g.add_nodes_from(papers.keys())\n", " for paper in papers.values():\n", " g.add_edges_from([paper.id, author] for author in paper.author_ids)\n", " return g\n", "\n", "\n", "def dblp_graph(\n", " root_id: str,\n", " n_hops: int = 1,\n", " graph_fn: Callable[\n", " [dict[str, Author], dict[str, Paper]], nx.Graph\n", " ] = coauthorship_graph,\n", ") -\u003e nx.Graph:\n", " author_list = [root_id]\n", " for _ in range(n_hops - 1):\n", " author_list = _expand(author_list)\n", " authors, papers = zip(*[dblp_data(x) for x in author_list])\n", " authors = {author.id: author for author in itertools.chain(*authors)}\n", " papers = {paper.id: paper for paper in itertools.chain(*papers)}\n", " return graph_fn(authors, papers)" ] }, { "cell_type": "markdown", "metadata": { "id": "_og7v0X-lA5V" }, "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "njSRRJLoAapA" }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": { "id": "JUCaM8NDYoMx" }, "source": [ "### Load Talk Like a Graph Library" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "kXymy2QvxdNw" }, "outputs": [], "source": [ "!git clone https://github.com/google-research/talk-like-a-graph.git\n", "import sys\n", "sys.path.insert(0, \"/content/talk-like-a-graph\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Dmb35I-Oxoca" }, "outputs": [], "source": [ "from talk_like_a_graph.graph_generators import generate_graphs\n", "from talk_like_a_graph.graph_tasks import CycleCheck, ShortestPath, NodeCount" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "qmgx9ffcX_2W" }, "outputs": [], "source": [ "import google.generativeai as genai\n", "import os\n", "\n", "genai.configure(api_key=API_KEY)\n", "\n", "### Talk like a Graph code\n", "import random\n", "import networkx as nx\n", "import numpy as np\n", "import tensorflow as tf" ] }, { "cell_type": "markdown", "metadata": { "id": "I88orawwu4BS" }, "source": [ "### Load Saved Data\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "51XDyyJWu5fJ" }, "outputs": [], "source": [ "saved_coauthorship_graph = nx.node_link_graph(json.loads(base64.b64decode(b'''eyJkaXJlY3RlZCI6IGZhbHNlLCAibXVsdGlncmFwaCI6IGZhbHNlLCAiZ3JhcGgiOiB7fSwgIm5vZGVzIjogW3sibmFtZSI6ICJCcnlhbiBQZXJvenppIiwgImlkIjogIjkxLzEwODEzIn0sIHsibmFtZSI6ICJTYW1pIEFidS1FbC1IYWlqYSIsICJpZCI6ICIxMjcvNjYyMCJ9LCB7Im5hbWUiOiAiTGVtYW4gQWtvZ2x1IiwgImlkIjogIjAyLzY5NzkifSwgeyJuYW1lIjogIlJhbWkgQWwtUmZvdSIsICJpZCI6ICIxMjIvMzAzMyJ9LCB7Im5hbWUiOiAiQWxleGFuZGVyIEEgQWxlbWkiLCAiaWQiOiAiMTYwLzgxNTgifSwgeyJuYW1lIjogIk5hemFuaW4gQWxpcG91cmZhcmQiLCAiaWQiOiAiMjAwLzAxNzgifSwgeyJuYW1lIjogIkZpbGlwZSBNaWd1ZWwgR29uXHUwMGU3YWx2ZXMgZGUgQWxtZWlkYSIsICJpZCI6ICIyNzYvNTcwMiJ9LCB7Im5hbWUiOiAiS3lyaWFrb3MgQXhpb3RpcyIsICJpZCI6ICIxNzYvNTEzOSJ9LCB7Im5hbWUiOiAiS2VzaGF2IEJhbGFzdWJyYW1hbmlhbiIsICJpZCI6ICIyNTgvOTEyOCJ9LCB7Im5hbWUiOiAiTWF0dCBCYXJuZXMgIiwgImlkIjogIjE2MC85OTEyIn0sIHsibmFtZSI6ICJNb2hhbW1hZCBIb3NzZWluIEJhdGVuaSAiLCAiaWQiOiAiMjIvNDczOSJ9LCB7Im5hbWUiOiAiUGV0ZXIgVyBCYXR0YWdsaWEiLCAiaWQiOiAiNDEvMzQwMCJ9LCB7Im5hbWUiOiAiU3BlbmNlciBCZWVjaGVyIiwgImlkIjogIjE1NC83ODAzIn0sIHsibmFtZSI6ICJYdWUgQmVuIiwgImlkIjogIjE1OS85NTE3In0sIHsibmFtZSI6ICJNYXJ0aW4gQmxhaXMiLCAiaWQiOiAiMjI1LzQxMTUifSwgeyJuYW1lIjogIkFsZWtzYW5kYXIgQm9qY2hldnNraSIsICJpZCI6ICIyMDMvODExNCJ9LCB7Im5hbWUiOiAiTmVzbGloYW4gQnVsdXQiLCAiaWQiOiAiMTk0LzQ4MjQifSwgeyJuYW1lIjogIk1pY2hhZWwgQnVycm93cyIsICJpZCI6ICI3My82MTUifSwgeyJuYW1lIjogIkthaWRpIENhbyIsICJpZCI6ICIyMDMvODIwNyJ9LCB7Im5hbWUiOiAiSW5lcyBDaGFtaSIsICJpZCI6ICIxODQvMjAyMCJ9LCB7Im5hbWUiOiAiSGFvY2hlbiBDaGVuIiwgImlkIjogIjE3Ni8yNjIwIn0sIHsibmFtZSI6ICJNdWhhbyBDaGVuIiwgImlkIjogIjE3My8yNjA4In0sIHsibmFtZSI6ICJUaW5nIENoZW4iLCAiaWQiOiAiMTkvMTc2NiJ9LCB7Im5hbWUiOiAiWWFucWluZyBDaGVuIiwgImlkIjogIjY4LzE0MjUifSwgeyJuYW1lIjogIlpoaWthaSBDaGVuIiwgImlkIjogIjkyLzQwIn0sIHsibmFtZSI6ICJaaGl5dWFuIENoZW5nICIsICJpZCI6ICI1OC83MjExLTIifSwgeyJuYW1lIjogIkVkIEggQ2hpIiwgImlkIjogIjEzLzMxMCJ9LCB7Im5hbWUiOiAiSm9zaHVhIFYgRGlsbG9uIiwgImlkIjogIjc4Lzg3MDAifSwgeyJuYW1lIjogIkthaXplIERpbmciLCAiaWQiOiAiMjM0LzY4NzgifSwgeyJuYW1lIjogIkppYWxpbiBEb25nIiwgImlkIjogIjIxMS8zOTY4In0sIHsibmFtZSI6ICJBcm5vIEVpZ2Vud2lsbGlnIiwgImlkIjogImUvQXJub0VpZ2Vud2lsbGlnIn0sIHsibmFtZSI6ICJQZXRlciBFbmdsZXJ0IiwgImlkIjogIjEzMi85MTEzIn0sIHsibmFtZSI6ICJBbGVzc2FuZHJvIEVwYXN0byIsICJpZCI6ICI1OC83ODAyIn0sIHsibmFtZSI6ICJIb3NzZWluIEVzZmFuZGlhcmkiLCAiaWQiOiAiMTQ2Lzc3NDYifSwgeyJuYW1lIjogIkJhaGFyZSBGYXRlbWkiLCAiaWQiOiAiMTgyLzIzNTYifSwgeyJuYW1lIjogIk9sZWtzYW5kciBGZXJsdWRpbiIsICJpZCI6ICIzMjQvMjYxMyJ9LCB7Im5hbWUiOiAiSGVuZHJpayBGaWNodGVuYmVyZ2VyIiwgImlkIjogIjEzMy8yMDI5In0sIHsibmFtZSI6ICJFZHVhcmRvIEZsZXVyeSIsICJpZCI6ICIyMTEvODA3MyJ9LCB7Im5hbWUiOiAiS2ltb24gRm91bnRvdWxha2lzIiwgImlkIjogIjE0OS81Nzk5In0sIHsibmFtZSI6ICJBcmFtIEdhbHN0eWFuIiwgImlkIjogIjE2LzM0MTEifSwgeyJuYW1lIjogIkpvaGFubmVzIEdhc3RlaWdlciIsICJpZCI6ICIyMjgvNzg5NyJ9LCB7Im5hbWUiOiAiU3RlcGhhbiBHXHUwMGZjbm5lbWFubiIsICJpZCI6ICI0My8zMDExIn0sIHsibmFtZSI6ICJKYW1lcyBUIEhhbGJlcnQiLCAiaWQiOiAiMTMwLzYzMzAifSwgeyJuYW1lIjogIkpvbmF0aGFuIEhhbGNyb3ciLCAiaWQiOiAiMjcxLzA3NjkifSwgeyJuYW1lIjogIkV0aGFuIEhhbGwiLCAiaWQiOiAiMzc4LzQyOTIifSwgeyJuYW1lIjogIkppYXdlaSBIYW4gIiwgImlkIjogImgvSmlhd2VpSGFuIn0sIHsibmFtZSI6ICJIcmF5ciBIYXJ1dHl1bnlhbiIsICJpZCI6ICIxOTgvMTQ2NSJ9LCB7Im5hbWUiOiAiRGFrZSBIZSIsICJpZCI6ICI0Ni81NDY2In0sIHsibmFtZSI6ICJaaWhhbyBIZSIsICJpZCI6ICIyNDYvMjk2MSJ9LCB7Im5hbWUiOiAiTGljaGFuIEhvbmciLCAiaWQiOiAiODUvNDY5NyJ9LCB7Im5hbWUiOiAiWWlmYW4gSHUgIiwgImlkIjogIjkyLzQ5OC0xIn0sIHsibmFtZSI6ICJaaW5pdSBIdSIsICJpZCI6ICIxODAvNTQzNiJ9LCB7Im5hbWUiOiAiRGkgSHVhbmciLCAiaWQiOiAiNDUvNzgwIn0sIHsibmFtZSI6ICJZdXpob25nIEh1YW5nIiwgImlkIjogIjIxMy84MDgwIn0sIHsibmFtZSI6ICJZaXpodSBKaWFvIiwgImlkIjogIjI1MC85NzU3In0sIHsibmFtZSI6ICJXZWkgSmluICIsICJpZCI6ICI2Ni8yMTczLTkifSwgeyJuYW1lIjogIkFtb2wgS2Fwb29yIiwgImlkIjogIjIxNS81MDE0In0sIHsibmFtZSI6ICJNZWhyYW4gS2F6ZW1pIiwgImlkIjogIjE0OS8xMjgzIn0sIHsibmFtZSI6ICJWaXZlayBLdWxrYXJuaSIsICJpZCI6ICI5Mi85OTIyIn0sIHsibmFtZSI6ICJTaWx2aW8gTGF0dGFuemkiLCAiaWQiOiAiNDYvNjYxMSJ9LCB7Im5hbWUiOiAiSm9vbnNlb2sgTGVlIiwgImlkIjogIjc3LzEzMTkifSwgeyJuYW1lIjogIktyaXN0aW5hIExlcm1hbiIsICJpZCI6ICI5OS80MzMifSwgeyJuYW1lIjogIkp1cmUgTGVza292ZWMiLCAiaWQiOiAibC9KdXJlTGVza292ZWMifSwgeyJuYW1lIjogIkFuZHJldyBMZXZpbmUiLCAiaWQiOiAiMDYvMTA4MTQifSwgeyJuYW1lIjogIkJpbmdoZW5nIExpIiwgImlkIjogIjIzMS8xOTA0In0sIHsibmFtZSI6ICJXYWkgTG9rIFNpYm9uIExpIiwgImlkIjogIjI1OC81MDAzIn0sIHsibmFtZSI6ICJBbGJlcnQgSmlvbmdxaWFuIExpYW5nIiwgImlkIjogIjM0OC82NDk4In0sIHsibmFtZSI6ICJBbmRyXHUwMGU5IExpbmhhcmVzIiwgImlkIjogIjEzNy8zMjUyIn0sIHsibmFtZSI6ICJIdWFuIExpdSAiLCAiaWQiOiAiOTIvMzA5LTEifSwgeyJuYW1lIjogIkh1aSBMaXUgIiwgImlkIjogIjkzLzQwMTAtMzEifSwgeyJuYW1lIjogIkppbmd6aGUgTGl1IiwgImlkIjogIjMzOS83NTU4In0sIHsibmFtZSI6ICJMdXlhbmcgTGl1IiwgImlkIjogIjY5LzgxNjcifSwgeyJuYW1lIjogIk1pY2hhbCBMdWthc2lrIiwgImlkIjogIjcyLzExMzM4In0sIHsibmFtZSI6ICJLYXJpc2htYSBNYWxrYW4iLCAiaWQiOiAiMjYzLzk5NTkifSwgeyJuYW1lIjogIkhhaXRhbyBNYW8iLCAiaWQiOiAiMjIxLzY1MTYifSwgeyJuYW1lIjogIkVsYW4gTWFya293aXR6IiwgImlkIjogIjI4NC85NDAxIn0sIHsibmFtZSI6ICJCcmFuZG9uIEEgTWF5ZXIiLCAiaWQiOiAiMjcvMTEyNjEifSwgeyJuYW1lIjogIkNocmlzIE1jQ3ViYmluIiwgImlkIjogIjI5Ny8xODIxIn0sIHsibmFtZSI6ICJDaGFyaXRoIE1lbmRpcyIsICJpZCI6ICIxNjMvMzE3NSJ9LCB7Im5hbWUiOiAiVmFoYWIgUyBNaXJyb2tuaSIsICJpZCI6ICJtL1ZhaGFiU01pcnJva25pIn0sIHsibmFtZSI6ICJNZWhybm9vc2ggTWlydGFoZXJpIiwgImlkIjogIjIzNi80NDQ5In0sIHsibmFtZSI6ICJGcmVkIE1vcnN0YXR0ZXIiLCAiaWQiOiAiNTEvOTY4NyJ9LCB7Im5hbWUiOiAiQWxleGFuZHJ1IE1vc29pIiwgImlkIjogIjI3MS8xMDI1In0sIHsibmFtZSI6ICJFbW1hbnVlbCBNXHUwMGZjbGxlciIsICJpZCI6ICI1NS82ODk5In0sIHsibmFtZSI6ICJNYXJpbmEgTXVua2hvZXZhIiwgImlkIjogIjIxNS8zNjgyIn0sIHsibmFtZSI6ICJLZXZpbiBNdXJwaHkgIiwgImlkIjogIjI2LzI1OTkifSwgeyJuYW1lIjogIlNoYXduIE9CYW5pb24iLCAiaWQiOiAiMTI3Lzc0OTcifSwgeyJuYW1lIjogIkpvaG4gUGFsb3dpdGNoIiwgImlkIjogIjE3NS8xMjMxIn0sIHsibmFtZSI6ICJNaWhpciBQYXJhZGthciIsICJpZCI6ICIyMDUvMzkwOCJ9LCB7Im5hbWUiOiAiSmFuIFBmZWlmZXIiLCAiaWQiOiAiMTYzLzIzMzkifSwgeyJuYW1lIjogIk1hbmdwbyBNYW5ncG8gUGhvdGhpbGltdGhhbmEiLCAiaWQiOiAiMzY5Lzc2ODkifSwgeyJuYW1lIjogIlBoaXRjaGF5YSBNYW5ncG8gUGhvdGhpbGltdGhhbmEiLCAiaWQiOiAiMTI3LzMxMjgifSwgeyJuYW1lIjogIk5hdGFsaWEgUG9ub21hcmV2YSAiLCAiaWQiOiAiNzEvNjc2OC0xIn0sIHsibmFtZSI6ICJTdGVmYW4gUG9zdGF2YXJ1IiwgImlkIjogIjE5NC8xNTcwIn0sIHsibmFtZSI6ICJBYmR1bCBSYWhtYW4iLCAiaWQiOiAiODcvMTAxMDAifSwgeyJuYW1lIjogIkRlZXBhayBSYW1hY2hhbmRyYW4iLCAiaWQiOiAiODAvNzAzIn0sIHsibmFtZSI6ICJDaHJpc3RvcGhlciBSXHUwMGU5IiwgImlkIjogInIvQ2hyaXN0b3BoZXJSZSJ9LCB7Im5hbWUiOiAiQXJpYSBSZXphZWkiLCAiaWQiOiAiMTYwLzg2NzcifSwgeyJuYW1lIjogIkJlbmVkZWsgUm96ZW1iZXJjemtpIiwgImlkIjogIjIxNS8zNzQyIn0sIHsibmFtZSI6ICJTYW0gUnV0aCIsICJpZCI6ICIyNzEvMDYzNyJ9LCB7Im5hbWUiOiAiSmFjayBTYWFsd2VhY2h0ZXIiLCAiaWQiOiAiMTg0LzAwMzMifSwgeyJuYW1lIjogIlJ1c2xhbiBTYWxha2h1dGRpbm92IiwgImlkIjogIjYyLzU4ODQifSwgeyJuYW1lIjogIlBhdHJpY2lhIElnbGVzaWFzIFNcdTAwZTFuY2hleiIsICJpZCI6ICIxMzEvNzE2OSJ9LCB7Im5hbWUiOiAiQWx2YXJvIFNhbmNoZXotR29uemFsZXoiLCAiaWQiOiAiMjIyLzE4ODkifSwgeyJuYW1lIjogIkNsYXl0b24gU2FuZm9yZCIsICJpZCI6ICIyMzIvMTc5NyJ9LCB7Im5hbWUiOiAiTWljaGFlbCBTY2h1ZXBwZXJ0IiwgImlkIjogIjE2OS8xNzQyIn0sIHsibmFtZSI6ICJTdW5neW9uZyBTZW8iLCAiaWQiOiAiMTc4LzMyMDkifSwgeyJuYW1lIjogIkplbm5pZmVyIFNoZSIsICJpZCI6ICIyNDMvNTc2MCJ9LCB7Im5hbWUiOiAiU3RldmVuIFNraWVuYSIsICJpZCI6ICJzL1N0ZXZlblNraWVuYSJ9LCB7Im5hbWUiOiAiR3JlZyBWZXIgU3RlZWciLCAiaWQiOiAiODIvOTA1OCJ9LCB7Im5hbWUiOiAiS2V4dWFuIFN1biAiLCAiaWQiOiAiMTE1LzY0NjYtMiJ9LCB7Im5hbWUiOiAiWGlhb2ZlaSBTdW4iLCAiaWQiOiAiODcvNzI5NyJ9LCB7Im5hbWUiOiAiSmlsaWFuZyBUYW5nIiwgImlkIjogIjY0LzEwODEyIn0sIHsibmFtZSI6ICJNYXl1ciBUaGFrdXIiLCAiaWQiOiAiNTgvNTQ5MiJ9LCB7Im5hbWUiOiAiWWluZ3RhbyBUaWFuIiwgImlkIjogIjE4MC81MzM1In0sIHsibmFtZSI6ICJMb25nIFRyYW4tVGhhbmgiLCAiaWQiOiAiNDYvODMzMyJ9LCB7Im5hbWUiOiAiQW50b24gVHNpdHN1bGluIiwgImlkIjogIjIxNy8xNjY4In0sIHsibmFtZSI6ICJLZXZpbiBWaWxsZWxhIiwgImlkIjogIjMyNC8yNTY3In0sIHsibmFtZSI6ICJMaXNhIFdhbmciLCAiaWQiOiAiMTk4Lzc0MjMifSwgeyJuYW1lIjogIlJ1b3hpIFdhbmciLCAiaWQiOiAiNzIvMTEyNDQifSwgeyJuYW1lIjogIkRhdmlkIFdvbmciLCAiaWQiOiAiNDcvMzE4In0sIHsibmFtZSI6ICJZdWUgV3UgIiwgImlkIjogIjQxLzU5NzktMSJ9LCB7Im5hbWUiOiAiTGluIFlhbmcgIiwgImlkIjogIjE2Ni82MjY0In0sIHsibmFtZSI6ICJTaGVuZ2hhbyBZYW5nICIsICJpZCI6ICI0MS80NDgyLTIifSwgeyJuYW1lIjogIk11c3RhZmEgWWFzaXIiLCAiaWQiOiAiMzUyLzQyMzcifSwgeyJuYW1lIjogIkppbnllb25nIFlpbSIsICJpZCI6ICIxNzYvNDIwNSJ9LCB7Im5hbWUiOiAiTWluamkgWW9vbiIsICJpZCI6ICIyMDUvMjY1MSJ9LCB7Im5hbWUiOiAiU29uZyBZdSIsICJpZCI6ICI1NC8xMjE2In0sIHsibmFtZSI6ICJEdXN0aW4gWmVsbGUiLCAiaWQiOiAiMjM5LzYwNzAifSwgeyJuYW1lIjogIlBlaWxpbiBaaG9uZyIsICJpZCI6ICIxNDgvOTYzMiJ9LCB7Im5hbWUiOiAiWWFucWkgWmhvdSIsICJpZCI6ICIxNDIvMzIwMiJ9LCB7Im5hbWUiOiAiUWkgWmh1ICIsICJpZCI6ICI2Ni81OTIzLTgifV0sICJsaW5rcyI6IFt7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMTgyLzIzNTYifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjI3MS8wNzY5In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIyMzkvNjA3MCJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjE0OS8xMjgzIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxMjIvMzAzMyJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMjExLzM5NjgifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjE2Ni82MjY0In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIyMzIvMTc5NyJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMzc4LzQyOTIifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIm0vVmFoYWJTTWlycm9rbmkifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjI2My85OTU5In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxNzYvNDIwNSJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMTc1LzEyMzEifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjE3OC8zMjA5In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI5Mi80MCJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMjIxLzY1MTYifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjMzOS83NTU4In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI1NC8xMjE2In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIyMzEvMTkwNCJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiNjYvMjE3My05In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI5My80MDEwLTMxIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI2NC8xMDgxMiJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiNTUvNjg5OSJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMTQ2Lzc3NDYifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjIyLzQ3MzkifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjgwLzcwMyJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMjA1LzI2NTEifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjQxLzU5NzktMSJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiNjIvNTg4NCJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMjcvMTEyNjEifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjEzMy8yMDI5In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxMjcvNjYyMCJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMjAzLzgyMDcifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjEyNy8zMTI4In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxNDIvMzIwMiJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMTYzLzMxNzUifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogImwvSnVyZUxlc2tvdmVjIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIzNjkvNzY4OSJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiNzMvNjE1In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIyMzQvNjg3OCJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMzQ4LzY0OTgifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjE5LzE3NjYifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjcyLzExMjQ0In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI4NS80Njk3In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxMy8zMTAifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjkyLzMwOS0xIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI1OC83MjExLTIifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjIxNS8zNjgyIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI3OC84NzAwIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxNzYvNTEzOSJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMTk0LzQ4MjQifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjIyOC83ODk3In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI2Ni81OTIzLTgifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjI1MC85NzU3In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI3MS82NzY4LTEifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogImgvSmlhd2VpSGFuIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIzNTIvNDIzNyJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiNDYvODMzMyJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMTg0LzIwMjAifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogInIvQ2hyaXN0b3BoZXJSZSJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMjYvMjU5OSJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiNTgvNzgwMiJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMTQ4Lzk2MzIifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjE4MC81NDM2In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIyMTUvMzc0MiJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMzI0LzI2MTMifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogImUvQXJub0VpZ2Vud2lsbGlnIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIyMjUvNDExNSJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMTYzLzIzMzkifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjIyMi8xODg5In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIyNTgvNTAwMyJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiNDEvMzQwMCJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMjc2LzU3MDIifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjQ2LzY2MTEifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjEzNy8zMjUyIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIyMDUvMzkwOCJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMjQzLzU3NjAifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjMyNC8yNTY3In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxOTgvNzQyMyJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiNDcvMzE4In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxNDkvNTc5OSJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiNDYvNTQ2NiJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiNDEvNDQ4Mi0yIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIyODQvOTQwMSJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMjU4LzkxMjgifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjIzNi80NDQ5In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI4Mi85MDU4In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxNi8zNDExIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxMzIvOTExMyJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMjE1LzUwMTQifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjIwMy84MTE0In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI3Mi8xMTMzOCJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiNDMvMzAxMSJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMjcxLzEwMjUifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjI3MS8wNjM3In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI0NS83ODAifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjI0Ni8yOTYxIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIyMTMvODA4MCJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMTE1LzY0NjYtMiJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiOTkvNDMzIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI1MS85Njg3In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxNTkvOTUxNyJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiNjkvODE2NyJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMTYwLzk5MTIifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjEyNy83NDk3In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxOTQvMTU3MCJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMTgwLzUzMzUifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjE3Ni8yNjIwIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxNzMvMjYwOCJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiODcvNzI5NyJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAicy9TdGV2ZW5Ta2llbmEifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjIwMC8wMTc4In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxOTgvMTQ2NSJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiNzcvMTMxOSJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMDIvNjk3OSJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiOTIvNDk4LTEifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjE2MC84MTU4In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI2OC8xNDI1In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI5Mi85OTIyIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxNjAvODY3NyJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMjExLzgwNzMifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjE2OS8xNzQyIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxODQvMDAzMyJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiNTgvNTQ5MiJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMjk3LzE4MjEifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjEzMC82MzMwIn0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICIxMzEvNzE2OSJ9LCB7InNvdXJjZSI6ICI5MS8xMDgxMyIsICJ0YXJnZXQiOiAiMTU0Lzc4MDMifSwgeyJzb3VyY2UiOiAiOTEvMTA4MTMiLCAidGFyZ2V0IjogIjA2LzEwODE0In0sIHsic291cmNlIjogIjkxLzEwODEzIiwgInRhcmdldCI6ICI4Ny8xMDEwMCJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIjIwMy84MjA3In0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICIxMjcvMzEyOCJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAiMjM5LzYwNzAifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIjE0Mi8zMjAyIn0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICIxNjMvMzE3NSJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAibC9KdXJlTGVza292ZWMifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIjM2OS83Njg5In0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICIxODIvMjM1NiJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAiNzMvNjE1In0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICI3OC84NzAwIn0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICIxNzYvNTEzOSJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAiMTk0LzQ4MjQifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIjIyOC83ODk3In0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICIyMi80NzM5In0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICIxNDkvMTI4MyJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAiMjcxLzA3NjkifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIjE4NC8yMDIwIn0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICJyL0NocmlzdG9waGVyUmUifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIjI2LzI1OTkifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIjMyNC8yNjEzIn0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICJlL0Fybm9FaWdlbndpbGxpZyJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAiMjI1LzQxMTUifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIjE2My8yMzM5In0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICIyMjIvMTg4OSJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAiMjU4LzUwMDMifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIjQxLzM0MDAifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIjI3Ni81NzAyIn0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICI0Ni82NjExIn0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICIxMzcvMzI1MiJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAiMjcvMTEyNjEifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIm0vVmFoYWJTTWlycm9rbmkifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIjE3NS8xMjMxIn0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICIyMDUvMzkwOCJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAiMjQzLzU3NjAifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIjMyNC8yNTY3In0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICIxOTgvNzQyMyJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAiNDcvMzE4In0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICIyODQvOTQwMSJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAiMjU4LzkxMjgifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIjIzNi80NDQ5In0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICI4Mi85MDU4In0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICIxNi8zNDExIn0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICI0NS83ODAifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIjI0Ni8yOTYxIn0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICIyMTMvODA4MCJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAiMTE1LzY0NjYtMiJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAiOTkvNDMzIn0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICI1MS85Njg3In0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICIyMTUvNTAxNCJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAiMjAwLzAxNzgifSwgeyJzb3VyY2UiOiAiMTI3LzY2MjAiLCAidGFyZ2V0IjogIjE5OC8xNDY1In0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICI3Ny8xMzE5In0sIHsic291cmNlIjogIjEyNy82NjIwIiwgInRhcmdldCI6ICIxMjIvMzAzMyJ9LCB7InNvdXJjZSI6ICIxMjcvNjYyMCIsICJ0YXJnZXQiOiAiMTYwLzgxNTgifSwgeyJzb3VyY2UiOiAiMDIvNjk3OSIsICJ0YXJnZXQiOiAiMTYwLzg2NzcifSwgeyJzb3VyY2UiOiAiMDIvNjk3OSIsICJ0YXJnZXQiOiAiMTMxLzcxNjkifSwgeyJzb3VyY2UiOiAiMDIvNjk3OSIsICJ0YXJnZXQiOiAiNTUvNjg5OSJ9LCB7InNvdXJjZSI6ICIxMjIvMzAzMyIsICJ0YXJnZXQiOiAiMTgyLzIzNTYifSwgeyJzb3VyY2UiOiAiMTIyLzMwMzMiLCAidGFyZ2V0IjogIjIzOS82MDcwIn0sIHsic291cmNlIjogIjEyMi8zMDMzIiwgInRhcmdldCI6ICIyMTcvMTY2OCJ9LCB7InNvdXJjZSI6ICIxMjIvMzAzMyIsICJ0YXJnZXQiOiAiMTQ5LzEyODMifSwgeyJzb3VyY2UiOiAiMTIyLzMwMzMiLCAidGFyZ2V0IjogIjI3MS8wNzY5In0sIHsic291cmNlIjogIjEyMi8zMDMzIiwgInRhcmdldCI6ICIxNjAvODE1OCJ9LCB7InNvdXJjZSI6ICIxMjIvMzAzMyIsICJ0YXJnZXQiOiAiMTc2LzI2MjAifSwgeyJzb3VyY2UiOiAiMTIyLzMwMzMiLCAidGFyZ2V0IjogInMvU3RldmVuU2tpZW5hIn0sIHsic291cmNlIjogIjEyMi8zMDMzIiwgInRhcmdldCI6ICI5Mi85OTIyIn0sIHsic291cmNlIjogIjEyMi8zMDMzIiwgInRhcmdldCI6ICI2OC8xNDI1In0sIHsic291cmNlIjogIjIwMC8wMTc4IiwgInRhcmdldCI6ICIyMTUvNTAxNCJ9LCB7InNvdXJjZSI6ICIyMDAvMDE3OCIsICJ0YXJnZXQiOiAiOTkvNDMzIn0sIHsic291cmNlIjogIjIwMC8wMTc4IiwgInRhcmdldCI6ICIxOTgvMTQ2NSJ9LCB7InNvdXJjZSI6ICIyMDAvMDE3OCIsICJ0YXJnZXQiOiAiODIvOTA1OCJ9LCB7InNvdXJjZSI6ICIyMDAvMDE3OCIsICJ0YXJnZXQiOiAiMTYvMzQxMSJ9LCB7InNvdXJjZSI6ICIyNzYvNTcwMiIsICJ0YXJnZXQiOiAiMzI0LzI2MTMifSwgeyJzb3VyY2UiOiAiMjc2LzU3MDIiLCAidGFyZ2V0IjogImUvQXJub0VpZ2Vud2lsbGlnIn0sIHsic291cmNlIjogIjI3Ni81NzAyIiwgInRhcmdldCI6ICIyMjUvNDExNSJ9LCB7InNvdXJjZSI6ICIyNzYvNTcwMiIsICJ0YXJnZXQiOiAiMjM5LzYwNzAifSwgeyJzb3VyY2UiOiAiMjc2LzU3MDIiLCAidGFyZ2V0IjogIjE2My8yMzM5In0sIHsic291cmNlIjogIjI3Ni81NzAyIiwgInRhcmdldCI6ICIyMjIvMTg4OSJ9LCB7InNvdXJjZSI6ICIyNzYvNTcwMiIsICJ0YXJnZXQiOiAiMjU4LzUwMDMifSwgeyJzb3VyY2UiOiAiMjc2LzU3MDIiLCAidGFyZ2V0IjogIjQxLzM0MDAifSwgeyJzb3VyY2UiOiAiMjc2LzU3MDIiLCAidGFyZ2V0IjogIjE5NC80ODI0In0sIHsic291cmNlIjogIjI3Ni81NzAyIiwgInRhcmdldCI6ICIyNzEvMDc2OSJ9LCB7InNvdXJjZSI6ICIyNzYvNTcwMiIsICJ0YXJnZXQiOiAiNDYvNjYxMSJ9LCB7InNvdXJjZSI6ICIyNzYvNTcwMiIsICJ0YXJnZXQiOiAiMTM3LzMyNTIifSwgeyJzb3VyY2UiOiAiMjc2LzU3MDIiLCAidGFyZ2V0IjogIjI3LzExMjYxIn0sIHsic291cmNlIjogIjI3Ni81NzAyIiwgInRhcmdldCI6ICJtL1ZhaGFiU01pcnJva25pIn0sIHsic291cmNlIjogIjI3Ni81NzAyIiwgInRhcmdldCI6ICIxNzUvMTIzMSJ9LCB7InNvdXJjZSI6ICIyNzYvNTcwMiIsICJ0YXJnZXQiOiAiMjA1LzM5MDgifSwgeyJzb3VyY2UiOiAiMjc2LzU3MDIiLCAidGFyZ2V0IjogIjI0My81NzYwIn0sIHsic291cmNlIjogIjI3Ni81NzAyIiwgInRhcmdldCI6ICIyMTcvMTY2OCJ9LCB7InNvdXJjZSI6ICIyNzYvNTcwMiIsICJ0YXJnZXQiOiAiMzI0LzI1NjcifSwgeyJzb3VyY2UiOiAiMjc2LzU3MDIiLCAidGFyZ2V0IjogIjE5OC83NDIzIn0sIHsic291cmNlIjogIjI3Ni81NzAyIiwgInRhcmdldCI6ICI0Ny8zMTgifSwgeyJzb3VyY2UiOiAiMjc2LzU3MDIiLCAidGFyZ2V0IjogIjE5NC8xNTcwIn0sIHsic291cmNlIjogIjI3Ni81NzAyIiwgInRhcmdldCI6ICIxODAvNTMzNSJ9LCB7InNvdXJjZSI6ICIxNzYvNTEzOSIsICJ0YXJnZXQiOiAiNzgvODcwMCJ9LCB7InNvdXJjZSI6ICIxNzYvNTEzOSIsICJ0YXJnZXQiOiAiMTgyLzIzNTYifSwgeyJzb3VyY2UiOiAiMTc2LzUxMzkiLCAidGFyZ2V0IjogIjE5NC80ODI0In0sIHsic291cmNlIjogIjE3Ni81MTM5IiwgInRhcmdldCI6ICIyMjgvNzg5NyJ9LCB7InNvdXJjZSI6ICIxNzYvNTEzOSIsICJ0YXJnZXQiOiAiMjIvNDczOSJ9LCB7InNvdXJjZSI6ICIyNTgvOTEyOCIsICJ0YXJnZXQiOiAiMjg0Lzk0MDEifSwgeyJzb3VyY2UiOiAiMjU4LzkxMjgiLCAidGFyZ2V0IjogIjIzNi80NDQ5In0sIHsic291cmNlIjogIjI1OC85MTI4IiwgInRhcmdldCI6ICI4Mi85MDU4In0sIHsic291cmNlIjogIjI1OC85MTI4IiwgInRhcmdldCI6ICIxNi8zNDExIn0sIHsic291cmNlIjogIjE2MC85OTEyIiwgInRhcmdldCI6ICIyMTUvNTAxNCJ9LCB7InNvdXJjZSI6ICIxNjAvOTkxMiIsICJ0YXJnZXQiOiAiMTU5Lzk1MTcifSwgeyJzb3VyY2UiOiAiMTYwLzk5MTIiLCAidGFyZ2V0IjogIjY5LzgxNjcifSwgeyJzb3VyY2UiOiAiMTYwLzk5MTIiLCAidGFyZ2V0IjogIjIyNS80MTE1In0sIHsic291cmNlIjogIjE2MC85OTEyIiwgInRhcmdldCI6ICIxMjcvNzQ5NyJ9LCB7InNvdXJjZSI6ICIyMi80NzM5IiwgInRhcmdldCI6ICIxNDkvMTI4MyJ9LCB7InNvdXJjZSI6ICIyMi80NzM5IiwgInRhcmdldCI6ICIyMTcvMTY2OCJ9LCB7InNvdXJjZSI6ICIyMi80NzM5IiwgInRhcmdldCI6ICIxNDYvNzc0NiJ9LCB7InNvdXJjZSI6ICIyMi80NzM5IiwgInRhcmdldCI6ICI4MC83MDMifSwgeyJzb3VyY2UiOiAiMjIvNDczOSIsICJ0YXJnZXQiOiAibS9WYWhhYlNNaXJyb2tuaSJ9LCB7InNvdXJjZSI6ICIyMi80NzM5IiwgInRhcmdldCI6ICI3OC84NzAwIn0sIHsic291cmNlIjogIjIyLzQ3MzkiLCAidGFyZ2V0IjogIjE4Mi8yMzU2In0sIHsic291cmNlIjogIjIyLzQ3MzkiLCAidGFyZ2V0IjogIjE5NC80ODI0In0sIHsic291cmNlIjogIjIyLzQ3MzkiLCAidGFyZ2V0IjogIjIyOC83ODk3In0sIHsic291cmNlIjogIjQxLzM0MDAiLCAidGFyZ2V0IjogIjMyNC8yNjEzIn0sIHsic291cmNlIjogIjQxLzM0MDAiLCAidGFyZ2V0IjogImUvQXJub0VpZ2Vud2lsbGlnIn0sIHsic291cmNlIjogIjQxLzM0MDAiLCAidGFyZ2V0IjogIjIyNS80MTE1In0sIHsic291cmNlIjogIjQxLzM0MDAiLCAidGFyZ2V0IjogIjIzOS82MDcwIn0sIHsic291cmNlIjogIjQxLzM0MDAiLCAidGFyZ2V0IjogIjE2My8yMzM5In0sIHsic291cmNlIjogIjQxLzM0MDAiLCAidGFyZ2V0IjogIjIyMi8xODg5In0sIHsic291cmNlIjogIjQxLzM0MDAiLCAidGFyZ2V0IjogIjI1OC81MDAzIn0sIHsic291cmNlIjogIjQxLzM0MDAiLCAidGFyZ2V0IjogIjE5NC80ODI0In0sIHsic291cmNlIjogIjQxLzM0MDAiLCAidGFyZ2V0IjogIjI3MS8wNzY5In0sIHsic291cmNlIjogIjQxLzM0MDAiLCAidGFyZ2V0IjogIjQ2LzY2MTEifSwgeyJzb3VyY2UiOiAiNDEvMzQwMCIsICJ0YXJnZXQiOiAiMTM3LzMyNTIifSwgeyJzb3VyY2UiOiAiNDEvMzQwMCIsICJ0YXJnZXQiOiAiMjcvMTEyNjEifSwgeyJzb3VyY2UiOiAiNDEvMzQwMCIsICJ0YXJnZXQiOiAibS9WYWhhYlNNaXJyb2tuaSJ9LCB7InNvdXJjZSI6ICI0MS8zNDAwIiwgInRhcmdldCI6ICIxNzUvMTIzMSJ9LCB7InNvdXJjZSI6ICI0MS8zNDAwIiwgInRhcmdldCI6ICIyMDUvMzkwOCJ9LCB7InNvdXJjZSI6ICI0MS8zNDAwIiwgInRhcmdldCI6ICIyNDMvNTc2MCJ9LCB7InNvdXJjZSI6ICI0MS8zNDAwIiwgInRhcmdldCI6ICIyMTcvMTY2OCJ9LCB7InNvdXJjZSI6ICI0MS8zNDAwIiwgInRhcmdldCI6ICIzMjQvMjU2NyJ9LCB7InNvdXJjZSI6ICI0MS8zNDAwIiwgInRhcmdldCI6ICIxOTgvNzQyMyJ9LCB7InNvdXJjZSI6ICI0MS8zNDAwIiwgInRhcmdldCI6ICI0Ny8zMTgifSwgeyJzb3VyY2UiOiAiMTU0Lzc4MDMiLCAidGFyZ2V0IjogIjI5Ny8xODIxIn0sIHsic291cmNlIjogIjE1NC83ODAzIiwgInRhcmdldCI6ICIxMzAvNjMzMCJ9LCB7InNvdXJjZSI6ICIxNTkvOTUxNyIsICJ0YXJnZXQiOiAiMjE1LzUwMTQifSwgeyJzb3VyY2UiOiAiMTU5Lzk1MTciLCAidGFyZ2V0IjogIjY5LzgxNjcifSwgeyJzb3VyY2UiOiAiMTU5Lzk1MTciLCAidGFyZ2V0IjogIjIyNS80MTE1In0sIHsic291cmNlIjogIjE1OS85NTE3IiwgInRhcmdldCI6ICIxMjcvNzQ5NyJ9LCB7InNvdXJjZSI6ICIyMjUvNDExNSIsICJ0YXJnZXQiOiAiMzI0LzI2MTMifSwgeyJzb3VyY2UiOiAiMjI1LzQxMTUiLCAidGFyZ2V0IjogImUvQXJub0VpZ2Vud2lsbGlnIn0sIHsic291cmNlIjogIjIyNS80MTE1IiwgInRhcmdldCI6ICIyMzkvNjA3MCJ9LCB7InNvdXJjZSI6ICIyMjUvNDExNSIsICJ0YXJnZXQiOiAiMTYzLzIzMzkifSwgeyJzb3VyY2UiOiAiMjI1LzQxMTUiLCAidGFyZ2V0IjogIjIyMi8xODg5In0sIHsic291cmNlIjogIjIyNS80MTE1IiwgInRhcmdldCI6ICIyNTgvNTAwMyJ9LCB7InNvdXJjZSI6ICIyMjUvNDExNSIsICJ0YXJnZXQiOiAiMTk0LzQ4MjQifSwgeyJzb3VyY2UiOiAiMjI1LzQxMTUiLCAidGFyZ2V0IjogIjI3MS8wNzY5In0sIHsic291cmNlIjogIjIyNS80MTE1IiwgInRhcmdldCI6ICI0Ni82NjExIn0sIHsic291cmNlIjogIjIyNS80MTE1IiwgInRhcmdldCI6ICIxMzcvMzI1MiJ9LCB7InNvdXJjZSI6ICIyMjUvNDExNSIsICJ0YXJnZXQiOiAiMjcvMTEyNjEifSwgeyJzb3VyY2UiOiAiMjI1LzQxMTUiLCAidGFyZ2V0IjogIm0vVmFoYWJTTWlycm9rbmkifSwgeyJzb3VyY2UiOiAiMjI1LzQxMTUiLCAidGFyZ2V0IjogIjE3NS8xMjMxIn0sIHsic291cmNlIjogIjIyNS80MTE1IiwgInRhcmdldCI6ICIyMDUvMzkwOCJ9LCB7InNvdXJjZSI6ICIyMjUvNDExNSIsICJ0YXJnZXQiOiAiMjQzLzU3NjAifSwgeyJzb3VyY2UiOiAiMjI1LzQxMTUiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjIyNS80MTE1IiwgInRhcmdldCI6ICIzMjQvMjU2NyJ9LCB7InNvdXJjZSI6ICIyMjUvNDExNSIsICJ0YXJnZXQiOiAiMTk4Lzc0MjMifSwgeyJzb3VyY2UiOiAiMjI1LzQxMTUiLCAidGFyZ2V0IjogIjQ3LzMxOCJ9LCB7InNvdXJjZSI6ICIyMjUvNDExNSIsICJ0YXJnZXQiOiAiMjE1LzM3NDIifSwgeyJzb3VyY2UiOiAiMjI1LzQxMTUiLCAidGFyZ2V0IjogIjEzMi85MTEzIn0sIHsic291cmNlIjogIjIyNS80MTE1IiwgInRhcmdldCI6ICIyMTUvNTAxNCJ9LCB7InNvdXJjZSI6ICIyMjUvNDExNSIsICJ0YXJnZXQiOiAiMjAzLzgxMTQifSwgeyJzb3VyY2UiOiAiMjI1LzQxMTUiLCAidGFyZ2V0IjogIjIyOC83ODk3In0sIHsic291cmNlIjogIjIyNS80MTE1IiwgInRhcmdldCI6ICI3Mi8xMTMzOCJ9LCB7InNvdXJjZSI6ICIyMjUvNDExNSIsICJ0YXJnZXQiOiAiNDMvMzAxMSJ9LCB7InNvdXJjZSI6ICIyMjUvNDExNSIsICJ0YXJnZXQiOiAiNjkvODE2NyJ9LCB7InNvdXJjZSI6ICIyMjUvNDExNSIsICJ0YXJnZXQiOiAiMTI3Lzc0OTcifSwgeyJzb3VyY2UiOiAiMjAzLzgxMTQiLCAidGFyZ2V0IjogIjIyOC83ODk3In0sIHsic291cmNlIjogIjIwMy84MTE0IiwgInRhcmdldCI6ICIyMTUvNTAxNCJ9LCB7InNvdXJjZSI6ICIyMDMvODExNCIsICJ0YXJnZXQiOiAiMjE1LzM3NDIifSwgeyJzb3VyY2UiOiAiMjAzLzgxMTQiLCAidGFyZ2V0IjogIjcyLzExMzM4In0sIHsic291cmNlIjogIjIwMy84MTE0IiwgInRhcmdldCI6ICI0My8zMDExIn0sIHsic291cmNlIjogIjE5NC80ODI0IiwgInRhcmdldCI6ICI3OC84NzAwIn0sIHsic291cmNlIjogIjE5NC80ODI0IiwgInRhcmdldCI6ICIxODIvMjM1NiJ9LCB7InNvdXJjZSI6ICIxOTQvNDgyNCIsICJ0YXJnZXQiOiAiMjI4Lzc4OTcifSwgeyJzb3VyY2UiOiAiMTk0LzQ4MjQiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjE5NC80ODI0IiwgInRhcmdldCI6ICIxNDkvMTI4MyJ9LCB7InNvdXJjZSI6ICIxOTQvNDgyNCIsICJ0YXJnZXQiOiAiMjM5LzYwNzAifSwgeyJzb3VyY2UiOiAiMTk0LzQ4MjQiLCAidGFyZ2V0IjogIjI3MS8wNzY5In0sIHsic291cmNlIjogIjE5NC80ODI0IiwgInRhcmdldCI6ICIzMjQvMjYxMyJ9LCB7InNvdXJjZSI6ICIxOTQvNDgyNCIsICJ0YXJnZXQiOiAiZS9Bcm5vRWlnZW53aWxsaWcifSwgeyJzb3VyY2UiOiAiMTk0LzQ4MjQiLCAidGFyZ2V0IjogIjE2My8yMzM5In0sIHsic291cmNlIjogIjE5NC80ODI0IiwgInRhcmdldCI6ICIyMjIvMTg4OSJ9LCB7InNvdXJjZSI6ICIxOTQvNDgyNCIsICJ0YXJnZXQiOiAiMjU4LzUwMDMifSwgeyJzb3VyY2UiOiAiMTk0LzQ4MjQiLCAidGFyZ2V0IjogIjQ2LzY2MTEifSwgeyJzb3VyY2UiOiAiMTk0LzQ4MjQiLCAidGFyZ2V0IjogIjEzNy8zMjUyIn0sIHsic291cmNlIjogIjE5NC80ODI0IiwgInRhcmdldCI6ICIyNy8xMTI2MSJ9LCB7InNvdXJjZSI6ICIxOTQvNDgyNCIsICJ0YXJnZXQiOiAibS9WYWhhYlNNaXJyb2tuaSJ9LCB7InNvdXJjZSI6ICIxOTQvNDgyNCIsICJ0YXJnZXQiOiAiMTc1LzEyMzEifSwgeyJzb3VyY2UiOiAiMTk0LzQ4MjQiLCAidGFyZ2V0IjogIjIwNS8zOTA4In0sIHsic291cmNlIjogIjE5NC80ODI0IiwgInRhcmdldCI6ICIyNDMvNTc2MCJ9LCB7InNvdXJjZSI6ICIxOTQvNDgyNCIsICJ0YXJnZXQiOiAiMzI0LzI1NjcifSwgeyJzb3VyY2UiOiAiMTk0LzQ4MjQiLCAidGFyZ2V0IjogIjE5OC83NDIzIn0sIHsic291cmNlIjogIjE5NC80ODI0IiwgInRhcmdldCI6ICI0Ny8zMTgifSwgeyJzb3VyY2UiOiAiNzMvNjE1IiwgInRhcmdldCI6ICIzNjkvNzY4OSJ9LCB7InNvdXJjZSI6ICI3My82MTUiLCAidGFyZ2V0IjogIjIwMy84MjA3In0sIHsic291cmNlIjogIjczLzYxNSIsICJ0YXJnZXQiOiAiMTgyLzIzNTYifSwgeyJzb3VyY2UiOiAiNzMvNjE1IiwgInRhcmdldCI6ICIxNjMvMzE3NSJ9LCB7InNvdXJjZSI6ICIyMDMvODIwNyIsICJ0YXJnZXQiOiAiMTI3LzMxMjgifSwgeyJzb3VyY2UiOiAiMjAzLzgyMDciLCAidGFyZ2V0IjogIjIzOS82MDcwIn0sIHsic291cmNlIjogIjIwMy84MjA3IiwgInRhcmdldCI6ICIxNDIvMzIwMiJ9LCB7InNvdXJjZSI6ICIyMDMvODIwNyIsICJ0YXJnZXQiOiAiMTYzLzMxNzUifSwgeyJzb3VyY2UiOiAiMjAzLzgyMDciLCAidGFyZ2V0IjogImwvSnVyZUxlc2tvdmVjIn0sIHsic291cmNlIjogIjIwMy84MjA3IiwgInRhcmdldCI6ICIzNjkvNzY4OSJ9LCB7InNvdXJjZSI6ICIyMDMvODIwNyIsICJ0YXJnZXQiOiAiMTgyLzIzNTYifSwgeyJzb3VyY2UiOiAiMTg0LzIwMjAiLCAidGFyZ2V0IjogInIvQ2hyaXN0b3BoZXJSZSJ9LCB7InNvdXJjZSI6ICIxODQvMjAyMCIsICJ0YXJnZXQiOiAiMjYvMjU5OSJ9LCB7InNvdXJjZSI6ICIxNzYvMjYyMCIsICJ0YXJnZXQiOiAiMTgwLzUzMzUifSwgeyJzb3VyY2UiOiAiMTc2LzI2MjAiLCAidGFyZ2V0IjogIjE3My8yNjA4In0sIHsic291cmNlIjogIjE3Ni8yNjIwIiwgInRhcmdldCI6ICI4Ny83Mjk3In0sIHsic291cmNlIjogIjE3Ni8yNjIwIiwgInRhcmdldCI6ICJzL1N0ZXZlblNraWVuYSJ9LCB7InNvdXJjZSI6ICIxNzYvMjYyMCIsICJ0YXJnZXQiOiAiOTIvNDk4LTEifSwgeyJzb3VyY2UiOiAiMTc2LzI2MjAiLCAidGFyZ2V0IjogIjkyLzk5MjIifSwgeyJzb3VyY2UiOiAiMTczLzI2MDgiLCAidGFyZ2V0IjogIjE4MC81MzM1In0sIHsic291cmNlIjogIjE3My8yNjA4IiwgInRhcmdldCI6ICI4Ny83Mjk3In0sIHsic291cmNlIjogIjE3My8yNjA4IiwgInRhcmdldCI6ICJzL1N0ZXZlblNraWVuYSJ9LCB7InNvdXJjZSI6ICIxOS8xNzY2IiwgInRhcmdldCI6ICIyMzQvNjg3OCJ9LCB7InNvdXJjZSI6ICIxOS8xNzY2IiwgInRhcmdldCI6ICIzNDgvNjQ5OCJ9LCB7InNvdXJjZSI6ICIxOS8xNzY2IiwgInRhcmdldCI6ICI3Mi8xMTI0NCJ9LCB7InNvdXJjZSI6ICIxOS8xNzY2IiwgInRhcmdldCI6ICI4NS80Njk3In0sIHsic291cmNlIjogIjE5LzE3NjYiLCAidGFyZ2V0IjogIjEzLzMxMCJ9LCB7InNvdXJjZSI6ICIxOS8xNzY2IiwgInRhcmdldCI6ICI5Mi8zMDktMSJ9LCB7InNvdXJjZSI6ICIxOS8xNzY2IiwgInRhcmdldCI6ICI1OC83MjExLTIifSwgeyJzb3VyY2UiOiAiNjgvMTQyNSIsICJ0YXJnZXQiOiAicy9TdGV2ZW5Ta2llbmEifSwgeyJzb3VyY2UiOiAiOTIvNDAiLCAidGFyZ2V0IjogIjIyMS82NTE2In0sIHsic291cmNlIjogIjkyLzQwIiwgInRhcmdldCI6ICIzMzkvNzU1OCJ9LCB7InNvdXJjZSI6ICI5Mi80MCIsICJ0YXJnZXQiOiAiNTQvMTIxNiJ9LCB7InNvdXJjZSI6ICI5Mi80MCIsICJ0YXJnZXQiOiAiMjMxLzE5MDQifSwgeyJzb3VyY2UiOiAiOTIvNDAiLCAidGFyZ2V0IjogIjY2LzIxNzMtOSJ9LCB7InNvdXJjZSI6ICI5Mi80MCIsICJ0YXJnZXQiOiAiMTgyLzIzNTYifSwgeyJzb3VyY2UiOiAiOTIvNDAiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjkyLzQwIiwgInRhcmdldCI6ICI5My80MDEwLTMxIn0sIHsic291cmNlIjogIjkyLzQwIiwgInRhcmdldCI6ICI2NC8xMDgxMiJ9LCB7InNvdXJjZSI6ICI1OC83MjExLTIiLCAidGFyZ2V0IjogIjIzNC82ODc4In0sIHsic291cmNlIjogIjU4LzcyMTEtMiIsICJ0YXJnZXQiOiAiMzQ4LzY0OTgifSwgeyJzb3VyY2UiOiAiNTgvNzIxMS0yIiwgInRhcmdldCI6ICI3Mi8xMTI0NCJ9LCB7InNvdXJjZSI6ICI1OC83MjExLTIiLCAidGFyZ2V0IjogIjg1LzQ2OTcifSwgeyJzb3VyY2UiOiAiNTgvNzIxMS0yIiwgInRhcmdldCI6ICIxMy8zMTAifSwgeyJzb3VyY2UiOiAiNTgvNzIxMS0yIiwgInRhcmdldCI6ICI5Mi8zMDktMSJ9LCB7InNvdXJjZSI6ICIxMy8zMTAiLCAidGFyZ2V0IjogIjIzNC82ODc4In0sIHsic291cmNlIjogIjEzLzMxMCIsICJ0YXJnZXQiOiAiMzQ4LzY0OTgifSwgeyJzb3VyY2UiOiAiMTMvMzEwIiwgInRhcmdldCI6ICI3Mi8xMTI0NCJ9LCB7InNvdXJjZSI6ICIxMy8zMTAiLCAidGFyZ2V0IjogIjg1LzQ2OTcifSwgeyJzb3VyY2UiOiAiMTMvMzEwIiwgInRhcmdldCI6ICI5Mi8zMDktMSJ9LCB7InNvdXJjZSI6ICI3OC84NzAwIiwgInRhcmdldCI6ICIxODIvMjM1NiJ9LCB7InNvdXJjZSI6ICI3OC84NzAwIiwgInRhcmdldCI6ICIyMjgvNzg5NyJ9LCB7InNvdXJjZSI6ICIyMzQvNjg3OCIsICJ0YXJnZXQiOiAiMzQ4LzY0OTgifSwgeyJzb3VyY2UiOiAiMjM0LzY4NzgiLCAidGFyZ2V0IjogIjcyLzExMjQ0In0sIHsic291cmNlIjogIjIzNC82ODc4IiwgInRhcmdldCI6ICI4NS80Njk3In0sIHsic291cmNlIjogIjIzNC82ODc4IiwgInRhcmdldCI6ICI5Mi8zMDktMSJ9LCB7InNvdXJjZSI6ICIyMTEvMzk2OCIsICJ0YXJnZXQiOiAiMTgyLzIzNTYifSwgeyJzb3VyY2UiOiAiMjExLzM5NjgiLCAidGFyZ2V0IjogIjE2Ni82MjY0In0sIHsic291cmNlIjogIjIxMS8zOTY4IiwgInRhcmdldCI6ICIyMTcvMTY2OCJ9LCB7InNvdXJjZSI6ICJlL0Fybm9FaWdlbndpbGxpZyIsICJ0YXJnZXQiOiAiMzI0LzI2MTMifSwgeyJzb3VyY2UiOiAiZS9Bcm5vRWlnZW53aWxsaWciLCAidGFyZ2V0IjogIjIzOS82MDcwIn0sIHsic291cmNlIjogImUvQXJub0VpZ2Vud2lsbGlnIiwgInRhcmdldCI6ICIxNjMvMjMzOSJ9LCB7InNvdXJjZSI6ICJlL0Fybm9FaWdlbndpbGxpZyIsICJ0YXJnZXQiOiAiMjIyLzE4ODkifSwgeyJzb3VyY2UiOiAiZS9Bcm5vRWlnZW53aWxsaWciLCAidGFyZ2V0IjogIjI1OC81MDAzIn0sIHsic291cmNlIjogImUvQXJub0VpZ2Vud2lsbGlnIiwgInRhcmdldCI6ICIyNzEvMDc2OSJ9LCB7InNvdXJjZSI6ICJlL0Fybm9FaWdlbndpbGxpZyIsICJ0YXJnZXQiOiAiNDYvNjYxMSJ9LCB7InNvdXJjZSI6ICJlL0Fybm9FaWdlbndpbGxpZyIsICJ0YXJnZXQiOiAiMTM3LzMyNTIifSwgeyJzb3VyY2UiOiAiZS9Bcm5vRWlnZW53aWxsaWciLCAidGFyZ2V0IjogIjI3LzExMjYxIn0sIHsic291cmNlIjogImUvQXJub0VpZ2Vud2lsbGlnIiwgInRhcmdldCI6ICJtL1ZhaGFiU01pcnJva25pIn0sIHsic291cmNlIjogImUvQXJub0VpZ2Vud2lsbGlnIiwgInRhcmdldCI6ICIxNzUvMTIzMSJ9LCB7InNvdXJjZSI6ICJlL0Fybm9FaWdlbndpbGxpZyIsICJ0YXJnZXQiOiAiMjA1LzM5MDgifSwgeyJzb3VyY2UiOiAiZS9Bcm5vRWlnZW53aWxsaWciLCAidGFyZ2V0IjogIjI0My81NzYwIn0sIHsic291cmNlIjogImUvQXJub0VpZ2Vud2lsbGlnIiwgInRhcmdldCI6ICIyMTcvMTY2OCJ9LCB7InNvdXJjZSI6ICJlL0Fybm9FaWdlbndpbGxpZyIsICJ0YXJnZXQiOiAiMzI0LzI1NjcifSwgeyJzb3VyY2UiOiAiZS9Bcm5vRWlnZW53aWxsaWciLCAidGFyZ2V0IjogIjE5OC83NDIzIn0sIHsic291cmNlIjogImUvQXJub0VpZ2Vud2lsbGlnIiwgInRhcmdldCI6ICI0Ny8zMTgifSwgeyJzb3VyY2UiOiAiMTMyLzkxMTMiLCAidGFyZ2V0IjogIjIxNS8zNzQyIn0sIHsic291cmNlIjogIjEzMi85MTEzIiwgInRhcmdldCI6ICIyMTUvNTAxNCJ9LCB7InNvdXJjZSI6ICI1OC83ODAyIiwgInRhcmdldCI6ICJtL1ZhaGFiU01pcnJva25pIn0sIHsic291cmNlIjogIjU4Lzc4MDIiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjU4Lzc4MDIiLCAidGFyZ2V0IjogIjE0OC85NjMyIn0sIHsic291cmNlIjogIjE0Ni83NzQ2IiwgInRhcmdldCI6ICIxNDkvMTI4MyJ9LCB7InNvdXJjZSI6ICIxNDYvNzc0NiIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiMTQ2Lzc3NDYiLCAidGFyZ2V0IjogIjgwLzcwMyJ9LCB7InNvdXJjZSI6ICIxNDYvNzc0NiIsICJ0YXJnZXQiOiAibS9WYWhhYlNNaXJyb2tuaSJ9LCB7InNvdXJjZSI6ICIxODIvMjM1NiIsICJ0YXJnZXQiOiAiMjcxLzA3NjkifSwgeyJzb3VyY2UiOiAiMTgyLzIzNTYiLCAidGFyZ2V0IjogIjIzOS82MDcwIn0sIHsic291cmNlIjogIjE4Mi8yMzU2IiwgInRhcmdldCI6ICIyMTcvMTY2OCJ9LCB7InNvdXJjZSI6ICIxODIvMjM1NiIsICJ0YXJnZXQiOiAiMTQ5LzEyODMifSwgeyJzb3VyY2UiOiAiMTgyLzIzNTYiLCAidGFyZ2V0IjogIjE2Ni82MjY0In0sIHsic291cmNlIjogIjE4Mi8yMzU2IiwgInRhcmdldCI6ICIyMzIvMTc5NyJ9LCB7InNvdXJjZSI6ICIxODIvMjM1NiIsICJ0YXJnZXQiOiAiMzc4LzQyOTIifSwgeyJzb3VyY2UiOiAiMTgyLzIzNTYiLCAidGFyZ2V0IjogIm0vVmFoYWJTTWlycm9rbmkifSwgeyJzb3VyY2UiOiAiMTgyLzIzNTYiLCAidGFyZ2V0IjogIjI2My85OTU5In0sIHsic291cmNlIjogIjE4Mi8yMzU2IiwgInRhcmdldCI6ICIxNzYvNDIwNSJ9LCB7InNvdXJjZSI6ICIxODIvMjM1NiIsICJ0YXJnZXQiOiAiMTc1LzEyMzEifSwgeyJzb3VyY2UiOiAiMTgyLzIzNTYiLCAidGFyZ2V0IjogIjE3OC8zMjA5In0sIHsic291cmNlIjogIjE4Mi8yMzU2IiwgInRhcmdldCI6ICIyMjEvNjUxNiJ9LCB7InNvdXJjZSI6ICIxODIvMjM1NiIsICJ0YXJnZXQiOiAiMzM5Lzc1NTgifSwgeyJzb3VyY2UiOiAiMTgyLzIzNTYiLCAidGFyZ2V0IjogIjU0LzEyMTYifSwgeyJzb3VyY2UiOiAiMTgyLzIzNTYiLCAidGFyZ2V0IjogIjIzMS8xOTA0In0sIHsic291cmNlIjogIjE4Mi8yMzU2IiwgInRhcmdldCI6ICI2Ni8yMTczLTkifSwgeyJzb3VyY2UiOiAiMTgyLzIzNTYiLCAidGFyZ2V0IjogIjkzLzQwMTAtMzEifSwgeyJzb3VyY2UiOiAiMTgyLzIzNTYiLCAidGFyZ2V0IjogIjY0LzEwODEyIn0sIHsic291cmNlIjogIjE4Mi8yMzU2IiwgInRhcmdldCI6ICIzNjkvNzY4OSJ9LCB7InNvdXJjZSI6ICIxODIvMjM1NiIsICJ0YXJnZXQiOiAiMTYzLzMxNzUifSwgeyJzb3VyY2UiOiAiMTgyLzIzNTYiLCAidGFyZ2V0IjogIjIyOC83ODk3In0sIHsic291cmNlIjogIjE4Mi8yMzU2IiwgInRhcmdldCI6ICIxMjcvMzEyOCJ9LCB7InNvdXJjZSI6ICIzMjQvMjYxMyIsICJ0YXJnZXQiOiAiMjM5LzYwNzAifSwgeyJzb3VyY2UiOiAiMzI0LzI2MTMiLCAidGFyZ2V0IjogIjE2My8yMzM5In0sIHsic291cmNlIjogIjMyNC8yNjEzIiwgInRhcmdldCI6ICIyMjIvMTg4OSJ9LCB7InNvdXJjZSI6ICIzMjQvMjYxMyIsICJ0YXJnZXQiOiAiMjU4LzUwMDMifSwgeyJzb3VyY2UiOiAiMzI0LzI2MTMiLCAidGFyZ2V0IjogIjI3MS8wNzY5In0sIHsic291cmNlIjogIjMyNC8yNjEzIiwgInRhcmdldCI6ICI0Ni82NjExIn0sIHsic291cmNlIjogIjMyNC8yNjEzIiwgInRhcmdldCI6ICIxMzcvMzI1MiJ9LCB7InNvdXJjZSI6ICIzMjQvMjYxMyIsICJ0YXJnZXQiOiAiMjcvMTEyNjEifSwgeyJzb3VyY2UiOiAiMzI0LzI2MTMiLCAidGFyZ2V0IjogIm0vVmFoYWJTTWlycm9rbmkifSwgeyJzb3VyY2UiOiAiMzI0LzI2MTMiLCAidGFyZ2V0IjogIjE3NS8xMjMxIn0sIHsic291cmNlIjogIjMyNC8yNjEzIiwgInRhcmdldCI6ICIyMDUvMzkwOCJ9LCB7InNvdXJjZSI6ICIzMjQvMjYxMyIsICJ0YXJnZXQiOiAiMjQzLzU3NjAifSwgeyJzb3VyY2UiOiAiMzI0LzI2MTMiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjMyNC8yNjEzIiwgInRhcmdldCI6ICIzMjQvMjU2NyJ9LCB7InNvdXJjZSI6ICIzMjQvMjYxMyIsICJ0YXJnZXQiOiAiMTk4Lzc0MjMifSwgeyJzb3VyY2UiOiAiMzI0LzI2MTMiLCAidGFyZ2V0IjogIjQ3LzMxOCJ9LCB7InNvdXJjZSI6ICIxMzMvMjAyOSIsICJ0YXJnZXQiOiAiMjcvMTEyNjEifSwgeyJzb3VyY2UiOiAiMTMzLzIwMjkiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjEzMy8yMDI5IiwgInRhcmdldCI6ICIyNzEvMDc2OSJ9LCB7InNvdXJjZSI6ICIyMTEvODA3MyIsICJ0YXJnZXQiOiAiNDYvNjYxMSJ9LCB7InNvdXJjZSI6ICIyMTEvODA3MyIsICJ0YXJnZXQiOiAibS9WYWhhYlNNaXJyb2tuaSJ9LCB7InNvdXJjZSI6ICIxNDkvNTc5OSIsICJ0YXJnZXQiOiAiNDYvNTQ2NiJ9LCB7InNvdXJjZSI6ICIxNDkvNTc5OSIsICJ0YXJnZXQiOiAiNDYvNjYxMSJ9LCB7InNvdXJjZSI6ICIxNDkvNTc5OSIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiMTQ5LzU3OTkiLCAidGFyZ2V0IjogIjQxLzQ0ODItMiJ9LCB7InNvdXJjZSI6ICIxNi8zNDExIiwgInRhcmdldCI6ICIyODQvOTQwMSJ9LCB7InNvdXJjZSI6ICIxNi8zNDExIiwgInRhcmdldCI6ICIyMzYvNDQ0OSJ9LCB7InNvdXJjZSI6ICIxNi8zNDExIiwgInRhcmdldCI6ICI4Mi85MDU4In0sIHsic291cmNlIjogIjE2LzM0MTEiLCAidGFyZ2V0IjogIjQ1Lzc4MCJ9LCB7InNvdXJjZSI6ICIxNi8zNDExIiwgInRhcmdldCI6ICIyNDYvMjk2MSJ9LCB7InNvdXJjZSI6ICIxNi8zNDExIiwgInRhcmdldCI6ICIyMTMvODA4MCJ9LCB7InNvdXJjZSI6ICIxNi8zNDExIiwgInRhcmdldCI6ICIxMTUvNjQ2Ni0yIn0sIHsic291cmNlIjogIjE2LzM0MTEiLCAidGFyZ2V0IjogIjk5LzQzMyJ9LCB7InNvdXJjZSI6ICIxNi8zNDExIiwgInRhcmdldCI6ICI1MS85Njg3In0sIHsic291cmNlIjogIjE2LzM0MTEiLCAidGFyZ2V0IjogIjIxNS81MDE0In0sIHsic291cmNlIjogIjE2LzM0MTEiLCAidGFyZ2V0IjogIjE5OC8xNDY1In0sIHsic291cmNlIjogIjIyOC83ODk3IiwgInRhcmdldCI6ICIyMTUvNTAxNCJ9LCB7InNvdXJjZSI6ICIyMjgvNzg5NyIsICJ0YXJnZXQiOiAiMjE1LzM3NDIifSwgeyJzb3VyY2UiOiAiMjI4Lzc4OTciLCAidGFyZ2V0IjogIjcyLzExMzM4In0sIHsic291cmNlIjogIjIyOC83ODk3IiwgInRhcmdldCI6ICI0My8zMDExIn0sIHsic291cmNlIjogIjQzLzMwMTEiLCAidGFyZ2V0IjogIjIxNS81MDE0In0sIHsic291cmNlIjogIjQzLzMwMTEiLCAidGFyZ2V0IjogIjIxNS8zNzQyIn0sIHsic291cmNlIjogIjQzLzMwMTEiLCAidGFyZ2V0IjogIjcyLzExMzM4In0sIHsic291cmNlIjogIjEzMC82MzMwIiwgInRhcmdldCI6ICIyOTcvMTgyMSJ9LCB7InNvdXJjZSI6ICIyNzEvMDc2OSIsICJ0YXJnZXQiOiAiMjM5LzYwNzAifSwgeyJzb3VyY2UiOiAiMjcxLzA3NjkiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjI3MS8wNzY5IiwgInRhcmdldCI6ICIxNDkvMTI4MyJ9LCB7InNvdXJjZSI6ICIyNzEvMDc2OSIsICJ0YXJnZXQiOiAiMjMyLzE3OTcifSwgeyJzb3VyY2UiOiAiMjcxLzA3NjkiLCAidGFyZ2V0IjogIjM3OC80MjkyIn0sIHsic291cmNlIjogIjI3MS8wNzY5IiwgInRhcmdldCI6ICJtL1ZhaGFiU01pcnJva25pIn0sIHsic291cmNlIjogIjI3MS8wNzY5IiwgInRhcmdldCI6ICIyNjMvOTk1OSJ9LCB7InNvdXJjZSI6ICIyNzEvMDc2OSIsICJ0YXJnZXQiOiAiMTc2LzQyMDUifSwgeyJzb3VyY2UiOiAiMjcxLzA3NjkiLCAidGFyZ2V0IjogIjE3NS8xMjMxIn0sIHsic291cmNlIjogIjI3MS8wNzY5IiwgInRhcmdldCI6ICIxNzgvMzIwOSJ9LCB7InNvdXJjZSI6ICIyNzEvMDc2OSIsICJ0YXJnZXQiOiAiMjcvMTEyNjEifSwgeyJzb3VyY2UiOiAiMjcxLzA3NjkiLCAidGFyZ2V0IjogIjE2My8yMzM5In0sIHsic291cmNlIjogIjI3MS8wNzY5IiwgInRhcmdldCI6ICIyMjIvMTg4OSJ9LCB7InNvdXJjZSI6ICIyNzEvMDc2OSIsICJ0YXJnZXQiOiAiMjU4LzUwMDMifSwgeyJzb3VyY2UiOiAiMjcxLzA3NjkiLCAidGFyZ2V0IjogIjQ2LzY2MTEifSwgeyJzb3VyY2UiOiAiMjcxLzA3NjkiLCAidGFyZ2V0IjogIjEzNy8zMjUyIn0sIHsic291cmNlIjogIjI3MS8wNzY5IiwgInRhcmdldCI6ICIyMDUvMzkwOCJ9LCB7InNvdXJjZSI6ICIyNzEvMDc2OSIsICJ0YXJnZXQiOiAiMjQzLzU3NjAifSwgeyJzb3VyY2UiOiAiMjcxLzA3NjkiLCAidGFyZ2V0IjogIjMyNC8yNTY3In0sIHsic291cmNlIjogIjI3MS8wNzY5IiwgInRhcmdldCI6ICIxOTgvNzQyMyJ9LCB7InNvdXJjZSI6ICIyNzEvMDc2OSIsICJ0YXJnZXQiOiAiNDcvMzE4In0sIHsic291cmNlIjogIjI3MS8wNzY5IiwgInRhcmdldCI6ICIyNzEvMTAyNSJ9LCB7InNvdXJjZSI6ICIyNzEvMDc2OSIsICJ0YXJnZXQiOiAiMjcxLzA2MzcifSwgeyJzb3VyY2UiOiAiMzc4LzQyOTIiLCAidGFyZ2V0IjogIjIzMi8xNzk3In0sIHsic291cmNlIjogIjM3OC80MjkyIiwgInRhcmdldCI6ICIyMTcvMTY2OCJ9LCB7InNvdXJjZSI6ICIzNzgvNDI5MiIsICJ0YXJnZXQiOiAiMTQ5LzEyODMifSwgeyJzb3VyY2UiOiAiMzc4LzQyOTIiLCAidGFyZ2V0IjogIm0vVmFoYWJTTWlycm9rbmkifSwgeyJzb3VyY2UiOiAiaC9KaWF3ZWlIYW4iLCAidGFyZ2V0IjogIjY2LzU5MjMtOCJ9LCB7InNvdXJjZSI6ICJoL0ppYXdlaUhhbiIsICJ0YXJnZXQiOiAiMjUwLzk3NTcifSwgeyJzb3VyY2UiOiAiaC9KaWF3ZWlIYW4iLCAidGFyZ2V0IjogIjcxLzY3NjgtMSJ9LCB7InNvdXJjZSI6ICIxOTgvMTQ2NSIsICJ0YXJnZXQiOiAiMjE1LzUwMTQifSwgeyJzb3VyY2UiOiAiMTk4LzE0NjUiLCAidGFyZ2V0IjogIjk5LzQzMyJ9LCB7InNvdXJjZSI6ICIxOTgvMTQ2NSIsICJ0YXJnZXQiOiAiODIvOTA1OCJ9LCB7InNvdXJjZSI6ICI0Ni81NDY2IiwgInRhcmdldCI6ICI0Ni82NjExIn0sIHsic291cmNlIjogIjQ2LzU0NjYiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjQ2LzU0NjYiLCAidGFyZ2V0IjogIjQxLzQ0ODItMiJ9LCB7InNvdXJjZSI6ICIyNDYvMjk2MSIsICJ0YXJnZXQiOiAiNDUvNzgwIn0sIHsic291cmNlIjogIjI0Ni8yOTYxIiwgInRhcmdldCI6ICIyMTMvODA4MCJ9LCB7InNvdXJjZSI6ICIyNDYvMjk2MSIsICJ0YXJnZXQiOiAiMTE1LzY0NjYtMiJ9LCB7InNvdXJjZSI6ICIyNDYvMjk2MSIsICJ0YXJnZXQiOiAiOTkvNDMzIn0sIHsic291cmNlIjogIjI0Ni8yOTYxIiwgInRhcmdldCI6ICI1MS85Njg3In0sIHsic291cmNlIjogIjg1LzQ2OTciLCAidGFyZ2V0IjogIjM0OC82NDk4In0sIHsic291cmNlIjogIjg1LzQ2OTciLCAidGFyZ2V0IjogIjcyLzExMjQ0In0sIHsic291cmNlIjogIjg1LzQ2OTciLCAidGFyZ2V0IjogIjkyLzMwOS0xIn0sIHsic291cmNlIjogIjkyLzQ5OC0xIiwgInRhcmdldCI6ICJzL1N0ZXZlblNraWVuYSJ9LCB7InNvdXJjZSI6ICIxODAvNTQzNiIsICJ0YXJnZXQiOiAiMjA1LzI2NTEifSwgeyJzb3VyY2UiOiAiMTgwLzU0MzYiLCAidGFyZ2V0IjogIjE3NS8xMjMxIn0sIHsic291cmNlIjogIjE4MC81NDM2IiwgInRhcmdldCI6ICIyMzkvNjA3MCJ9LCB7InNvdXJjZSI6ICIxODAvNTQzNiIsICJ0YXJnZXQiOiAiNjIvNTg4NCJ9LCB7InNvdXJjZSI6ICI0NS83ODAiLCAidGFyZ2V0IjogIjIxMy84MDgwIn0sIHsic291cmNlIjogIjQ1Lzc4MCIsICJ0YXJnZXQiOiAiMTE1LzY0NjYtMiJ9LCB7InNvdXJjZSI6ICI0NS83ODAiLCAidGFyZ2V0IjogIjk5LzQzMyJ9LCB7InNvdXJjZSI6ICI0NS83ODAiLCAidGFyZ2V0IjogIjUxLzk2ODcifSwgeyJzb3VyY2UiOiAiMjEzLzgwODAiLCAidGFyZ2V0IjogIjExNS82NDY2LTIifSwgeyJzb3VyY2UiOiAiMjEzLzgwODAiLCAidGFyZ2V0IjogIjk5LzQzMyJ9LCB7InNvdXJjZSI6ICIyMTMvODA4MCIsICJ0YXJnZXQiOiAiNTEvOTY4NyJ9LCB7InNvdXJjZSI6ICIyNTAvOTc1NyIsICJ0YXJnZXQiOiAiNjYvNTkyMy04In0sIHsic291cmNlIjogIjI1MC85NzU3IiwgInRhcmdldCI6ICI3MS82NzY4LTEifSwgeyJzb3VyY2UiOiAiNjYvMjE3My05IiwgInRhcmdldCI6ICIyMjEvNjUxNiJ9LCB7InNvdXJjZSI6ICI2Ni8yMTczLTkiLCAidGFyZ2V0IjogIjMzOS83NTU4In0sIHsic291cmNlIjogIjY2LzIxNzMtOSIsICJ0YXJnZXQiOiAiNTQvMTIxNiJ9LCB7InNvdXJjZSI6ICI2Ni8yMTczLTkiLCAidGFyZ2V0IjogIjIzMS8xOTA0In0sIHsic291cmNlIjogIjY2LzIxNzMtOSIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiNjYvMjE3My05IiwgInRhcmdldCI6ICI5My80MDEwLTMxIn0sIHsic291cmNlIjogIjY2LzIxNzMtOSIsICJ0YXJnZXQiOiAiNjQvMTA4MTIifSwgeyJzb3VyY2UiOiAiMjE1LzUwMTQiLCAidGFyZ2V0IjogIjIxNS8zNzQyIn0sIHsic291cmNlIjogIjIxNS81MDE0IiwgInRhcmdldCI6ICI3Mi8xMTMzOCJ9LCB7InNvdXJjZSI6ICIyMTUvNTAxNCIsICJ0YXJnZXQiOiAiNjkvODE2NyJ9LCB7InNvdXJjZSI6ICIyMTUvNTAxNCIsICJ0YXJnZXQiOiAiMTI3Lzc0OTcifSwgeyJzb3VyY2UiOiAiMjE1LzUwMTQiLCAidGFyZ2V0IjogIjk5LzQzMyJ9LCB7InNvdXJjZSI6ICIyMTUvNTAxNCIsICJ0YXJnZXQiOiAiODIvOTA1OCJ9LCB7InNvdXJjZSI6ICIyMTUvNTAxNCIsICJ0YXJnZXQiOiAiNzcvMTMxOSJ9LCB7InNvdXJjZSI6ICIxNDkvMTI4MyIsICJ0YXJnZXQiOiAiMjM5LzYwNzAifSwgeyJzb3VyY2UiOiAiMTQ5LzEyODMiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjE0OS8xMjgzIiwgInRhcmdldCI6ICIyMzIvMTc5NyJ9LCB7InNvdXJjZSI6ICIxNDkvMTI4MyIsICJ0YXJnZXQiOiAibS9WYWhhYlNNaXJyb2tuaSJ9LCB7InNvdXJjZSI6ICIxNDkvMTI4MyIsICJ0YXJnZXQiOiAiMjYzLzk5NTkifSwgeyJzb3VyY2UiOiAiMTQ5LzEyODMiLCAidGFyZ2V0IjogIjE3Ni80MjA1In0sIHsic291cmNlIjogIjE0OS8xMjgzIiwgInRhcmdldCI6ICIxNzUvMTIzMSJ9LCB7InNvdXJjZSI6ICIxNDkvMTI4MyIsICJ0YXJnZXQiOiAiMTc4LzMyMDkifSwgeyJzb3VyY2UiOiAiMTQ5LzEyODMiLCAidGFyZ2V0IjogIjgwLzcwMyJ9LCB7InNvdXJjZSI6ICI5Mi85OTIyIiwgInRhcmdldCI6ICJzL1N0ZXZlblNraWVuYSJ9LCB7InNvdXJjZSI6ICI5Mi85OTIyIiwgInRhcmdldCI6ICIxODAvNTMzNSJ9LCB7InNvdXJjZSI6ICI0Ni82NjExIiwgInRhcmdldCI6ICIyMzkvNjA3MCJ9LCB7InNvdXJjZSI6ICI0Ni82NjExIiwgInRhcmdldCI6ICIxNjMvMjMzOSJ9LCB7InNvdXJjZSI6ICI0Ni82NjExIiwgInRhcmdldCI6ICIyMjIvMTg4OSJ9LCB7InNvdXJjZSI6ICI0Ni82NjExIiwgInRhcmdldCI6ICIyNTgvNTAwMyJ9LCB7InNvdXJjZSI6ICI0Ni82NjExIiwgInRhcmdldCI6ICIxMzcvMzI1MiJ9LCB7InNvdXJjZSI6ICI0Ni82NjExIiwgInRhcmdldCI6ICIyNy8xMTI2MSJ9LCB7InNvdXJjZSI6ICI0Ni82NjExIiwgInRhcmdldCI6ICJtL1ZhaGFiU01pcnJva25pIn0sIHsic291cmNlIjogIjQ2LzY2MTEiLCAidGFyZ2V0IjogIjE3NS8xMjMxIn0sIHsic291cmNlIjogIjQ2LzY2MTEiLCAidGFyZ2V0IjogIjIwNS8zOTA4In0sIHsic291cmNlIjogIjQ2LzY2MTEiLCAidGFyZ2V0IjogIjI0My81NzYwIn0sIHsic291cmNlIjogIjQ2LzY2MTEiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjQ2LzY2MTEiLCAidGFyZ2V0IjogIjMyNC8yNTY3In0sIHsic291cmNlIjogIjQ2LzY2MTEiLCAidGFyZ2V0IjogIjE5OC83NDIzIn0sIHsic291cmNlIjogIjQ2LzY2MTEiLCAidGFyZ2V0IjogIjQ3LzMxOCJ9LCB7InNvdXJjZSI6ICI0Ni82NjExIiwgInRhcmdldCI6ICI0MS80NDgyLTIifSwgeyJzb3VyY2UiOiAiNDYvNjYxMSIsICJ0YXJnZXQiOiAiMTk0LzE1NzAifSwgeyJzb3VyY2UiOiAiNDYvNjYxMSIsICJ0YXJnZXQiOiAiMTgwLzUzMzUifSwgeyJzb3VyY2UiOiAiOTkvNDMzIiwgInRhcmdldCI6ICIxMTUvNjQ2Ni0yIn0sIHsic291cmNlIjogIjk5LzQzMyIsICJ0YXJnZXQiOiAiNTEvOTY4NyJ9LCB7InNvdXJjZSI6ICI5OS80MzMiLCAidGFyZ2V0IjogIjgyLzkwNTgifSwgeyJzb3VyY2UiOiAibC9KdXJlTGVza292ZWMiLCAidGFyZ2V0IjogIjEyNy8zMTI4In0sIHsic291cmNlIjogImwvSnVyZUxlc2tvdmVjIiwgInRhcmdldCI6ICIyMzkvNjA3MCJ9LCB7InNvdXJjZSI6ICJsL0p1cmVMZXNrb3ZlYyIsICJ0YXJnZXQiOiAiMTQyLzMyMDIifSwgeyJzb3VyY2UiOiAibC9KdXJlTGVza292ZWMiLCAidGFyZ2V0IjogIjE2My8zMTc1In0sIHsic291cmNlIjogIjA2LzEwODE0IiwgInRhcmdldCI6ICIyOTcvMTgyMSJ9LCB7InNvdXJjZSI6ICIwNi8xMDgxNCIsICJ0YXJnZXQiOiAiODcvMTAxMDAifSwgeyJzb3VyY2UiOiAiMjMxLzE5MDQiLCAidGFyZ2V0IjogIjIyMS82NTE2In0sIHsic291cmNlIjogIjIzMS8xOTA0IiwgInRhcmdldCI6ICIzMzkvNzU1OCJ9LCB7InNvdXJjZSI6ICIyMzEvMTkwNCIsICJ0YXJnZXQiOiAiNTQvMTIxNiJ9LCB7InNvdXJjZSI6ICIyMzEvMTkwNCIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiMjMxLzE5MDQiLCAidGFyZ2V0IjogIjkzLzQwMTAtMzEifSwgeyJzb3VyY2UiOiAiMjMxLzE5MDQiLCAidGFyZ2V0IjogIjY0LzEwODEyIn0sIHsic291cmNlIjogIjI1OC81MDAzIiwgInRhcmdldCI6ICIyMzkvNjA3MCJ9LCB7InNvdXJjZSI6ICIyNTgvNTAwMyIsICJ0YXJnZXQiOiAiMTYzLzIzMzkifSwgeyJzb3VyY2UiOiAiMjU4LzUwMDMiLCAidGFyZ2V0IjogIjIyMi8xODg5In0sIHsic291cmNlIjogIjI1OC81MDAzIiwgInRhcmdldCI6ICIxMzcvMzI1MiJ9LCB7InNvdXJjZSI6ICIyNTgvNTAwMyIsICJ0YXJnZXQiOiAiMjcvMTEyNjEifSwgeyJzb3VyY2UiOiAiMjU4LzUwMDMiLCAidGFyZ2V0IjogIm0vVmFoYWJTTWlycm9rbmkifSwgeyJzb3VyY2UiOiAiMjU4LzUwMDMiLCAidGFyZ2V0IjogIjE3NS8xMjMxIn0sIHsic291cmNlIjogIjI1OC81MDAzIiwgInRhcmdldCI6ICIyMDUvMzkwOCJ9LCB7InNvdXJjZSI6ICIyNTgvNTAwMyIsICJ0YXJnZXQiOiAiMjQzLzU3NjAifSwgeyJzb3VyY2UiOiAiMjU4LzUwMDMiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjI1OC81MDAzIiwgInRhcmdldCI6ICIzMjQvMjU2NyJ9LCB7InNvdXJjZSI6ICIyNTgvNTAwMyIsICJ0YXJnZXQiOiAiMTk4Lzc0MjMifSwgeyJzb3VyY2UiOiAiMjU4LzUwMDMiLCAidGFyZ2V0IjogIjQ3LzMxOCJ9LCB7InNvdXJjZSI6ICIzNDgvNjQ5OCIsICJ0YXJnZXQiOiAiNzIvMTEyNDQifSwgeyJzb3VyY2UiOiAiMzQ4LzY0OTgiLCAidGFyZ2V0IjogIjkyLzMwOS0xIn0sIHsic291cmNlIjogIjEzNy8zMjUyIiwgInRhcmdldCI6ICIyMzkvNjA3MCJ9LCB7InNvdXJjZSI6ICIxMzcvMzI1MiIsICJ0YXJnZXQiOiAiMTYzLzIzMzkifSwgeyJzb3VyY2UiOiAiMTM3LzMyNTIiLCAidGFyZ2V0IjogIjIyMi8xODg5In0sIHsic291cmNlIjogIjEzNy8zMjUyIiwgInRhcmdldCI6ICIyNy8xMTI2MSJ9LCB7InNvdXJjZSI6ICIxMzcvMzI1MiIsICJ0YXJnZXQiOiAibS9WYWhhYlNNaXJyb2tuaSJ9LCB7InNvdXJjZSI6ICIxMzcvMzI1MiIsICJ0YXJnZXQiOiAiMTc1LzEyMzEifSwgeyJzb3VyY2UiOiAiMTM3LzMyNTIiLCAidGFyZ2V0IjogIjIwNS8zOTA4In0sIHsic291cmNlIjogIjEzNy8zMjUyIiwgInRhcmdldCI6ICIyNDMvNTc2MCJ9LCB7InNvdXJjZSI6ICIxMzcvMzI1MiIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiMTM3LzMyNTIiLCAidGFyZ2V0IjogIjMyNC8yNTY3In0sIHsic291cmNlIjogIjEzNy8zMjUyIiwgInRhcmdldCI6ICIxOTgvNzQyMyJ9LCB7InNvdXJjZSI6ICIxMzcvMzI1MiIsICJ0YXJnZXQiOiAiNDcvMzE4In0sIHsic291cmNlIjogIjkyLzMwOS0xIiwgInRhcmdldCI6ICI3Mi8xMTI0NCJ9LCB7InNvdXJjZSI6ICI5My80MDEwLTMxIiwgInRhcmdldCI6ICIyMjEvNjUxNiJ9LCB7InNvdXJjZSI6ICI5My80MDEwLTMxIiwgInRhcmdldCI6ICIzMzkvNzU1OCJ9LCB7InNvdXJjZSI6ICI5My80MDEwLTMxIiwgInRhcmdldCI6ICI1NC8xMjE2In0sIHsic291cmNlIjogIjkzLzQwMTAtMzEiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjkzLzQwMTAtMzEiLCAidGFyZ2V0IjogIjY0LzEwODEyIn0sIHsic291cmNlIjogIjMzOS83NTU4IiwgInRhcmdldCI6ICIyMjEvNjUxNiJ9LCB7InNvdXJjZSI6ICIzMzkvNzU1OCIsICJ0YXJnZXQiOiAiNTQvMTIxNiJ9LCB7InNvdXJjZSI6ICIzMzkvNzU1OCIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiMzM5Lzc1NTgiLCAidGFyZ2V0IjogIjY0LzEwODEyIn0sIHsic291cmNlIjogIjY5LzgxNjciLCAidGFyZ2V0IjogIjEyNy83NDk3In0sIHsic291cmNlIjogIjcyLzExMzM4IiwgInRhcmdldCI6ICIyMTUvMzc0MiJ9LCB7InNvdXJjZSI6ICIyNjMvOTk1OSIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiMjYzLzk5NTkiLCAidGFyZ2V0IjogIjE3Ni80MjA1In0sIHsic291cmNlIjogIjI2My85OTU5IiwgInRhcmdldCI6ICIxNzUvMTIzMSJ9LCB7InNvdXJjZSI6ICIyNjMvOTk1OSIsICJ0YXJnZXQiOiAiMTc4LzMyMDkifSwgeyJzb3VyY2UiOiAiMjIxLzY1MTYiLCAidGFyZ2V0IjogIjU0LzEyMTYifSwgeyJzb3VyY2UiOiAiMjIxLzY1MTYiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjIyMS82NTE2IiwgInRhcmdldCI6ICI2NC8xMDgxMiJ9LCB7InNvdXJjZSI6ICIyODQvOTQwMSIsICJ0YXJnZXQiOiAiMjM2LzQ0NDkifSwgeyJzb3VyY2UiOiAiMjg0Lzk0MDEiLCAidGFyZ2V0IjogIjgyLzkwNTgifSwgeyJzb3VyY2UiOiAiMjcvMTEyNjEiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjI3LzExMjYxIiwgInRhcmdldCI6ICIxNzUvMTIzMSJ9LCB7InNvdXJjZSI6ICIyNy8xMTI2MSIsICJ0YXJnZXQiOiAiMjM5LzYwNzAifSwgeyJzb3VyY2UiOiAiMjcvMTEyNjEiLCAidGFyZ2V0IjogIjE2My8yMzM5In0sIHsic291cmNlIjogIjI3LzExMjYxIiwgInRhcmdldCI6ICIyMjIvMTg4OSJ9LCB7InNvdXJjZSI6ICIyNy8xMTI2MSIsICJ0YXJnZXQiOiAibS9WYWhhYlNNaXJyb2tuaSJ9LCB7InNvdXJjZSI6ICIyNy8xMTI2MSIsICJ0YXJnZXQiOiAiMjA1LzM5MDgifSwgeyJzb3VyY2UiOiAiMjcvMTEyNjEiLCAidGFyZ2V0IjogIjI0My81NzYwIn0sIHsic291cmNlIjogIjI3LzExMjYxIiwgInRhcmdldCI6ICIzMjQvMjU2NyJ9LCB7InNvdXJjZSI6ICIyNy8xMTI2MSIsICJ0YXJnZXQiOiAiMTk4Lzc0MjMifSwgeyJzb3VyY2UiOiAiMjcvMTEyNjEiLCAidGFyZ2V0IjogIjQ3LzMxOCJ9LCB7InNvdXJjZSI6ICIyOTcvMTgyMSIsICJ0YXJnZXQiOiAiODcvMTAxMDAifSwgeyJzb3VyY2UiOiAiMTYzLzMxNzUiLCAidGFyZ2V0IjogIjEyNy8zMTI4In0sIHsic291cmNlIjogIjE2My8zMTc1IiwgInRhcmdldCI6ICIyMzkvNjA3MCJ9LCB7InNvdXJjZSI6ICIxNjMvMzE3NSIsICJ0YXJnZXQiOiAiMTQyLzMyMDIifSwgeyJzb3VyY2UiOiAiMTYzLzMxNzUiLCAidGFyZ2V0IjogIjM2OS83Njg5In0sIHsic291cmNlIjogIm0vVmFoYWJTTWlycm9rbmkiLCAidGFyZ2V0IjogIjIzMi8xNzk3In0sIHsic291cmNlIjogIm0vVmFoYWJTTWlycm9rbmkiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIm0vVmFoYWJTTWlycm9rbmkiLCAidGFyZ2V0IjogIjgwLzcwMyJ9LCB7InNvdXJjZSI6ICJtL1ZhaGFiU01pcnJva25pIiwgInRhcmdldCI6ICIxNDgvOTYzMiJ9LCB7InNvdXJjZSI6ICJtL1ZhaGFiU01pcnJva25pIiwgInRhcmdldCI6ICIyMzkvNjA3MCJ9LCB7InNvdXJjZSI6ICJtL1ZhaGFiU01pcnJva25pIiwgInRhcmdldCI6ICIxNjMvMjMzOSJ9LCB7InNvdXJjZSI6ICJtL1ZhaGFiU01pcnJva25pIiwgInRhcmdldCI6ICIyMjIvMTg4OSJ9LCB7InNvdXJjZSI6ICJtL1ZhaGFiU01pcnJva25pIiwgInRhcmdldCI6ICIxNzUvMTIzMSJ9LCB7InNvdXJjZSI6ICJtL1ZhaGFiU01pcnJva25pIiwgInRhcmdldCI6ICIyMDUvMzkwOCJ9LCB7InNvdXJjZSI6ICJtL1ZhaGFiU01pcnJva25pIiwgInRhcmdldCI6ICIyNDMvNTc2MCJ9LCB7InNvdXJjZSI6ICJtL1ZhaGFiU01pcnJva25pIiwgInRhcmdldCI6ICIzMjQvMjU2NyJ9LCB7InNvdXJjZSI6ICJtL1ZhaGFiU01pcnJva25pIiwgInRhcmdldCI6ICIxOTgvNzQyMyJ9LCB7InNvdXJjZSI6ICJtL1ZhaGFiU01pcnJva25pIiwgInRhcmdldCI6ICI0Ny8zMTgifSwgeyJzb3VyY2UiOiAiMjM2LzQ0NDkiLCAidGFyZ2V0IjogIjgyLzkwNTgifSwgeyJzb3VyY2UiOiAiNTEvOTY4NyIsICJ0YXJnZXQiOiAiMTE1LzY0NjYtMiJ9LCB7InNvdXJjZSI6ICIyNzEvMTAyNSIsICJ0YXJnZXQiOiAiMjcxLzA2MzcifSwgeyJzb3VyY2UiOiAiNTUvNjg5OSIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiNTUvNjg5OSIsICJ0YXJnZXQiOiAiMTc1LzEyMzEifSwgeyJzb3VyY2UiOiAiNTUvNjg5OSIsICJ0YXJnZXQiOiAiMTMxLzcxNjkifSwgeyJzb3VyY2UiOiAiMjE1LzM2ODIiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjI2LzI1OTkiLCAidGFyZ2V0IjogInIvQ2hyaXN0b3BoZXJSZSJ9LCB7InNvdXJjZSI6ICIxNzUvMTIzMSIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiMTc1LzEyMzEiLCAidGFyZ2V0IjogIjE3Ni80MjA1In0sIHsic291cmNlIjogIjE3NS8xMjMxIiwgInRhcmdldCI6ICIxNzgvMzIwOSJ9LCB7InNvdXJjZSI6ICIxNzUvMTIzMSIsICJ0YXJnZXQiOiAiMjA1LzI2NTEifSwgeyJzb3VyY2UiOiAiMTc1LzEyMzEiLCAidGFyZ2V0IjogIjQxLzU5NzktMSJ9LCB7InNvdXJjZSI6ICIxNzUvMTIzMSIsICJ0YXJnZXQiOiAiNjIvNTg4NCJ9LCB7InNvdXJjZSI6ICIxNzUvMTIzMSIsICJ0YXJnZXQiOiAiMzUyLzQyMzcifSwgeyJzb3VyY2UiOiAiMTc1LzEyMzEiLCAidGFyZ2V0IjogIjQ2LzgzMzMifSwgeyJzb3VyY2UiOiAiMTc1LzEyMzEiLCAidGFyZ2V0IjogIjIzOS82MDcwIn0sIHsic291cmNlIjogIjE3NS8xMjMxIiwgInRhcmdldCI6ICIyMTUvMzc0MiJ9LCB7InNvdXJjZSI6ICIxNzUvMTIzMSIsICJ0YXJnZXQiOiAiMTYzLzIzMzkifSwgeyJzb3VyY2UiOiAiMTc1LzEyMzEiLCAidGFyZ2V0IjogIjIyMi8xODg5In0sIHsic291cmNlIjogIjE3NS8xMjMxIiwgInRhcmdldCI6ICIyMDUvMzkwOCJ9LCB7InNvdXJjZSI6ICIxNzUvMTIzMSIsICJ0YXJnZXQiOiAiMjQzLzU3NjAifSwgeyJzb3VyY2UiOiAiMTc1LzEyMzEiLCAidGFyZ2V0IjogIjMyNC8yNTY3In0sIHsic291cmNlIjogIjE3NS8xMjMxIiwgInRhcmdldCI6ICIxOTgvNzQyMyJ9LCB7InNvdXJjZSI6ICIxNzUvMTIzMSIsICJ0YXJnZXQiOiAiNDcvMzE4In0sIHsic291cmNlIjogIjIwNS8zOTA4IiwgInRhcmdldCI6ICIyMzkvNjA3MCJ9LCB7InNvdXJjZSI6ICIyMDUvMzkwOCIsICJ0YXJnZXQiOiAiMTYzLzIzMzkifSwgeyJzb3VyY2UiOiAiMjA1LzM5MDgiLCAidGFyZ2V0IjogIjIyMi8xODg5In0sIHsic291cmNlIjogIjIwNS8zOTA4IiwgInRhcmdldCI6ICIyNDMvNTc2MCJ9LCB7InNvdXJjZSI6ICIyMDUvMzkwOCIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiMjA1LzM5MDgiLCAidGFyZ2V0IjogIjMyNC8yNTY3In0sIHsic291cmNlIjogIjIwNS8zOTA4IiwgInRhcmdldCI6ICIxOTgvNzQyMyJ9LCB7InNvdXJjZSI6ICIyMDUvMzkwOCIsICJ0YXJnZXQiOiAiNDcvMzE4In0sIHsic291cmNlIjogIjE2My8yMzM5IiwgInRhcmdldCI6ICIyMzkvNjA3MCJ9LCB7InNvdXJjZSI6ICIxNjMvMjMzOSIsICJ0YXJnZXQiOiAiMjIyLzE4ODkifSwgeyJzb3VyY2UiOiAiMTYzLzIzMzkiLCAidGFyZ2V0IjogIjI0My81NzYwIn0sIHsic291cmNlIjogIjE2My8yMzM5IiwgInRhcmdldCI6ICIyMTcvMTY2OCJ9LCB7InNvdXJjZSI6ICIxNjMvMjMzOSIsICJ0YXJnZXQiOiAiMzI0LzI1NjcifSwgeyJzb3VyY2UiOiAiMTYzLzIzMzkiLCAidGFyZ2V0IjogIjE5OC83NDIzIn0sIHsic291cmNlIjogIjE2My8yMzM5IiwgInRhcmdldCI6ICI0Ny8zMTgifSwgeyJzb3VyY2UiOiAiMTI3LzMxMjgiLCAidGFyZ2V0IjogIjIzOS82MDcwIn0sIHsic291cmNlIjogIjEyNy8zMTI4IiwgInRhcmdldCI6ICIxNDIvMzIwMiJ9LCB7InNvdXJjZSI6ICI3MS82NzY4LTEiLCAidGFyZ2V0IjogIjY2LzU5MjMtOCJ9LCB7InNvdXJjZSI6ICIxOTQvMTU3MCIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiMTk0LzE1NzAiLCAidGFyZ2V0IjogIjE4MC81MzM1In0sIHsic291cmNlIjogIjgwLzcwMyIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiMjE1LzM3NDIiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjE4NC8wMDMzIiwgInRhcmdldCI6ICIxNjkvMTc0MiJ9LCB7InNvdXJjZSI6ICIxODQvMDAzMyIsICJ0YXJnZXQiOiAiNTgvNTQ5MiJ9LCB7InNvdXJjZSI6ICI2Mi81ODg0IiwgInRhcmdldCI6ICIyMDUvMjY1MSJ9LCB7InNvdXJjZSI6ICI2Mi81ODg0IiwgInRhcmdldCI6ICI0MS81OTc5LTEifSwgeyJzb3VyY2UiOiAiNjIvNTg4NCIsICJ0YXJnZXQiOiAiMjM5LzYwNzAifSwgeyJzb3VyY2UiOiAiMjIyLzE4ODkiLCAidGFyZ2V0IjogIjIzOS82MDcwIn0sIHsic291cmNlIjogIjIyMi8xODg5IiwgInRhcmdldCI6ICIyNDMvNTc2MCJ9LCB7InNvdXJjZSI6ICIyMjIvMTg4OSIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiMjIyLzE4ODkiLCAidGFyZ2V0IjogIjMyNC8yNTY3In0sIHsic291cmNlIjogIjIyMi8xODg5IiwgInRhcmdldCI6ICIxOTgvNzQyMyJ9LCB7InNvdXJjZSI6ICIyMjIvMTg4OSIsICJ0YXJnZXQiOiAiNDcvMzE4In0sIHsic291cmNlIjogIjIzMi8xNzk3IiwgInRhcmdldCI6ICIyMTcvMTY2OCJ9LCB7InNvdXJjZSI6ICIxNjkvMTc0MiIsICJ0YXJnZXQiOiAiNTgvNTQ5MiJ9LCB7InNvdXJjZSI6ICIxNzgvMzIwOSIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiMTc4LzMyMDkiLCAidGFyZ2V0IjogIjE3Ni80MjA1In0sIHsic291cmNlIjogIjI0My81NzYwIiwgInRhcmdldCI6ICIyMzkvNjA3MCJ9LCB7InNvdXJjZSI6ICIyNDMvNTc2MCIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiMjQzLzU3NjAiLCAidGFyZ2V0IjogIjMyNC8yNTY3In0sIHsic291cmNlIjogIjI0My81NzYwIiwgInRhcmdldCI6ICIxOTgvNzQyMyJ9LCB7InNvdXJjZSI6ICIyNDMvNTc2MCIsICJ0YXJnZXQiOiAiNDcvMzE4In0sIHsic291cmNlIjogInMvU3RldmVuU2tpZW5hIiwgInRhcmdldCI6ICIxODAvNTMzNSJ9LCB7InNvdXJjZSI6ICJzL1N0ZXZlblNraWVuYSIsICJ0YXJnZXQiOiAiODcvNzI5NyJ9LCB7InNvdXJjZSI6ICI4Ny83Mjk3IiwgInRhcmdldCI6ICIxODAvNTMzNSJ9LCB7InNvdXJjZSI6ICI2NC8xMDgxMiIsICJ0YXJnZXQiOiAiNTQvMTIxNiJ9LCB7InNvdXJjZSI6ICI2NC8xMDgxMiIsICJ0YXJnZXQiOiAiMjE3LzE2NjgifSwgeyJzb3VyY2UiOiAiMTgwLzUzMzUiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjQ2LzgzMzMiLCAidGFyZ2V0IjogIjM1Mi80MjM3In0sIHsic291cmNlIjogIjQ2LzgzMzMiLCAidGFyZ2V0IjogIjIxNy8xNjY4In0sIHsic291cmNlIjogIjIxNy8xNjY4IiwgInRhcmdldCI6ICIyMzkvNjA3MCJ9LCB7InNvdXJjZSI6ICIyMTcvMTY2OCIsICJ0YXJnZXQiOiAiMTY2LzYyNjQifSwgeyJzb3VyY2UiOiAiMjE3LzE2NjgiLCAidGFyZ2V0IjogIjE3Ni80MjA1In0sIHsic291cmNlIjogIjIxNy8xNjY4IiwgInRhcmdldCI6ICI1NC8xMjE2In0sIHsic291cmNlIjogIjIxNy8xNjY4IiwgInRhcmdldCI6ICIzNTIvNDIzNyJ9LCB7InNvdXJjZSI6ICIyMTcvMTY2OCIsICJ0YXJnZXQiOiAiMTQ4Lzk2MzIifSwgeyJzb3VyY2UiOiAiMjE3LzE2NjgiLCAidGFyZ2V0IjogIjMyNC8yNTY3In0sIHsic291cmNlIjogIjIxNy8xNjY4IiwgInRhcmdldCI6ICIxOTgvNzQyMyJ9LCB7InNvdXJjZSI6ICIyMTcvMTY2OCIsICJ0YXJnZXQiOiAiNDcvMzE4In0sIHsic291cmNlIjogIjIxNy8xNjY4IiwgInRhcmdldCI6ICI0MS80NDgyLTIifSwgeyJzb3VyY2UiOiAiMzI0LzI1NjciLCAidGFyZ2V0IjogIjIzOS82MDcwIn0sIHsic291cmNlIjogIjMyNC8yNTY3IiwgInRhcmdldCI6ICIxOTgvNzQyMyJ9LCB7InNvdXJjZSI6ICIzMjQvMjU2NyIsICJ0YXJnZXQiOiAiNDcvMzE4In0sIHsic291cmNlIjogIjE5OC83NDIzIiwgInRhcmdldCI6ICIyMzkvNjA3MCJ9LCB7InNvdXJjZSI6ICIxOTgvNzQyMyIsICJ0YXJnZXQiOiAiNDcvMzE4In0sIHsic291cmNlIjogIjQ3LzMxOCIsICJ0YXJnZXQiOiAiMjM5LzYwNzAifSwgeyJzb3VyY2UiOiAiNDEvNTk3OS0xIiwgInRhcmdldCI6ICIyMDUvMjY1MSJ9LCB7InNvdXJjZSI6ICIyMDUvMjY1MSIsICJ0YXJnZXQiOiAiMjM5LzYwNzAifSwgeyJzb3VyY2UiOiAiMjM5LzYwNzAiLCAidGFyZ2V0IjogIjE0Mi8zMjAyIn1dfQ==''')))" ] }, { "cell_type": "markdown", "metadata": { "id": "2N4mDMcuY1xT" }, "source": [ "# Test out that you can call Gemini" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "F3KwvNgPYmcJ" }, "outputs": [], "source": [ "model = genai.GenerativeModel('gemini-1.5-flash')\n", "response = model.generate_content(\"How many r's are in the word 'strawberry'?\")\n", "print(response.text)" ] }, { "cell_type": "markdown", "metadata": { "id": "GefRU-PdfINx" }, "source": [ "# Ok, let's make some graphs!" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "DTelg4DpZRMq" }, "outputs": [], "source": [ "random_seed = 9876\n", "\n", "graphs = generate_graphs(number_of_graphs=500,\n", " algorithm='er', # Erdos-Reyni random graphs\n", " directed=False,\n", " random_seed=random_seed)\n", "\n", "from matplotlib import pyplot as plt\n", "fig, axs = plt.subplots(2, 2)\n", "i = 0\n", "for x in [0, 1]:\n", " for y in [0, 1]:\n", " nx.draw(graphs[i], ax=axs[x, y])\n", " axs[x, y].set_title('Graph {}'.format(i))\n", " i += 1\n" ] }, { "cell_type": "markdown", "metadata": { "id": "4uPk8uewkuBw" }, "source": [ "Generate zero-shot prompts for the cycle-check task, using the 'adjacency encoding method." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "xvfHNbb5h9DA" }, "outputs": [], "source": [ "task = CycleCheck()\n", "encoded_examples = task.prepare_examples_dict(\n", " graphs,\n", " generator_algorithms = ['er']*len(graphs),\n", " encoding_method='adjacency')\n", "\n", "print('First example: \\n')\n", "print('Question: ', encoded_examples[0]['question'])\n", "print('\\n\\nExpected response: ', encoded_examples[0]['answer'])" ] }, { "cell_type": "markdown", "metadata": { "id": "6wN3Da704iMB" }, "source": [ "Ask Gemini to determine if this graph contains a cycle:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "vGMGlmmE4gvH" }, "outputs": [], "source": [ "print(model.generate_content(encoded_examples[0]['question']).text)" ] }, { "cell_type": "markdown", "metadata": { "id": "9I2J0p6Xk-4k" }, "source": [ "Define a simple scoring function for the task, and compute accuracy for the first 10 examples." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "P1GWa-SRlB_M" }, "outputs": [], "source": [ "# This is a fairly simple way of checking the answer.\n", "def judge_cycle_check(example, response_text):\n", " has_cycle = 'yes' in example['answer'].lower()\n", " parsed_response = 'yes' in response_text[:10].lower()\n", " return has_cycle == parsed_response\n", "\n", "\n", "def score_task(model, examples, score_fn):\n", " num_correct = 0\n", " for ex in examples:\n", " response = model.generate_content(ex['question'])\n", " num_correct += score_fn(ex, response.text)\n", " return num_correct / len(examples)\n", "\n", "score_task(model, [encoded_examples[i] for i in range(10)], judge_cycle_check)" ] }, { "cell_type": "markdown", "metadata": { "id": "rI9hm8pPfRuZ" }, "source": [ "We should get a fairly high score (0.9, or 90% in our running), which shows that the model can answer the cycle check task to some level of fidelity." ] }, { "cell_type": "markdown", "metadata": { "id": "a9PhAwLcyh6P" }, "source": [ "## Contest! Design the best Graph Encoding function" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "zDrqxn7tylHG" }, "outputs": [], "source": [ "def my_cool_graph_encoding_function(graph: nx.Graph) -\u003e str:\n", " # TODO - improve this graph encoding function.\n", " return 'The graph is cool.'" ] }, { "cell_type": "markdown", "metadata": { "id": "qAk29lShAkM2" }, "source": [ "Great, so lets try to score my_cool_graph_encoding_function. Because the prompt has nothing to do with the graph (so far), we don't expect the model to do perform well using it." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "etu9L7NzdGWG" }, "outputs": [], "source": [ "# use your function to encode and then score\n", "\n", "task = CycleCheck()\n", "cool_encoded_examples = task.prepare_examples_dict(\n", " graphs,\n", " generator_algorithms = ['er']*len(graphs),\n", " encoding_method=my_cool_graph_encoding_function)\n", "\n", "score_task(model, [cool_encoded_examples[i] for i in range(10)], judge_cycle_check)" ] }, { "cell_type": "markdown", "metadata": { "id": "BFLcG4uDfuFy" }, "source": [ "And the first eval (for us 0.1, or 10%) matches our intuition. A graph encoding should mention something material about the graph!\n", "\n", "Let's try to change that now!" ] }, { "cell_type": "markdown", "metadata": { "id": "K7BRk9VDvDCd" }, "source": [ "## Load DBLP Graph Data" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "id": "hMzsW1sRpg_N" }, "outputs": [], "source": [ "#@title Get Coauthorship Graph\n", "#@markdown You can use a **saved** coauthorship graph, or get one from the DBLP API.\n", "\n", "input_source = \"saved\" # @param [\"saved\",\"dblp\"]\n", "\n", "#@markdown If you want to use another author, you'll need the authorid from DBLP. This can be found from your DBLP profile URL.\n", "\n", "#@markdown If your profile URL is: https://dblp.org/pid/91/10813.html\n", "\n", "#@markdown Then your authorid is:\n", "\n", "dblp_author_id = \"91/10813\" # @param {\"type\":\"string\"}\n", "\n", "#@markdown Lets also write the corresponding author's name for use later.\n", "\n", "author_name = \"Bryan Perozzi\" # @param {\"type\":\"string\"}\n", "\n", "if input_source == \"dblp\":\n", " G = dblp_graph(dblp_author_id, graph_fn=coauthorship_graph)\n", "else:\n", " G = saved_coauthorship_graph" ] }, { "cell_type": "markdown", "metadata": { "id": "q07pigrcwBW7" }, "source": [ "### View Graph Data" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "CV_-FeggeDLe" }, "outputs": [], "source": [ "# array of node label sizes\n", "label_size = {x:max(6, 2*math.log2(y+1)) for x,y in G.degree()}\n", "\n", "Sigma(G,\n", " node_label='name',\n", " raw_node_label_size=label_size,\n", " start_layout=1,\n", " node_metrics=[\"louvain\"],\n", " node_color=\"louvain\",\n", " default_edge_type=\"line\",\n", " node_border_color_from=\"node\",\n", " label_grid_cell_size=50,\n", " label_font=\"sans-serif\")" ] }, { "cell_type": "markdown", "metadata": { "id": "ZIHwHdpEsUad" }, "source": [ "### Make a simple coauthorship encoder." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "8Kc_1cGysFXP" }, "outputs": [], "source": [ "def coauthorship_encoder(graph: nx.Graph) -\u003e str:\n", " text = \"This is the coauthorship information (a graph) of the researcher \" + author_name + \". \"\n", "\n", " authorships = []\n", " for src,dst in G.edges():\n", " src_name = G.nodes[src][\"name\"]\n", " dst_name = G.nodes[dst][\"name\"]\n", " authorships.append(src_name + \" collaborated with \" + dst_name + \".\")\n", "\n", " text += '\\n'.join(authorships).strip()\n", " return text" ] }, { "cell_type": "markdown", "metadata": { "id": "6DVmtrldyn1r" }, "source": [ "## Combining parametric knowledge and graph information" ] }, { "cell_type": "markdown", "metadata": { "id": "6lwQKArYuCel" }, "source": [ "Now that we have an encoder, lets come up with some interesting questions to ask.\n", "\n", "**First**, we'll use the LLM's parametric knowledge to try and answer the question, and then we'll with the graph context added.\n", "\n", "#### **Example Question: How many colaborators are there (degree)**?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "vHXLT7Udt_sC" }, "outputs": [], "source": [ "prompt_input = \"How many coauthors does the researcher \" + author_name + \" have?\"\n", "response = model.generate_content(prompt_input)\n", "print(\"PROMPT: \" + prompt_input)\n", "print(\"OUTPUT: \" + response.text)" ] }, { "cell_type": "markdown", "metadata": { "id": "WC-2SurnvIK1" }, "source": [ "We see that the LLM is hestiant to answer the question. (note: *If it had tried, its quite likely the answer would be wrong*)\n", "\n", "Now let's try using our encoder function from above:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "An6qBhg4vMT3" }, "outputs": [], "source": [ "prompt_input = coauthorship_encoder(G)+ \"\\nHow many collaborators does the researcher \" + author_name + \" have?\"\n", "response = model.generate_content(prompt_input)\n", "# print(\"PROMPT:\" + prompt_input)\n", "print(\"OUTPUT: \" + response.text)\n", "print(\"Expected output: \" + str(G.degree(dblp_author_id)))" ] }, { "cell_type": "markdown", "metadata": { "id": "a_3obiIcvNGo" }, "source": [ "Here we see that the LLM is better able to answer the question, but it still might not be able to do it accurately. 😀\n", "\n", "(*If you are interested in improving the LLM's capabilities, check out Part II of this tutorial.*)" ] }, { "cell_type": "markdown", "metadata": { "id": "agyudyNWvTAm" }, "source": [ "We can use the graph to help a model remember knowledge that it already knows (which can benefit factuality, grounding, etc)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "ZLAOjx_Wc2VT" }, "outputs": [], "source": [] } ], "metadata": { "colab": { "collapsed_sections": [ "N6cnEIIlXhCj", "YFxTwdqup_z5", "-VvPogUCwLUC", "JUCaM8NDYoMx", "I88orawwu4BS" ], "provenance": [ { "file_id": "1P7nxQJmxy0Jsw8GBDDarUb-vrwPFX-F6", "timestamp": 1724521885203 }, { "file_id": "1S9yN9gwUUJmmhPau88lKFNtUYD0G2hnh", "timestamp": 1724422548058 } ] }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: tutorial/KDD-Tutorial-2-Let-Your-Graph-Do-The-Talking.ipynb ================================================ { "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "provenance": [], "gpuType": "V28" }, "kernelspec": { "name": "python3", "display_name": "Python 3" }, "language_info": { "name": "python" }, "accelerator": "TPU" }, "cells": [ { "cell_type": "markdown", "source": [ "This is a noteboook that illustrates how to use Graph Neural Networks to encode structured data for use in Large Language Models. It is Part 2 of a two part tutorial from KDD'24.\n", "\n", "**This notebook requires a TPUv2 runtime**\n", "\n", "If you find this tutorial useful or want to know more, please consider our publication:\n", "Let your graph do the talking: Encoding structured data for LLMs\n", "```\n", "@article{perozzi2024let,\n", " title={Let your graph do the talking: Encoding structured data for llms},\n", " author={Perozzi, Bryan and Fatemi, Bahare and Zelle, Dustin and Tsitsulin, Anton and Kazemi, Mehran and Al-Rfou, Rami and Halcrow, Jonathan},\n", " journal={arXiv preprint arXiv:2402.05862},\n", " year={2024}\n", "}\n", "```\n", "\n", "## Tutorial Part II: GNN Encoding of Graph Information\n", "This notebook takes the work we did in the first part of the tutorial and extends it to using a Graph Neural Network to directly encode a representation of a graph into a prompt (vs using a text encoding as we did in the previous part).\n", "\n", "## Notebook Outline:\n", "\n", "Setup (Install Dependencies, download Gemma weights)\n", "Dataset creation\n", "Graph-to-Text conversion\n", "Evaluation\n", "Exercise: Graph Encoding Challenge\n", "Exercise: DBLP Dataset\n", "Setup\n", "\n", "## Prework!\n", "\n", "Sign-up for Kaggle and consent to the Gemma TOS (this is a requirement to download the Gemma weights used in this notebook).\n", "https://www.kaggle.com/models/google/gemma/license/consent?returnUrl=%2Fmodels%2Fgoogle%2Fgemma%2FFlax%2F2b-it%2F2" ], "metadata": { "id": "EdaaBjBQ3c5g" } }, { "cell_type": "code", "source": [ "%%capture\n", "# @title Install Dependencies\n", "!pip install git+https://github.com/google-deepmind/gemma.git\n", "!pip install --user kaggle\n", "!pip install sparse_deferred\n", "!git clone https://github.com/google-research/talk-like-a-graph.git\n", "import sys\n", "sys.path.insert(0, \"/content/talk-like-a-graph\")\n" ], "metadata": { "id": "BtYsxQuUQ-K0" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Login to Kaggle\n", "Follow the link in the login dialog to get an API key if you don't already have one. Also make sure to approve the [Gemma TOS](https://www.kaggle.com/models/google/gemma/license/consent?returnUrl=%2Fmodels%2Fgoogle%2Fgemma%2FFlax%2F2b-it%2F2) as well." ], "metadata": { "id": "42csum1-l0bn" } }, { "cell_type": "code", "source": [ "import kagglehub\n", "\n", "kagglehub.login()" ], "metadata": { "id": "xmM6CSh7RbIk" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Download Gemma" ], "metadata": { "id": "500UNvQOmcMn" } }, { "cell_type": "code", "source": [ "import os\n", "VARIANT = '2b-it' # @param ['2b', '2b-it', '7b', '7b-it'] {type:\"string\"}\n", "weights_dir = kagglehub.model_download(f'google/gemma/Flax/{VARIANT}')\n", "ckpt_path = os.path.join(weights_dir, VARIANT)\n", "vocab_path = os.path.join(weights_dir, 'tokenizer.model')" ], "metadata": { "id": "W0BzTj2tPROY" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Import dependencies" ], "metadata": { "id": "WGFZHBaCRa77" } }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "ycYOnVRpHZuJ", "cellView": "form" }, "outputs": [], "source": [ "# @title\n", "import os\n", "from collections.abc import Sequence\n", "import dataclasses\n", "from typing import Any, Callable, Mapping\n", "import sys\n", "\n", "\n", "import chex\n", "from flax import linen as nn\n", "import jax\n", "import jax.numpy as jnp\n", "import networkx as nx\n", "import numpy as np\n", "\n", "\n", "from gemma import params as params_lib\n", "from gemma import transformer as transformer_lib\n", "from gemma import sampler as sampler_lib\n", "import sentencepiece as spm\n", "import sparse_deferred as sd\n", "from sparse_deferred import jax as sdjnp\n", "from sparse_deferred.structs import graph_struct\n", "from sparse_deferred import np as sdnp\n" ] }, { "cell_type": "markdown", "source": [ "## GraphToken library code" ], "metadata": { "id": "DzQUN-bCmrjg" } }, { "cell_type": "code", "source": [ "# @title\n", "import collections\n", "from collections.abc import Iterable\n", "import io\n", "import json\n", "from typing import Any, Callable, NamedTuple, Sequence\n", "\n", "import numpy as np\n", "import tqdm\n", "\n", "# Code for converting NetworkX graphs to graph tensor\n", "def laplacian_pos_embedding(graph: nx.Graph, units: int = 4) -\u003e nx.Graph:\n", " \"\"\"Adds the laplacian positional encoding.\"\"\"\n", " m = nx.normalized_laplacian_matrix(\n", " graph, nodelist=sorted(graph.nodes), weight=None\n", " ).astype(np.float32)\n", " u, _, _ = np.linalg.svd(m.todense(), compute_uv=True)\n", " if units \u003e u.shape[1]:\n", " u = np.pad(u, ((0, 0), (0, units - u.shape[1])))\n", " nx.set_node_attributes(\n", " graph, dict(zip(sorted(graph.nodes), u[:, :units])), name='lpe'\n", " )\n", " return graph\n", "\n", "\n", "def to_graph_struct(graph: nx.Graph, node_ids: list[int]=None) -\u003e graph_struct.GraphStruct:\n", " if graph.edges(data=True):\n", " s, t, w = zip(*[\n", " (s, t, (d['weight'] if d and 'weight' in d else None))\n", " for s, t, d in graph.edges(data=True)\n", " ])\n", " else:\n", " s, t, w = (), (), ()\n", " # tfgnn assumes graphs are directed. Adding the rev edges for an undirected\n", " # graph.\n", " if not graph.is_directed():\n", " s, t, w = s + t, t + s, w + w\n", "\n", " graph = laplacian_pos_embedding(graph, units=4)\n", " return graph_struct.GraphStruct.new(\n", " nodes={'nodes': {'lpe': np.stack([graph.nodes('lpe')[i] for i in range(graph.number_of_nodes())])}},\n", " edges={'edges': ((np.array(s, dtype=np.int32), np.array(t, dtype=np.int32)), {})}\n", " )\n", "\n", "\n", "Tensor = sd.matrix.Tensor\n", "Features = dict[str, Tensor]\n", "FeatureSets = dict[str, Features]\n", "Edge = tuple[tuple[Tensor, ...], Features] # (endpoints, edge features)\n", "Edges = dict[str, Edge]\n", "Nodes = FeatureSets\n", "Schema = dict[str, tuple[str, ...]]\n", "_Schema = dict[str, tuple[dict[str, int], ...]]\n", "\n", "\n", "\n", "class FixedSizePadder:\n", " \"\"\"Adds padding to `GraphStruct` instances for fixed-sized tensors.\n", "\n", " Fixed-size tensors can be preferred when running on TPU accelerators.\n", "\n", " To use this class, you must first initialize it with statistics of your graphs\n", " then use it to pad graphs. The statistics can be initialized by invoking\n", " `calculate_pad_statistics`: this function records the *maximum* observerd size\n", " of every node and edge set, as well as the standard deviation (std) of sizes.\n", "\n", " Once initialized, the function: `pad_graph()` will add padding to the graph.\n", " Specifically, the node feature (tensors) will be padded with zeros. Similarly,\n", " edges will be inserted, among newly-added virtual nodes.\n", "\n", " Each node (or edge) size will become:\n", "\n", " `max observed [per calculate_pad_statistics] + slack*std + 1`\n", "\n", " NOTE: there will always be at least one more node or edge, even if the\n", " statistics show zero std. This is required for making virtual nodes.\n", "\n", " All sizes node-set (features) and edge-set (features and adjacency list)\n", " \"\"\"\n", "\n", " def __init__(self, engine: sd.ComputeEngine, slack: float = 1.0):\n", " # `('edge'|'node', NodeOrEdgeName) -\u003e target size`\n", " # where `target size` is maximum observed size for node (or edge) set, plus\n", " # one, plus slack-times-std of observed sizes.\n", " self.sizes: dict[tuple[str, str], int] = {}\n", " self.slack = slack\n", " self._engine = engine\n", "\n", " def calculate_pad_statistics(\n", " self, examples: Iterable[graph_struct.GraphStruct], num_steps: int = 100):\n", " \"\"\"Measures the max and std of node \u0026 edge sizes of elements of `examples`.\n", "\n", " Calling this function is necessary before invoking `pad_graph`.\n", "\n", " Args:\n", " examples: iterable that yields `GraphStruct` examples.\n", " num_steps: If positive, considers this many samples of `examples`.\n", " Otherwise, iterates over all `examples`. Warning: this may run\n", " infinitely on infinite iterators (e.g., `dataset.repeat()`).\n", " \"\"\"\n", " sizes: dict[tuple[str, str], list[int]] = collections.defaultdict(list)\n", " for i, graph in enumerate(examples):\n", " assert isinstance(graph, graph_struct.GraphStruct)\n", " if i \u003e 0 and i \u003e= num_steps:\n", " break\n", " for node_name, features in graph.nodes.items():\n", " value_list = sizes[('nodes', node_name)]\n", " if not features:\n", " value_list.append(0)\n", " else:\n", " value_list.append(list(features.values())[0].shape[0])\n", "\n", " for edge_name, edges_tuple in graph.edges.items():\n", " value_list = sizes[('edges', edge_name)]\n", " source_nodes = edges_tuple[0][0]\n", " # if len(value_list) and edge_set.sizes.shape != value_list[-1].shape:\n", " # continue\n", " value_list.append(source_nodes.shape[0])\n", "\n", " self.sizes = {k: int(1 + max(v) + self.slack * np.std(v))\n", " for k, v in sizes.items()}\n", "\n", " def pad_graph(self, graph: graph_struct.GraphStruct) -\u003e graph_struct.GraphStruct:\n", " \"\"\"Pads node-sets and edge-sets, with zeros, to max-seen during `calc..`.\n", "\n", " This function is useful for running on TPU hardware.\n", "\n", " Args:\n", " graph: contains any number of nodes and edges.\n", "\n", " Returns:\n", " graph with deterministic number of nodes and edges. See class docstring.\n", " \"\"\"\n", " if not self.sizes:\n", " raise ValueError(\n", " 'No statistics have been initialized. '\n", " 'Perhaps you forgot to invoke \"calculate_pad_statistics\"?')\n", " # Edge set name -\u003e (1D vectors containing endpoints**), {\"feature\": Tensor})\n", " edges: Edges = {}\n", " # ** tuple should have 2 entries for directed graphs\n", "\n", " nodes: Nodes = {}\n", "\n", " # For every key in `edges`, store names of node sets that `key` edge\n", " # connects.\n", " schema = graph.schema\n", "\n", " e = self._engine # for short.\n", " for node_name, node_features in graph.nodes.items():\n", " padded_features = {}\n", " desired_size = self.sizes[('nodes', node_name)]\n", "\n", " for feature_name, feature in node_features.items():\n", " feature = feature[:desired_size] # if `is_oversized`.\n", " pad = self._engine.maximum(\n", " desired_size - self._engine.shape(feature)[0], 0)\n", " zeros = e.zeros(\n", " tuple([pad] + list(feature.shape[1:])), dtype=feature.dtype)\n", " padded_feature = e.concat([feature, zeros], axis=0)\n", " padded_feature = e.reshape(\n", " padded_feature, [desired_size] + list(padded_feature.shape[1:]))\n", " padded_features[feature_name] = padded_feature\n", "\n", " nodes[node_name] = padded_features\n", "\n", " for edge_name, (edge_endpoints, features) in graph.edges.items():\n", " padded_features = {}\n", " padded_endpoints = []\n", " desired_size = self.sizes[('edges', edge_name)]\n", " current_size = e.shape(edge_endpoints[0])[0]\n", "\n", " pad = e.maximum(desired_size - current_size, 0)\n", " e.assert_greater(pad, -1)\n", "\n", " for feature_name, feature in features.items():\n", " feature = feature[:desired_size] # if `is_oversized`.\n", " zeros = e.zeros(\n", " tuple([pad] + list(feature.shape[1:])), dtype=feature.dtype\n", " )\n", " padded_feature = e.concat([feature, zeros], axis=0)\n", " padded_feature = e.reshape(\n", " padded_feature, [desired_size] + list(padded_feature.shape[1:])\n", " )\n", " padded_features[feature_name] = padded_feature\n", "\n", " edge_endpoints = [node_ids[:desired_size] for node_ids in edge_endpoints]\n", " # [[src1_is_valid, src2_is_valid, ...], [tgt1_is_valid, ...]]\n", " valid = e.cast(\n", " [\n", " ids \u003c self.sizes[('nodes', node_name)]\n", " for ids, node_name in zip(edge_endpoints, schema[edge_name])\n", " ],\n", " dtype=bool,\n", " )\n", " valid = e.reduce_all(valid, axis=0)\n", "\n", " for node_ids, node_name in zip(edge_endpoints, schema[edge_name]):\n", " # Universe size (e.g., of source or target).\n", " max_endpoint = self.sizes[('nodes', node_name)] - 1\n", " node_ids = node_ids[:desired_size]\n", " node_ids = e.boolean_mask(node_ids, valid)\n", " pad = desired_size - e.shape(node_ids)[0] # Need only to compute once.\n", "\n", " padded_ids = e.concat([\n", " node_ids,\n", " e.ones((pad), dtype=node_ids.dtype) * max_endpoint\n", " ], axis=0)\n", " padded_ids = e.reshape(padded_ids, [desired_size])\n", " padded_endpoints.append(padded_ids)\n", "\n", " edges[edge_name] = (tuple(padded_endpoints), padded_features)\n", "\n", " graph = graph_struct.GraphStruct.new(nodes=nodes, edges=edges, schema=schema)\n", " return graph\n", "\n", "\n", "\n", "## gnn.py\n", "class GIN(nn.Module):\n", " \"\"\"Graph Isomorphism Network: https://arxiv.org/pdf/1810.00826.pdf.\"\"\"\n", "\n", " output_dim: int\n", " num_hidden_layers: int = 1\n", " hidden_dim: int = 32\n", " epsilon: float = 0.1 # See GIN paper (link above)\n", "\n", " def setup(self):\n", " layer_dims = [self.hidden_dim] * self.num_hidden_layers\n", " self.layers = [\n", " nn.Dense(dim, use_bias=False, dtype=jnp.bfloat16) for dim in layer_dims\n", " ]\n", " self.out_layer = nn.Dense(\n", " self.output_dim, use_bias=False, dtype=jnp.bfloat16\n", " )\n", "\n", " def __call__(self, graph: graph_struct.GraphStruct) -\u003e jax.Array:\n", " x = graph.nodes['nodes']['lpe']\n", " adj = graph.adj(sdjnp.engine, 'edges')\n", " adj = adj.add_eye(1 + self.epsilon) # self connections with 1+eps weight.\n", "\n", " for i, layer in enumerate(self.layers):\n", " x = layer(adj @ x)\n", " if i \u003c self.num_hidden_layers:\n", " x = nn.relu(x)\n", " x = jnp.concat(x, axis=-1)\n", " return self.out_layer(x)\n", "\n", "\n", "class GCN(nn.Module):\n", " \"\"\"Graph convolutional network: https://arxiv.org/pdf/1609.02907.pdf.\"\"\"\n", "\n", " output_dim: int\n", " num_hidden_layers: int = 1\n", " hidden_dim: int = 32\n", "\n", " def setup(self):\n", " layer_dims = [self.hidden_dim] * self.num_hidden_layers\n", " self.layers = [nn.Dense(dim, use_bias=False) for dim in layer_dims]\n", " self.out_layer = nn.Dense(\n", " self.output_dim, use_bias=False, dtype=jnp.bfloat16\n", " )\n", "\n", " def __call__(self, graph: graph_struct.GraphStruct) -\u003e jax.Array:\n", " x = graph.nodes['nodes']['lpe']\n", " adj = graph.adj(sdjnp.engine, 'edges')\n", " adj_symnorm = (adj + adj.transpose()).add_eye().normalize_symmetric()\n", "\n", " for i, layer in enumerate(self.layers):\n", " x = layer(adj_symnorm @ x)\n", " if i \u003c self.num_hidden_layers:\n", " x = nn.relu(x)\n", " x = jnp.concat(x, axis=-1)\n", " return self.out_layer(x)\n", "\n", "\n", "## sampler.py\n", "\n", "@dataclasses.dataclass\n", "class SamplerOutput:\n", "\n", " # Decoded samples from the model.\n", " text: list[str]\n", "\n", " # Per-step logits used during sampling.\n", " logits: list[list[float]]\n", "\n", " # Tokens corresponding to the generated samples.\n", " tokens: list[list[int]]\n", "\n", " graph_embeddings: list[jnp.ndarray]\n", "\n", "\n", "class GraphTokenSampler:\n", " \"\"\"Sampler for GraphToken.\"\"\"\n", "\n", " def __init__(\n", " self,\n", " gnn: nn.Module,\n", " llm: transformer_lib.Transformer,\n", " vocab: spm.SentencePieceProcessor,\n", " params: Mapping[str, Any],\n", " gnn_token_template: str = r'\u003cunused%d\u003e',\n", " ):\n", " \"\"\"Initializes the sampler.\n", "\n", " Args:\n", " gnn: The GNN model.\n", " llm: The LLM model.\n", " vocab: The vocab used by the LLM.\n", " params: The parameters for the GNN and LLM. This should contain the params\n", " for the gnn under params['gnn'] and the params for the llm under\n", " params['transformer']\n", " gnn_token_template: The token used to represent the GNN embedding.\n", " \"\"\"\n", "\n", " self._gnn = gnn\n", " self._llm = llm\n", " self._params = params\n", " self._vocab = vocab\n", " self._gnn_token_template = gnn_token_template\n", " self._sampler = sampler_lib.Sampler(\n", " transformer=self._llm,\n", " vocab=self._vocab,\n", " params=self._params['transformer'],\n", " )\n", "\n", " def __call__(\n", " self,\n", " input_strings: Sequence[str],\n", " input_graphs: Sequence[graph_struct.GraphStruct],\n", " total_generation_steps: int,\n", " echo: bool = False,\n", " return_logits: bool = True,\n", " forbidden_tokens: Sequence[str] | None = None,\n", " ) -\u003e SamplerOutput:\n", " \"\"\"Samples from the model.\n", "\n", " Args:\n", " input_strings: The input strings.\n", " input_graphs: The input graphs.\n", " total_generation_steps: The number of steps to generate.\n", " echo: Whether to echo the input.\n", " return_logits: Whether to return the logits.\n", " forbidden_tokens: Tokens that are forbidden, in addition to the GNN token.\n", "\n", " Returns:\n", " The sampled output.\n", " \"\"\"\n", " assert len(input_graphs) == len(input_strings), (\n", " len(input_graphs),\n", " len(input_strings),\n", " )\n", " augmented_inputs = []\n", " full_forbidden_tokens = []\n", " if forbidden_tokens is not None:\n", " full_forbidden_tokens += forbidden_tokens\n", " graph_embeddings = []\n", " augmented_transformer_params = self._params['transformer']\n", "\n", " placeholder_token = PLACEHOLDER_TOKEN\n", " full_forbidden_tokens.append(placeholder_token)\n", " placeholder_token_id = self._vocab.EncodeAsIds(placeholder_token)\n", " assert len(placeholder_token_id) == 1, placeholder_token\n", " placeholder_token_id = placeholder_token_id[0]\n", "\n", " for prompt, graph in zip(input_strings, input_graphs):\n", " embed = self._gnn.apply(self._params['gnn'], graph)\n", " assert (\n", " self._params['transformer']['embedder']['input_embedding'][\n", " placeholder_token_id\n", " ].shape\n", " == embed.shape\n", " )\n", " augmented_transformer_params['embedder']['input_embedding'] = (\n", " augmented_transformer_params['embedder']['input_embedding']\n", " .at[placeholder_token_id]\n", " .set(embed)\n", " )\n", " graph_embeddings.append(embed)\n", " augmented_inputs.append(placeholder_token + prompt)\n", "\n", " self._sampler.params = augmented_transformer_params\n", " o = self._sampler(\n", " input_strings=augmented_inputs,\n", " total_generation_steps=total_generation_steps,\n", " echo=echo,\n", " return_logits=return_logits,\n", " forbidden_tokens=full_forbidden_tokens,\n", " )\n", " return SamplerOutput(\n", " **dataclasses.asdict(o),\n", " graph_embeddings=graph_embeddings,\n", " )\n", "\n", "\n", "\n", "@chex.dataclass(frozen=True)\n", "class TrainingInput:\n", " \"\"\"Batch of training data for a GraphToken model.\"\"\"\n", "\n", " # Input tokens given to the model\n", " input_tokens: np.ndarray # size [B, L]\n", "\n", " # A mask that determines which tokens contribute to the target loss\n", " # calculation.\n", " target_mask: np.ndarray # size [B, L]\n", "\n", " input_graphs: list[graph_struct.GraphStruct] # size [B]\n", "\n", " # Ground truth for the input tokens, if representable as an integer.\n", " # For boolean classification tasks, this is 0/1.\n", " parsed_ground_truth: np.ndarray | None # size [B]\n", "\n", "\n", "def parse_int(s: str) -\u003e int:\n", " \"\"\"Parse a string as an integer.\"\"\"\n", " return int(float(s.strip()))\n", "\n", "\n", "def parse_yes_no(s: str) -\u003e bool:\n", " \"\"\"Parse a string as a yes/no answer, looking at the first 10 chars.\"\"\"\n", " return 'yes' in s.lower()[:10]\n", "\n", "PLACEHOLDER_TOKEN = '\u003cunused0\u003e'\n", "\n", "def graphqa_ds(\n", " vocab: spm.SentencePieceProcessor,\n", " encoded_examples: list,\n", " padder: graph_struct.FixedSizePadder | None = None,\n", " max_tokens: int = 100,\n", " gt_parser: Callable[[str], Any] | None = None,\n", ") -\u003e tuple[graph_struct.FixedSizePadder, list[TrainingInput]]:\n", " \"\"\"Load a GraphQA dataset as a list of TrainingInput.\n", "\n", " Args:\n", " vocab: The vocab to use for tokenization.\n", " encoded_examples: List of encoded examples generated by GraphQA\n", " padder: The padder to use for padding the graph. If None, a new padder will\n", " be created and returned. This is so a padder can be shared across multiple\n", " datasets / splits.\n", " max_tokens: The maximum number of tokens to allow in the input. For\n", " 'task_only' prompting this can be quite small (100 tokens is plenty)\n", " gt_parser: A function to parse the ground truth from the answer string, used\n", " to supply the 'parsed_ground_truth' field in the TrainingInput.\n", "\n", " Returns:\n", " The padder used for padding the graphs, and a list of TrainingInput.\n", " \"\"\"\n", "\n", " output = []\n", " for ex in encoded_examples:\n", " query = PLACEHOLDER_TOKEN + ex['question'][ex['question'].find('Q:'):]\n", " answer = ex['answer']\n", " graph = to_graph_struct(ex['graph'])\n", " query_tokens = vocab.EncodeAsIds(query)\n", " answer_tokens = vocab.EncodeAsIds(answer) + [vocab.eos_id()]\n", " input_tokens = np.array([vocab.bos_id()] + query_tokens + answer_tokens)\n", " target_mask = np.zeros_like(input_tokens, dtype=jnp.int32)\n", " # Add one for BOS token\n", " target_mask[len(query_tokens) + 1 :] = 1\n", " orig_len = len(query_tokens) + len(answer_tokens) + 1\n", " input_tokens = np.pad(\n", " input_tokens,\n", " [[0, max_tokens - orig_len]],\n", " constant_values=vocab.pad_id(),\n", " )\n", "\n", " target_mask = np.pad(target_mask, [[0, max_tokens - orig_len]])\n", "\n", "\n", " # The GNN library that we are using requires a global feature. We set\n", " # a fake value here, but it is unused otherwise.\n", " #graph = graph.update(nodes={'g': {'foo': np.zeros([1])}})\n", "\n", " output.append(\n", " TrainingInput(\n", " input_tokens=np.array([input_tokens]),\n", " target_mask=np.array([target_mask]),\n", " input_graphs=[graph],\n", " parsed_ground_truth=np.array(gt_parser(answer)) if gt_parser else None,\n", " )\n", " )\n", " if padder is None:\n", " padder = FixedSizePadder(sdnp.engine)\n", " padder.calculate_pad_statistics(\n", " [e.input_graphs[0] for e in output], len(output)\n", " )\n", " for o in output:\n", " o.input_graphs[0] = padder.pad_graph(o.input_graphs[0])\n", " return padder, output\n", "\n", "\n", "def decode_questions(\n", " training_input: TrainingInput, vocab: spm.SentencePieceProcessor\n", ") -\u003e list[str]:\n", " \"\"\"Decode the question from the input tokens. (ignoring the first 2).\"\"\"\n", " b, l = training_input.input_tokens.shape\n", " question_tokens = []\n", " for i in range(b):\n", " question_tokens.append([])\n", " # Skip the first two tokens (BOS and control token).\n", " for j in range(2, l):\n", " if training_input.target_mask[i, j] == 1:\n", " break\n", " question_tokens[i].append(int(training_input.input_tokens[i, j]))\n", " return [''.join(vocab.DecodeIds(q)) for q in question_tokens]\n", "\n", "\n", "## training_loop\n", "import functools\n", "from typing import Any, MutableMapping\n", "\n", "import chex\n", "from flax import linen as nn\n", "from gemma import transformer as transformer_lib\n", "import jax\n", "import jax.numpy as jnp\n", "import optax\n", "import tqdm\n", "\n", "Params = MutableMapping[str, Any]\n", "\n", "\n", "def get_attention_mask_and_positions(\n", " example: jax.Array,\n", " pad_id: int,\n", ") -\u003e tuple[jax.Array, jax.Array]:\n", " \"\"\"Builds the position and attention mask vectors from the given tokens.\"\"\"\n", " pad_mask = example != pad_id\n", " current_token_position = transformer_lib.build_positions_from_mask(pad_mask)\n", " attention_mask = transformer_lib.make_causal_attn_mask(pad_mask)\n", " return current_token_position, attention_mask\n", "\n", "\n", "def forward_and_loss_fn(\n", " params: Params,\n", " *,\n", " gnn: nn.Module,\n", " llm: transformer_lib.Transformer,\n", " input_tokens: jax.Array, # Shape [B, L]\n", " input_graphs: list[graph_struct.GraphStruct], # Shape [B]\n", " input_mask: jax.Array, # Shape [B, L]\n", " positions: jax.Array, # Shape [B, L]\n", " attention_mask: jax.Array, # [B, L, L]\n", " placeholder_token_id: int,\n", ") -\u003e jax.Array:\n", " \"\"\"Forward pass and loss function.\n", "\n", " Args:\n", " params: Params for the gnn and transformer. The gnn params are stored in\n", " params['gnn'] and the llm params are stored in params['transformer'].\n", " gnn: gnn model to call.\n", " llm: gemma transformer model to call.\n", " input_tokens: input tokens sequence, shape [B, L].\n", " input_graphs: input graphs.\n", " input_mask: tokens to ignore when computing the loss, shape [B, L].\n", " positions: relative position of each token, shape [B, L].\n", " attention_mask: input attention mask, shape [B, L].\n", " placeholder_token_id: Index in the LLM vocabulary that we are using for passing\n", " graph embeddings.\n", "\n", " Returns:\n", " Softmax cross-entropy loss for the next-token prediction task.\n", " \"\"\"\n", " # Right now we only support batch_size = 1\n", " chex.assert_axis_dimension(input_tokens, 0, 1)\n", " chex.assert_equal_shape([input_tokens, input_mask, positions])\n", " chex.assert_axis_dimension(attention_mask, 0, 1)\n", " chex.assert_equal(len(input_graphs), 1)\n", "\n", " # Get the GNN embedding and update the transformer input embedding for a\n", " # control token.\n", " graph_embed = gnn.apply(params['gnn'], input_graphs[0])\n", " params['transformer']['embedder']['input_embedding'] = (\n", " params['transformer']['embedder']['input_embedding']\n", " .at[placeholder_token_id]\n", " .set(graph_embed)\n", " )\n", " # Forward pass on the input data.\n", " # No attention cache is needed here.\n", " logits, _ = llm.apply(\n", " {'params': params['transformer']},\n", " input_tokens,\n", " positions,\n", " None, # Attention cache is None.\n", " attention_mask,\n", " )\n", "\n", " # Exclude the last step as it does not appear in the targets.\n", " logits = logits[0, :-1]\n", "\n", " # Similarly, the first token cannot be predicted.\n", " target_tokens = input_tokens[0, 1:]\n", " target_mask = input_mask[0, 1:]\n", "\n", " # Convert the target labels into one-hot encoded vectors.\n", " one_hot = jax.nn.one_hot(target_tokens, logits.shape[-1])\n", "\n", " # Don't update on unwanted tokens.\n", " one_hot = one_hot * target_mask.astype(one_hot.dtype)[..., jnp.newaxis]\n", "\n", " # Normalisation factor.\n", " norm_factor = 1 / (jnp.sum(target_mask) + 1e-8)\n", "\n", " # Return the nll loss.\n", " return -jnp.sum(jax.nn.log_softmax(logits) * one_hot) * norm_factor\n", "\n", "\n", "@functools.partial(\n", " jax.jit,\n", " static_argnames=['gnn', 'llm', 'optimizer', 'pad_id', 'placeholder_token_id'],\n", ")\n", "def train_step(\n", " llm: transformer_lib.Transformer,\n", " gnn: nn.Module,\n", " params: MutableMapping[str, Any],\n", " optimizer: optax.GradientTransformation,\n", " opt_state: optax.OptState,\n", " pad_id: int,\n", " example: TrainingInput,\n", " placeholder_token_id: int,\n", ") -\u003e tuple[jax.Array, Params, optax.OptState]:\n", " \"\"\"Train step.\n", "\n", " Args:\n", " llm: gemma transformer model.\n", " gnn: gnn model.\n", " params: model's input parameters.\n", " optimizer: optax optimizer to use.\n", " opt_state: input optimizer's state.\n", " pad_id: id of the pad token.\n", " example: input batch.\n", " placeholder_token_id: Index in the LLM vocabulary that we are using for passing\n", " graph embeddings.\n", "\n", " Returns:\n", " Training loss, updated parameters, updated optimizer state.\n", " \"\"\"\n", "\n", " # Build the position and attention mask vectors.\n", " positions, attention_mask = get_attention_mask_and_positions(\n", " jnp.array(example.input_tokens), pad_id\n", " )\n", "\n", " # Forward and backward passes\n", " train_loss, grads = jax.value_and_grad(forward_and_loss_fn)(\n", " params,\n", " gnn=gnn,\n", " llm=llm,\n", " input_tokens=example.input_tokens,\n", " input_mask=example.target_mask,\n", " input_graphs=example.input_graphs,\n", " positions=positions,\n", " attention_mask=attention_mask,\n", " placeholder_token_id=placeholder_token_id,\n", " )\n", "\n", " updates, opt_state = optimizer.update(\n", " grads['gnn'], opt_state, params=params['gnn']\n", " )\n", " params['gnn'] = optax.apply_updates(params['gnn'], updates)\n", "\n", " return train_loss, params, opt_state\n", "\n", "\n", "@functools.partial(\n", " jax.jit, static_argnames=['gnn', 'llm', 'pad_id', 'placeholder_token_id']\n", ")\n", "def validation_step(\n", " gnn: nn.Module,\n", " llm: transformer_lib.Transformer,\n", " params: MutableMapping[str, Any],\n", " pad_id: int,\n", " example: TrainingInput,\n", " placeholder_token_id: int,\n", ") -\u003e jax.Array:\n", " \"\"\"Validation step.\n", "\n", " Args:\n", " gnn: gnn model.\n", " llm: gemma transformer model.\n", " params: model's input parameters. The gnn params are stored in params['gnn']\n", " and the llm params are stored in params['transformer'].\n", " pad_id: id of the pad token.\n", " example: input batch\n", " placeholder_token_id: Index in the LLM vocabulary that we are using for passing\n", " graph embeddings.\n", "\n", " Returns:\n", " Validation loss.\n", " \"\"\"\n", " jax_input = jax.tree.map(jnp.array, example)\n", " positions, attention_mask = get_attention_mask_and_positions(\n", " jax_input.input_tokens, pad_id\n", " )\n", " val_loss = forward_and_loss_fn(\n", " params,\n", " gnn=gnn,\n", " llm=llm,\n", " input_tokens=jax_input.input_tokens,\n", " input_mask=jax_input.target_mask,\n", " input_graphs=jax_input.input_graphs,\n", " positions=positions,\n", " attention_mask=attention_mask,\n", " placeholder_token_id=placeholder_token_id,\n", " )\n", " return val_loss\n", "\n", "\n", "@chex.dataclass(frozen=True)\n", "class TrainingConfig:\n", " learning_rate: float\n", " num_epochs: int\n", " eval_every_n: int\n", " batch_size: int\n", " max_steps: int | None = None\n", "\n", "\n", "def train_loop(\n", " llm: transformer_lib.Transformer,\n", " gnn: nn.Module,\n", " train_ds: list[TrainingInput],\n", " validation_ds: list[TrainingInput],\n", " params: Params,\n", " training_cfg: TrainingConfig,\n", " vocab: spm.SentencePieceProcessor,\n", ") -\u003e Params:\n", " \"\"\"Main training loop for GraphToken.\n", "\n", " Args:\n", " llm: Gemma transformer model.\n", " gnn: gnn model.\n", " train_ds: training dataset.\n", " validation_ds: validation dataset.\n", " params: Combined params for both the LLM and GNN. The GNN params are stored\n", " in params['gnn'] and the LLM params are stored in params['transformer'].\n", " training_cfg: training configuration.\n", " vocab: sentence piece vocabulary.\n", "\n", " Returns:\n", " Updated model's input parameters.\n", " \"\"\"\n", " optimizer = optax.lion(training_cfg.learning_rate)\n", " opt_state = optimizer.init(params['gnn'])\n", "\n", " avg_loss = 0\n", "\n", " placeholder_token_id = vocab.EncodeAsIds(PLACEHOLDER_TOKEN)\n", " assert (\n", " len(placeholder_token_id) == 1\n", " ), f'Placeholder token multiple ids: {placeholder_token_id}'\n", " placeholder_token_id = placeholder_token_id[0]\n", " # A first round of validation loss\n", " n_steps_eval = 0\n", " eval_loss = 0\n", "\n", " with tqdm.tqdm(range(training_cfg.num_epochs * len(train_ds))) as pbar:\n", " averaged_steps = 0\n", " for n_steps in pbar:\n", " train_example = train_ds[n_steps % len(train_ds)]\n", " train_loss, params, opt_state = train_step(\n", " gnn=gnn,\n", " llm=llm,\n", " params=params,\n", " optimizer=optimizer,\n", " opt_state=opt_state,\n", " pad_id=vocab.pad_id(),\n", " example=train_example,\n", " placeholder_token_id=placeholder_token_id,\n", " )\n", " averaged_steps += 1\n", " avg_loss += train_loss\n", " if n_steps and n_steps % training_cfg.eval_every_n == 0:\n", " val_iterator = validation_ds\n", " avg_loss /= averaged_steps\n", " averaged_steps = 0\n", " pbar.write(\n", " f'STEP {n_steps} training loss: {avg_loss}'\n", " )\n", " avg_loss = 0\n", " if (\n", " training_cfg.max_steps is not None\n", " and n_steps \u003e training_cfg.max_steps\n", " ):\n", " break\n", " if averaged_steps != 0:\n", " avg_loss /= averaged_steps\n", " pbar.write(\n", " f'STEP {n_steps} training loss: {avg_loss}'\n", " )\n", " return params\n", "\n", "def merge_params(llm_params, gnn_params):\n", " out = {}\n", " out.update(llm_params)\n", " out['gnn'] = gnn_params\n", " return out" ], "metadata": { "id": "RZKA-lU7IsZ8", "cellView": "form" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Train a GraphToken Model" ], "metadata": { "id": "NSuoM8KbnXbF" } }, { "cell_type": "markdown", "source": [ "Load Gemma Weights" ], "metadata": { "id": "oknoqsTMI78J" } }, { "cell_type": "code", "source": [ "params = params_lib.load_and_format_params(ckpt_path)\n", "\n", "# Reshard params over TPU device mesh\n", "from jax.sharding import PartitionSpec as P\n", "mesh = jax.sharding.Mesh(np.array(jax.devices()).reshape(4, 2), ('x', 'y'))\n", "sharding = jax.sharding.NamedSharding(mesh, P('x'))\n", "def try_to_shard(x):\n", " try:\n", " return jax.device_put(x, sharding)\n", " except:\n", " return x\n", "params = jax.tree_map(try_to_shard, params)\n", "\n", "\n", "config_2b = transformer_lib.TransformerConfig.from_params(\n", " params,\n", " cache_size=128 # Number of time steps in the transformer's cache\n", ")\n", "model_2b = transformer_lib.Transformer(config=config_2b)\n", "\n", "# Load vocabulary\n", "vocab = spm.SentencePieceProcessor()\n", "assert vocab.Load(vocab_path)" ], "metadata": { "id": "U1q6PiLSCTTa" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "Generate some training data, for the CycleCheck task" ], "metadata": { "id": "NUDd2EisJHX_" } }, { "cell_type": "code", "source": [ "from talk_like_a_graph import graph_generators\n", "from talk_like_a_graph import graph_tasks\n", "random_seed = 9876\n", "\n", "train_graphs = graph_generators.generate_graphs(number_of_graphs=500,\n", " algorithm='er', # Erdos-Reyni random graphs\n", " directed=False,\n", " random_seed=random_seed)\n", "test_graphs = graph_generators.generate_graphs(number_of_graphs=10,\n", " algorithm='er', # Erdos-Reyni random graphs\n", " directed=False,\n", " random_seed=random_seed + 12385)\n", "task = graph_tasks.CycleCheck()\n", "train_examples = list(task.prepare_examples_dict(\n", " train_graphs,\n", " generator_algorithms = ['er']*len(train_graphs),\n", " encoding_method='adjacency').values())\n", "test_examples = list(task.prepare_examples_dict(\n", " test_graphs,\n", " generator_algorithms = ['er']*len(test_graphs),\n", " encoding_method='adjacency').values())\n", "padder, train_ds = graphqa_ds(vocab, train_examples, max_tokens=25)\n", "_, test_ds = graphqa_ds(vocab, test_examples, max_tokens=25, padder=padder)" ], "metadata": { "id": "93oCqfLYnXFr" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "Train GraphToken" ], "metadata": { "id": "dJTlxG9yUx9y" } }, { "cell_type": "code", "source": [ "gin = GIN(config_2b.embed_dim, num_hidden_layers=3, hidden_dim=4)\n", "key = jax.random.PRNGKey(0)\n", "gnn_params = gin.init(key, train_ds[0].input_graphs[0])\n", "\n", "\n", "train_config = TrainingConfig(\n", " learning_rate=0.0001, num_epochs=3, eval_every_n=250, batch_size=1\n", ")\n", "params_learned = train_loop(\n", " llm=model_2b,\n", " gnn=gin,\n", " train_ds=train_ds,\n", " validation_ds=test_ds,\n", " params=merge_params(params, gnn_params),\n", " training_cfg=train_config,\n", " vocab=vocab,\n", ")" ], "metadata": { "id": "Uq3ZF3v7Ivo7" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "Sample outputs" ], "metadata": { "id": "4eaBMWhPchoJ" } }, { "cell_type": "code", "source": [ "from IPython.display import Markdown, display\n", "\n", "graph_token_sampler = GraphTokenSampler(\n", " params=params_learned, llm=model_2b, gnn=gin, vocab=vocab\n", ")\n", "\n", "\n", "def get_graph_qa_question(ex):\n", " with_graph = ex['question']\n", " q_index = with_graph.find('Q:')\n", " return with_graph[q_index:]\n", "\n", "for i in range(len(test_examples)):\n", " tokenized_input = test_ds[i]\n", " ex = test_examples[i]\n", " prompt = get_graph_qa_question(ex)\n", " if i == 0:\n", " display(\n", " Markdown(\n", " '**Prompt:** '\n", " + prompt\n", " + '\\n\\n'\n", " )\n", " )\n", " llm_output = graph_token_sampler(\n", " [prompt],\n", " tokenized_input.input_graphs,\n", " total_generation_steps=15,\n", " return_logits=False,\n", " ).text[0]\n", " display(Markdown(f'**LLM Output:** \"{llm_output}\"'))\n", " display(\n", " Markdown(f\"**Ground Truth:** {ex['answer']}\")\n", " )\n", " display(Markdown('-' * 80))\n", " print()\n", "\n" ], "metadata": { "id": "0p4CxB2Lcilw" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Exercise: Train a model for a different task.\n", "\n", "Take the above code and modify it for the NodeCount task.\n", "How does your model perform?\n", "\n", "If your kernel runs out of memory run the following code to clear the TPU memory, then re-run the code block labeled 'Load Gemma weights' and retry.\n", "```\n", "for a in jax.live_arrays():\n", " a.delete()\n", "```" ], "metadata": { "id": "uuW3rA3-qKy9" } } ] } ================================================ FILE: tutorial/README.md ================================================ #

Tutorial on Graph Reasoning with LLMs (GReaL)

##

**KDD'24 Tutorial**

###

Sunday, August 25th, 2024

Centre de Convencions Internacional de Barcelona

P1 122-123

2:00 pm - 5:00 pm CEST

### Overview Graphs are a powerful tool for representing and analyzing complex relationships in real-world applications. Large Language Models (LLMs) have demonstrated impressive capabilities by advancing state-of-the-art on many language-based benchmarks. Their ability to process and understand natural language open exciting possibilities in various domains. Despite the remarkable progress in automated reasoning with natural text, reasoning on graphs with LLMs remains an understudied problem that has recently gained more attention. This tutorial builds upon recent advances in expressing reasoning problems through the lens of tasks on graph data. The first part of the tutorial will provide an in-depth discussion of techniques for representing graphs as inputs to LLMs. The second, hands-on, portion will demonstrate these techniques in a practical setting. ### Schedule This talk is being given at [KDD 2024](https://kdd2024.kdd.org/) at 2pm in room P1 122-123 at the Centre de Convencions Internacional de Barcelona. ### Tutorial Material The slides and notebooks used in the tutorial are available here: - [Tutorial Slides](https://drive.google.com/file/d/16rHZVyCeGDfY3djVafKHFPhnabOVtd03/) - [Talk Like a Graph Notebook](https://github.com/google-research/talk-like-a-graph/blob/main/tutorial/KDD-Tutorial-1-Talk-Like-a-Graph.ipynb) - [GraphToken (Let Your Graph Do the Talking) Notebook](https://github.com/google-research/talk-like-a-graph/blob/main/tutorial/KDD-Tutorial-2-Let-Your-Graph-Do-The-Talking.ipynb) ### Additional Resources *coming soon*