darabos commited on
Commit
956034e
·
unverified ·
2 Parent(s): 6b0a3b26cc3607

Merge pull request #225 from biggraph/darabos-delete-lynxkite

Browse files

Instead of forking the open-source LynxKite, take it as a dependency

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .github/workflows/docs.yaml +0 -28
  2. .github/workflows/test.yaml +4 -35
  3. .pre-commit-config.yaml +14 -24
  4. docs/assets/lynxkite-icon-white.png +0 -0
  5. docs/contributing.md +0 -56
  6. docs/guides/analytics.md +0 -82
  7. docs/guides/plugins.md +0 -283
  8. docs/guides/quickstart.md +0 -27
  9. docs/index.md +0 -5
  10. docs/license.md +0 -11
  11. docs/lynxkite-core.md +0 -6
  12. docs/lynxkite-graph-analytics.md +0 -6
  13. docs/reference/lynxkite-core/executors/one_by_one.md +0 -1
  14. docs/reference/lynxkite-core/executors/simple.md +0 -1
  15. docs/reference/lynxkite-core/ops.md +0 -1
  16. docs/reference/lynxkite-core/workspace.md +0 -1
  17. docs/reference/lynxkite-graph-analytics/core.md +0 -1
  18. docs/reference/lynxkite-graph-analytics/operations.md +0 -3
  19. docs/stylesheets/extra.css +0 -8
  20. examples/Airlines demo.lynxkite.json +0 -0
  21. examples/Cheminformatics/chembl_tools.py +1 -1
  22. examples/Cheminformatics/cheminfo_tools.py +1 -1
  23. examples/Cheminformatics/rcsb_api.py +1 -1
  24. examples/Image processing.lynxkite.json +0 -303
  25. examples/Image table.lynxkite.json +0 -364
  26. examples/Model definition.lynxkite.json +0 -671
  27. examples/Model use.lynxkite.json +0 -0
  28. examples/Multi-output demo.lynxkite.json +0 -301
  29. examples/NetworkX demo.lynxkite.json +0 -0
  30. examples/Word2vec.lynxkite.json +0 -0
  31. examples/fake_data.py +0 -21
  32. examples/make_image_table.py +0 -11
  33. examples/matplotlib_example.py +0 -34
  34. examples/multi_output_demo.py +0 -24
  35. examples/ode_lstm.py +0 -54
  36. examples/requirements.txt +0 -3
  37. examples/sql.lynxkite.json +0 -0
  38. examples/uploads/example-pizza.md +0 -136
  39. examples/uploads/molecules2.csv +0 -4
  40. examples/uploads/plus-one-dataset.parquet +0 -0
  41. examples/word2vec.py +0 -27
  42. lynxkite-app/.gitignore +0 -5
  43. lynxkite-app/MANIFEST.in +0 -2
  44. lynxkite-app/README.md +0 -31
  45. lynxkite-app/pyproject.toml +0 -60
  46. lynxkite-app/src/build_frontend.py +0 -26
  47. lynxkite-app/src/lynxkite_app/__init__.py +0 -0
  48. lynxkite-app/src/lynxkite_app/__main__.py +0 -20
  49. lynxkite-app/src/lynxkite_app/crdt.py +0 -342
  50. lynxkite-app/src/lynxkite_app/main.py +0 -165
.github/workflows/docs.yaml DELETED
@@ -1,28 +0,0 @@
1
- name: docs
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- permissions:
7
- contents: write
8
- jobs:
9
- deploy:
10
- runs-on: ubuntu-latest
11
- steps:
12
- - uses: actions/checkout@v4
13
- - name: Configure Git Credentials
14
- run: |
15
- git config user.name github-actions[bot]
16
- git config user.email 41898282+github-actions[bot]@users.noreply.github.com
17
- - uses: actions/setup-python@v5
18
- with:
19
- python-version: 3.x
20
- - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
21
- - uses: actions/cache@v4
22
- with:
23
- key: mkdocs-material-${{ env.cache_id }}
24
- path: .cache
25
- restore-keys: |
26
- mkdocs-material-
27
- - run: pip install mkdocs-material mkdocstrings-python mkdocs-autorefs
28
- - run: mkdocs gh-deploy --force
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/workflows/test.yaml CHANGED
@@ -15,53 +15,22 @@ jobs:
15
  - name: Set up Python
16
  run: uv python install
17
 
18
- # - name: Debug ty issue
19
- # run: |
20
- # uv pip install ty
21
- # uv pip list
22
- # uv run python -m ty check
23
-
24
  - name: Install dependencies
25
  run: |
26
  eval `ssh-agent -s`
27
  ssh-add - <<< '${{ secrets.LYNXSCRIBE_DEPLOY_KEY }}'
28
  uv venv
29
  . .venv/bin/activate
30
- uv pip install \
31
- -e lynxkite-core/[dev] \
32
- -e lynxkite-app/[dev] \
33
- -e lynxkite-graph-analytics/[dev] \
34
- -e lynxkite-bio \
35
- -e lynxkite-lynxscribe/ \
36
- -e lynxkite-pillow-example/
37
 
38
  - name: Run pre-commits
39
  run: |
40
- uv pip install pre-commit
41
- uv run pre-commit run --all-files
42
 
43
  - name: Run Python unittests
44
  run: |
45
- uv run pytest --asyncio-mode=auto
46
-
47
- - name: Build the documentation
48
- run: |
49
- uv pip install mkdocs-material mkdocstrings[python]
50
- uv run mkdocs build
51
-
52
- - uses: actions/setup-node@v4
53
- with:
54
- node-version: lts/*
55
-
56
- - name: Install frontend dependencies
57
- run: |
58
- cd lynxkite-app/web
59
- npm i
60
- npx playwright install --with-deps
61
-
62
- - name: Run Playwright tests
63
- run: |
64
- uv run bash -c 'cd lynxkite-app/web; npm run build; npm run test'
65
 
66
  - uses: actions/upload-artifact@v4
67
  name: Upload playwright report
 
15
  - name: Set up Python
16
  run: uv python install
17
 
 
 
 
 
 
 
18
  - name: Install dependencies
19
  run: |
20
  eval `ssh-agent -s`
21
  ssh-add - <<< '${{ secrets.LYNXSCRIBE_DEPLOY_KEY }}'
22
  uv venv
23
  . .venv/bin/activate
24
+ echo PATH=$PATH >> $GITHUB_ENV
25
+ uv sync
 
 
 
 
 
26
 
27
  - name: Run pre-commits
28
  run: |
29
+ pre-commit run --all-files
 
30
 
31
  - name: Run Python unittests
32
  run: |
33
+ pytest
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  - uses: actions/upload-artifact@v4
36
  name: Upload playwright report
.pre-commit-config.yaml CHANGED
@@ -11,32 +11,22 @@ repos:
11
  - id: ruff
12
  args: [ --fix ]
13
  - id: ruff-format
14
- - repo: https://github.com/biomejs/pre-commit
15
- rev: v1.9.4
16
- hooks:
17
- - id: biome-check
18
- # https://github.com/astral-sh/ty/issues/269
19
- - repo: local
20
- hooks:
21
- - id: ty-check
22
- name: ty-check
23
- language: python
24
- entry: uv run ty check
25
- pass_filenames: false
26
- args: [--python=.venv/]
27
- additional_dependencies: [ty]
28
  - repo: https://github.com/fpgmaas/deptry.git
29
  rev: "0.23.0"
30
  hooks:
31
  - id: deptry
32
- name: deptry for lynxkite-app
33
- entry: bash -c 'cd lynxkite-app && deptry .'
34
- - id: deptry
35
- name: deptry for lynxkite-core
36
- entry: bash -c 'cd lynxkite-core && deptry .'
37
- - id: deptry
38
- name: deptry for lynxkite-graph-analytics
39
- entry: bash -c 'cd lynxkite-graph-analytics && deptry .'
40
  - id: deptry
41
- name: deptry for lynxkite-pillow-example
42
- entry: bash -c 'cd lynxkite-pillow-example && deptry .'
 
11
  - id: ruff
12
  args: [ --fix ]
13
  - id: ruff-format
14
+ # # https://github.com/astral-sh/ty/issues/269
15
+ # - repo: local
16
+ # hooks:
17
+ # - id: ty-check
18
+ # name: ty-check
19
+ # language: python
20
+ # entry: uv run ty check
21
+ # pass_filenames: false
22
+ # args: [--python=.venv/]
23
+ # additional_dependencies: [ty]
 
 
 
 
24
  - repo: https://github.com/fpgmaas/deptry.git
25
  rev: "0.23.0"
26
  hooks:
27
  - id: deptry
28
+ name: deptry for lynxkite-bio
29
+ entry: bash -c 'cd lynxkite-bio && deptry .'
 
 
 
 
 
 
30
  - id: deptry
31
+ name: deptry for lynxkite-lynxscribe
32
+ entry: bash -c 'cd lynxkite-lynxscribe && deptry .'
docs/assets/lynxkite-icon-white.png DELETED
Binary file (3.19 kB)
 
docs/contributing.md DELETED
@@ -1,56 +0,0 @@
1
- # Contributing
2
-
3
- The LynxKite 2000:MM repository lives at
4
- [https://github.com/lynxkite/lynxkite-2000](https://github.com/lynxkite/lynxkite-2000). Bug reports, feature requests,
5
- and pull requests are welcome!
6
-
7
- ## Project structure
8
-
9
- - `lynxkite-core`: Core types and utilities. Depend on this lightweight package if you are writing LynxKite plugins.
10
- - `lynxkite-app`: The LynxKite web application. Install some plugins then run this to use LynxKite.
11
- - `lynxkite-graph-analytics`: Graph analytics plugin. The classical LynxKite experience!
12
- - `lynxkite-pillow`: A simple example plugin.
13
- - `lynxkite-lynxscribe`: A plugin for building and running LynxScribe applications.
14
- - `lynxkite-bio`: Bioinformatics additions for LynxKite Graph Analytics.
15
- - `docs`: User-facing documentation. It's shared between all packages.
16
-
17
- ## Development setup
18
-
19
- Install everything like this:
20
-
21
- ```bash
22
- uv venv
23
- source .venv/bin/activate
24
- uvx pre-commit install
25
- uv sync
26
- ```
27
-
28
- This also builds the frontend, hopefully very quickly. To run it:
29
-
30
- ```bash
31
- cd examples
32
- lynxkite
33
- ```
34
-
35
- If you also want to make changes to the frontend with hot reloading:
36
-
37
- ```bash
38
- cd lynxkite-app/web
39
- npm run dev
40
- ```
41
-
42
- ## Executing tests
43
-
44
- ```bash
45
- pytest # Runs all backend unit tests.
46
- pytest lynxkite-core # Runs tests for one package.
47
- cd lynxkite-app/web && npm run test # Runs frontend tests.
48
- ```
49
-
50
- ## Documentation
51
-
52
- To work on the documentation:
53
-
54
- ```bash
55
- mkdocs serve
56
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/guides/analytics.md DELETED
@@ -1,82 +0,0 @@
1
- # Graph analytics & data science
2
-
3
- Install LynxKite with the graph analytics package:
4
-
5
- ```bash
6
- pip install lynxkite lynxkite-graph-analytics
7
- ```
8
-
9
- Run LynxKite in your data directory:
10
-
11
- ```bash
12
- cd lynxkite-data
13
- lynxkite
14
- ```
15
-
16
- LynxKite by default runs on port 8000, so you can access it in your browser at
17
- [http://localhost:8000](http://localhost:8000).
18
- To run it on a different port, set the `PORT` environment variable (e.g., `PORT=8080 lynxkite`).
19
-
20
- ## Using a CUDA GPU
21
-
22
- To make full use of your GPU, install the `lynxkite-graph-analytics` package with GPU support.
23
-
24
- ```bash
25
- pip install lynxkite 'lynxkite-graph-analytics[gpu]'
26
- ```
27
-
28
- And start it with the cuGraph backend for NetworkX:
29
-
30
- ```bash
31
- NX_CUGRAPH_AUTOCONFIG=true lynxkite
32
- ```
33
-
34
- ## Directory browser
35
-
36
- When you open the LynxKite web interface, you arrive in the directory browser. You see
37
- the files and directories in your data directory — if you just created it, it will be empty.
38
-
39
- You can create workspaces, [code files](plugins.md), and folders in the directory browser.
40
-
41
- ## Workspaces
42
-
43
- A LynxKite workspace is the place where you build a data science pipeline.
44
- Pipelines are built from boxes, which have inputs and outputs that can be connected to each other.
45
-
46
- To place a box, click anywhere in the workspace. This opens a search menu where you can
47
- find the box you want to add.
48
-
49
- ## Importing your data
50
-
51
- To import CSV, Parquet, JSON, and Excel files, you can simply drag and drop them into the LynxKite workspace.
52
- This will upload the file to the server and add an "Import file" box to the workspace.
53
-
54
- You can also create "Import file" boxes manually and type the path to the file.
55
- You can either use an absolute path, or a relative path from the data directory.
56
- (Where you started LynxKite.)
57
-
58
- ## Neural network design
59
-
60
- The graph analytics package includes two environments, _"LynxKite Graph Analytics"_, and _"PyTorch model"_.
61
- Use the dropdown in the top right corner to switch to the "PyTorch model" environment.
62
- This environment allows you to define neural network architectures visually.
63
-
64
- The important parts of a neural network definition are:
65
-
66
- - **Inputs**: These boxes stand for the inputs. You will connect them to actual data in the workspace that
67
- uses this model.
68
- - **Layers**: The heart of the model. Use the _"Repeat"_ box looping back from the output of a layer to the
69
- input of an earlier layer to repeat a set of layers.
70
- - **Outputs**: These boxes mark the place in the data flow that holds the predictions of the model.
71
- - **Loss**: Build the loss computation after the output box. This part is only used during training.
72
- - **Optimizer**: The result of the loss computation goes into the optimizer. Training is partially configured
73
- in the optimizer box, partially in the training box in the workspace that uses the model.
74
-
75
- Once the model is defined, you can use it in other workspaces.
76
-
77
- - Load it with the _"Define model"_ box.
78
- - Train it with the _"Train model"_ box.
79
- - Generate predictions with the _"Model inference"_ box.
80
-
81
- See the [_Model definition_ and _Model use_ workspaces](https://github.com/lynxkite/lynxkite-2000/tree/main/examples)
82
- for a practical example.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/guides/plugins.md DELETED
@@ -1,283 +0,0 @@
1
- # Plugin development
2
-
3
- Plugins can provide additional operations for an existing LynxKite environment,
4
- and they can also provide new environments.
5
-
6
- ## Creating a new plugin
7
-
8
- `.py` files inside the LynxKite data directory are automatically imported each time a
9
- workspace is executed. You can create a new plugin by creating a new `.py` file in the
10
- data directory. LynxKite even includes an integrated editor for this purpose.
11
- Click **New code file** in the directory where you want to create the file.
12
-
13
- Plugins in subdirectories of the data directory are imported when executing workspaces
14
- within those directories. This allows you to create plugins that are only available
15
- in specific workspaces.
16
-
17
- You can also create and distribute plugins as Python packages. In this case the
18
- module name must start with `lynxkite_` for it to be automatically imported on startup.
19
-
20
- ### Plugin dependencies
21
-
22
- When creating a plugin as a "code file", you can create a `requirements.txt` file in the same
23
- directory. This file will be used to install the dependencies of the plugin.
24
-
25
- ## Adding new operations
26
-
27
- Any piece of Python code can easily be wrapped into a LynxKite operation.
28
- Let's say we have some code that calculates the length of a string column in a Pandas DataFrame:
29
-
30
- ```python
31
- df["length"] = df["my_column"].str.len()
32
- ```
33
-
34
- We can turn it into a LynxKite operation using the
35
- [`@op`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.op) decorator:
36
-
37
- ```python
38
- import pandas as pd
39
- from lynxkite.core.ops import op
40
-
41
- @op("LynxKite Graph Analytics", "Get column length")
42
- def get_length(df: pd.DataFrame, *, column_name: str):
43
- """
44
- Gets the length of a string column.
45
-
46
- Args:
47
- column_name: The name of the column to get the length of.
48
- """
49
- df = df.copy()
50
- df["length"] = df[column_name].str.len()
51
- return df
52
- ```
53
-
54
- Let's review the changes we made.
55
-
56
- ### The `@op` decorator
57
-
58
- The [`@op`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.op) decorator registers a
59
- function as a LynxKite operation. The first argument is the name of the environment,
60
- the last argument is the name of the operation. Between the two, you can list the hierarchy of
61
- categories the operation belongs to. For example:
62
-
63
- ```python
64
- @op("LynxKite Graph Analytics", "Machine learning", "Preprocessing", "Split train/test set")
65
- ```
66
-
67
- When defining multiple operations, you can use
68
- [`ops.op_registration`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.op_registration)
69
- for convenience:
70
- ```python
71
- op = ops.op_registration("LynxKite Graph Analytics")
72
-
73
- @op("An operation")
74
- def my_op():
75
- ...
76
- ```
77
-
78
- ### The function signature
79
-
80
- `*` in the list of function arguments marks the start of keyword-only arguments.
81
- The arguments before `*` will become _inputs_ of the operation. The arguments after `*` will
82
- be its _parameters_.
83
-
84
- ```python
85
- # /--- inputs ---\ /- parameters -\
86
- def get_length(df: pd.DataFrame, *, column_name: str):
87
- ```
88
-
89
- LynxKite uses the type annotations of the function arguments to provide input validation,
90
- conversion, and the right UI on the frontend.
91
-
92
- The types supported for **inputs** are determined by the environment. For graph analytics,
93
- the possibilities are:
94
-
95
- - `pandas.DataFrame`
96
- - `networkx.Graph`
97
- - [`lynxkite_graph_analytics.Bundle`](../reference/lynxkite-graph-analytics/core.md#lynxkite_graph_analytics.core.Bundle)
98
-
99
- The inputs of an operation are automatically converted to the right type, when possible.
100
-
101
- To make an input optional, use an optional type, like `pd.DataFrame | None`.
102
-
103
- The position of the input and output connectors can be controlled using the
104
- [`@ops.input_position`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.input_position) and
105
- [`@ops.output_position`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.output_position)
106
- decorators. By default, inputs are on the left and outputs on the right.
107
-
108
- All **parameters** are stored in LynxKite workspaces as strings. If a type annotation is provided,
109
- LynxKite will convert the string to the right type and provide the right UI.
110
-
111
- - `str`, `int`, `float` are presented as a text box and converted to the given type.
112
- - `bool` is presented as a checkbox.
113
- - [`lynxkite.core.ops.LongStr`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.LongStr)
114
- is presented as a text area.
115
- - Enums are presented as a dropdown list.
116
- - Pydantic models are presented as their JSON string representations. (Unless you add custom UI
117
- for them.) They are converted to the model object when your function is called.
118
-
119
- ### Slow operations
120
-
121
- If the function takes a significant amount of time to run, we must either:
122
-
123
- - Write an asynchronous function.
124
- - Pass `slow=True` to the `@op` decorator. LynxKite will run the function in a separate thread.
125
-
126
- `slow=True` also causes the results of the operation to be cached on disk. As long as
127
- its inputs don't change, the operation will not be run again. This is useful for both
128
- synchronous and synchronous operations.
129
-
130
- ### Documentation
131
-
132
- The docstring of the function is used as the operation's description. You can use
133
- Google-style or Numpy-style docstrings.
134
- (See [Griffe's documentation](https://mkdocstrings.github.io/griffe/reference/docstrings/).)
135
-
136
- The docstring should be omitted for simple operations like the one above.
137
-
138
- ### Outputting results
139
-
140
- The return value of the function is the output of the operation. It will be passed to the
141
- next operation in the pipeline.
142
-
143
- An operation can have multiple outputs. In this case, the return value must be a dictionary,
144
- and the list of outputs must be declared in the `@op` decorator.
145
-
146
- ```python
147
- @op("LynxKite Graph Analytics", "Train/test split", outputs=["train", "test"])
148
- def test_split(df: pd.DataFrame, *, test_ratio=0.1):
149
- test = df.sample(frac=test_ratio).reset_index()
150
- train = df.drop(test.index).reset_index()
151
- return {"train": train, "test": test}
152
- ```
153
-
154
- ### Displaying results
155
-
156
- The outputs of the operation can be used by other operations. But we can also generate results
157
- that are meant to be viewed by the user. The different options for this are controlled by the `view`
158
- argument of the `@op` decorator.
159
-
160
- The `view` argument can be one of the following:
161
-
162
- - `matplotlib`: Just plot something with Matplotlib and it will be displayed in the UI.
163
-
164
- ```python
165
- @op("LynxKite Graph Analytics", "Plot column histogram", view="matplotlib")
166
- def plot(df: pd.DataFrame, *, column_name: str):
167
- df[column_name].value_counts().sort_index().plot.bar()
168
- ```
169
-
170
- - `visualization`: Draws a chart using [ECharts](https://echarts.apache.org/examples/en/index.html).
171
- You need to return a dictionary with the chart configuration, which ECharts calls `option`.
172
-
173
- ```python
174
- @op("View loss", view="visualization")
175
- def view_loss(bundle: core.Bundle):
176
- loss = bundle.dfs["training"].training_loss.tolist()
177
- v = {
178
- "title": {"text": "Training loss"},
179
- "xAxis": {"type": "category"},
180
- "yAxis": {"type": "value"},
181
- "series": [{"data": loss, "type": "line"}],
182
- }
183
- return v
184
- ```
185
-
186
- - `image`: Return an image as a
187
- [data URL](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data)
188
- and it will be displayed.
189
- - `molecule`: Return a molecule as a PDB or SDF string, or an `rdkit.Chem.Mol` object.
190
- It will be displayed using [3Dmol.js](https://3Dmol.org/).
191
- - `table_view`: Return
192
- [`Bundle.to_dict()`](../reference/lynxkite-graph-analytics/core.md#lynxkite_graph_analytics.core.Bundle.to_dict).
193
-
194
- ## Adding new environments
195
-
196
- A new environment means a completely new set of operations, and (optionally) a new
197
- executor. There's nothing to be done for setting up a new environment. Just start
198
- registering operations into it.
199
-
200
- ### No executor
201
-
202
- By default, the new environment will have no executor. This can be useful!
203
-
204
- LynxKite workspaces are stored as straightforward JSON files and updated on every modification.
205
- You can use LynxKite for configuring workflows and have a separate system
206
- read the JSON files.
207
-
208
- Since the code of the operations is not executed in this case, you can create functions that do nothing.
209
- Alternatively, you can use the
210
- [`register_passive_op`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.register_passive_op)
211
- and
212
- [`passive_op_registration`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.passive_op_registration)
213
- functions to easily whip up a set of operations:
214
-
215
- ```python
216
- from lynxkite.core.ops import passive_op_registration, Parameter as P
217
-
218
- op = passive_op_registration("My Environment")
219
- op('Scrape documents', params=[P('url', '')])
220
- op('Conversation logs')
221
- op('Extract graph')
222
- op('Compute embeddings', params=[P.options('method', ['LLM', 'graph', 'random']), P('dimensions', 1234)])
223
- op('Vector DB', params=[P.options('backend', ['ANN', 'HNSW'])])
224
- op('Chat UI', outputs=[])
225
- op('Chat backend')
226
- ```
227
-
228
- ### Built-in executors
229
-
230
- LynxKite comes with two built-in executors. You can register these for your environment
231
- and you're good to go.
232
-
233
- ```python
234
- from lynxkite.core.executors import simple
235
- simple.register("My Environment")
236
- ```
237
-
238
- The [`simple` executor](../reference/lynxkite-core/executors/simple.md)
239
- runs each operation once, passing the output of the preceding operation
240
- as the input to the next one. No tricks. You can use any types as inputs and outputs.
241
-
242
- ```python
243
- from lynxkite.core.executors import one_by_one
244
- one_by_one.register("My Environment")
245
- ```
246
-
247
- The [`one_by_one` executor](../reference/lynxkite-core/executors/one_by_one.md)
248
- expects that the code for operations is the code for transforming
249
- a single element. If an operation returns an iterable, it will be split up
250
- into its elements, and the next operation is called for each element.
251
-
252
- Sometimes you need the full contents of an input. The `one_by_one` executor
253
- lets you choose between the two modes by the orientation of the input connector.
254
- If the input connector is horizontal (left or right), it takes single elements.
255
- If the input connector is vertical (top or bottom), it takes an iterable of all the incoming data.
256
-
257
- A unique advantage of this setup is that horizontal inputs can have loops across
258
- horizontal inputs. Just make sure that loops eventually discard all elements, so you don't
259
- end up with an infinite loop.
260
-
261
- ### Custom executors
262
-
263
- A custom executor can be registered using
264
- [`@ops.register_executor`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.register_executor).
265
-
266
- ```python
267
- @ops.register_executor(ENV)
268
- async def execute(ws: workspace.Workspace):
269
- catalog = ops.CATALOGS[ws.env]
270
- ...
271
- ```
272
-
273
- The executor must be an asynchronous function that takes a
274
- [`workspace.Workspace`](../reference/lynxkite-core/workspace.md#lynxkite.core.workspace.Workspace)
275
- as an argument. The return value is ignored and it's up to you how you process the workspace.
276
-
277
- To update the frontend as the executor processes the workspace, call
278
- [`WorkspaceNode.publish_started`](../reference/lynxkite-core/workspace.md#lynxkite.core.workspace.WorkspaceNode.publish_started)
279
- when starting to execute a node, and
280
- [`WorkspaceNode.publish_result`](../reference/lynxkite-core/workspace.md#lynxkite.core.workspace.WorkspaceNode.publish_result)
281
- to publish the results. Use
282
- [`WorkspaceNode.publish_error`](../reference/lynxkite-core/workspace.md#lynxkite.core.workspace.WorkspaceNode.publish_error)
283
- if the node failed.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/guides/quickstart.md DELETED
@@ -1,27 +0,0 @@
1
- # Quickstart
2
-
3
- Install the LynxKite application with `pip`:
4
- ```bash
5
- pip install lynxkite
6
- ```
7
-
8
- To be able to do anything useful, you also need to install one or more LynxKite environments.
9
- If you want to work with data science and graph analytics, install the `lynxkite-graph-analytics` package:
10
- ```bash
11
- pip install lynxkite-graph-analytics
12
- ```
13
-
14
- Create a folder for storing your LynxKite projects:
15
- ```bash
16
- mkdir ~/lynxkite_projects
17
- ```
18
-
19
- You're ready to run LynxKite!
20
- ```bash
21
- cd ~/lynxkite_projects
22
- lynxkite
23
- ```
24
-
25
- Open [http://localhost:8000/](http://localhost:8000/) in your browser.
26
-
27
- Find example workspaces in the [`examples` folder](https://github.com/lynxkite/lynxkite-2000/tree/main/examples).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/index.md DELETED
@@ -1,5 +0,0 @@
1
- ---
2
- title: Overview
3
- ---
4
-
5
- --8<-- "README.md"
 
 
 
 
 
 
docs/license.md DELETED
@@ -1,11 +0,0 @@
1
- # License
2
-
3
- LynxKite 2000:MM is available under the GNU AGPLv3 license below.
4
-
5
- Additionally, [Lynx Analytics](https://www.lynxanalytics.com/) offers a commercial license of LynxKite 2000:MM
6
- that includes additional features and support. Get in touch if you are interested in life sciences tools
7
- and cluster deployment!
8
-
9
- ```
10
- --8<-- "LICENSE"
11
- ```
 
 
 
 
 
 
 
 
 
 
 
 
docs/lynxkite-core.md DELETED
@@ -1,6 +0,0 @@
1
- # LynxKite Core
2
-
3
- LynxKite core is for writing LynxKite plugins.
4
- It contains core types and utilities that can be used by all LynxKite plugins.
5
-
6
- ::: lynxkite.core.ops
 
 
 
 
 
 
 
docs/lynxkite-graph-analytics.md DELETED
@@ -1,6 +0,0 @@
1
- # LynxKite Graph Analytics
2
-
3
- This is the classical LynxKite experience!
4
- The graph analytics plugin is a collection of graph algorithms that can be run on a LynxKite graph.
5
-
6
- ::: lynxkite_graph_analytics.lynxkite_ops
 
 
 
 
 
 
 
docs/reference/lynxkite-core/executors/one_by_one.md DELETED
@@ -1 +0,0 @@
1
- ::: lynxkite.core.executors.one_by_one
 
 
docs/reference/lynxkite-core/executors/simple.md DELETED
@@ -1 +0,0 @@
1
- ::: lynxkite.core.executors.simple
 
 
docs/reference/lynxkite-core/ops.md DELETED
@@ -1 +0,0 @@
1
- ::: lynxkite.core.ops
 
 
docs/reference/lynxkite-core/workspace.md DELETED
@@ -1 +0,0 @@
1
- ::: lynxkite.core.workspace
 
 
docs/reference/lynxkite-graph-analytics/core.md DELETED
@@ -1 +0,0 @@
1
- ::: lynxkite_graph_analytics.core
 
 
docs/reference/lynxkite-graph-analytics/operations.md DELETED
@@ -1,3 +0,0 @@
1
- ::: lynxkite_graph_analytics.lynxkite_ops
2
- ::: lynxkite_graph_analytics.ml_ops
3
- ::: lynxkite_graph_analytics.networkx_ops
 
 
 
 
docs/stylesheets/extra.css DELETED
@@ -1,8 +0,0 @@
1
- [data-md-color-scheme="lynxkite"] {
2
- --md-primary-fg-color: #39bcf9;
3
- --md-primary-fg-color--light: #9fdef9;
4
- --md-primary-fg-color--dark: #096f9a;
5
- --md-accent-fg-color: #ff8800;
6
- --md-accent-fg-color--dark: #b86200;
7
- --md-accent-fg-color--light: #ffbc70;
8
- }
 
 
 
 
 
 
 
 
 
examples/Airlines demo.lynxkite.json DELETED
The diff for this file is too large to render. See raw diff
 
examples/Cheminformatics/chembl_tools.py CHANGED
@@ -1,4 +1,4 @@
1
- from lynxkite.core.ops import op
2
  import pandas as pd
3
  from chembl_webresource_client.new_client import new_client
4
  from rdkit import Chem
 
1
+ from lynxkite_core.ops import op
2
  import pandas as pd
3
  from chembl_webresource_client.new_client import new_client
4
  from rdkit import Chem
examples/Cheminformatics/cheminfo_tools.py CHANGED
@@ -1,6 +1,6 @@
1
  import os
2
  import pickle
3
- from lynxkite.core.ops import op
4
  from matplotlib import pyplot as plt
5
  import pandas as pd
6
  from rdkit.Chem.Draw import rdMolDraw2D
 
1
  import os
2
  import pickle
3
+ from lynxkite_core.ops import op
4
  from matplotlib import pyplot as plt
5
  import pandas as pd
6
  from rdkit.Chem.Draw import rdMolDraw2D
examples/Cheminformatics/rcsb_api.py CHANGED
@@ -4,7 +4,7 @@ import pypdb
4
  import biotite.database.rcsb as rcsb
5
  from MDAnalysis.analysis import rms
6
  from opencadd.structure.superposition.engines.mda import MDAnalysisAligner
7
- from lynxkite.core.ops import op
8
  import os
9
  import numpy as np
10
  from Bio.PDB import PDBList, PDBParser, Superimposer
 
4
  import biotite.database.rcsb as rcsb
5
  from MDAnalysis.analysis import rms
6
  from opencadd.structure.superposition.engines.mda import MDAnalysisAligner
7
+ from lynxkite_core.ops import op
8
  import os
9
  import numpy as np
10
  from Bio.PDB import PDBList, PDBParser, Superimposer
examples/Image processing.lynxkite.json DELETED
@@ -1,303 +0,0 @@
1
- {
2
- "edges": [
3
- {
4
- "id": "xy-edge__Open image 1output-View image 1image",
5
- "source": "Open image 1",
6
- "sourceHandle": "output",
7
- "target": "View image 1",
8
- "targetHandle": "image"
9
- },
10
- {
11
- "id": "xy-edge__To grayscale 1output-View image 2image",
12
- "source": "To grayscale 1",
13
- "sourceHandle": "output",
14
- "target": "View image 2",
15
- "targetHandle": "image"
16
- },
17
- {
18
- "id": "xy-edge__Blur 1output-To grayscale 1image",
19
- "source": "Blur 1",
20
- "sourceHandle": "output",
21
- "target": "To grayscale 1",
22
- "targetHandle": "image"
23
- },
24
- {
25
- "id": "Open image 1 Flip vertically 1",
26
- "source": "Open image 1",
27
- "sourceHandle": "output",
28
- "target": "Flip vertically 1",
29
- "targetHandle": "image"
30
- },
31
- {
32
- "id": "Flip vertically 1 Blur 1",
33
- "source": "Flip vertically 1",
34
- "sourceHandle": "output",
35
- "target": "Blur 1",
36
- "targetHandle": "image"
37
- }
38
- ],
39
- "env": "Pillow",
40
- "nodes": [
41
- {
42
- "data": {
43
- "__execution_delay": 0.0,
44
- "collapsed": null,
45
- "display": null,
46
- "error": null,
47
- "input_metadata": null,
48
- "meta": {
49
- "inputs": [],
50
- "name": "Open image",
51
- "outputs": [
52
- {
53
- "name": "output",
54
- "position": "right",
55
- "type": {
56
- "type": "None"
57
- }
58
- }
59
- ],
60
- "params": [
61
- {
62
- "default": null,
63
- "name": "filename",
64
- "type": {
65
- "type": "<class 'str'>"
66
- }
67
- }
68
- ],
69
- "type": "basic"
70
- },
71
- "params": {
72
- "filename": "https://media.licdn.com/dms/image/v2/C4E03AQEq4tdJKQiNHQ/profile-displayphoto-shrink_200_200/profile-displayphoto-shrink_200_200/0/1657270040827?e=2147483647&v=beta&t=lDxix0_0-_K7NUFqgPdzxY5-P7f73bWpPS_XRre842c"
73
- },
74
- "status": "done",
75
- "title": "Open image"
76
- },
77
- "dragHandle": ".bg-primary",
78
- "height": 222.0,
79
- "id": "Open image 1",
80
- "parentId": null,
81
- "position": {
82
- "x": -316.51795927908694,
83
- "y": 122.80901061526373
84
- },
85
- "type": "basic",
86
- "width": 422.0
87
- },
88
- {
89
- "data": {
90
- "display": "data:image/jpeg;base64,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",
91
- "error": null,
92
- "input_metadata": null,
93
- "meta": {
94
- "inputs": [
95
- {
96
- "name": "image",
97
- "position": "left",
98
- "type": {
99
- "type": "<module 'PIL.Image' from '/media/nvme/darabos/lynxkite-2024/.venv/lib/python3.11/site-packages/PIL/Image.py'>"
100
- }
101
- }
102
- ],
103
- "name": "View image",
104
- "outputs": [],
105
- "params": [],
106
- "type": "image"
107
- },
108
- "params": {},
109
- "status": "done",
110
- "title": "View image"
111
- },
112
- "dragHandle": ".bg-primary",
113
- "height": 288.0,
114
- "id": "View image 1",
115
- "parentId": null,
116
- "position": {
117
- "x": 371.2152385614552,
118
- "y": -243.68185336918702
119
- },
120
- "type": "image",
121
- "width": 265.0
122
- },
123
- {
124
- "data": {
125
- "display": "data:image/jpeg;base64,UklGRoADAABXRUJQVlA4IHQDAAAwNQCdASrIAMgAPm0ukkYyMaGhrtt5UkANiWluu7AObIPJl9NfJmVCwA06FIbr8ZA3WaSJ7/dHH5er4u683qcKWcW7qn4VGE7XCnSEtZUZsarT1ox9y8J2EionbqsxfkAAIOk7JTtvXiLVAeVPQlEW3R4aJSLoxXCJTuGOqcp6fJ7+tzWWmOSq1IqFDN7536TfOvEoiw/K8AQcP06J13RTpwyFow3MFoK9BWZDv0mPqMmHjuC7Hb5MkHc6QnQihSek5PyYdUN4+yG0NpM3NK0+lOSMpu5jaeY5YrFUKke5nLaJ0Mu/3it1akkzebF4fRj8xdIkJDrC4xD4RcyC8aQ1zI4MjffvaCX8X0jbNJ5n5xzGOOU9kFhpry6Uy30xVtGdrEYwbGflOPdNp1u0tiKemkGylZmdj/9rbvWo0IYnCFLs1tAQ1fpkGVQYVo4FA68IbkKvB2KNxrQ17ySt96mzi6ODrNuVzsgisAS/q2ILoOlZL2nfdCNWb523fz7hEM319hb+aWTmo2zExKfWXzUZRGWy09rt8rQZXW5qBKTs5vsYabOjubwNNfFoX7dv32XwAAD++LH6dpCJbVSQouNoBCx0XMzcYw+6o+G//GpIy1eD9A/1cvAp/zhr1mDURDqx2xBgY79tNgsjX9AArhyCNX/BRq/st/uDXRqUv7Wx+Pl/40TsVhng3/XrKZXhDYKnD2aAAyOHG8yS8FTlr5aM6Nh//ra74YvdX+3/j/RLZDm6wRAbHmTF1ZWhK3HdcgOeU+RMXPeCEL8zqEcm3nMeTpuJhH7GTnjejYZx6OrdHAWBghyiaVrgH9f2nXLm1VZ4AgOokXq16NNjRz3FgLYzr7qd94Y2TRJW/cGAcaH5CN5+iXZy4LY2JaCo5I4//pJ/TthQ8SBbQdOIDZPayUXSmAo7cTuD/rk97N3QBGdx5oIzTeSYL8FZfiH/nJLQARX7PafEYZBW9a/ZMjDSPg9GFTPL4BL42F32uL8ZcYLrpkRGRW6jbjmj7GuGSwV5+YuKOhEAf2ZVwU55UjaxRygygrjlYc8FoCe+HyL6rJ5V3jfZvZy1Nw3CdfX4CWQ5/gUjrgONEGb7g2SL/nS9dGgl2gAHKKCzEneCdL38wwsnQYPGdrerT/4uEWM1T24cHLkgBmT+zH6YiwG7DkqEmAbjF4AAAA==",
126
- "error": null,
127
- "input_metadata": null,
128
- "meta": {
129
- "inputs": [
130
- {
131
- "name": "image",
132
- "position": "left",
133
- "type": {
134
- "type": "<module 'PIL.Image' from '/media/nvme/darabos/lynxkite-2024/.venv/lib/python3.11/site-packages/PIL/Image.py'>"
135
- }
136
- }
137
- ],
138
- "name": "View image",
139
- "outputs": [],
140
- "params": [],
141
- "type": "image"
142
- },
143
- "params": {},
144
- "status": "done",
145
- "title": "View image"
146
- },
147
- "dragHandle": ".bg-primary",
148
- "height": 456.0,
149
- "id": "View image 2",
150
- "parentId": null,
151
- "position": {
152
- "x": 1068.1904563045216,
153
- "y": 313.7040149772122
154
- },
155
- "type": "image",
156
- "width": 398.0
157
- },
158
- {
159
- "data": {
160
- "__execution_delay": null,
161
- "collapsed": true,
162
- "display": null,
163
- "error": null,
164
- "input_metadata": null,
165
- "meta": {
166
- "inputs": [
167
- {
168
- "name": "image",
169
- "position": "left",
170
- "type": {
171
- "type": "<module 'PIL.Image' from '/media/nvme/darabos/lynxkite-2024/.venv/lib/python3.11/site-packages/PIL/Image.py'>"
172
- }
173
- }
174
- ],
175
- "name": "To grayscale",
176
- "outputs": [
177
- {
178
- "name": "output",
179
- "position": "right",
180
- "type": {
181
- "type": "None"
182
- }
183
- }
184
- ],
185
- "params": [],
186
- "type": "basic"
187
- },
188
- "params": {},
189
- "status": "done",
190
- "title": "To grayscale"
191
- },
192
- "dragHandle": ".bg-primary",
193
- "height": 200.0,
194
- "id": "To grayscale 1",
195
- "parentId": null,
196
- "position": {
197
- "x": 788.18031953735,
198
- "y": 541.1434137066244
199
- },
200
- "type": "basic",
201
- "width": 200.0
202
- },
203
- {
204
- "data": {
205
- "__execution_delay": 0.0,
206
- "collapsed": null,
207
- "display": null,
208
- "error": null,
209
- "input_metadata": null,
210
- "meta": {
211
- "inputs": [
212
- {
213
- "name": "image",
214
- "position": "left",
215
- "type": {
216
- "type": "<module 'PIL.Image' from '/media/nvme/darabos/lynxkite-2024/.venv/lib/python3.11/site-packages/PIL/Image.py'>"
217
- }
218
- }
219
- ],
220
- "name": "Blur",
221
- "outputs": [
222
- {
223
- "name": "output",
224
- "position": "right",
225
- "type": {
226
- "type": "None"
227
- }
228
- }
229
- ],
230
- "params": [
231
- {
232
- "default": 5.0,
233
- "name": "radius",
234
- "type": {
235
- "type": "<class 'float'>"
236
- }
237
- }
238
- ],
239
- "type": "basic"
240
- },
241
- "params": {
242
- "radius": "5"
243
- },
244
- "status": "done",
245
- "title": "Blur"
246
- },
247
- "dragHandle": ".bg-primary",
248
- "height": 200.0,
249
- "id": "Blur 1",
250
- "parentId": null,
251
- "position": {
252
- "x": 505.15961556359304,
253
- "y": 539.8477981917164
254
- },
255
- "type": "basic",
256
- "width": 200.0
257
- },
258
- {
259
- "data": {
260
- "__execution_delay": null,
261
- "collapsed": true,
262
- "display": null,
263
- "error": null,
264
- "input_metadata": null,
265
- "meta": {
266
- "inputs": [
267
- {
268
- "name": "image",
269
- "position": "left",
270
- "type": {
271
- "type": "<module 'PIL.Image' from '/media/nvme/darabos/lynxkite-2024/.venv/lib/python3.11/site-packages/PIL/Image.py'>"
272
- }
273
- }
274
- ],
275
- "name": "Flip vertically",
276
- "outputs": [
277
- {
278
- "name": "output",
279
- "position": "right",
280
- "type": {
281
- "type": "None"
282
- }
283
- }
284
- ],
285
- "params": [],
286
- "type": "basic"
287
- },
288
- "params": {},
289
- "status": "done",
290
- "title": "Flip vertically"
291
- },
292
- "dragHandle": ".bg-primary",
293
- "height": 200.0,
294
- "id": "Flip vertically 1",
295
- "position": {
296
- "x": 148.51544517498044,
297
- "y": 288.98657171134255
298
- },
299
- "type": "basic",
300
- "width": 200.0
301
- }
302
- ]
303
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/Image table.lynxkite.json DELETED
@@ -1,364 +0,0 @@
1
- {
2
- "edges": [
3
- {
4
- "id": "Example image table 1 View tables 1",
5
- "source": "Example image table 1",
6
- "sourceHandle": "output",
7
- "target": "View tables 1",
8
- "targetHandle": "bundle"
9
- },
10
- {
11
- "id": "Import CSV 1 Draw molecules 1",
12
- "source": "Import CSV 1",
13
- "sourceHandle": "output",
14
- "target": "Draw molecules 1",
15
- "targetHandle": "df"
16
- },
17
- {
18
- "id": "Draw molecules 1 View tables 2",
19
- "source": "Draw molecules 1",
20
- "sourceHandle": "output",
21
- "target": "View tables 2",
22
- "targetHandle": "bundle"
23
- }
24
- ],
25
- "env": "LynxKite Graph Analytics",
26
- "nodes": [
27
- {
28
- "data": {
29
- "__execution_delay": null,
30
- "collapsed": false,
31
- "display": null,
32
- "error": null,
33
- "input_metadata": [],
34
- "meta": {
35
- "color": "orange",
36
- "inputs": [],
37
- "name": "Example image table",
38
- "outputs": [
39
- {
40
- "name": "output",
41
- "position": "right",
42
- "type": {
43
- "type": "None"
44
- }
45
- }
46
- ],
47
- "params": [],
48
- "type": "basic"
49
- },
50
- "params": {},
51
- "status": "done",
52
- "title": "Example image table"
53
- },
54
- "dragHandle": ".bg-primary",
55
- "height": 200.0,
56
- "id": "Example image table 1",
57
- "position": {
58
- "x": 356.1935187064265,
59
- "y": 224.8472733628614
60
- },
61
- "type": "basic",
62
- "width": 280.0
63
- },
64
- {
65
- "data": {
66
- "display": {
67
- "dataframes": {
68
- "df": {
69
- "columns": [
70
- "names",
71
- "images"
72
- ],
73
- "data": [
74
- [
75
- "svg",
76
- "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\" enable-background=\"new 0 0 64 64\"><path d=\"M56 2 18.8 42.909 8 34.729 2 34.729 18.8 62 62 2z\"/></svg>"
77
- ],
78
- [
79
- "data",
80
- "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgNjQgNjQiPjxwYXRoIGQ9Ik01NiAyIDE4LjggNDIuOTA5IDggMzQuNzI5IDIgMzQuNzI5IDE4LjggNjIgNjIgMnoiLz48L3N2Zz4="
81
- ],
82
- [
83
- "http",
84
- "https://upload.wikimedia.org/wikipedia/commons/2/2e/Emojione_BW_2714.svg"
85
- ]
86
- ]
87
- }
88
- },
89
- "other": {},
90
- "relations": []
91
- },
92
- "error": null,
93
- "input_metadata": [
94
- {
95
- "dataframes": {
96
- "df": {
97
- "columns": [
98
- "images",
99
- "names"
100
- ]
101
- }
102
- },
103
- "other": {},
104
- "relations": []
105
- }
106
- ],
107
- "meta": {
108
- "color": "orange",
109
- "inputs": [
110
- {
111
- "name": "bundle",
112
- "position": "left",
113
- "type": {
114
- "type": "<class 'lynxkite_graph_analytics.core.Bundle'>"
115
- }
116
- }
117
- ],
118
- "name": "View tables",
119
- "outputs": [],
120
- "params": [
121
- {
122
- "default": 100,
123
- "name": "limit",
124
- "type": {
125
- "type": "<class 'int'>"
126
- }
127
- }
128
- ],
129
- "type": "table_view"
130
- },
131
- "params": {
132
- "limit": 100.0
133
- },
134
- "status": "done",
135
- "title": "View tables"
136
- },
137
- "dragHandle": ".bg-primary",
138
- "height": 440.0,
139
- "id": "View tables 1",
140
- "position": {
141
- "x": 757.4687936995374,
142
- "y": 116.39895719598661
143
- },
144
- "type": "table_view",
145
- "width": 376.0
146
- },
147
- {
148
- "data": {
149
- "__execution_delay": 0.0,
150
- "collapsed": null,
151
- "display": null,
152
- "error": null,
153
- "input_metadata": [],
154
- "meta": {
155
- "color": "orange",
156
- "inputs": [],
157
- "name": "Import CSV",
158
- "outputs": [
159
- {
160
- "name": "output",
161
- "position": "right",
162
- "type": {
163
- "type": "None"
164
- }
165
- }
166
- ],
167
- "params": [
168
- {
169
- "default": null,
170
- "name": "filename",
171
- "type": {
172
- "type": "<class 'str'>"
173
- }
174
- },
175
- {
176
- "default": "<from file>",
177
- "name": "columns",
178
- "type": {
179
- "type": "<class 'str'>"
180
- }
181
- },
182
- {
183
- "default": "<auto>",
184
- "name": "separator",
185
- "type": {
186
- "type": "<class 'str'>"
187
- }
188
- }
189
- ],
190
- "type": "basic"
191
- },
192
- "params": {
193
- "columns": "<from file>",
194
- "filename": "uploads/molecules2.csv",
195
- "separator": "<auto>"
196
- },
197
- "status": "done",
198
- "title": "Import CSV"
199
- },
200
- "dragHandle": ".bg-primary",
201
- "height": 339.0,
202
- "id": "Import CSV 1",
203
- "position": {
204
- "x": 13.802068621055497,
205
- "y": -269.65065144888104
206
- },
207
- "type": "basic",
208
- "width": 296.0
209
- },
210
- {
211
- "data": {
212
- "display": {
213
- "dataframes": {
214
- "df": {
215
- "columns": [
216
- "name",
217
- "smiles",
218
- "image"
219
- ],
220
- "data": [
221
- [
222
- "ciprofloxacin",
223
- "C1CNCCN1c(c2)c(F)cc3c2N(C4CC4)C=C(C3=O)C(=O)O",
224
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAIAAAD2HxkiAAAn/klEQVR4nO3deVxU9foH8M/MsCOL4AKoiLhdBhMRURH3yzVT1NwqNbS0LC23ul3N+mVe9Wa3Rb2V5p5SgmaluLSYpoJiqYAobqGpyCigbCLIAPP8/jg4DgMi6Mz5jvC8X/7RGYfzfWj8zFm+y1EQERhj4ihFF8BYfcchZEwwDiFjgnEIGROMQ8iYYBxCxgTjEDImGIeQMcE4hIwJxiFkTDAOIWOCcQgZE4xDyJhgHELGBOMQMiYYh5AxwTiEjAnGIWRMMA4hY4JxCBkTjEPImGAcQsYE4xAyJhiHkDHBOISMCcYhZEwwDiFjgnEIGROMQ8iYYBxCxgTjEDImGIeQMcE4hIwJxiFkTDAOIWOCcQgZE4xDyJhgHELGBOMQMiYYh5AxwTiEjAnGIWRMMA4hY4JxCBkTjEPImGAcQsYE4xAyJhiHkDHBOISMCcYhZEwwDiFjgnEIGROMQ8iYYBxCxgTjEDImGIeQMcE4hIwJxiFkTDAOIWOCcQgZE4xDyJhgHELGBOMQMiYYh5AxwTiEjAnGIWRMMA4hY4JxCBkTjEPImGAcQsYE4xAyJhiHkDHBrEQX8PjJLy1deOWKftNaoVjUqpXAetjjjkNYa3eI9uXkfOjra61QAFAqFKIrYo83DuFDCnVxsVPyyTwzAQ7hQ7pRUmKjVAJwVCodVSrR5bDHGIfwIU0+f146DR3duPELHh6Cq2GPMw7hQ/re359PR03g1i1YWcHeXnQdIvE/IybIxo1o1QrNm6NhQ3Ttivh40QUJwyFkIvzwA6ZPx5dfIjcXt27h2Wfxj3/g4kXRZYnBIaw1K6C5ra3h/7j80tIZqannCguF1fTYWbIEb7yBJ5+EQgFra7z5JkJCsGqV6LLE4BDWmpu19bYOHWwMLgg3ZGQcysubmZqaodUKLOxxkpKCnj0rvNKzJ06fFlSNYBxCE5ji5dXVySmrpGRaauqtsjLR5Vg8Ity6hQYNKrzo4oLcXDH1iMYhNAErheKj1q3b2NtfLCp668KFEiLRFVk2hQItW8Jg6B8AXL6M+jr6j0NoGo4q1adt2rhZWx+7desDo39erLK+fbFuHfTfVgUFiIpC//5CaxJGQfy1bTpnCgsnnztXpNNNbdZsIvfgV+PaNYSGwt8fzzyDwkJ88QWaNcPOnaiXY484hCYWl5f3xoULRDTfx2eQu7vocixPXh5SUxEUhNxcrFmDEydgY4O+fTFmDKzq6dARDqHpbcrM/DQtzVqh+Kxt2y5OTqLLsTDr1mHSJEyYgK++El2KpeBrQtMb26TJc02alBD96+LFy3fuiC7HwkRFAUDv3gCg1aJPHyxejPp9S5mPhGahI3rr4sUDubnds7PnderUuHFj0RVZhsxMNGsGpRLXrsHNDTt2YOhQdOyIEydEVyYSHwnNQqlQLGrVqnda2vKBA4cNG1ZUVCS6IsuweTNKS/HUU3BzK98E8NxzYosSjkNoLnZK5duhoa19fePj48ePH6/T6URXZAGio4G7qSssxPbtADB6tMiSLACH0IwaN24cExPTsGHDrVu3zpkzx9zNHThwYPXq1QUFBeZu6CFduYL4eDg4IDwcAHbuREEBundHmzaiKxONmJkdOHDA1tYWwGeffWbC3ZaWll64cCEmJmbevHnh4eH6y057e/szZ86YsCGTWbyYAHruufLN4cMJoKVLhdZkETiEcoiKilIoFCqVatu2bQ+9kzt37hw9enTVqlWvvvpqt27d7CtNhHV0dFSpVAACAwOLiopMWL9pdOpEAG3fTkSUl0f29qRU0tWrosuqik4nZ2scQpnMmzcPgIODw++//17DH8nPz4+NjV25cuX06dNDQ0Pt7OyMUufp6RkeHj5v3ryYmBiNRkNESUlJzZs3BzB69OiysjJz/kK1dPYsAeTqSnfuEBF99RUB1K+f6LIqOnuWBg0iW1uytaX27emrr+RplkMoE51ON2HCBCk5ly9frvI9OTk5sbGxS5cujYiIUKvVyorLZ6hUKl9fX33qsrKyqtxJSkpKw4YNAcyePducv1AtzZtHAE2cWL45cCABtHKl0JoqunGDPD1p1izKzaWSEtq5kxo2pMhIGVrmEMpHq9WGhYUBUKvVOTk5RJSenh4TE7N48WIpdYqKS5haW1ur1eqIiIilS5fGxsYWFBRUuduysrLz58/v2rVL/8r+/fulq9DPP/9cnl/twfz8CKBffiEiysoia2uytqYbN0SXZWDJEurYscKJ6Mcf0xNPyNAyh1BWOTk5arUagJeXl3ulkaVOTk69evWaPn36+vXrT5w4odVqq9xJSUnJqVOnNmzYMHv27PDwcGk/VlZWhYWF+vds2rRJugrdLl2DCZWdlESOjtSkCZWUEBEtX04ADR4suq6KJk2iadMqvHL8OFlZ0X0+BROqp0NmRXF1dd2+fXtgYKBWq71586arq6u/v3/QXX5+fsqqVnArLCw8efJkQkJCYmJiYmLiyZMni4uLDd/QokWLwMDA3Nxc/d2aMWPGnDlzZsGCBc8///zBgwc7deokw293P4s3bVpHtPbVV4dKQ7QNewstR04OjCa+NGyI0lLculU+tMBsOIRyO3fuXEFBgaenZ2JionQTpbL8/Pzk5OTjx48fP3789OnTJ0+e1FZcOMPT01Mf3a5duzZt2rTyTubPn3/58uWNGzcOHjw4Pj7e29vbLL/PgxDRli1bbhQWug8YACA9Pf1HouHdurkPHSqknvtq2RJXr1Z45coVuLiYO4HgEMovKioKwAsvvGCYwJycnJSUlON3nT171nCEjZWVlVqtDgoK8vf3V6vVPXr0qHwqW5lCoVizZk16evrevXsHDRoUFxfn6upqhl/oAQ4fPnzp0qUWLVr06NEDwObNm9+Mjf151KhvnZ3lL6Y6PXti0iTk5kL/f2nDhvKB5uZm7vNdZqioqMjZ2RnAn3/+Kb2yfPlyT09Pow/FwcGhe/fuU6ZMWbVq1bFjx+5It/UfSm5ubocOHQA8+eSTJdIlmbxef/11AG+99Za0GRwcDGDr1q3yV/IAZWU0ZAh16EAbNtD27fTSS9SoEcky7IFDKKstW7YA6Natm/6VtWvXAnB2dg4NDZ0+ffqGDRuOHTtWXFxswkb/+usv6Xx10qRJJtxtTZSVlUlfMcePHyei1NRUhULh5ORkeA/JIiQlUVkZlZTQmjX03HM0ZAjNmUNpafI0ziGU1YgRIwAsWbJE/0p2dvaFCxfM3e7Ro0cdHR0BfPDBB+Zuy9CePXsAtG7dWtpcsGABgPHjx8tZw4NJXSYtW9IjnHE8Cg6hfPLy8uzt7ZVK5VURY7V27NihUqkUCkWkLB3QkkmTJgF47733pE3pxHj37t2yFVAjK1YQQIMGlW/OnUtvv03p6bK1zyGUz1dffQWgn7ixWp9++ikAGxubffv2ydCcVqt1c3MDkJKSQkTJyckA3NzcTHuybQJ9+hBAGzcSERUVkYsLAXTunGztcwjlM3DgQAArhY7Vmj59OgB3d/ezZ8+au62YmBgAAQEB0ubcuXMBvPLKK+Zut3bS00mlIjs7ys0lIvruOwKoSxc5S+AQyiQrK8va2tra2vqG0LFaZWVlTz/9NABfX9+MjAyztjV27FjDq9A2bdoA+O2338zaaK198gkBNGpU+ebo0QTQxx/LWcJjH8KSkpKDBw9a4sydilasWAFgkP7CQ5zCwsLu3bsDCA4Ovn37tplauX37doMGDRQKxZIlS7Kysn7//XcAnp6epaWlZmrxIQUHE0BSl0lBATk6kkJBly7JWcLjF0L9yMnp06eHhYVJE3ycnJySkpJEl1ad3r17A9goXXiIdu3atZYtWwIYNWqUaWc85eXlSRNB+vXrp+/2VKlUf//73wHMmjXLhG2ZQGoqKRTk5ERSl8nXXxNAvXrJXMVjEMLbt2/Hx8cvX7785ZdfDgoKkuYHGJLGW/r4+Fy7dk10sVVLT09XKpV2dna50oWHBTh9+rQ04+lf//rXo+zn2rVru3fvXrRo0ahRo3x9fY0+GpVKJc0zliaI1HwupUwWLCCA9F0m4eEE0BdfyFyFJYZQ/20aERERFBRkY2Nj9NEaTma9fv16UVGRNCQqKCjofvN9xPrkk0+kw47oQip4uBlP0vQraU2Nyqkzmn5VWFhYXFwsfTq2trbXr18336/zMDp0IICkLpPsbLKxISsrkr1IiwhhdnZ2NZNZpZGT0ue6Z8+emzdvVt7DjRs32rZtC2Dw4MEWd9VhwWO1Hjjj6X4r2ejVZKzP7du3u3btCqBnz54WdPWenEwAubmRVPOqVQTQk0/KX4iYEBp+m1YeOVn527Qm+0xNTZX+iVjaTXDLHatFRHfX3WjQoEFCQgIRabVa/SV3aGioNM7GUMOGDfWpO3XqVA0vKTUajXQVakHrbsydSwDp/7X0708ArV8vfyHyhTA/P3/AgAHNmjWrPJbf1dW1X79+b7zxRmRkZEpKykMfymJjY6X7NEstaQ0vCx2rdZdOp4uIiADg4uLSsWNHa2tro0+nVatWI0eOXLhw4a5du6SVbB5OSkqK9NHPmTPHhPU/tJLOnQkgadzCtWukUpGtLeXkyF+JfCHsbTArxOjbVGe6xa02b96sVCqVSuX3339vqn0+Igsdq2VAq9X6+PhIRyqjlWwyMzNN2NBvv/0mXeF/IfvNDyN//PGHg7X1JwMGkHRYXrqUABo+XEgxMoWwqKjIxcUFwLRp065cuWLWthYtWgTA3t4+Pj7erA3VhMWO1RozZsysWbOkjN25c0f6dDZv3my+nkPJunXrpKjHxMSYtaHqzZo1CwZdJqvGjUvr3r1kyxYhxcgUwq1btwIIDg6Wp7kpU6YAaNSokX7aniiWOVYrPT1dpVLpu0y+//576d6yPK2/++67AJycnBITE+Vp0UhZWVmzZs1wt8vk8uXLCoXC0dFR1K11mUI4atQoAJ988ok8zZWUlDz55JM+Pv/o378gO1ueNqtmmWO1pC6TkSNHSpujR48G8NFHH8nTuv4q1MvLy9ynRVX67bffAPj6+krXQR988AGAsWPHyl+JRI4Q5ufnS1N40uSaJUlEeXl5vXppAerbl0SdCR45cgQWOVbLsMukoKDA0dFRoVBcknGsllarlcbQdOjQQf4BDK+88gqAuXPnSpsBAQEABJ4eyxHCjRs3AujTp48MbRlKT6cWLcoffyDvuublZs6cCcsbq3XhwgXDLpOvv/4aQC/Zx2rp190YOHCgnOtulJSUNGnSBEBycjIRnTlzRrpT+ChriDwiOUI4aNAgACtWrJChLSMnT5bPDps3T+6mjS48LMfChQsBRERESJvh4eGibldevHhR/nU3du/eDcDPz0/afO+99wC89NJLshVQmdlDmJ2dbWNjY2VlZe6JM/fz449kZUUKhdzdsEYXHpZDOv5IK3ZLn45KpRI1oEy/7sbixYvlaVG6HF2wYIG02a5dOwC//vqrPK1XyewhXLlypXTKYe6GqrF6NQFkbV2+Crs8pAuPd955R74ma+D06dOGXSarVq0CMGDAAIEl6dfd+Prrr83dlr6r7Pz580R07NgxAE2aNBGyDp2e2UPYt29fABs2bDB3Q9V76y0CyNmZkpPlaE5/4XHy5Ek52quxd955x7DLpH///gDWixirZUi6W2tnZxcXF2fWhoy6yv75z39KfddmbfSBzBtCjUZj2B8lkE5HY8cSQM2by/FIPOnD1l94ENH+/fsNF1kTRRrmLnWZXLt2TaVS2dra5ogYq2Vk2rRpANzd3c+Zc3EXqats1qxZ58+f1+l00iChQ4cOma/FmjBvCL/8MkqpVA4XNBrISFER9ehBAAUFkcl7ZXNzcw0ngigUChcXF/3cJY1GY29vr1AoNm3aZOKGa8NoevuyZcsAPP300wJL0jPruhvShIG5c+dKkxsl0vKT3t7ewi/azRvC7t3Jy+vKtm2W8vTmGzeoXTsaNYoefT5DWlpaTEzM/Pnzhw0bVvkxD9bW1gqFwsbGRt9Nv2TJEsi40lmVpLFaM2fOlDZDQkIAREdHi6rHSGFhYbdu3QB07dr1UUbPlZSUJCcnb9iwYcaMGb1793autN6+4i5YxlMczRjCCxfKlw4w82jE2snIIJ2OIiNp4EDSP2bzyhWq/s6RTkfnz9PmzTR7Nr366obK0+oaNGgQGho6bdq0devWJSYmarXaN998E4Czs3Py3ctQaaUzNzc3GVY6q0zfZXLkyBGygLFaVXq4GU9G068cHByMPh1PT8+wsDDDCQMfffSRlEbh18Nk1hAuWkQAPf+8+Vp4ePPnk7U1vfBC+eaZM2RjU+ENJSV06hRt2ECzZ1N4OLm7E1D+p1u3bQBcXFyqn1ZXVlY2cuRIAD4+PlIHQFlZ2fDhwwG0atVK/g6b/fv3W9RYrfvRz3iq5hglPUh86dKlkydPDg0NrbziieHaC/db9ER6SIa5r0JrwowhfOIJAmjnTvO18PDmz6eRI6lJEzpwgOhuCPPyaMUKmjyZunQhO7t7qZP+NG9OQ4bQe+/Rjh0F93vetZHCwkKjdTfkWemsSq+++iosaaxWNfQznvTrblT/IHH92guLFy+OiYmp4aKSpaWlw4YNA9C6dWvTztiqLXOF8MwZAqhhQ2HjNqs3fz5NnEirVpFaTcXF5SHMziaF4l7qPD0pPJzmzaOYGHroFaSysrKM1t3IzMyURnWbfKWzalQ5VsvV1VXgWK3qrV+/HoBKpQoJCfHy8jI60Nnb23ft2vWVV15ZuXLlH3/88dBLZljIuhvmCuG77xJAL79spt0/KimEZWXUtSstWnTvdHTWLFq6lA4epLw8k7WlX3dDf0dEv9KZ/oFh5iaN1frb3/4mbUpjteR/SFOtjB07Vv/wUycnJ+nkf+XKlbGxsSb87tBoNNJ9NYHrbpgrhG3bEkB795pp949KCiERHTtGDRrQzz8bXxOa1sGDB6XrlmXLlkmvHDhwQHrls88+M2PDd40fPx4GY7X8/PwA7NmzR4ama27dunWTJk2SlrohojFjxgAYN25camqqWXsR9Fehb7/9tvlaqYZZQnj0KAHk4UEWNoPnHn0IiWjqVOrWzbwhJKLo6GiFQmG47kZUVJS00tm2bdvM2rTRWK3jx4/DAsZqVWbYZaJfwPuvv/6SoelffvlFWlxn09q1MjRnxCwhfPNNAmjGDHPs2zQMQ5iXR56eZg8h3V3xyXDdDWmlMwcHB7POtDAaq/XWW2/BAsZqGZG6TBwcHKQ7WNHR0QBCQkJkK2Dt2rUv+PuXeXjQjh2yNSoxfQh1OmrZkgCygBVe7mv1apo//95mVBR17y5Hu9ItysaNG6emphKRTqebMGGCdEu9hndcH4LhsgY6nc7HxwcWMFbLiNRlMmbMGGlTum+pP3uXR9n//V/5COMTJ+Rs1/QhPHCAAPL2FjOPtoZycigykvLz5W5Xq9UOGDAAgJ+fX3Z2tvRKWFgYALVabY4xnNKyBgqFQgp5bGwsgBYtWggfq2XEsMskLy/Pzs5OqVSmy/ikTiIinY4iIgggLy+Scd0N04cwPp6eeorefdfkOzaltWsJoKeeEtB0fn6+9A+uT58+0l2+vLy8J554AkDfvn1NvihbZGQkgN69e0ubr732Gh75+RMmZ9RlIq3I9ve//11AKcXF5asAd+hAcs06qF0Iv/uObt26t3n2LB05QkR05AgZ3mlLT6effzZJeeYSFkYAibgIJyK6evVq8+bNpbMv6Yj0119/eXh4AJiov1Q1kf/85z+2trbSxPnS0lLppr/+DqSFMOoykU4WVq9eLaaamzepfXsCaOBAkuXeVe1CCNAZg8HYCxfSiBFEROPGkVJJ+quM7dupQwdTVWh6GRlkZUU2NlTVUy1kkpCQ0KBBAwDvv/++9MqxY8ekOeaLFi0ybVu5ubm3bt0iol9++QVA+/btTbv/R2fYZZKZmWllZSX4aaoXL1LTpgSQLMtemCyEgYEUEFD+xWHhIfzf/wigoUMFl7F7924rKyuFQvHVV19Jr+zcuVOaY26mxxhOnDgRwDz519upllGXyeeffw5gyJAhgss6epQcHQmgDz80d1MVxuA9imeegZ0dli0z1f7MKDoaAJ57TnAZTz311PLly4no5Zdf/vXXXwEMHjxYOm986aWX9u3bZ9rmtFrtDz/8AOCZZ54x7Z4fkdQb8eyzz1pZWek3nxP+8XTpgqgoqFSYMwfffGPetmoVWYD69aMhQ8r//O1v946EixfT77+TszNdvmzRR8IrV0ihIAeHChe3AkkrLBjOeJoxYwZMNOPJ8OlX7u7ubm5uHh4eFnVf1KjL5MqVK0ql0sHB4ZaFfDzSE+3d3Ew5jrESq9qG9vnn0aJF+X9HRSEv795fde2KMWPwxhsYP94E3w5mEhUFIgwdigYNRJcCAPjvf/+r0Wg2bdo0dOjQ+Ph4Dw+PTz/99MqVK3/++ae9vX2tdqXValNSUhISEhITExMTE0+cOHH79m3DNzg6OmZnZy9YsEC6EWIJDh06dOnSpRYtWkjDZTZv3qzT6cLDwxtYyMfzxhvIzkZ4OCrNDDalWkW2mmtCacW6mzepcWN64w3LPRIGBhJAZh4oVjuVnzR8+/bt/Bp0YhYXFxtOZpUeC2dIP61uy5Ytp06d2rVrl3QVKnzdLT2jLpOgoCAAlvNErQqysmjOHOrVi3r0oKlT6cKF8tcXLSLD1QnS0mrb92XiEBLRunVka2uhITx7tnxEhOU8LlZSwycNG61kY7hiilHqYmJiqpw3LK1AaW1tLXalTYlRl0lqaioAZ2dnS3yaak4OtW1Lw4fTr79SbCxNnUpubnT+PBHRiBG0cOG9d0qz+Gqj1qejD/TCC9iwATdvmnzHJiDdkhk5EpWOGYK5u7v/+OOPISEhu3btmjx58tq1a6XXNRrN6dOnU1JSjh8/fvz48TNnzhCR/qekyaxBd3Xq1OmBZ3GTJ08+d+7cp59+OnLkyLi4OGkhYFH27duXkZHRvn37wMBAAN988w2AESNG1PY8XA6ffw4HB3z3HRQKAOjZE5mZmD8fX39tgp3XKrJ//FHhGKLRUGoqEdHFixWmvWZmUkoKZWfTlCkChoZVQ60mwHIHEhw8eFCaUe7h4dGlSxdpGq4hR0fHHj16vPbaa2vWrElISHi44TVlZWXSQmP6dTdEefHFF2HQZeLv7w/gp59+EljSfQ0dWmG0MRFt3kzt2hERjRhBb79NGRnlfw4dqu2R0IzLWzz9tJyjDh4sKSkpIKDPwIHfWEg9VZJujeoZrWRjqqc7FRYWSjdCunTpImqhp+LiYjc3NwApKSlElJSUBKBRo0aWNsGqXOfOtHJlhVcOHiQHByKiESPI1ZW8vcv/eHlZUAj1ow4sZAL37NmzAbz22muiC3mA77//fujQoQsXLjTrs8r0626Eh4cLeXLbtm3bAAQGBkqbc+bMATBlyhT5K6mRwYMrXPgR0bffUps2RCa4JjTvuqP6UQdyPe3jvvT9UbGxsYJLsRj6dTeEPLxN6o7/8O54lNatWwM4IC28ZYHeeYdCQyu88uKL9MwzRBYfQiKKiSGVihQKMv/TPqpz6NAhAC1atBC1johl2r9/v62tbdeuCz//XNb/LUYT5+Pj4wF4eXlZ7qdz7Ro1bUqzZtGlS3T9On38MTk5UVISkQlCaLJha/czZAg+/BBEeOklHDpk7tbuSz82ymi1vHquT58+33zzR0LCOzNmKGNi5Gt3+/btBQUFPXr0kE5PpE9nzJgxlvvpeHggLg7p6ejRAwEB2LsXe/ciIAAAfHxwd0EqALCzwxNP1G7nJv26uK9p0wggd3cSss5qWVmZtGzesWPHBDRv8f79bwLI3l6+xRCGDh2Ku4tc6ZcGP3r0qEzNWxiZQlhaSsOGEUC+viT/w0Kl4dGtW7eWu+HHx6uvlq/NJcOD63NycmxtbfVPJt27d6/06VjUoFY5yXT0V6mwaRO6dcPFixgyBIWF8jRbTjrbGTdunKytPlb+9z/84x+4fh1PPYWcHPO2lZycbGtr27dvX2m4jP5cVHpCS30kZ+I1GvL2Jn//WxMnviXb155Wq3V3dwdw6tQpeVp8TOXlUceOBFCfPmTuhbmLioqkDhj+dEi201G9U6eKPDx8IeMjqWJiYgAEBATI09xj7epVat6cABozRqZ1unbu3AngiSeekKMxSyX3zSh/f7uoqLU2NjYffvjh8uXLZWjRUiaJPg6aNcP27WjQAFFR+Pe/5Wjx7bffBjBkyBA5GrNUCjIYECyb9evXT5w4UaVS/fDDDyb/AMrKys6ePSuNe46MjJROe1JTU319fU3bUF31448YOhRlZVi/HhMmmHjnt27dOnHihH5Uenx8vE6n27dvX79+/Uzc0mNE1CH43XffBeDk5JSYmPiIu3rgMyL1j61mNfTFF6RS0RdfmGBXmZmZP//88+LFi5955pm2bdsa3X1RKpUvvviiCZp5nIk5EgIgogkTJkRGRnp5eR05cqSFfrp+DRh9mx47dqy4uNjwDZ6entLsHl9f306dOnXs2NHU5dd9p09DrcZrr6FtW8ycWf7i11/jzh289FJ1P3jlChITkZCAP/+cGBu75+rVq4Z/a2dn16FDh86dOwcGBgYGBnbs2NESJy7Jy/TzCWtIoVCsXbtWo9Hs3bt30KBBcXFx0kNLqpSbm3vq1Knjd509e1an0+n/VqVSqdVqf39/aXJdSEhIo0aNZPkl6jK1GgB27YJGg7590akTACQno6DA+J0aDY4fL/9z9CgyMspf79Ll+tWrV52cnDp27Kj/dIKDgys/WLeeE3YklOTl5fXs2fPUqVMDBw7csWOHtN4WAI1GI+VNOtydPn3a8Kesra3btm2rn8zauXPnyqegzCR8fBAaigsXcPgwlEr8618oKMCbb+LgQSQkIDERJ04Yx7JxYwQGIjAQISEn1Wq7Nm3a1N8OwJoRHEIAf/31V0hISEZGRqdOnby9vQsLCxMSErKzsw3f4+zs3KlTJ+kEpnPnzn5+fvq4MrPy8cGGDXj9dUydiilTykPYvDneeefeezw9ERSEoCD4+0OthloNDl2tiA8hgN9//713797SbE7pFVdXV39/f/2xzs/Pz3KH9tZpPj745huUlmL4cJw5g08+QUEBxo7FF18gMBCdOyMwEO7uoqt8zFlECAFs3rx5xYoV1tbWM2bMCAwMlEb0MuGkEIaGIiIC1tZo1AgFBZClf7cesZQQMsukD+H161Cr0bkz2rXjEJoYn+OxGvHwwPz52LtXdB11EYeQVcfb+97ykFOnIiwM3Ptjcnw6yqpz4gRcXODjI7qOOo2PhKw6//wnfH2xfbvoOuo0PhKy+7p+Hc2bQ6XCtWtwcxNdTd3FR0J2X1u2oKwMgwZxAs2LQ8juy0Keplrn8ekoq9qVK/Dxgb09MjPh6Ci6mjqNj4SsatLTVIcN4wSaHYeQVY3PRWXDp6OsCmfPws8Prq64fh08+8/c+EjIqqB/mionUAYcQlaFLVsAPheVC5+OMmMJCQgKQuPG0GjAc6dlwP+PmbEdO5b07ZsdEvKylZW36FrqBT4SsgqIyNfX99KlS3FxcaGhoaLLqRf4mpBVcPjw4UuXLrVo0aJHjx6ia6kvOISsAn5Gkvz4dJTdIz2vMyMjIyEhITAwUHQ59QUfCdk9+/bty8jIaN++PSdQThxCdg8/wUoIPh1l5bRaraenZ3Z2dkpKilpaBJ/Jgo+ErNxPP/2UnZ3dqVMnTqDMOISsHJ+LisKnowwACgsLmzZtevv27YsXL/rw4mry4iMhA4DFixcXFhZ269aNEyg/DiHDokWLFi5cqFQqmzZtKrqW+ogHcNdrpaWl06ZN+/LLL5VKZWlpqdFTdZk8+EhYfxUUFAwbNuzLL790dHTctGmTUqk8efKkVqsVXVe9wyGspzQaTa9evXbv3u3h4fHbb789++yzbdq00Wq1Z86cEV1avcMhrI+Sk5O7d++elJSkVqvj4+ODg4MBdO7cGUBCQoLo6uodDmG988svv/Tq1SstLa1///6HDh3S3w6VxosmJiaKLK5e4hDWL2vWrBk8eHB+fv6ECRN+/PFHV1dX/V9xCEXhENYXRPT++++//PLLZWVl8+bNW79+vY2NjeEbpNPRpKQknU4nqMZ6ikfM1AvFxcUvvPBCdHS0jY3N6tWrx48fX+XbvL2909LSzp07165dO5krrM/4SFj33bx5MywsLDo6umHDhj/99NP9Egi+NyMIh7COu3DhQo8ePeLi4nx8fA4dOtSvX79q3syXhUJwCOuy+Pj4kJCQ8+fPBwcHHzlyxM/Pr/r3cwiF4BDWWVu3bu3fv39WVtbTTz+9f//+mowL5dNRITiEddOyZcueffbZO3fuTJ8+fevWrQ4ODjX5qebNmzdu3PjmzZtpaWnmrpDpcQjrmtLS0ilTpsycOVOhUCxbtmzZsmUqlarmPy6dkfLBUE4cwjrFcEz2d999N3369NrugS8L5cdTmeoOjUYTHh6emJjo4eERExMjjQitLQ6h/DiEdURycnJ4eHhaWppard61a9dDT5CX7s1wCOXEp6N1wcKFC4ODgyuPyX4Ibdq0cXZ2TktLy8rKMl2BrDocwrpg6dKlWq22adOmr7/+uqOj46PsSqFQBAQEgA+GMuIQ1gVSbDIyMkaMGNGsWbOpU6cePHjwocdh82WhzDiEdcHevXvj4+OXLl0aGBiYlZW1YsWKPn36eHt7z5gxIy4urrZj9DmEMuNZFHVNSkrKt99+u2nTpj///FN6pWXLlsOGDZswYYJ00+WBkpOTAwIC2rVrd+7cOXNWyspxCOuslJSUyMjIyMhIjUYjvaJWq0ePHj1u3Li2bdtW84OlpaVOTk7FxcW5ubnOzs6yFFuvcQjrOJ1Od/jw4W+//TY6OjozM1N6UUrjhAkTWrVqVeVPBQcHHzt27ODBg7169ZKx2HqKrwnrOKVS2bNnz2XLlmk0mj179kRERDg7O58+fXr+/Plt2rSR/kofTj2+LJQTh7C+UKlUYWFhGzduzMjIiImJiYiIsLe3P3To0MyZM728vHr27Llq1ar8/HzpzRxCOfHpaP1VWFi4a9eujRs3/vzzzyUlJQDs7OzCwsJGjx7t7e3dr1+/gICApKQk0WXWfRxChhs3bmzdujU6Ojo2NlbqXXR2dpaOirm5uS4uLqILrOM4hOye9PT0rVu3fvvtt4cPHyYia2vrEydOPHA+PntEHEJWhbi4uEWLFn388cf+/v6ia6n7OISMCcZ3RxkTjEPImGAcQsYE4xAyJhiHkDHBOISMCcYhZEwwDiFjgnEIGROMQ8iYYBxCxgTjEDImGIeQMcE4hIwJxiFkTDAOIWOCcQgZE4xDyJhgHELGBOMQMiYYh5AxwTiEjAnGIWRMMA4hY4JxCBkTjEPImGAcQsYE4xAyJhiHkDHBOISMCcYhZEwwDiFjgnEIGROMQ8iYYBxCxgTjEDImGIeQMcE4hIwJxiFkTDAOIWOCcQgZE4xDyJhgHELGBOMQMiYYh5AxwTiEjAnGIWRMMA4hY4JxCBkTjEPImGAcQsYE4xAyJhiHkDHBOISMCcYhZEwwDiFjgnEIGROMQ8iYYBxCxgTjEDImGIeQMcE4hIwJxiFkTDAOIWOCcQgZE4xDyJhgHELGBOMQMiYYh5AxwTiEjAnGIWRMsP8HgnruqIM8x+0AAAAASUVORK5CYII="
225
- ],
226
- [
227
- "caffeine",
228
- "CN1C=NC2=C1C(=O)N(C(=O)N2C)C",
229
- "data:image/png;base64,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"
230
- ],
231
- [
232
- "\u03b1-d-glucopyranose",
233
- "C([C@@H]1[C@H]([C@@H]([C@H]([C@H](O1)O)O)O)O)O",
234
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAIAAAD2HxkiAAAnBUlEQVR4nO3deXxM9/4/8NdMZrJvEhFJrIkQEkEssUVQtIilRYtq00rrJrWVX6t16XXba2/t+xcl1FV0T1FVoS0ipLiWWCNBloZsk5DMZJbP748zEokJmcjMZ5b38+HhMTnnZOYlD698zpw553NEjDEQQvgR8w5AiLWjEhLCGZWQEM6ohIRwRiUkhDMqISGcUQkJ4YxKSAhnVEJCOKMSEsIZlZAQzqiEhHBGJSSEMyohIZxRCQnhjEpICGdUQkI4oxISwhmVkBDOqISEcEYlJIQzKiEhnFEJCeGMSkgIZ1RCQjijEhLCGZWQEM6ohIRwRiUkhDMqISGcUQkJ4YxKSAhnVEJCOKMSEsIZlZAQzqiEhHBGJSSEMyohIZxRCQnhjEpICGdUQkI4oxISwhmVkBDOqISEcEYlJIQzKiEhnFEJCeGMSkgIZ1RCQjijEhLCGZXwCSoViot5hyBWxEJLuHcvGjXC3Lk6Vt2+jUaNMHBg9eWZmZg2DS1bQiqFmxvs7BAZifh4qNVGyEusmYR3AMMoK8P9+ygp0bFKpcL9+ygoqLLw2DGMHAmZDIGBiImBiwvS0/Hrr/jjD+zZg2++gaOjcYITK2ShJdRLZiZeeQUlJVi9GpMnQyyusvzgQcyYgU2buEYklsxCd0f1smgRCgsxdSqmTq1sIIAmTfDjj3Bzw5YtuHGDXz5i4ay+hIxhzx4AmDFDx1ofH0yYAI1Guw0hBmD1JUxLQ34+mjVD8+a6N4iMBIAzZ4wZilgVi35PuHMnDh2qvlCprPJlbi4A+PnV+CRNmgDA33/XbzRCKlh0Ce3t4elZfaFcXuVLoZOSmn8OUmnlZoQYgEWXcMwYrFpVfWFaGlq1qvzS3R0ACgtrfBLhwwwPj/oOR4iW1b8nbN0aEglu3IBCoXuDCxcAoF07Y4YiVsXqS+joiJ49oVDgxx91rK04djpokJFzEeth9SUE8P77ADB7Nu7fr75q40akpCAoCIMHGz0WsRZUQuDllzFhAm7dQvfu+OorZGVBJsO5c5g2DVOmwNER27fDxoZ3SmKxLPrATO1t344mTbBiBd54o8rydu3w5ZcID+cUi1gFEWOMdwYDuHULJ06gbVt06VJ91YMH+P57eHpiyJDqq3JzcfAgbt7Egwfw9kaPHoiIoDGQGJqFlpAQ80HvCWtHo8GmTfD3x7ZtvKMQS0Mj4bMI9du0CVevQqFAcDAuXeKdiVgUKmHNysuxfj127MDlyygv1y6USlFURNf4knpER0drUFqKsDDcvFl9egupFPHxiIvjFItYIHpPWANHR7RurWOCGakU+/bxCEQsFpWwZlu2IDCw+kKNBqmpyMriEYhYJiphzRo1QnQ0bG2rLFQqkZur4+IMQuqKDsw8lUaDHj1w+nSVhQ4OCA6ma+1JfaGR8KnEYqxaBW/vKgslEly/jpQUTpmIpaESPkv37hgwoMoSsRjFxVi5kk8eYnGohLWwfj2CgqovPHeO5rwg9YJKWAuurpg6FU5O2i81GgC4eRPff88xFLEYVMLaee89hIVpHwsDYHk5tm/nF4hYDiphrf3f/2lnRpTL4eAAAFeuoKiIayZiCaiEtRYUhOHDtfPkC1Mk3r6NDRv4hiIWgEqoj2XLEBwMQFtFxnDgAN9ExAJQCfXh4IBPPoGbW+WSK1dw7Rq/QMQSUAn1NGYMunSpPLE7P58+MCTPiU5b09/t22jXDqWlAOSOjsfd3F7IyhKJRLxjEXNFI6H+mjfH6NHldnZnXFwGKJUjZLIjR47wzkTMGI2EdaGRy1/u2PGnR+8GX3755e+++45vJGK+aCSsC7G9/eTVqz0e3SXm0qVLpaWlfCMR80UlrKNBgwZFREQIj2/durVjxw6+eYj5ohLW3datWwMDAwGo1ep9NOcFqSsqYd15enpGR0fb2toCuHbt2t90N19SJ1TC5zJ79uxOnToByMrKWrFiBe84xCxRCZ+LWCxeu3Zt48aNASQmJvKOQ8wSlfB5denSZeDAgQBu3Lhx9uxZ3nGI+aES1oO1a9e2bdtWJpOtpFPYiP6ohPXA1dV12rRpTk5Of/31l0ql4h2HmBkqYf2IjY3t2rXrzZs3v6c5L4ieqIT1ZsOGDY0aNdpOc14QPVEJ601QUNDw4cPPnTtXRHNeEH1QCevTF198YWtru3HjRt5BHiOXY9Uq9OwJFxeIRHB2Rs+eWLECZWW8kxEtuoqinu3du3fOnDk3btzgHQQAkJODl17ChQtwc0NkJBo3Rm4ujh2DTIbgYPzyC5o04R2RUAkNoHv37n5+fp07d27btq2Hh4enp6fwt52dnVFzqNXo0wcnT2LcOGzYUDkrR1ERJk3Cvn3o1g0nTmgnrSL8UAnrWUZGRuvWrZW6Jue2t7dvUANfX18fHx/hsZeXl1QqrYco+/bh1VcRFobk5OpNU6nQtSvOn8d//4tx4+rhtchzoN+C9ax///5KpdLW1nbEiBEqlSo/P7+goED4Wy6X5+Tk5OTkPPNJsvr29c3MhKcnPDyq/F3xoGFDeHrC1fVpz7JrFwC8/76OsU4iwcyZePNN7NpFJeSOSliffvzxx/T0dJFItH///gHVbiMDlJWVFeqSk5OTnZ1d8WVeXp5LQQFu3sTNm894PYlER1E9PTF6NAIDtXd069NH9/f26weg+l3fCA+0O1pv8vLyQkJCcnNzV65cOX369Od5KnVBgU1+PgoKoPPvij8lJbq/PyEBQ4ZAKgVjUCphY6NjG8YglUKjQXk5vS3ki3769SYuLi43N7d///7Tpk17zqeyEfY8n0mp1DazWlHbtIFKBY0GNja6GwhAJIJEAoWCSliFRoPDh3HkCLKywBi8vdG3r/Y3WoXcXKxYgRYtEBur4xk+/RRlZVi8WI8XZaQ+7Ny5E4Crq+vt27d5Z3nE0ZEBrLhY99qHDxnA7OyMm8m03bjBOnViAAOYvb32BwiwwECWklK52cWLDGC9eul+Ejc3pmet6MP6epCdnS3sf65cubJZs2a84zzSti0AXLqke+3Fi5XbEAD37qFPH5w7h9dfx+XLKCvDw4dIS0NsLG7cQL9+hptqnUpYD959992CgoKoqKi3336bd5bHvPACAHz7re61wqQ4Txw9sl4ffoicHEyahK++Qrt22oX+/tiwAbNno6QEkycb6qWfY/AmjDG2adMmAO7u7nfv3uWdpaqbN5mtLXNwYBcuVF914QJzcGB2diwtjUcy01NQwGxtmb09KyjQsVYuZw0bMoBdvcoY7Y6amIyMjA8++ADAhg0bmpjaKWABAVi8GGVl6NsX69YhKwsAsrOxfj369kVZGRYtgr8/75SmISkJ5eXo3RsNGuhYa2eHwYMB4I8/DPHidFis7jQazcSJE0tKSkaOHDl27FjecXSZMQMODvj4Y0yZgilTKpe7u2PjRvzjH/ySmZi0NABo06bGDYKCKjcTZGVh6VIdWyoU+r44lbDu1qxZc/ToUS8vL2GP1ETFxuK113DwIM6dQ3ExXF3RqRMGD9b9K99qCZ+4OjvXuIFwcpJMVrkkIwMffVQvL04lrKO0tLS5c+fi0bW8vOM8VYMGGD8e48dXX65QwMjnlJssJycAT7u86+FDoGpLw8Lw3//q2LJr1xpPoqgBlbAuNBrNW2+99eDBg+jo6FGjRvGOUyfz52PlSpw6hVateEcxAcL7+fT0Gje4dQsAHv/8ycFB9+6rWO/jLHRgpi6WLFly/PhxPz8/M57wNyMD+flYsoR3DtPQvTtEIhw/rvsdHWMQ7n7Xs6chXpxKqLfU1NTPPvtMJBJt3ry5gfm+s5o9GxIJ4uNx+zbvKCbAzw8DB6KwEDpnRfj6a6SloUMHdO5siBenEupHpVJFR0fL5fLY2NjBwmFrMxUQgNdeg1KJL77gHcU0fP457Owwaxa2bMHjVzV88w3efRdisQHvi67vp5pW7l//+heAli1bFtd0TqYZSU1lYjGzt2dZWbyjmIaffmJOTgxgTZqwYcPYiBHM3197hm18fOVm9GE9R+fOnVu0aJFYLN62bZuLiwvvOM+tbVu8/DLkcgP+jjcvw4bh+nV89BEaNUJyMk6cgJMTpk9HairefLNyM2dn9O2LsDDdTxIRgb599XpZup6wthQKRZcuXS5dujRz5sxly5bxjlNPzp9HWBgcHZGeDi8v3mmsFI2EtTV37txLly4FBQXNnz+fd5b607EjhgzBw4dYs4Z3FH6OHsXbb1f5IN64aCSslaSkpIiICJFIdPz48fDwcN5x6lVyMrp3h5sbMjLg7s47jdGVlCA0FBkZ+PxzfPABlwg0Ej5baWlpdHS0Wq3++OOPLa2BAMLD0b8/ZDKsW8c7Cg8zZyIjA5064flmJHkudT6SZD0mT54MoEOHDgqFgncWwzhyhAHM05OVlPCOYly//spEImZnxy5e5JiCRsJnSExMXL9+vVQq3bp1q3B7egvUvz9690Z+Pkz5TPR6V1SEiRPBGObPR0gIzyQcfwGYPplMJkxXMX/+fN5ZDGz/fgawxo1ZaSnvKMby+usMYD17MpWKbxAq4dMI01WEhYWVl5fzzmJ4XbowgK1bxzuHUfzwAwOYoyO7fp13FCphzRISEgDY2dldunSJdxaj+PZbBrCmTZmlvvWtcP8+8/Y2nd84VELdCgsLhekqli9fzjuLsWg0rH17BrCtW3lHMbDRoxnA+vdnGg3vKIxRCWsiTFfRq1cvFe83DEa1axcDWEAAUyp5RzGYnTsZwFxdmcnMEEsl1EG477yTk9ONGzd4ZzEulYq1bs0AtmsX7yiGkZXFPDwYwL78kneUSnqeMfOvf8HBAbNn61i1dStu38aUKag210NhIX76CSkpyM+HszOCghAVhdat63gw1/Du378fEhJy7969DRs2xOqc59yiyXfsWLZt2+Hy8sQ//xTrf5G4qRs6FAcOICoKCQm8ozxGv84CzM1N96pevRhQ/UPPL79k7u7aucQdHLQPxGI2aZLJvvt/5ZVXAAwYMEBjGm8YjKy8vLxFixYAvv32W95Z6tumTQxg7u7MxGaINWQJt23T7nyvXs3u32eMMbmc/fQTCwpiABs1qo6RDWn79u0A3NzcTOiWEka3du1aAB07drSoX0Pp6czFhQFs927eUaozWAnz8pizM5NI2PHj1bfMy2MtWjCAmdjv2szMTGG6ivjHr+C0PnK53NfXF8D+/ft5Z6knajXr25cBbORI3lF0MFgJly9nAJswQffGwhGqF16oXLJiBfP3Z+PGsT17WFoaU6v1C/bcNBqNMF3FsGHDjPzSJuiLL74A0L17d95B6snKlQxgXl4sN5d3FB0MVsLhwxnA9u7VvXFxMROLmYMDqzgT5Y03tGcw2NszOzvm68u6dWNRUSwuju3dy27dMvRHOuvXrwfQsGHDnJwcg76QWXjw4IGXlxeAxMRE3lme17Vr1/4W/nP+8APvLLrpP++oQqF7NoTs7CpfChOGC5OHP8nFBU2a4M4dZGWhRQsAKC2t/BtAcTGuXsWlSygtxYYNaNAAnp7w9ISXF7y9ERmJsDC0bVuHOR51Sk9P/+ijjwCsW7eucePG9fKcZs3JyWn69Olz585dsGBBP+HG2uZJrVa/9dZbyadOHfzoo0EjRvCOUwP9Oisc3nzKn4qRsGlTBrD09BqfKiSEAez8ee2XQ4boeDaxmDk5MWdnZmtbZblIxBo2ZK1bsx492MiRRTExP+7bl5GRUZffQoyp1erIyEgA48aNq9szWCSZTCa8Qz7+5Lt687Fw4UIAfn5+BTpvt2Qa9C+hiwu7cEHHH+EWpxUlFA6BpqbW+FTCPFYVn4b36/e0bkskzNWVubkxG5sn18qcnRsAbm5u/v7+Xbt2jYqKmjx58g8//FDLI5wzZ84E4OPjk5eXp99Pw9J98sknAIYOHco7SB1dvnzZ3t5eJBIdOHCAd5anMdh7wgEDGMBqOrymUDBbWyaRsIcPtUvCw589zAo3MXZzYy4uTCSqWHjP2VnnXqm7u3tAQEB4eHhUVFRcXNzevXvT0tKqHXbfv3+/sPGePXv0+1FYgfz8fGFSuTNnzvDOojelUtmlSxcAcXFxvLM8g8FKOHcuA9j/+3+6N/7tNwawsLDKJaGhtSphxR9nZ+bqKtxV/K5wx5xaqFbLffv2CXtcgYGB+v0crMaHH34IYJRJfqj7dGY0Q6yep62JRHBzQ1GRjlW9e+PECVy8qL1I+fp1BAWhQQOkpsLbu8qWjGHgQBw5gtWrMXWqdmHz5rhzBwDEYjg6aqdArnhgawuxGIxBLIZUCo0GNjZwcEBeHoqKchwchrZoAcDBwUEkEqnValtbWwcHB7lcLpVKnZycysvLRSKRk5MTY0ypVDo7O0ul0rKyMkdHx507d6pUqs2bN7/zzjt6/BysRm5ubsuWLeVy+YULF0L4Xn6uj3PnzoWHh6vV6sTEROENv0nTr7N6nTEzZYp2uHv8NOiSEjZxIgNYSAiTyyuXHz3KDh9mhw+zlBTtnytXWFoaS0tjd++yggJWUMAKC/VLWwsdOnQA0Lx583p/ZosxZcoUAK+//jrvILUll8uF3xczZ87knaVWDFlCuZy99pr2sEqvXmzsWDZ4MHN1ZQBr2/ZpB06N6PTp02KxWCwWX7t2jXcWE3Xnzh1bW1sbGxtz+REJNzAPCgoqNZOpOvQsYa9e7KWXdK+Ki2O9erFbt6ovT0hgo0axJk2YvT1r1IhFRrLVq6uMgbxNmjQJwMSJE3kHMV3CvnpMTAzvIM928uRJGxsbiURy6tQp3llqi64nZGlpaRKJRCqVppvG4GyCzOVH9PDhw8DAQABz5szhnUUPFnfBmP78/f3Hjx+vVCo///xz3llMlL+//9ixY5VK5RemfR+1WbNm3bhxo0OHDsKhUXNB0+ADwNWrV4ODg6VSaVpamp+fH+84pujKlSshISEikSglJaVjx4684+iQmJg4YMAAiUSSlJTU2TB38zQQGgkBICgoaNSoUQqFYvny5byzmKi2bdtKJBK1Wp2SkgLg9OnTR4Q7SAPr1q3Ly8sDcOfOnY2P7nT79ddfnzlzBgBjbOrUqcLv+t9//33Hjh3CBvPmzbt165bwXR88ugnEpk2bDhw4IDweOXKkXC4H8Ntvvy1YsEBYOHny5LNnzwLIzMwcNmxYRbzi4uK3336bMTZv3jzzaiCohBXmzJkjEok2btx479493llMUUxMjPBxa9euXQG0b9++U6dOwqqrV69qNBoAqampwvQ8AL799tuMjAwAMpls586dIpEIwJkzZy5cuCBssGfPnvLycgC3b99OTk4WFp48efL+/fsAlErlgQMH7OzsAFy/fj0zM1PYICkpSXiQm5ublZVVEW/GjBl37twJCwubNWuWAX8KhkEl1OrQoUNUVFRpaemqVat4ZzE5WVlZe/fuBRAbGyt8surg4ODh4SGsXbNmTaNGjQB079599erVwsJmzZoJk5fn5+d7enoKC3U+rrZQeFrhgVDdgoKCJzd4fOHPP//85Zdf2tnZ7dixQyqVGvZnYQBUwkpz584FsHbt2sLCQt5ZTAhjLCYm5sGDB0OHDhWuuqyJu7t7mzZthMfLli0T7mDl7e29YcMGYaGNjU3Tpk2F5ywqKhLOGazoFR6r1uMLn76BRqMR9mYXLlwYHBxcv/9246ASVurWrdvAgQOLi4vXWPMdM5+wbt26Q4cONWzYcOvWrXX4dmdn50GDBgmP58+fP2HCBABqtXrNmjUSiQRAaWnp4317cnisKF55eXl5ebmzs/PjC8Vi8aFDhz744IP333//+f6h/PD8fMT0HDt2DICHh4fpn/VrHGlpacJ/+n379hnh5RYuXCiTyRhj33///YgRI4SFQ4cOTUhIYIxlZ2c3btxYWPjZZ5/NnTvXCJGMgEbCKiIjIyMiIgoKCiqO8lkzjUbz9ttvP3jwYMKECaNHjzbCK86ePdvV1RXAkCFDKo6j9u7dOyAgAE/sl1Y8Nnu8fwuYnF9++QWAt7e3uZx5aDhLly4F4Ovrm5+fzzsLY4zJ5fIrV64Ijz///HMTv1S39ujDeh26det25syZ1atXT6240sr6XLlypXPnzmVlZfv37x8yZAjvOJaMdkd1mDNnDoClS5cKH2RZIZVKFR0dXVZW9u6771IDDY1KqMPw4cNDQ0MzMzPj4+N5Z+FjwYIFZ86cadGihYmfLGoZaHdUtz179owdO9bf3//atWvCkXTrcf78+fDwcJVK9dtvv5n1fIfmgkZC3caMGdOmTZtbt27t3r2bdxajUigUb775Znl5+dSpU6mBxkEl1E0sFn/88ccAFi5cKJwYaSX+/e9/X7x4MSAgYP78+byzWAvaHa2RUqls06ZNenr63r17x4wZwzuOMZw6dap3796Msd9//713796841gLGglrJJVKhVPyFyxYYA2/qkpLS6Ojo9Vq9axZs6iBxkQj4dMoFIqAgICsrKyEhISoqCjecQxr+vTpq1evbteu3V9//WVvb887jhWhkfBp7OzshDP0Lf4N0vHjx9euXSuRSOLj46mBRkYlfIbY2FgfH5/k5OTffvuNdxZDefjw4VtvvaXRaD755BNh6nhiTFTCZ7C3t582bRqAihkWLM+MGTPS0tI6deo0e/Zs3lmsEb0nfLYHDx60bNkyLy/vjz/+iIiI4B2nnh0+fPjFF1+0tbVNSUkxo4nuLQmNhM/m7Ow8efJkAMLN7ixJUVGRMKXvf/7zH2ogLzQS1opMJmvevLlMJjt9+rQw05FlmDBhwq5du3r06PHnn3/a2NjwjmOlaCSsFTc3t9jYWFjWYPjjjz/u2rXL0dExPj6eGsgRjYS1lZeX16JFi9LS0vPnz4eGhvKO87zy8vJCQkJyc3PXrl0r7GwTXmgkrK2GDRu+8847jLFFixbxzlIP4uLicnNz+/fv/9577/HOYu1oJNRDTk6Ov7+/Uqm8fPlyxdx+5uirr7564403XF1dL168KMwOSjiikVAPPj4+wtmVixcv5p2l7rKzs6dPnw5gxYoV1EBTQCOhfu7cudOqVSsA165da9myJe84dTF06NADBw5ERUUlJCTwzkIAGgn11axZM7O+j9rmzZsPHDjg7u5eMSs24Y5GQr3dvHkzKChIIpGY3X3UMjIyOnToUFxcvHv37rFjx/KOQ7RoJNRbq1atRo8erVAoli1bxjuLHjQazcSJE4uLi0eOHEkNNCk0EtbF5cuXQ0ND7e3t09PThRsSmb5Vq1a9//77Xl5ely5dMpfMVoJGwroIDg4eNmxYaWnpypUreWeplbS0NOGeUxs2bKAGmhoaCevo7NmzXbp0cXFxycjIEG7xZbI0Gk1kZOTx48ejo6O3b9/OOw6pjkbCOgoLCxPuo2b6g+HSpUuPHz/u5+e3YsUK3lmIDjQS1t2ff/7Zp08fsVicmZnp4+PDO45uqampnTt3VigU+/fvHzx4MO84RAcq4XNxd3eXyWQBAQETJkzo2LFju3btPDw8PD09hfs8c6dSqXr06JGSkhIbG0sfDJosKuFzWbJkiTBHcDX29vYNHvH19fXx8WmgS+PGjcViA74jmDdv3meffdayZcv//e9/Li4uhnsh8jyohM9r9erVq1atKikp8fLyUiqVBQUF+fn5tfxeW1tbYeR8/O8KFQs9PDzqMAPazz///Morr6jV6sTExMjISH2/nRgNlbDulEqlSCTSebuYsrKywkdycnKys7MLdfn7779r+fN/fGit5vGR1svLSyqVApDJZN7e3gqFIiYmZsuWLfX8Lyf1ikpYd9u3b/fx8XnxxRfr/Azl5eX5+fnC4Fnxd15e3pMLFQpFLZ/T2dlZoVAwxlQqla2tbU5OjuXcVtpCUQnNw+ND6+OqDbN5eXlKpVL4FrFYvHnz5okTJ/JNTp7Juu68V1+Sk5ObNm3q6+trtFd0cHBwcHCozStmZ2dfvnz57t27W7ZsMdOLrawNlbAukpOTN27cuG3bNt5BdPD19RW66urqGh0dnZ6eTpM4mTjaHa0jxpiJfBhYE5VK5e/vHxMTM2/ePN5ZyNPQaWv6iYmJSUxMBGDiDQQgkUj69OmzdevWrKws3lnI01AJ9fPqq69OmTKlsLCQd5BamTVrVnFxcVxcHO8g5Glod7S28vPzPT09AWg0GoOe5lK/wsPDr1+/Hh8fP3z4cN5ZiG5m85+JL5lMFhoaunjxYrVabUYNBNC/f/+ioqJ58+bV/pNGYmTm9P+JIzc3t6SkpCNHjnz11Ve8s+hnxowZfn5+Fy9eFC7qJSaIdkefobCw8ODBg+PHjwfAGGOMmddICKB///5Hjx5t0qTJsWPHAgICeMch1ZnZ/yfjKygoWLp06ciRI3Nzc0Uikdk1EMC4ceMkEklmZuakSZN4ZyE6mN9/KSMLCAg4ffp0SEhIdHQ07yx19MYbb/j7+wM4ffq02e1OWwPaHa3Ztm04cwZLl8LZGYBSqRQuUDBHo0aN+u677wAEBwcnJyc7OTnxTkQq0UhYs1GjoFYjNBTHjgEw3wYCmDJlirOzM4DU1NQZM2bwjkOqoJFQl7t34eMD4ULBhARMnowTJ9C0Ke9Yz6Vz585nz54F4OPjc+jQofbt2/NORLRoJNRl6VL06IHUVAAYNgw3bph7AwF069ZNeJCTkxMXF0e/fE0HlVCXNWswdSr69sWSJVCrYWfHO1A9+PDDDyum/b2TknL4ww/55iEVqIRVrVqF7GwAePNNJCfjl1+wfDnvTPXD39+/devWfsAeJ6e/7OwGbtyIoiLeoQhAJayCMRQWIiwMu3cDQMuWOHIEU6fyjlVv5kZGXnFyevXhQ6/iYpFYDDqx2zTQgZlHGINwddKFC4iORqtWWL8eXl68Y9Wr4mJ07Ij0dABwcYFUiu++A03ExhuNhACAvDyEhuLoUQAIDUVSEvz9MXgwLOw3lKsr2rbVPlarUVCAWbOgVnPNRKiEgoYNsWwZoqPxj3/gwQPY22PJEhw7BpO/cldvMTHa40ylpbCzw7lzWLSIdyZrZ/UllMtx+TIADBqEixcBoEMH/PEHAOFEGUszciQCA7WPpVIoldi2DTk5XDNZO6sv4YUL6NcPixdDrYabGzZtwhdfYOxY/O9/vJMZhliMjh21j4UJoG7dwnvv8QtE6MAMgDt3EBODkhJs346gIACQyeDmxjuWwVy8iL59UVAANzfIZADg7o6dOxEVxTuZlbLikTAhATt2AECzZvj1V0yciD59sGQJNBpLbiCA9u21e6QajXZJURH+/W88mjWYGJkVl7BZMyxbhpEjkZsLkQiTJuHkSSQkwBquQH/hBQBVjoueP49//pNXHCtn3bujSiWWL8fy5Vi2DBMmAIBajZISuLtzDmZo9+6hc2dkZsLeHnK5dmGTJvj9d/j7c01mjay7hILTpxEdjfbtsX49GjbkncZYBgzAkSNwcUFJSeXCQYNw6BC/TFbKindHK3TrhrNn0aQJeva0ovdF48ZBIkG12TqSkkCX3hsdjYSPuXcPj64zsHzl5QgNxd9/aw+QVggORnIy6NJ7IzK3kVAmQ0QExo/Xvfb11xERoePigIQEjB+Pdu3QuDHatMHLL2PXLh2na1lPAwHY2iI4WMcPITUVM2fyCGS9zG0kvH8fjRohMBDXr+tY26YNrl9Hbm5lnUpLMXYsEhK0H1J7e6OwECkpUKkQHo6ffrKu4lXzxx946SWUlVVf7uODQ4dAl94bi7mNhPp65x0kJKBnT1y/jr/+woEDSErC7dsYPBjJyXjlFas+fblPH7i5wd6++nKlEl9/zSOQlbLoEp48id270bQpDh7E45Pe+vrihx8QGooTJ6z9f1tUlHYqHYGXFyIjcfAgFizgl8nqWHQJt28HgKlT4epafZWtLWbPBoD4eCOHMi1z52oPkPr6YuhQHDuGY8fQpQvvWNbFou/Ue+oUAPTvr3vtgAHabSou57VCzZujaVOEhGDJEjRvzjuNlTLPEmZnQ+eNvqrdDTMzEwBatND9JA0baj+qlsks/xSZp7h0iXcCa2eeJZTLkZSke/mTXz5lrjQHB5SUoLTUqkv4pOvXcfgwbt+GSgUfH0REIDy8ys7CgwfYvh0NG2LsWB3fvmMHiovxj3/AnKdLNipmXu7dYwALDNS9tnVrBrDcXO2X3t4MYNnZujfWaJi9PQPYw4cGiWqOCgrY6NFMJGJAlT9du7LU1MrN7txhAGvfXveTNGvGACaTGSeyBbDoAzPCBTtXr+pem54OuRx+fnB0NGYo01VWhhdewDffoEcP7N+Pe/dQVIQTJ/DqqzhzBr17Iy2Nd0TLZNElFOYRS0jQvfaHHwCgXz+jxTF18+fj3DkMGoRjxzBkCLy84OaGnj2xZw9mzEBBAejOaoZh0SWMiYFUik2bcO1a9VW5uViyBCIRzeygVV6OjRshEmHdOh3v5RYtgrc3EhPpKI4hWHQJW7bEp5+itBT9+mHvXu1xGpUKBw8iIgL37iEuDj168E5pGs6fR0EBOnRAq1Y61trZYcQIAEhMNHIua2CeR0drb/ZsiET49FO89hrs7ODhgaIilJVBIsHMmVi6lHc+kyHsLAQH17iBsOrxfYqiIuzapWPLhw/rN5rFM7cSOjlh7twaL7197z3k5VW/DOfjj/H669i3D2fPIi8PHh4ICcHo0Wjd2gh5zYZwQdOTpxZVED7Fefy6p7t3tdMRkOdjbiV0dMR//lPj2unTdS9v2pQuz3kGBwcAUChq3EDYmX/8SLK/PzZv1rHl+PHIza3fdJbN3EpIDMTHBwDu3Klxg9u3AcDXt3KJk5PuUwIt4k5yxmTRB2ZI7XXtCrEYp0/ruLxQcOwYAISHGzGTtaASEgCAlxcGDUJxse49zBMnkJQEHx/tXImkXlEJySMLFsDWFh99hG++qbL87Fm89hoYw8KFsLXlFM6S0XtC8khYGOLjER2NMWPQsSO6d4dUisuX8fvvUKvxz3/irbd4R7RMVELymLFj0akTlizBL79g40YAcHPDsGGYMQN9+lRuJpUiMLDGa8T8/WFnp73bDKkFc5voiRhNeTlUKjq73QiohIRwRgdmCOGMSkgIZ1RCQjijEhLCGZWQEM6ohIRwRiUkhDMqISGcUQkJ4YxKSAhnVEJCOKMSEsIZlZAQzqiEhHBGJSSEMyohIZxRCQnhjEpICGdUQkI4oxISwhmVkBDOqISEcEYlJIQzKiEhnFEJCeGMSkgIZ1RCQjijEhLCGZWQEM6ohIRwRiUkhDMqISGcUQkJ4YxKSAhnVEJCOKMSEsIZlZAQzqiEhHBGJSSEMyohIZxRCQnhjEpICGdUQkI4oxISwhmVkBDOqISEcEYlJIQzKiEhnFEJCeGMSkgIZ/8fQCoX2YRsNUMAAAAASUVORK5CYII="
235
- ]
236
- ]
237
- }
238
- },
239
- "other": {},
240
- "relations": []
241
- },
242
- "error": null,
243
- "input_metadata": [
244
- {
245
- "dataframes": {
246
- "df": {
247
- "columns": [
248
- "image",
249
- "name",
250
- "smiles"
251
- ]
252
- }
253
- },
254
- "other": {},
255
- "relations": []
256
- }
257
- ],
258
- "meta": {
259
- "color": "orange",
260
- "inputs": [
261
- {
262
- "name": "bundle",
263
- "position": "left",
264
- "type": {
265
- "type": "<class 'lynxkite_graph_analytics.core.Bundle'>"
266
- }
267
- }
268
- ],
269
- "name": "View tables",
270
- "outputs": [],
271
- "params": [
272
- {
273
- "default": 100,
274
- "name": "limit",
275
- "type": {
276
- "type": "<class 'int'>"
277
- }
278
- }
279
- ],
280
- "type": "table_view"
281
- },
282
- "params": {
283
- "limit": 100.0
284
- },
285
- "status": "planned",
286
- "title": "View tables"
287
- },
288
- "dragHandle": ".bg-primary",
289
- "height": 418.0,
290
- "id": "View tables 2",
291
- "position": {
292
- "x": 815.4121289519509,
293
- "y": -330.8232285057863
294
- },
295
- "type": "table_view",
296
- "width": 1116.0
297
- },
298
- {
299
- "data": {
300
- "__execution_delay": 0.0,
301
- "collapsed": null,
302
- "display": null,
303
- "error": "module 'rdkit.Chem' has no attribute 'Draw'",
304
- "input_metadata": [
305
- {}
306
- ],
307
- "meta": {
308
- "color": "orange",
309
- "inputs": [
310
- {
311
- "name": "df",
312
- "position": "left",
313
- "type": {
314
- "type": "<class 'pandas.core.frame.DataFrame'>"
315
- }
316
- }
317
- ],
318
- "name": "Draw molecules",
319
- "outputs": [
320
- {
321
- "name": "output",
322
- "position": "right",
323
- "type": {
324
- "type": "None"
325
- }
326
- }
327
- ],
328
- "params": [
329
- {
330
- "default": null,
331
- "name": "smiles_column",
332
- "type": {
333
- "type": "<class 'str'>"
334
- }
335
- },
336
- {
337
- "default": "image",
338
- "name": "image_column",
339
- "type": {
340
- "type": "<class 'str'>"
341
- }
342
- }
343
- ],
344
- "type": "basic"
345
- },
346
- "params": {
347
- "image_column": "image",
348
- "smiles_column": "smiles"
349
- },
350
- "status": "done",
351
- "title": "Draw molecules"
352
- },
353
- "dragHandle": ".bg-primary",
354
- "height": 296.0,
355
- "id": "Draw molecules 1",
356
- "position": {
357
- "x": 351.1956913898301,
358
- "y": -235.00831568554486
359
- },
360
- "type": "basic",
361
- "width": 212.0
362
- }
363
- ]
364
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/Model definition.lynxkite.json DELETED
@@ -1,671 +0,0 @@
1
- {
2
- "edges": [
3
- {
4
- "id": "MSE loss 2 Optimizer 2",
5
- "source": "MSE loss 2",
6
- "sourceHandle": "output",
7
- "target": "Optimizer 2",
8
- "targetHandle": "loss"
9
- },
10
- {
11
- "id": "Activation 1 Repeat 1",
12
- "source": "Activation 1",
13
- "sourceHandle": "output",
14
- "target": "Repeat 1",
15
- "targetHandle": "input"
16
- },
17
- {
18
- "id": "Linear 1 Activation 1",
19
- "source": "Linear 1",
20
- "sourceHandle": "output",
21
- "target": "Activation 1",
22
- "targetHandle": "x"
23
- },
24
- {
25
- "id": "Repeat 1 Linear 1",
26
- "source": "Repeat 1",
27
- "sourceHandle": "output",
28
- "target": "Linear 1",
29
- "targetHandle": "x"
30
- },
31
- {
32
- "id": "Input: tensor 1 Linear 1",
33
- "source": "Input: tensor 1",
34
- "sourceHandle": "output",
35
- "target": "Linear 1",
36
- "targetHandle": "x"
37
- },
38
- {
39
- "id": "Constant vector 1 Add 1",
40
- "source": "Constant vector 1",
41
- "sourceHandle": "output",
42
- "target": "Add 1",
43
- "targetHandle": "b"
44
- },
45
- {
46
- "id": "Input: tensor 3 Add 1",
47
- "source": "Input: tensor 3",
48
- "sourceHandle": "output",
49
- "target": "Add 1",
50
- "targetHandle": "a"
51
- },
52
- {
53
- "id": "Add 1 MSE loss 2",
54
- "source": "Add 1",
55
- "sourceHandle": "output",
56
- "target": "MSE loss 2",
57
- "targetHandle": "y"
58
- },
59
- {
60
- "id": "Activation 1 Output 1",
61
- "source": "Activation 1",
62
- "sourceHandle": "output",
63
- "target": "Output 1",
64
- "targetHandle": "x"
65
- },
66
- {
67
- "id": "Output 1 MSE loss 2",
68
- "source": "Output 1",
69
- "sourceHandle": "x",
70
- "target": "MSE loss 2",
71
- "targetHandle": "x"
72
- }
73
- ],
74
- "env": "PyTorch model",
75
- "nodes": [
76
- {
77
- "data": {
78
- "__execution_delay": 0.0,
79
- "collapsed": null,
80
- "display": null,
81
- "error": null,
82
- "input_metadata": null,
83
- "meta": {
84
- "categories": [],
85
- "color": "green",
86
- "doc": null,
87
- "id": "Optimizer",
88
- "inputs": [
89
- {
90
- "name": "loss",
91
- "position": "bottom",
92
- "type": {
93
- "type": "tensor"
94
- }
95
- }
96
- ],
97
- "name": "Optimizer",
98
- "outputs": [],
99
- "params": [
100
- {
101
- "default": "AdamW",
102
- "name": "type",
103
- "type": {
104
- "enum": [
105
- "AdamW",
106
- "Adafactor",
107
- "Adagrad",
108
- "SGD",
109
- "Lion",
110
- "Paged AdamW",
111
- "Galore AdamW"
112
- ]
113
- }
114
- },
115
- {
116
- "default": 0.0001,
117
- "name": "lr",
118
- "type": {
119
- "type": "<class 'float'>"
120
- }
121
- }
122
- ],
123
- "type": "basic"
124
- },
125
- "op_id": "Optimizer",
126
- "params": {
127
- "lr": "0.1",
128
- "type": "SGD"
129
- },
130
- "status": "done",
131
- "title": "Optimizer"
132
- },
133
- "dragHandle": ".drag-handle",
134
- "height": 250.0,
135
- "id": "Optimizer 2",
136
- "position": {
137
- "x": 359.75221367487865,
138
- "y": -1150.2183224762075
139
- },
140
- "type": "basic",
141
- "width": 232.0
142
- },
143
- {
144
- "data": {
145
- "__execution_delay": 0.0,
146
- "collapsed": null,
147
- "display": null,
148
- "error": null,
149
- "input_metadata": null,
150
- "meta": {
151
- "categories": [],
152
- "color": "orange",
153
- "doc": null,
154
- "id": "Activation",
155
- "inputs": [
156
- {
157
- "name": "x",
158
- "position": "bottom",
159
- "type": {
160
- "type": "<class 'inspect._empty'>"
161
- }
162
- }
163
- ],
164
- "name": "Activation",
165
- "outputs": [
166
- {
167
- "name": "output",
168
- "position": "top",
169
- "type": {
170
- "type": "None"
171
- }
172
- }
173
- ],
174
- "params": [
175
- {
176
- "default": "ReLU",
177
- "name": "type",
178
- "type": {
179
- "enum": [
180
- "ELU",
181
- "GELU",
182
- "LeakyReLU",
183
- "Mish",
184
- "PReLU",
185
- "ReLU",
186
- "Sigmoid",
187
- "SiLU",
188
- "Softplus",
189
- "Tanh"
190
- ]
191
- }
192
- }
193
- ],
194
- "type": "basic"
195
- },
196
- "op_id": "Activation",
197
- "params": {
198
- "type": "LeakyReLU"
199
- },
200
- "status": "done",
201
- "title": "Activation"
202
- },
203
- "dragHandle": ".drag-handle",
204
- "height": 200.0,
205
- "id": "Activation 1",
206
- "position": {
207
- "x": 99.77615018185415,
208
- "y": -249.43925929074078
209
- },
210
- "type": "basic",
211
- "width": 200.0
212
- },
213
- {
214
- "data": {
215
- "__execution_delay": 0.0,
216
- "collapsed": null,
217
- "display": null,
218
- "error": null,
219
- "input_metadata": null,
220
- "meta": {
221
- "categories": [],
222
- "color": "gray",
223
- "doc": null,
224
- "id": "Input: tensor",
225
- "inputs": [],
226
- "name": "Input: tensor",
227
- "outputs": [
228
- {
229
- "name": "output",
230
- "position": "top",
231
- "type": {
232
- "type": "tensor"
233
- }
234
- }
235
- ],
236
- "params": [
237
- {
238
- "default": null,
239
- "name": "name",
240
- "type": {
241
- "type": "None"
242
- }
243
- }
244
- ],
245
- "type": "basic"
246
- },
247
- "op_id": "Input: tensor",
248
- "params": {
249
- "name": "Y"
250
- },
251
- "status": "done",
252
- "title": "Input: tensor"
253
- },
254
- "dragHandle": ".drag-handle",
255
- "height": 200.0,
256
- "id": "Input: tensor 3",
257
- "position": {
258
- "x": 454.7823474758749,
259
- "y": -212.0655794519241
260
- },
261
- "type": "basic",
262
- "width": 200.0
263
- },
264
- {
265
- "data": {
266
- "__execution_delay": null,
267
- "collapsed": true,
268
- "display": null,
269
- "error": null,
270
- "input_metadata": null,
271
- "meta": {
272
- "categories": [],
273
- "color": "orange",
274
- "doc": null,
275
- "id": "MSE loss",
276
- "inputs": [
277
- {
278
- "name": "x",
279
- "position": "bottom",
280
- "type": {
281
- "type": "<class 'inspect._empty'>"
282
- }
283
- },
284
- {
285
- "name": "y",
286
- "position": "bottom",
287
- "type": {
288
- "type": "<class 'inspect._empty'>"
289
- }
290
- }
291
- ],
292
- "name": "MSE loss",
293
- "outputs": [
294
- {
295
- "name": "output",
296
- "position": "top",
297
- "type": {
298
- "type": "None"
299
- }
300
- }
301
- ],
302
- "params": [],
303
- "type": "basic"
304
- },
305
- "op_id": "MSE loss",
306
- "params": {},
307
- "status": "done",
308
- "title": "MSE loss"
309
- },
310
- "dragHandle": ".drag-handle",
311
- "height": 200.0,
312
- "id": "MSE loss 2",
313
- "position": {
314
- "x": 375.21624462193034,
315
- "y": -721.0552036572305
316
- },
317
- "type": "basic",
318
- "width": 200.0
319
- },
320
- {
321
- "data": {
322
- "__execution_delay": 0.0,
323
- "collapsed": null,
324
- "display": null,
325
- "error": null,
326
- "input_metadata": null,
327
- "meta": {
328
- "categories": [],
329
- "color": "orange",
330
- "doc": null,
331
- "id": "Repeat",
332
- "inputs": [
333
- {
334
- "name": "input",
335
- "position": "top",
336
- "type": {
337
- "type": "tensor"
338
- }
339
- }
340
- ],
341
- "name": "Repeat",
342
- "outputs": [
343
- {
344
- "name": "output",
345
- "position": "bottom",
346
- "type": {
347
- "type": "tensor"
348
- }
349
- }
350
- ],
351
- "params": [
352
- {
353
- "default": 1.0,
354
- "name": "times",
355
- "type": {
356
- "type": "<class 'int'>"
357
- }
358
- },
359
- {
360
- "default": false,
361
- "name": "same_weights",
362
- "type": {
363
- "type": "<class 'bool'>"
364
- }
365
- }
366
- ],
367
- "type": "basic"
368
- },
369
- "op_id": "Repeat",
370
- "params": {
371
- "same_weights": false,
372
- "times": "2"
373
- },
374
- "status": "done",
375
- "title": "Repeat"
376
- },
377
- "dragHandle": ".drag-handle",
378
- "height": 200.0,
379
- "id": "Repeat 1",
380
- "position": {
381
- "x": -210.0,
382
- "y": -135.0
383
- },
384
- "type": "basic",
385
- "width": 200.0
386
- },
387
- {
388
- "data": {
389
- "__execution_delay": 0.0,
390
- "collapsed": null,
391
- "display": null,
392
- "error": null,
393
- "input_metadata": null,
394
- "meta": {
395
- "categories": [],
396
- "color": "blue",
397
- "doc": null,
398
- "id": "Linear",
399
- "inputs": [
400
- {
401
- "name": "x",
402
- "position": "bottom",
403
- "type": {
404
- "type": "<class 'inspect._empty'>"
405
- }
406
- }
407
- ],
408
- "name": "Linear",
409
- "outputs": [
410
- {
411
- "name": "output",
412
- "position": "top",
413
- "type": {
414
- "type": "None"
415
- }
416
- }
417
- ],
418
- "params": [
419
- {
420
- "default": 1024.0,
421
- "name": "output_dim",
422
- "type": {
423
- "type": "<class 'int'>"
424
- }
425
- }
426
- ],
427
- "type": "basic"
428
- },
429
- "op_id": "Linear",
430
- "params": {
431
- "output_dim": "4"
432
- },
433
- "status": "done",
434
- "title": "Linear"
435
- },
436
- "dragHandle": ".drag-handle",
437
- "height": 189.0,
438
- "id": "Linear 1",
439
- "position": {
440
- "x": 98.54861342271252,
441
- "y": 14.121603973834155
442
- },
443
- "type": "basic",
444
- "width": 199.0
445
- },
446
- {
447
- "data": {
448
- "__execution_delay": 0.0,
449
- "collapsed": null,
450
- "display": null,
451
- "error": null,
452
- "input_metadata": null,
453
- "meta": {
454
- "categories": [],
455
- "color": "gray",
456
- "doc": null,
457
- "id": "Input: tensor",
458
- "inputs": [],
459
- "name": "Input: tensor",
460
- "outputs": [
461
- {
462
- "name": "output",
463
- "position": "top",
464
- "type": {
465
- "type": "tensor"
466
- }
467
- }
468
- ],
469
- "params": [
470
- {
471
- "default": null,
472
- "name": "name",
473
- "type": {
474
- "type": "None"
475
- }
476
- }
477
- ],
478
- "type": "basic"
479
- },
480
- "op_id": "Input: tensor",
481
- "params": {
482
- "name": "X"
483
- },
484
- "status": "done",
485
- "title": "Input: tensor"
486
- },
487
- "dragHandle": ".drag-handle",
488
- "height": 200.0,
489
- "id": "Input: tensor 1",
490
- "position": {
491
- "x": 108.75735538875443,
492
- "y": 331.53404347930933
493
- },
494
- "type": "basic",
495
- "width": 200.0
496
- },
497
- {
498
- "data": {
499
- "__execution_delay": 0.0,
500
- "collapsed": null,
501
- "display": null,
502
- "error": null,
503
- "input_metadata": null,
504
- "meta": {
505
- "categories": [],
506
- "color": "orange",
507
- "doc": null,
508
- "id": "Constant vector",
509
- "inputs": [],
510
- "name": "Constant vector",
511
- "outputs": [
512
- {
513
- "name": "output",
514
- "position": "top",
515
- "type": {
516
- "type": "None"
517
- }
518
- }
519
- ],
520
- "params": [
521
- {
522
- "default": 0.0,
523
- "name": "value",
524
- "type": {
525
- "type": "<class 'int'>"
526
- }
527
- },
528
- {
529
- "default": 1.0,
530
- "name": "size",
531
- "type": {
532
- "type": "<class 'int'>"
533
- }
534
- }
535
- ],
536
- "type": "basic"
537
- },
538
- "op_id": "Constant vector",
539
- "params": {
540
- "size": "1",
541
- "value": "1"
542
- },
543
- "status": "done",
544
- "title": "Constant vector"
545
- },
546
- "dragHandle": ".drag-handle",
547
- "height": 258.0,
548
- "id": "Constant vector 1",
549
- "position": {
550
- "x": 846.2767459753351,
551
- "y": -226.90556526533476
552
- },
553
- "type": "basic",
554
- "width": 238.0
555
- },
556
- {
557
- "data": {
558
- "__execution_delay": null,
559
- "collapsed": true,
560
- "display": null,
561
- "error": null,
562
- "input_metadata": null,
563
- "meta": {
564
- "categories": [],
565
- "color": "orange",
566
- "doc": null,
567
- "id": "Add",
568
- "inputs": [
569
- {
570
- "name": "a",
571
- "position": "bottom",
572
- "type": {
573
- "type": "<class 'inspect._empty'>"
574
- }
575
- },
576
- {
577
- "name": "b",
578
- "position": "bottom",
579
- "type": {
580
- "type": "<class 'inspect._empty'>"
581
- }
582
- }
583
- ],
584
- "name": "Add",
585
- "outputs": [
586
- {
587
- "name": "output",
588
- "position": "top",
589
- "type": {
590
- "type": "None"
591
- }
592
- }
593
- ],
594
- "params": [],
595
- "type": "basic"
596
- },
597
- "op_id": "Add",
598
- "params": {},
599
- "status": "done",
600
- "title": "Add"
601
- },
602
- "dragHandle": ".drag-handle",
603
- "height": 200.0,
604
- "id": "Add 1",
605
- "position": {
606
- "x": 631.934390777073,
607
- "y": -395.6855954439944
608
- },
609
- "type": "basic",
610
- "width": 200.0
611
- },
612
- {
613
- "data": {
614
- "__execution_delay": null,
615
- "collapsed": true,
616
- "display": null,
617
- "error": null,
618
- "input_metadata": null,
619
- "meta": {
620
- "categories": [],
621
- "color": "gray",
622
- "doc": null,
623
- "id": "Output",
624
- "inputs": [
625
- {
626
- "name": "x",
627
- "position": "bottom",
628
- "type": {
629
- "type": "tensor"
630
- }
631
- }
632
- ],
633
- "name": "Output",
634
- "outputs": [
635
- {
636
- "name": "x",
637
- "position": "top",
638
- "type": {
639
- "type": "tensor"
640
- }
641
- }
642
- ],
643
- "params": [
644
- {
645
- "default": null,
646
- "name": "name",
647
- "type": {
648
- "type": "None"
649
- }
650
- }
651
- ],
652
- "type": "basic"
653
- },
654
- "op_id": "Output",
655
- "params": {},
656
- "status": "done",
657
- "title": "Output"
658
- },
659
- "dragHandle": ".drag-handle",
660
- "height": 200.0,
661
- "id": "Output 1",
662
- "position": {
663
- "x": 119.83887514325258,
664
- "y": -453.23756095856885
665
- },
666
- "type": "basic",
667
- "width": 200.0
668
- }
669
- ],
670
- "paused": false
671
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/Model use.lynxkite.json DELETED
The diff for this file is too large to render. See raw diff
 
examples/Multi-output demo.lynxkite.json DELETED
@@ -1,301 +0,0 @@
1
- {
2
- "edges": [
3
- {
4
- "id": "Multi-output example 1 one View tables 1 bundle",
5
- "source": "Multi-output example 1",
6
- "sourceHandle": "one",
7
- "target": "View tables 1",
8
- "targetHandle": "bundle"
9
- },
10
- {
11
- "id": "Multi-output example 1 two View tables 2 bundle",
12
- "source": "Multi-output example 1",
13
- "sourceHandle": "two",
14
- "target": "View tables 2",
15
- "targetHandle": "bundle"
16
- }
17
- ],
18
- "env": "LynxKite Graph Analytics",
19
- "nodes": [
20
- {
21
- "data": {
22
- "__execution_delay": 0.0,
23
- "collapsed": false,
24
- "display": null,
25
- "error": null,
26
- "input_metadata": [],
27
- "meta": {
28
- "categories": [
29
- "Examples"
30
- ],
31
- "color": "orange",
32
- "doc": [
33
- {
34
- "kind": "text",
35
- "value": "Returns two outputs. Also demonstrates Numpy-style docstrings."
36
- },
37
- {
38
- "kind": "parameters",
39
- "value": [
40
- {
41
- "annotation": "int",
42
- "description": "Number of elements in output \"one\".",
43
- "name": "a_limit"
44
- },
45
- {
46
- "annotation": "int",
47
- "description": "Number of elements in output \"two\".",
48
- "name": "b_limit"
49
- }
50
- ]
51
- },
52
- {
53
- "kind": "returns",
54
- "value": [
55
- {
56
- "annotation": "A dict with two DataFrames in it.",
57
- "description": "",
58
- "name": ""
59
- }
60
- ]
61
- }
62
- ],
63
- "id": "Examples > Multi-output example",
64
- "inputs": [],
65
- "name": "Multi-output example",
66
- "outputs": [
67
- {
68
- "name": "one",
69
- "position": "right",
70
- "type": {
71
- "type": "None"
72
- }
73
- },
74
- {
75
- "name": "two",
76
- "position": "right",
77
- "type": {
78
- "type": "None"
79
- }
80
- }
81
- ],
82
- "params": [
83
- {
84
- "default": 4,
85
- "name": "a_limit",
86
- "type": {
87
- "type": "<class 'int'>"
88
- }
89
- },
90
- {
91
- "default": 10,
92
- "name": "b_limit",
93
- "type": {
94
- "type": "<class 'int'>"
95
- }
96
- }
97
- ],
98
- "type": "basic"
99
- },
100
- "op_id": "Examples > Multi-output example",
101
- "params": {
102
- "a_limit": "2",
103
- "b_limit": "10"
104
- },
105
- "status": "done",
106
- "title": "Multi-output example"
107
- },
108
- "dragHandle": ".drag-handle",
109
- "height": 275.0,
110
- "id": "Multi-output example 1",
111
- "position": {
112
- "x": 86.0,
113
- "y": 33.0
114
- },
115
- "type": "basic",
116
- "width": 200.0
117
- },
118
- {
119
- "data": {
120
- "display": {
121
- "dataframes": {
122
- "df": {
123
- "columns": [
124
- "a"
125
- ],
126
- "data": [
127
- [
128
- 0
129
- ],
130
- [
131
- 1
132
- ]
133
- ]
134
- }
135
- },
136
- "other": {},
137
- "relations": []
138
- },
139
- "error": null,
140
- "input_metadata": [
141
- {
142
- "dataframes": {
143
- "df": {
144
- "columns": [
145
- "a"
146
- ]
147
- }
148
- },
149
- "other": {},
150
- "relations": []
151
- }
152
- ],
153
- "meta": {
154
- "categories": [],
155
- "color": "orange",
156
- "doc": null,
157
- "id": "View tables",
158
- "inputs": [
159
- {
160
- "name": "bundle",
161
- "position": "left",
162
- "type": {
163
- "type": "<class 'lynxkite_graph_analytics.core.Bundle'>"
164
- }
165
- }
166
- ],
167
- "name": "View tables",
168
- "outputs": [],
169
- "params": [
170
- {
171
- "default": 100,
172
- "name": "limit",
173
- "type": {
174
- "type": "<class 'int'>"
175
- }
176
- }
177
- ],
178
- "type": "table_view"
179
- },
180
- "op_id": "View tables",
181
- "params": {
182
- "limit": 100.0
183
- },
184
- "status": "done",
185
- "title": "View tables"
186
- },
187
- "dragHandle": ".drag-handle",
188
- "height": 200.0,
189
- "id": "View tables 1",
190
- "position": {
191
- "x": 485.0,
192
- "y": -31.0
193
- },
194
- "type": "table_view",
195
- "width": 200.0
196
- },
197
- {
198
- "data": {
199
- "display": {
200
- "dataframes": {
201
- "df": {
202
- "columns": [
203
- "b"
204
- ],
205
- "data": [
206
- [
207
- 0
208
- ],
209
- [
210
- 1
211
- ],
212
- [
213
- 2
214
- ],
215
- [
216
- 3
217
- ],
218
- [
219
- 4
220
- ],
221
- [
222
- 5
223
- ],
224
- [
225
- 6
226
- ],
227
- [
228
- 7
229
- ],
230
- [
231
- 8
232
- ],
233
- [
234
- 9
235
- ]
236
- ]
237
- }
238
- },
239
- "other": {},
240
- "relations": []
241
- },
242
- "error": null,
243
- "input_metadata": [
244
- {
245
- "dataframes": {
246
- "df": {
247
- "columns": [
248
- "b"
249
- ]
250
- }
251
- },
252
- "other": {},
253
- "relations": []
254
- }
255
- ],
256
- "meta": {
257
- "categories": [],
258
- "color": "orange",
259
- "doc": null,
260
- "id": "View tables",
261
- "inputs": [
262
- {
263
- "name": "bundle",
264
- "position": "left",
265
- "type": {
266
- "type": "<class 'lynxkite_graph_analytics.core.Bundle'>"
267
- }
268
- }
269
- ],
270
- "name": "View tables",
271
- "outputs": [],
272
- "params": [
273
- {
274
- "default": 100,
275
- "name": "limit",
276
- "type": {
277
- "type": "<class 'int'>"
278
- }
279
- }
280
- ],
281
- "type": "table_view"
282
- },
283
- "op_id": "View tables",
284
- "params": {
285
- "limit": 100.0
286
- },
287
- "status": "done",
288
- "title": "View tables"
289
- },
290
- "dragHandle": ".drag-handle",
291
- "height": 215.0,
292
- "id": "View tables 2",
293
- "position": {
294
- "x": 480.0,
295
- "y": 191.0
296
- },
297
- "type": "table_view",
298
- "width": 225.0
299
- }
300
- ]
301
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/NetworkX demo.lynxkite.json DELETED
The diff for this file is too large to render. See raw diff
 
examples/Word2vec.lynxkite.json DELETED
The diff for this file is too large to render. See raw diff
 
examples/fake_data.py DELETED
@@ -1,21 +0,0 @@
1
- from lynxkite.core.ops import op
2
- from faker import Faker # ty: ignore[unresolved-import]
3
- import pandas as pd
4
-
5
- faker = Faker()
6
-
7
-
8
- @op("LynxKite Graph Analytics", "Fake data")
9
- def fake(*, n=10):
10
- """Creates a DataFrame with random-generated names and postal addresses.
11
-
12
- Parameters:
13
- n: Number of rows to create.
14
- """
15
- df = pd.DataFrame(
16
- {
17
- "name": [faker.name() for _ in range(n)],
18
- "address": [faker.address() for _ in range(n)],
19
- }
20
- )
21
- return df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/make_image_table.py DELETED
@@ -1,11 +0,0 @@
1
- from lynxkite.core.ops import op
2
- import pandas as pd
3
- import base64
4
-
5
-
6
- @op("LynxKite Graph Analytics", "Example image table")
7
- def make_image_table():
8
- svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" enable-background="new 0 0 64 64"><path d="M56 2 18.8 42.909 8 34.729 2 34.729 18.8 62 62 2z"/></svg>'
9
- data = "data:image/svg+xml;base64," + base64.b64encode(svg.encode("utf-8")).decode("utf-8")
10
- http = "https://upload.wikimedia.org/wikipedia/commons/2/2e/Emojione_BW_2714.svg"
11
- return pd.DataFrame({"names": ["svg", "data", "http"], "images": [svg, data, http]})
 
 
 
 
 
 
 
 
 
 
 
 
examples/matplotlib_example.py DELETED
@@ -1,34 +0,0 @@
1
- # From https://matplotlib.org/stable/gallery/images_contours_and_fields/contour_corner_mask.html
2
- import matplotlib.pyplot as plt
3
- import numpy as np
4
- from lynxkite.core.ops import op
5
-
6
-
7
- @op("LynxKite Graph Analytics", "Matplotlib example", view="matplotlib")
8
- def example():
9
- # Data to plot.
10
- x, y = np.meshgrid(np.arange(7), np.arange(10))
11
- z = np.sin(0.5 * x) * np.cos(0.52 * y)
12
-
13
- # Mask various z values.
14
- mask = np.zeros_like(z, dtype=bool)
15
- mask[2, 3:5] = True
16
- mask[3:5, 4] = True
17
- mask[7, 2] = True
18
- mask[5, 0] = True
19
- mask[0, 6] = True
20
- z = np.ma.array(z, mask=mask)
21
- print(z)
22
-
23
- corner_masks = [False, True]
24
- fig, axs = plt.subplots(ncols=2)
25
- for ax, corner_mask in zip(axs, corner_masks):
26
- cs = ax.contourf(x, y, z, corner_mask=corner_mask)
27
- ax.contour(cs, colors="k")
28
- ax.set_title(f"{corner_mask=}")
29
-
30
- # Plot grid.
31
- ax.grid(c="k", ls="-", alpha=0.3)
32
-
33
- # Indicate masked points with red circles.
34
- ax.plot(np.ma.array(x, mask=~mask), y, "ro")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/multi_output_demo.py DELETED
@@ -1,24 +0,0 @@
1
- from lynxkite.core.ops import op
2
- import pandas as pd
3
-
4
-
5
- @op("LynxKite Graph Analytics", "Examples", "Multi-output example", outputs=["one", "two"])
6
- def multi_output(*, a_limit=4, b_limit=10):
7
- """
8
- Returns two outputs. Also demonstrates Numpy-style docstrings.
9
-
10
- Parameters
11
- ----------
12
- a_limit : int
13
- Number of elements in output "one".
14
- b_limit : int
15
- Number of elements in output "two".
16
-
17
- Returns
18
- -------
19
- A dict with two DataFrames in it.
20
- """
21
- return {
22
- "one": pd.DataFrame({"a": range(a_limit)}),
23
- "two": pd.DataFrame({"b": range(b_limit)}),
24
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/ode_lstm.py DELETED
@@ -1,54 +0,0 @@
1
- from lynxkite.core.ops import op_registration, LongStr
2
- from lynxkite_graph_analytics.core import Bundle
3
- from matplotlib import pyplot as plt
4
- import numpy as np
5
- import pandas as pd
6
- import json
7
-
8
- op = op_registration("LynxKite Graph Analytics")
9
-
10
-
11
- @op("Drop NA")
12
- def drop_na(df: pd.DataFrame):
13
- return df.replace("", np.nan).dropna()
14
-
15
-
16
- @op("Sort by")
17
- def sort_by(df: pd.DataFrame, *, key_columns: str):
18
- df = df.copy()
19
- df.sort_values(
20
- by=[k.strip() for k in key_columns.split(",")],
21
- inplace=True,
22
- ignore_index=True,
23
- )
24
- return df
25
-
26
-
27
- @op("Group by")
28
- def group_by(df: pd.DataFrame, *, key_columns: str, aggregation: LongStr):
29
- key_columns = [k.strip() for k in key_columns.split(",")]
30
- j = json.loads(aggregation)
31
- for k, vs in j.items():
32
- j[k] = [list if v == "list" else v for v in vs]
33
- res = df.groupby(key_columns).agg(j).reset_index()
34
- res.columns = ["_".join(col) for col in res.columns]
35
- return res
36
-
37
-
38
- @op("Take first element of list")
39
- def take_first_element(df: pd.DataFrame, *, column: str):
40
- df = df.copy()
41
- df[f"{column}_first_element"] = df[column].apply(lambda x: x[0])
42
- return df
43
-
44
-
45
- @op("Plot time series", view="matplotlib")
46
- def plot_time_series(bundle: Bundle, *, table_name: str, index: int, x_column: str, y_columns: str):
47
- df = bundle.dfs[table_name]
48
- y_columns = [y.strip() for y in y_columns.split(",")]
49
- x = df[x_column].iloc[index]
50
- for y_column in y_columns:
51
- y = df[y_column].iloc[index]
52
- plt.plot(x, y, "o-", label=y_column)
53
- plt.xlabel(x_column)
54
- plt.legend()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/requirements.txt DELETED
@@ -1,3 +0,0 @@
1
- # Example of a requirements.txt file. LynxKite will automatically install anything you put here.
2
- faker
3
- matplotlib
 
 
 
 
examples/sql.lynxkite.json DELETED
The diff for this file is too large to render. See raw diff
 
examples/uploads/example-pizza.md DELETED
@@ -1,136 +0,0 @@
1
- hello
2
-
3
- ### 1. **Overview**
4
-
5
- This document outlines the pricing structure and available options for our pizza delivery service. The goal is to provide clear guidance on the pricing tiers, additional offerings, and optional extras to ensure consistency across all locations and platforms (phone, online, in-app). All pricing is based on current market trends, food costs, and competitive analysis.
6
-
7
- ---
8
-
9
- ### 2. **Pizza Options**
10
-
11
- #### 2.1 **Size & Base Pricing**
12
-
13
- | Size | Diameter | Price (Cheese Pizza) |
14
- |------------------|------------|----------------------|
15
- | Small | 10 inches | $8.99 |
16
- | Medium | 12 inches | $11.99 |
17
- | Large | 14 inches | $14.99 |
18
- | Extra Large | 16 inches | $17.99 |
19
-
20
- **Note**: Cheese pizza pricing includes sauce and cheese. Toppings are additional (see section 2.3).
21
-
22
- #### 2.2 **Crust Options**
23
-
24
- | Crust Type | Description | Price Adjustment |
25
- |------------------|------------------------------------------|------------------|
26
- | Classic Hand-Tossed | Soft, airy texture | No Change |
27
- | Thin & Crispy | Light and crunchy | No Change |
28
- | Stuffed Crust | Filled with mozzarella | +$2.00 (M-XL) |
29
- | Gluten-Free | 10" only; made with rice flour | +$2.50 (Small Only) |
30
-
31
- ---
32
-
33
- ### 3. **Toppings**
34
-
35
- #### 3.1 **Standard Toppings**
36
- **Price per topping:**
37
-
38
- - Small: $1.00
39
- - Medium: $1.50
40
- - Large: $2.00
41
- - Extra Large: $2.50
42
-
43
- | Topping | Category |
44
- |------------------|----------------|
45
- | Pepperoni | Meat |
46
- | Sausage | Meat |
47
- | Mushrooms | Vegetable |
48
- | Onions | Vegetable |
49
- | Bell Peppers | Vegetable |
50
- | Olives | Vegetable |
51
- | Extra Cheese | Dairy |
52
-
53
- #### 3.2 **Premium Toppings**
54
- **Price per topping:**
55
-
56
- - Small: $1.75
57
- - Medium: $2.25
58
- - Large: $2.75
59
- - Extra Large: $3.25
60
-
61
- | Topping | Category |
62
- |------------------|----------------|
63
- | Grilled Chicken | Meat |
64
- | Bacon | Meat |
65
- | Sun-Dried Tomatoes| Vegetable |
66
- | Artichoke Hearts | Vegetable |
67
- | Feta Cheese | Dairy |
68
- | Vegan Cheese | Dairy Alternative |
69
-
70
- ---
71
-
72
- ### 4. **Specialty Pizzas**
73
-
74
- Specialty pizzas include a combination of premium toppings and are available in all sizes. Prices below are for Medium size, with additional costs for upgrading to larger sizes.
75
-
76
- | Pizza Name | Description | Price (Medium) |
77
- |----------------------|----------------------------------------------------|-----------------|
78
- | Meat Lover’s | Pepperoni, sausage, bacon, ham | $16.99 |
79
- | Veggie Delight | Mushrooms, bell peppers, onions, olives | $14.99 |
80
- | BBQ Chicken | BBQ sauce, grilled chicken, red onions, cilantro | $17.99 |
81
- | Margherita | Fresh mozzarella, tomatoes, basil | $15.99 |
82
- | Hawaiian | Ham, pineapple | $14.99 |
83
-
84
- ---
85
-
86
- ### 5. **Additional Menu Items**
87
-
88
- #### 5.1 **Side Orders**
89
-
90
- | Item | Description | Price |
91
- |--------------------|--------------------------------------|---------------|
92
- | Garlic Breadsticks | Served with marinara dipping sauce | $5.99 |
93
- | Chicken Wings | Buffalo, BBQ, or plain (10 pieces) | $9.99 |
94
- | Mozzarella Sticks | Served with marinara (8 pieces) | $6.99 |
95
- | Caesar Salad | Romaine, croutons, Caesar dressing | $7.99 |
96
-
97
- #### 5.2 **Desserts**
98
-
99
- | Item | Description | Price |
100
- |--------------------|--------------------------------------|---------------|
101
- | Chocolate Brownies | Chewy and rich (6 pieces) | $4.99 |
102
- | Cinnamon Sticks | Dusted with cinnamon sugar | $5.99 |
103
-
104
- ---
105
-
106
- ### 6. **Drinks**
107
-
108
- | Size | Price |
109
- |--------------------|---------------|
110
- | 20 oz Bottle | $1.99 |
111
- | 2-Liter Bottle | $3.50 |
112
-
113
- Available options: Coke, Diet Coke, Sprite, Root Beer, Lemonade.
114
-
115
- ---
116
-
117
- ### 7. **Delivery Fees & Minimum Order**
118
-
119
- - **Delivery Fee**: $2.99
120
- - **Minimum Order**: $12.00
121
-
122
- *Note: Delivery fees and minimum order thresholds apply to all delivery orders within a 5-mile radius. Additional charges may apply for orders outside this zone.*
123
-
124
- ---
125
-
126
- ### 8. **Promotions & Discounts**
127
-
128
- - **Monday Madness**: Buy one large pizza, get a second pizza for 50% off.
129
- - **Student Discount**: 10% off with valid student ID (pickup only).
130
- - **Family Deal**: 2 large pizzas, 1 side, and 2-liter soda for $29.99.
131
-
132
- ---
133
-
134
- ### 9. **Conclusion**
135
-
136
- This pricing and menu structure is designed to offer a wide range of choices for our customers while maintaining competitive pricing and ensuring profitability. Please ensure all team members are familiar with the details in this document and implement it accordingly.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/uploads/molecules2.csv DELETED
@@ -1,4 +0,0 @@
1
- name,smiles
2
- ciprofloxacin,C1CNCCN1c(c2)c(F)cc3c2N(C4CC4)C=C(C3=O)C(=O)O
3
- caffeine,CN1C=NC2=C1C(=O)N(C(=O)N2C)C
4
- α-d-glucopyranose,C([C@@H]1[C@H]([C@@H]([C@H]([C@H](O1)O)O)O)O)O
 
 
 
 
 
examples/uploads/plus-one-dataset.parquet DELETED
Binary file (7.54 kB)
 
examples/word2vec.py DELETED
@@ -1,27 +0,0 @@
1
- from lynxkite.core.ops import op
2
- import pandas as pd
3
-
4
- ENV = "LynxKite Graph Analytics"
5
-
6
-
7
- @op(ENV, "Word2vec for the top 1000 words", slow=True)
8
- def word2vec_1000():
9
- import staticvectors # ty: ignore[unresolved-import]
10
-
11
- model = staticvectors.StaticVectors("neuml/word2vec-quantized")
12
- df = pd.read_csv(
13
- "https://gist.githubusercontent.com/deekayen/4148741/raw/98d35708fa344717d8eee15d11987de6c8e26d7d/1-1000.txt",
14
- names=["word"],
15
- )
16
- df["embedding"] = model.embeddings(df.word.tolist()).tolist()
17
- return df
18
-
19
-
20
- @op(ENV, "Take first N")
21
- def first_n(df: pd.DataFrame, *, n=10):
22
- return df.head(n)
23
-
24
-
25
- @op(ENV, "Sample N")
26
- def sample_n(df: pd.DataFrame, *, n=10):
27
- return df.sample(n)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lynxkite-app/.gitignore DELETED
@@ -1,5 +0,0 @@
1
- /src/lynxkite_app/web_assets
2
- !/src/lynxkite_app/web_assets/__init__.py
3
- !/src/lynxkite_app/web_assets/assets/__init__.py
4
- data/
5
- !/web/tests/data
 
 
 
 
 
 
lynxkite-app/MANIFEST.in DELETED
@@ -1,2 +0,0 @@
1
- graft web
2
- prune web/node_modules
 
 
 
lynxkite-app/README.md DELETED
@@ -1,31 +0,0 @@
1
- # LynxKite MM
2
-
3
- This is an experimental rewrite of [LynxKite](https://github.com/lynxkite/lynxkite). It is not compatible with the
4
- original LynxKite. The primary goals of this rewrite are:
5
-
6
- - Target GPU clusters instead of Hadoop clusters. We use Python instead of Scala, RAPIDS instead of Apache Spark.
7
- - More extensible backend. Make it easy to add new LynxKite boxes. Make it easy to use our frontend for other purposes,
8
- configuring and executing other pipelines.
9
-
10
- ## Development
11
-
12
- To run the backend:
13
-
14
- ```bash
15
- uv pip install -e .
16
- cd ../examples && LYNXKITE_RELOAD=1 lynxkite
17
- ```
18
-
19
- To run the frontend:
20
-
21
- ```bash
22
- cd web
23
- npm i
24
- npm run dev
25
- ```
26
-
27
- To update the frontend types with the backend types:
28
-
29
- ```bash
30
- $ uv run pydantic2ts --module lynxkite_app.main --output ./web/src/apiTypes.ts --json2ts-cmd "npx json-schema-to-typescript"
31
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lynxkite-app/pyproject.toml DELETED
@@ -1,60 +0,0 @@
1
- [project]
2
- name = "lynxkite"
3
- version = "0.1.0"
4
- description = "The LynxKite application, with web server and UI"
5
- readme = "README.md"
6
- requires-python = ">=3.11"
7
- dependencies = [
8
- "fastapi[standard]>=0.115.6",
9
- "griffe>=1.7.3",
10
- "joblib>=1.5.1",
11
- "lynxkite-core",
12
- "pycrdt-websocket>=0.16",
13
- "pycrdt>=0.12.26",
14
- "pydantic>=2.11.7",
15
- "sse-starlette>=2.2.1",
16
- "uvicorn>=0.35.0",
17
- ]
18
- classifiers = ["Private :: Do Not Upload"]
19
-
20
- [project.urls]
21
- Homepage = "https://github.com/lynxkite/lynxkite-2000/"
22
-
23
- [dependency-groups]
24
- dev = [
25
- "pydantic-to-typescript>=2.0.0",
26
- "setuptools>=80.9.0",
27
- ]
28
-
29
- [tool.uv.sources]
30
- lynxkite-core = { workspace = true }
31
-
32
- [build-system]
33
- requires = ["setuptools", "wheel", "setuptools-scm"]
34
- build-backend = "setuptools.build_meta"
35
-
36
- [tool.setuptools.packages.find]
37
- namespaces = true
38
- where = ["src"]
39
-
40
- [tool.setuptools.package-data]
41
- "lynxkite_app.web_assets" = ["*"]
42
- "lynxkite_app.web_assets.assets" = ["*"]
43
-
44
- [tool.setuptools]
45
- py-modules = ["build_frontend"]
46
- include-package-data = true
47
-
48
- [tool.setuptools.cmdclass]
49
- build_py = "build_frontend.build_py"
50
-
51
- [project.scripts]
52
- lynxkite = "lynxkite_app.__main__:main"
53
-
54
- [tool.deptry.package_module_name_map]
55
- lynxkite-core = "lynxkite"
56
- sse-starlette = "starlette"
57
-
58
- [tool.deptry.per_rule_ignores]
59
- DEP002 = ["pycrdt-websocket", "griffe"]
60
- DEP004 = ["setuptools"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lynxkite-app/src/build_frontend.py DELETED
@@ -1,26 +0,0 @@
1
- """Customized build process for setuptools."""
2
-
3
- import subprocess
4
- from setuptools.command.build_py import build_py as _build_py
5
- from pathlib import Path
6
- import shutil
7
-
8
-
9
- class build_py(_build_py):
10
- def run(self):
11
- print("\n\nBuilding frontend...", __file__)
12
- here = Path(__file__).parent.parent
13
- frontend_dir = here / "web"
14
- package_dir = here / "src" / "lynxkite_app" / "web_assets"
15
- subprocess.check_call(["npm", "install"], cwd=frontend_dir)
16
- subprocess.check_call(["npm", "run", "build"], cwd=frontend_dir)
17
- print("files in", frontend_dir / "dist")
18
- for file in (frontend_dir / "dist").iterdir():
19
- print(file)
20
- # shutil.rmtree(package_dir)
21
- shutil.copytree(frontend_dir / "dist", package_dir, dirs_exist_ok=True)
22
- # (frontend_dir / "dist").rename(package_dir)
23
- print("files in", package_dir)
24
- for file in package_dir.iterdir():
25
- print(file)
26
- super().run()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lynxkite-app/src/lynxkite_app/__init__.py DELETED
File without changes
lynxkite-app/src/lynxkite_app/__main__.py DELETED
@@ -1,20 +0,0 @@
1
- import uvicorn
2
- from .main import app # noqa: F401
3
- import os
4
-
5
-
6
- def main():
7
- port = int(os.environ.get("PORT", "8000"))
8
- reload = bool(os.environ.get("LYNXKITE_RELOAD", ""))
9
- uvicorn.run(
10
- "lynxkite_app.main:app",
11
- host="0.0.0.0",
12
- port=port,
13
- reload=reload,
14
- loop="asyncio",
15
- proxy_headers=True,
16
- )
17
-
18
-
19
- if __name__ == "__main__":
20
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lynxkite-app/src/lynxkite_app/crdt.py DELETED
@@ -1,342 +0,0 @@
1
- """CRDT is used to synchronize workspace state for backend and frontend(s)."""
2
-
3
- import asyncio
4
- import contextlib
5
- import enum
6
- import pathlib
7
- import fastapi
8
- import os.path
9
- import pycrdt.websocket
10
- import pycrdt.store.file
11
- import uvicorn.protocols.utils
12
- import builtins
13
- from lynxkite.core import workspace, ops
14
-
15
- router = fastapi.APIRouter()
16
-
17
-
18
- def ws_exception_handler(exception, log):
19
- if isinstance(exception, builtins.ExceptionGroup):
20
- for ex in exception.exceptions:
21
- if not isinstance(ex, uvicorn.protocols.utils.ClientDisconnected):
22
- log.exception(ex)
23
- else:
24
- log.exception(exception)
25
- return True
26
-
27
-
28
- class WorkspaceWebsocketServer(pycrdt.websocket.WebsocketServer):
29
- async def init_room(self, name: str) -> pycrdt.websocket.YRoom:
30
- """Initialize a room for the workspace with the given name.
31
-
32
- The workspace is loaded from ".crdt" if it exists there, or from a JSON file, or a new workspace is created.
33
- """
34
- crdt_path = pathlib.Path(".crdt")
35
- path = crdt_path / f"{name}.crdt"
36
- assert path.is_relative_to(crdt_path), f"Path '{path}' is invalid"
37
- ystore = pycrdt.store.file.FileYStore(path)
38
- ydoc = pycrdt.Doc()
39
- ydoc["workspace"] = ws = pycrdt.Map()
40
- # Replay updates from the store.
41
- try:
42
- for update, timestamp in [(item[0], item[-1]) async for item in ystore.read()]:
43
- ydoc.apply_update(update)
44
- except pycrdt.store.YDocNotFound:
45
- pass
46
- if "nodes" not in ws:
47
- ws["nodes"] = pycrdt.Array()
48
- if "edges" not in ws:
49
- ws["edges"] = pycrdt.Array()
50
- if "env" not in ws:
51
- ws["env"] = next(iter(ops.CATALOGS), "unset")
52
- # We have two possible sources of truth for the workspaces, the YStore and the JSON files.
53
- # In case we didn't find the workspace in the YStore, we try to load it from the JSON files.
54
- try_to_load_workspace(ws, name)
55
- ws_simple = workspace.Workspace.model_validate(ws.to_py())
56
- clean_input(ws_simple)
57
- # Set the last known version to the current state, so we don't trigger a change event.
58
- last_known_versions[name] = ws_simple
59
- room = pycrdt.websocket.YRoom(
60
- ystore=ystore, ydoc=ydoc, exception_handler=ws_exception_handler
61
- )
62
- # We hang the YDoc pointer on the room, so it only gets garbage collected when the room does.
63
- room.ws = ws # ty: ignore[unresolved-attribute]
64
-
65
- def on_change(changes):
66
- task = asyncio.create_task(workspace_changed(name, changes, ws))
67
- # We have no way to await workspace_changed(). The best we can do is to
68
- # dereference its result after it's done, so exceptions are logged normally.
69
- task.add_done_callback(lambda t: t.result())
70
-
71
- ws.observe_deep(on_change)
72
- return room
73
-
74
- async def get_room(self, name: str) -> pycrdt.websocket.YRoom:
75
- """Get a room by name.
76
-
77
- This method overrides the parent get_room method. The original creates an empty room,
78
- with no associated Ydoc. Instead, we want to initialize the the room with a Workspace
79
- object.
80
- """
81
- if name not in self.rooms:
82
- self.rooms[name] = await self.init_room(name)
83
- room = self.rooms[name]
84
- await self.start_room(room)
85
- return room
86
-
87
-
88
- class CodeWebsocketServer(WorkspaceWebsocketServer):
89
- async def init_room(self, name: str) -> pycrdt.websocket.YRoom:
90
- """Initialize a room for a text document with the given name."""
91
- crdt_path = pathlib.Path(".crdt")
92
- path = crdt_path / f"{name}.crdt"
93
- assert path.is_relative_to(crdt_path), f"Path '{path}' is invalid"
94
- ystore = pycrdt.store.file.FileYStore(path)
95
- ydoc = pycrdt.Doc()
96
- ydoc["text"] = text = pycrdt.Text()
97
- # Replay updates from the store.
98
- try:
99
- for update, timestamp in [(item[0], item[-1]) async for item in ystore.read()]:
100
- ydoc.apply_update(update)
101
- except pycrdt.store.YDocNotFound:
102
- pass
103
- if len(text) == 0:
104
- if os.path.exists(name):
105
- with open(name, encoding="utf-8") as f:
106
- text += f.read().replace("\r\n", "\n")
107
- room = pycrdt.websocket.YRoom(
108
- ystore=ystore, ydoc=ydoc, exception_handler=ws_exception_handler
109
- )
110
- # We hang the YDoc pointer on the room, so it only gets garbage collected when the room does.
111
- room.text = text # ty: ignore[unresolved-attribute]
112
-
113
- def on_change(changes):
114
- asyncio.create_task(code_changed(name, changes, text))
115
-
116
- text.observe(on_change)
117
- return room
118
-
119
-
120
- last_ws_input = None
121
-
122
-
123
- def clean_input(ws_pyd):
124
- """Delete everything that we want to ignore for the purposes of change detection."""
125
- for node in ws_pyd.nodes:
126
- node.data.display = None
127
- node.data.input_metadata = None
128
- node.data.error = None
129
- node.data.status = workspace.NodeStatus.done
130
- for p in list(node.data.params):
131
- if p.startswith("_"):
132
- del node.data.params[p]
133
- if node.data.op_id == "Comment":
134
- node.data.params = {}
135
- node.position.x = 0
136
- node.position.y = 0
137
- node.width = 0
138
- node.height = 0
139
- if node.model_extra:
140
- for key in list(node.model_extra.keys()):
141
- delattr(node, key)
142
-
143
-
144
- def crdt_update(
145
- crdt_obj: pycrdt.Map | pycrdt.Array,
146
- python_obj: dict | list,
147
- non_collaborative_fields: set[str] = set(),
148
- ):
149
- """Update a CRDT object to match a Python object.
150
-
151
- The types between the CRDT object and the Python object must match. If the Python object
152
- is a dict, the CRDT object must be a Map. If the Python object is a list, the CRDT object
153
- must be an Array.
154
-
155
- Args:
156
- crdt_obj: The CRDT object, that will be updated to match the Python object.
157
- python_obj: The Python object to update with.
158
- non_collaborative_fields: List of fields to treat as a black box. Black boxes are
159
- updated as a whole, instead of having a fine-grained data structure to edit
160
- collaboratively. Useful for complex fields that contain auto-generated data or
161
- metadata.
162
- The default is an empty set.
163
-
164
- Raises:
165
- ValueError: If the Python object provided is not a dict or list.
166
- """
167
- if isinstance(python_obj, dict):
168
- assert isinstance(crdt_obj, pycrdt.Map), "CRDT object must be a Map for a dict input"
169
- for key, value in python_obj.items():
170
- if key in non_collaborative_fields:
171
- crdt_obj[key] = value
172
- elif isinstance(value, dict):
173
- if crdt_obj.get(key) is None:
174
- crdt_obj[key] = pycrdt.Map()
175
- crdt_update(crdt_obj[key], value, non_collaborative_fields)
176
- elif isinstance(value, list):
177
- if crdt_obj.get(key) is None:
178
- crdt_obj[key] = pycrdt.Array()
179
- crdt_update(crdt_obj[key], value, non_collaborative_fields)
180
- elif isinstance(value, enum.Enum):
181
- crdt_obj[key] = str(value.value)
182
- else:
183
- crdt_obj[key] = value
184
- elif isinstance(python_obj, list):
185
- assert isinstance(crdt_obj, pycrdt.Array), "CRDT object must be an Array for a list input"
186
- for i, value in enumerate(python_obj):
187
- if isinstance(value, dict):
188
- if i >= len(crdt_obj):
189
- crdt_obj.append(pycrdt.Map())
190
- crdt_update(crdt_obj[i], value, non_collaborative_fields)
191
- elif isinstance(value, list):
192
- if i >= len(crdt_obj):
193
- crdt_obj.append(pycrdt.Array())
194
- crdt_update(crdt_obj[i], value, non_collaborative_fields)
195
- else:
196
- if isinstance(value, enum.Enum):
197
- value = str(value.value)
198
- if i >= len(crdt_obj):
199
- crdt_obj.append(value)
200
- else:
201
- crdt_obj[i] = value
202
- else:
203
- raise ValueError("Invalid type:", python_obj)
204
-
205
-
206
- def try_to_load_workspace(ws: pycrdt.Map, name: str):
207
- """Load the workspace `name`, if it exists, and update the `ws` CRDT object to match its contents.
208
-
209
- Args:
210
- ws: CRDT object to udpate with the workspace contents.
211
- name: Name of the workspace to load.
212
- """
213
- if os.path.exists(name):
214
- ws_pyd = workspace.Workspace.load(name)
215
- crdt_update(
216
- ws,
217
- ws_pyd.model_dump(),
218
- # We treat some fields as black boxes. They are not edited on the frontend.
219
- non_collaborative_fields={"display", "input_metadata", "meta"},
220
- )
221
-
222
-
223
- last_known_versions = {}
224
- delayed_executions = {}
225
-
226
-
227
- async def workspace_changed(name: str, changes: list[pycrdt.MapEvent], ws_crdt: pycrdt.Map):
228
- """Callback to react to changes in the workspace.
229
-
230
- Args:
231
- name: Name of the workspace.
232
- changes: Changes performed to the workspace.
233
- ws_crdt: CRDT object representing the workspace.
234
- """
235
- ws_pyd = workspace.Workspace.model_validate(ws_crdt.to_py())
236
- # Do not trigger execution for superficial changes.
237
- # This is a quick solution until we build proper caching.
238
- ws_simple = ws_pyd.model_copy(deep=True)
239
- clean_input(ws_simple)
240
- if ws_simple == last_known_versions.get(name):
241
- return
242
- last_known_versions[name] = ws_simple
243
- # Frontend changes that result from typing are delayed to avoid
244
- # rerunning the workspace for every keystroke.
245
- if name in delayed_executions:
246
- delayed_executions[name].cancel()
247
- delay = min(
248
- getattr(change, "keys", {}).get("__execution_delay", {}).get("newValue", 0)
249
- for change in changes
250
- )
251
- # Check if workspace is paused - if so, skip automatic execution
252
- if getattr(ws_pyd, "paused", False):
253
- print(f"Skipping automatic execution for {name} in {ws_pyd.env} - workspace is paused")
254
- return
255
- if delay:
256
- task = asyncio.create_task(execute(name, ws_crdt, ws_pyd, delay))
257
- delayed_executions[name] = task
258
- else:
259
- await execute(name, ws_crdt, ws_pyd)
260
-
261
-
262
- async def execute(name: str, ws_crdt: pycrdt.Map, ws_pyd: workspace.Workspace, delay: int = 0):
263
- """Execute the workspace and update the CRDT object with the results.
264
-
265
- Args:
266
- name: Name of the workspace.
267
- ws_crdt: CRDT object representing the workspace.
268
- ws_pyd: Workspace object to execute.
269
- delay: Wait time before executing the workspace. The default is 0.
270
- """
271
- if delay:
272
- try:
273
- await asyncio.sleep(delay)
274
- except asyncio.CancelledError:
275
- return
276
- print(f"Running {name} in {ws_pyd.env}...")
277
- cwd = pathlib.Path()
278
- path = cwd / name
279
- assert path.is_relative_to(cwd), f"Path '{path}' is invalid"
280
- # Save user changes before executing, in case the execution fails.
281
- ws_pyd.save(path)
282
- ops.load_user_scripts(name)
283
- ws_pyd.connect_crdt(ws_crdt)
284
- ws_pyd.update_metadata()
285
- if not ws_pyd.has_executor():
286
- return
287
- with ws_crdt.doc.transaction():
288
- for nc in ws_crdt["nodes"]:
289
- nc["data"]["status"] = "planned"
290
- ws_pyd.normalize()
291
- await ws_pyd.execute()
292
- ws_pyd.save(path)
293
- print(f"Finished running {name} in {ws_pyd.env}.")
294
-
295
-
296
- async def code_changed(name: str, changes: pycrdt.TextEvent, text: pycrdt.Text):
297
- contents = str(text).strip() + "\n"
298
- with open(name, "w", encoding="utf-8") as f:
299
- f.write(contents)
300
-
301
-
302
- ws_websocket_server: WorkspaceWebsocketServer
303
- code_websocket_server: CodeWebsocketServer
304
-
305
-
306
- def get_room(name):
307
- return ws_websocket_server.get_room(name)
308
-
309
-
310
- @contextlib.asynccontextmanager
311
- async def lifespan(app):
312
- global ws_websocket_server
313
- global code_websocket_server
314
- ws_websocket_server = WorkspaceWebsocketServer(auto_clean_rooms=False)
315
- code_websocket_server = CodeWebsocketServer(auto_clean_rooms=False)
316
- async with ws_websocket_server:
317
- async with code_websocket_server:
318
- yield
319
- print("closing websocket server")
320
-
321
-
322
- def delete_room(name: str):
323
- if name in ws_websocket_server.rooms:
324
- del ws_websocket_server.rooms[name]
325
-
326
-
327
- def sanitize_path(path):
328
- return os.path.relpath(os.path.normpath(os.path.join("/", path)), "/")
329
-
330
-
331
- @router.websocket("/ws/crdt/{room_name:path}")
332
- async def crdt_websocket(websocket: fastapi.WebSocket, room_name: str):
333
- room_name = sanitize_path(room_name)
334
- server = pycrdt.websocket.ASGIServer(ws_websocket_server)
335
- await server({"path": room_name, "type": "websocket"}, websocket._receive, websocket._send)
336
-
337
-
338
- @router.websocket("/ws/code/crdt/{room_name:path}")
339
- async def code_crdt_websocket(websocket: fastapi.WebSocket, room_name: str):
340
- room_name = sanitize_path(room_name)
341
- server = pycrdt.websocket.ASGIServer(code_websocket_server)
342
- await server({"path": room_name, "type": "websocket"}, websocket._receive, websocket._send)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lynxkite-app/src/lynxkite_app/main.py DELETED
@@ -1,165 +0,0 @@
1
- """The FastAPI server for serving the LynxKite application."""
2
-
3
- import shutil
4
- import pydantic
5
- import fastapi
6
- import importlib
7
- import joblib
8
- import pathlib
9
- import pkgutil
10
- from fastapi.staticfiles import StaticFiles
11
- from fastapi.middleware.gzip import GZipMiddleware
12
- import starlette.exceptions
13
- from lynxkite.core import ops
14
- from lynxkite.core import workspace
15
- from . import crdt
16
-
17
- mem = joblib.Memory(".joblib-cache")
18
- ops.CACHE_WRAPPER = mem.cache
19
-
20
-
21
- def detect_plugins():
22
- plugins = {}
23
- for _, name, _ in pkgutil.iter_modules():
24
- if name.startswith("lynxkite_") and name != "lynxkite_app":
25
- print(f"Importing {name}")
26
- plugins[name] = importlib.import_module(name)
27
- if not plugins:
28
- print("No LynxKite plugins found. Be sure to install some!")
29
- return plugins
30
-
31
-
32
- lynxkite_plugins = detect_plugins()
33
- ops.save_catalogs("plugins loaded")
34
-
35
- app = fastapi.FastAPI(lifespan=crdt.lifespan)
36
- app.include_router(crdt.router)
37
- app.add_middleware(GZipMiddleware)
38
-
39
-
40
- def _get_ops(env: str):
41
- catalog = ops.CATALOGS[env]
42
- res = {op.name: op.model_dump() for op in catalog.values()}
43
- res.setdefault("Comment", ops.COMMENT_OP.model_dump())
44
- return res
45
-
46
-
47
- @app.get("/api/catalog")
48
- def get_catalog(workspace: str):
49
- ops.load_user_scripts(workspace)
50
- return {env: _get_ops(env) for env in ops.CATALOGS}
51
-
52
-
53
- data_path = pathlib.Path()
54
-
55
-
56
- @app.post("/api/delete")
57
- async def delete_workspace(req: dict):
58
- json_path: pathlib.Path = data_path / req["path"]
59
- crdt_path: pathlib.Path = data_path / ".crdt" / f"{req['path']}.crdt"
60
- assert json_path.is_relative_to(data_path), f"Path '{json_path}' is invalid"
61
- json_path.unlink()
62
- crdt_path.unlink()
63
- crdt.delete_room(req["path"])
64
-
65
-
66
- class DirectoryEntry(pydantic.BaseModel):
67
- name: str
68
- type: str
69
-
70
-
71
- def _get_path_type(path: pathlib.Path) -> str:
72
- if path.is_dir():
73
- return "directory"
74
- elif path.suffixes[-2:] == [".lynxkite", ".json"]:
75
- return "workspace"
76
- else:
77
- return "file"
78
-
79
-
80
- @app.get("/api/dir/list")
81
- def list_dir(path: str):
82
- path = data_path / path
83
- assert path.is_relative_to(data_path), f"Path '{path}' is invalid"
84
- return sorted(
85
- [
86
- DirectoryEntry(
87
- name=str(p.relative_to(data_path)),
88
- type=_get_path_type(p),
89
- )
90
- for p in path.iterdir()
91
- if not p.name.startswith(".")
92
- ],
93
- key=lambda x: (x.type != "directory", x.name.lower()),
94
- )
95
-
96
-
97
- @app.post("/api/dir/mkdir")
98
- def make_dir(req: dict):
99
- path = data_path / req["path"]
100
- assert path.is_relative_to(data_path), f"Path '{path}' is invalid"
101
- assert not path.exists(), f"{path} already exists"
102
- path.mkdir()
103
-
104
-
105
- @app.post("/api/dir/delete")
106
- def delete_dir(req: dict):
107
- path: pathlib.Path = data_path / req["path"]
108
- assert all([path.is_relative_to(data_path), path.exists(), path.is_dir()]), (
109
- f"Path '{path}' is invalid"
110
- )
111
- shutil.rmtree(path)
112
-
113
-
114
- @app.get("/api/service/{module_path:path}")
115
- async def service_get(req: fastapi.Request, module_path: str):
116
- """Executors can provide extra HTTP APIs through the /api/service endpoint."""
117
- module = lynxkite_plugins[module_path.split("/")[0]]
118
- return await module.api_service_get(req)
119
-
120
-
121
- @app.post("/api/service/{module_path:path}")
122
- async def service_post(req: fastapi.Request, module_path: str):
123
- """Executors can provide extra HTTP APIs through the /api/service endpoint."""
124
- module = lynxkite_plugins[module_path.split("/")[0]]
125
- return await module.api_service_post(req)
126
-
127
-
128
- @app.post("/api/upload")
129
- async def upload(req: fastapi.Request):
130
- """Receives file uploads and stores them in DATA_PATH."""
131
- form = await req.form()
132
- for file in form.values():
133
- file_path = data_path / "uploads" / file.filename
134
- assert file_path.is_relative_to(data_path), f"Path '{file_path}' is invalid"
135
- with file_path.open("wb") as buffer:
136
- shutil.copyfileobj(file.file, buffer)
137
- return {"status": "ok"}
138
-
139
-
140
- @app.post("/api/execute_workspace")
141
- async def execute_workspace(name: str):
142
- """Trigger and await the execution of a workspace."""
143
- room = await crdt.get_room(name)
144
- ws_pyd = workspace.Workspace.model_validate(room.ws.to_py())
145
- await crdt.execute(name, room.ws, ws_pyd)
146
-
147
-
148
- class SPAStaticFiles(StaticFiles):
149
- """Route everything to index.html. https://stackoverflow.com/a/73552966/3318517"""
150
-
151
- async def get_response(self, path: str, scope):
152
- try:
153
- return await super().get_response(path, scope)
154
- except (
155
- fastapi.HTTPException,
156
- starlette.exceptions.HTTPException,
157
- ) as ex:
158
- if ex.status_code == 404:
159
- return await super().get_response(".", scope)
160
- else:
161
- raise ex
162
-
163
-
164
- static_dir = SPAStaticFiles(packages=[("lynxkite_app", "web_assets")], html=True)
165
- app.mount("/", static_dir, name="web_assets")