From 89c36bd4250b239db7cd9310d2544cf65d539b53 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:28:13 -0700 Subject: [PATCH 01/21] Add minimum viable mkdocs configuration --- g3doc/assets/favicon.png | Bin 0 -> 404 bytes g3doc/index.md | 0 g3doc/javascripts/mathjax.js | 19 ++++++ g3doc/stylesheets/extra.css | 16 +++++ mkdocs.yml | 118 +++++++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+) create mode 100644 g3doc/assets/favicon.png create mode 100644 g3doc/index.md create mode 100644 g3doc/javascripts/mathjax.js create mode 100644 g3doc/stylesheets/extra.css create mode 100644 mkdocs.yml diff --git a/g3doc/assets/favicon.png b/g3doc/assets/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..00a8af6dabc6bd2705678240ab4a59398f490e80 GIT binary patch literal 404 zcmV;F0c-w=P)bWUp}xZee%_FF+E28Gr&LzQXejWB zFF+E26#x_I#XCg22?r-%Yj0PYEp7nRKcP5}NPfXW>d zAQS*X4L}sYk;G^K2?3k{u>_C=$Zl&HU|oxb9nw&M7hvPP797D5^YtB!^1A>lE_VEA zWtT8;3G)qN)hY)JtNaxtw(!6H4J$lDHv|41;8=iuIsgd)i~wGMGznl`3oC$`4v;qk yX22l>+=$W4)dl4JFr1FvuXH>&TgG!!vezfc^{4$!^Uogu0000 { + MathJax.startup.output.clearCache() + MathJax.typesetClear() + MathJax.texReset() + MathJax.typesetPromise() +}) diff --git a/g3doc/stylesheets/extra.css b/g3doc/stylesheets/extra.css new file mode 100644 index 0000000..2c35c02 --- /dev/null +++ b/g3doc/stylesheets/extra.css @@ -0,0 +1,16 @@ +:root { + --md-primary-fg-color: #FFA800; + --md-primary-fg-color--light: #CCCCCC; + --md-primary-fg-color--dark: #425066; +} + +.video-wrapper { + max-width: 240px; + display: flex; + flex-direction: row; +} +.video-wrapper > iframe { + width: 100%; + aspect-ratio: 16 / 9; +} + diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..5f84685 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,118 @@ +site_name: Struct2Tensor +repo_name: "Struct2Tensor" +repo_url: https://github.com/google/struct2tensor + +docs_dir: g3doc + +theme: + name: material + palette: + # Palette toggle for automatic mode + - media: "(prefers-color-scheme)" + primary: custom + accent: custom + toggle: + icon: material/brightness-auto + name: Switch to light mode + + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + primary: custom + accent: custom + scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + primary: custom + accent: custom + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to system preference + favicon: assets/favicon.png + + features: + - content.code.copy + - content.code.select + - content.action.edit +plugins: + - search + - autorefs + - mkdocstrings: + default_handler: python + handlers: + python: + options: + show_source: true + show_root_heading: true + unwrap_annotated: true + show_symbol_type_toc: true + show_symbol_type_heading: true + merge_init_into_class: true + show_signature_annotations: true + separate_signature: true + signature_crossrefs: true + group_by_category: true + show_category_heading: true + inherited_members: true + show_submodules: true + show_object_full_path: false + show_root_full_path: true + docstring_section_style: "spacy" + summary: true + filters: + - "!^_" + - "^__init__$" + - "^__call__$" + - "!^logger" + extensions: + - griffe_inherited_docstrings + import: + - https://docs.python.org/3/objects.inv + - mkdocs-jupyter: + execute: false + - caption: + figure: + ignore_alt: true + +markdown_extensions: + - admonition + - attr_list + - def_list + - tables + - toc: + permalink: true + - pymdownx.highlight: + anchor_linenums: true + linenums: false + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.arithmatex: + generic: true + - pymdownx.critic + - pymdownx.caret + - pymdownx.keys + - pymdownx.mark + - pymdownx.tilde + - markdown_grid_tables + - md_in_html + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + +extra_css: + - stylesheets/extra.css + +extra_javascript: + - javascripts/mathjax.js + - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js + +watch: + - struct2tensor +nav: From 952f431ef1b4ac7526d779abd6b1db20bb468be9 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:10:31 -0700 Subject: [PATCH 02/21] Add examples section --- README.md | 2 +- examples/prensor_playground.ipynb | 1614 ------------ .../examples/prensor_playground/aqOX7nS.png | Bin 0 -> 62680 bytes g3doc/examples/prensor_playground.ipynb | 2207 +++++++++++++++++ g3doc/stylesheets/extra.css | 3 + mkdocs.yml | 2 + 6 files changed, 2213 insertions(+), 1615 deletions(-) delete mode 100644 examples/prensor_playground.ipynb create mode 100644 g3doc/assets/examples/prensor_playground/aqOX7nS.png create mode 100644 g3doc/examples/prensor_playground.ipynb diff --git a/README.md b/README.md index 9c23203..94d68d5 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ examples of struct2tensor in action and an introduction to the main concepts. You can [run the notebook in your browser](https://colab.research.google.com/github/google/struct2tensor/blob/master/examples/prensor_playground.ipynb) through Google's colab environment, or [download the -file](examples/prensor_playground.ipynb) to run it in your own Jupyter +file](g3doc/examples/prensor_playground.ipynb) to run it in your own Jupyter environment. diff --git a/examples/prensor_playground.ipynb b/examples/prensor_playground.ipynb deleted file mode 100644 index 1d382d3..0000000 --- a/examples/prensor_playground.ipynb +++ /dev/null @@ -1,1614 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "PHuwKiTF02Zq" - }, - "source": [ - "# Your structured data into Tensorflow.\n", - "\n", - "ML training often expects _flat_ data, like a line in a CSV.\n", - "[tf.Example](https://www.tensorflow.org/api_docs/python/tf/train/Example) was\n", - "designed to represent flat data. But the data you care about and want to predict\n", - "things about usually starts out _structured_.\n", - "\n", - "Over and over again you have to write transform code that turns your structured data into Tensors. This repetitive transform code must be rewritten over and over for all your ML pipelines both for training _and_ serving! And it lets bugs slip into your ML pipeline. \n", - "\n", - "`struct2tensor` lets you take advantage of structured data _within_ your ML pipelines. It is:\n", - "\n", - "* **for**: ML Engineers \n", - "* **who**: train models on data that starts out structured\n", - "* **it is**: a python library \n", - "* **that**: transforms your structured data into model-friendly (Sparse, Raggged, Dense, ...) tensors hermetically _within_ your model\n", - "* **unlike**: writing custom transforms over and over for training and serving.\n", - "\n", - "\n", - "---\n", - "\n", - "\n", - "![struct2tensor diagram showing the transform happens in the model](https://imgur.com/aqOX7nS.png)\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "7ozCmoF33ogj" - }, - "source": [ - "# Demo example\n", - "\n", - "Suppose we have this _structured_ data we want to train on. The source example data format is a [protobuff](https://developers.google.com/protocol-buffers). `struct2tensor` was built internally and works on protobuffers now. It can be extended to parquet, json, etc. in the future.\n", - "\n", - "```\n", - "# e.g. a web session\n", - "message Session{\n", - " message SessionInfo {\n", - " string session_feature = 1;\n", - " double session_duration_sec = 2;\n", - " }\n", - " SessionInfo session_info = 1;\n", - " message Event {\n", - " string query = 1;\n", - " message Action {\n", - " int number_of_views = 1;\n", - " }\n", - " repeated Action action = 2;\n", - " }\n", - " repeated Event event = 2;\n", - "}\n", - "```\n", - "\n", - "\n", - "In 3 steps we'll extract the fields we want with `struct2tensor`. We'll end up with batch-aligned `SparseTensors`:\n", - "\n", - "1. Tell our model what examples we care about, e.g. **`event`** (submessage `Session::Event`).\n", - "2. Pick the proto fields that we think are good features, say:\n", - " * `session_info.session_feature`\n", - " * `event.query`\n", - "3. Identify the label to predict, say **`event.action.number_of_views`** (the actual label could be sum(action.number_of_views for action in event))\n", - "\n", - "\n", - "Then we can build a struct2tensor query that:\n", - "* parses instances of this protocol buffer\n", - "* transforms the fields we care about\n", - "* creates the necessary `SparseTensor`s\n", - "\n", - "Don't worry about some of these terms yet. We'll show you an example. And then explain the terms later." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "w2RkJ6mN2Y6-" - }, - "source": [ - "## Install required packages (internal colab users: skip)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 461 - }, - "id": "q3REXR58msJe", - "outputId": "9b3a6130-83ce-46af-fe61-44a8cb95b4d8" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: struct2tensor in /usr/local/lib/python3.6/dist-packages (0.0.1.dev6)\n", - "Requirement already satisfied: tensorflow-metadata\u003e=0.13.0 in /usr/local/lib/python3.6/dist-packages (from struct2tensor) (0.15.1)\n", - "Requirement already satisfied: protobuf\u003e=3.8.0 in /usr/local/lib/python3.6/dist-packages (from struct2tensor) (3.10.0)\n", - "Requirement already satisfied: tensorflow==1.15.0 in /usr/local/lib/python3.6/dist-packages (from struct2tensor) (1.15.0)\n", - "Requirement already satisfied: googleapis-common-protos in /usr/local/lib/python3.6/dist-packages (from tensorflow-metadata\u003e=0.13.0-\u003estruct2tensor) (1.6.0)\n", - "Requirement already satisfied: six\u003e=1.9 in /usr/local/lib/python3.6/dist-packages (from protobuf\u003e=3.8.0-\u003estruct2tensor) (1.12.0)\n", - "Requirement already satisfied: setuptools in /usr/local/lib/python3.6/dist-packages (from protobuf\u003e=3.8.0-\u003estruct2tensor) (42.0.1)\n", - "Requirement already satisfied: numpy\u003c2.0,\u003e=1.16.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0-\u003estruct2tensor) (1.17.4)\n", - "Requirement already satisfied: opt-einsum\u003e=2.3.2 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0-\u003estruct2tensor) (3.1.0)\n", - "Requirement already satisfied: keras-preprocessing\u003e=1.0.5 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0-\u003estruct2tensor) (1.1.0)\n", - "Requirement already satisfied: termcolor\u003e=1.1.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0-\u003estruct2tensor) (1.1.0)\n", - "Requirement already satisfied: wheel\u003e=0.26 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0-\u003estruct2tensor) (0.33.6)\n", - "Requirement already satisfied: grpcio\u003e=1.8.6 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0-\u003estruct2tensor) (1.15.0)\n", - "Requirement already satisfied: tensorboard\u003c1.16.0,\u003e=1.15.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0-\u003estruct2tensor) (1.15.0)\n", - "Requirement already satisfied: keras-applications\u003e=1.0.8 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0-\u003estruct2tensor) (1.0.8)\n", - "Requirement already satisfied: tensorflow-estimator==1.15.1 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0-\u003estruct2tensor) (1.15.1)\n", - "Requirement already satisfied: google-pasta\u003e=0.1.6 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0-\u003estruct2tensor) (0.1.8)\n", - "Requirement already satisfied: gast==0.2.2 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0-\u003estruct2tensor) (0.2.2)\n", - "Requirement already satisfied: wrapt\u003e=1.11.1 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0-\u003estruct2tensor) (1.11.2)\n", - "Requirement already satisfied: absl-py\u003e=0.7.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0-\u003estruct2tensor) (0.8.1)\n", - "Requirement already satisfied: astor\u003e=0.6.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0-\u003estruct2tensor) (0.8.0)\n", - "Requirement already satisfied: werkzeug\u003e=0.11.15 in /usr/local/lib/python3.6/dist-packages (from tensorboard\u003c1.16.0,\u003e=1.15.0-\u003etensorflow==1.15.0-\u003estruct2tensor) (0.16.0)\n", - "Requirement already satisfied: markdown\u003e=2.6.8 in /usr/local/lib/python3.6/dist-packages (from tensorboard\u003c1.16.0,\u003e=1.15.0-\u003etensorflow==1.15.0-\u003estruct2tensor) (3.1.1)\n", - "Requirement already satisfied: h5py in /usr/local/lib/python3.6/dist-packages (from keras-applications\u003e=1.0.8-\u003etensorflow==1.15.0-\u003estruct2tensor) (2.8.0)\n", - "Requirement already satisfied: graphviz in /usr/local/lib/python3.6/dist-packages (0.10.1)\n" - ] - } - ], - "source": [ - "#@test {\"skip\": true} \n", - "# install struct2tensor\n", - "!pip install struct2tensor\n", - "# graphviz for pretty output\n", - "!pip install graphviz" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "dIxHSM3VQfUu" - }, - "source": [ - "## Some Pretty Printing and Imports\n", - "\n", - "(not the \"real\" work yet)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "cellView": "code", - "colab": { - "height": 34 - }, - "executionInfo": { - "elapsed": 437, - "status": "ok", - "timestamp": 1600375610067, - "user": { - "displayName": "", - "photoUrl": "", - "userId": "" - }, - "user_tz": 420 - }, - "id": "lc5KF8MILYrS", - "outputId": "5b8c3534-db19-4f98-cc12-f44716bc402a", - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "type-specific pretty printing ready to go\n" - ] - } - ], - "source": [ - "import base64\n", - "import numpy as np\n", - "import pprint\n", - "import os\n", - "import tensorflow\n", - "from graphviz import Source\n", - "\n", - "import tensorflow as tf\n", - "\n", - "from IPython.display import Image\n", - "from IPython.lib import pretty\n", - "\n", - "import struct2tensor as s2t\n", - "from struct2tensor.test import test_pb2\n", - "from google.protobuf import text_format\n", - "\n", - "\n", - "def _display(graph):\n", - " \"\"\"Renders a graphviz digraph.\"\"\"\n", - " s = Source(graph)\n", - " s.format='svg'\n", - " return s\n", - " \n", - "\n", - "def _create_query_from_text_sessions(text_sessions):\n", - " \"\"\"Creates a struct2tensor query from a list of pbtxt of struct2tensor.test.Session.\"\"\"\n", - " sessions = tf.constant([\n", - " text_format.Merge(\n", - " text_session, \n", - " test_pb2.Session()\n", - " ).SerializeToString()\n", - " for text_session in text_sessions\n", - " ])\n", - " return s2t.create_expression_from_proto(\n", - " sessions, test_pb2.Session.DESCRIPTOR)\n", - "\n", - "def _prensor_pretty_printer(prensor, p, cycle):\n", - " \"\"\"Pretty printing function for struct2tensor.prensor.Prensor\"\"\"\n", - " pretty.pprint(prensor.get_sparse_tensors())\n", - "\n", - "def _sp_pretty_printer(sp, p, cycle):\n", - " \"\"\"Pretty printing function for SparseTensor.\"\"\"\n", - "\n", - " del cycle\n", - " p.begin_group(4, \"SparseTensor(\")\n", - " p.text(\"values={}, \".format(sp.values.numpy().tolist()))\n", - " p.text(\"dense_shape={}, \".format(sp.dense_shape.numpy().tolist()))\n", - " p.break_()\n", - " p.text(\"indices={}\".format(sp.indices.numpy().tolist()))\n", - " p.end_group(4, \")\")\n", - "\n", - "\n", - "pretty.for_type(tf.SparseTensor, _sp_pretty_printer)\n", - "pretty.for_type(s2t.Prensor, _prensor_pretty_printer)\n", - "\n", - "_pretty_print = pretty.pprint\n", - "\n", - "print(\"type-specific pretty printing ready to go\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "sEsOWNuz7jC7" - }, - "source": [ - "## The real work:\n", - "\n", - "A function that parses our structured data (protobuffers) into tensors:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "height": 34 - }, - "executionInfo": { - "elapsed": 378, - "status": "ok", - "timestamp": 1600375613528, - "user": { - "displayName": "", - "photoUrl": "", - "userId": "" - }, - "user_tz": 420 - }, - "id": "ZC-oUzvBoPjA", - "outputId": "7a19c4e5-0cb1-479d-9245-e302a932448e" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Defined the workhorse func: (structured data at rest) -\u003e (tensors)\n" - ] - } - ], - "source": [ - "@tf.function(input_signature=[tf.TensorSpec(shape=(None), dtype=tf.string)], autograph=False)\n", - "def parse_session(serialized_sessions):\n", - " \"\"\"A TF function parsing a batch of serialized Session protos into tensors.\n", - "\n", - " It is a TF graph that takes one 1-D tensor as input, and outputs a\n", - " Dict[str, tf.SparseTensor]\n", - " \"\"\"\n", - " query = s2t.create_expression_from_proto(\n", - " serialized_sessions, test_pb2.Session.DESCRIPTOR)\n", - " # Move all the fields of our interest to under \"event\". \n", - " query = query.promote_and_broadcast({\n", - " \"session_feature\": \"session_info.session_feature\",\n", - " \"action_number_of_views\": \"event.action.number_of_views\" },\n", - " \"event\")\n", - " # Specify \"event\" to be examples.\n", - " query = query.reroot(\"event\")\n", - " # Extract all the fields of our interest.\n", - " projection = query.project([\"session_feature\", \"query\", \"action_number_of_views\"]) \n", - " prensors = s2t.calculate_prensors([projection])\n", - " \n", - " output_sparse_tensors = {}\n", - " for prensor in prensors:\n", - " path_to_tensor = prensor.get_sparse_tensors()\n", - " output_sparse_tensors.update({str(k): v for k, v in path_to_tensor.items()})\n", - " \n", - " return output_sparse_tensors\n", - "\n", - "print(\"Defined the workhorse func: (structured data at rest) -\u003e (tensors)\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "VKp4SxTgPzpe" - }, - "source": [ - "## Lets see it in action:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "height": 119 - }, - "executionInfo": { - "elapsed": 569, - "status": "ok", - "timestamp": 1600375616071, - "user": { - "displayName": "", - "photoUrl": "", - "userId": "" - }, - "user_tz": 420 - }, - "id": "-cIlFypdPeZX", - "outputId": "7840d783-249c-4be2-d675-10f8e817dded" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'action_number_of_views': SparseTensor(values=[1, 2, 3], dense_shape=[2, 2], \n", - " indices=[[0, 0], [1, 0], [1, 1]]),\n", - " 'query': SparseTensor(values=[b'Hello', b'world'], dense_shape=[2], \n", - " indices=[[0], [1]]),\n", - " 'session_feature': SparseTensor(values=[b'foo', b'foo'], dense_shape=[2, 1], \n", - " indices=[[0, 0], [1, 0]])}\n" - ] - } - ], - "source": [ - "serialized_sessions = tf.constant([\n", - " text_format.Merge(\n", - " \"\"\"\n", - " session_info {\n", - " session_duration_sec: 1.0\n", - " session_feature: \"foo\"\n", - " }\n", - " event {\n", - " query: \"Hello\"\n", - " action {\n", - " number_of_views: 1\n", - " }\n", - " action {\n", - " }\n", - " }\n", - " event {\n", - " query: \"world\"\n", - " action {\n", - " number_of_views: 2\n", - " }\n", - " action {\n", - " number_of_views: 3\n", - " }\n", - " }\n", - " \"\"\",\n", - " test_pb2.Session()\n", - " ).SerializeToString()\n", - "])\n", - "\n", - "_pretty_print(parse_session(serialized_sessions))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "pW9zBjNMWMIH" - }, - "source": [ - "See how we went from our pre-pipeline data (the Protobuffer) all the way to the structured data, packed into `SparseTensor`s?" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "QY7wy_6loF4v" - }, - "source": [ - "# Digging Far Deeper\n", - "Interested and want to learn more? Read on...\n", - "\n", - "Let's define several terms we mentioned before:\n", - "\n", - "### Prensor\n", - "\n", - "A Prensor (protobuffer + tensor) is a data structure storing the data we work on. We use protobuffers a lot at Google. `struct2tensor` can support other structured formats, too.\n", - "\n", - "For example, throughout this colab we will be using proto\n", - "[`struct2tensor.test.Session`](http://cs/symbol:struct2tensor.test.Session). A schematic visualization\n", - "of a selected part of the prensor from that proto looks like:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 543 - }, - "id": "ZTjNwx4bBXFk", - "outputId": "4927fdf8-0d2c-46e6-ee2f-a1b2bf4a298f" - }, - "outputs": [ - { - "data": { - "image/svg+xml": "\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n\u003c!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"\u003e\n\u003c!-- Generated by graphviz version 2.40.1 (20161225.0304)\n --\u003e\n\u003c!-- Title: %3 Pages: 1 --\u003e\n\u003csvg width=\"288pt\" height=\"392pt\"\n viewBox=\"0.00 0.00 288.04 392.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\u003e\n\u003cg id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 388)\"\u003e\n\u003ctitle\u003e%3\u003c/title\u003e\n\u003cpolygon fill=\"#ffffff\" stroke=\"transparent\" points=\"-4,4 -4,-388 284.0386,-388 284.0386,4 -4,4\"/\u003e\n\u003c!-- root --\u003e\n\u003cg id=\"node1\" class=\"node\"\u003e\n\u003ctitle\u003eroot\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"181.643\" cy=\"-366\" rx=\"27\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"181.643\" y=\"-362.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eroot\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session --\u003e\n\u003cg id=\"node2\" class=\"node\"\u003e\n\u003ctitle\u003esession\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"181.643\" cy=\"-279\" rx=\"37.0935\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"181.643\" y=\"-275.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003esession\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- root\u0026#45;\u0026gt;session --\u003e\n\u003cg id=\"edge1\" class=\"edge\"\u003e\n\u003ctitle\u003eroot\u0026#45;\u0026gt;session\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M181.643,-347.9735C181.643,-336.1918 181.643,-320.5607 181.643,-307.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"185.1431,-307.0033 181.643,-297.0034 178.1431,-307.0034 185.1431,-307.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"185.643\" y=\"-318.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event --\u003e\n\u003cg id=\"node3\" class=\"node\"\u003e\n\u003ctitle\u003eevent\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"132.643\" cy=\"-192\" rx=\"30.5947\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"132.643\" y=\"-188.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eevent\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session\u0026#45;\u0026gt;event --\u003e\n\u003cg id=\"edge2\" class=\"edge\"\u003e\n\u003ctitle\u003esession\u0026#45;\u0026gt;event\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M171.7271,-261.3943C164.7744,-249.0496 155.358,-232.3306 147.51,-218.3965\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"150.3339,-216.2782 142.3769,-209.2827 144.2348,-219.7133 150.3339,-216.2782\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"164.643\" y=\"-231.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session_id --\u003e\n\u003cg id=\"node4\" class=\"node\"\u003e\n\u003ctitle\u003esession_id\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"230.643\" cy=\"-192\" rx=\"49.2915\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"230.643\" y=\"-188.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003esession_id\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session\u0026#45;\u0026gt;session_id --\u003e\n\u003cg id=\"edge3\" class=\"edge\"\u003e\n\u003ctitle\u003esession\u0026#45;\u0026gt;session_id\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M191.5589,-261.3943C198.4208,-249.211 207.6823,-232.7669 215.4675,-218.9443\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"218.7159,-220.3089 220.5737,-209.8782 212.6167,-216.8737 218.7159,-220.3089\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"213.143\" y=\"-231.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e?\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- action --\u003e\n\u003cg id=\"node5\" class=\"node\"\u003e\n\u003ctitle\u003eaction\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"78.643\" cy=\"-105\" rx=\"33.2948\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"78.643\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eaction\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event\u0026#45;\u0026gt;action --\u003e\n\u003cg id=\"edge4\" class=\"edge\"\u003e\n\u003ctitle\u003eevent\u0026#45;\u0026gt;action\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M121.9744,-174.8116C114.2085,-162.3 103.5554,-145.1366 94.7556,-130.9592\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"97.5369,-128.8033 89.2895,-122.1527 91.5894,-132.4949 97.5369,-128.8033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"113.643\" y=\"-144.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- query_token --\u003e\n\u003cg id=\"node6\" class=\"node\"\u003e\n\u003ctitle\u003equery_token\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"187.643\" cy=\"-105\" rx=\"57.6901\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"187.643\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003equery_token\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event\u0026#45;\u0026gt;query_token --\u003e\n\u003cg id=\"edge5\" class=\"edge\"\u003e\n\u003ctitle\u003eevent\u0026#45;\u0026gt;query_token\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M143.5093,-174.8116C151.3162,-162.4624 161.988,-145.5816 170.8819,-131.513\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"174.0365,-133.0729 176.4218,-122.75 168.1197,-129.3323 174.0365,-133.0729\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"167.643\" y=\"-144.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- number_of_views --\u003e\n\u003cg id=\"node7\" class=\"node\"\u003e\n\u003ctitle\u003enumber_of_views\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"78.643\" cy=\"-18\" rx=\"78.7863\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"78.643\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003enumber_of_views\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- action\u0026#45;\u0026gt;number_of_views --\u003e\n\u003cg id=\"edge6\" class=\"edge\"\u003e\n\u003ctitle\u003eaction\u0026#45;\u0026gt;number_of_views\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M78.643,-86.9735C78.643,-75.1918 78.643,-59.5607 78.643,-46.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"82.1431,-46.0033 78.643,-36.0034 75.1431,-46.0034 82.1431,-46.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"82.143\" y=\"-57.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e?\u003c/text\u003e\n\u003c/g\u003e\n\u003c/g\u003e\n\u003c/svg\u003e\n", - "text/plain": [ - "\u003cgraphviz.files.Source at 0x7efc6d4c1160\u003e" - ] - }, - "execution_count": 5, - "metadata": { - "tags": [] - }, - "output_type": "execute_result" - } - ], - "source": [ - "#@title { display-mode: \"form\" }\n", - "#@test {\"skip\": true} \n", - "\n", - "_display(\"\"\"\n", - "digraph {\n", - " root -\u003e session [label=\"*\"];\n", - " session -\u003e event [label=\"*\"];\n", - " session -\u003e session_id [label=\"?\"];\n", - " event -\u003e action [label=\"*\"];\n", - " event -\u003e query_token [label=\"*\"]\n", - " action -\u003e number_of_views [label=\"?\"];\n", - "}\n", - "\"\"\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "P0aBK7NbO0wp" - }, - "source": [ - "We will be using visualizations like this to demostrate struct2tensor queries later.\n", - "\n", - "Note:\n", - "\n", - "* The \"*\" on the edge means the pointed node has repeated values; while the \"?\" means it has an optional value.\n", - "* There is always a \"root\" node whose only child is the root of the structure. Note that it's \"repeated\" because one struct2tensorTree can represent multiple instances of a structure.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "5stZYn1dBXdl" - }, - "source": [ - "\n", - "### struct2tensor Query\n", - "A struct2tensor query transforms a Prensor into another Prensor.\n", - "\n", - "For example, `broadcast` is a query that replicates a node as a child of one of its siblings.\n", - "\n", - "Applying\n", - "```\n", - "broadcast(\n", - " source_path=\"session.session_id\",\n", - " sibling=\"event\",\n", - " new_field_name=\"session_session_id\")\n", - "```\n", - "\n", - "on the previous tree gives:\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 543 - }, - "id": "zfhF-frzPxZm", - "outputId": "36bd1e53-befe-4bfa-e7da-737bd18a2611" - }, - "outputs": [ - { - "data": { - "image/svg+xml": "\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n\u003c!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"\u003e\n\u003c!-- Generated by graphviz version 2.40.1 (20161225.0304)\n --\u003e\n\u003c!-- Title: %3 Pages: 1 --\u003e\n\u003csvg width=\"387pt\" height=\"392pt\"\n viewBox=\"0.00 0.00 387.44 392.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\u003e\n\u003cg id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 388)\"\u003e\n\u003ctitle\u003e%3\u003c/title\u003e\n\u003cpolygon fill=\"#ffffff\" stroke=\"transparent\" points=\"-4,4 -4,-388 383.4377,-388 383.4377,4 -4,4\"/\u003e\n\u003c!-- session_session_id --\u003e\n\u003cg id=\"node1\" class=\"node\"\u003e\n\u003ctitle\u003esession_session_id\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#ff0000\" cx=\"80.5928\" cy=\"-105\" rx=\"80.6858\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"80.5928\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003esession_session_id\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- root --\u003e\n\u003cg id=\"node2\" class=\"node\"\u003e\n\u003ctitle\u003eroot\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"261.5928\" cy=\"-366\" rx=\"27\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"261.5928\" y=\"-362.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eroot\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session --\u003e\n\u003cg id=\"node3\" class=\"node\"\u003e\n\u003ctitle\u003esession\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"261.5928\" cy=\"-279\" rx=\"37.0935\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"261.5928\" y=\"-275.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003esession\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- root\u0026#45;\u0026gt;session --\u003e\n\u003cg id=\"edge1\" class=\"edge\"\u003e\n\u003ctitle\u003eroot\u0026#45;\u0026gt;session\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M261.5928,-347.9735C261.5928,-336.1918 261.5928,-320.5607 261.5928,-307.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"265.0929,-307.0033 261.5928,-297.0034 258.0929,-307.0034 265.0929,-307.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"265.5928\" y=\"-318.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event --\u003e\n\u003cg id=\"node4\" class=\"node\"\u003e\n\u003ctitle\u003eevent\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"212.5928\" cy=\"-192\" rx=\"30.5947\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"212.5928\" y=\"-188.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eevent\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session\u0026#45;\u0026gt;event --\u003e\n\u003cg id=\"edge2\" class=\"edge\"\u003e\n\u003ctitle\u003esession\u0026#45;\u0026gt;event\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M251.677,-261.3943C244.7242,-249.0496 235.3078,-232.3306 227.4598,-218.3965\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"230.2838,-216.2782 222.3268,-209.2827 224.1846,-219.7133 230.2838,-216.2782\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"244.5928\" y=\"-231.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session_id --\u003e\n\u003cg id=\"node5\" class=\"node\"\u003e\n\u003ctitle\u003esession_id\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"310.5928\" cy=\"-192\" rx=\"49.2915\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"310.5928\" y=\"-188.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003esession_id\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session\u0026#45;\u0026gt;session_id --\u003e\n\u003cg id=\"edge3\" class=\"edge\"\u003e\n\u003ctitle\u003esession\u0026#45;\u0026gt;session_id\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M271.5087,-261.3943C278.3706,-249.211 287.6322,-232.7669 295.4173,-218.9443\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"298.6657,-220.3089 300.5235,-209.8782 292.5665,-216.8737 298.6657,-220.3089\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"293.0928\" y=\"-231.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e?\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event\u0026#45;\u0026gt;session_session_id --\u003e\n\u003cg id=\"edge5\" class=\"edge\"\u003e\n\u003ctitle\u003eevent\u0026#45;\u0026gt;session_session_id\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M191.9678,-178.4063C171.4317,-164.8711 139.5358,-143.8488 115.1315,-127.7641\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"116.79,-124.6654 106.5143,-122.0846 112.9378,-130.5101 116.79,-124.6654\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"158.0928\" y=\"-144.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e?\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- action --\u003e\n\u003cg id=\"node6\" class=\"node\"\u003e\n\u003ctitle\u003eaction\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"212.5928\" cy=\"-105\" rx=\"33.2948\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"212.5928\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eaction\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event\u0026#45;\u0026gt;action --\u003e\n\u003cg id=\"edge4\" class=\"edge\"\u003e\n\u003ctitle\u003eevent\u0026#45;\u0026gt;action\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M212.5928,-173.9735C212.5928,-162.1918 212.5928,-146.5607 212.5928,-133.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"216.0929,-133.0033 212.5928,-123.0034 209.0929,-133.0034 216.0929,-133.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"216.5928\" y=\"-144.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- query_token --\u003e\n\u003cg id=\"node7\" class=\"node\"\u003e\n\u003ctitle\u003equery_token\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"321.5928\" cy=\"-105\" rx=\"57.6901\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"321.5928\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003equery_token\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event\u0026#45;\u0026gt;query_token --\u003e\n\u003cg id=\"edge6\" class=\"edge\"\u003e\n\u003ctitle\u003eevent\u0026#45;\u0026gt;query_token\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M231.0832,-177.2416C247.8774,-163.8371 272.929,-143.8418 292.4215,-128.2836\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"294.7624,-130.8933 300.3947,-121.9196 290.3956,-125.4223 294.7624,-130.8933\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"278.5928\" y=\"-144.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- number_of_views --\u003e\n\u003cg id=\"node8\" class=\"node\"\u003e\n\u003ctitle\u003enumber_of_views\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"212.5928\" cy=\"-18\" rx=\"78.7863\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"212.5928\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003enumber_of_views\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- action\u0026#45;\u0026gt;number_of_views --\u003e\n\u003cg id=\"edge7\" class=\"edge\"\u003e\n\u003ctitle\u003eaction\u0026#45;\u0026gt;number_of_views\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M212.5928,-86.9735C212.5928,-75.1918 212.5928,-59.5607 212.5928,-46.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"216.0929,-46.0033 212.5928,-36.0034 209.0929,-46.0034 216.0929,-46.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"216.0928\" y=\"-57.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e?\u003c/text\u003e\n\u003c/g\u003e\n\u003c/g\u003e\n\u003c/svg\u003e\n", - "text/plain": [ - "\u003cgraphviz.files.Source at 0x7efc6d4c1320\u003e" - ] - }, - "execution_count": 6, - "metadata": { - "tags": [] - }, - "output_type": "execute_result" - } - ], - "source": [ - "#@title { display-mode: \"form\" }\n", - "#@test {\"skip\": true} \n", - "\n", - "_display(\"\"\"\n", - "digraph {\n", - " session_session_id [color=\"red\"];\n", - " root -\u003e session [label=\"*\"];\n", - " session -\u003e event [label=\"*\"];\n", - " session -\u003e session_id [label=\"?\"];\n", - " event -\u003e action [label=\"*\"];\n", - " event -\u003e session_session_id [label=\"?\"];\n", - " event -\u003e query_token [label=\"*\"];\n", - " action -\u003e number_of_views [label=\"?\"];\n", - "}\n", - "\"\"\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "eOHokTjBPyHW" - }, - "source": [ - "We will talk about common struct2tensor queries in later sections.\n", - "\n", - "### Projection\n", - "A projection of paths in a Prensor produces another Prensor with just the selected paths.\n", - "\n", - "#### Logical representation of a projection\n", - "The structure of the projected path can be represented losslessly as nested lists. For example, the projection of `event.action.number_of_views` from the struct2tensorTree formed by the following two instances of `struct2tensor.test.Session`:\n", - "```\n", - "{\n", - " event { action { number_of_views: 1} action { number_of_views: 2} action {} }\n", - " event {}\n", - "}, {\n", - " event { action { number_of_views: 3} }\n", - "}\n", - "```\n", - "\n", - "is:\n", - "\n", - "```\n", - "[ # the outer list has two elements b/c there are two Session protos.\n", - " [ # the first proto has two events\n", - " [[1],[2],[]], # 3 actions, the last one does not have a number_of_views.\n", - " [], # the second event does not have action\n", - " ],\n", - " [ # the second proto has one event\n", - " [[3]],\n", - " ],\n", - "]\n", - "```\n", - "\n", - "#### Representing nested lists with `tf.SparseTensor`\n", - "\n", - "struct2tensor uses `tf.SparseTensor` to represent the above nested list in the projection results. Note that `tf.SparseTensor` essentially enforces that the lists nested at the same level to have the same length (because the there is a certain size for each dimension), therefore this representation is lossy. The above nested lists, when written as a SparseTensor will look like:\n", - "```\n", - "tf.SparseTensor(\n", - " dense_shape=[2, 2, 3, 1], # each is the maximum length of lists at the same nesting level.\n", - " values = [1, 2, 3],\n", - " indices = [[0, 0, 0, 0], [0, 0, 1, 0], [1, 0, 0, 0]]\n", - ")\n", - "```\n", - "\n", - "Note that the last dimension is useless: the index of that dimension will always be 0 for any present value because number_of_views is an optional field. So struct2tensors library will actually \"squeeze\" all the optional dimensions.\n", - "\n", - "The actual result would be:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "height": 68 - }, - "executionInfo": { - "elapsed": 352, - "status": "ok", - "timestamp": 1600368919353, - "user": { - "displayName": "", - "photoUrl": "", - "userId": "" - }, - "user_tz": 420 - }, - "id": "XvuEY3D3WIP7", - "outputId": "ee33d486-90e7-4328-accc-11f13f82a9db" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{event.action.number_of_views: SparseTensor(values=[1, 2, 3], dense_shape=[2, 2, 3], \n", - " indices=[[0, 0, 0], [0, 0, 1], [1, 0, 0]])}\n", - "]\n" - ] - } - ], - "source": [ - "query = _create_query_from_text_sessions(['''\n", - " event { action { number_of_views: 1} action { number_of_views: 2} action {} }\n", - " event {}\n", - " ''', '''\n", - " event { action { number_of_views: 3} }\n", - " ''']\n", - " ).project([\"event.action.number_of_views\"])\n", - "\n", - "prensor = s2t.calculate_prensors([query])\n", - "pretty.pprint(prensor)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "iaailOJhWLLa" - }, - "source": [ - "struct2tensor's internal data model is closer to the above \"nested lists\" abstraction and sometimes it's easier to reason with \"nested lists\" than with `SparseTensor`s.\n", - "\n", - "Recently, [`tf.RaggedTensor`](https://www.tensorflow.org/guide/ragged_tensors) was introduced to represent nested lists exactly. We are working on adding support for projecting into ragged tensors." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "UD48ikK4Eop-" - }, - "source": [ - "## Common struct2tensor Queries\n", - "\n", - "### `promote`\n", - "\n", - "Promotes a node to become a sibling of its parent. If the node is repeated, then all its values are concatenated (the order is preserved)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 426 - }, - "id": "tnbD2lKoDbsk", - "outputId": "244aeef6-48b7-4ae5-f01c-2429bb65a7ea" - }, - "outputs": [ - { - "data": { - "image/svg+xml": "\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n\u003c!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"\u003e\n\u003c!-- Generated by graphviz version 2.40.1 (20161225.0304)\n --\u003e\n\u003c!-- Title: %3 Pages: 1 --\u003e\n\u003csvg width=\"124pt\" height=\"305pt\"\n viewBox=\"0.00 0.00 123.69 305.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\u003e\n\u003cg id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 301)\"\u003e\n\u003ctitle\u003e%3\u003c/title\u003e\n\u003cpolygon fill=\"#ffffff\" stroke=\"transparent\" points=\"-4,4 -4,-301 119.6897,-301 119.6897,4 -4,4\"/\u003e\n\u003c!-- root --\u003e\n\u003cg id=\"node1\" class=\"node\"\u003e\n\u003ctitle\u003eroot\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"57.8449\" cy=\"-279\" rx=\"27\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"57.8449\" y=\"-275.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eroot\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session --\u003e\n\u003cg id=\"node2\" class=\"node\"\u003e\n\u003ctitle\u003esession\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"57.8449\" cy=\"-192\" rx=\"37.0935\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"57.8449\" y=\"-188.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003esession\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- root\u0026#45;\u0026gt;session --\u003e\n\u003cg id=\"edge1\" class=\"edge\"\u003e\n\u003ctitle\u003eroot\u0026#45;\u0026gt;session\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M57.8449,-260.9735C57.8449,-249.1918 57.8449,-233.5607 57.8449,-220.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"61.345,-220.0033 57.8449,-210.0034 54.345,-220.0034 61.345,-220.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"61.8449\" y=\"-231.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event --\u003e\n\u003cg id=\"node3\" class=\"node\"\u003e\n\u003ctitle\u003eevent\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"57.8449\" cy=\"-105\" rx=\"30.5947\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"57.8449\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eevent\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session\u0026#45;\u0026gt;event --\u003e\n\u003cg id=\"edge2\" class=\"edge\"\u003e\n\u003ctitle\u003esession\u0026#45;\u0026gt;event\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M57.8449,-173.9735C57.8449,-162.1918 57.8449,-146.5607 57.8449,-133.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"61.345,-133.0033 57.8449,-123.0034 54.345,-133.0034 61.345,-133.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"61.8449\" y=\"-144.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- query_token --\u003e\n\u003cg id=\"node4\" class=\"node\"\u003e\n\u003ctitle\u003equery_token\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"57.8449\" cy=\"-18\" rx=\"57.6901\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"57.8449\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003equery_token\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event\u0026#45;\u0026gt;query_token --\u003e\n\u003cg id=\"edge3\" class=\"edge\"\u003e\n\u003ctitle\u003eevent\u0026#45;\u0026gt;query_token\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M57.8449,-86.9735C57.8449,-75.1918 57.8449,-59.5607 57.8449,-46.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"61.345,-46.0033 57.8449,-36.0034 54.345,-46.0034 61.345,-46.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"61.8449\" y=\"-57.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c/g\u003e\n\u003c/svg\u003e\n", - "text/plain": [ - "\u003cgraphviz.files.Source at 0x7efc6d4c1438\u003e" - ] - }, - "execution_count": 8, - "metadata": { - "tags": [] - }, - "output_type": "execute_result" - } - ], - "source": [ - "#@title { display-mode: \"form\" }\n", - "#@test {\"skip\": true} \n", - "\n", - "_display('''\n", - "digraph {\n", - " root -\u003e session [label=\"*\"];\n", - " session -\u003e event [label=\"*\"];\n", - " event -\u003e query_token [label=\"*\"];\n", - "}\n", - "''')" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "OOgJlcHPDyIk" - }, - "source": [ - "`promote(source_path=\"event.query_token\", new_field_name=\"event_query_token\")`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 426 - }, - "id": "xKJ_EraVHyUA", - "outputId": "64c3720d-3672-4625-d8c1-1a4b301d6a2d" - }, - "outputs": [ - { - "data": { - "image/svg+xml": "\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n\u003c!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"\u003e\n\u003c!-- Generated by graphviz version 2.40.1 (20161225.0304)\n --\u003e\n\u003c!-- Title: %3 Pages: 1 --\u003e\n\u003csvg width=\"281pt\" height=\"305pt\"\n viewBox=\"0.00 0.00 281.04 305.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\u003e\n\u003cg id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 301)\"\u003e\n\u003ctitle\u003e%3\u003c/title\u003e\n\u003cpolygon fill=\"#ffffff\" stroke=\"transparent\" points=\"-4,4 -4,-301 277.0375,-301 277.0375,4 -4,4\"/\u003e\n\u003c!-- event_query_token --\u003e\n\u003cg id=\"node1\" class=\"node\"\u003e\n\u003ctitle\u003eevent_query_token\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#ff0000\" cx=\"83.1926\" cy=\"-105\" rx=\"83.3857\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"83.1926\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eevent_query_token\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- root --\u003e\n\u003cg id=\"node2\" class=\"node\"\u003e\n\u003ctitle\u003eroot\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"98.1926\" cy=\"-279\" rx=\"27\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"98.1926\" y=\"-275.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eroot\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session --\u003e\n\u003cg id=\"node3\" class=\"node\"\u003e\n\u003ctitle\u003esession\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"98.1926\" cy=\"-192\" rx=\"37.0935\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"98.1926\" y=\"-188.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003esession\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- root\u0026#45;\u0026gt;session --\u003e\n\u003cg id=\"edge1\" class=\"edge\"\u003e\n\u003ctitle\u003eroot\u0026#45;\u0026gt;session\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M98.1926,-260.9735C98.1926,-249.1918 98.1926,-233.5607 98.1926,-220.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"101.6927,-220.0033 98.1926,-210.0034 94.6927,-220.0034 101.6927,-220.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"102.1926\" y=\"-231.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session\u0026#45;\u0026gt;event_query_token --\u003e\n\u003cg id=\"edge3\" class=\"edge\"\u003e\n\u003ctitle\u003esession\u0026#45;\u0026gt;event_query_token\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M95.0846,-173.9735C93.0533,-162.1918 90.3582,-146.5607 88.0475,-133.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"91.4449,-132.2633 86.2966,-123.0034 84.5467,-133.4527 91.4449,-132.2633\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"96.1926\" y=\"-144.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event --\u003e\n\u003cg id=\"node4\" class=\"node\"\u003e\n\u003ctitle\u003eevent\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"215.1926\" cy=\"-105\" rx=\"30.5947\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"215.1926\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eevent\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session\u0026#45;\u0026gt;event --\u003e\n\u003cg id=\"edge2\" class=\"edge\"\u003e\n\u003ctitle\u003esession\u0026#45;\u0026gt;event\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M118.5725,-176.8458C137.7919,-162.5544 166.7343,-141.0331 187.9324,-125.2705\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"190.2713,-127.8928 196.2075,-119.1171 186.0944,-122.2756 190.2713,-127.8928\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"168.1926\" y=\"-144.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- query_token --\u003e\n\u003cg id=\"node5\" class=\"node\"\u003e\n\u003ctitle\u003equery_token\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"215.1926\" cy=\"-18\" rx=\"57.6901\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"215.1926\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003equery_token\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event\u0026#45;\u0026gt;query_token --\u003e\n\u003cg id=\"edge4\" class=\"edge\"\u003e\n\u003ctitle\u003eevent\u0026#45;\u0026gt;query_token\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M215.1926,-86.9735C215.1926,-75.1918 215.1926,-59.5607 215.1926,-46.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"218.6927,-46.0033 215.1926,-36.0034 211.6927,-46.0034 218.6927,-46.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"219.1926\" y=\"-57.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c/g\u003e\n\u003c/svg\u003e\n", - "text/plain": [ - "\u003cgraphviz.files.Source at 0x7efc58c02358\u003e" - ] - }, - "execution_count": 9, - "metadata": { - "tags": [] - }, - "output_type": "execute_result" - } - ], - "source": [ - "#@title { display-mode: \"form\" }\n", - "#@test {\"skip\": true} \n", - "\n", - "_display('''\n", - "digraph {\n", - " event_query_token [color=\"red\"];\n", - " root -\u003e session [label=\"*\"];\n", - " session -\u003e event [label=\"*\"];\n", - " session -\u003e event_query_token [label=\"*\"];\n", - " event -\u003e query_token [label=\"*\"];\n", - "}\n", - "''')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "height": 68 - }, - "executionInfo": { - "elapsed": 344, - "status": "ok", - "timestamp": 1600296594869, - "user": { - "displayName": "", - "photoUrl": "", - "userId": "" - }, - "user_tz": 420 - }, - "id": "oQCcVWd-JDT9", - "outputId": "acad1b9a-0985-46bb-b895-7d085814d20e" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{event_query_token: SparseTensor(values=[b'abc', b'def', b'ghi'], dense_shape=[1, 3], \n", - " indices=[[0, 0], [0, 1], [0, 2]])}\n", - "]\n" - ] - } - ], - "source": [ - "query = (_create_query_from_text_sessions([\n", - "\"\"\"\n", - "event {\n", - " query_token: \"abc\"\n", - " query_token: \"def\"\n", - "}\n", - "event {\n", - " query_token: \"ghi\"\n", - "}\n", - "\"\"\"])\n", - " .promote(source_path=\"event.query_token\", new_field_name=\"event_query_token\")\n", - " .project([\"event_query_token\"]))\n", - "\n", - "prensor = s2t.calculate_prensors([query])\n", - "\n", - "_pretty_print(prensor)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "av9km5d_KM8o" - }, - "source": [ - "The projected structure is like:\n", - "```\n", - "{\n", - " # this is under Session.\n", - " event_query_token: \"abc\"\n", - " event_query_token: \"def\"\n", - " event_query_token: \"ghi\"\n", - "}\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "t2BU8warKqFm" - }, - "source": [ - "### `broadcast`\n", - "\n", - "Broadcasts the value of a node to one of its sibling. The value will be replicated if the sibling is repeated. This is similar to TensorFlow and Numpy's [broadcasting semantics](https://docs.scipy.org/doc/numpy-1.13.0/user/basics.broadcasting.html).\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 311 - }, - "id": "1hFGEV0DMmOo", - "outputId": "ff23c5ce-17c9-4604-f309-c9b384b6d8ca" - }, - "outputs": [ - { - "data": { - "image/svg+xml": "\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n\u003c!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"\u003e\n\u003c!-- Generated by graphviz version 2.40.1 (20161225.0304)\n --\u003e\n\u003c!-- Title: %3 Pages: 1 --\u003e\n\u003csvg width=\"186pt\" height=\"218pt\"\n viewBox=\"0.00 0.00 185.94 218.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\u003e\n\u003cg id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 214)\"\u003e\n\u003ctitle\u003e%3\u003c/title\u003e\n\u003cpolygon fill=\"#ffffff\" stroke=\"transparent\" points=\"-4,4 -4,-214 181.9429,-214 181.9429,4 -4,4\"/\u003e\n\u003c!-- root --\u003e\n\u003cg id=\"node1\" class=\"node\"\u003e\n\u003ctitle\u003eroot\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"98.3956\" cy=\"-192\" rx=\"27\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"98.3956\" y=\"-188.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eroot\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session --\u003e\n\u003cg id=\"node2\" class=\"node\"\u003e\n\u003ctitle\u003esession\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"98.3956\" cy=\"-105\" rx=\"37.0935\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"98.3956\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003esession\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- root\u0026#45;\u0026gt;session --\u003e\n\u003cg id=\"edge1\" class=\"edge\"\u003e\n\u003ctitle\u003eroot\u0026#45;\u0026gt;session\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M98.3956,-173.9735C98.3956,-162.1918 98.3956,-146.5607 98.3956,-133.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"101.8957,-133.0033 98.3956,-123.0034 94.8957,-133.0034 101.8957,-133.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"102.3956\" y=\"-144.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session_id --\u003e\n\u003cg id=\"node3\" class=\"node\"\u003e\n\u003ctitle\u003esession_id\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"49.3956\" cy=\"-18\" rx=\"49.2915\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"49.3956\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003esession_id\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session\u0026#45;\u0026gt;session_id --\u003e\n\u003cg id=\"edge2\" class=\"edge\"\u003e\n\u003ctitle\u003esession\u0026#45;\u0026gt;session_id\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M88.4797,-87.3943C81.6179,-75.211 72.3563,-58.7669 64.5711,-44.9443\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"67.4219,-42.8737 59.4649,-35.8782 61.3228,-46.3089 67.4219,-42.8737\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"80.8956\" y=\"-57.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e?\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event --\u003e\n\u003cg id=\"node4\" class=\"node\"\u003e\n\u003ctitle\u003eevent\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"147.3956\" cy=\"-18\" rx=\"30.5947\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"147.3956\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eevent\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session\u0026#45;\u0026gt;event --\u003e\n\u003cg id=\"edge3\" class=\"edge\"\u003e\n\u003ctitle\u003esession\u0026#45;\u0026gt;event\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M108.3115,-87.3943C115.2642,-75.0496 124.6807,-58.3306 132.5286,-44.3965\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"135.8039,-45.7133 137.6617,-35.2827 129.7047,-42.2782 135.8039,-45.7133\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"129.3956\" y=\"-57.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c/g\u003e\n\u003c/svg\u003e\n", - "text/plain": [ - "\u003cgraphviz.files.Source at 0x7efc58d8cc50\u003e" - ] - }, - "execution_count": 11, - "metadata": { - "tags": [] - }, - "output_type": "execute_result" - } - ], - "source": [ - "#@title { display-mode: \"form\" }\n", - "#@test {\"skip\": true} \n", - "\n", - "_display('''\n", - "digraph {\n", - " root -\u003e session [label=\"*\"];\n", - " session -\u003e session_id [label=\"?\"];\n", - " session -\u003e event [label=\"*\"];\n", - "}\n", - "''')" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DvBCsWBuNDKV" - }, - "source": [ - "`broadcast(source_path=\"session_id\", sibling_field=\"event\", new_field_name=\"session_session_id\")`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 426 - }, - "id": "wTUHyGvSNMGp", - "outputId": "d1ef4902-72eb-4bd2-e0bb-3008a9de17e7" - }, - "outputs": [ - { - "data": { - "image/svg+xml": "\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n\u003c!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"\u003e\n\u003c!-- Generated by graphviz version 2.40.1 (20161225.0304)\n --\u003e\n\u003c!-- Title: %3 Pages: 1 --\u003e\n\u003csvg width=\"236pt\" height=\"305pt\"\n viewBox=\"0.00 0.00 235.99 305.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\u003e\n\u003cg id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 301)\"\u003e\n\u003ctitle\u003e%3\u003c/title\u003e\n\u003cpolygon fill=\"#ffffff\" stroke=\"transparent\" points=\"-4,4 -4,-301 231.9885,-301 231.9885,4 -4,4\"/\u003e\n\u003c!-- session_session_id --\u003e\n\u003cg id=\"node1\" class=\"node\"\u003e\n\u003ctitle\u003esession_session_id\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#ff0000\" cx=\"147.3956\" cy=\"-18\" rx=\"80.6858\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"147.3956\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003esession_session_id\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- root --\u003e\n\u003cg id=\"node2\" class=\"node\"\u003e\n\u003ctitle\u003eroot\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"64.3956\" cy=\"-279\" rx=\"27\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"64.3956\" y=\"-275.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eroot\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session --\u003e\n\u003cg id=\"node3\" class=\"node\"\u003e\n\u003ctitle\u003esession\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"64.3956\" cy=\"-192\" rx=\"37.0935\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"64.3956\" y=\"-188.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003esession\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- root\u0026#45;\u0026gt;session --\u003e\n\u003cg id=\"edge1\" class=\"edge\"\u003e\n\u003ctitle\u003eroot\u0026#45;\u0026gt;session\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M64.3956,-260.9735C64.3956,-249.1918 64.3956,-233.5607 64.3956,-220.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"67.8957,-220.0033 64.3956,-210.0034 60.8957,-220.0034 67.8957,-220.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"68.3956\" y=\"-231.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session_id --\u003e\n\u003cg id=\"node4\" class=\"node\"\u003e\n\u003ctitle\u003esession_id\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"49.3956\" cy=\"-105\" rx=\"49.2915\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"49.3956\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003esession_id\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session\u0026#45;\u0026gt;session_id --\u003e\n\u003cg id=\"edge2\" class=\"edge\"\u003e\n\u003ctitle\u003esession\u0026#45;\u0026gt;session_id\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M61.2876,-173.9735C59.2563,-162.1918 56.5612,-146.5607 54.2505,-133.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"57.6479,-132.2633 52.4996,-123.0034 50.7497,-133.4527 57.6479,-132.2633\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"61.8956\" y=\"-144.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e?\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event --\u003e\n\u003cg id=\"node5\" class=\"node\"\u003e\n\u003ctitle\u003eevent\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"147.3956\" cy=\"-105\" rx=\"30.5947\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"147.3956\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eevent\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session\u0026#45;\u0026gt;event --\u003e\n\u003cg id=\"edge3\" class=\"edge\"\u003e\n\u003ctitle\u003esession\u0026#45;\u0026gt;event\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M80.0073,-175.636C92.8036,-162.2229 111.0676,-143.0787 125.3545,-128.1034\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"128.0426,-130.3561 132.413,-120.7047 122.9778,-125.5241 128.0426,-130.3561\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"114.3956\" y=\"-144.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event\u0026#45;\u0026gt;session_session_id --\u003e\n\u003cg id=\"edge4\" class=\"edge\"\u003e\n\u003ctitle\u003eevent\u0026#45;\u0026gt;session_session_id\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M147.3956,-86.9735C147.3956,-75.1918 147.3956,-59.5607 147.3956,-46.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"150.8957,-46.0033 147.3956,-36.0034 143.8957,-46.0034 150.8957,-46.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"150.8956\" y=\"-57.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e?\u003c/text\u003e\n\u003c/g\u003e\n\u003c/g\u003e\n\u003c/svg\u003e\n", - "text/plain": [ - "\u003cgraphviz.files.Source at 0x7efc58c25550\u003e" - ] - }, - "execution_count": 12, - "metadata": { - "tags": [] - }, - "output_type": "execute_result" - } - ], - "source": [ - "#@title { display-mode: \"form\" }\n", - "#@test {\"skip\": true} \n", - "\n", - "_display('''\n", - "digraph {\n", - " session_session_id [color=\"red\"];\n", - " root -\u003e session [label=\"*\"];\n", - " session -\u003e session_id [label=\"?\"];\n", - " session -\u003e event [label=\"*\"];\n", - " event -\u003e session_session_id [label=\"?\"];\n", - "}\n", - "''')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "height": 68 - }, - "executionInfo": { - "elapsed": 341, - "status": "ok", - "timestamp": 1600296607633, - "user": { - "displayName": "", - "photoUrl": "", - "userId": "" - }, - "user_tz": 420 - }, - "id": "wQE_ceMzNjzv", - "outputId": "d92d9977-1e4c-4138-e89c-94e9c0f4bf5e" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{event.session_session_id: SparseTensor(values=[8, 8], dense_shape=[1, 2], \n", - " indices=[[0, 0], [0, 1]])}\n", - "]\n" - ] - } - ], - "source": [ - "query = (_create_query_from_text_sessions([\n", - "\"\"\"\n", - "session_id: 8\n", - "event { }\n", - "event { }\n", - "\"\"\"])\n", - " .broadcast(source_path=\"session_id\",\n", - " sibling_field=\"event\",\n", - " new_field_name=\"session_session_id\")\n", - " .project([\"event.session_session_id\"]))\n", - "\n", - "prensor = s2t.calculate_prensors([query])\n", - "_pretty_print(prensor)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "7fL8pmsQObUT" - }, - "source": [ - "The projected structure is like:\n", - "```\n", - "{\n", - " event {\n", - " session_session_id: 8\n", - " }\n", - " event {\n", - " session_session_id: 8\n", - " }\n", - "}\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Ch2WgR9AP23Q" - }, - "source": [ - "### `promote_and_broadcast`\n", - "The query accepts multiple source fields and a destination field. For each source field, it first promotes it to the least common ancestor with the destination field (if necessary), then broadcasts it to the destination field (if necessary).\n", - "\n", - "Usually for the purpose of machine learning, this gives a reasonable flattened representation of nested structures." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "-uNfasxgRsvO" - }, - "source": [ - "```\n", - "promote_and_broadcast(\n", - " path_dictionary={\n", - " 'session_info_duration_sec': 'session_info.session_duration_sec'},\n", - " dest_path_parent='event.action')\n", - "```\n", - "is equivalent to:\n", - "```\n", - "promote(source_path='session_info.session_duration_sec',\n", - " new_field_name='anonymous_field1')\n", - "\n", - "broadcast(source_path='anonymous_field1',\n", - " sibling_field='event.action',\n", - " new_field_name='session_info_duration_sec')\n", - "```\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "rYkWc_u8SR_k" - }, - "source": [ - "### `map_field_values`\n", - "\n", - "Creates a new node that is a sibling of a leaf node. The values of the new node are results of applying the given function to the values of the source node.\n", - "\n", - "Note that the function provided takes 1-D tensor that contains all the values of the source node as input and should also output a 1-D tensor of the same size, and it should build TF ops.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "height": 68 - }, - "executionInfo": { - "elapsed": 333, - "status": "ok", - "timestamp": 1600296617311, - "user": { - "displayName": "", - "photoUrl": "", - "userId": "" - }, - "user_tz": 420 - }, - "id": "DWjA2OFcS4k1", - "outputId": "7a2d8ef5-7111-4b45-81dd-65d8222badae" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{session_id_plus_one: SparseTensor(values=[9, 10], dense_shape=[2], \n", - " indices=[[0], [1]])}\n", - "]\n" - ] - } - ], - "source": [ - "query = (_create_query_from_text_sessions([\n", - "\"\"\"\n", - "session_id: 8\n", - "\"\"\",\n", - "\"\"\"\n", - "session_id: 9\n", - "\"\"\"])\n", - " .map_field_values(\"session_id\", lambda x: tf.add(x, 1), dtype=tf.int64,\n", - " new_field_name=\"session_id_plus_one\")\n", - " .project([\"session_id_plus_one\"]))\n", - " \n", - "prensor = s2t.calculate_prensors([query])\n", - "\n", - "_pretty_print(prensor)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_NusnDa1T7s5" - }, - "source": [ - "### `reroot`\n", - "\n", - "Makes the given node the new root of the struct2tensorTree. This has two effects:\n", - "\n", - "* restricts the scope of the struct2tensorTree\n", - " + The field paths in all the following queries are relative to the new root\n", - " + There's no way to refer to nodes that are outside the subtree rooted at the new root.\n", - "* changes the batch dimension.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 426 - }, - "id": "OBNPdOlQU6qS", - "outputId": "df56d6d2-72c5-4c90-e2b9-b3fe2bffde00" - }, - "outputs": [ - { - "data": { - "image/svg+xml": "\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n\u003c!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"\u003e\n\u003c!-- Generated by graphviz version 2.40.1 (20161225.0304)\n --\u003e\n\u003c!-- Title: %3 Pages: 1 --\u003e\n\u003csvg width=\"198pt\" height=\"305pt\"\n viewBox=\"0.00 0.00 198.29 305.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\u003e\n\u003cg id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 301)\"\u003e\n\u003ctitle\u003e%3\u003c/title\u003e\n\u003cpolygon fill=\"#ffffff\" stroke=\"transparent\" points=\"-4,4 -4,-301 194.2918,-301 194.2918,4 -4,4\"/\u003e\n\u003c!-- root --\u003e\n\u003cg id=\"node1\" class=\"node\"\u003e\n\u003ctitle\u003eroot\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"64.3956\" cy=\"-279\" rx=\"27\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"64.3956\" y=\"-275.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eroot\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session --\u003e\n\u003cg id=\"node2\" class=\"node\"\u003e\n\u003ctitle\u003esession\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"64.3956\" cy=\"-192\" rx=\"37.0935\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"64.3956\" y=\"-188.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003esession\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- root\u0026#45;\u0026gt;session --\u003e\n\u003cg id=\"edge1\" class=\"edge\"\u003e\n\u003ctitle\u003eroot\u0026#45;\u0026gt;session\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M64.3956,-260.9735C64.3956,-249.1918 64.3956,-233.5607 64.3956,-220.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"67.8957,-220.0033 64.3956,-210.0034 60.8957,-220.0034 67.8957,-220.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"68.3956\" y=\"-231.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session_id --\u003e\n\u003cg id=\"node3\" class=\"node\"\u003e\n\u003ctitle\u003esession_id\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"49.3956\" cy=\"-105\" rx=\"49.2915\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"49.3956\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003esession_id\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session\u0026#45;\u0026gt;session_id --\u003e\n\u003cg id=\"edge2\" class=\"edge\"\u003e\n\u003ctitle\u003esession\u0026#45;\u0026gt;session_id\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M61.2876,-173.9735C59.2563,-162.1918 56.5612,-146.5607 54.2505,-133.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"57.6479,-132.2633 52.4996,-123.0034 50.7497,-133.4527 57.6479,-132.2633\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"61.8956\" y=\"-144.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e?\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event --\u003e\n\u003cg id=\"node4\" class=\"node\"\u003e\n\u003ctitle\u003eevent\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"147.3956\" cy=\"-105\" rx=\"30.5947\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"147.3956\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eevent\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- session\u0026#45;\u0026gt;event --\u003e\n\u003cg id=\"edge3\" class=\"edge\"\u003e\n\u003ctitle\u003esession\u0026#45;\u0026gt;event\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M80.0073,-175.636C92.8036,-162.2229 111.0676,-143.0787 125.3545,-128.1034\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"128.0426,-130.3561 132.413,-120.7047 122.9778,-125.5241 128.0426,-130.3561\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"114.3956\" y=\"-144.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event_id --\u003e\n\u003cg id=\"node5\" class=\"node\"\u003e\n\u003ctitle\u003eevent_id\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"147.3956\" cy=\"-18\" rx=\"42.7926\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"147.3956\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eevent_id\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event\u0026#45;\u0026gt;event_id --\u003e\n\u003cg id=\"edge4\" class=\"edge\"\u003e\n\u003ctitle\u003eevent\u0026#45;\u0026gt;event_id\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M147.3956,-86.9735C147.3956,-75.1918 147.3956,-59.5607 147.3956,-46.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"150.8957,-46.0033 147.3956,-36.0034 143.8957,-46.0034 150.8957,-46.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"150.8956\" y=\"-57.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e?\u003c/text\u003e\n\u003c/g\u003e\n\u003c/g\u003e\n\u003c/svg\u003e\n", - "text/plain": [ - "\u003cgraphviz.files.Source at 0x7efc58c9f3c8\u003e" - ] - }, - "execution_count": 15, - "metadata": { - "tags": [] - }, - "output_type": "execute_result" - } - ], - "source": [ - "#@title { display-mode: \"form\" }\n", - "#@test {\"skip\": true} \n", - "\n", - "_display('''\n", - "digraph {\n", - " root -\u003e session [label=\"*\"];\n", - " session -\u003e session_id [label=\"?\"];\n", - " session -\u003e event [label=\"*\"];\n", - " event -\u003e event_id [label=\"?\"];\n", - "}\n", - "''')" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "HFlI3zMtU6Ev" - }, - "source": [ - "`reroot(\"event\")`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 311 - }, - "id": "uANFWQA-T68W", - "outputId": "659a11b1-39bb-456d-de90-b2675182ca25" - }, - "outputs": [ - { - "data": { - "image/svg+xml": "\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n\u003c!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"\u003e\n\u003c!-- Generated by graphviz version 2.40.1 (20161225.0304)\n --\u003e\n\u003c!-- Title: %3 Pages: 1 --\u003e\n\u003csvg width=\"94pt\" height=\"218pt\"\n viewBox=\"0.00 0.00 93.79 218.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\u003e\n\u003cg id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 214)\"\u003e\n\u003ctitle\u003e%3\u003c/title\u003e\n\u003cpolygon fill=\"#ffffff\" stroke=\"transparent\" points=\"-4,4 -4,-214 89.7924,-214 89.7924,4 -4,4\"/\u003e\n\u003c!-- root --\u003e\n\u003cg id=\"node1\" class=\"node\"\u003e\n\u003ctitle\u003eroot\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"42.8962\" cy=\"-192\" rx=\"27\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"42.8962\" y=\"-188.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eroot\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event --\u003e\n\u003cg id=\"node2\" class=\"node\"\u003e\n\u003ctitle\u003eevent\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"42.8962\" cy=\"-105\" rx=\"30.5947\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"42.8962\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eevent\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- root\u0026#45;\u0026gt;event --\u003e\n\u003cg id=\"edge1\" class=\"edge\"\u003e\n\u003ctitle\u003eroot\u0026#45;\u0026gt;event\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M42.8962,-173.9735C42.8962,-162.1918 42.8962,-146.5607 42.8962,-133.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"46.3963,-133.0033 42.8962,-123.0034 39.3963,-133.0034 46.3963,-133.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"46.8962\" y=\"-144.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e*\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event_id --\u003e\n\u003cg id=\"node3\" class=\"node\"\u003e\n\u003ctitle\u003eevent_id\u003c/title\u003e\n\u003cellipse fill=\"none\" stroke=\"#000000\" cx=\"42.8962\" cy=\"-18\" rx=\"42.7926\" ry=\"18\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"42.8962\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003eevent_id\u003c/text\u003e\n\u003c/g\u003e\n\u003c!-- event\u0026#45;\u0026gt;event_id --\u003e\n\u003cg id=\"edge2\" class=\"edge\"\u003e\n\u003ctitle\u003eevent\u0026#45;\u0026gt;event_id\u003c/title\u003e\n\u003cpath fill=\"none\" stroke=\"#000000\" d=\"M42.8962,-86.9735C42.8962,-75.1918 42.8962,-59.5607 42.8962,-46.1581\"/\u003e\n\u003cpolygon fill=\"#000000\" stroke=\"#000000\" points=\"46.3963,-46.0033 42.8962,-36.0034 39.3963,-46.0034 46.3963,-46.0033\"/\u003e\n\u003ctext text-anchor=\"middle\" x=\"46.3962\" y=\"-57.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\"\u003e?\u003c/text\u003e\n\u003c/g\u003e\n\u003c/g\u003e\n\u003c/svg\u003e\n", - "text/plain": [ - "\u003cgraphviz.files.Source at 0x7efc58c9f908\u003e" - ] - }, - "execution_count": 16, - "metadata": { - "tags": [] - }, - "output_type": "execute_result" - } - ], - "source": [ - "#@title { display-mode: \"form\" }\n", - "#@test {\"skip\": true} \n", - "\n", - "_display('''\n", - "digraph {\n", - " root -\u003e event [label=\"*\"];\n", - " event -\u003e event_id [label=\"?\"];\n", - "}\n", - "''')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "height": 459 - }, - "executionInfo": { - "elapsed": 408, - "status": "ok", - "timestamp": 1600297140484, - "user": { - "displayName": "", - "photoUrl": "", - "userId": "" - }, - "user_tz": 420 - }, - "id": "lMoeHHllVeet", - "outputId": "631afc7a-ebb3-456f-d0a1-11eeeccee95c" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Assume the following Sessions: \n", - "[session_id: 1\n", - "event {\n", - " event_id: \"a\"\n", - "}\n", - "event {\n", - " event_id: \"b\"\n", - "}\n", - ", session_id: 2\n", - ", session_id: 3\n", - "event {\n", - " event_id: \"c\"\n", - "}\n", - "]\n", - "\n", - "\n", - "project([\"event.event_id\"]) before reroot() (the batch dimension is the index to sessions):\n", - "[{event.event_id: SparseTensor(values=[b'a', b'b', b'c'], dense_shape=[3, 2], \n", - " indices=[[0, 0], [0, 1], [2, 0]])}\n", - "]\n", - "\n", - "\n", - "project([\"event_id\"]) after reroot() (the batch dimension becomes the index to events):\n", - "[{event_id: SparseTensor(values=[b'a', b'b', b'c'], dense_shape=[3], \n", - " indices=[[0], [1], [2]])}\n", - "]\n" - ] - } - ], - "source": [ - "#@title { display-mode: \"form\" }\n", - "text_protos = [\"\"\"\n", - "session_id: 1\n", - "event {\n", - " event_id: \"a\"\n", - "}\n", - "event {\n", - " event_id: \"b\"\n", - "}\n", - "\"\"\",\n", - "\"\"\"\n", - "session_id: 2\n", - "\"\"\",\n", - "\"\"\"\n", - "session_id: 3\n", - "event {\n", - " event_id: \"c\"\n", - "}\n", - "\"\"\"\n", - "]\n", - "print(\"\"\"Assume the following Sessions: \"\"\")\n", - "print([text_format.Merge(p, s2t.test.test_pb2.Session()) for p in text_protos])\n", - "print(\"\\n\")\n", - "reroot_example_query = _create_query_from_text_sessions(text_protos)\n", - "\n", - "print(\"\"\"project([\"event.event_id\"]) before reroot() (the batch dimension is the index to sessions):\"\"\")\n", - "_pretty_print(s2t.calculate_prensors([reroot_example_query.project([\"event.event_id\"])]))\n", - "print(\"\\n\")\n", - "print(\"\"\"project([\"event_id\"]) after reroot() (the batch dimension becomes the index to events):\"\"\")\n", - "_pretty_print(s2t.calculate_prensors([reroot_example_query.reroot(\"event\").project([\"event_id\"])]))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "70nd50UgLlQU" - }, - "source": [ - "### Proto Map\n", - "\n", - "You can specify a key for the proto map field in a path via brackets.\n", - "\n", - "Given the following tf.Example:\n", - "```\n", - "features {\n", - " feature {\n", - " key: \"my_feature\"\n", - " value {\n", - " float_list {\n", - " value: 1.0\n", - " }\n", - " }\n", - " }\n", - " feature {\n", - " key: \"other_feature\"\n", - " value {\n", - " bytes_list {\n", - " value: \"my_val\"\n", - " }\n", - " }\n", - " }\n", - "}\n", - "```\n", - "\n", - "To get the values of `my_feature` and `other_feature`, we can `promote_and_broadcast` and `project` the following paths: `features.feature[my_feature].float_list.value` and `features.feature[other_feature].bytes_list.value`\n", - "\n", - "This results in the following dict of ragged tensors: \n", - "```\n", - "{\n", - " features.my_new_feature: \u003ctf.RaggedTensor [[[1.0]]]\u003e,\n", - " features.other_new_feature: \u003ctf.RaggedTensor [[[b'my_val']]]\u003e\n", - "}\n", - "```\n", - "\n", - "Note: we renamed `my_feature` to `my_new_feature` in the `promote_and_broadcast` (and similarly for `other_feature`)." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "executionInfo": { - "elapsed": 111, - "status": "ok", - "timestamp": 1632331356173, - "user": { - "displayName": "", - "photoUrl": "", - "userId": "" - }, - "user_tz": 420 - }, - "id": "9ESrpLsEL4SO", - "outputId": "ba002544-d4b2-4ae2-f343-f1f25ab91a4d" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{features.my_new_feature: \u003ctf.RaggedTensor [[[1.0]]]\u003e, features.other_new_feature: \u003ctf.RaggedTensor [[[b'my_val']]]\u003e}\n" - ] - } - ], - "source": [ - "tf_example = text_format.Parse(\"\"\"\n", - "features {\n", - " feature {\n", - " key: \"my_feature\"\n", - " value {\n", - " float_list {\n", - " value: 1.0\n", - " }\n", - " }\n", - " }\n", - " feature {\n", - " key: \"other_feature\"\n", - " value {\n", - " bytes_list {\n", - " value: \"my_val\"\n", - " }\n", - " }\n", - " }\n", - "}\n", - "\"\"\", tf.train.Example())\n", - "\n", - "query = s2t.create_expression_from_proto(\n", - " tf_example.SerializeToString(), tf.train.Example.DESCRIPTOR)\n", - "query = query.promote_and_broadcast({'my_new_feature': \"features.feature[my_feature].float_list.value\", \"other_new_feature\": \"features.feature[other_feature].bytes_list.value\"}, \"features\")\n", - "query = query.project([\"features.my_new_feature\", \"features.other_new_feature\"])\n", - "[prensor] = s2t.calculate_prensors([query])\n", - "ragged_tensors = prensor.get_ragged_tensors()\n", - "\n", - "print(ragged_tensors)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "UxLZtkdmM7lj" - }, - "source": [ - "## Apache Parquet Support\n", - "\n", - "`struct2tensor` offers an [Apache Parquet](https://parquet.apache.org/) [tf.DataSet](https://www.tensorflow.org/api_docs/python/tf/data/Dataset) that allows reading from a Parquet file and apply queries to manipulate the structure of the data.\n", - "\n", - "Because of the powerful struct2tensor library, the dataset will only read the Parquet columns that are required. This reduces I/O cost if we only need a select few columns." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "7IHo8w3HMuyT" - }, - "source": [ - "### Preparation\n", - "\n", - "Please run the code cell at [Some Pretty Printing and Imports](#scrollTo=dIxHSM3VQfUu\u0026line=1\u0026uniqifier=1) to ensure that all required modules are imported, and that pretty print works properly.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Vu02cwvfXVpf" - }, - "source": [ - "#### Prepare the input data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 68 - }, - "id": "xEDieagzNL-9", - "outputId": "773de200-cd49-496f-ff3f-60074be06bca" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " % Total % Received % Xferd Average Speed Time Time Time Current\n", - " Dload Upload Total Spent Left Speed\n", - "100 1657 100 1657 0 0 8122 0 --:--:-- --:--:-- --:--:-- 8122\n" - ] - } - ], - "source": [ - "# Download our sample data file from the struct2tensor repository. The desciption of the data is below.\n", - "#@test {\"skip\": true} \n", - "\n", - "!curl -o dremel_example.parquet 'https://raw.githubusercontent.com/google/struct2tensor/master/struct2tensor/testdata/parquet_testdata/dremel_example.parquet'" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Gn5J62JkNN-P" - }, - "source": [ - "### Example\n", - "\n", - "We will use a sample Parquet data file (*dremel_example.parquet*), which contains data based on the example used in this paper: https://storage.googleapis.com/pub-tools-public-publication-data/pdf/36632.pdf \n", - "\n", - "The file *dremel_example.parquet* has the following schema:\n", - "\n", - "```\n", - "message Document {\n", - " required int64 DocId;\n", - " optional group Links {\n", - " repeated int64 Backward;\n", - " repeated int64 Forward; }\n", - " repeated group Name {\n", - " repeated group Language {\n", - " required string Code;\n", - " optional string Country; }\n", - " optional string Url; }}\n", - " ```\n", - "\n", - "and contains the following data:\n", - " \n", - "\n", - "```\n", - "Document\n", - " DocId: 10\n", - " Links\n", - " Forward: 20\n", - " Forward: 40\n", - " Forward: 60\n", - " Name\n", - " Language\n", - " Code: 'en-us'\n", - " Country: 'us'\n", - " Language\n", - " Code: 'en'\n", - " Url: 'http://A'\n", - " Name\n", - " Url: 'http://B'\n", - " Name\n", - " Language\n", - " Code: 'en-gb'\n", - " Country: 'gb'\n", - "Document\n", - " DocId: 20\n", - " Links\n", - " Backward: 10\n", - " Backward: 30\n", - " Forward: 80\n", - " Name\n", - " Url: 'http://C'\n", - "```\n", - "\n", - "\n", - "In this example, we will promote and broadcast the field `Links.Forward` and project it.\n", - "\n", - "batch_size will be the number of records (`Document`) per prensor. This works with optional and repeated fields, and will be able to batch the entire record.\n", - "\n", - "Feel free to try `batch_size = 2` in the below code. (Note this parquet file only has 2 records (`Document`) total).\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 442 - }, - "id": "A8RyaU3EX4av", - "outputId": "9235bd5b-cca6-45e0-8244-0f58d6bdcd9b" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/lib/python3.6/dist-packages/struct2tensor/expression_impl/parquet.py:65: FutureWarning: The 'field_by_name' method is deprecated, use 'field' instead\n", - " [arrow_schema.field_by_name(name) for name in arrow_schema.names]))\n", - "/usr/local/lib/python3.6/dist-packages/struct2tensor/expression_impl/parquet.py:396: FutureWarning: The 'field_by_name' method is deprecated, use 'field' instead\n", - " for step in curr_steps_as_set\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "============================\n", - "Schema of new_field prensor: \n", - "RootNodeTensor root\n", - " repeated ChildNodeTensor Name\n", - " repeated \u003cdtype: 'int64'\u003e new_field\n", - "\n", - "Sparse tensor representation: \n", - "{Name.new_field: SparseTensor(values=[20, 40, 60, 20, 40, 60, 20, 40, 60], dense_shape=[1, 3, 3], \n", - " indices=[[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 0], [0, 1, 1], [0, 1, 2], [0, 2, 0], [0, 2, 1], [0, 2, 2]])}\n", - "\n", - "============================\n", - "Schema of new_field prensor: \n", - "RootNodeTensor root\n", - " repeated ChildNodeTensor Name\n", - " repeated \u003cdtype: 'int64'\u003e new_field\n", - "\n", - "Sparse tensor representation: \n", - "{Name.new_field: SparseTensor(values=[80], dense_shape=[1, 1, 1], \n", - " indices=[[0, 0, 0]])}\n", - "\n", - "============================\n" - ] - } - ], - "source": [ - "#@test {\"skip\": true} \n", - "\n", - "from struct2tensor import expression_impl\n", - "\n", - "filenames = [\"dremel_example.parquet\"]\n", - "batch_size = 1\n", - "\n", - "exp = s2t.expression_impl.parquet.create_expression_from_parquet_file(filenames)\n", - "new_exp = exp.promote_and_broadcast({\"new_field\": \"Links.Forward\"}, \"Name\")\n", - "proj_exp = new_exp.project([\"Name.new_field\"])\n", - "proj_exp_needed = exp.project([\"Name.Url\"]) \n", - "# Please note that currently, proj_exp_needed needs to be passed into calculate.\n", - "# This is due to the way data is stored in parquet (values and repetition \u0026 \n", - "# definition levels). To construct the node for \"Name\", we need to read the \n", - "# values of a column containing \"Name\".\n", - "pqds = s2t.expression_impl.parquet.calculate_parquet_values([proj_exp, proj_exp_needed], exp, \n", - " filenames, batch_size)\n", - "\n", - "for prensors in pqds:\n", - " new_field_prensor = prensors[0]\n", - " print(\"============================\")\n", - " print(\"Schema of new_field prensor: \")\n", - " print(new_field_prensor)\n", - " print(\"\\nSparse tensor representation: \")\n", - " pretty.pprint(new_field_prensor)\n", - "print(\"============================\")" - ] - } - ], - "metadata": { - "colab": { - "collapsed_sections": [], - "last_runtime": { - "kind": "private" - }, - "name": "struct2tensor: ML on structured data", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/g3doc/assets/examples/prensor_playground/aqOX7nS.png b/g3doc/assets/examples/prensor_playground/aqOX7nS.png new file mode 100644 index 0000000000000000000000000000000000000000..a294533fa8cc54a795bd787f7c7d8fc6e1b8cb45 GIT binary patch literal 62680 zcmd43^;cV6w+4z6pty%p+$mDr-HR5dSg``d-7P8ZPH}gqxTUze28RO0p?HAH`<{Eh z^PT_Tj`>3}_85EZwWmDunRBirTuns|6CH>S2M33#ATO-}2ZtaA2M6y8KzY3~wyKN# z`T_5vAtwn}J@NJs4o)0SL0Ur76aF|0IZa#Ualk4PC0>Kp92q}J!5jQM3V z?FS=a{{KgkQc*M_!dhyNT4g|nv!jJ}hl$!=CC$b6m|Gzz!T*u+Ae5-j1S#{uutH`W zUAkQYA_=ByTn9?wBDK!|JZ_x-h*}HE40`wH1Fa#nzCqRx$nk}eG{gJ(xXf%-?A$nE zK1DdRUn&1fOrp8fa7;N7oJ+oJDC*MoJxO||0J;HZXbu2?f7BbVP`qc|HgdvK`n*WE z`*ga$1ivwCGrs5lpJ(E5*S4XIQFE(L#HHt-cfjT%^TQGghstzOG310d0EMlRcX}i& zmDaEbYWXSg|47eDb}16qI$O9VEe^b3aO9@C{ciptiu z@ZCY+6Vh$lRsWzh*OWgOw*hjPaBJQKlRc#E)UB6KoDR!)P%h7t|I1fGm%F+1TSIPaM*+ z6C6-Iz4z10wYO4cdc*O5y$Ap-MFVmmT!HPs4|H>I@b2664k@)w#GAb?H_KKV zYoW*X=HkH?ffCM+2hL1UWCTASJ_!hx5~!Quj>ReQQ|T?V_8?3v^pq9U!66F3m~VwF zKz1aBtFf;sdfAaeYYjny2!>`ZU~dFT{?VNhK{1&n*}#@P=|NAZGy4Al6>@I1&zD@9 zd{^f1d`Q!t>L%ixYhOw*qv2ZC&(9V0vK0CIY%$MHI1%O+-}RZ{Vt1M7>(Wmkrbt- z7&=HPCiNw*MlU#?J`3glJR}Cl$ac>uDZWF&04Mu_UXD+-S=|Nx@|U0hz|4|TP6lmv zpshdQ6#@P6KY(SY`}x!BVAD%5llgi_LCpVSlSFP4 zbE`^wbQZY{Nqc(+!ylygKD57MImJOmKi+J|v9ihowt6Sy>muXmpt9(3n4_cc@j?q3 z<6!e;9V}1?qgy1Kq`54h#o%4or2I!;!DqNu1`O#FrJ!?c8Db1-&FQ`W>;5r74oEL6 zBhVkvmjyvYSkpzZ)OnA8&ZV;?FU|@J5!?qFnv|d&VH}}9Vk9C}FCV`TLzywC<&G{~ zy68Uu68(?@(?h%=hY*Ay#}Fi>1j)=Zw3ag}$`n4KcMN>hA*&!|&;vq4yAh#d4%IhG z%D(>#ci*jh!g_Dp*lAFZ$?F1)_F%z-sZMdbdT_Ocrr3$LAT;{9`jt3557z2u!MJ-zDIoW^GCl0q- z6}L%rfOD}^DALs0yrPLW--~h!)?qpgjFzJSBdBmG@Zjz~#<|46Mk%PelnJ44pC`!k z!nUxWX#yL+7>yl+{^IUo2=_w+xK_htfYje^A#E>*LpPn5FdNqr;I3R^bo?^#;1LHm z9b|4g{2(9rjWbXvyX*P?x~erWkRVB#5_MSetxP{YwhmtRu=Pbw3wmThuI+^|0fB@6 zY47|0#^em^T*?n7&FTc{Jvn;cwo|AuCgZu^IC)Ie;n9WuVn5ur*QU+-9d0=35~hNF z>2Mootbf5jW(h)y+3=FRx&^3x>yH{oXpBz}MQ=)Q=REw zx4oisTtuG5x?P|+qjLWDAMY6N9V8C*fGIzVCa0m>_rvVj!LW?>qTT@7cMcGX>y6!` z6#B%n6#y|>K+QW^7{>o>+lSkP!kYLXg=;Z;70Uz2UAthjiM}Sl)g5oHk?d&r%eX>w zfg=M)_+LxpLV0?K^X#O4wVB-P>7Js6BK@OEW@$uMulL0KzxlSx#y;Ql91Z2GR5PI{r(#e7zs`ekE^h%jbfq>l-juVjWf186x z1u<{6VnAfZQAz^QHVfqdYe#CrGO{l=ip&HiUQe3!GHm~wU38&mn{D_@2?+ke^REXF zQ%uf00!Zwibu3DP#QjH9QmKmn0kMchI9dceGqa$}Q3;7f)=DsEz;F9ObC0W>jcEui zenrbh3x!auG4}qyeo*<(t$Mvw5C7ciJPVfs3_~NM%3*~uQQ|~AKuREH5V*Xu9!hHv zPq00aECvzktPKTDHo+27dK>NwYk8QEr&5m?LMs({1Hfs52U4qWISs)K9{?WoMIMb<2XA1=tah+2hgvo_}9X;u(p{)*;C<8`YZKY zR$o?eqjkb{g5Llq>tZpG2#YgljPem372V)m4q+J~jxa_xm|xN+t_=c@fP~KZ^17s< z>w07)o)XIiJM3SVZbv!ZW4#4QBK(i0Eh^!MkovYN0MY>o=b-cV0kq^`JqYD9Et+Yx z`252C*Sw%9DH0Y(Ke;WXUQvQN$}Flzd{F`vj6QWaez~u7V+8)hfY@UO@!V&eQ~Y9d zT@Vr4zahpffp{aSBRNAg{686lX92G7*Pprv!O*@X$uQ}O>+g~QLDj{-z~o3ETYA=v z7V#c^P!nnp(pC)F!rNW9yl4(heytWQ@l#@VqFUy7EPvgM7x^vyErBh*Ehcz9CQz(u zO~g4qx7q~|z;4`sBwR@HkJtSKa^RWmSRPn@=*6rUb3#Z-k%ce@+1HxqiTbLy$hIgh z83x`p5~iTVk-;))DL*00Pc1-hASwt%6!HXx0BZLPj*n@SFvd)#x73@1@;_8U{wbz3 z3sDg6%jE4s8~^oreZ)DZ05nvY!Qh!t+UY%HrhFyz3RSD1aY8Cqobm+(B$ObvaiSD} z0ooOq5Fr@6uL_T$DFXimL?OSzzeOB$$~p7qpJb=@YI@^$$+>^y%>S`^t2L$lH5D!Z zl{O?@H!S*>0_<^r5yqN4OJbS}7u4G(Xp3mVxo6T9y}Qfsj%< zt{@Z{#n9}6l|Ms_N`})y9R3H4t{33U5sy@$5HsUM zED`9#+ouNCt}7` zp5Y%=Q@?T-+H7rq`-`JYDaAlEq<-3W>qC)-4Q3LjpJPK~A7e|bPPhK`S9oA8dm6et zGcE3aCFxhsxc>(cFLNY@B>o#Gk!@ZdgRbc=&-~xX@sbB;NOeyvBrfEC*;;N8`kc50 zmgmo9Y^G4ot1XTvMgNy4kF)HetK~?d`zs1E-oJ>5KlmK7`NYrf_w}s4HAzHxEw;SY z&$;L}JtM+;JHmUlh0c>;8TD6ko9qKk7!kD)F2-BDGu(J!|}!)A9In{tDC zbA^dU>siL5B(D+C3c{;*tT>i{ZnTyvP49|izt;HJ#o2LZOvqpt(5tQ>*z z`W2pH+q-8JOj=vY?hmm!GrMuq)}EI&_H0UR)9oHEg0%HwlQ53z?&(I6#RDS_n)4{rUSCL zBU3}?wfJF;{!iX#&7*8Yo@F+9%i5W)s!oV17g=L>yjJ)@=JdEFQZi^tB zxl|H77#Ncg2X^U)rA1&pk{eNPt&=8Ji>mPMd%opVoH8*# z-u=492G=asC+(uhT-4;(sV(ZEV+OSiX&05zXa@T603a=uZuPm513uclUA-}{&@rfB~(7hSYosnTS=ba~REyINbayy0p#5TiY$IV+ z=~5_)pSmt$@L4ChV2>p(r~Fdd#!~dM!XfgL+{J~-wUl~>!VN}QIDR(D!-n=(TAUI$ zi=#OuXHS$CI{9mB~Pv?>B&#F$MEW4%ZN)_MDs1@@Q z%6Iwh`)d@vXxMM4owJ!Tou2RaDcUDRAGsHM%olUvuaib`!*ekroNKm??k%!EQfc1^ z5TjAMB)xF&@gdvBwGBd9YC1k>w@4Wo5u~uZxfQ3usfoL8*67ccq7+MhAh9vuPICK` z7d)h7y0~+#W7|LdX-VFMT8cb~ZLb??pvspa3Xk#=5!-t4O}tM^Z48Hwi6k~;NrGMq z=b6VeMLauh8>ay@`RB^^%sZJ%OjIg^CxQ91*EAYKrq}N4HfIl%ndiT`nHF7IC}gFV z@>Nt9&x=p%K0z&MK)Sc81T{JM!_YY0(Ii7wRY#))|#lq!(@)W1FC~?!(VDB1p@5o|KjMs*4b}5b5C>aH#GST z@02~YOgIASIfNQxYb$#0M115bzO0oUSH&;28_~T+)9#q|djnBU*Qs3ccKbT;EO+wp z9<{LVg>!{g)?ZpNtVibGk-E3_inNS=caLZ71#GRm?)OCrGRN!qFU-Fe-#x?MZn*Yg z*ztb7+`mi~uX(FG zOT>Ch$ji)E%I6p!sH%g5kZ?6iZ<%g8>((=8h9Rb4pK{s8)$`4 zK$Kq_gL#lD1TJo6c^bAG_$Z;RIut`Zub%O0IhFYblQZw4M2ZR+mf*?xFN)61yB#vC zpZ1*^4dpZ35uY}qhbK!PnAQ?0Ere*^zh_Y1q3O!xpt>FlLKJ(rY(I-hxq8#fBO{YL`2;wDd3&% zdu36!Nq4F2=b>vc2l;?|V1AuBtAsdMBYoSY@u+aVE=%jGInQ+0Rvi`= zRVM<{cuLk5ficNhxWmR!3V+WDDQpoWcp#gm>-{AA>WvE0Wnzwduz>!PQQvuPf~pPM6OKN&wUqy`z~@QTJix0kXl7fe z`b%u&*=zX9M10|d>2%Xst&=SFHfFGQODehbpLRGUachE(tGAo9GQ)I5u*zB`(x}8u zSh1=NIt0gQ$TDkKGU-fksgJKAE)bHSYIasxneh8n*>~&&F$9#-{ZBocT~SC`)h^rZ z1xQl>2vxSYKWGo*%{BN47#4!Y1o2coOa$cew30&x(|| z^qaocUH;*k$j!NtsJ70;4~-zgRMybbDdwn~e(>&_+*!PIiESrzwRh^PG0;agjC|aK z#=V_@48~4-*}JP`0Orfh9#0QYHvFL`Ot<`&ni7G``qS#(g-Udu3$GaDBN_Wngga?` z5<0Wie>9^wVu(yz!6{xznp90Q*=y~{x0l2Iv5TMOs8zEkE0nf89)&Tq(ZJI5f`ajZ zLTrU6=8oAU_V9e^$LcWfVB4=E@fAfyA1R4^F_L*bb&_BBZ>~)mD=9 z-Cf&H=IC>)nKD|^=!y14VG2U=Ja|d?ov|`nf2T~w=kL8Ku{^a?ER-{(O<`7Ot;OVaUPCc)eA$BK1+o-r)x0p8aOwdFJEEv z8SzctkYSO1;arlIq2N#Dd#Bi!0d$EsTIwV8&$B_m=Y z2)7sk7#A(q&Ej?EamjU%ScDVyYZV2sW8vshUI;i|QW^Je-_?=~2?HzM$etlX-n_In zmcgv~tmvx4hD)ORiLO9852DVMPU6cH9EV@)t@~>0lh5@^h>wWK&jMfNuG@$XKGi^H zZWGb^*V(ad-~-o93H5vNdj4fQipJM>nZ9mF=m!jMtS~~G+q!1Blg0K+3u5xA1cYaP zt(3(z)!}GX`4rdj1v}?XIN)R9zP1$5Rncz&*Z_6_4#p7n+RwWa7D^m27-7f#z^E7R z)91@C&tb%uA9Oe*3udXou&d`NqFNH%pWJ1`ZY>*Wku1qlJ?-)MFwv6&tgri zeLd)rhb^Wlcua}EI{lR+7xL8F61j|{D`$z5_I+`#=a6bKOk*RUO?2JrJfQ9~F^2y# zr_Dj_V6LdrkLSbNu=iNUC~du6yKgj8JDlF=*|mV&R$PL9FdUY@r=ucqISYqu%Y%E* zS+sCezigS-yQoO_<-rO?H&vMre2CcRns`U-KPrvLU5b+%z$*S!_W6MeC5)~1gF@kf zyoQkbaZ9$l=HQl1A@a4?-b|E?-nAL%AFZ+ zi1maoY@eymZ1>>F#qnw{GijgX2K4UbG zsIC_(aiJEfW(I(ojC+vZYnj%%f|F#uac@Vej;3D*rPV`noz9n05gOp)Maa^WHB)a@ zWc2$}hLfHr6}pnjPxSX~JcVuJl(k`=k?UW<@8?Hi{uaIy7Pf>CVtv&kZ)Jj6gMc+C z8zca9hAfY{7x#Vmh)Hl?OuV~TD$BdO@A`-1i{+130uh4AyVM{Rie-3B@$#94ohqm6 zjT%;dy|}l<{kv(m+sK|L&bSBXyt+WFk*jqAziT41!Ta_8C5LXe$K(1)2f#uxyJ>MJ z*FgBV&c@xjl&cw`c9%DLj70%Fjd(a)EqUqrJTmW+M-V2-W@DS@y$EV{>&#^5L4M?C z^QWzVWvDJs6tbVV|I%Bxhrfq!+>I)()q~}RnD4L>e(8RoTvj_^YBaRUa$EVT+VauP zf7RycdeLFPokl5zRgX=CokYwZcAMjOLmIjEMC)62IhaZuO>%MX>DI@s4(l=K}!UGcxTJmT+h!FoHE;7gDEf)nPTfMGFb;=fD%J; z_g~O9(9ZymfM$Rc5|DkiNF}6?=bZggYM+W`NbswSfxGm}%^=0v?~_%ogJt|+&&>>P zmHrw`^);Uy-)-Inca$94`xi>Wf}mhP+zNg)yz`33`)QISH+%VJof;#R6nZ%enO`;4 zlTG!#`(1kHYFwQiqUL_*m|oI2qx#o8bfQ2tAa|t@be3L{;_x&sns2>j$9>hV`CSEw zCh2`;t6vRTZjkGd90V3(-K_fud zOq+~cIl%A$n zQ5mCQ_nODX(~$eg3J-p3*3)50jwo|8UkhdUqf$%Kaq9V-&F^<60)6Oz+s?xs8)~aL zJ_3;B73GPNnlr*+=3%hhz8Bw@KNZ-ReplnkF9OH4QP&x~ZW*UWJ7_qBVm}(|H&WdL zjGGKo*~-?iMH)l@UbL#=f&r2vi^|znaF-x5)G{gHuceufzAn z+y}F{JlPL-tDJpN%5GoF*0EycANCv;BT1jfofS=q|29?i@@@aR__=Ct;C7G~TKs&s z6KkC1zGA&rghBXAkreUk8It7_F2vJRnG8!t-SW#l4Fn64%(w2jqi)#ds$e@`o}Abu z=fdDzUq`Q0(>1XNsPsM5XWJA>HK+z?JbajMrWRLOs|&F09kwDo>xA&bmxZ-4#%t`s z*7M9M@JXiH>2GcJb~A)dTXxFH-IscKZ05qQD>K(!h%VpeUHawDn5?wj&s&FsYjGH9 zimg{*cZUWJolR#%D_DvZeUEIrPCEmwi7JAJD)(k@@ayTV>A&b^wdm_`E|20x&}M{Wxo!)udcR)l+vv50gzG3 zi-}7?ao%o6Z4NvOhr^3TE^^i@4ZrVnW`)Yvmy2{+TpZq0!!ZMN2T#U~-U3*d* zvrfkl%DO#XPW(QCNg)cw_hY5R3Cdtg3OG$%w&!kJS%=s)jPt4(k8~YX6mJADolOqx zp4Zi9a~wplRFk|6GvvHwIWo-vcD}V7RmiE(5Y|lKNb$6e= z5|Vli^U;#BvafUZZq$6+KiPFr;=nYrHxPk3}{Wru(`te{8c?J zCd~__zL@4EPcx6P=f`$33g0Q~ZWbNm-5w9LIUGFcB5a$o_=1Yaaplmir=PPE*Uyp4 z81Jgnze^ox*!GaMb7Qay4R)`NhLxI184o83PCyWV@M?>}>^rw>9ytzoO{cME&%tju zOX$Os1KLbmCc}Z70gormKHG@6yBO)V63<2OeCVqHLpL6y#@%Hb2qwkSx5le!_x578`Z@U)tsFq?N6qy za9)gPLlh!(!~g~iqZpB!rBBkIy#j=n>BhsB1oe=z!j?$m7d1AF2}HXQoi1m-@R;&0 zsUv878d2D`UpQ}`(vO~xX(}i)tt!^q$pdIB``D_FHZd7?>6$?7 zgUR(ty>|yw*Y3KVhHHPqZL;&exWMPEK2hb;_-SRAQP-dOPaZ*147!$PJuI?fSZ+^y z?61s06$Q8GYooJfwtQ0Mhi@CX2Xwj&!R;a9Hq!VD>4#niw?xl-D=6A6$1Baf?FxIz z33v|ITOT$*doQjV91K%JOpl{z`{a);ThxI?F(W_3!&?j*UG`hXC)ZyoOR0%aR87Yzs zV|0b@oP~CxxS(R+e^ z_bk@h36mK6(OqpIVQI_Z%=ZuDzNmSxm8*1={^lE>7i?;cbrBl6JqC8 z`9YC|nr1h(S_1ujpl}pU9FI*Q&uPS;Y@oyd7`qm%^LGSw`aVCWj_RrVLQAn zAgkT}$a8)V^dlLY6Oz?~pW14L?N|o-1pR zY`C|4TJ_HMjsf%BeYpgd zh(n=lYTW91vHgZ7jj*Vww6c)##1%!lB5%zA(HfQI$t+(uVX@o@952WG6^Jrhq0%|O zXQe{xbPG~K^Dm%-2XM+G-eC)#bzJdk-!3wDl6gmWxHYH9hmnbR|GmDv%FZ^U=tLzx zgrRK_2EhoEQCHOD$0{Hf=#!P3y&3a73Zn#Xy|=>|7QVy^i>YY4g^LQ?Y`jj;-u&*8 zWpt?fAnyGpL2k+i$2xaucd)TPGP@wSLE1R%4c=*Xh~is1lL| zR_Ho+k7jbeoK$9)*haFweVGnqzheHR%T2|<#8L;rRxX1WEJ{csz`CuAKOU- z5cm1~V{EGWmnP>7-zejTIR&yUdEqejin^C|b+Ha5xKXS%-Cc)2b7ft&wo+9({Vk{{ z{?6vY2#02C_0>X3quUsR^V~flJ@p^dSgzki^bdwj+H^6WV^1ngE=@A~Y=Mbm7V41_ zlVMH4drb%~pI%MbgW`&IaY#F5(Wu+=C&c1=1jTkx4i9b^MqAqL7}w-+wVvng_1Q*% zD$3idE$XbNyA|7-tcs(v_^@?y0Cvg`Hc6K)s-W{-hG~*RT6$Su_xROmgxe1~Lvupz z;DO+*;wHbCg|vKfnIu0EPS10b=(qQmn)m5cN&OU!W_knnwJw={TP%(!v%x*t6#k1X;p(Fj6+6!%PdNzHln4~Ns3 zB`b00AlvVV+ezsm6^+BQnoNNL!@C z1G}g?Jkg_>S2bizt!NLw5P8T}>|nPA-e-Uqu$j%uaGa}{T;}ihb0$C5E+UU!Wk?%q zn+{_T0?hjsmt1ZxuJ)@Jwx#{q=TT%oJX2> zN}o#h*l;KYNRY#=b2WDvh(v4hiy=j;VI?cNgu=88pI9H(7#hv{eef(NmwA%3amlx zdTyQwhyjgqQMWE8?#mTUcf5^bOtywiQ9J3ZzXyXc476J7hQDYx`tvj)h`lX(h1cb! zy}YJkk&^l@;~m>sHZ&|Ng20pzPra<*)XHdY&l_7t|2gBO+2^1v7J4{%?Rf-bO9du0a3%)G64u(GZ@6*TN3pyT#|&}0ArS{E69I3j zKGZK@arvDu*8OWEJJ4)+?Mg3o1dFo8%OMwVzuuS2kUhBQ0b}1rh1AFzM>i}1cqeQ6 zgnIw0-(G|@T0{ORX^_htPyTYtiv@BFRehpntv@(4KnzwHf69jESreS>Mr&H_M8=qT zX!$h7iX`hdmefke{m<7?LVt4Kou>H1M## zRk!?cPAw|M3`E%DXIH!!Iq&mW!bj#U1Jn;SaOb9V7#2v{3M;o`D&`=b&85zR6v-Ya zF@m?KFOQP}j}~AbRmb7mD0(`V6UC0tVjo(DKc%2$pcRbe6k%&~3oG)mFxp zy`JZYZ~vWPtFi#(i>)wyF?y!NLa9wgeVF@W32$gt%n zF-SZ@3g2v`3d`pya=~x3xs%oi8cRpd;<@9aJW}BD*!Sa9x}8ns*RqH=UFECt0kDab`VN z5(lY8&0n$)j^&A4Xv2+q0rae2FT0uLq6+~t2{KA+tvX_7zoXihtGZ1wZYK~3hwu?OFYpV$ zV&-<=V`!$99--4jE|vkN>WfC#&|6Ear<_INDUB^kSLDsa8(@+tuQR($+vV#o9dGPAlyMn_XrtpSM!19j|W;nmz0chHc(c z%cSb(WdC5f)D(-XR<3aK(*e%QZi>o?{n{+h{~9P8_q{pmW=@b=|DsLNf#c&ErILw_ z3nqKqHR`Ag3%oO=GYLFpByKc?aS@CO+5wS)50S5G}QY4cWR%9lcFwgO>6E?x)hk_G*pj;aQL6Hp0KAHa0mxc?=A zy)YIo+?F~{wOuT$dhH8xwXVJi>v+8WxgDzV6*=s3)L*^vtcmNCbkX?i9SKJdwzegC z7Pi<6RC!ikup=riyS4k}^qEL6m~mR>Iua#_y0RP=dLC^#I5sCfRX!JM#0u8TWeCGU zj5es+Sn9l4m*%_QZ)GbAy2-@5z+H8!>3od#*$l^qwPe1*CF{}j9JuAswwkXewBHcT zT07fV-&UE@%`2GnAM1oY=w!1xkyl_&+A@WthJP~iJb>)3O{g^kC#MITs`QIad2d!D z(y+G1%5{R6(G-v|lYXFZ8x8y!@Lzdh(xR}^-}<$Z=&a-(O|y_zN3h4Eqr};$X6}P; zdTgYKFXr9zB~XoOs#Hq<2Dh4+4Nq;%Y+9gji;qg+t*O(mGbV%=s*iw+s~r7P22*cW zBU1Ec-ze>OKtR+H6@>?&1|6p(6!aS*CCe3lEBoDz)eF?>Z!-jYH~jH{l7F=v1yn%a?3r-J#dqvPoZlM>wuO8YVO$fLE6a*oLG(h$tA_xmP zAK?+CQ~N)W1`j&w{5hp&PT*F{7VHuNRzl*1I5f z({<6!Se;^jg*|o*9a`S=Ih?2ne_^QV)g0wng!>&onRAA+e$f`D^mFy6t&qo3*P5Z9 zUhHW{bxpgmcEO90Q<%?AI2W`EyMrdb;*j*xWv&<4P+DD`7`whaUnHcgopW=de^)2{ zMReu3;ir&ij{SPDw3`gk>v?xgE6g_}9F$rHG*j~5sPXC$M{!H-KOD*BHCs|RJe$#3 zlJa<9Wcsc_N4HBAD4AHnap~!364oV+#fS39Z{nHcJzXvk55pdhs$WFF3kW-&Y2(}~ zx@?r7hGx-s6UI?bIDCQ@9OowR5+yDDMZyZIirU)R8AIP9e%22~TVCv?TD4OY_q%j2 zqNxJP;X3^X{@fXFDQ>9;aVjDr(+YHH7NrA~5+C2!_|R??Zs;Dce`aSTjEe;uBC|@o z0zO_m1^0LNbc-36A9gT((Z`!VMNIRguT{C&$<4aTQB3W3g-=!IzH!egY!325mOmR-akEabyt5sHVRfdgF9;1?MGrR21dgfE6 zHI*^;KiJZ#Q?09i4xxRTpWisB-|ez%j_%mAT%)f)xa4rwX>CwhnpX|2sp5ZateGuZ zMDps>Iu{v~gr{tn2>wi)LM}OG3od_9ayrSAm^9+v^bWZOviLgaA-PonT`>sq)*J=C z_JqT9Wg3%#)oycpKdJ<4ba(tn2IYx4-Dn5z1hh&2NW}OaI7QzkU)2-fzE|aLB%v{W z=%}1Uf+mnz3he1I`_4y&fHC4auB80l#!N9*{L*IB`xbXX}Q4sOR-bfutR3 z8Kk7q1AWJ$YP5j4QKIpmqpz%UB}SW$6q~6=V zXg-(E*PhMej@~uUxcz_s1u%Z&z0fj?6fbv*y&ckR04f0qArIUhQ*JJ9&y-O-a(^Bi z2d^Pln*JV?agw4$m9;jix{BWMcQsmo_^}dEitZ-wgI0x2)2Fn>kJD+NtH9mD#~Nrw z;LZ-u>A!lrympHdlQ-Ufi&8(cyIFJ!Sw^iYR0uU|r0Tf5cf2c4V_#MBEp?Lo#!=op zn@HVVqh02#MuVtMMNpweU)*xU6xJi|y=@M9X)O;6k8q*kNAt*rb}xPznNRZUn0Y@e z;Q#k)=Z|MbEa)XMY3%bwYIkb+AH}`EE_Lm=TzD4juG-s!X)~Hit~n<#+TwlK?8Kkb zeqv+S+6y_riSpgC?RG6cprUfAEeS|(n>wM!$LgX;Bgw1%2U|^ z6keOD7rDF)_Cw_d$0ygzRyl^g!*r?8V%vSIGNF%?A&*kVI%vASUBq#N;2m)6Jq?xU z?nijMYbDoj#WZbPqYGLYZXV+kLTAU#sX@m!6_KM3ZHL7TyZaKom&LOm@O^$BtX*2i zPtGX#n0^;uv32VVMNp*cFljdSE@KexSo)^qbVQeW2S4s1dI2g{t8oqCzDsAie7bh# zOF%w2Ey;1mv>*fuJLaA}{8_InFy<;P9VyZzWnC4&NtWDGH=@Kur=cJHf@a>I_`}zC zXX4tNiSZ+}=C;7Ta;~lEbA)lLfI!Op%iynS|Dvd8IwLD%?Q72H?2&EKISpY=tg&s0 zPeuO31nyNhdYmJYXGPJ79gt9_TW?fKLGSV3%~YR+fid6Aw}=M|Gs~B*^>*4QCW-flFL>pM;&!kC*l_I zp{&!m(}lCEKo`jWgm%JP!g<2O0*yD%sE&{!pmWoENuM3zd3&u+tFQC&`X8l>)>E9Z)X56|jV% zHzO1$=zOv;RahasDm*2;cESlKXuNB`-eP zW`glQmz{iao;!Y-^4?2dJO0hkc|nbMUZ6@iA)isH`6Ei+2Kkj&Zq8EeFjO|nUzI?P zmDtr1iX|Ki_z>#eO3tLWSF=_^snfW3?5k(VsR-%$5ULk5v)!sfJ)n&JqSmT$lP*1uyr z?X;81>NG#zk*+fc58YpkhvXgXrLmH9i-GmE(X)+%EKWK_`Qe$724gMN^~K;+ zm@NIzQJGFV@x&8PdgPHuzR@$vnm>R3k@9a3JYLt_LOj9<0V~<2!X<*qQ94z>z*=K2j!AwS*% zqD_LF28(C@`gbE0C99lp zFO4XJ{tNU(9rwu0%zS6+)T#fc{H-We9!ryh>JcV|hF^nsG3OtrujX{dt5kyU1;0?} zEwmJV5_n(AN+=HuSUpzQuT7gaqh`*Wd9g@I3gZ`TL)gL+rnz0jRnHyLJxs^M|h{G zaS|0eE^8({Gsr4wU|m7CbA{&wq^#0O#Fo!57Cgw$x#ym{C0&hd|G_gtAgXGUG*>5M zRoAXv+so-ek=_4BL&0Ze7FD+qU>Y}8A@m3^y$+(>N#)8 zbJuf~oq*s{bb@rT2b+*^tKU{%6uR8fK@t|HT`sQW5aI;AE*bZ7ZSjfPN-uOYu& z6!cOT=H zk>?b>JhOiJ!(DUx>+Z=rPjIcZ=FiflEB!c~MOwe7nFc>2zyJD|zE*QxlXdI63=N9T z*3@=Zd1ha(dB6ST8fkjF-~ByeSM#(-7mZvCtr4UV>2BuFziI0GM{ed9H@Vi@*9&qp zf1Y+G7tw0bypbnDIf{=36tOPUt8lZXI%OT0W~kAxG$_B5Udp{ z57TvAKzT4->VJWPp82?=n`yV*cJt$ZAT~61A|Jk7;D@-(4J!cWj1__)aUJ1k@mUzV zR$vASxDXLn8tr8&tCMv6R`^s{DNvWuX!BB*;>A>`!i8t{m(Vuy_KS{R3vHtR<&kC- z_38(aja;4sdX7t^=M={ONajmlSQR1Gst8xBV|X~!(bo&N3SSDh3oQi7Bv>L)CYy3p zcd1mKn)mGG=7luLhsKAM947fzd2DPmGK_0Y5vPJJTHBTgcq0x$OfnWrPP0B zcwr&oKiBP{mnh;S&%V}8ax06!`oO}I? z@w2J0`dXam!mpq#vNB@yL^o;jT=&EapSWh4wZ%e&5S#BO&v6g@_h|Rh8(+EC-~Ubv z7-~z3ZtdK47ayRnj~sXQmCw2k?V7vyKmJhz(CfRK|L;(jssGQ*iuBB|FWkI87i*ud zO}wS&*|b z>bUfvc2px-rZ@4H;j0$wEa})?f9>X(az=oJxMy)?|N0{pa3zr*&_9 z!H;GTZzT(6r;)@q{+g<9e`i%Hz&6<_HNHbHTb$$}{T_FimCajT#)IdJapRXqs zj%UJk6a1SSehKsQ&p&J7(nYRw=gz)-;U7OG5uTf#S*|QrDVlvYUSSW`_8go4^2;xK z>U?kEV}Y*TijY}KlPOjYM0j#c0GNL7X(X)5R9rAz%(_UNVam3=%)+>0Y|e`yKQ;Yn3d zVTnB+mhkG zPoJ&)Mx~q?NU0 z{dwZW&)lB9I=NB9FL7NvwQ=u$_?=t2WQFT{>O*eG?fu<{FJI_dwphn4(mr0N9=nT6 zO|R>2xb#3bRlCIRd)z(li93&X?>={~X6UVOk3KtEm20XSJoFQH!0uh#T?3EOnolBB z_rBwfJZM|Dbjl!i#ud-%3n#~oeB~n7?||-Z$a7Ws8Vq;&8l>P6%`_6s*@yB`PoHDg=;X)@fwi`QkEVD@b5kG1` z{TF@#QTtT5R?zjhdqMu#^G^$tn6Fk5*30LjNU+!&oI{V4W!A zJin5}QIuceg9LvzATcY7nOO6es&1nmD2||KW^-w5jVCcoZHLV8JSZ2;{1UP9W#?sW z4>Uq6;D7YdM^4=&ZrHG4Zj5Hgs=LjXneV^<-o2s~^F>&SEI;|>O=U7=@#4kgWg6Et z{hp7v+k{7i4#K?x-BIlY6P_=1Tv9p%Q#--$XOD{Ldw7AP2X z1mr~_UE!Ad97?1}Dgnmn+@*(i(p6WvcR%^WpPwp*JzFyy z>ZvbtxMneY^36AXTMH@!EEasx5H?eK~$c;5H!xo z(+xM=;Kq+1pXZlw7LSFE-DXKn%S!j9iHCghj0lU^J5!pt@(Yt!9yC^jxFI5QvF_Z+~*j~6w_*2+RKtEV~ItwQY z2Mb$^*#4p8yBAz=!HI(g4O*yYm?3LVhTh2xZIU}z>F%fNp9|>4mo*)e1-&6dgm%JB z0(r4}(rK}T`$x7Eta}c(Ld!G(YPzh>Dhjz17?SFPFLu)Klefk@oI- zM=$LOhaBQQ(AV#FGTB0ssEFxWVkh|Znl($j1*)VWM0Aq@hzeliQ#M0wt(gzaG&^j% z&hJwN&~NX(UA6`X(se+^&r8i$I8P2T!vJd*QCy%|6dPzz8*#DNBJs6Xg<^ipom^KXCZomMv+V~5bW7bU|fiyA_Z z$OG3IXidJ*aiY2;4Z1^B_i=~$}17J({Xp<5Mi|NwqRc?)_DF?&~>o{ zLb}$#6BqH^EL83@)9vnKd*OwCk;SKFie@8K;(A|s+EOzyRLd0W-m99Y>9hWH zzi9w6TT8G}X)Xjq_1sf0ed>-nsE6CP?@n%^2+hOKj&)f(Z00Ldh!AfQ?-S#7*vev| z20!n0)Sd2q?fKPJ{-&vm>-!&PyY8|Ot;$~NXNvXOaZ|U=-Z#2=+iv2TYS!1Y8Y9$2 zdx$aK=>0K2dNRZhWQPq;P5%6gx>XWgqr0p+2@Mcrh4ot0aQKyZabqeomLL|g(~OP0 zhLLN$7aRe?k@sfq+_~(m?Yl#0%=+scxbC{^9ArfcuemPT>kbQpW}3Kbuf5iP`Ey;f zPdssI+_(9?FBJ zR{O;cAxAs|#uG8%mTs|q_SwfBp^cuYXL*Knk!g2!@2@1!N`5uTa~1nnp^~Xddf{Ho zBEx<1|I-K?>Ue-~qkt8J#(jo>;J2s-+j}hJhsGL$@sn_eFi60nF-bs3gX;*yfV`E3 zSh@=o8J4F!(R-PuY3D{lIFkM%;WPoVK^_YOm>>wKXExzvxdZ}!Sh|7xRsS&iL2U5iO3HFn>W^` zrH`m$zEYc*vNsniJ7b;cv;Fp-SnQXT<+^OXx!Xi9|4lOa?xuykp8nteJdwFw6|om8 z3w;$W(F@K-smExs#3%pzU$>JM`~u^D_`w~lSreT_px89^PgM>x)qo(B1i}y&YsO$% zp3ich=} ze|%nL;c2Wv#K#`8t^4oYZ@Meb+s}6?`Rywh)U0tp18y1aein&ox^4q^<|}SAdn@x*|a#Ck6Vkf`LQ8b6f~&P>8sL*r%}D-mRp?6`#ucB?HhfJ zFqY)8#~$-UYlz;5m$U-A7JDn!^zG{Oyvj1K1GLwZK$PEp@WBVa>sd9IRYt~5zh_py zN%6cPGnuOQ+P81-&+!Q1!O~;Ge*N?y*M zs%YO-r8`xFIkC{}qb`641`Kc~9DK0v`rw6zm}tJA+iKHIU1Pocf2x0*E+{Xm0FXv% z2nbJ?7^I3WvYkbKcHKz}gvrFus?c<$kS^nae5Ijqn1>nXLoAzSX1Xm@*)&qu*bh^u z`Z%5(KHML%K-EzmumJ6;INQpUOhp1go2ako>L{$kbLR2A-QoLh<2N+D^4xv3x^`{9 z;V8ShKXPk-H+${^m)W+d*U@4PYOW(I&%?JID;?@_Yg&5N4x6fB*E^$buv1Rx?e^cJ ziyFff3e(InJw00%oCj|{R=U^o(z81DsGb^I)Jb2X^0Q$xy>jZB!Ox&$ecVp_if!F_ zL+v8pN-9k}+y7m4n7T>zktC9}S^Jjm{8Kc+L;HzY#a>->Pg#T5P^RO+%`83K3+0M# zE6pnL4VK-Xt>=RKdFP$ytpZPLrE`tNa4dqxm<~t@1E%>>w>Vi31`>p@U>;=0y1bYT zM0!{i*cO2>%noAx5Ejkjh@R+%6 zEPtO!-}{S%t`ZSZwPtN!{yBg!Q%}YHSvO{WA-KjOhlIh}6Bpv5{FOwAhjNOwQC7ZjMTtM)IWyJL$;ymcxaS0z@b>>-t7rfB!|Sa4U{LC?Gu(gyRGn zc=EDX7$Qs;$Wx+H(a9PL@f4;9iylH@WCrsYU*1Ovj|t0#Qv?XbRYEsmnqWHA>1u*| zLDyFESi~R8_wDlcfRHIn6$;W11&1&j2t9-|1jOP={9B8Ei%oubsURT3H9LVWr*pMz zcW03ozF6MZm*usxtQ@Lt0A4&u4lh41E){I9USf`U<3b=u(Y0FX>24vbp6VggQT)HD z@}4bX!gd*4qaU5L@lV`oa`P2SR0$CV(zFUQzT)VpuDD%gg`(nEphkj?RT=-o0BXY7 z7@&CNRIQ%Bx|bL~vpxfmWeKWNSGkuP!iJ^|wV~;zvih*dAjGB7I`W|FjT_5N&j7bN zveFRF@8Ok`?OGBSRw0FFBUHxpu(7H4PrM>J>$lX`D>~P8zgepC&{*pTkq6?eC4yB; zV~8v->#U<|awiQoU}dvZyXG@njeaP9kOj(&5eANwN!$_s(-qdOTem#&gMY?yAag{( ztubTZD`Z70!TL=w=_;Yigf2GXL5SBHO^yb;jvq)5|9LMUQx-qhNSl0bE-NfsDiB_a z6mrSun`_nf9q@NizCW7L=U=LK-+lLe4PM?~ix;M%-=cKJ^`iVF=bUGl-}5MGJj5H7 z)fBo&WjTBvw*iT*EjTSoxMvzis#3uJ(GfCr++U#Eg>I_h!YlzX%Lb&OD8VoOS_-=f zrwOF_me4}LOs20nKQ2axG9y03Ra_yDSaEn^wiC$bl>(L^Ug+b5T?PCw-sepL=4v|A zX_*Vp0%D5p=mI-JxIr*|>NHQthY_yJbv{HOuNGdXK~P+=1kEEG>Ff5qn{IM%Yuv*O z4Wyi+uhr)?g>!!`*S-!*xT`{*p^BdA?|#rTFSf$1BcjAuo9PNOTNa~7UUGIA zuR($zj~nNAdw=bN4}5o3eSN)-((cG})aTwn1m+cuX&}9s0^L~|5h-Fk5%JRyVP$$l z3vGzH#^|!Md*Z&Tn?OK}Tv*A1pIi zc(Cly0MbQ8T%0rh2eI_5apanjoF7!SnW~E=h$E}mGEkO5wfKQxJ@n8+-V(H-EUQet zCU2}$M0`OC)_}S!tT4Zz?nCvoGc&sISziUmR`eBnr;7GjxDWEEyr!ruLQFo7e3$fL zGE{@Kb_Kjbw4De6mTa+9q5AO&NAIJ{E;OrlS@xl_Jvc&X7 z_tHaACa%+w?k{xEOMvW<_m7361fe|{$<9Qm0;85A$F1H(1N#C#8U7M(qW&riNnjU(U z#yK#y=>7o%+_M@tw5!HP9I5dUM=PxsQJ2738uW}siUwvgrTMtVTpXwI6?AuX)}YX1 z4>-WJl;7tyNc8gCZ+A@;Z)cIDOp!QWVybB)V3j2VCN7Ep3CH*~^UqBu1<4~btH-mt zue|UvPswia|M7EzB>#^^Q)N$$aP;oo+mE}UiwT0k0AtE61chnJSy@@0U~r9v)v(Ag z=H?xZH@oYuyL@*TRw0NEr0F7U_{;h(#~gEv)AU3fiN^ePaXFY zutJO!-V-1Nh&94SA``9&qQte$1sZ2$MdGkp)no)Dx>R8Y)7N22agH!9N}2AaX~HwY zHNs^Ad3-?_B%qUp0(vUXywhf*ubINu0#+xhtjRls4B@5c`~iXM_8fH@1Pz$d^0c8& z*bI%5qjvny+z&o@NSl(bT(*39M!fN291dMgOfx3}DteCmr%}M-16e|>0_Pu7%oUfv zMkByrKK#?Q#W`^j7hO|S?leBcNf&^2hVxMYe!Y6DzWVf*^q?ey$rin=`boW_h&UsoHIMre@o0APbLe zYvNTzIf0aLM0ezibB^(I%1rz{Au#_bYt}txZV(VGFpzf2C+GOZoia{bRz59Wu5-?H zrq3UL{PAw^;K6xeDdU9qdXrzmdy4o%-aHPktrKRd@YY7E;Qy zVlOCL_D-B94$8q=_DMtWu&p@odpYiUz&^s1<~% zvmh#EttaBMI8FZ@&Uh{eB$Vs9_pD`;?K9)WD4bGo`_2zLsI z-B;pY)&8}2{Yqu?5n;AaF8Q~DSt4~nQm8~B5mdNTteyy|!sR7|oIpqb6*j_KLM2~Y z{%Ht&C9dlT?tXwY4M6lCcf$I`3UN~!kQplTIN^cp#064B`gDDf7vd%!8U_3kAMV6c zU4{I6MR|QE?gA&EoAvO1q8rRR*SMVfpZLIi@y#^9#*beg2!%)S`0!DfaQH_9lvT<( zWrnh3WheQZa?Q0kcuxo$7oLX^6~bVp!Hsl`Bv~4S;2z`ga&GD6m!>Y4NKBXLoNg}!OHf1M zH~}lvTLQ#~v_V;y0YzE;#U6Hv?}Q2f62g%ekIL4B|7bf|Z+FVeuM>;op|{(KlvS#^M{|j zva080V)5A#_xL&gvD`qIuD||zZ}oxTAokwG{U@hketdxNQ=|q)jT&VS=hnf42hWgI zFkQqoC$8fnBPnMOURS9(m+5CHGjAq~*Q5azH~*8dT3T?0*T6hO>lEgo6c$ z*xmxNi_lG=u`Unmt)eYZXzQpE$unO7AeXgxbVN9EZ9Ad10D%}Gd?TDMaF2j|bFagN zXN3C&2+_+ze*y7dJ5k)EY3cbi_21IOZ)pWW)>7ytTq;~I)D^IhzbB9eX@em7Xn==U zBBAocvJw}pKEA=#wZep#G*rHYX{hjPUVj6A%PNnkZJRfQ@ME8a2__jRm8ByD_4PEgHKKTkJ+fz>X-ffv6x&njj#( z-QEBB?)(18vRru&?v5+J-{+p&-PzeG&+g7V@67DX$bMQot5+!Y9)J2{O+xs&#!FSx zQWHaho_lJ(5e? zUzb>ci(3N?BBoj0n%xN|dY)ta(e1QRg>_US;g54}`7;pmq?Vd_bCv{M2LCzu^tH=_ zcC8u*$NlbsaDu|07Ay;BgVb0*ysEL*-NIOcbc1oI~TIasuK zWpK&$&j;6CJT&-KEkKv`RA-&?_$mnQ`$9p6vIfW71Sr^(OmSO%i+ z+m201xsRQSkqYGv4QRhmvt{3@*}bzh4vb23hZbHcJ9FqN)xi6l8*aGaET!kOWLimc zNgD-+6&~=#{`bPI0-_A-Akaldix5`a{F(6SdE}8tK6uC>hjeSB=}@%#dhqVM?=HFj z{`+Y)LA+oHpI?N#gy#hYjZ;S=V&aAyEj)|`rK{<8!YbiG0r7K&aP+CCo_d#7ch^D) zJBJI{YH`~iB#JmULD(LV(m>bKDn(k&2oI6ABksMWq0DSk7C&?&Rns_{Z#6S=PfgX& zG7&tr8#IPsZ+%jGu?Alvr&^^ft=FH#Cb|_R`?4f|T;^!f z%4{uplA&c%t`Qq!V#_s@C_%BvtOAA@D&>2Ge+je%F+QfZfKgc&mznTssjnR0y6m#c z>gj7pRX17L`f@K#b2l~AGBN+qk}u13Z(kH>nQANCAUrA1{Y8aFIJp^l#Lw3P-Cpkq zRrNLI3@wXt@Ksk`^*kRXsE$+TNLG$!{!ZU#pMBb@#rEydqesI5*A-R(!6%}U7A9JM zFbl&tPq_O3d>P2=rfz)^Ei`iF*XDs*F5->*?+?a3@<=d2bMEk2`*~XOg2@CCGYm{- z;=%=*VHtsAbMlP5?6Pp25TXb_48HtHGm#>YY#{R15xYy2fpm@AsmaXA5-{28H-t-7 zFoW|N&9YpLNe0*JUDX(#+BK_&<9ulOsmFwcn!u22YHDc<#1hL?u%yN6)$4-=ia9JMBqVGEcrIG@Ac)^vy&%JuB0#MvXfn4i8CeeAK^os ze7%S8(fm>ODcb81VUhWXN+2aEMFD3B2&#`I!big00u}%M0_Riy5iY+eo9>tSy;LnQ zTK=)-2F=lxt{>i&lA?9v(4j+vqmMrNa`_!CKD%n{)JeJs;e)Lg5EKZsynr7J;gqE? zHcI$Z(Q-4_Nr-ZvHY(0c;?#Jt3=K-(AR(rLI`Nsm?pa~QpVir&6viw8v*$8#-&j$0=PrwIq=sv}(CNQFR9#=Y1oI_=5UaG(xj6&Qz*Y?KVo+x0XqN5~>PETq%{Sj< z{7)K#peg%`+lYg~&BUp$t~3dsYZd1?#G7n=Dy=Us=|TWe@nf`&!Fz)9s|$)-_{_etPSd9d(#~$XC)fNAqvd3UhADmM!m-8)61gHXua|mcMQS z<8N{_*Vk-qYiT)~9%_}#(KHjp5#AfhR;8g5>-%Z0uYU4ZS!pTk{j!Iwm99@zs@icY zJOkULxak7pB}AxfOuq1gv?4pRH{&*Y!GhpstyaxLc9zulyIP5wB_}WzEM;=Td$mC0 z)mjs*vnFDDTx$>gO@fJ4+1+wy0HLDhnxJF5Cc()g_6{z(?wMeB36^nMS)J~#e!bfU z!_K@fSh93w@Z+o{!GOKn1>LneJ701=_v+-}|89FJ6g{7OK3m<4Il*qNcMi@usef?X zS@#78^zRs4apNn&3lE(cq*c=VHEI>&-nlsg&cId-Q28K$*#7aHbIzHe?u~miKU9V) zwN0w*(p5H6!)|rq_Bc?ksuIm+jWNyWovFE(mTM8+GZf}?$#99Vuoda{Bxzf&C4Dv+ zo3TI)(!El+QQ(|)-)J{i+on8f>6i>#(=L8r;8pjFa4%7-%q(rGs7Pa~H;^>f7j_ji zmv8kiwEd^zd|s_URhbPw8YlbIo$3bTy22W1;gXX|iPF+GnQy%D#x!}`fZswG+|WvPqA)gj9o)d1 zTOk>MxI!C~7g|#uV|8c=dQ7c1-8HTL*ILi$Of@%7&}!8UG!_4${rZJB9kt6R@z`=L zi7`q`UHnsPY-!P)U~uo=n-?A}bYd#d1&Ee#tuFDsd&lNM{{fxCMMGI*3-{_Zs)TNw zLkuO>@XFF+u<5}*eLDng+U^q6tW{M@AZTqTU85Tpv~Ig|F!+G3LB0Al!xoV|0i6or;Dz!@T!1- zge$jgU?99vDPI>h8EEK;T6h2gXt|p8{`U0K!7=L2YS*%5F#7Sw!$nRnQCV-XvzS z*NI03lla9+cqk_Q$vt8G5>1@vn3dqAlOq0;-hAT>I0LCLK!rpF#+NsL(Wldo2^VWU zwKF<(>QujL*RGk2@lY2?wkpCLegvAxQL953zT7wen50al#fTP(<;$0+Q#ntaI(6Rd zx8FWmo~fh}3RqeH#gDL|sKWT9oJxJ9aJ=xT@U8Hx0Kc@VoGVNco)Ngtgpqdhw6KLv zXW!HEEN3oRvgF!a~FU)cH}#5GW*52{-9|i;XoTm>Bt+xGy=s;7emh}9x>iLPXl#8mI037?v}=6qq4fThYq!ngn5f`Qb;QIEc@>g>Wvb zWZpDgaZc%K!gu-`utOvCE#vCOS^7` zWgunoL#4``KD#w*7R*+&9WO0U63svD%Oj=Ku&`}FD4v_*>+J881Snkq+g)FQA#>xM0wG-=Wto&R2#n#4+N z&8Y0MlW~|^dD&8l!y8o!Uoe5F(b~~8>A=@w3`j1F5ia3>EnFfFwMm$|#?l4OgQ7CR zN~4a{k4uE>Sh~7o7pfKL{SVdSk?gJ{O_pzqpJxZa32r=7bO&A_JSI#KT=!}=042pj|t&Af=Ca4w(9mBTJDTU-$#MJo@- z2q4x9BMvubU`H^Zg32Gh81b|W>PLz565}PGLu&yD2SlrdAx)JntwG>puG8E+A=uB) zXys9B5o3!8v%LSx-^`oxiXcD$@Z6Day8G@ErVF$XZHoXR{B&`eu89o^J9yxM2R2Ih zSiCqDr7`m*;bk!2kDh~S8hg}J!l$F$8ViURx-j#LzlF}9ATZYHdf`XGg^x%m3foHj z@MM|MsuMTF2@@Sgjhmdj(cu;`gIvXMNBFQs2-(;ko@&Mo#r=d1rQ}~I#1djGe2Pm+ zvOF2Z#DJbrHDD%ILucz2F!qG^-N`8Y!(aoVxiYNGsRYfU`?yKq*eTmDudO_r^O?tv z2_8FbmHjnxxKp@7m?=3&m#1R-S%}fa>*O;Vfa0uvqv> zm?tpc8mlF=6q+UX$7y?$fEgEwBmci$3~VKS21;#@PXxJfmOfLjTf6pF1z$lGL;SoQ z+m_(NKqD1Q^jsqP9s8eOyBg1w{@-cS=xe758{@@vE1wKxYvQ_9jJ-$>Ox^0aFQ3`Y zJy#O`%pjaLfByVcD!iGGgf{O3+rL9XGi`&kEgoy&S zEh30_n|rdG&|7#oXuh3MPJxcxW+EyGA%JU75{t5;`UefQnpD0?@P#ZigROUqeqJv&JTC={_* zPdMR(C#FxIo~a4Yv~r6%Cr(z7|I&Rqe2$%?!I{ghyzr7qDY3i7 zac=H-$nCm<{6>NgUeYSb^I2W!EA$Zh3kaC10wog9E#T`K1~xNs;sk*)HD=7V ze7$hFut+fRv@Oqkw63^DVa*g?6{7AsblcGkfU=#69=CPJL;jWRJ*3$-zt@= zq$Swqo>V9chu3DLXVl21d+va`-Q>ye8uzW5qV3Wrw{hOx{1&>7fa3?zOO zCIk+Hgx^krcesv#AhP<679IR76QY8K=Nfz^Fo7Ts``?651gtP3h;S$)NEhPk24Swi zcph`tu3KqgfSZz^ao!%5p%K0%U|y|?W|AXX$Te%( z@>O;JeCHedieez3@l#xESol2qSEvQ(+Jg^1xN>dHLarvLEIs$>QVM1EnU9i&;!#Ez z;2o;E*|TQ{4?q0y7mq#m7)|vI2E};t5QaO_sD!A%E)>QJR94ZlNr!F~oB`fj4Dpel z;g32k`b^t9zl+@YqHLD%4}mYU%?n}xaLEqUGMmnM0Vv$VK?|~EwsdQeHa=Y!NM{Ca-_4vEy!GLSvoE>*cE&b5 zD9jYF=!=kWDf$0tW5x_mA07-Z`It*#jAU1I41e!^$Iv^@7DC@FZt7l0NXiT-RML*06T){SDlp&zsk6|Fpv3g2;! ztTa6TiIgP%TiN5{C(4TN7c~P>u|i)vgQNEm5I!q}CBm-)V~3chr-4vAxur|(6@gYs zthBvDiBy&T|MsqzD)9qttb-B=)@0bdQ|s2}j5zSXefxClwtH7CQp$Q>EKW+dnDkJN zL`J%{X+TCH94;$f)Uqi0tCnBmwCsdX<)mM^84^8d%QawRh30Yj_=_)o{rsD6zPs&_ zM|fB%^ydl~4?k%PD+?UlDm(-6LToQBR(Xb2hwj~_OP7v9Mhz{>OShPw`5d~&^!$Zg zW3;O9%=0|TP)5Z+VYPtq{*=ad zC`?=#_-F5WsS`iOU4{fuPDBKueg_JJ{&LABN4IF&v_ZR;Eo-#ib=Po73tC(%Wo9CT zSj#IXTlIs!8rxhIsOaWl3I9<5v!9o#P~TkC%VSvTvz5LH(j{=T&k_a9&Wzv+fB89>n;N=LP`JtKmbWZK~#LcbsTO@5ZQ5F zdF7erocx7e?3iu%?CUEnS@`Trgr%_r4epwuInby3DUw{4eI0e8!e*1+Q zl!52p3Pcw_;obLtCBy(fhIvo3QizZ8f%+ryf~IhRd&!jnd)G^?__0){j~PBZZGfV$ZT9WaqfhJR&82}d!bMJ5__UT*dk@7>QlNx|x(aeVX*3b> z0BwGVc=~8T(uNa^FQTAF<%AQ`=_hrte8q}jv3}WVo2@3N^sm1A?w4_sCVjMI>C*XQ zCr@V!`<9>TLub!CD*MKF~FlpcUT&efkx6*S^K*SeYIkDmj(?QH15%($DaC9 zY}cw)tLA9VoV5B{=(nC0_>~|Ee-M~3=eg&37fg8DHVmEzJt>SfZ{-xWBGIbD^(*x3 z)2gGbg3V6XG9BwDPMrAhym|9x>&va#3J9?S7KefAA7PFdF(SQ;h<+;&UHsS$^leEp z5We@hM2Nf9Rh#RpT>QCH>I~SsUh2hF%W0W)sFvsMURv|{ww-$Ear(3sndv@&D zdZz{r>Pie{)~!{mVV#;a8`Z8^vvL(x4E5A~z$dp2B#;m{6!a{Bx=aQ9T8&fqRb5y5 z)IM0bX3ff_%a$!%vu@oAjWf!gyKv#6r(b>bht=!X(*((vT62U2!a5;pg;Dd->wMoC zh%yj9^K#&Mk3aLgz>S4wLOY?&kRe0bv~JzHMXg%3sz|g{u2ZK@L+v-z0BTL3o*C5D z^UjzY1QN%H8f@9JW#K}?%a<a5%w8bc_>0i-0ywl3W^G>*oGhm;=my3kB zxR+DH)GeK^v;dxad;aZoS=w5G=;Eg=-8E0EGf;R2>|HN*@l*Ju75)kea#~v$Cqy?{ z#+Wf2bVbYC79Kf=^(j79CbR!`xAq^wa(%m&fhl>M4GK^X}>-W}=EKcQXFRqq*a({@kE|eeRiBB8s%QTnwVyMJkS}`(?Dd z($6%bqv$U(miTgEb^a|r%A=Ec`n)L{%{C7yD0|$LDYyg#fq;<3{U5SOGzuBAb%OvU z;QMBws(Nde;?}lnk4g|Mu4`o^C?{qb@QDYZ72TK$bDNt zGymsZ|6glH+ko<$j(phtfWLRaioKEYIn?c-XBf&B)Xt$SEgyWvBKY1QM_}F0?Vqr6 zX+h!3arSf*#SQF#X6L;$*6xcXt!FdM$iV#PTWZB6-+)1ljSkxeZ=}95JiDdP8K#T> zXN0Yz!tB(vJUe&Sx`4yvKE${5*N2OznI4$aQYgM}z9G!H-nK4WaOtlOQ*;_p|qGu1bhbsi0qv*~q~#^y&QJ6Tj$&5tM^DqTy$AnYkG(qd}wBVu0V!feHCk?8bP{;(gH#Ib0?VoiYT7mT@shOmm1uBhhUHL&F?S8&7 z^c!x|%xSz?tCPJw@$~&SF2sgM4EyYKN^o@ha^?mS;2bk(hYH9Gg)$~JF`vie!whnJh|W2C_(8Mx^0OcEV& z(GlKAJ|tUy=divZ_chlE$r*_NA{Q}_=ms^trOYTiVu)y$U?Jg{UQc55B3`P&r zLNnokEG~ey7~@kwTT@Tdc*iKPa4QgP$hFHHq!R3tEGh`T4eXZ+*)$1U__XYl)+W>7 zLoU21Z~%_0oH5Zu9CULP_AR!mHMh!P9ZIHMohW{ed>hOR9bDC>)s zBwV?r3_M4=!amq1FQk5yW0FR*Cat&0*tO>#7Wmd62Ym%WG1p{Ui-8n3WN077M6sI9 znKb<%Iit7)J7SFZMaxpcze_RAALy#3v3e4J;~tvwktc7XHe*b@q#!Q~kjX4j5l*Z5&GLzb?sx1cR^cGn>+JZv0&cr!@y?F$s^ zUtUBZVh*|H$A0%Q4Eh3R0ApkdfVwHQB$4 zWhv()9*QyW2&+>Nqw*1B9i*70k=<^fwa4$MpMQ&GQ4;$=W1$vff9Dpt47xT>J5Wv2 z1*pzOR4-M19>G2in=Iw@{5=E>p|y2gZdsC^- zNX<6+uCg5i`I;TVmX*bPLL!>`IE#Ci{f!cmbv_F3K!Uw_DCmzE${Ts}zjR57sxS-| zBU9x?!rG`hKjvDAUTS`W;3RK+__9P1CiLd~b(pU*&RToEthg$XA0k3k-f>v~W&taE zlOrjuZj!TDghYUr56ZiVw%D?TF)NqT86Q<(Aa|Kencip~bxXK?lD)OT<;FnC7#K1` z)?F}i4vj%WJH*IlkSV}3xWj~|DNX9iKG+R1eN7@dbVWpTj1Yt`g6C@gB9=kHzQxjH zXT-NGCnVA&GvGq_4SWc}1+Z~KVU^V?48L?SSFYqljTrbOa6fv4EQ4W0%Ti%J=}^~= zKvvj$S!13-wYrd&2Yj_~4~%^)C$w8aiY8_FY5uSoCF===XeEFR>7u#-FlNlIxdwQG zh+0f!InYb(FwX`O>F;AQ@kymmp@JJ6d=dn1xA!0RnR2edA}|Cx)y95m9XT z!o-x*#xd|}lh1IEt-6uwledbDCU)!)5eyGz6EMsBcnwl-Vw_~pgRXH39FLlV9BnfO z0{GVpp0G?jowhb|8=5^nh_6ltt{KL)COI{3?_siRnlNG==^s`HLNedX>6NF*8oTy5 z;L!4|$VN)^JBq#0Cl_766bieBu$i>7A3z93R~V8gO@8%=C>fpt#}9AzArv_yDZY&>LZt zMy@Oeb2oE_y|V#8s?q%`)llB~6iqcBA$2qI$evSjHs|Id(#HuytTVZcQyw+)S2AF3 z%#q&HT8xoz(zDH=p*t?L)#k5{M0P2{pnllU1-?YyU&1rFoNwr5CEIHCF@C|?DAvAK zGV&l%MqR+r%=k62s;e>6{v=sK?mzlVbb&r4A9kFgiCmG&Ww&%a4wE&ULmzpfvqX(# zn7TT#UR#GxXPRIbaHHCc!x8)&J>^!GS zs%I(EvxnE7eNYQyfqym5vnRLcSYq#v%P?yV7H)Z-<^liC4|{hOg4Z_Mx0pG@K0hb1 zs{o6&^lJ>lOZH46UP`%A^5<1o>knBYCm^jX27BM)?U5$Vj~dN{C{-UUtvn4od3VmU z>=kC4Ao}e`kgs`u^q5VP0X>p!_?<5+?Z)?)Tx1McP^fls8eio=9enIbO00 z+po8S_%SIkL0wlIj2hKf*A#ooqqrh>Bs}WA3>AG(eQOd$Xu`-QRwiXl$5u89H;!hK zEJ%q2gR@?U>YV$gN$%>z0e7(S%}nr|mZTZxBNNfzPLhMZEi3(pq6M%&_4J*^9LV?* z2|DHLKy&+np>|Q=6L)Zyh5vIYiu-mPM0G+Ap)375ouBsmd!p$}s5QqdB;!$TV=iS^ zKrTRBlXWf}$*~!DF9jMS(|NesbT@~LZ~a;o1H8DzoffcQvYt`8$=o;Mb{D*h|BwdV zVEGVKkTz9-v)V8DF{@wZ2VSk!ncFI@Ga&0Jv5*5TbW`l96LnW(=MwGoT&H0BA>0UU z?mNn#|7rTxWkAC1Wr}8E;Z{5yO)k03$rNA*dEkwX7^OjR*{h(ugd?N`qlw4Q*e93i z4O{loJkEHQBbWP;EJL&=3wwr|CU7b#n-CKu%j*%@Ih)^}N~2#dHfxHwbADYT*Zc=p z>Y&l|Q#A-;nZy!7|9`q~k#^|$R%sAvxpvNlzo2i*%4QlHyRbKMoC zX4|N-6W^N}fx)Wlp*-2YttxA+@FJ{2r-kSG?a~gd@X+tl9@XZWi1QKx7pqOCLmGM1 zAB|LJO$^@7qn$b6&lklF&%On3OAfKZS+7XYFCzh;AL zi&HM)MIb4eE1LT%Yk!|#Al}Y?yS~1@68n#5ckh%v!$kkBHOM2^+&nGTfRo!csHkJU zF+cxbuPrN#$u^JS@Fnp|l0i-}(hXEwF5t2D3XQnOl*~==oKPGvoHQ*7b41{?7=m+8 zhMMdWJG}1w#(%qymdXNMYra$CASN8BzEDbLs6gYqXK;t*WOa~c*CkjB7u9{_$odR* zaQDA|`TGaxY_GVuA5XwwHxDPs^6PDh(Bkk?n+!9!WU#?FIqULLVZu^|xrK<(uRUD} zO>WpJgnR1_zmGqeCp*gSZ~oV%{gWCthoLOl$i{WKsrUlcb^s5(QU#F{^;b@_x{3SB z&EDbAgFP@?enNB`P#NBh0yKIF-#~TL%j8LFnkJONyVRP7#tjE+|8m0q@szdssW?Zp51&q4UK}8K~c0~`2_>`aF+J-{a z85aDI$MPO%f}KDf_3wYj z>2mHrzvO=URbs+wju2_0A}g8Xlr2@RwhV$cD<%FbP+wV-Uw2}DgNF3I8YW@;m8_tG zl6RK!>o4~_e)4oHCVKZAhm53n9Ag?64pxp4?k`Efw89*tz-f5@ifsJPfE~deybbrF zfRF|L8CU9pa8G>(ZS1eh+DxY5v?x1gDc4!D$x}PhA1dH!>voq{dXwXrdZ>@^bH%&9 zJP_L0k`@#MzS33mPf_UWsx-e=B!jUN51S|irNQq4)O-E``&L49*j zXnOazXaZ6h@<^wOfx%wVU?+>#ZfWE%q)^zQIHj8t&N<0lj_N)=i5s(dyqi)j#tL?C zoVGiF75kq(!2qAoYRAIxP6;nMu?-`tIn(g% zB=7ZpVnqDoiaVT-LzRpgMj$3UN5(P8uA0Cc`H@!SCVv>YI7;R}+wFeWSu*e)M)=Cw z;geI&Mr7wI`o0#ifS3ZB~P)!tarnpokPo;}CDM#}I!!bc}0bkyLLy>p;dx4G2UA|BVtn z-6)Rm@Qcgg8jI8$wtvAq{{5qmUNMLqM&!x5){)s=bXO8f#IypF>d_g08a+3)V=3PDJs`Sl}jOTr;H< zl$KcyVzJW~-^^pQd^{2~cEHyOJIu<}$n1>|XbV+_b=*9g`;EGsLNZbXspq)`sljH=|m7SwJj3>`M+VQ{E6fsnJ#4`)U}j83!yvTolcs}esCm~x zToA81WRtLGPzrz*-v}3K&Xv(DGKH@E#P4!tt_|gSgHI-noMB5*AOb^A;>Pv)G1@PinP!yaMD_N}vce z^WbB5kub%+>LwYW(+k&W0lqqG7T1^GI2VD-D$Kp>0lneyBk9kyYhP>c13!L()w!Db zzphTsayeD5Ux(mhMrpsXqJI;pDwT08XPs&@&@ts;J+lVjR4C>cs)v(pb7%z ziz1aoh1llxQZ5yF{&T+N@d#LY1h*IV4nZIkS~wy&$p{O$G145_j3nvq#U^D{O3>O2 z00%l9$*%P-&nzn2z4eUK`VlL*g4lU2Zvcl(rd7w9TiH8T9GhVdV(6`Wy8|pD*&4y6 zm69pWGaTSF$xEZmLtrgKC2$LjI06}_{KLy1MdSGEMAJ-?U@39`3;+o#pOfLB=Z~qO zRI0xwn>gIPgt4QAol=DPscz#=;*=|hT3E6<)C$%qsF4azCX(Cv$zxQI-XDFI*wCe) z6%wJINh81ti;YL?jr(AQLxG{Tr?MH1Zya*Vk;OC&@_ZATegMm*5056rc;n@^R}q9k zVrKIJbJsfTYo-gu8O1G|B@wO!9?wBriLB4G!?F)RP(c6)>xT3q4r)>u_gVnVyR52! zB|=HHZ_5q%0Bn@^_-WBKxFZlro2wDRAgSmq!*8)Uz3O&A?pqwEygTa~F zzD2=a2@=RXD_nu&j{#Sj#gub+M$e3rvyFFH93EZ97Y~6Lo8QJ_`~yJi;)DDfZ-uQ8Ot|UV;pvigU4nLLE{ylzL7|Qq_B3cKkCywsvojh7| z<8>l7$N6{$;+0wjswyD{>lUs5?6Y_HD88h3uVz=>frbC5`+?aDe7imfgWrdMEQPh< z8IRAQO|WaidHbv@r75*MJbcEd=j0UrHcbeSH> zv-}sy43dxHe_DrvOr9bmhZZt(LbG?G`<}V~q{7u~*c7AJhROkcb z4DJSQ3HS+RYyH{rK%6Fk4I)dt2R9h}%4G3y`TGLF+#|RPY)Z0BG;RnUiHuGPSl4oW zLu5vrbiT1^@Mpui^X%N5eYU^=pY!pmiEUm1)bg)?4RC^&7C?3`Jb@8C`uPCKG*gdg zk`0BXu=5qt@azvE8F@&dyH;5}GX3j)Pqr(2er_}`AQ>KYCGPo)-Q)XZRr!I9@-gkq z_kN?WP)5hm!Zqf28$Ek(o>s-+z29uMGxTfr2)g$Ng=iOjfcLz#=?V^d=pjVc_TQ68 z6ul?{I68_}rG2^neShD#2fxqh$SVd2{rA)Yd<@`!;`@J(%?NA;-UH} z)0HH_ro}CoetG%)QCQ{HRrHzK0ME!ZI{Org#EYGC62JdSh(DxMDr97 z-@O$6{KCbs-C?|Jyv#u3vvIS#cCAuEs8hCaJu+)2!*cKPv^!S+b2tH@I=WD4)acw( zR__>M)){=W;qoo(PEHMR^J&^?6Ct@DqL&fUpw>{-X$?U;5 zr&=<6W|z&C^z1wkWC{~f?{ah;(7RiZVk7yF5eohp8l?EX9Na@cImXREhPTuBQMt9R z;gjj#;ODQpoV8;YL;}=wQw{o1ysfpy+pjwrn619;g*;_;hx5!BNg0rWl@XG>Xmc8BVC0f1zXtgpK6(D7Oy*!_!M$|kuKR`&$~l!s83RI zpSwv&5;sG;APLw05K%4b)Y&A*GATt}^B#WRTd~yHdiTdKY=E8Kc%3$(@GfTAz zF`ekq);j)s1FUjc&qH0~zuFhiQuWn8)iw1#)e6w5iR;oP>ihVZ)CR1E$wTLT?H?IA zrg~}enOEu|4MuHKhzB2^34q@CfQor%uBD!P*1DP~u{sq&1?# z(t(%2PjA6z3gnUmzwUjn1vwzYe7H~Hl0(e=AU`>Cs^<73)2+hPcJH>hPZn@h?UoZ$ zKJWIEp8CWKIdeotfoz6^u5fag0cuAFSbJ#5H53lFstgcm*2<`!8_5*a^}eIwD^j{P z49`1>mC5#7M=0ad&6VnAK9T$I?8BGM4w4ZT9^~yW0Hn{k9T0=_%sA0F!u@?xmd5ln z6<#V2u6ZsLHtGj-_vKlOSL(N+-B;WYiq{?A9-mbJ2j@cxbc0*K8uKpW*27>kFG#N} zL+`~0p)-<_NuoB={JUB{VL99>E;9oW&m_kcrLjE z5Qnm7+-2WoV+g(FwM>cZ)ww{q0GJ|(%vtDerE|rVOUqJtFm)5H-mk$y<4!lLjF$Ql zzh^(fm%jg;MTWL4=QEDKObROtj_6U$-nYa63C)^?^n;jQhjh9wOU zX}mY|^PeIS1DqV$E$gm-uqx+tA3T3>M4*L-$I>rbMoOHfdg-J?tHmqQf0;3>lWocp2Iv|pDbY8@jBuk;t9}&~{ z01oUCcfmQow6iwUd)*w#!|EPO5y$yRfHn>#i!<-*F#pV=-P%UE^ATElB)waH`*;!OJ;@dmL|{C!{my{K0}{JayPkk7FMuCM+YGalPOF09>8Rl zAz?M55_mWdX_`!RuNv{D+ytD4S9K#BlIq5DXfycDb$sO?vl zU31NGPbX)9YuPTFh8ZvzJDgEaS{-MH9!zsQoS@zU^3aZ{N>Zq>y86v)6p1a?-O{>!p`ZXN3WP`mGWzHS(mXkWX0YWdW7Il znix^ICDO_ftkNP!QhqcrQ6Vw%-9lY+ zT#F9ASjvN>MYaCC2fFA^KpX6~o^G2n-w||$etG_P(V#!8zr8x8?BF-fJDpyq^LO<* z(Rhso0&K5dy&lYkvjNQBLll0qH#-tYLIn}!HoPg~EE0&In-g6nZJ`0?r# zBJ)@H9rLr>m1upJa5HEdsMNOgJ{wdRtX`hd*dK00j+g1d^8f6z0ysSLCf`G?3cG73h|q3B3j$v42ykGC|~QS5aoU{q$sEM$Nqijff4U(M^`uz47EkV4$+6M&oyw+_ScTa-`%wqLh#RfyyuEaDZ#7Tj zPLtVlZX_mOQkJ&{2#5>1QY~kHWIyJ0tMv)$kD<4|II+$RJX&%jWqUoQ81f-edMVU$ zuf#fch)T;sk`gcjhn^0W)$b450bgO3kWkA zQhz1-z%3hZ*`E`%TNCp9vtO#zOe2v1i(Q#sWmYXeH+K}~`<@KLI2lLDFfgUlPl$$R zjNRwQXF|NS$;GE6B2L3R$BVzxYRq+C2ZsEVI=@z1xDXyJxi!==$ccfd4TW(1RmL># z+`XlE;>9@sJ#IgEt0?u?&Qy(6mvQ2kI$_5N43T-y(e0AlVE^iC^@k10EUr$5PArY% z{^$EQ+mC$aYAja!5=8o&eGgZbwm(Gl&XOUIODW>3~3|G17d?@Yz_Toh1`t;of!ulHHVnBOY&x}`^wDXb+ zbv3Gm-AOMdP+Eqz#BET?v)X*TcJB`n^UGeSBBBnaH$vObgEvmE_OB)@C@kiRhUfxb zsrA0vQcN_C8y$21MIDVQ`AWG(dTYz4xI%gE`I9@P!C`NP)+>zv)9Z$ofihFiADm{= zA!pM%of7+^Eh3r^p({lrynHr(V$Y~D*k30dgpqz;B=kcqM-A=#F2uHS{^G^gL@zhr z;w6$5u-`1amK6JB%410CQ0tG0A%oBt_~uR1QY4M@R25M5IMPni5vzMHTw+vfLsJFR zAekQpWP}<~lPb@i<=PI%r_hgd$~9@^{lRB;awf()Vx=SQl3iSU6cVUirJ21{j7^7Q zb96*o_^d#yif#>NC+vBLames^=h0C+2C$FlETA!Gity9(Q4`-A8$bQJ#<08*k zbf_R`X8ks{BrDJH>MUKv!c!+{<8RB33wRqXzeBc2&HBYe!UWz?_#V3?`ToriuV31w zQba<}4|_mD-qvSlf)|iTh@d{MhlpHn=aieZHn;=MB0$D`*sDP1a0TcuWz{^2jzWU+ z-O8b&eoix6z`}}zYgRkYi|3F&RXq!@JIxTRdfOrsML^>X>JR^ue*Dy$Efn>`so2$1 zz;a|If^dHoWD$3MiI5{m{B&Mz95-@AGhPd*N)M) zues@O1uAD|JEZ&%te|QI$_afjKk7?g7B>`+{cef%EG&8?ZKqwqS=HwL!@6;C;cOh2 z6h-0?YiZ9_W~U}j8ywxU^#ImUE!*`Iqu3kIcG5wd1UE0wWwFDF#T_E?Qs5yYO!oba zy2VJ9Md#9Y_S`ldtFWBg-P$4QO%|rj5W3?e@`ZGhP4WDo*#@WFyX7eQ{pZ}etJ{~v z6_vtUJYR^p82SQ>?7IT0Td_I^Vk|~*N>-q&dyC*}{AJ91QMiHI$96^J;EQWGr;0R> zLkq#ovlCPQ%dPIUPr4B$<^jB=CZ7$eEpnyh;@C8h6C{^)RKrn_mF^X}IO*4{?+QUo z`Ciy)J?N?-lDuLceGb1^clQHS41pK(K5``!6%lw^(qeXcoxyp<8;a)dh+NUSuk7zF zghl+cZ+3I-3Q_z?^MM|BK$OJ#QUkN1b@|(cT?o_lLA(mjb%4vHn(DOa#G7p|ouP z4*mN~2agarMD`P9mZ0|rFARayrI0(wYoMC@+@wQ)q~QPk9c$gMhb6>f_L+x@hVx2! zjVv?9k@GrK?2!R-ZfAKn=ZhgK`n+);D*@$jnwKgSa+xW77Uo+(U?fQE_6f%$n0o_$ z*|(%(Zbj*^T-GKOF|Of{73`roSvu+ri|I+(wmgs7hCyB8hP3#rlTphCMGR!=cTEX| zeDp*zegciWqAPFSDFQpppkUaOQ0W|Mol@s#v+)w?yl?XMNC;Wq(;Ksu zVw6indQK7>=72gHAMgH2EBpQDL!Cr_Je8m+DeaR|quS!K?DE&+1UPwvslxHA0J)eI z{&*`1r?P|Ezz`(rM+;#r314Ezng@IhX*-QO@ES$SQ@^) z>6svDNeVV@g6_(PTd!9Pt5NSRl<8MzVufziUgVXrosCp|(0-lPwe*-#8`{u7@2!id zjA@Nb<6jyKZW@=VEPiS`zC8|`fe6I04t6}>Am*>W!UP!H38o<3f~x6J~`(1`GbO!e>C* zWO1utzbU(Jh{X!uq4;Su^kPm2bIOL_`XXjyH0eG)Cl6#=4hF7eq;<>7NSt=e7Mrx> zK4T7&PWYedY#WeW`1-{bJ8j+pHEwhTF4D9fQiP|cCJ4y0_ami|e-XbyN_02_gE=vT z?s={8b)#kzPkmLb{9B0Vr2{vx>3o+AmMxpdAt_)Nym(@+?^g;tdTvSl&mUS3G^kx0 z8u&SsV`9NTgWKbkk76O<*r2tds6dv5xpAg1Q2mHi(6dblqt>H=h!#NRP zC_J!iqiQ04%&Y2Yq4Qwux2fb7i5w22;zJO)6P6WAuk%VNLrnqAX6x|Y)p;+DRr--* zIK$L;MAe5@*w@t%>r>WqfZU>PmG_}6HF~e%V5fQT#Z!v8z~h}~;}03lVX3cP_Ar?e ze@Tdi_VwYD7=u3}lo8DUGgrZqQhw=A3?d8wi`Q=Pzd=kSVc zt!PXeN~h5`4AKB*G<;VfQg_6l{xKzd#$nIC+<*xK$0ge6WZ!Tq3-5+yHQ}nCzCtB* zXD`iwaRn)3A6Js(I{#1R<6A;l7&>-w61_|g`0vPQqI655aWN@+mcGpgwoxL5bemS_fIX>Juh{p!Yz$o+sq|Ho7~j_5edC05cwTE)s{UZ{b$qp9m-MsO;lVQ_^Ln&O z{-szd^s~?e5)K^t_~R@9ANf>NuSrgJZjQ;6ZK))-+MJRB$Y)wNszA>>9XnGGibH>I z%bSw39~x!N@EQmn&MMWky^H_8uUeyIN)8(0dCg?6(QPLur@|nhK0LfaO2&=C!&H75 zb+>%+Ca4$OZ<;LHedkpBv1xgwsp&8!+o^D@${mYK9G!&FGirEybh+jDmqf{jbunEE z-A|kQv5PbcYQGdc$EJMR^7U(YW>9k1Ucj>E zGCQLKzAGK4wj3V?gg5~M{uUQ&k32?a+*RgSfE254%#^2%4G>k$a?K5$Mz}K#?hyZy~YJD^nUD$OcA2~ zkzvGFN@^_P&a3b2a(6jW=q=4cxjr27ukjObnMm0@E!=+1)h9z#-97%5^A>_K99n*F zIs#(hm8N2X9?8;2I5H*;q(!eYQ|j$?CvF4&nwf2h>!V)1=aR@5jYnqucm%T|<3d`8 z4x899Tr*tjq70y`)EHC+mNLtVuyR=+itr7L{2=#SQJ&X(DB75KmUARqV$i;+e24JF=l&ib^ORXn zvrM;5TAI}sP_&_Y4be5F4p z?>)&{&OhsuLmI18vXsMk!ELLEef!wEgMs%alAXuov_~*INtKRL*r92pW2aP+?)l>l zFO6U<_Opoe73yV$DNlPdhU=%BJZ;QSn{}6PT>8pY|ImZ{`VGou`DM;9Ftyd-Put~% z#80Ifw8NJkZ)9&F0xmc>0BiddCS1!J?e1V$Z_QQ3EZyzCX6FcqSO(9O) zqWww0T-tqFs~#r^ZnH;GB%Mbu$(9>fq8yjQc`*zdbtLw3x#t*n`P4}_ z$>kpe@o4{MqULd~WTu6K$OPoWb}D97TJtShk|x}$BjP3B?PDT|kkBw83nqq{7Vryj zUFmDcrUCMnfg$Z1`y|D6^*3F9s>9^^ctblJgmAd-CPu7wg zYSZ)cPHdNsyWoS#etM!l>1)^O+V#uks~Gn1kTSAp^1N9(e-7W1#XG`{Q!3u`Eug^v z!#K4fMY?o9{;C3om(+HtHj3x+Pbz41iSA!>jf-XXq&soWrSo=Br`VqVOU10T8{7FE zK6)M*4U~KY4(Vu|N}^|fsymnas8-{v(6-p{oXm8#Q0nw2fbf=OGJb2!-h}JrHkPqt z6FFl~%0$8*wXL<$Oy<1j44sDqK85N~W#%EPVpxBXr4lO# z9~$C)FFcx5Xp+z4jtCqgdra}`AtV(iFCSE}Clz-6=5MJ{;Kf&}(@v=?SFP()%>Cao z7{(JbcoMi-+%cXU86!>;@bu?geiu}mgLy4?c8GKIAxXhu^^)$c_A_v5jITDY^k3;C zm0`VPs{JCPW-w8w_Q^uWLp^?7if7RvStpS9zyeN?>7Fy@$f_Q zYNWTANpfWx_3?fz)Sp--)@K}XXF{IfCtbTia&$Y8b$+C-|!!KNVz?H_5M@cEmX^Rhn&}VtNQ6osZMQu z(et`zX06TC!8qYylFq!hvfTaV@*1!&2YFBhXq<)Z{%_u3obyJc2S`5VvLBx0t-vjs zyk2`?Ldr&tZI4`O>p}}gJzda6Y398{W(ysAQS>+B9*oL0>2?*smKTp6<&4RUgwm@; zG*f&{LE*HPxmFy!k;Nld@)2Y#{hTBt9TdIK(iI~^A#zwD%)#&r;-oC)v zc- ze}b#rxd@{4o^iIcatzu1%pZ@+N5i4~p=49IflZGm+j+YXfEH28t(8eGdo51`SD)s$Z;U zF5U85h0}Y{Ss82`Q2cyS(!~cLYXD>W8)cs4$8nbOCV%h!x!CCyv5&k^CrS4J#UYar+17%cCNY7-S(FS z-YYLGFyj%lPhP1s(L!LP%3MlV?-U4TmMkiaM0Gxy6e+W6u`cL{YUF*9QB32CCvkqr z2G$!f#TJ2g3maTGHp((ff3~F*6yo!H>OjQ_MS-LY=O|Qm+dkH}MFNszhZ?`6&gYz0 z`;$5^^CAcf$iN>|a+cr*$=GlaAXRq;mpy;s6gE+Ia4}4-a}q9_I!Ozwb4{?^INckn zB{KCI*S}#2rM3vZ_;XX=)@Rld1-A_(7bfa;%Ig0+YMPCg8}*UiiK}utLdOovuHJ9+ za;Jx!-hAeVe+^Y@ZDtGdmTP%b2d{bl;A%07@5Dt9hqii1dilX-(8-QXd*R4Z=P{(g2bd$!?5L1<$G904`im#4Z@ZeRBoF~H zKAg=a{36S0=2PXzYS9*C{hv8gQqw6&Ma&ykPeTbL4!RC%y%mZ1B#&?7X~l)E^%c)k z$w2WmD=lB33(8trE7lrnc$p5PSOhB+q4%}r4`tA4-szP8PH8r%E(pFWH_CTdA1B=th=DVjCc-Os^v)^c?iZzd|ggw7Y;70Ks61GGjS{*S46@m~$s_w*akj zi>mRJySxwc=LRocR1+?jHK#WDjCp`1D(d%ZMm&BrxvkQT3d4d5oK&YAp32YglPbm( zf>9xvy-9};Z-hw}OxyEXMwiuo><$^uWjj9>iyUOm`thcS6+x|$8+5r}15|Z&UVl-y z&ck=xQ}x0sz}H^pAuV;~FhrdiYp7f^{Ql)l{fA*yy7FP7<7nH3)#%2rcKgX}pUU1S z8XT%0HP`XS?``83Yuw+ivWeTMP4nlysB#@hlBwrH$Y8Nnhtp=4Sks1N?UYZrcAUKo z)b>cg`?7N!C*%KEJ z4@)9ChBR@AQr;gl%L~$sgIcq@@iNlbT!5)-e_CZVS4dK!g+0Ih-MQN0_N#SIfIYUE z>CzU+#{W$hum-PKRzt`p&#v{~N(|u)(XTrA;}9U8f~@QxY81NI4CT!0@=8mwegwr- z{ItL&5s@z~Q*_Cd4iZK|^!H$hu1j9o`0dSJ zv^5;M9v!_f^ic~DGUG(#2YzB$33Dox4wPoE+b!{I4!FmSI=u{~j(e6I8yOo5j)*NS zjX$u@&vQ4Vyj6eElVtqKqqN%h``wF-Ee4`O({8@iAP9e5+i<=09|9(D;hfyCWqj(I zsIrO2DmDJOlM@+xd+%pA`S*uWYHxJXL%0L^wEY?>pd`;}|4^#t6;AOSD6pcwsbi z>ZS0m8o!*CQ~Yxyw;v|Yl&EEO4xVP$vrsv@%^klQwzkr>JM6*xs__R?WHmLh{bM}l z%L3kjw?Bs(Uhgf}AgxT0-}b1He^SXX^%&!=lR1aUcQV*Jo{!f3LkPiv?l}o%v)1JQ zdRdOFIJ?Ob)0^pT%MDqmdxrjjlMlS@T@YWOzUElbD09h?_DO3dy)WXFaBEwnJ0h(> z?HRfj@*<#goI_#@(5no(a@|d;&q$8+{TgE;m%>x%F;Q-oS?pYU_v23PbbD+|#OHTG z*i1>iZC2L{Q&yN3=Q|oq$HQimD`AWl%h+)s7}o;5Zd?q$;{JI)l@AD-ive7)^E}Ty zE%d#tZPjxmspNdWUtb&tI0n?e$sHEe-(UyIf^=v~BPxL$v$Cc7C6Jkz*acdcAOF(p zOFw_6m|Z%NR}PQ`3G1Tg+J_0smClVZ>f}?I3f$|K24c8G0rjFhzmur)*2I0Muvn!zT6xru9==pT`%{kX^Fz4+}e$V zcxIE%2BHvZ0-CLn6}SS@^|{5(B4Df7Z0xt5@Nl=mQk}X7HGf?Ruc?QW~^00*YC0L_8@<3+DGw8&-4=|h0J;G9o(WRCuAiCcJVbW`!4v(>7aY< zd$(lJpVLU`pj>`i&FaAMvZcbiTM^%l)ubM@pMle-+i=|O)Wx92=4QqZqQ^%HJ7gAV z?UxGyf`^5C9*dKa260J<9(Vv^6c88_M16c(QR<7({ic9|u=V+7kEavUe+gHEAR0?+(Vf&=ah3*)=T%cPR0W4d?m8&d(~#YJR8bUh)c&!fNA zvLZg!j{dj8fDt#E!Z(;}s}7QFbFuTTnFvtWa>{kM1jXc@0Lat);3r?koxTU|R5XTD z3EEr>w{Z~%yZih<-F;<4RAINaq@<(_J%ofP4blt^N_R7qbT`sSgM=W+5JPt0c_gj>1Mcjp`4^2q z?uo^L9LdeIFHniFUZHsI_+MM^8tQ0mi8{HSG&FIG*lp`RU=7C$JtkMMY5!PO4j6M5 z2i~@xl3ejyPHl2ypJDb~@~`$kR6`aleYycJY{eMqzB(o`D2mns(|Q!A+!+J&zF~Oo zxI0~HmdkB@e=8gJJy&#SjMaj5i%=M1pL3eC+}fQA9^y54-&YoADWd=T355DNZ+q&1 zuk&O<4F=32@H02pHU7v$t7_;=Gz(NO|wZU+SAKEaqv3^;5LzA!ca&@91@9>~a{@m74lF#@0Jr}xG{ z64gv`q`07VX}|>6A1Qpvhgj5hP62_&7(1Iy+lN<;TeLb# zI?EX2p1Oq}x^Io%NiA%>JKyze-#q`-Y~G6_cAr@WOlzt-1(=(hz5{HcnA^hqpHtei zK?h{qf{gc3Gf-SnmN;+T9XCU#lgEsQ1=h(O&0rik8q2(sNQEIJBhP*^hC+J(S|I>R zY>)SDSADO8S3`Ih$%{s_59CC-cFoG$oRFHl`nqWNAzKO1MlVNiaJ{U5`Z$7szSjI; zULyU&9+!{#d(2L2cpxR-EX-`FE|G05VuAn<)XRROB|V!2g#t;luDP6fYGIs@*y`bN z3KknOK#`g8V;SN>5V?8e70+kWm+SCA4b^383N(t|N54W}aA_Hyy$yjCJPTZ8N#K+v z{vs*fU!r4|H3*MaYwLD{7|!4oU4@3$rhvtTe7_DqF5jcRQd`ie40T_q!#?ec_cV`u zGV$l~7kdh+PTq0%)^rGEGg&Dt`Mto38#@F7_dtQVxCK^7AXG-Ayb(CSa0SZ5Ba9#4 zi|Lki&-B^N5J!3l+T3ahgsM;&;hXTQEg@k}PUq}n`ww-(fg><}!!!=#>pavSENanU zZ~~_%654gwg>DuRZ2W>QrKYrt1n873~!-F!O#n*&iOGzCs3cT~+a@$s8$zGlgEs4s0xE^s1 z4ZK_=aTrgVEQOI+l-T7ELXS#t(yt=qY^8cduvHmf&;}hgo91;nSO37wjZoA0G}U>f z#jf2K%w02vS3+0d(y_dpM}eCVk{fY`c~1t6q?&39mP_JIBLu5iflG3YbcaHd>g|2lx+v})A@ugn zXkdo5Tr5o{dE0Gz6*EaqHb?Uqj1DUz6nLawr%=MYPnu2a1Tw&lD%3|kTup28YumC9 zY70wv3yJiq4U|pm`^;;^#1%@>ujGE#hJd~x>U{3RB?6JuleYwI&$jonLyh)`Siu2F!Xxk z7YS`Xz0J?v3Ji@E-3Zr+%@!YLfTF^9ByvK!Hjf9Jp*CN`T}Jh;hFdgccUzY!b2FZX zDzmt{XjVJm_6JZQ;w*Ysmm#wLrTvcm^TqXE$j>co+UcHHFza$uW29%6kbpU7z?|WN z0A?JDMF;3ftX0eT&rLGrjGr1Noy}mLYQgvcMdY@<=jmYQ39OX@5rWL!iKTRfA)F7V z`q^*XLn7*f3Sb9TWr$726Y>CRo^D=6bZ#)=$l2TU5g`t?4SOO_^#OHvO6WDXr>Y;+ zP!JhPl+VTNNO~NMde0Ejm1>`|=#BwX<{C9aBAVS;J|ZQ_T^>jgA|bFu77RPR?U$7C zEUs+j58s$_5zY;;hp!p@_Nny}zvI*%>AF_6>d{rs;#^|26RLo?z+k(8k`FsCI}$*$ z^PZba2iThY%Hd@pj4enmi^gY*9UwV&LyTO*%TdhVc(;XZ@4eTPIv($j#Os=A z=&>`}F7ECsj$($@-IwmJZk8pPrJ#?P`eR&~!Q)2PJ`3OkJ-T6^u9LOO=Q!_lW6wBA zf^AfdjCk(tj8}M7wUy~vSJ+2B=_D#EG@pFVOzWpwFKq?9Fs69VOzEVGt(CPJweHYX zbOODSV2LxXM6O;%WiB(-W0~mHNYb>wa?3~-;3kwURCP95B)5nEE?3fXOQsSu;rh)zuAHb!J#ee6RSh9VISrr<*#=( zuzM)fJxY&75SAzi9qRV5G^Nj{c{1HWO*w$B)NXc}=L!b;q+-Fl(R&s?pz0Wj=Iioq z3)r4?!(-Y6s!JY=)C)G6xPK72Z`N@FGbQobAYB?$@v|y7P| zop@WYJY6M_qhh~gh}h0}PrgQ3FMSyJmV7ZI->;eAlKd`lK(BAu@O+ycHsMw{aBin12oflCBRdk0!ULCSe7H zukegnRP(InY4)VYozJzmZ^d?_7>!B`5!)P`>lQ=;ff=kSgPEEthI-13aeOe(G}sl3 zdxDG+9~#FJ8fU1>Ys^9J(8tqc9Hf}o8DYP=L_0x6O)Qx;VBy7K){5px%D4wo!l#De z2B=b@luvQ6WaMNiY$XM6l}cFt5ZcV679`DQzX-$c%11r9SISXiW509zsw&rn9P`Q? z*KE>W%?!ev_r&ZMr|qtTyqqW}$d-Jz?U4SFrmiB0pvzhR(m+78@=(gu&H!iL%Ke{b+=T$bfq%MYgXmDu+ z`03##wkaeefRA9~+sKM)I)R}zD&zd13Fc^ec#+M}{_MYkuWnRm4PxJ`^P zcr0x~N=x6z-S-nMNqQ>vAqw~VYv#DMMb?6sr$&_|a>n&D6uP7Y`w8YNp-jlwvy9$eL3jMyf{%q{a$EupebhfQsI7-C#0(U@#b9t5z=CT@l*u+{O9xJe?RQ440>Cb2u!3lMGU*5$8R~5T7NX=cm z({r4*wU^ZLgw_X+X13G%_7Hu4rcrjfCF7u07z6Z>?v__!Zuc9cZK766Ef!kz2bZqy z)ZAL1*Eeqh6tUx~u-0j-!~1QpjrL`6krFm#8LwM_Csx6S8}kpF=J&WV1P2=*e&075 zV10@Yu_#vYRMtleyjfU1vKytm>B8;YvD37cwXEV5Hvd!by0Z{uvaO@I89fEH$=jTl z2+Jx}C#&F%Fch{}2Rmb<#?O~JZNZNsVAldnN=mnFi!!7u`;X-k?vX{2w8F+ltU(Lq zt%CQjL|y^fe`g&WYSSpe-sxbLn|KUI`+-mHw)bp#CJ<{hXr_3wZ6)Q?_Yo^Vim|URH2=afUE-4|)a-MON(U`rHj#p#?+JS~F)U%_ z$n$BZf`7|u2n5-8NVb}K*#QwaC~Cd>(|JT5a>>SY(m%zZ^AXuek2>Hzu#hJ%%oqNJ z3w-BqvzBKYLrHVJJ^m-$FBp+JPt)c_D6V9>4eNSV;?lief~U;ljwm0fcFwmm+5+++ z$L}|}q4{CZ>04wFc2Hp1#~p=AePv^Qp1=uFxt1sv{@0m|ZG*5|fs^Ey`kSNVtWZq< z2tkSEQL-mdNOi$BI+5o`n;DRn!%D7kX8!sLj(hmY z7kihZ*nzpyx+UbpyP;kJnbbi-W-Qz?M=1?*Y z@%ZK~kvRR4$DKQ*h1iHA;vvDS%!tLIig17nf=mX$QEV3qgkpY1B$c!~WvpqJwa7ztsuz zVL@j`P)$$9d?sbrUKceALfSW){GDo(W=^o2!w*mywl!A4CsgY0BQeDJ_Qd<+V@B$t zC&QNF@%A2|yP_HcsmF*wggJ_2*zt+km7mAZ58hSqM~I5uaI&;NodzN2$-#ok^nmg$ zNgoY^yg>7fzbP_=-fQl%-{BHj7!am}{MHU>VZu9blG1Pe-`bER}s+ z5$qy4w@=I}kcNGSQN`##r@=ee8YOuf2AA>_oVDUSo4x#&xDl=FHfu!AHci$>bT5xo z=!iNB*_gvHNDuT`eaj<*UeC?PIdS{j-WQ4X?#vkmdE%VqgAK-+n-a1{^#HErL-KLM;!(^5xDmszN{;0bjv9qVa6wDaB9Oz@w8nNtFp7 zz(K`%p&iHJ=OIxPMja$MO7#0j;iagd!H$Mzsa&cyY;=TA!hDk?smDb2Yr5r3{0{1` zKqfx)G@pW)kb77_w?;BB|8W>KpomaBil*wuT4hnFQ61UpQaEmqL!c&tu$ehUemys@ zJU@SF_R4t~ARI8JGeth-SY90Dxm81xg=gD#Id%o0c0Gn*FN6WiD1$!1uc_+U7G{$4 z_ImeAjdOXqlYQtz$=7yzWr?^8Zn&zNtylvJO`{i^a4#(I$kF;FuDR4C+@p^d-wG_x zxh>a@Ex#MJEk5|!HLQJWQyCLeb~+AJTug;Uh`d{Rw?`Axk1Hih7KlaX97V_TfYBA* zl@KN!Zq|j4(5E*Lv|*9m#26kYVw79-8Wq~osxG(Hs*AE6|L%P>j7dOyyQf{QLvP}R zg=bsMw1Lwa){FYqk|DQGX!0_sYX;R;Sc4px`+n~5f$X;V0z83Q+%BS>pH84FRC+9I z^JPl>4(J1^vTwvPd2}VXt2HU72gQyx_a+22^;ZX+aBeDZIPN zhfD8o?yqN8JBzb%u9scq$9FE<2|kZdpvWYX$Q=;6?;=&k{>-S?f1yGh7%;j2AS26AO|THV!z~>s`;) zGx9fj!Dc6JWCwnPy7d&{lUpU>j!%=HKn$F(Q@_(=)j;vU;;6xve9+#tGP^;YP2L$=y=r?FkgiqrMByNkh|Zbfpm6{bZU0%+k&0}Snk|P; z9Q|dqQv2@Z$lj2uO*iqh8MzkAh?7iNIE?XfhevSNKjG$DxM&CW%v``Y+mBDj_TG?r z&G-FIS~uRH@B7D(gW3*W2~3^g>zYt_>@SI!@{!TxeBn3v7#jXYe=5SxoSC9UWzUEE ztYmMX!hL@|f*p0>KLq&Jm~M=e-$$pZK1xf@_iFe+)vWU64+;7M(g$W$Lr#xly^eEi)ov8b&6 zWUsniywrLud$!);7Y_jJ>QVaStC%NIhi6r~;Esvk?B@VD?`-q;p=HtE_Cp5S_iwo& z4z&b$DF=o5S8sxZ)dF!pr4b>!W&qR<~VpB-2V@`^E5Y*hsFxgr;>y$KjULpUEXx;Kr}kdxYxjGI1`&}KnX+S<*w|#=#KX57Rrm=`WFWr> z1GCdp-KT{eirhF(64X2Hyc#wP{?FCO=(&bQ?vecvOk#r1|*8rLrQMWGW35MGIv zX*fV0=2&vz?YTRhUI$RZWz?yT;Jdmy)%Oy*M8}_?sn42}Xw+JbRE%T@a0;IN;5972 ziv69!_gqlrk4v5QY~sGOR$v2wq3!@YRi)1*@KSrDK>P;%xXI=MMagp?WS`#&yd{Br zsiu;1nDBl){-ak{K~dRq;90m6HcoW*Y#okBz0mtQ%()ltTbOCa9!VJ;E>WqyrUx1R z?8)3FispO{b6lU(;YU_hhb6CZy$d1MWYWM+4H#jzJese~*z98esJi89^ZRe!JNX(9ka$m$6eXeV*J}u_ zG)50qy=02~bWw`ES=Brl|0@}J98DeHSnM3B^9O|4ol@vVIY93Q<_L^7{7R50SPdEl z?UrqB(Vo-8*pg~x%(RnN={v*%iI}H4f$I8jkJK4LvBk)7sr|{$dH7V7zCJl?1~U5U z2ku6WMZ=f00iYRN`>cB-?9Y)I{{;qYCO!O(dTU@dZNtmZp`qO|B{;2riGHr?G){k8 zyP7_+snb7)DcFJMgVN*Wa*2XQwq@eIgx0?EvyvnBz@QM6-Ywg4=twG zpDyHkGP??YiF0+pi+;$}g1Q%NFU*Uq>$lRZ`br&b z?>xC6qj{s9hwg9C*^)OODYAkl+5(Qj2HBx)l2NR@|7+h5XOK{> zYq3f~e7dObLc~ahdr7XkwI!@1}xQc&tq{Ap?FpD zpC9SGVACZE$7+(2vv(1TU0Io8T#FQ?-~K2Kv+Yzepq8)X3ElnYFKd z*VZ`OU#hh=S6(L4|2gngit@_V`rHheM&I@*Jq61azxhoSSq9FZr`!o+u zIl-PJ!HL(fJ5W{gCd>~Uh(s7J)Z3?NR-1^StJ0~HQoM4Q!~b?))!`?7{f1{UMg+Dd zjLm|5hhD)GvuyKYYIXMRs_Vo?^G8=vQ|`}A_sWXE3?0kjzDO8#+|0Be^j)c;QOX`c z0@YS(T8Sn=$aC0rpiN0z{Y(D+R^dB&JWA3Z0S^I<5^T7c8oWKTBidr`&?`1o;0&!% zqemWB6NZlVeucx`H9nSV!n*67a&hzpOv8$T?8dms%$AE_&Z)O%ZxWr`ZaBstBdiT_JbteOMf`c0 zEzieg!qjWbA8U5X!@72CE7l{r%uq2P^)#0_wP69ihxfE{ZcF3sK@XdNBQvhH=9lYF z2H^K-+JhD6x#}uPN3VLhjF~JE??d_{;OY!sO@#1GB;m>XIzP$2#9JuV9R_Uc&3C8@ zTjABiHCJ!;R;>=!nZ8qW*AxR88Dy`n>_$CTd&zYG?p-YGa23#~lO{uyUq{{*A2qQ7 zbt=5L?x$YgJVA^dM0W6QWO+8~1;y68&FPXKj*Wrg)j1w~oPlz26la$iVE>v=KI6r( z%H8bvv7t4Oaqd;#9uG1|wuhZ}i5%9f(sBK=icLaOXnBS{*N$(n*tp1h%7gM}58FF>$Kgn_*@6mEZ@9KpFFv z3h>&Oc-8TZ>5h#w21|oY^n>EZ$w zlyzEh56*dQi8xb1f4a^w(fZW$$w^x}{{g?rY6n4nMTww(6A+uJ*^=jU%qK=?iG@yS>r7rGI;gKJ|L%O7V?9V_vWamQAP7|(!y4f8?`)Q?`6N@t? ztu~Axyc6{`+znXYEOiay3MFN>9w;X$XlP_I+XJ%Rr14qLZ0t3YZM{j^u zDtyYSz?AE=D+ zejJk6B z9+O)4kR;OHa=oy|yddUdn`R_=_h&V?l0w9BK2DWi)EFShc~suA66>b&I;KS$X`>Jn z5|O17V7EJsk7K3svSjk!@_9^>ul>Ksu8(^agYYhY8yQb{9XoEaN{QOJH5i2{gURr? zW=sfApso2!_63EkHZ+Gh+O4`J?zqLKS?5NzSCqLbcrOeDRvmTy;IlSb{QA4q??H*K zNHOk)C$mNDh9-9#v7FyL>}C)3Dm&{H13cWiMs$8tBzgS7?JQ2$#>K(Gb-%CRAFLyz zI2eh=@LBKW;|0LBY2MZLp)qx};qYn<=;uOl4_taj5Sp!6z?WE*n53R?X zC=&?Th?U)xjNqEKhK==EEUfJ3Qc%Rq9JlYyJ3cK?b3&JQH_@96hfS0*8=XZ3cyy%n&D&#gMFqnHCVlsuSDvrldF z6FJAdi_(L!>|SIt9am-2EHq3tI~+D>%zdaGv<+XTRc!ok(2X>mh^@&n%ky35Fhb6_l2L zaj69c^xz%Wyy2l`aC#M3>$|JEZqC77mH*MO{UB%ldXUFtpD~sqt($W#{JfN?#QsKv(8BTBQ6BT{GXz-ZKGH!)R;U< zT7+XgaPSf7;JiS$OKHI6%hv`GzTx|VxKJ}|<5HeuS_S=AT zlhUG)ay=6#JL#LhzioNt$Z@Z|HF^N!cR;T58q~QAYt!-7ImkZCOV3OHmCpRNgw$;) z&j<08M3MV}#EFoXiL#vW1vxg2op-21h<1wi;o{)+VCU9w+GAku)UUhIu5iVNo2xDL zU1u!!RygdaBxY0O>pZP$l;ekx7>ob>{X+`NHv=h(DX#a74}=Ge#45zQSW%P@l1f*{ zpCptwG&&9SWUkyBl;cqzpsJ7)l9(Vbr~dd~&9WG%oc&D1QU```w>JyC83NAFxJjqS z%MSa0R_@tF=<&Q~duj*OdMf=Xj!KM~={M}hG1zRq7hlo#xQO0!PQ!0KS6VlDy}>OH zM(ru_aq;K4N2uwE){GQJdHel5yW$sM>eFhgZ0svegRhqN8~U}Mh5hr6DH^d*?00P5 z_-m<<>nEKRL&C|Lvi=xE^^jvBiXP zQvdAS;|ce)TGMvf;gaDi;lm~iyPqYl{;iA&70atl>!Bp^;`8hgWqWZo=A;yNk&A1& z_n!@@oME;T?5RVyBGb?46A3i~bF~haO0?J0Dvc;A?lv=`tSn6)4!lM{B^Ui2N#vl+t4)<&G|@;2jmr9}305jmi&7k8S$$7mQP&0&UDEi=lVs@?SzUWQ-?ounV}x!Fi}h;PPN6*T_**rp+e%(wwmHagtNHQ(AY$C zCvJP)XnG`{JUU_f86c&)UjVpkt!HSKQRHh|T8-8Z2y*-eZpk+yc*&ta{u zC8RH-VF;m(O1f)UEGcmfTnkd#Pl;W*p%FU!3lUyKqQL24$X{Tx0}~6nJHURNzgM|U zkrCTxMp3Yy(ybTqu}3(H8GBaf+?TBgx-{zNO$_xm%2yjekJ<-#?b#7aR>&@>@$a`LA3z1t5pu3}#vZmiEub z4eiCBZ=kk1@-?eLU1|e>Ui*L zO#HO_EFJlpzk9PCxO)l+ZCcAY#l#&~Bv|knsX;}0Z}+EO`rhdF+`Q$voA5FLN8gR+qHCUCcXpZw*fqXAn{~e?C=fzj zwX$U}H&(NZ_?1H2*dJ$mR`<7h+K5<3NLC%o&;vbfk1>=qb^KNdN zU5vVKj0nIi^IO!L>q)HH>Y%p|>>Q%t2!e&kv7d0|{GCgXv$Z$6T~D>F|I0@=yQ%R}KIF`$`l= admy+FKBVULaIkm;d|u0{$W%$0hW-!M>nh&> literal 0 HcmV?d00001 diff --git a/g3doc/examples/prensor_playground.ipynb b/g3doc/examples/prensor_playground.ipynb new file mode 100644 index 0000000..45f724e --- /dev/null +++ b/g3doc/examples/prensor_playground.ipynb @@ -0,0 +1,2207 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "PHuwKiTF02Zq" + }, + "source": [ + "# Your structured data into Tensorflow.\n", + "\n", + "ML training often expects _flat_ data, like a line in a CSV.\n", + "[tf.Example](https://www.tensorflow.org/api_docs/python/tf/train/Example) was\n", + "designed to represent flat data. But the data you care about and want to predict\n", + "things about usually starts out _structured_.\n", + "\n", + "Over and over again you have to write transform code that turns your structured data into Tensors. This repetitive transform code must be rewritten over and over for all your ML pipelines both for training _and_ serving! And it lets bugs slip into your ML pipeline. \n", + "\n", + "`struct2tensor` lets you take advantage of structured data _within_ your ML pipelines. It is:\n", + "\n", + "* **for**: ML Engineers \n", + "* **who**: train models on data that starts out structured\n", + "* **it is**: a python library \n", + "* **that**: transforms your structured data into model-friendly (Sparse, Raggged, Dense, ...) tensors hermetically _within_ your model\n", + "* **unlike**: writing custom transforms over and over for training and serving.\n", + "---\n", + "![struct2tensor diagram showing the transform happens in the model](../../assets/examples/prensor_playground/aqOX7nS.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7ozCmoF33ogj" + }, + "source": [ + "# Demo example\n", + "\n", + "Suppose we have this _structured_ data we want to train on. The source example data format is a [protobuff](https://developers.google.com/protocol-buffers). `struct2tensor` was built internally and works on protobuffers now. It can be extended to parquet, json, etc. in the future.\n", + "\n", + "```\n", + "# e.g. a web session\n", + "message Session{\n", + " message SessionInfo {\n", + " string session_feature = 1;\n", + " double session_duration_sec = 2;\n", + " }\n", + " SessionInfo session_info = 1;\n", + " message Event {\n", + " string query = 1;\n", + " message Action {\n", + " int number_of_views = 1;\n", + " }\n", + " repeated Action action = 2;\n", + " }\n", + " repeated Event event = 2;\n", + "}\n", + "```\n", + "\n", + "\n", + "In 3 steps we'll extract the fields we want with `struct2tensor`. We'll end up with batch-aligned `SparseTensors`:\n", + "\n", + "1. Tell our model what examples we care about, e.g. **`event`** (submessage `Session::Event`).\n", + "2. Pick the proto fields that we think are good features, say:\n", + " * `session_info.session_feature`\n", + " * `event.query`\n", + "3. Identify the label to predict, say **`event.action.number_of_views`** (the actual label could be sum(action.number_of_views for action in event))\n", + "\n", + "\n", + "Then we can build a struct2tensor query that:\n", + "* parses instances of this protocol buffer\n", + "* transforms the fields we care about\n", + "* creates the necessary `SparseTensor`s\n", + "\n", + "Don't worry about some of these terms yet. We'll show you an example. And then explain the terms later." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "w2RkJ6mN2Y6-" + }, + "source": [ + "## Install required packages (internal colab users: skip)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 461 + }, + "id": "q3REXR58msJe", + "outputId": "9b3a6130-83ce-46af-fe61-44a8cb95b4d8" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: struct2tensor in /usr/local/lib/python3.6/dist-packages (0.0.1.dev6)\n", + "Requirement already satisfied: tensorflow-metadata>=0.13.0 in /usr/local/lib/python3.6/dist-packages (from struct2tensor) (0.15.1)\n", + "Requirement already satisfied: protobuf>=3.8.0 in /usr/local/lib/python3.6/dist-packages (from struct2tensor) (3.10.0)\n", + "Requirement already satisfied: tensorflow==1.15.0 in /usr/local/lib/python3.6/dist-packages (from struct2tensor) (1.15.0)\n", + "Requirement already satisfied: googleapis-common-protos in /usr/local/lib/python3.6/dist-packages (from tensorflow-metadata>=0.13.0->struct2tensor) (1.6.0)\n", + "Requirement already satisfied: six>=1.9 in /usr/local/lib/python3.6/dist-packages (from protobuf>=3.8.0->struct2tensor) (1.12.0)\n", + "Requirement already satisfied: setuptools in /usr/local/lib/python3.6/dist-packages (from protobuf>=3.8.0->struct2tensor) (42.0.1)\n", + "Requirement already satisfied: numpy<2.0,>=1.16.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0->struct2tensor) (1.17.4)\n", + "Requirement already satisfied: opt-einsum>=2.3.2 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0->struct2tensor) (3.1.0)\n", + "Requirement already satisfied: keras-preprocessing>=1.0.5 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0->struct2tensor) (1.1.0)\n", + "Requirement already satisfied: termcolor>=1.1.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0->struct2tensor) (1.1.0)\n", + "Requirement already satisfied: wheel>=0.26 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0->struct2tensor) (0.33.6)\n", + "Requirement already satisfied: grpcio>=1.8.6 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0->struct2tensor) (1.15.0)\n", + "Requirement already satisfied: tensorboard<1.16.0,>=1.15.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0->struct2tensor) (1.15.0)\n", + "Requirement already satisfied: keras-applications>=1.0.8 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0->struct2tensor) (1.0.8)\n", + "Requirement already satisfied: tensorflow-estimator==1.15.1 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0->struct2tensor) (1.15.1)\n", + "Requirement already satisfied: google-pasta>=0.1.6 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0->struct2tensor) (0.1.8)\n", + "Requirement already satisfied: gast==0.2.2 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0->struct2tensor) (0.2.2)\n", + "Requirement already satisfied: wrapt>=1.11.1 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0->struct2tensor) (1.11.2)\n", + "Requirement already satisfied: absl-py>=0.7.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0->struct2tensor) (0.8.1)\n", + "Requirement already satisfied: astor>=0.6.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow==1.15.0->struct2tensor) (0.8.0)\n", + "Requirement already satisfied: werkzeug>=0.11.15 in /usr/local/lib/python3.6/dist-packages (from tensorboard<1.16.0,>=1.15.0->tensorflow==1.15.0->struct2tensor) (0.16.0)\n", + "Requirement already satisfied: markdown>=2.6.8 in /usr/local/lib/python3.6/dist-packages (from tensorboard<1.16.0,>=1.15.0->tensorflow==1.15.0->struct2tensor) (3.1.1)\n", + "Requirement already satisfied: h5py in /usr/local/lib/python3.6/dist-packages (from keras-applications>=1.0.8->tensorflow==1.15.0->struct2tensor) (2.8.0)\n", + "Requirement already satisfied: graphviz in /usr/local/lib/python3.6/dist-packages (0.10.1)\n" + ] + } + ], + "source": [ + "#@test {\"skip\": true} \n", + "# install struct2tensor\n", + "!pip install struct2tensor\n", + "# graphviz for pretty output\n", + "!pip install graphviz" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "dIxHSM3VQfUu" + }, + "source": [ + "## Some Pretty Printing and Imports\n", + "\n", + "(not the \"real\" work yet)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "code", + "colab": { + "height": 34 + }, + "executionInfo": { + "elapsed": 437, + "status": "ok", + "timestamp": 1600375610067, + "user": { + "displayName": "", + "photoUrl": "", + "userId": "" + }, + "user_tz": 420 + }, + "id": "lc5KF8MILYrS", + "outputId": "5b8c3534-db19-4f98-cc12-f44716bc402a", + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "type-specific pretty printing ready to go\n" + ] + } + ], + "source": [ + "import base64\n", + "import numpy as np\n", + "import pprint\n", + "import os\n", + "import tensorflow\n", + "from graphviz import Source\n", + "\n", + "import tensorflow as tf\n", + "\n", + "from IPython.display import Image\n", + "from IPython.lib import pretty\n", + "\n", + "import struct2tensor as s2t\n", + "from struct2tensor.test import test_pb2\n", + "from google.protobuf import text_format\n", + "\n", + "\n", + "def _display(graph):\n", + " \"\"\"Renders a graphviz digraph.\"\"\"\n", + " s = Source(graph)\n", + " s.format='svg'\n", + " return s\n", + " \n", + "\n", + "def _create_query_from_text_sessions(text_sessions):\n", + " \"\"\"Creates a struct2tensor query from a list of pbtxt of struct2tensor.test.Session.\"\"\"\n", + " sessions = tf.constant([\n", + " text_format.Merge(\n", + " text_session, \n", + " test_pb2.Session()\n", + " ).SerializeToString()\n", + " for text_session in text_sessions\n", + " ])\n", + " return s2t.create_expression_from_proto(\n", + " sessions, test_pb2.Session.DESCRIPTOR)\n", + "\n", + "def _prensor_pretty_printer(prensor, p, cycle):\n", + " \"\"\"Pretty printing function for struct2tensor.prensor.Prensor\"\"\"\n", + " pretty.pprint(prensor.get_sparse_tensors())\n", + "\n", + "def _sp_pretty_printer(sp, p, cycle):\n", + " \"\"\"Pretty printing function for SparseTensor.\"\"\"\n", + "\n", + " del cycle\n", + " p.begin_group(4, \"SparseTensor(\")\n", + " p.text(\"values={}, \".format(sp.values.numpy().tolist()))\n", + " p.text(\"dense_shape={}, \".format(sp.dense_shape.numpy().tolist()))\n", + " p.break_()\n", + " p.text(\"indices={}\".format(sp.indices.numpy().tolist()))\n", + " p.end_group(4, \")\")\n", + "\n", + "\n", + "pretty.for_type(tf.SparseTensor, _sp_pretty_printer)\n", + "pretty.for_type(s2t.Prensor, _prensor_pretty_printer)\n", + "\n", + "_pretty_print = pretty.pprint\n", + "\n", + "print(\"type-specific pretty printing ready to go\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "sEsOWNuz7jC7" + }, + "source": [ + "## The real work:\n", + "\n", + "A function that parses our structured data (protobuffers) into tensors:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "height": 34 + }, + "executionInfo": { + "elapsed": 378, + "status": "ok", + "timestamp": 1600375613528, + "user": { + "displayName": "", + "photoUrl": "", + "userId": "" + }, + "user_tz": 420 + }, + "id": "ZC-oUzvBoPjA", + "outputId": "7a19c4e5-0cb1-479d-9245-e302a932448e" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Defined the workhorse func: (structured data at rest) -> (tensors)\n" + ] + } + ], + "source": [ + "@tf.function(input_signature=[tf.TensorSpec(shape=(None), dtype=tf.string)], autograph=False)\n", + "def parse_session(serialized_sessions):\n", + " \"\"\"A TF function parsing a batch of serialized Session protos into tensors.\n", + "\n", + " It is a TF graph that takes one 1-D tensor as input, and outputs a\n", + " Dict[str, tf.SparseTensor]\n", + " \"\"\"\n", + " query = s2t.create_expression_from_proto(\n", + " serialized_sessions, test_pb2.Session.DESCRIPTOR)\n", + " # Move all the fields of our interest to under \"event\". \n", + " query = query.promote_and_broadcast({\n", + " \"session_feature\": \"session_info.session_feature\",\n", + " \"action_number_of_views\": \"event.action.number_of_views\" },\n", + " \"event\")\n", + " # Specify \"event\" to be examples.\n", + " query = query.reroot(\"event\")\n", + " # Extract all the fields of our interest.\n", + " projection = query.project([\"session_feature\", \"query\", \"action_number_of_views\"]) \n", + " prensors = s2t.calculate_prensors([projection])\n", + " \n", + " output_sparse_tensors = {}\n", + " for prensor in prensors:\n", + " path_to_tensor = prensor.get_sparse_tensors()\n", + " output_sparse_tensors.update({str(k): v for k, v in path_to_tensor.items()})\n", + " \n", + " return output_sparse_tensors\n", + "\n", + "print(\"Defined the workhorse func: (structured data at rest) -> (tensors)\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "VKp4SxTgPzpe" + }, + "source": [ + "## Lets see it in action:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "height": 119 + }, + "executionInfo": { + "elapsed": 569, + "status": "ok", + "timestamp": 1600375616071, + "user": { + "displayName": "", + "photoUrl": "", + "userId": "" + }, + "user_tz": 420 + }, + "id": "-cIlFypdPeZX", + "outputId": "7840d783-249c-4be2-d675-10f8e817dded" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'action_number_of_views': SparseTensor(values=[1, 2, 3], dense_shape=[2, 2], \n", + " indices=[[0, 0], [1, 0], [1, 1]]),\n", + " 'query': SparseTensor(values=[b'Hello', b'world'], dense_shape=[2], \n", + " indices=[[0], [1]]),\n", + " 'session_feature': SparseTensor(values=[b'foo', b'foo'], dense_shape=[2, 1], \n", + " indices=[[0, 0], [1, 0]])}\n" + ] + } + ], + "source": [ + "serialized_sessions = tf.constant([\n", + " text_format.Merge(\n", + " \"\"\"\n", + " session_info {\n", + " session_duration_sec: 1.0\n", + " session_feature: \"foo\"\n", + " }\n", + " event {\n", + " query: \"Hello\"\n", + " action {\n", + " number_of_views: 1\n", + " }\n", + " action {\n", + " }\n", + " }\n", + " event {\n", + " query: \"world\"\n", + " action {\n", + " number_of_views: 2\n", + " }\n", + " action {\n", + " number_of_views: 3\n", + " }\n", + " }\n", + " \"\"\",\n", + " test_pb2.Session()\n", + " ).SerializeToString()\n", + "])\n", + "\n", + "_pretty_print(parse_session(serialized_sessions))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "pW9zBjNMWMIH" + }, + "source": [ + "See how we went from our pre-pipeline data (the Protobuffer) all the way to the structured data, packed into `SparseTensor`s?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "QY7wy_6loF4v" + }, + "source": [ + "# Digging Far Deeper\n", + "Interested and want to learn more? Read on...\n", + "\n", + "Let's define several terms we mentioned before:\n", + "\n", + "### Prensor\n", + "\n", + "A Prensor (protobuffer + tensor) is a data structure storing the data we work on. We use protobuffers a lot at Google. `struct2tensor` can support other structured formats, too.\n", + "\n", + "For example, throughout this colab we will be using proto\n", + "[`struct2tensor.test.Session`](http://cs/symbol:struct2tensor.test.Session). A schematic visualization\n", + "of a selected part of the prensor from that proto looks like:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 543 + }, + "id": "ZTjNwx4bBXFk", + "outputId": "4927fdf8-0d2c-46e6-ee2f-a1b2bf4a298f" + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "%3\n", + "\n", + "\n", + "\n", + "root\n", + "\n", + "root\n", + "\n", + "\n", + "\n", + "session\n", + "\n", + "session\n", + "\n", + "\n", + "\n", + "root->session\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "event\n", + "\n", + "event\n", + "\n", + "\n", + "\n", + "session->event\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "session_id\n", + "\n", + "session_id\n", + "\n", + "\n", + "\n", + "session->session_id\n", + "\n", + "\n", + "?\n", + "\n", + "\n", + "\n", + "action\n", + "\n", + "action\n", + "\n", + "\n", + "\n", + "event->action\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "query_token\n", + "\n", + "query_token\n", + "\n", + "\n", + "\n", + "event->query_token\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "number_of_views\n", + "\n", + "number_of_views\n", + "\n", + "\n", + "\n", + "action->number_of_views\n", + "\n", + "\n", + "?\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": { + "tags": [] + }, + "output_type": "execute_result" + } + ], + "source": [ + "#@title { display-mode: \"form\" }\n", + "#@test {\"skip\": true} \n", + "\n", + "_display(\"\"\"\n", + "digraph {\n", + " root -> session [label=\"*\"];\n", + " session -> event [label=\"*\"];\n", + " session -> session_id [label=\"?\"];\n", + " event -> action [label=\"*\"];\n", + " event -> query_token [label=\"*\"]\n", + " action -> number_of_views [label=\"?\"];\n", + "}\n", + "\"\"\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "P0aBK7NbO0wp" + }, + "source": [ + "We will be using visualizations like this to demostrate struct2tensor queries later.\n", + "\n", + "Note:\n", + "\n", + "* The \"*\" on the edge means the pointed node has repeated values; while the \"?\" means it has an optional value.\n", + "* There is always a \"root\" node whose only child is the root of the structure. Note that it's \"repeated\" because one struct2tensorTree can represent multiple instances of a structure.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "5stZYn1dBXdl" + }, + "source": [ + "\n", + "### struct2tensor Query\n", + "A struct2tensor query transforms a Prensor into another Prensor.\n", + "\n", + "For example, `broadcast` is a query that replicates a node as a child of one of its siblings.\n", + "\n", + "Applying\n", + "```\n", + "broadcast(\n", + " source_path=\"session.session_id\",\n", + " sibling=\"event\",\n", + " new_field_name=\"session_session_id\")\n", + "```\n", + "\n", + "on the previous tree gives:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 543 + }, + "id": "zfhF-frzPxZm", + "outputId": "36bd1e53-befe-4bfa-e7da-737bd18a2611" + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "%3\n", + "\n", + "\n", + "\n", + "session_session_id\n", + "\n", + "session_session_id\n", + "\n", + "\n", + "\n", + "root\n", + "\n", + "root\n", + "\n", + "\n", + "\n", + "session\n", + "\n", + "session\n", + "\n", + "\n", + "\n", + "root->session\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "event\n", + "\n", + "event\n", + "\n", + "\n", + "\n", + "session->event\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "session_id\n", + "\n", + "session_id\n", + "\n", + "\n", + "\n", + "session->session_id\n", + "\n", + "\n", + "?\n", + "\n", + "\n", + "\n", + "event->session_session_id\n", + "\n", + "\n", + "?\n", + "\n", + "\n", + "\n", + "action\n", + "\n", + "action\n", + "\n", + "\n", + "\n", + "event->action\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "query_token\n", + "\n", + "query_token\n", + "\n", + "\n", + "\n", + "event->query_token\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "number_of_views\n", + "\n", + "number_of_views\n", + "\n", + "\n", + "\n", + "action->number_of_views\n", + "\n", + "\n", + "?\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": { + "tags": [] + }, + "output_type": "execute_result" + } + ], + "source": [ + "#@title { display-mode: \"form\" }\n", + "#@test {\"skip\": true} \n", + "\n", + "_display(\"\"\"\n", + "digraph {\n", + " session_session_id [color=\"red\"];\n", + " root -> session [label=\"*\"];\n", + " session -> event [label=\"*\"];\n", + " session -> session_id [label=\"?\"];\n", + " event -> action [label=\"*\"];\n", + " event -> session_session_id [label=\"?\"];\n", + " event -> query_token [label=\"*\"];\n", + " action -> number_of_views [label=\"?\"];\n", + "}\n", + "\"\"\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "eOHokTjBPyHW" + }, + "source": [ + "We will talk about common struct2tensor queries in later sections.\n", + "\n", + "### Projection\n", + "A projection of paths in a Prensor produces another Prensor with just the selected paths.\n", + "\n", + "#### Logical representation of a projection\n", + "The structure of the projected path can be represented losslessly as nested lists. For example, the projection of `event.action.number_of_views` from the struct2tensorTree formed by the following two instances of `struct2tensor.test.Session`:\n", + "```\n", + "{\n", + " event { action { number_of_views: 1} action { number_of_views: 2} action {} }\n", + " event {}\n", + "}, {\n", + " event { action { number_of_views: 3} }\n", + "}\n", + "```\n", + "\n", + "is:\n", + "\n", + "```\n", + "[ # the outer list has two elements b/c there are two Session protos.\n", + " [ # the first proto has two events\n", + " [[1],[2],[]], # 3 actions, the last one does not have a number_of_views.\n", + " [], # the second event does not have action\n", + " ],\n", + " [ # the second proto has one event\n", + " [[3]],\n", + " ],\n", + "]\n", + "```\n", + "\n", + "#### Representing nested lists with `tf.SparseTensor`\n", + "\n", + "struct2tensor uses `tf.SparseTensor` to represent the above nested list in the projection results. Note that `tf.SparseTensor` essentially enforces that the lists nested at the same level to have the same length (because the there is a certain size for each dimension), therefore this representation is lossy. The above nested lists, when written as a SparseTensor will look like:\n", + "```\n", + "tf.SparseTensor(\n", + " dense_shape=[2, 2, 3, 1], # each is the maximum length of lists at the same nesting level.\n", + " values = [1, 2, 3],\n", + " indices = [[0, 0, 0, 0], [0, 0, 1, 0], [1, 0, 0, 0]]\n", + ")\n", + "```\n", + "\n", + "Note that the last dimension is useless: the index of that dimension will always be 0 for any present value because number_of_views is an optional field. So struct2tensors library will actually \"squeeze\" all the optional dimensions.\n", + "\n", + "The actual result would be:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "height": 68 + }, + "executionInfo": { + "elapsed": 352, + "status": "ok", + "timestamp": 1600368919353, + "user": { + "displayName": "", + "photoUrl": "", + "userId": "" + }, + "user_tz": 420 + }, + "id": "XvuEY3D3WIP7", + "outputId": "ee33d486-90e7-4328-accc-11f13f82a9db" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{event.action.number_of_views: SparseTensor(values=[1, 2, 3], dense_shape=[2, 2, 3], \n", + " indices=[[0, 0, 0], [0, 0, 1], [1, 0, 0]])}\n", + "]\n" + ] + } + ], + "source": [ + "query = _create_query_from_text_sessions(['''\n", + " event { action { number_of_views: 1} action { number_of_views: 2} action {} }\n", + " event {}\n", + " ''', '''\n", + " event { action { number_of_views: 3} }\n", + " ''']\n", + " ).project([\"event.action.number_of_views\"])\n", + "\n", + "prensor = s2t.calculate_prensors([query])\n", + "pretty.pprint(prensor)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "iaailOJhWLLa" + }, + "source": [ + "struct2tensor's internal data model is closer to the above \"nested lists\" abstraction and sometimes it's easier to reason with \"nested lists\" than with `SparseTensor`s.\n", + "\n", + "Recently, [`tf.RaggedTensor`](https://www.tensorflow.org/guide/ragged_tensors) was introduced to represent nested lists exactly. We are working on adding support for projecting into ragged tensors." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UD48ikK4Eop-" + }, + "source": [ + "## Common struct2tensor Queries\n", + "\n", + "### `promote`\n", + "\n", + "Promotes a node to become a sibling of its parent. If the node is repeated, then all its values are concatenated (the order is preserved)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 426 + }, + "id": "tnbD2lKoDbsk", + "outputId": "244aeef6-48b7-4ae5-f01c-2429bb65a7ea" + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "%3\n", + "\n", + "\n", + "\n", + "root\n", + "\n", + "root\n", + "\n", + "\n", + "\n", + "session\n", + "\n", + "session\n", + "\n", + "\n", + "\n", + "root->session\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "event\n", + "\n", + "event\n", + "\n", + "\n", + "\n", + "session->event\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "query_token\n", + "\n", + "query_token\n", + "\n", + "\n", + "\n", + "event->query_token\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": { + "tags": [] + }, + "output_type": "execute_result" + } + ], + "source": [ + "#@title { display-mode: \"form\" }\n", + "#@test {\"skip\": true} \n", + "\n", + "_display('''\n", + "digraph {\n", + " root -> session [label=\"*\"];\n", + " session -> event [label=\"*\"];\n", + " event -> query_token [label=\"*\"];\n", + "}\n", + "''')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OOgJlcHPDyIk" + }, + "source": [ + "`promote(source_path=\"event.query_token\", new_field_name=\"event_query_token\")`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 426 + }, + "id": "xKJ_EraVHyUA", + "outputId": "64c3720d-3672-4625-d8c1-1a4b301d6a2d" + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "%3\n", + "\n", + "\n", + "\n", + "event_query_token\n", + "\n", + "event_query_token\n", + "\n", + "\n", + "\n", + "root\n", + "\n", + "root\n", + "\n", + "\n", + "\n", + "session\n", + "\n", + "session\n", + "\n", + "\n", + "\n", + "root->session\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "session->event_query_token\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "event\n", + "\n", + "event\n", + "\n", + "\n", + "\n", + "session->event\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "query_token\n", + "\n", + "query_token\n", + "\n", + "\n", + "\n", + "event->query_token\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": { + "tags": [] + }, + "output_type": "execute_result" + } + ], + "source": [ + "#@title { display-mode: \"form\" }\n", + "#@test {\"skip\": true} \n", + "\n", + "_display('''\n", + "digraph {\n", + " event_query_token [color=\"red\"];\n", + " root -> session [label=\"*\"];\n", + " session -> event [label=\"*\"];\n", + " session -> event_query_token [label=\"*\"];\n", + " event -> query_token [label=\"*\"];\n", + "}\n", + "''')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "height": 68 + }, + "executionInfo": { + "elapsed": 344, + "status": "ok", + "timestamp": 1600296594869, + "user": { + "displayName": "", + "photoUrl": "", + "userId": "" + }, + "user_tz": 420 + }, + "id": "oQCcVWd-JDT9", + "outputId": "acad1b9a-0985-46bb-b895-7d085814d20e" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{event_query_token: SparseTensor(values=[b'abc', b'def', b'ghi'], dense_shape=[1, 3], \n", + " indices=[[0, 0], [0, 1], [0, 2]])}\n", + "]\n" + ] + } + ], + "source": [ + "query = (_create_query_from_text_sessions([\n", + "\"\"\"\n", + "event {\n", + " query_token: \"abc\"\n", + " query_token: \"def\"\n", + "}\n", + "event {\n", + " query_token: \"ghi\"\n", + "}\n", + "\"\"\"])\n", + " .promote(source_path=\"event.query_token\", new_field_name=\"event_query_token\")\n", + " .project([\"event_query_token\"]))\n", + "\n", + "prensor = s2t.calculate_prensors([query])\n", + "\n", + "_pretty_print(prensor)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "av9km5d_KM8o" + }, + "source": [ + "The projected structure is like:\n", + "```\n", + "{\n", + " # this is under Session.\n", + " event_query_token: \"abc\"\n", + " event_query_token: \"def\"\n", + " event_query_token: \"ghi\"\n", + "}\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "t2BU8warKqFm" + }, + "source": [ + "### `broadcast`\n", + "\n", + "Broadcasts the value of a node to one of its sibling. The value will be replicated if the sibling is repeated. This is similar to TensorFlow and Numpy's [broadcasting semantics](https://docs.scipy.org/doc/numpy-1.13.0/user/basics.broadcasting.html).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 311 + }, + "id": "1hFGEV0DMmOo", + "outputId": "ff23c5ce-17c9-4604-f309-c9b384b6d8ca" + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "%3\n", + "\n", + "\n", + "\n", + "root\n", + "\n", + "root\n", + "\n", + "\n", + "\n", + "session\n", + "\n", + "session\n", + "\n", + "\n", + "\n", + "root->session\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "session_id\n", + "\n", + "session_id\n", + "\n", + "\n", + "\n", + "session->session_id\n", + "\n", + "\n", + "?\n", + "\n", + "\n", + "\n", + "event\n", + "\n", + "event\n", + "\n", + "\n", + "\n", + "session->event\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": { + "tags": [] + }, + "output_type": "execute_result" + } + ], + "source": [ + "#@title { display-mode: \"form\" }\n", + "#@test {\"skip\": true} \n", + "\n", + "_display('''\n", + "digraph {\n", + " root -> session [label=\"*\"];\n", + " session -> session_id [label=\"?\"];\n", + " session -> event [label=\"*\"];\n", + "}\n", + "''')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "DvBCsWBuNDKV" + }, + "source": [ + "`broadcast(source_path=\"session_id\", sibling_field=\"event\", new_field_name=\"session_session_id\")`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 426 + }, + "id": "wTUHyGvSNMGp", + "outputId": "d1ef4902-72eb-4bd2-e0bb-3008a9de17e7" + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "%3\n", + "\n", + "\n", + "\n", + "session_session_id\n", + "\n", + "session_session_id\n", + "\n", + "\n", + "\n", + "root\n", + "\n", + "root\n", + "\n", + "\n", + "\n", + "session\n", + "\n", + "session\n", + "\n", + "\n", + "\n", + "root->session\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "session_id\n", + "\n", + "session_id\n", + "\n", + "\n", + "\n", + "session->session_id\n", + "\n", + "\n", + "?\n", + "\n", + "\n", + "\n", + "event\n", + "\n", + "event\n", + "\n", + "\n", + "\n", + "session->event\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "event->session_session_id\n", + "\n", + "\n", + "?\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 12, + "metadata": { + "tags": [] + }, + "output_type": "execute_result" + } + ], + "source": [ + "#@title { display-mode: \"form\" }\n", + "#@test {\"skip\": true} \n", + "\n", + "_display('''\n", + "digraph {\n", + " session_session_id [color=\"red\"];\n", + " root -> session [label=\"*\"];\n", + " session -> session_id [label=\"?\"];\n", + " session -> event [label=\"*\"];\n", + " event -> session_session_id [label=\"?\"];\n", + "}\n", + "''')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "height": 68 + }, + "executionInfo": { + "elapsed": 341, + "status": "ok", + "timestamp": 1600296607633, + "user": { + "displayName": "", + "photoUrl": "", + "userId": "" + }, + "user_tz": 420 + }, + "id": "wQE_ceMzNjzv", + "outputId": "d92d9977-1e4c-4138-e89c-94e9c0f4bf5e" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{event.session_session_id: SparseTensor(values=[8, 8], dense_shape=[1, 2], \n", + " indices=[[0, 0], [0, 1]])}\n", + "]\n" + ] + } + ], + "source": [ + "query = (_create_query_from_text_sessions([\n", + "\"\"\"\n", + "session_id: 8\n", + "event { }\n", + "event { }\n", + "\"\"\"])\n", + " .broadcast(source_path=\"session_id\",\n", + " sibling_field=\"event\",\n", + " new_field_name=\"session_session_id\")\n", + " .project([\"event.session_session_id\"]))\n", + "\n", + "prensor = s2t.calculate_prensors([query])\n", + "_pretty_print(prensor)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7fL8pmsQObUT" + }, + "source": [ + "The projected structure is like:\n", + "```\n", + "{\n", + " event {\n", + " session_session_id: 8\n", + " }\n", + " event {\n", + " session_session_id: 8\n", + " }\n", + "}\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Ch2WgR9AP23Q" + }, + "source": [ + "### `promote_and_broadcast`\n", + "The query accepts multiple source fields and a destination field. For each source field, it first promotes it to the least common ancestor with the destination field (if necessary), then broadcasts it to the destination field (if necessary).\n", + "\n", + "Usually for the purpose of machine learning, this gives a reasonable flattened representation of nested structures." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-uNfasxgRsvO" + }, + "source": [ + "```\n", + "promote_and_broadcast(\n", + " path_dictionary={\n", + " 'session_info_duration_sec': 'session_info.session_duration_sec'},\n", + " dest_path_parent='event.action')\n", + "```\n", + "is equivalent to:\n", + "```\n", + "promote(source_path='session_info.session_duration_sec',\n", + " new_field_name='anonymous_field1')\n", + "\n", + "broadcast(source_path='anonymous_field1',\n", + " sibling_field='event.action',\n", + " new_field_name='session_info_duration_sec')\n", + "```\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rYkWc_u8SR_k" + }, + "source": [ + "### `map_field_values`\n", + "\n", + "Creates a new node that is a sibling of a leaf node. The values of the new node are results of applying the given function to the values of the source node.\n", + "\n", + "Note that the function provided takes 1-D tensor that contains all the values of the source node as input and should also output a 1-D tensor of the same size, and it should build TF ops.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "height": 68 + }, + "executionInfo": { + "elapsed": 333, + "status": "ok", + "timestamp": 1600296617311, + "user": { + "displayName": "", + "photoUrl": "", + "userId": "" + }, + "user_tz": 420 + }, + "id": "DWjA2OFcS4k1", + "outputId": "7a2d8ef5-7111-4b45-81dd-65d8222badae" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{session_id_plus_one: SparseTensor(values=[9, 10], dense_shape=[2], \n", + " indices=[[0], [1]])}\n", + "]\n" + ] + } + ], + "source": [ + "query = (_create_query_from_text_sessions([\n", + "\"\"\"\n", + "session_id: 8\n", + "\"\"\",\n", + "\"\"\"\n", + "session_id: 9\n", + "\"\"\"])\n", + " .map_field_values(\"session_id\", lambda x: tf.add(x, 1), dtype=tf.int64,\n", + " new_field_name=\"session_id_plus_one\")\n", + " .project([\"session_id_plus_one\"]))\n", + " \n", + "prensor = s2t.calculate_prensors([query])\n", + "\n", + "_pretty_print(prensor)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_NusnDa1T7s5" + }, + "source": [ + "### `reroot`\n", + "\n", + "Makes the given node the new root of the struct2tensorTree. This has two effects:\n", + "\n", + "* restricts the scope of the struct2tensorTree\n", + " + The field paths in all the following queries are relative to the new root\n", + " + There's no way to refer to nodes that are outside the subtree rooted at the new root.\n", + "* changes the batch dimension.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 426 + }, + "id": "OBNPdOlQU6qS", + "outputId": "df56d6d2-72c5-4c90-e2b9-b3fe2bffde00" + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "%3\n", + "\n", + "\n", + "\n", + "root\n", + "\n", + "root\n", + "\n", + "\n", + "\n", + "session\n", + "\n", + "session\n", + "\n", + "\n", + "\n", + "root->session\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "session_id\n", + "\n", + "session_id\n", + "\n", + "\n", + "\n", + "session->session_id\n", + "\n", + "\n", + "?\n", + "\n", + "\n", + "\n", + "event\n", + "\n", + "event\n", + "\n", + "\n", + "\n", + "session->event\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "event_id\n", + "\n", + "event_id\n", + "\n", + "\n", + "\n", + "event->event_id\n", + "\n", + "\n", + "?\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": { + "tags": [] + }, + "output_type": "execute_result" + } + ], + "source": [ + "#@title { display-mode: \"form\" }\n", + "#@test {\"skip\": true} \n", + "\n", + "_display('''\n", + "digraph {\n", + " root -> session [label=\"*\"];\n", + " session -> session_id [label=\"?\"];\n", + " session -> event [label=\"*\"];\n", + " event -> event_id [label=\"?\"];\n", + "}\n", + "''')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HFlI3zMtU6Ev" + }, + "source": [ + "`reroot(\"event\")`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 311 + }, + "id": "uANFWQA-T68W", + "outputId": "659a11b1-39bb-456d-de90-b2675182ca25" + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "%3\n", + "\n", + "\n", + "\n", + "root\n", + "\n", + "root\n", + "\n", + "\n", + "\n", + "event\n", + "\n", + "event\n", + "\n", + "\n", + "\n", + "root->event\n", + "\n", + "\n", + "*\n", + "\n", + "\n", + "\n", + "event_id\n", + "\n", + "event_id\n", + "\n", + "\n", + "\n", + "event->event_id\n", + "\n", + "\n", + "?\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 16, + "metadata": { + "tags": [] + }, + "output_type": "execute_result" + } + ], + "source": [ + "#@title { display-mode: \"form\" }\n", + "#@test {\"skip\": true} \n", + "\n", + "_display('''\n", + "digraph {\n", + " root -> event [label=\"*\"];\n", + " event -> event_id [label=\"?\"];\n", + "}\n", + "''')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "height": 459 + }, + "executionInfo": { + "elapsed": 408, + "status": "ok", + "timestamp": 1600297140484, + "user": { + "displayName": "", + "photoUrl": "", + "userId": "" + }, + "user_tz": 420 + }, + "id": "lMoeHHllVeet", + "outputId": "631afc7a-ebb3-456f-d0a1-11eeeccee95c" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Assume the following Sessions: \n", + "[session_id: 1\n", + "event {\n", + " event_id: \"a\"\n", + "}\n", + "event {\n", + " event_id: \"b\"\n", + "}\n", + ", session_id: 2\n", + ", session_id: 3\n", + "event {\n", + " event_id: \"c\"\n", + "}\n", + "]\n", + "\n", + "\n", + "project([\"event.event_id\"]) before reroot() (the batch dimension is the index to sessions):\n", + "[{event.event_id: SparseTensor(values=[b'a', b'b', b'c'], dense_shape=[3, 2], \n", + " indices=[[0, 0], [0, 1], [2, 0]])}\n", + "]\n", + "\n", + "\n", + "project([\"event_id\"]) after reroot() (the batch dimension becomes the index to events):\n", + "[{event_id: SparseTensor(values=[b'a', b'b', b'c'], dense_shape=[3], \n", + " indices=[[0], [1], [2]])}\n", + "]\n" + ] + } + ], + "source": [ + "#@title { display-mode: \"form\" }\n", + "text_protos = [\"\"\"\n", + "session_id: 1\n", + "event {\n", + " event_id: \"a\"\n", + "}\n", + "event {\n", + " event_id: \"b\"\n", + "}\n", + "\"\"\",\n", + "\"\"\"\n", + "session_id: 2\n", + "\"\"\",\n", + "\"\"\"\n", + "session_id: 3\n", + "event {\n", + " event_id: \"c\"\n", + "}\n", + "\"\"\"\n", + "]\n", + "print(\"\"\"Assume the following Sessions: \"\"\")\n", + "print([text_format.Merge(p, s2t.test.test_pb2.Session()) for p in text_protos])\n", + "print(\"\\n\")\n", + "reroot_example_query = _create_query_from_text_sessions(text_protos)\n", + "\n", + "print(\"\"\"project([\"event.event_id\"]) before reroot() (the batch dimension is the index to sessions):\"\"\")\n", + "_pretty_print(s2t.calculate_prensors([reroot_example_query.project([\"event.event_id\"])]))\n", + "print(\"\\n\")\n", + "print(\"\"\"project([\"event_id\"]) after reroot() (the batch dimension becomes the index to events):\"\"\")\n", + "_pretty_print(s2t.calculate_prensors([reroot_example_query.reroot(\"event\").project([\"event_id\"])]))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "70nd50UgLlQU" + }, + "source": [ + "### Proto Map\n", + "\n", + "You can specify a key for the proto map field in a path via brackets.\n", + "\n", + "Given the following tf.Example:\n", + "```\n", + "features {\n", + " feature {\n", + " key: \"my_feature\"\n", + " value {\n", + " float_list {\n", + " value: 1.0\n", + " }\n", + " }\n", + " }\n", + " feature {\n", + " key: \"other_feature\"\n", + " value {\n", + " bytes_list {\n", + " value: \"my_val\"\n", + " }\n", + " }\n", + " }\n", + "}\n", + "```\n", + "\n", + "To get the values of `my_feature` and `other_feature`, we can `promote_and_broadcast` and `project` the following paths: `features.feature[my_feature].float_list.value` and `features.feature[other_feature].bytes_list.value`\n", + "\n", + "This results in the following dict of ragged tensors: \n", + "```\n", + "{\n", + " features.my_new_feature: ,\n", + " features.other_new_feature: \n", + "}\n", + "```\n", + "\n", + "Note: we renamed `my_feature` to `my_new_feature` in the `promote_and_broadcast` (and similarly for `other_feature`)." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "executionInfo": { + "elapsed": 111, + "status": "ok", + "timestamp": 1632331356173, + "user": { + "displayName": "", + "photoUrl": "", + "userId": "" + }, + "user_tz": 420 + }, + "id": "9ESrpLsEL4SO", + "outputId": "ba002544-d4b2-4ae2-f343-f1f25ab91a4d" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{features.my_new_feature: , features.other_new_feature: }\n" + ] + } + ], + "source": [ + "tf_example = text_format.Parse(\"\"\"\n", + "features {\n", + " feature {\n", + " key: \"my_feature\"\n", + " value {\n", + " float_list {\n", + " value: 1.0\n", + " }\n", + " }\n", + " }\n", + " feature {\n", + " key: \"other_feature\"\n", + " value {\n", + " bytes_list {\n", + " value: \"my_val\"\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\"\"\", tf.train.Example())\n", + "\n", + "query = s2t.create_expression_from_proto(\n", + " tf_example.SerializeToString(), tf.train.Example.DESCRIPTOR)\n", + "query = query.promote_and_broadcast({'my_new_feature': \"features.feature[my_feature].float_list.value\", \"other_new_feature\": \"features.feature[other_feature].bytes_list.value\"}, \"features\")\n", + "query = query.project([\"features.my_new_feature\", \"features.other_new_feature\"])\n", + "[prensor] = s2t.calculate_prensors([query])\n", + "ragged_tensors = prensor.get_ragged_tensors()\n", + "\n", + "print(ragged_tensors)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UxLZtkdmM7lj" + }, + "source": [ + "## Apache Parquet Support\n", + "\n", + "`struct2tensor` offers an [Apache Parquet](https://parquet.apache.org/) [tf.DataSet](https://www.tensorflow.org/api_docs/python/tf/data/Dataset) that allows reading from a Parquet file and apply queries to manipulate the structure of the data.\n", + "\n", + "Because of the powerful struct2tensor library, the dataset will only read the Parquet columns that are required. This reduces I/O cost if we only need a select few columns." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7IHo8w3HMuyT" + }, + "source": [ + "### Preparation\n", + "\n", + "Please run the code cell at [Some Pretty Printing and Imports](#some-pretty-printing-and-imports) to ensure that all required modules are imported, and that pretty print works properly.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Vu02cwvfXVpf" + }, + "source": [ + "#### Prepare the input data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 68 + }, + "id": "xEDieagzNL-9", + "outputId": "773de200-cd49-496f-ff3f-60074be06bca" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " % Total % Received % Xferd Average Speed Time Time Time Current\n", + " Dload Upload Total Spent Left Speed\n", + "100 1657 100 1657 0 0 8122 0 --:--:-- --:--:-- --:--:-- 8122\n" + ] + } + ], + "source": [ + "# Download our sample data file from the struct2tensor repository. The desciption of the data is below.\n", + "#@test {\"skip\": true} \n", + "\n", + "!curl -o dremel_example.parquet 'https://raw.githubusercontent.com/google/struct2tensor/master/struct2tensor/testdata/parquet_testdata/dremel_example.parquet'" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Gn5J62JkNN-P" + }, + "source": [ + "### Example\n", + "\n", + "We will use a sample Parquet data file (*dremel_example.parquet*), which contains data based on the example used in this paper: https://storage.googleapis.com/pub-tools-public-publication-data/pdf/36632.pdf \n", + "\n", + "The file *dremel_example.parquet* has the following schema:\n", + "\n", + "```\n", + "message Document {\n", + " required int64 DocId;\n", + " optional group Links {\n", + " repeated int64 Backward;\n", + " repeated int64 Forward; }\n", + " repeated group Name {\n", + " repeated group Language {\n", + " required string Code;\n", + " optional string Country; }\n", + " optional string Url; }}\n", + " ```\n", + "\n", + "and contains the following data:\n", + " \n", + "\n", + "```\n", + "Document\n", + " DocId: 10\n", + " Links\n", + " Forward: 20\n", + " Forward: 40\n", + " Forward: 60\n", + " Name\n", + " Language\n", + " Code: 'en-us'\n", + " Country: 'us'\n", + " Language\n", + " Code: 'en'\n", + " Url: 'http://A'\n", + " Name\n", + " Url: 'http://B'\n", + " Name\n", + " Language\n", + " Code: 'en-gb'\n", + " Country: 'gb'\n", + "Document\n", + " DocId: 20\n", + " Links\n", + " Backward: 10\n", + " Backward: 30\n", + " Forward: 80\n", + " Name\n", + " Url: 'http://C'\n", + "```\n", + "\n", + "\n", + "In this example, we will promote and broadcast the field `Links.Forward` and project it.\n", + "\n", + "batch_size will be the number of records (`Document`) per prensor. This works with optional and repeated fields, and will be able to batch the entire record.\n", + "\n", + "Feel free to try `batch_size = 2` in the below code. (Note this parquet file only has 2 records (`Document`) total).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 442 + }, + "id": "A8RyaU3EX4av", + "outputId": "9235bd5b-cca6-45e0-8244-0f58d6bdcd9b" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.6/dist-packages/struct2tensor/expression_impl/parquet.py:65: FutureWarning: The 'field_by_name' method is deprecated, use 'field' instead\n", + " [arrow_schema.field_by_name(name) for name in arrow_schema.names]))\n", + "/usr/local/lib/python3.6/dist-packages/struct2tensor/expression_impl/parquet.py:396: FutureWarning: The 'field_by_name' method is deprecated, use 'field' instead\n", + " for step in curr_steps_as_set\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================\n", + "Schema of new_field prensor: \n", + "RootNodeTensor root\n", + " repeated ChildNodeTensor Name\n", + " repeated new_field\n", + "\n", + "Sparse tensor representation: \n", + "{Name.new_field: SparseTensor(values=[20, 40, 60, 20, 40, 60, 20, 40, 60], dense_shape=[1, 3, 3], \n", + " indices=[[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 0], [0, 1, 1], [0, 1, 2], [0, 2, 0], [0, 2, 1], [0, 2, 2]])}\n", + "\n", + "============================\n", + "Schema of new_field prensor: \n", + "RootNodeTensor root\n", + " repeated ChildNodeTensor Name\n", + " repeated new_field\n", + "\n", + "Sparse tensor representation: \n", + "{Name.new_field: SparseTensor(values=[80], dense_shape=[1, 1, 1], \n", + " indices=[[0, 0, 0]])}\n", + "\n", + "============================\n" + ] + } + ], + "source": [ + "#@test {\"skip\": true} \n", + "\n", + "from struct2tensor import expression_impl\n", + "\n", + "filenames = [\"dremel_example.parquet\"]\n", + "batch_size = 1\n", + "\n", + "exp = s2t.expression_impl.parquet.create_expression_from_parquet_file(filenames)\n", + "new_exp = exp.promote_and_broadcast({\"new_field\": \"Links.Forward\"}, \"Name\")\n", + "proj_exp = new_exp.project([\"Name.new_field\"])\n", + "proj_exp_needed = exp.project([\"Name.Url\"]) \n", + "# Please note that currently, proj_exp_needed needs to be passed into calculate.\n", + "# This is due to the way data is stored in parquet (values and repetition & \n", + "# definition levels). To construct the node for \"Name\", we need to read the \n", + "# values of a column containing \"Name\".\n", + "pqds = s2t.expression_impl.parquet.calculate_parquet_values([proj_exp, proj_exp_needed], exp, \n", + " filenames, batch_size)\n", + "\n", + "for prensors in pqds:\n", + " new_field_prensor = prensors[0]\n", + " print(\"============================\")\n", + " print(\"Schema of new_field prensor: \")\n", + " print(new_field_prensor)\n", + " print(\"\\nSparse tensor representation: \")\n", + " pretty.pprint(new_field_prensor)\n", + "print(\"============================\")" + ] + } + ], + "metadata": { + "colab": { + "collapsed_sections": [], + "last_runtime": { + "kind": "private" + }, + "name": "struct2tensor: ML on structured data", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/g3doc/stylesheets/extra.css b/g3doc/stylesheets/extra.css index 2c35c02..91c73fa 100644 --- a/g3doc/stylesheets/extra.css +++ b/g3doc/stylesheets/extra.css @@ -14,3 +14,6 @@ aspect-ratio: 16 / 9; } +p img { + background: white; +} diff --git a/mkdocs.yml b/mkdocs.yml index 5f84685..74469cf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -116,3 +116,5 @@ extra_javascript: watch: - struct2tensor nav: + - Examples: + - "Your structured data into Tensorflow": examples/prensor_playground From b44c44f72b1739bd48e1e1ebca55ef691f746a7a Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:24:21 -0700 Subject: [PATCH 03/21] Add links to api docs and overviews --- mkdocs.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index 74469cf..3c74c47 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -118,3 +118,9 @@ watch: nav: - Examples: - "Your structured data into Tensorflow": examples/prensor_playground + + - API Docs: + - "expression_impl": + Overview: api_docs/python/expression_impl.md + - "s2t": + Overview: api_docs/python/s2t.md From 660b1536b65206ec7275bb06b11f65ff4f74645b Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 24 Sep 2024 19:22:28 -0700 Subject: [PATCH 04/21] Get api docs up and running --- g3doc/api_docs/python/expression_impl.md | 70 --------- .../{ => deprecated}/_api_cache.json | 0 .../{ => deprecated}/_toc.yaml | 0 .../{ => deprecated}/all_symbols.md | 0 .../{ => deprecated}/apply_schema.md | 0 .../apply_schema/apply_schema.md | 0 .../{ => deprecated}/broadcast.md | 0 .../{ => deprecated}/broadcast/broadcast.md | 0 .../broadcast/broadcast_anonymous.md | 0 .../{ => deprecated}/depth_limit.md | 0 .../depth_limit/limit_depth.md | 0 .../{ => deprecated}/filter_expression.md | 0 .../filter_expression/filter_by_child.md | 0 .../filter_expression/filter_by_sibling.md | 0 .../expression_impl/deprecated/index.md | 136 ++++++++++++++++++ .../index/get_index_from_end.md | 0 .../index/get_positional_index.md | 0 .../{ => deprecated}/map_prensor.md | 0 .../map_prensor/map_ragged_tensor.md | 0 .../map_prensor/map_sparse_tensor.md | 0 .../map_prensor_to_prensor.md | 0 .../map_prensor_to_prensor/Schema.md | 0 .../map_prensor_to_prensor/create_schema.md | 0 .../map_prensor_to_prensor.md | 0 .../{ => deprecated}/map_values.md | 0 .../map_values/map_many_values.md | 0 .../{ => deprecated}/map_values/map_values.md | 0 .../map_values/map_values_anonymous.md | 0 .../{ => deprecated}/parquet.md | 0 .../parquet/ParquetDataset.md | 0 .../parquet/calculate_parquet_values.md | 0 .../create_expression_from_parquet_file.md | 0 .../{ => deprecated}/placeholder.md | 0 .../create_expression_from_schema.md | 0 .../get_placeholder_paths_from_graph.md | 0 .../{ => deprecated}/project.md | 0 .../{ => deprecated}/project/project.md | 0 .../{ => deprecated}/promote.md | 0 .../promote/PromoteChildExpression.md | 0 .../promote/PromoteExpression.md | 0 .../{ => deprecated}/promote/promote.md | 0 .../promote/promote_anonymous.md | 0 .../{ => deprecated}/promote_and_broadcast.md | 0 .../promote_and_broadcast.md | 0 .../promote_and_broadcast_anonymous.md | 0 .../expression_impl/{ => deprecated}/proto.md | 0 .../{ => deprecated}/proto/DescriptorPool.md | 0 .../proto/FileDescriptorSet.md | 0 .../{ => deprecated}/proto/ProtoExpression.md | 0 .../{ => deprecated}/proto/TransformFn.md | 0 ...ate_expression_from_file_descriptor_set.md | 0 .../proto/create_expression_from_proto.md | 0 .../proto/create_transformed_field.md | 0 .../proto/is_proto_expression.md | 0 .../{ => deprecated}/reroot.md | 0 .../reroot/create_proto_index_field.md | 0 .../{ => deprecated}/reroot/reroot.md | 0 .../expression_impl/{ => deprecated}/size.md | 0 .../{ => deprecated}/size/SizeExpression.md | 0 .../{ => deprecated}/size/has.md | 0 .../{ => deprecated}/size/size.md | 0 .../{ => deprecated}/size/size_anonymous.md | 0 .../{ => deprecated}/slice_expression.md | 0 .../slice_expression/IndexValue.md | 0 .../slice_expression/slice_expression.md | 0 .../python/expression_impl/expression_impl.md | 3 + .../api_docs/python/expression_impl/index.md | 136 +++++------------- .../s2t/{ => deprecated}/ChildNodeTensor.md | 0 .../python/s2t/{ => deprecated}/Expression.md | 0 .../s2t/{ => deprecated}/LeafNodeTensor.md | 0 .../python/s2t/{ => deprecated}/NodeTensor.md | 0 .../python/s2t/{ => deprecated}/Path.md | 0 .../python/s2t/{ => deprecated}/Prensor.md | 0 .../s2t/{ => deprecated}/RootNodeTensor.md | 0 .../python/s2t/{ => deprecated}/Step.md | 0 .../s2t/{ => deprecated}/_api_cache.json | 0 .../python/s2t/{ => deprecated}/_toc.yaml | 0 .../s2t/{ => deprecated}/all_symbols.md | 0 .../{ => deprecated}/calculate_prensors.md | 0 .../calculate_prensors_with_graph.md | 0 .../calculate_prensors_with_source_paths.md | 0 ...ate_expression_from_file_descriptor_set.md | 0 .../create_expression_from_prensor.md | 0 .../create_expression_from_proto.md | 0 .../s2t/{ => deprecated}/create_path.md | 0 .../create_prensor_from_descendant_nodes.md | 0 .../create_prensor_from_root_and_children.md | 0 .../{ => deprecated}/get_default_options.md | 0 .../get_options_with_minimal_checks.md | 0 .../s2t/{ => deprecated}/get_ragged_tensor.md | 0 .../{ => deprecated}/get_ragged_tensors.md | 0 .../s2t/{ => deprecated}/get_sparse_tensor.md | 0 .../{ => deprecated}/get_sparse_tensors.md | 0 .../api_docs/python/{s2t.md => s2t/index.md} | 0 g3doc/api_docs/python/s2t/s2t.md | 3 + mkdocs.yml | 8 +- 96 files changed, 182 insertions(+), 174 deletions(-) delete mode 100644 g3doc/api_docs/python/expression_impl.md rename g3doc/api_docs/python/expression_impl/{ => deprecated}/_api_cache.json (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/_toc.yaml (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/all_symbols.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/apply_schema.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/apply_schema/apply_schema.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/broadcast.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/broadcast/broadcast.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/broadcast/broadcast_anonymous.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/depth_limit.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/depth_limit/limit_depth.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/filter_expression.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/filter_expression/filter_by_child.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/filter_expression/filter_by_sibling.md (100%) create mode 100644 g3doc/api_docs/python/expression_impl/deprecated/index.md rename g3doc/api_docs/python/expression_impl/{ => deprecated}/index/get_index_from_end.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/index/get_positional_index.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/map_prensor.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/map_prensor/map_ragged_tensor.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/map_prensor/map_sparse_tensor.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/map_prensor_to_prensor.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/map_prensor_to_prensor/Schema.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/map_prensor_to_prensor/create_schema.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/map_prensor_to_prensor/map_prensor_to_prensor.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/map_values.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/map_values/map_many_values.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/map_values/map_values.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/map_values/map_values_anonymous.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/parquet.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/parquet/ParquetDataset.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/parquet/calculate_parquet_values.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/parquet/create_expression_from_parquet_file.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/placeholder.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/placeholder/create_expression_from_schema.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/placeholder/get_placeholder_paths_from_graph.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/project.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/project/project.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/promote.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/promote/PromoteChildExpression.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/promote/PromoteExpression.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/promote/promote.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/promote/promote_anonymous.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/promote_and_broadcast.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/promote_and_broadcast/promote_and_broadcast.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/promote_and_broadcast/promote_and_broadcast_anonymous.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/proto.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/proto/DescriptorPool.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/proto/FileDescriptorSet.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/proto/ProtoExpression.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/proto/TransformFn.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/proto/create_expression_from_file_descriptor_set.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/proto/create_expression_from_proto.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/proto/create_transformed_field.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/proto/is_proto_expression.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/reroot.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/reroot/create_proto_index_field.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/reroot/reroot.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/size.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/size/SizeExpression.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/size/has.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/size/size.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/size/size_anonymous.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/slice_expression.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/slice_expression/IndexValue.md (100%) rename g3doc/api_docs/python/expression_impl/{ => deprecated}/slice_expression/slice_expression.md (100%) create mode 100644 g3doc/api_docs/python/expression_impl/expression_impl.md rename g3doc/api_docs/python/s2t/{ => deprecated}/ChildNodeTensor.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/Expression.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/LeafNodeTensor.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/NodeTensor.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/Path.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/Prensor.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/RootNodeTensor.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/Step.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/_api_cache.json (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/_toc.yaml (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/all_symbols.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/calculate_prensors.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/calculate_prensors_with_graph.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/calculate_prensors_with_source_paths.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/create_expression_from_file_descriptor_set.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/create_expression_from_prensor.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/create_expression_from_proto.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/create_path.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/create_prensor_from_descendant_nodes.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/create_prensor_from_root_and_children.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/get_default_options.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/get_options_with_minimal_checks.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/get_ragged_tensor.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/get_ragged_tensors.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/get_sparse_tensor.md (100%) rename g3doc/api_docs/python/s2t/{ => deprecated}/get_sparse_tensors.md (100%) rename g3doc/api_docs/python/{s2t.md => s2t/index.md} (100%) create mode 100644 g3doc/api_docs/python/s2t/s2t.md diff --git a/g3doc/api_docs/python/expression_impl.md b/g3doc/api_docs/python/expression_impl.md deleted file mode 100644 index 40da37a..0000000 --- a/g3doc/api_docs/python/expression_impl.md +++ /dev/null @@ -1,70 +0,0 @@ -description: Import all modules in expression_impl. - -
- - -
- -# Module: expression_impl - - - - - - - - - -Import all modules in expression_impl. - - -The modules in this file should be accessed like the following: - -``` -import struct2tensor as s2t -from struct2tensor import expression_impl - -s2t.expression_impl.apply_schema -``` - -## Modules - -[`apply_schema`](./expression_impl/apply_schema.md) module: Apply a schema to an expression. - -[`broadcast`](./expression_impl/broadcast.md) module: Methods for broadcasting a path in a tree. - -[`depth_limit`](./expression_impl/depth_limit.md) module: Caps the depth of an expression. - -[`filter_expression`](./expression_impl/filter_expression.md) module: Create a new expression that is a filtered version of an original one. - -[`index`](./expression_impl/index.md) module: get_positional_index and get_index_from_end methods. - -[`map_prensor`](./expression_impl/map_prensor.md) module: Arbitrary operations from sparse and ragged tensors to a leaf field. - -[`map_prensor_to_prensor`](./expression_impl/map_prensor_to_prensor.md) module: Arbitrary operations from prensors to prensors in an expression. - -[`map_values`](./expression_impl/map_values.md) module: Maps the values of various leaves of the same child to a single result. - -[`parquet`](./expression_impl/parquet.md) module: Apache Parquet Dataset. - -[`placeholder`](./expression_impl/placeholder.md) module: Placeholder expression. - -[`project`](./expression_impl/project.md) module: project selects a subtree of an expression. - -[`promote`](./expression_impl/promote.md) module: Promote an expression to be a child of its grandparent. - -[`promote_and_broadcast`](./expression_impl/promote_and_broadcast.md) module: promote_and_broadcast a set of nodes. - -[`proto`](./expression_impl/proto.md) module: Expressions to parse a proto. - -[`reroot`](./expression_impl/reroot.md) module: Reroot to a subtree, maintaining an input proto index. - -[`size`](./expression_impl/size.md) module: Functions for creating new size or has expression. - -[`slice_expression`](./expression_impl/slice_expression.md) module: Implementation of slice. - diff --git a/g3doc/api_docs/python/expression_impl/_api_cache.json b/g3doc/api_docs/python/expression_impl/deprecated/_api_cache.json similarity index 100% rename from g3doc/api_docs/python/expression_impl/_api_cache.json rename to g3doc/api_docs/python/expression_impl/deprecated/_api_cache.json diff --git a/g3doc/api_docs/python/expression_impl/_toc.yaml b/g3doc/api_docs/python/expression_impl/deprecated/_toc.yaml similarity index 100% rename from g3doc/api_docs/python/expression_impl/_toc.yaml rename to g3doc/api_docs/python/expression_impl/deprecated/_toc.yaml diff --git a/g3doc/api_docs/python/expression_impl/all_symbols.md b/g3doc/api_docs/python/expression_impl/deprecated/all_symbols.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/all_symbols.md rename to g3doc/api_docs/python/expression_impl/deprecated/all_symbols.md diff --git a/g3doc/api_docs/python/expression_impl/apply_schema.md b/g3doc/api_docs/python/expression_impl/deprecated/apply_schema.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/apply_schema.md rename to g3doc/api_docs/python/expression_impl/deprecated/apply_schema.md diff --git a/g3doc/api_docs/python/expression_impl/apply_schema/apply_schema.md b/g3doc/api_docs/python/expression_impl/deprecated/apply_schema/apply_schema.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/apply_schema/apply_schema.md rename to g3doc/api_docs/python/expression_impl/deprecated/apply_schema/apply_schema.md diff --git a/g3doc/api_docs/python/expression_impl/broadcast.md b/g3doc/api_docs/python/expression_impl/deprecated/broadcast.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/broadcast.md rename to g3doc/api_docs/python/expression_impl/deprecated/broadcast.md diff --git a/g3doc/api_docs/python/expression_impl/broadcast/broadcast.md b/g3doc/api_docs/python/expression_impl/deprecated/broadcast/broadcast.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/broadcast/broadcast.md rename to g3doc/api_docs/python/expression_impl/deprecated/broadcast/broadcast.md diff --git a/g3doc/api_docs/python/expression_impl/broadcast/broadcast_anonymous.md b/g3doc/api_docs/python/expression_impl/deprecated/broadcast/broadcast_anonymous.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/broadcast/broadcast_anonymous.md rename to g3doc/api_docs/python/expression_impl/deprecated/broadcast/broadcast_anonymous.md diff --git a/g3doc/api_docs/python/expression_impl/depth_limit.md b/g3doc/api_docs/python/expression_impl/deprecated/depth_limit.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/depth_limit.md rename to g3doc/api_docs/python/expression_impl/deprecated/depth_limit.md diff --git a/g3doc/api_docs/python/expression_impl/depth_limit/limit_depth.md b/g3doc/api_docs/python/expression_impl/deprecated/depth_limit/limit_depth.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/depth_limit/limit_depth.md rename to g3doc/api_docs/python/expression_impl/deprecated/depth_limit/limit_depth.md diff --git a/g3doc/api_docs/python/expression_impl/filter_expression.md b/g3doc/api_docs/python/expression_impl/deprecated/filter_expression.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/filter_expression.md rename to g3doc/api_docs/python/expression_impl/deprecated/filter_expression.md diff --git a/g3doc/api_docs/python/expression_impl/filter_expression/filter_by_child.md b/g3doc/api_docs/python/expression_impl/deprecated/filter_expression/filter_by_child.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/filter_expression/filter_by_child.md rename to g3doc/api_docs/python/expression_impl/deprecated/filter_expression/filter_by_child.md diff --git a/g3doc/api_docs/python/expression_impl/filter_expression/filter_by_sibling.md b/g3doc/api_docs/python/expression_impl/deprecated/filter_expression/filter_by_sibling.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/filter_expression/filter_by_sibling.md rename to g3doc/api_docs/python/expression_impl/deprecated/filter_expression/filter_by_sibling.md diff --git a/g3doc/api_docs/python/expression_impl/deprecated/index.md b/g3doc/api_docs/python/expression_impl/deprecated/index.md new file mode 100644 index 0000000..822383a --- /dev/null +++ b/g3doc/api_docs/python/expression_impl/deprecated/index.md @@ -0,0 +1,136 @@ +description: get_positional_index and get_index_from_end methods. + +
+ + +
+ +# Module: expression_impl.index + + + + + + + + + +get_positional_index and get_index_from_end methods. + + +The parent_index identifies the index of the parent of each element. These +methods take the parent_index to determine the relationship with respect to +other elements. + +#### Given: + + + +``` +session: { + event: { + val: 111 + } + event: { + val: 121 + val: 122 + } +} + +session: { + event: { + val: 10 + val: 7 + } + event: { + val: 1 + } +} +``` + +``` +get_positional_index(expr, path.Path(["event","val"]), "val_index") +``` + +yields: + +``` +session: { + event: { + val: 111 + val_index: 0 + } + event: { + val: 121 + val: 122 + val_index: 0 + val_index: 1 + } +} + +session: { + event: { + val: 10 + val: 7 + val_index: 0 + val_index: 1 + } + event: { + val: 1 + val_index: 0 + } +} +``` + +``` +get_index_from_end(expr, path.Path(["event","val"]), "neg_val_index") +``` +yields: + +``` +session: { + event: { + val: 111 + neg_val_index: -1 + } + event: { + val: 121 + val: 122 + neg_val_index: -2 + neg_val_index: -1 + } +} + +session: { + event: { + val: 10 + val: 7 + neg_val_index: 2 + neg_val_index: -1 + } + event: { + val: 1 + neg_val_index: -1 + } +} +``` + +These methods are useful when you want to depend upon the index of a field. +For example, if you want to filter examples based upon their index, or +cogroup two fields by index, then first creating the index is useful. + +Note that while the parent indices of these fields seem like overhead, they +are just references to the parent indices of other fields, and are therefore +take little memory or CPU. + +## Functions + +[`get_index_from_end(...)`](../expression_impl/index/get_index_from_end.md): Gets the number of steps from the end of the array. + +[`get_positional_index(...)`](../expression_impl/index/get_positional_index.md): Gets the positional index. + diff --git a/g3doc/api_docs/python/expression_impl/index/get_index_from_end.md b/g3doc/api_docs/python/expression_impl/deprecated/index/get_index_from_end.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/index/get_index_from_end.md rename to g3doc/api_docs/python/expression_impl/deprecated/index/get_index_from_end.md diff --git a/g3doc/api_docs/python/expression_impl/index/get_positional_index.md b/g3doc/api_docs/python/expression_impl/deprecated/index/get_positional_index.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/index/get_positional_index.md rename to g3doc/api_docs/python/expression_impl/deprecated/index/get_positional_index.md diff --git a/g3doc/api_docs/python/expression_impl/map_prensor.md b/g3doc/api_docs/python/expression_impl/deprecated/map_prensor.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/map_prensor.md rename to g3doc/api_docs/python/expression_impl/deprecated/map_prensor.md diff --git a/g3doc/api_docs/python/expression_impl/map_prensor/map_ragged_tensor.md b/g3doc/api_docs/python/expression_impl/deprecated/map_prensor/map_ragged_tensor.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/map_prensor/map_ragged_tensor.md rename to g3doc/api_docs/python/expression_impl/deprecated/map_prensor/map_ragged_tensor.md diff --git a/g3doc/api_docs/python/expression_impl/map_prensor/map_sparse_tensor.md b/g3doc/api_docs/python/expression_impl/deprecated/map_prensor/map_sparse_tensor.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/map_prensor/map_sparse_tensor.md rename to g3doc/api_docs/python/expression_impl/deprecated/map_prensor/map_sparse_tensor.md diff --git a/g3doc/api_docs/python/expression_impl/map_prensor_to_prensor.md b/g3doc/api_docs/python/expression_impl/deprecated/map_prensor_to_prensor.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/map_prensor_to_prensor.md rename to g3doc/api_docs/python/expression_impl/deprecated/map_prensor_to_prensor.md diff --git a/g3doc/api_docs/python/expression_impl/map_prensor_to_prensor/Schema.md b/g3doc/api_docs/python/expression_impl/deprecated/map_prensor_to_prensor/Schema.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/map_prensor_to_prensor/Schema.md rename to g3doc/api_docs/python/expression_impl/deprecated/map_prensor_to_prensor/Schema.md diff --git a/g3doc/api_docs/python/expression_impl/map_prensor_to_prensor/create_schema.md b/g3doc/api_docs/python/expression_impl/deprecated/map_prensor_to_prensor/create_schema.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/map_prensor_to_prensor/create_schema.md rename to g3doc/api_docs/python/expression_impl/deprecated/map_prensor_to_prensor/create_schema.md diff --git a/g3doc/api_docs/python/expression_impl/map_prensor_to_prensor/map_prensor_to_prensor.md b/g3doc/api_docs/python/expression_impl/deprecated/map_prensor_to_prensor/map_prensor_to_prensor.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/map_prensor_to_prensor/map_prensor_to_prensor.md rename to g3doc/api_docs/python/expression_impl/deprecated/map_prensor_to_prensor/map_prensor_to_prensor.md diff --git a/g3doc/api_docs/python/expression_impl/map_values.md b/g3doc/api_docs/python/expression_impl/deprecated/map_values.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/map_values.md rename to g3doc/api_docs/python/expression_impl/deprecated/map_values.md diff --git a/g3doc/api_docs/python/expression_impl/map_values/map_many_values.md b/g3doc/api_docs/python/expression_impl/deprecated/map_values/map_many_values.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/map_values/map_many_values.md rename to g3doc/api_docs/python/expression_impl/deprecated/map_values/map_many_values.md diff --git a/g3doc/api_docs/python/expression_impl/map_values/map_values.md b/g3doc/api_docs/python/expression_impl/deprecated/map_values/map_values.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/map_values/map_values.md rename to g3doc/api_docs/python/expression_impl/deprecated/map_values/map_values.md diff --git a/g3doc/api_docs/python/expression_impl/map_values/map_values_anonymous.md b/g3doc/api_docs/python/expression_impl/deprecated/map_values/map_values_anonymous.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/map_values/map_values_anonymous.md rename to g3doc/api_docs/python/expression_impl/deprecated/map_values/map_values_anonymous.md diff --git a/g3doc/api_docs/python/expression_impl/parquet.md b/g3doc/api_docs/python/expression_impl/deprecated/parquet.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/parquet.md rename to g3doc/api_docs/python/expression_impl/deprecated/parquet.md diff --git a/g3doc/api_docs/python/expression_impl/parquet/ParquetDataset.md b/g3doc/api_docs/python/expression_impl/deprecated/parquet/ParquetDataset.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/parquet/ParquetDataset.md rename to g3doc/api_docs/python/expression_impl/deprecated/parquet/ParquetDataset.md diff --git a/g3doc/api_docs/python/expression_impl/parquet/calculate_parquet_values.md b/g3doc/api_docs/python/expression_impl/deprecated/parquet/calculate_parquet_values.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/parquet/calculate_parquet_values.md rename to g3doc/api_docs/python/expression_impl/deprecated/parquet/calculate_parquet_values.md diff --git a/g3doc/api_docs/python/expression_impl/parquet/create_expression_from_parquet_file.md b/g3doc/api_docs/python/expression_impl/deprecated/parquet/create_expression_from_parquet_file.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/parquet/create_expression_from_parquet_file.md rename to g3doc/api_docs/python/expression_impl/deprecated/parquet/create_expression_from_parquet_file.md diff --git a/g3doc/api_docs/python/expression_impl/placeholder.md b/g3doc/api_docs/python/expression_impl/deprecated/placeholder.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/placeholder.md rename to g3doc/api_docs/python/expression_impl/deprecated/placeholder.md diff --git a/g3doc/api_docs/python/expression_impl/placeholder/create_expression_from_schema.md b/g3doc/api_docs/python/expression_impl/deprecated/placeholder/create_expression_from_schema.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/placeholder/create_expression_from_schema.md rename to g3doc/api_docs/python/expression_impl/deprecated/placeholder/create_expression_from_schema.md diff --git a/g3doc/api_docs/python/expression_impl/placeholder/get_placeholder_paths_from_graph.md b/g3doc/api_docs/python/expression_impl/deprecated/placeholder/get_placeholder_paths_from_graph.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/placeholder/get_placeholder_paths_from_graph.md rename to g3doc/api_docs/python/expression_impl/deprecated/placeholder/get_placeholder_paths_from_graph.md diff --git a/g3doc/api_docs/python/expression_impl/project.md b/g3doc/api_docs/python/expression_impl/deprecated/project.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/project.md rename to g3doc/api_docs/python/expression_impl/deprecated/project.md diff --git a/g3doc/api_docs/python/expression_impl/project/project.md b/g3doc/api_docs/python/expression_impl/deprecated/project/project.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/project/project.md rename to g3doc/api_docs/python/expression_impl/deprecated/project/project.md diff --git a/g3doc/api_docs/python/expression_impl/promote.md b/g3doc/api_docs/python/expression_impl/deprecated/promote.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/promote.md rename to g3doc/api_docs/python/expression_impl/deprecated/promote.md diff --git a/g3doc/api_docs/python/expression_impl/promote/PromoteChildExpression.md b/g3doc/api_docs/python/expression_impl/deprecated/promote/PromoteChildExpression.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/promote/PromoteChildExpression.md rename to g3doc/api_docs/python/expression_impl/deprecated/promote/PromoteChildExpression.md diff --git a/g3doc/api_docs/python/expression_impl/promote/PromoteExpression.md b/g3doc/api_docs/python/expression_impl/deprecated/promote/PromoteExpression.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/promote/PromoteExpression.md rename to g3doc/api_docs/python/expression_impl/deprecated/promote/PromoteExpression.md diff --git a/g3doc/api_docs/python/expression_impl/promote/promote.md b/g3doc/api_docs/python/expression_impl/deprecated/promote/promote.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/promote/promote.md rename to g3doc/api_docs/python/expression_impl/deprecated/promote/promote.md diff --git a/g3doc/api_docs/python/expression_impl/promote/promote_anonymous.md b/g3doc/api_docs/python/expression_impl/deprecated/promote/promote_anonymous.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/promote/promote_anonymous.md rename to g3doc/api_docs/python/expression_impl/deprecated/promote/promote_anonymous.md diff --git a/g3doc/api_docs/python/expression_impl/promote_and_broadcast.md b/g3doc/api_docs/python/expression_impl/deprecated/promote_and_broadcast.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/promote_and_broadcast.md rename to g3doc/api_docs/python/expression_impl/deprecated/promote_and_broadcast.md diff --git a/g3doc/api_docs/python/expression_impl/promote_and_broadcast/promote_and_broadcast.md b/g3doc/api_docs/python/expression_impl/deprecated/promote_and_broadcast/promote_and_broadcast.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/promote_and_broadcast/promote_and_broadcast.md rename to g3doc/api_docs/python/expression_impl/deprecated/promote_and_broadcast/promote_and_broadcast.md diff --git a/g3doc/api_docs/python/expression_impl/promote_and_broadcast/promote_and_broadcast_anonymous.md b/g3doc/api_docs/python/expression_impl/deprecated/promote_and_broadcast/promote_and_broadcast_anonymous.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/promote_and_broadcast/promote_and_broadcast_anonymous.md rename to g3doc/api_docs/python/expression_impl/deprecated/promote_and_broadcast/promote_and_broadcast_anonymous.md diff --git a/g3doc/api_docs/python/expression_impl/proto.md b/g3doc/api_docs/python/expression_impl/deprecated/proto.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/proto.md rename to g3doc/api_docs/python/expression_impl/deprecated/proto.md diff --git a/g3doc/api_docs/python/expression_impl/proto/DescriptorPool.md b/g3doc/api_docs/python/expression_impl/deprecated/proto/DescriptorPool.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/proto/DescriptorPool.md rename to g3doc/api_docs/python/expression_impl/deprecated/proto/DescriptorPool.md diff --git a/g3doc/api_docs/python/expression_impl/proto/FileDescriptorSet.md b/g3doc/api_docs/python/expression_impl/deprecated/proto/FileDescriptorSet.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/proto/FileDescriptorSet.md rename to g3doc/api_docs/python/expression_impl/deprecated/proto/FileDescriptorSet.md diff --git a/g3doc/api_docs/python/expression_impl/proto/ProtoExpression.md b/g3doc/api_docs/python/expression_impl/deprecated/proto/ProtoExpression.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/proto/ProtoExpression.md rename to g3doc/api_docs/python/expression_impl/deprecated/proto/ProtoExpression.md diff --git a/g3doc/api_docs/python/expression_impl/proto/TransformFn.md b/g3doc/api_docs/python/expression_impl/deprecated/proto/TransformFn.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/proto/TransformFn.md rename to g3doc/api_docs/python/expression_impl/deprecated/proto/TransformFn.md diff --git a/g3doc/api_docs/python/expression_impl/proto/create_expression_from_file_descriptor_set.md b/g3doc/api_docs/python/expression_impl/deprecated/proto/create_expression_from_file_descriptor_set.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/proto/create_expression_from_file_descriptor_set.md rename to g3doc/api_docs/python/expression_impl/deprecated/proto/create_expression_from_file_descriptor_set.md diff --git a/g3doc/api_docs/python/expression_impl/proto/create_expression_from_proto.md b/g3doc/api_docs/python/expression_impl/deprecated/proto/create_expression_from_proto.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/proto/create_expression_from_proto.md rename to g3doc/api_docs/python/expression_impl/deprecated/proto/create_expression_from_proto.md diff --git a/g3doc/api_docs/python/expression_impl/proto/create_transformed_field.md b/g3doc/api_docs/python/expression_impl/deprecated/proto/create_transformed_field.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/proto/create_transformed_field.md rename to g3doc/api_docs/python/expression_impl/deprecated/proto/create_transformed_field.md diff --git a/g3doc/api_docs/python/expression_impl/proto/is_proto_expression.md b/g3doc/api_docs/python/expression_impl/deprecated/proto/is_proto_expression.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/proto/is_proto_expression.md rename to g3doc/api_docs/python/expression_impl/deprecated/proto/is_proto_expression.md diff --git a/g3doc/api_docs/python/expression_impl/reroot.md b/g3doc/api_docs/python/expression_impl/deprecated/reroot.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/reroot.md rename to g3doc/api_docs/python/expression_impl/deprecated/reroot.md diff --git a/g3doc/api_docs/python/expression_impl/reroot/create_proto_index_field.md b/g3doc/api_docs/python/expression_impl/deprecated/reroot/create_proto_index_field.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/reroot/create_proto_index_field.md rename to g3doc/api_docs/python/expression_impl/deprecated/reroot/create_proto_index_field.md diff --git a/g3doc/api_docs/python/expression_impl/reroot/reroot.md b/g3doc/api_docs/python/expression_impl/deprecated/reroot/reroot.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/reroot/reroot.md rename to g3doc/api_docs/python/expression_impl/deprecated/reroot/reroot.md diff --git a/g3doc/api_docs/python/expression_impl/size.md b/g3doc/api_docs/python/expression_impl/deprecated/size.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/size.md rename to g3doc/api_docs/python/expression_impl/deprecated/size.md diff --git a/g3doc/api_docs/python/expression_impl/size/SizeExpression.md b/g3doc/api_docs/python/expression_impl/deprecated/size/SizeExpression.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/size/SizeExpression.md rename to g3doc/api_docs/python/expression_impl/deprecated/size/SizeExpression.md diff --git a/g3doc/api_docs/python/expression_impl/size/has.md b/g3doc/api_docs/python/expression_impl/deprecated/size/has.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/size/has.md rename to g3doc/api_docs/python/expression_impl/deprecated/size/has.md diff --git a/g3doc/api_docs/python/expression_impl/size/size.md b/g3doc/api_docs/python/expression_impl/deprecated/size/size.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/size/size.md rename to g3doc/api_docs/python/expression_impl/deprecated/size/size.md diff --git a/g3doc/api_docs/python/expression_impl/size/size_anonymous.md b/g3doc/api_docs/python/expression_impl/deprecated/size/size_anonymous.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/size/size_anonymous.md rename to g3doc/api_docs/python/expression_impl/deprecated/size/size_anonymous.md diff --git a/g3doc/api_docs/python/expression_impl/slice_expression.md b/g3doc/api_docs/python/expression_impl/deprecated/slice_expression.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/slice_expression.md rename to g3doc/api_docs/python/expression_impl/deprecated/slice_expression.md diff --git a/g3doc/api_docs/python/expression_impl/slice_expression/IndexValue.md b/g3doc/api_docs/python/expression_impl/deprecated/slice_expression/IndexValue.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/slice_expression/IndexValue.md rename to g3doc/api_docs/python/expression_impl/deprecated/slice_expression/IndexValue.md diff --git a/g3doc/api_docs/python/expression_impl/slice_expression/slice_expression.md b/g3doc/api_docs/python/expression_impl/deprecated/slice_expression/slice_expression.md similarity index 100% rename from g3doc/api_docs/python/expression_impl/slice_expression/slice_expression.md rename to g3doc/api_docs/python/expression_impl/deprecated/slice_expression/slice_expression.md diff --git a/g3doc/api_docs/python/expression_impl/expression_impl.md b/g3doc/api_docs/python/expression_impl/expression_impl.md new file mode 100644 index 0000000..5c805f6 --- /dev/null +++ b/g3doc/api_docs/python/expression_impl/expression_impl.md @@ -0,0 +1,3 @@ +# `expression_impl` + +::: struct2tensor.expression_impl diff --git a/g3doc/api_docs/python/expression_impl/index.md b/g3doc/api_docs/python/expression_impl/index.md index 822383a..40da37a 100644 --- a/g3doc/api_docs/python/expression_impl/index.md +++ b/g3doc/api_docs/python/expression_impl/index.md @@ -1,17 +1,17 @@ -description: get_positional_index and get_index_from_end methods. +description: Import all modules in expression_impl.
- +
-# Module: expression_impl.index +# Module: expression_impl