diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml
index 29bfc57..6e3ff49 100644
--- a/.github/workflows/deploy.yaml
+++ b/.github/workflows/deploy.yaml
@@ -1,14 +1,13 @@
name: Deploy to GitHub Pages
-
-permissions:
- contents: write
- pages: write
-
on:
push:
- branches: [ "main", "master" ]
+ branches: [main]
workflow_dispatch:
+env:
+ OBJC_DISABLE_INITIALIZE_FORK_SAFETY: YES
jobs:
deploy:
runs-on: ubuntu-latest
- steps: [uses: fastai/workflows/quarto-ghp@master]
+ steps:
+ - uses: answerdotai/workflows/quarto-ghp@master
+# with: {pre: 1}
\ No newline at end of file
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 5608592..500912c 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -1,7 +1,38 @@
name: CI
-on: [workflow_dispatch, pull_request, push]
+on:
+ workflow_dispatch:
+ pull_request:
+ push:
+ branches: [master]
+
+env:
+ OBJC_DISABLE_INITIALIZE_FORK_SAFETY: YES
jobs:
test:
- runs-on: ubuntu-latest
- steps: [uses: fastai/workflows/nbdev-ci@master]
+ strategy:
+ fail-fast: true
+ matrix:
+ os: [ubuntu, macos]
+ version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
+ runs-on: ${{ matrix.os }}-latest
+ steps:
+ - uses: answerdotai/workflows/nbdev-ci@master
+ with:
+ version: ${{ matrix.version }}
+ pre: 1
+ - name: test docs build
+ if: ${{ (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch') && matrix.version == '3.10' && matrix.os == 'ubuntu' }}
+ run: |
+ set -ux
+ wget -q $(curl https://latest.fast.ai/pre/quarto-dev/quarto-cli/linux-amd64.deb)
+ sudo dpkg -i quarto*.deb
+ nbdev_docs
+ if [ -f "_docs/index.html" ]; then
+ echo "docs built successfully."
+ else
+ echo "index page not found in rendered docs."
+ ls -la
+ ls -la _docs
+ exit 1
+ fi
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 5f112e2..bb2297e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -149,3 +149,5 @@ checklink/cookies.txt
# Quarto
.quarto
+
+saves
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
index 261eeb9..ef865b5 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,192 +1,4 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
+ Copyright 2025 Florian Fürrutter
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index 80df770..4b2718a 100644
--- a/README.md
+++ b/README.md
@@ -2,94 +2,110 @@
+
-
-
-
-
+
+
+
+
+
Code repository for generating quantum circuits with diffusion models.
-[\[Arxiv\]](https://arxiv.org/abs/2311.02041)
-[\[Demo\]](https://huggingface.co/spaces/Floki00/genQC)
-
+
+
+
+
+
+
+## 📰 News
+
+- 🔥 \[2025-06-01\] *Discrete-continuous circuits with multimodal
+ diffusion* - model released on [Hugging Face:
+ huggingface.co/collections/Floki00](https://huggingface.co/collections/Floki00/discrete-continuous-circuits-with-multimodal-diffusion-6839c4e4553e56b957bbd5bf).
## The codebase
The code contained within this repo allows the sampling of pre-trained
diffusion models and includes our pipeline to fine-tune and train models
-from scratch. Pre-trained weights can be found on [Hugging
-Face](https://huggingface.co/collections/Floki00/generative-quantum-circuits-6550e926c67f60a368b02bc3)
-and can be downloaded automatically via our code (see minimal example).
-For the CLIP model weights we use the
-[OpenCLIP](https://github.com/mlfoundations/open_clip) library, which
+from scratch. Pre-trained weights can be found on [\[Hugging
+Face\]](https://huggingface.co/collections/Floki00/) and can be
+downloaded automatically via our code (see minimal example). For the
+text CLIP model weights we use the
+[`OpenCLIP`](https://github.com/mlfoundations/open_clip) library, which
will download (and cache) the CLIP model on first usage of our pipeline.
-In case you prefer reading a documentation rather than notebooks or code
-see [\[Documentation\]](https://florianfuerrutter.github.io/genQC/).
+In case you prefer reading a documentation, rather than notebooks or
+code, see the project page under
+[\[Documentation\]](https://florianfuerrutter.github.io/genQC/).
-The repo inlcudes:
+This repo inlcudes:
1. `genQC/` a full release of our used diffusion pipeline.
-2. `src/examples` examples how to reproduce some figures of the
- [Paper](https://arxiv.org/abs/2311.02041).
+2. `src/examples/` examples and tutorials to show how to use the
+ library.
3. `src/` the source notebooks for
- [nbdev](https://github.com/fastai/nbdev).
+ [`nbdev`](https://github.com/fastai/nbdev).
## Examples
#### Minimal example
-A minimal example to generate a 5 qubit circuit conditioned on a SRV of
-$[1,1,1,2,2]$. You can try it out on your own with our
-[\[Demo\]](https://huggingface.co/spaces/Floki00/genQC), no coding
-required.
+A minimal example to compile the 4-qubit Quantum Fourier transform (QFT)
+unitary, using parameterized circuits.
``` python
-from genQC.pipeline.diffusion_pipeline import DiffusionPipeline
-from genQC.inference.infer_srv import generate_srv_tensors, convert_tensors_to_srvs
-
-pipeline = DiffusionPipeline.from_pretrained("Floki00/qc_srv_3to8qubit", "cpu")
-pipeline.scheduler.set_timesteps(20)
-
-out_tensor = generate_srv_tensors(pipeline, "Generate SRV: [1,1,2,2,2]", samples=1, system_size=5, num_of_qubits=5, max_gates=16, g=10)
-qc_list, _, srv_list = convert_tensors_to_srvs(out_tensor, pipeline.gate_pool)
+import torch
+from genQC.pipeline.multimodal_diffusion_pipeline import MultimodalDiffusionPipeline_ParametrizedCompilation
+from genQC.inference.sampling import generate_compilation_tensors, decode_tensors_to_backend
+from genQC.utils.misc_utils import infer_torch_device, set_seed
+from genQC.platform.tokenizer.circuits_tokenizer import CircuitTokenizer
+from genQC.benchmark.bench_compilation import SpecialUnitaries
+from genQC.platform.simulation import Simulator, CircuitBackendType
+
+device = infer_torch_device()
+set_seed(0)
+
+pipeline = MultimodalDiffusionPipeline_ParametrizedCompilation.from_pretrained(
+ repo_id="Floki00/cirdit_multimodal_compile_3to5qubit",
+ device=device)
+
+pipeline.scheduler.set_timesteps(40)
+pipeline.scheduler_w.set_timesteps(40)
+
+pipeline.g_h, pipeline.g_w = 0.3, 0.1
+pipeline.lambda_h, pipeline.lambda_w = 1.0, 0.35
+
+U = SpecialUnitaries.QFT(num_qubits=4).to(torch.complex64)
+
+out_tensor, params = generate_compilation_tensors(pipeline,
+ prompt="Compile 4 qubits using: ['h', 'cx', 'ccx', 'swap', 'rx', 'ry', 'rz', 'cp']",
+ U=U,
+ samples=8,
+ system_size=5,
+ num_of_qubits=4,
+ max_gates=32)
```
- [INFO]: `genQC.models.unet_qc.QC_Cond_UNet` instantiated from given config on cpu.
- [INFO]: `genQC.models.frozen_open_clip.CachedFrozenOpenCLIPEmbedder` instantiated from given config on cpu.
- [INFO]: `genQC.models.frozen_open_clip.CachedFrozenOpenCLIPEmbedder`. No save_path` provided. No state dict loaded.
-
``` python
-print(f"Circuit is SRV {srv_list[0]}")
+vocabulary = {g:i+1 for i, g in enumerate(pipeline.gate_pool)}
+tokenizer = CircuitTokenizer(vocabulary)
+simulator = Simulator(CircuitBackendType.QISKIT)
+
+qc_list, _ = decode_tensors_to_backend(simulator, tokenizer, out_tensor, params)
qc_list[0].draw("mpl")
```
- Circuit is SRV [1, 1, 2, 2, 2]
-
-
+
-#### Included examples
+#### Further examples
-Example notebooks are provided in the directory `src/examples/`.
-
-- `0_hello_circuit`
- [\[doc\]](https://florianfuerrutter.github.io/genQC/examples/hello_circuit.html)
- [\[notebook\]](https://github.com/FlorianFuerrutter/genQC/blob/main/src/examples/0_hello_circuit.ipynb):
- How to sample a circuit (conditioned on a SRV)
-- `1_editing_and_masking`
- [\[doc\]](https://florianfuerrutter.github.io/genQC/examples/editing_and_masking.html)
- [\[notebook\]](https://github.com/FlorianFuerrutter/genQC/blob/main/src/examples/1_editing_and_masking.ipynb):
- Presents editing and masking of circuits
-- `2_unitary_compilation`
- [\[doc\]](https://florianfuerrutter.github.io/genQC/examples/unitary_compilation.html)
- [\[notebook\]](https://github.com/FlorianFuerrutter/genQC/blob/main/src/examples/2_unitary_compilation.ipynb):
- Compile unitaries and transpile circuits
-- `3_dataset_and_fineTune`
- [\[doc\]](https://florianfuerrutter.github.io/genQC/examples/dataset_and_finetune.html)
- [\[notebook\]](https://github.com/FlorianFuerrutter/genQC/blob/main/src/examples/3_dataset_and_fineTune.ipynb):
- How to create a dataset and fine-tune a pre-trained model
+More detailed examples and tutorial notebooks are provided on the
+project page
+[\[tutorials\]](https://florianfuerrutter.github.io/genQC/examples/tutorials.html)
+or in the directory `src/examples/`.
## Installation
@@ -106,20 +122,21 @@ pip install genQC
Note, this will install missing requirements automatically. You may want
to install some of them manually beforehand, e.g. `torch` for specific
-cuda support, see
-[pytorch.org/get-started/locally](https://pytorch.org/get-started/locally/).
-
-**Requirements:** `genQC` depends on `python` (min. version 3.10) and
-the libraries: `torch`, `numpy`, `matplotlib`, `scipy`, `pandas`,
-`omegaconf`, `qiskit`, `tqdm`, `joblib`, `open_clip_torch`,
-`ipywidgets`, `pylatexenc` and `huggingface_hub`. All can be installed
-with `pip`. In `src/RELEASES.md`
+cuda support, see .
+
+**Requirements:** `genQC` depends on `python` (min. version 3.12) and
+the libraries: `torch`, `numpy`, `matplotlib`, `scipy`, `omegaconf`,
+`qiskit`, `tqdm`, `joblib`, `open_clip_torch`, `ipywidgets`,
+`pylatexenc`, `safetensors`, `tensordict` and `huggingface_hub`. All can
+be installed with `pip install`. In `src/RELEASES.md`
[\[doc\]](https://florianfuerrutter.github.io/genQC/RELEASES.html) and
-the release descriptions specific tested-on versions are listed.
+the [GitHub release
+descriptions](https://github.com/FlorianFuerrutter/genQC/releases),
+specific tested-on versions are listed.
#### Method 2: clone the repository
-To use the latest GitHub code you can clone the repository by running:
+To use the latest GitHub code, you can clone the repository by running:
``` sh
git clone https://github.com/FlorianFuerrutter/genQC.git
@@ -136,9 +153,10 @@ pip install -e .
#### Test installation
-You can run the provided `0_hello_circuit`
-[\[doc\]](https://florianfuerrutter.github.io/genQC/examples/hello_circuit.html)
-[\[notebook\]](https://github.com/FlorianFuerrutter/genQC/blob/main/src/examples/0_hello_circuit.ipynb)
+You can run the provided
+`src/examples/Quantum circuit synthesis with diffusion models/0_hello_circuit`
+[\[doc\]](https://florianfuerrutter.github.io/genQC/examples/Quantum%20circuit%20synthesis%20with%20diffusion%20models/hello_circuit.html)
+[\[notebook\]](https://github.com/FlorianFuerrutter/genQC/blob/main/src/examples/Quantum%20circuit%20synthesis%20with%20diffusion%20models/0_hello_circuit.ipynb)
example to test your installation. On a computer with a moderate GPU
this inference example notebook should run under half a minute.
@@ -153,6 +171,8 @@ License
We kindly ask you to cite our paper if any of the previous material was
useful for your work.
+#### Quantum circuit synthesis with diffusion models
+
``` latex
@article{furrutter2024quantum,
title={Quantum circuit synthesis with diffusion models},
diff --git a/genQC/__init__.py b/genQC/__init__.py
index 485f44a..d3ec452 100644
--- a/genQC/__init__.py
+++ b/genQC/__init__.py
@@ -1 +1 @@
-__version__ = "0.1.1"
+__version__ = "0.2.0"
diff --git a/genQC/_modidx.py b/genQC/_modidx.py
index c05ae31..7a3cb50 100644
--- a/genQC/_modidx.py
+++ b/genQC/_modidx.py
@@ -5,250 +5,403 @@
'doc_host': 'https://FlorianFuerrutter.github.io',
'git_url': 'https://github.com/FlorianFuerrutter/genQC',
'lib_path': 'genQC'},
- 'syms': { 'genQC.config_loader': { 'genQC.config_loader.class_to_str': ('config_loader.html#class_to_str', 'genQC/config_loader.py'),
- 'genQC.config_loader.config_to_dict': ('config_loader.html#config_to_dict', 'genQC/config_loader.py'),
- 'genQC.config_loader.get_obj_from_str': ( 'config_loader.html#get_obj_from_str',
- 'genQC/config_loader.py'),
- 'genQC.config_loader.instantiate_from_config': ( 'config_loader.html#instantiate_from_config',
- 'genQC/config_loader.py'),
- 'genQC.config_loader.load_config': ('config_loader.html#load_config', 'genQC/config_loader.py'),
- 'genQC.config_loader.load_model_from_config': ( 'config_loader.html#load_model_from_config',
- 'genQC/config_loader.py'),
- 'genQC.config_loader.save_dataclass_yaml': ( 'config_loader.html#save_dataclass_yaml',
- 'genQC/config_loader.py'),
- 'genQC.config_loader.save_dict_yaml': ('config_loader.html#save_dict_yaml', 'genQC/config_loader.py')},
- 'genQC.dataset.cached_qc_dataset': { 'genQC.dataset.cached_qc_dataset.Cached_OpenClip_Dataset': ( 'dataset/cached_qc_dataset.html#cached_openclip_dataset',
- 'genQC/dataset/cached_qc_dataset.py'),
- 'genQC.dataset.cached_qc_dataset.Cached_OpenClip_Dataset.caching': ( 'dataset/cached_qc_dataset.html#cached_openclip_dataset.caching',
- 'genQC/dataset/cached_qc_dataset.py'),
- 'genQC.dataset.cached_qc_dataset.Cached_OpenClip_Dataset.from_config_file': ( 'dataset/cached_qc_dataset.html#cached_openclip_dataset.from_config_file',
- 'genQC/dataset/cached_qc_dataset.py'),
- 'genQC.dataset.cached_qc_dataset.Cached_OpenClip_Dataset.get_dataloaders': ( 'dataset/cached_qc_dataset.html#cached_openclip_dataset.get_dataloaders',
- 'genQC/dataset/cached_qc_dataset.py'),
- 'genQC.dataset.cached_qc_dataset.Cached_OpenClip_Dataset.x_y_preprocess': ( 'dataset/cached_qc_dataset.html#cached_openclip_dataset.x_y_preprocess',
- 'genQC/dataset/cached_qc_dataset.py')},
- 'genQC.dataset.config_dataset': { 'genQC.dataset.config_dataset.Config_Dataset': ( 'dataset/config_dataset.html#config_dataset',
- 'genQC/dataset/config_dataset.py'),
- 'genQC.dataset.config_dataset.Config_Dataset.__init__': ( 'dataset/config_dataset.html#config_dataset.__init__',
- 'genQC/dataset/config_dataset.py'),
- 'genQC.dataset.config_dataset.Config_Dataset.from_config': ( 'dataset/config_dataset.html#config_dataset.from_config',
- 'genQC/dataset/config_dataset.py'),
- 'genQC.dataset.config_dataset.Config_Dataset.from_config_file': ( 'dataset/config_dataset.html#config_dataset.from_config_file',
- 'genQC/dataset/config_dataset.py'),
- 'genQC.dataset.config_dataset.Config_Dataset.from_huggingface': ( 'dataset/config_dataset.html#config_dataset.from_huggingface',
- 'genQC/dataset/config_dataset.py'),
- 'genQC.dataset.config_dataset.Config_Dataset.get_config': ( 'dataset/config_dataset.html#config_dataset.get_config',
+ 'syms': { 'genQC.benchmark.bench_compilation': { 'genQC.benchmark.bench_compilation.BaseHamiltonian': ( 'benchmark/bench_compilation.html#basehamiltonian',
+ 'genQC/benchmark/bench_compilation.py'),
+ 'genQC.benchmark.bench_compilation.BaseHamiltonian.__init__': ( 'benchmark/bench_compilation.html#basehamiltonian.__init__',
+ 'genQC/benchmark/bench_compilation.py'),
+ 'genQC.benchmark.bench_compilation.BaseHamiltonian._generate_matrix': ( 'benchmark/bench_compilation.html#basehamiltonian._generate_matrix',
+ 'genQC/benchmark/bench_compilation.py'),
+ 'genQC.benchmark.bench_compilation.BaseHamiltonian.get_evolution': ( 'benchmark/bench_compilation.html#basehamiltonian.get_evolution',
+ 'genQC/benchmark/bench_compilation.py'),
+ 'genQC.benchmark.bench_compilation.IsingHamiltonian': ( 'benchmark/bench_compilation.html#isinghamiltonian',
+ 'genQC/benchmark/bench_compilation.py'),
+ 'genQC.benchmark.bench_compilation.IsingHamiltonian.__init__': ( 'benchmark/bench_compilation.html#isinghamiltonian.__init__',
+ 'genQC/benchmark/bench_compilation.py'),
+ 'genQC.benchmark.bench_compilation.IsingHamiltonian._generate_matrix': ( 'benchmark/bench_compilation.html#isinghamiltonian._generate_matrix',
+ 'genQC/benchmark/bench_compilation.py'),
+ 'genQC.benchmark.bench_compilation.SpecialUnitaries': ( 'benchmark/bench_compilation.html#specialunitaries',
+ 'genQC/benchmark/bench_compilation.py'),
+ 'genQC.benchmark.bench_compilation.SpecialUnitaries.QFT': ( 'benchmark/bench_compilation.html#specialunitaries.qft',
+ 'genQC/benchmark/bench_compilation.py'),
+ 'genQC.benchmark.bench_compilation.XXZHamiltonian': ( 'benchmark/bench_compilation.html#xxzhamiltonian',
+ 'genQC/benchmark/bench_compilation.py'),
+ 'genQC.benchmark.bench_compilation.XXZHamiltonian.__init__': ( 'benchmark/bench_compilation.html#xxzhamiltonian.__init__',
+ 'genQC/benchmark/bench_compilation.py'),
+ 'genQC.benchmark.bench_compilation.XXZHamiltonian._generate_matrix': ( 'benchmark/bench_compilation.html#xxzhamiltonian._generate_matrix',
+ 'genQC/benchmark/bench_compilation.py'),
+ 'genQC.benchmark.bench_compilation.qubit_tensor_product': ( 'benchmark/bench_compilation.html#qubit_tensor_product',
+ 'genQC/benchmark/bench_compilation.py')},
+ 'genQC.dataset.balancing': { 'genQC.dataset.balancing.add_balance_fn_quantile_qc_length': ( 'dataset/balancing.html#add_balance_fn_quantile_qc_length',
+ 'genQC/dataset/balancing.py'),
+ 'genQC.dataset.balancing.get_tensor_gate_length': ( 'dataset/balancing.html#get_tensor_gate_length',
+ 'genQC/dataset/balancing.py')},
+ 'genQC.dataset.cached_dataset': { 'genQC.dataset.cached_dataset.CachedOpenCLIPDataset': ( 'dataset/cached_dataset.html#cachedopenclipdataset',
+ 'genQC/dataset/cached_dataset.py'),
+ 'genQC.dataset.cached_dataset.CachedOpenCLIPDataset.caching': ( 'dataset/cached_dataset.html#cachedopenclipdataset.caching',
+ 'genQC/dataset/cached_dataset.py'),
+ 'genQC.dataset.cached_dataset.CachedOpenCLIPDataset.get_dataloaders': ( 'dataset/cached_dataset.html#cachedopenclipdataset.get_dataloaders',
+ 'genQC/dataset/cached_dataset.py'),
+ 'genQC.dataset.cached_dataset.CachedOpenCLIPDataset.x_y_preprocess': ( 'dataset/cached_dataset.html#cachedopenclipdataset.x_y_preprocess',
+ 'genQC/dataset/cached_dataset.py'),
+ 'genQC.dataset.cached_dataset.CachedOpenCLIPDatasetConfig': ( 'dataset/cached_dataset.html#cachedopenclipdatasetconfig',
+ 'genQC/dataset/cached_dataset.py')},
+ 'genQC.dataset.circuits_dataset': { 'genQC.dataset.circuits_dataset.CircuitsConfigDataset': ( 'dataset/circuits_dataset.html#circuitsconfigdataset',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.CircuitsConfigDataset.__init__': ( 'dataset/circuits_dataset.html#circuitsconfigdataset.__init__',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.CircuitsConfigDataset.params_config': ( 'dataset/circuits_dataset.html#circuitsconfigdataset.params_config',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.CircuitsConfigDatasetConfig': ( 'dataset/circuits_dataset.html#circuitsconfigdatasetconfig',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.MixedCircuitsConfigDataset': ( 'dataset/circuits_dataset.html#mixedcircuitsconfigdataset',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.MixedCircuitsConfigDataset._cut': ( 'dataset/circuits_dataset.html#mixedcircuitsconfigdataset._cut',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.MixedCircuitsConfigDataset._cut_compilation_params': ( 'dataset/circuits_dataset.html#mixedcircuitsconfigdataset._cut_compilation_params',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.MixedCircuitsConfigDataset._get_cut_sizes': ( 'dataset/circuits_dataset.html#mixedcircuitsconfigdataset._get_cut_sizes',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.MixedCircuitsConfigDataset._preprocess_dataset': ( 'dataset/circuits_dataset.html#mixedcircuitsconfigdataset._preprocess_dataset',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.MixedCircuitsConfigDataset.cut_padding_Bucket_collate_fn': ( 'dataset/circuits_dataset.html#mixedcircuitsconfigdataset.cut_padding_bucket_collate_fn',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.MixedCircuitsConfigDataset.cut_padding_Bucket_collate_fn_compilation': ( 'dataset/circuits_dataset.html#mixedcircuitsconfigdataset.cut_padding_bucket_collate_fn_compilation',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.MixedCircuitsConfigDataset.cut_padding_Bucket_collate_fn_compilation_params': ( 'dataset/circuits_dataset.html#mixedcircuitsconfigdataset.cut_padding_bucket_collate_fn_compilation_params',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.MixedCircuitsConfigDataset.cut_padding_collate_fn': ( 'dataset/circuits_dataset.html#mixedcircuitsconfigdataset.cut_padding_collate_fn',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.MixedCircuitsConfigDataset.cut_padding_collate_fn_compilation': ( 'dataset/circuits_dataset.html#mixedcircuitsconfigdataset.cut_padding_collate_fn_compilation',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.MixedCircuitsConfigDataset.cut_padding_collate_fn_compilation_params': ( 'dataset/circuits_dataset.html#mixedcircuitsconfigdataset.cut_padding_collate_fn_compilation_params',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.MixedCircuitsConfigDataset.from_datasets': ( 'dataset/circuits_dataset.html#mixedcircuitsconfigdataset.from_datasets',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.MixedCircuitsConfigDataset.params_config': ( 'dataset/circuits_dataset.html#mixedcircuitsconfigdataset.params_config',
+ 'genQC/dataset/circuits_dataset.py'),
+ 'genQC.dataset.circuits_dataset.MixedCircuitsConfigDatasetConfig': ( 'dataset/circuits_dataset.html#mixedcircuitsconfigdatasetconfig',
+ 'genQC/dataset/circuits_dataset.py')},
+ 'genQC.dataset.config_dataset': { 'genQC.dataset.config_dataset.ConfigDataset': ( 'dataset/config_dataset.html#configdataset',
+ 'genQC/dataset/config_dataset.py'),
+ 'genQC.dataset.config_dataset.ConfigDataset.__init__': ( 'dataset/config_dataset.html#configdataset.__init__',
+ 'genQC/dataset/config_dataset.py'),
+ 'genQC.dataset.config_dataset.ConfigDataset.check_save_type': ( 'dataset/config_dataset.html#configdataset.check_save_type',
+ 'genQC/dataset/config_dataset.py'),
+ 'genQC.dataset.config_dataset.ConfigDataset.from_config': ( 'dataset/config_dataset.html#configdataset.from_config',
'genQC/dataset/config_dataset.py'),
- 'genQC.dataset.config_dataset.Config_Dataset.load_x_y': ( 'dataset/config_dataset.html#config_dataset.load_x_y',
- 'genQC/dataset/config_dataset.py'),
- 'genQC.dataset.config_dataset.Config_Dataset.params_config': ( 'dataset/config_dataset.html#config_dataset.params_config',
+ 'genQC.dataset.config_dataset.ConfigDataset.from_config_file': ( 'dataset/config_dataset.html#configdataset.from_config_file',
+ 'genQC/dataset/config_dataset.py'),
+ 'genQC.dataset.config_dataset.ConfigDataset.from_huggingface': ( 'dataset/config_dataset.html#configdataset.from_huggingface',
+ 'genQC/dataset/config_dataset.py'),
+ 'genQC.dataset.config_dataset.ConfigDataset.get_config': ( 'dataset/config_dataset.html#configdataset.get_config',
+ 'genQC/dataset/config_dataset.py'),
+ 'genQC.dataset.config_dataset.ConfigDataset.get_dataloaders': ( 'dataset/config_dataset.html#configdataset.get_dataloaders',
+ 'genQC/dataset/config_dataset.py'),
+ 'genQC.dataset.config_dataset.ConfigDataset.load_x_y': ( 'dataset/config_dataset.html#configdataset.load_x_y',
+ 'genQC/dataset/config_dataset.py'),
+ 'genQC.dataset.config_dataset.ConfigDataset.memory_summary': ( 'dataset/config_dataset.html#configdataset.memory_summary',
'genQC/dataset/config_dataset.py'),
- 'genQC.dataset.config_dataset.Config_Dataset.save_dataset': ( 'dataset/config_dataset.html#config_dataset.save_dataset',
+ 'genQC.dataset.config_dataset.ConfigDataset.params_config': ( 'dataset/config_dataset.html#configdataset.params_config',
'genQC/dataset/config_dataset.py'),
- 'genQC.dataset.config_dataset.Config_Dataset.store_x_y': ( 'dataset/config_dataset.html#config_dataset.store_x_y',
- 'genQC/dataset/config_dataset.py'),
- 'genQC.dataset.config_dataset.Config_Dataset.to': ( 'dataset/config_dataset.html#config_dataset.to',
- 'genQC/dataset/config_dataset.py'),
- 'genQC.dataset.config_dataset.Config_Dataset_config': ( 'dataset/config_dataset.html#config_dataset_config',
- 'genQC/dataset/config_dataset.py')},
+ 'genQC.dataset.config_dataset.ConfigDataset.save_dataset': ( 'dataset/config_dataset.html#configdataset.save_dataset',
+ 'genQC/dataset/config_dataset.py'),
+ 'genQC.dataset.config_dataset.ConfigDataset.store_x_y': ( 'dataset/config_dataset.html#configdataset.store_x_y',
+ 'genQC/dataset/config_dataset.py'),
+ 'genQC.dataset.config_dataset.ConfigDataset.to': ( 'dataset/config_dataset.html#configdataset.to',
+ 'genQC/dataset/config_dataset.py'),
+ 'genQC.dataset.config_dataset.ConfigDataset.valid_split': ( 'dataset/config_dataset.html#configdataset.valid_split',
+ 'genQC/dataset/config_dataset.py'),
+ 'genQC.dataset.config_dataset.ConfigDataset.x_y_preprocess': ( 'dataset/config_dataset.html#configdataset.x_y_preprocess',
+ 'genQC/dataset/config_dataset.py'),
+ 'genQC.dataset.config_dataset.ConfigDatasetConfig': ( 'dataset/config_dataset.html#configdatasetconfig',
+ 'genQC/dataset/config_dataset.py')},
'genQC.dataset.dataset_helper': { 'genQC.dataset.dataset_helper.balance_tensor_dataset': ( 'dataset/dataset_helper.html#balance_tensor_dataset',
'genQC/dataset/dataset_helper.py'),
'genQC.dataset.dataset_helper.check_duplicate_in_dataset': ( 'dataset/dataset_helper.html#check_duplicate_in_dataset',
'genQC/dataset/dataset_helper.py'),
'genQC.dataset.dataset_helper.check_duplicates_in_dataset': ( 'dataset/dataset_helper.html#check_duplicates_in_dataset',
'genQC/dataset/dataset_helper.py'),
- 'genQC.dataset.dataset_helper.check_duplicates_in_dataset_python': ( 'dataset/dataset_helper.html#check_duplicates_in_dataset_python',
- 'genQC/dataset/dataset_helper.py'),
'genQC.dataset.dataset_helper.get_unique_elements_indices': ( 'dataset/dataset_helper.html#get_unique_elements_indices',
'genQC/dataset/dataset_helper.py'),
- 'genQC.dataset.dataset_helper.map_old_tensor_to_new': ( 'dataset/dataset_helper.html#map_old_tensor_to_new',
- 'genQC/dataset/dataset_helper.py'),
'genQC.dataset.dataset_helper.shuffle_tensor_dataset': ( 'dataset/dataset_helper.html#shuffle_tensor_dataset',
'genQC/dataset/dataset_helper.py'),
'genQC.dataset.dataset_helper.uniquify_tensor_dataset': ( 'dataset/dataset_helper.html#uniquify_tensor_dataset',
'genQC/dataset/dataset_helper.py')},
- 'genQC.dataset.mixed_cached_qc_dataset': { 'genQC.dataset.mixed_cached_qc_dataset.Mixed_Cached_OpenClip_Dataset': ( 'dataset/mixed_cached_qc_dataset.html#mixed_cached_openclip_dataset',
- 'genQC/dataset/mixed_cached_qc_dataset.py'),
- 'genQC.dataset.mixed_cached_qc_dataset.Mixed_Cached_OpenClip_Dataset.cut_padding_Bucket_collate_fn': ( 'dataset/mixed_cached_qc_dataset.html#mixed_cached_openclip_dataset.cut_padding_bucket_collate_fn',
- 'genQC/dataset/mixed_cached_qc_dataset.py'),
- 'genQC.dataset.mixed_cached_qc_dataset.Mixed_Cached_OpenClip_Dataset.cut_padding_Bucket_collate_fn_compilation': ( 'dataset/mixed_cached_qc_dataset.html#mixed_cached_openclip_dataset.cut_padding_bucket_collate_fn_compilation',
- 'genQC/dataset/mixed_cached_qc_dataset.py'),
- 'genQC.dataset.mixed_cached_qc_dataset.Mixed_Cached_OpenClip_Dataset.cut_padding_Bucket_collate_fn_compilation_params': ( 'dataset/mixed_cached_qc_dataset.html#mixed_cached_openclip_dataset.cut_padding_bucket_collate_fn_compilation_params',
- 'genQC/dataset/mixed_cached_qc_dataset.py'),
- 'genQC.dataset.mixed_cached_qc_dataset.Mixed_Cached_OpenClip_Dataset.cut_padding_collate_fn': ( 'dataset/mixed_cached_qc_dataset.html#mixed_cached_openclip_dataset.cut_padding_collate_fn',
- 'genQC/dataset/mixed_cached_qc_dataset.py'),
- 'genQC.dataset.mixed_cached_qc_dataset.Mixed_Cached_OpenClip_Dataset.cut_padding_collate_fn_compilation': ( 'dataset/mixed_cached_qc_dataset.html#mixed_cached_openclip_dataset.cut_padding_collate_fn_compilation',
- 'genQC/dataset/mixed_cached_qc_dataset.py'),
- 'genQC.dataset.mixed_cached_qc_dataset.Mixed_Cached_OpenClip_Dataset.cut_padding_collate_fn_compilation_params': ( 'dataset/mixed_cached_qc_dataset.html#mixed_cached_openclip_dataset.cut_padding_collate_fn_compilation_params',
- 'genQC/dataset/mixed_cached_qc_dataset.py'),
- 'genQC.dataset.mixed_cached_qc_dataset.Mixed_Cached_OpenClip_Dataset.flexPadAttn_TimeOnly_padding_collate_fn': ( 'dataset/mixed_cached_qc_dataset.html#mixed_cached_openclip_dataset.flexpadattn_timeonly_padding_collate_fn',
- 'genQC/dataset/mixed_cached_qc_dataset.py'),
- 'genQC.dataset.mixed_cached_qc_dataset.Mixed_Cached_OpenClip_Dataset.flexPadAttn_padding_collate_fn': ( 'dataset/mixed_cached_qc_dataset.html#mixed_cached_openclip_dataset.flexpadattn_padding_collate_fn',
- 'genQC/dataset/mixed_cached_qc_dataset.py'),
- 'genQC.dataset.mixed_cached_qc_dataset.Mixed_Cached_OpenClip_Dataset.from_config_file': ( 'dataset/mixed_cached_qc_dataset.html#mixed_cached_openclip_dataset.from_config_file',
- 'genQC/dataset/mixed_cached_qc_dataset.py'),
- 'genQC.dataset.mixed_cached_qc_dataset.Mixed_Cached_OpenClip_Dataset.from_datasets': ( 'dataset/mixed_cached_qc_dataset.html#mixed_cached_openclip_dataset.from_datasets',
- 'genQC/dataset/mixed_cached_qc_dataset.py'),
- 'genQC.dataset.mixed_cached_qc_dataset.Mixed_Cached_OpenClip_Dataset.get_dataloaders': ( 'dataset/mixed_cached_qc_dataset.html#mixed_cached_openclip_dataset.get_dataloaders',
- 'genQC/dataset/mixed_cached_qc_dataset.py'),
- 'genQC.dataset.mixed_cached_qc_dataset.Mixed_Cached_OpenClip_Dataset.params_config': ( 'dataset/mixed_cached_qc_dataset.html#mixed_cached_openclip_dataset.params_config',
- 'genQC/dataset/mixed_cached_qc_dataset.py'),
- 'genQC.dataset.mixed_cached_qc_dataset.Mixed_Cached_OpenClip_Dataset_config': ( 'dataset/mixed_cached_qc_dataset.html#mixed_cached_openclip_dataset_config',
- 'genQC/dataset/mixed_cached_qc_dataset.py')},
- 'genQC.dataset.qc_dataset': { 'genQC.dataset.qc_dataset.Qc_Config_Dataset': ( 'dataset/qc_dataset.html#qc_config_dataset',
- 'genQC/dataset/qc_dataset.py'),
- 'genQC.dataset.qc_dataset.Qc_Config_Dataset.__init__': ( 'dataset/qc_dataset.html#qc_config_dataset.__init__',
- 'genQC/dataset/qc_dataset.py'),
- 'genQC.dataset.qc_dataset.Qc_Config_Dataset.get_dataloaders': ( 'dataset/qc_dataset.html#qc_config_dataset.get_dataloaders',
- 'genQC/dataset/qc_dataset.py'),
- 'genQC.dataset.qc_dataset.Qc_Config_Dataset.params_config': ( 'dataset/qc_dataset.html#qc_config_dataset.params_config',
- 'genQC/dataset/qc_dataset.py'),
- 'genQC.dataset.qc_dataset.Qc_Config_Dataset.plot_distribution': ( 'dataset/qc_dataset.html#qc_config_dataset.plot_distribution',
- 'genQC/dataset/qc_dataset.py'),
- 'genQC.dataset.qc_dataset.Qc_Config_Dataset.plot_example': ( 'dataset/qc_dataset.html#qc_config_dataset.plot_example',
- 'genQC/dataset/qc_dataset.py'),
- 'genQC.dataset.qc_dataset.Qc_Config_Dataset.valid_split': ( 'dataset/qc_dataset.html#qc_config_dataset.valid_split',
- 'genQC/dataset/qc_dataset.py'),
- 'genQC.dataset.qc_dataset.Qc_Config_Dataset.x_y_preprocess': ( 'dataset/qc_dataset.html#qc_config_dataset.x_y_preprocess',
- 'genQC/dataset/qc_dataset.py'),
- 'genQC.dataset.qc_dataset.Qc_Config_Dataset_config': ( 'dataset/qc_dataset.html#qc_config_dataset_config',
- 'genQC/dataset/qc_dataset.py')},
+ 'genQC.dataset.mixed_cached_dataset': { 'genQC.dataset.mixed_cached_dataset.MixedCachedOpenCLIPDataset': ( 'dataset/mixed_cached_dataset.html#mixedcachedopenclipdataset',
+ 'genQC/dataset/mixed_cached_dataset.py'),
+ 'genQC.dataset.mixed_cached_dataset.MixedCachedOpenCLIPDataset._add_missing_conditions': ( 'dataset/mixed_cached_dataset.html#mixedcachedopenclipdataset._add_missing_conditions',
+ 'genQC/dataset/mixed_cached_dataset.py'),
+ 'genQC.dataset.mixed_cached_dataset.MixedCachedOpenCLIPDataset._create_train_valid_datasets': ( 'dataset/mixed_cached_dataset.html#mixedcachedopenclipdataset._create_train_valid_datasets',
+ 'genQC/dataset/mixed_cached_dataset.py'),
+ 'genQC.dataset.mixed_cached_dataset.MixedCachedOpenCLIPDataset._pad_conditions': ( 'dataset/mixed_cached_dataset.html#mixedcachedopenclipdataset._pad_conditions',
+ 'genQC/dataset/mixed_cached_dataset.py'),
+ 'genQC.dataset.mixed_cached_dataset.MixedCachedOpenCLIPDataset._preprocess_datasets': ( 'dataset/mixed_cached_dataset.html#mixedcachedopenclipdataset._preprocess_datasets',
+ 'genQC/dataset/mixed_cached_dataset.py'),
+ 'genQC.dataset.mixed_cached_dataset.MixedCachedOpenCLIPDataset._reorder_to_buckets': ( 'dataset/mixed_cached_dataset.html#mixedcachedopenclipdataset._reorder_to_buckets',
+ 'genQC/dataset/mixed_cached_dataset.py'),
+ 'genQC.dataset.mixed_cached_dataset.MixedCachedOpenCLIPDataset.from_datasets': ( 'dataset/mixed_cached_dataset.html#mixedcachedopenclipdataset.from_datasets',
+ 'genQC/dataset/mixed_cached_dataset.py'),
+ 'genQC.dataset.mixed_cached_dataset.MixedCachedOpenCLIPDataset.get_dataloaders': ( 'dataset/mixed_cached_dataset.html#mixedcachedopenclipdataset.get_dataloaders',
+ 'genQC/dataset/mixed_cached_dataset.py'),
+ 'genQC.dataset.mixed_cached_dataset.MixedCachedOpenCLIPDataset.params_config': ( 'dataset/mixed_cached_dataset.html#mixedcachedopenclipdataset.params_config',
+ 'genQC/dataset/mixed_cached_dataset.py'),
+ 'genQC.dataset.mixed_cached_dataset.MixedCachedOpenCLIPDatasetConfig': ( 'dataset/mixed_cached_dataset.html#mixedcachedopenclipdatasetconfig',
+ 'genQC/dataset/mixed_cached_dataset.py')},
'genQC.imports': {},
- 'genQC.inference.export_cudaq': { 'genQC.inference.export_cudaq.CircuitInstruction': ( 'inference/export_cudaq.html#circuitinstruction',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.CircuitInstructions': ( 'inference/export_cudaq.html#circuitinstructions',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.CircuitInstructions.__init__': ( 'inference/export_cudaq.html#circuitinstructions.__init__',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.CircuitInstructions.__repr__': ( 'inference/export_cudaq.html#circuitinstructions.__repr__',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.CircuitInstructions.add_instruction': ( 'inference/export_cudaq.html#circuitinstructions.add_instruction',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.CircuitInstructions.data': ( 'inference/export_cudaq.html#circuitinstructions.data',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.CircuitInstructions.length': ( 'inference/export_cudaq.html#circuitinstructions.length',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.CircuitInstructions.max_gates': ( 'inference/export_cudaq.html#circuitinstructions.max_gates',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.CircuitInstructions.num_qubits': ( 'inference/export_cudaq.html#circuitinstructions.num_qubits',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.CircuitInstructions.print': ( 'inference/export_cudaq.html#circuitinstructions.print',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.CircuitsCudaqBackend': ( 'inference/export_cudaq.html#circuitscudaqbackend',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.CircuitsCudaqBackend._construct_kernel': ( 'inference/export_cudaq.html#circuitscudaqbackend._construct_kernel',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.CircuitsCudaqBackend.check_error_circuit': ( 'inference/export_cudaq.html#circuitscudaqbackend.check_error_circuit',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.CircuitsCudaqBackend.draw': ( 'inference/export_cudaq.html#circuitscudaqbackend.draw',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.CircuitsCudaqBackend.export_cudaq': ( 'inference/export_cudaq.html#circuitscudaqbackend.export_cudaq',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.CircuitsCudaqBackend.get_unitary': ( 'inference/export_cudaq.html#circuitscudaqbackend.get_unitary',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.genqc_to_cudaq': ( 'inference/export_cudaq.html#genqc_to_cudaq',
- 'genQC/inference/export_cudaq.py'),
- 'genQC.inference.export_cudaq.tensor_to_instructions': ( 'inference/export_cudaq.html#tensor_to_instructions',
- 'genQC/inference/export_cudaq.py')},
- 'genQC.inference.infer_compilation': { 'genQC.inference.infer_compilation.check_correct_gates': ( 'inference/infer_compilation.html#check_correct_gates',
- 'genQC/inference/infer_compilation.py'),
- 'genQC.inference.infer_compilation.check_correct_unitary_distance': ( 'inference/infer_compilation.html#check_correct_unitary_distance',
- 'genQC/inference/infer_compilation.py'),
- 'genQC.inference.infer_compilation.check_correct_unitary_exact': ( 'inference/infer_compilation.html#check_correct_unitary_exact',
- 'genQC/inference/infer_compilation.py'),
- 'genQC.inference.infer_compilation.generate_comp_tensors': ( 'inference/infer_compilation.html#generate_comp_tensors',
- 'genQC/inference/infer_compilation.py'),
- 'genQC.inference.infer_compilation.get_gate_and_U_acc': ( 'inference/infer_compilation.html#get_gate_and_u_acc',
- 'genQC/inference/infer_compilation.py'),
- 'genQC.inference.infer_compilation.get_new_unitary_indices': ( 'inference/infer_compilation.html#get_new_unitary_indices',
- 'genQC/inference/infer_compilation.py'),
- 'genQC.inference.infer_compilation.get_new_unitary_indices_batch': ( 'inference/infer_compilation.html#get_new_unitary_indices_batch',
- 'genQC/inference/infer_compilation.py'),
- 'genQC.inference.infer_compilation.plot_hist_overview': ( 'inference/infer_compilation.html#plot_hist_overview',
- 'genQC/inference/infer_compilation.py'),
- 'genQC.inference.infer_compilation.split_U_to_tensor': ( 'inference/infer_compilation.html#split_u_to_tensor',
- 'genQC/inference/infer_compilation.py'),
- 'genQC.inference.infer_compilation.test_comp_acc': ( 'inference/infer_compilation.html#test_comp_acc',
- 'genQC/inference/infer_compilation.py'),
- 'genQC.inference.infer_compilation.test_comp_acc_on_rnd_samples': ( 'inference/infer_compilation.html#test_comp_acc_on_rnd_samples',
- 'genQC/inference/infer_compilation.py'),
- 'genQC.inference.infer_compilation.test_comp_acc_on_testset': ( 'inference/infer_compilation.html#test_comp_acc_on_testset',
- 'genQC/inference/infer_compilation.py')},
- 'genQC.inference.infer_gate_hist': { 'genQC.inference.infer_gate_hist.get_circuit_gate_length': ( 'inference/infer_gate_hist.html#get_circuit_gate_length',
- 'genQC/inference/infer_gate_hist.py'),
- 'genQC.inference.infer_gate_hist.get_tensor_gate_length': ( 'inference/infer_gate_hist.html#get_tensor_gate_length',
- 'genQC/inference/infer_gate_hist.py')},
- 'genQC.inference.infer_misc': { 'genQC.inference.infer_misc.convert_tensors_to_circuits': ( 'inference/infer_misc.html#convert_tensors_to_circuits',
- 'genQC/inference/infer_misc.py'),
- 'genQC.inference.infer_misc.get_rnd_gatepool_subset': ( 'inference/infer_misc.html#get_rnd_gatepool_subset',
- 'genQC/inference/infer_misc.py')},
- 'genQC.inference.infer_srv': { 'genQC.inference.infer_srv.convert_tensors_to_srvs': ( 'inference/infer_srv.html#convert_tensors_to_srvs',
- 'genQC/inference/infer_srv.py'),
- 'genQC.inference.infer_srv.generate_srv_tensors': ( 'inference/infer_srv.html#generate_srv_tensors',
- 'genQC/inference/infer_srv.py'),
- 'genQC.inference.infer_srv.get_all_srvs': ( 'inference/infer_srv.html#get_all_srvs',
- 'genQC/inference/infer_srv.py'),
- 'genQC.inference.infer_srv.get_srv_accuracy': ( 'inference/infer_srv.html#get_srv_accuracy',
- 'genQC/inference/infer_srv.py'),
- 'genQC.inference.infer_srv.plot_guidance_dep': ( 'inference/infer_srv.html#plot_guidance_dep',
- 'genQC/inference/infer_srv.py'),
- 'genQC.inference.infer_srv.plot_srv_acc_vs_length': ( 'inference/infer_srv.html#plot_srv_acc_vs_length',
- 'genQC/inference/infer_srv.py'),
- 'genQC.inference.infer_srv.plot_srv_acc_vs_maxLength': ( 'inference/infer_srv.html#plot_srv_acc_vs_maxlength',
- 'genQC/inference/infer_srv.py'),
- 'genQC.inference.infer_srv.plot_srv_clr_distribution_bin_accuracy': ( 'inference/infer_srv.html#plot_srv_clr_distribution_bin_accuracy',
- 'genQC/inference/infer_srv.py'),
- 'genQC.inference.infer_srv.plot_srv_clr_distribution_hist': ( 'inference/infer_srv.html#plot_srv_clr_distribution_hist',
- 'genQC/inference/infer_srv.py'),
- 'genQC.inference.infer_srv.test_guidance_dep': ( 'inference/infer_srv.html#test_guidance_dep',
- 'genQC/inference/infer_srv.py'),
- 'genQC.inference.infer_srv.test_srv_acc_vs_length': ( 'inference/infer_srv.html#test_srv_acc_vs_length',
- 'genQC/inference/infer_srv.py'),
- 'genQC.inference.infer_srv.test_srv_acc_vs_maxLength': ( 'inference/infer_srv.html#test_srv_acc_vs_maxlength',
- 'genQC/inference/infer_srv.py'),
- 'genQC.inference.infer_srv.test_srv_clr_distribution': ( 'inference/infer_srv.html#test_srv_clr_distribution',
- 'genQC/inference/infer_srv.py'),
- 'genQC.inference.infer_srv.test_srv_clr_distribution_bin_samples': ( 'inference/infer_srv.html#test_srv_clr_distribution_bin_samples',
- 'genQC/inference/infer_srv.py'),
- 'genQC.inference.infer_srv.test_srv_length_distribution': ( 'inference/infer_srv.html#test_srv_length_distribution',
- 'genQC/inference/infer_srv.py'),
- 'genQC.inference.infer_srv.true_sample_bin_dist': ( 'inference/infer_srv.html#true_sample_bin_dist',
- 'genQC/inference/infer_srv.py')},
- 'genQC.metrics': { 'genQC.metrics.Accuracy': ('metrics.html#accuracy', 'genQC/metrics.py'),
- 'genQC.metrics.Accuracy._eval': ('metrics.html#accuracy._eval', 'genQC/metrics.py'),
- 'genQC.metrics.Mean': ('metrics.html#mean', 'genQC/metrics.py'),
- 'genQC.metrics.Mean.__init__': ('metrics.html#mean.__init__', 'genQC/metrics.py'),
- 'genQC.metrics.Mean._eval': ('metrics.html#mean._eval', 'genQC/metrics.py'),
- 'genQC.metrics.Mean.reset_state': ('metrics.html#mean.reset_state', 'genQC/metrics.py'),
- 'genQC.metrics.Mean.result': ('metrics.html#mean.result', 'genQC/metrics.py'),
- 'genQC.metrics.Mean.update_state': ('metrics.html#mean.update_state', 'genQC/metrics.py'),
- 'genQC.metrics.Metric': ('metrics.html#metric', 'genQC/metrics.py'),
- 'genQC.metrics.Metric.__init__': ('metrics.html#metric.__init__', 'genQC/metrics.py'),
- 'genQC.metrics.Metric.__repr__': ('metrics.html#metric.__repr__', 'genQC/metrics.py'),
- 'genQC.metrics.Metric._eval': ('metrics.html#metric._eval', 'genQC/metrics.py'),
- 'genQC.metrics.Metric.reset_state': ('metrics.html#metric.reset_state', 'genQC/metrics.py'),
- 'genQC.metrics.Metric.result': ('metrics.html#metric.result', 'genQC/metrics.py'),
- 'genQC.metrics.Metric.update_state': ('metrics.html#metric.update_state', 'genQC/metrics.py')},
- 'genQC.models.config_model': { 'genQC.models.config_model.Config_Model': ( 'models/config_model.html#config_model',
- 'genQC/models/config_model.py'),
- 'genQC.models.config_model.Config_Model.__init__': ( 'models/config_model.html#config_model.__init__',
- 'genQC/models/config_model.py'),
- 'genQC.models.config_model.Config_Model.from_config': ( 'models/config_model.html#config_model.from_config',
- 'genQC/models/config_model.py'),
- 'genQC.models.config_model.Config_Model.from_config_file': ( 'models/config_model.html#config_model.from_config_file',
- 'genQC/models/config_model.py'),
- 'genQC.models.config_model.Config_Model.get_config': ( 'models/config_model.html#config_model.get_config',
+ 'genQC.inference.eval_metrics': { 'genQC.inference.eval_metrics.BaseNorm': ( 'inference/eval_metrics.html#basenorm',
+ 'genQC/inference/eval_metrics.py'),
+ 'genQC.inference.eval_metrics.BaseNorm.distance': ( 'inference/eval_metrics.html#basenorm.distance',
+ 'genQC/inference/eval_metrics.py'),
+ 'genQC.inference.eval_metrics.BaseNorm.name': ( 'inference/eval_metrics.html#basenorm.name',
+ 'genQC/inference/eval_metrics.py'),
+ 'genQC.inference.eval_metrics.UnitaryFrobeniusNorm': ( 'inference/eval_metrics.html#unitaryfrobeniusnorm',
+ 'genQC/inference/eval_metrics.py'),
+ 'genQC.inference.eval_metrics.UnitaryFrobeniusNorm.__call__': ( 'inference/eval_metrics.html#unitaryfrobeniusnorm.__call__',
+ 'genQC/inference/eval_metrics.py'),
+ 'genQC.inference.eval_metrics.UnitaryFrobeniusNorm.distance': ( 'inference/eval_metrics.html#unitaryfrobeniusnorm.distance',
+ 'genQC/inference/eval_metrics.py'),
+ 'genQC.inference.eval_metrics.UnitaryFrobeniusNorm.name': ( 'inference/eval_metrics.html#unitaryfrobeniusnorm.name',
+ 'genQC/inference/eval_metrics.py'),
+ 'genQC.inference.eval_metrics.UnitaryInfidelityNorm': ( 'inference/eval_metrics.html#unitaryinfidelitynorm',
+ 'genQC/inference/eval_metrics.py'),
+ 'genQC.inference.eval_metrics.UnitaryInfidelityNorm.__call__': ( 'inference/eval_metrics.html#unitaryinfidelitynorm.__call__',
+ 'genQC/inference/eval_metrics.py'),
+ 'genQC.inference.eval_metrics.UnitaryInfidelityNorm.distance': ( 'inference/eval_metrics.html#unitaryinfidelitynorm.distance',
+ 'genQC/inference/eval_metrics.py'),
+ 'genQC.inference.eval_metrics.UnitaryInfidelityNorm.name': ( 'inference/eval_metrics.html#unitaryinfidelitynorm.name',
+ 'genQC/inference/eval_metrics.py')},
+ 'genQC.inference.evaluation_helper': { 'genQC.inference.evaluation_helper.get_srvs': ( 'inference/evaluation_helper.html#get_srvs',
+ 'genQC/inference/evaluation_helper.py'),
+ 'genQC.inference.evaluation_helper.get_unitaries': ( 'inference/evaluation_helper.html#get_unitaries',
+ 'genQC/inference/evaluation_helper.py')},
+ 'genQC.inference.sampling': { 'genQC.inference.sampling.batched_sampling': ( 'inference/sampling.html#batched_sampling',
+ 'genQC/inference/sampling.py'),
+ 'genQC.inference.sampling.decode_tensors_to_backend': ( 'inference/sampling.html#decode_tensors_to_backend',
+ 'genQC/inference/sampling.py'),
+ 'genQC.inference.sampling.generate_compilation_tensors': ( 'inference/sampling.html#generate_compilation_tensors',
+ 'genQC/inference/sampling.py'),
+ 'genQC.inference.sampling.generate_tensors': ( 'inference/sampling.html#generate_tensors',
+ 'genQC/inference/sampling.py'),
+ 'genQC.inference.sampling.get_batch_samples': ( 'inference/sampling.html#get_batch_samples',
+ 'genQC/inference/sampling.py'),
+ 'genQC.inference.sampling.prepare_prompts': ( 'inference/sampling.html#prepare_prompts',
+ 'genQC/inference/sampling.py')},
+ 'genQC.models.clip.frozen_open_clip': { 'genQC.models.clip.frozen_open_clip.CachedFrozenOpenCLIPEmbedder': ( 'models/clip/frozen_open_clip.html#cachedfrozenopenclipembedder',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.CachedFrozenOpenCLIPEmbedder.__init__': ( 'models/clip/frozen_open_clip.html#cachedfrozenopenclipembedder.__init__',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.CachedFrozenOpenCLIPEmbedder.forward': ( 'models/clip/frozen_open_clip.html#cachedfrozenopenclipembedder.forward',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.CachedFrozenOpenCLIPEmbedder.generate_cache': ( 'models/clip/frozen_open_clip.html#cachedfrozenopenclipembedder.generate_cache',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.CachedFrozenOpenCLIPEmbedder.get_token_count': ( 'models/clip/frozen_open_clip.html#cachedfrozenopenclipembedder.get_token_count',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.CachedFrozenOpenCLIPEmbedder.look_up_cos_sim_cached_index': ( 'models/clip/frozen_open_clip.html#cachedfrozenopenclipembedder.look_up_cos_sim_cached_index',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.CachedFrozenOpenCLIPEmbedderConfig': ( 'models/clip/frozen_open_clip.html#cachedfrozenopenclipembedderconfig',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.FrozenOpenCLIPEmbedder': ( 'models/clip/frozen_open_clip.html#frozenopenclipembedder',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.FrozenOpenCLIPEmbedder.__init__': ( 'models/clip/frozen_open_clip.html#frozenopenclipembedder.__init__',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.FrozenOpenCLIPEmbedder.encode_with_transformer': ( 'models/clip/frozen_open_clip.html#frozenopenclipembedder.encode_with_transformer',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.FrozenOpenCLIPEmbedder.forward': ( 'models/clip/frozen_open_clip.html#frozenopenclipembedder.forward',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.FrozenOpenCLIPEmbedder.freeze': ( 'models/clip/frozen_open_clip.html#frozenopenclipembedder.freeze',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.FrozenOpenCLIPEmbedder.from_config': ( 'models/clip/frozen_open_clip.html#frozenopenclipembedder.from_config',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.FrozenOpenCLIPEmbedder.get_config': ( 'models/clip/frozen_open_clip.html#frozenopenclipembedder.get_config',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.FrozenOpenCLIPEmbedder.store_model': ( 'models/clip/frozen_open_clip.html#frozenopenclipembedder.store_model',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.FrozenOpenCLIPEmbedder.text_transformer_forward': ( 'models/clip/frozen_open_clip.html#frozenopenclipembedder.text_transformer_forward',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.FrozenOpenCLIPEmbedder.to': ( 'models/clip/frozen_open_clip.html#frozenopenclipembedder.to',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.FrozenOpenCLIPEmbedder.tokenize_and_push_to_device': ( 'models/clip/frozen_open_clip.html#frozenopenclipembedder.tokenize_and_push_to_device',
+ 'genQC/models/clip/frozen_open_clip.py'),
+ 'genQC.models.clip.frozen_open_clip.FrozenOpenCLIPEmbedderConfig': ( 'models/clip/frozen_open_clip.html#frozenopenclipembedderconfig',
+ 'genQC/models/clip/frozen_open_clip.py')},
+ 'genQC.models.clip.unitary_clip': { 'genQC.models.clip.unitary_clip.CircuitEncoder': ( 'models/clip/unitary_clip.html#circuitencoder',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.CircuitEncoder.__init__': ( 'models/clip/unitary_clip.html#circuitencoder.__init__',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.CircuitEncoder._init_weights': ( 'models/clip/unitary_clip.html#circuitencoder._init_weights',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.CircuitEncoder.forward': ( 'models/clip/unitary_clip.html#circuitencoder.forward',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.CircuitEncoderConfig': ( 'models/clip/unitary_clip.html#circuitencoderconfig',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.CoreTransformer': ( 'models/clip/unitary_clip.html#coretransformer',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.CoreTransformer.__init__': ( 'models/clip/unitary_clip.html#coretransformer.__init__',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.CoreTransformer.forward': ( 'models/clip/unitary_clip.html#coretransformer.forward',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.FeedForwardBlock': ( 'models/clip/unitary_clip.html#feedforwardblock',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.FeedForwardBlock.__init__': ( 'models/clip/unitary_clip.html#feedforwardblock.__init__',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.FeedForwardBlock._init_weights': ( 'models/clip/unitary_clip.html#feedforwardblock._init_weights',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.FeedForwardBlock.forward': ( 'models/clip/unitary_clip.html#feedforwardblock.forward',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.FeedForwardBlock.siglu': ( 'models/clip/unitary_clip.html#feedforwardblock.siglu',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.PackingTransformer': ( 'models/clip/unitary_clip.html#packingtransformer',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.PackingTransformer.__init__': ( 'models/clip/unitary_clip.html#packingtransformer.__init__',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.PackingTransformer.forward': ( 'models/clip/unitary_clip.html#packingtransformer.forward',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.RotaryMultiheadAttention': ( 'models/clip/unitary_clip.html#rotarymultiheadattention',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.RotaryMultiheadAttention.__init__': ( 'models/clip/unitary_clip.html#rotarymultiheadattention.__init__',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.RotaryMultiheadAttention._init_weights': ( 'models/clip/unitary_clip.html#rotarymultiheadattention._init_weights',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.RotaryMultiheadAttention.forward': ( 'models/clip/unitary_clip.html#rotarymultiheadattention.forward',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.SelfAttnBlock': ( 'models/clip/unitary_clip.html#selfattnblock',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.SelfAttnBlock.__init__': ( 'models/clip/unitary_clip.html#selfattnblock.__init__',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.SelfAttnBlock._init_weights': ( 'models/clip/unitary_clip.html#selfattnblock._init_weights',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.SelfAttnBlock.forward': ( 'models/clip/unitary_clip.html#selfattnblock.forward',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryCLIP': ( 'models/clip/unitary_clip.html#unitaryclip',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryCLIP.__init__': ( 'models/clip/unitary_clip.html#unitaryclip.__init__',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryCLIP._init_weights': ( 'models/clip/unitary_clip.html#unitaryclip._init_weights',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryCLIP.forward': ( 'models/clip/unitary_clip.html#unitaryclip.forward',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryCLIPConfig': ( 'models/clip/unitary_clip.html#unitaryclipconfig',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryEncoderAttnBlock': ( 'models/clip/unitary_clip.html#unitaryencoderattnblock',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryEncoderAttnBlock.__init__': ( 'models/clip/unitary_clip.html#unitaryencoderattnblock.__init__',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryEncoderAttnBlock._init_weights': ( 'models/clip/unitary_clip.html#unitaryencoderattnblock._init_weights',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryEncoderAttnBlock.forward': ( 'models/clip/unitary_clip.html#unitaryencoderattnblock.forward',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryTextEncoder': ( 'models/clip/unitary_clip.html#unitarytextencoder',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryTextEncoder.__init__': ( 'models/clip/unitary_clip.html#unitarytextencoder.__init__',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryTextEncoder._init_weights': ( 'models/clip/unitary_clip.html#unitarytextencoder._init_weights',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryTextEncoder.forward': ( 'models/clip/unitary_clip.html#unitarytextencoder.forward',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryTextEncoder.preproc_text': ( 'models/clip/unitary_clip.html#unitarytextencoder.preproc_text',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryTextEncoder.preproc_unitary': ( 'models/clip/unitary_clip.html#unitarytextencoder.preproc_unitary',
+ 'genQC/models/clip/unitary_clip.py'),
+ 'genQC.models.clip.unitary_clip.UnitaryTextEncoderConfig': ( 'models/clip/unitary_clip.html#unitarytextencoderconfig',
+ 'genQC/models/clip/unitary_clip.py')},
+ 'genQC.models.config_model': { 'genQC.models.config_model.ConfigModel': ( 'models/config_model.html#configmodel',
+ 'genQC/models/config_model.py'),
+ 'genQC.models.config_model.ConfigModel.__init__': ( 'models/config_model.html#configmodel.__init__',
+ 'genQC/models/config_model.py'),
+ 'genQC.models.config_model.ConfigModel.check_save_type': ( 'models/config_model.html#configmodel.check_save_type',
+ 'genQC/models/config_model.py'),
+ 'genQC.models.config_model.ConfigModel.freeze': ( 'models/config_model.html#configmodel.freeze',
+ 'genQC/models/config_model.py'),
+ 'genQC.models.config_model.ConfigModel.from_config': ( 'models/config_model.html#configmodel.from_config',
'genQC/models/config_model.py'),
- 'genQC.models.config_model.Config_Model.store_model': ( 'models/config_model.html#config_model.store_model',
- 'genQC/models/config_model.py')},
+ 'genQC.models.config_model.ConfigModel.from_config_file': ( 'models/config_model.html#configmodel.from_config_file',
+ 'genQC/models/config_model.py'),
+ 'genQC.models.config_model.ConfigModel.get_config': ( 'models/config_model.html#configmodel.get_config',
+ 'genQC/models/config_model.py'),
+ 'genQC.models.config_model.ConfigModel.store_model': ( 'models/config_model.html#configmodel.store_model',
+ 'genQC/models/config_model.py'),
+ 'genQC.models.config_model.ConfigModel.unfreeze': ( 'models/config_model.html#configmodel.unfreeze',
+ 'genQC/models/config_model.py')},
+ 'genQC.models.embedding.base_embedder': { 'genQC.models.embedding.base_embedder.BaseEmbedder': ( 'models/embedding/base_embedder.html#baseembedder',
+ 'genQC/models/embedding/base_embedder.py'),
+ 'genQC.models.embedding.base_embedder.BaseEmbedder.__init__': ( 'models/embedding/base_embedder.html#baseembedder.__init__',
+ 'genQC/models/embedding/base_embedder.py'),
+ 'genQC.models.embedding.base_embedder.BaseEmbedder.embed': ( 'models/embedding/base_embedder.html#baseembedder.embed',
+ 'genQC/models/embedding/base_embedder.py'),
+ 'genQC.models.embedding.base_embedder.BaseEmbedder.forward': ( 'models/embedding/base_embedder.html#baseembedder.forward',
+ 'genQC/models/embedding/base_embedder.py'),
+ 'genQC.models.embedding.base_embedder.BaseEmbedder.invert': ( 'models/embedding/base_embedder.html#baseembedder.invert',
+ 'genQC/models/embedding/base_embedder.py')},
+ 'genQC.models.embedding.rotational_preset_embedder': { 'genQC.models.embedding.rotational_preset_embedder.MultimodialEmbedder': ( 'models/embedding/rotational_preset_embedder.html#multimodialembedder',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialEmbedder.__init__': ( 'models/embedding/rotational_preset_embedder.html#multimodialembedder.__init__',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialEmbedder.invert_scale_emb': ( 'models/embedding/rotational_preset_embedder.html#multimodialembedder.invert_scale_emb',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialEmbedder.scale_emb': ( 'models/embedding/rotational_preset_embedder.html#multimodialembedder.scale_emb',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialEmbedder.set_scaling': ( 'models/embedding/rotational_preset_embedder.html#multimodialembedder.set_scaling',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialPresetEmbedder': ( 'models/embedding/rotational_preset_embedder.html#multimodialpresetembedder',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialPresetEmbedder.__init__': ( 'models/embedding/rotational_preset_embedder.html#multimodialpresetembedder.__init__',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialPresetEmbedder._init_weights': ( 'models/embedding/rotational_preset_embedder.html#multimodialpresetembedder._init_weights',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialPresetEmbedder._prepare_params': ( 'models/embedding/rotational_preset_embedder.html#multimodialpresetembedder._prepare_params',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialPresetEmbedder._reduce_params_spatial': ( 'models/embedding/rotational_preset_embedder.html#multimodialpresetembedder._reduce_params_spatial',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialPresetEmbedder.embed': ( 'models/embedding/rotational_preset_embedder.html#multimodialpresetembedder.embed',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialPresetEmbedder.embed_discrete': ( 'models/embedding/rotational_preset_embedder.html#multimodialpresetembedder.embed_discrete',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialPresetEmbedder.get_discrete_sim': ( 'models/embedding/rotational_preset_embedder.html#multimodialpresetembedder.get_discrete_sim',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialPresetEmbedder.get_parametrized_mask': ( 'models/embedding/rotational_preset_embedder.html#multimodialpresetembedder.get_parametrized_mask',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialPresetEmbedder.invert': ( 'models/embedding/rotational_preset_embedder.html#multimodialpresetembedder.invert',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialPresetEmbedder.invert_discrete': ( 'models/embedding/rotational_preset_embedder.html#multimodialpresetembedder.invert_discrete',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialPresetEmbedder.print_emb_matrix': ( 'models/embedding/rotational_preset_embedder.html#multimodialpresetembedder.print_emb_matrix',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialPresetEmbedder.tokens_to_unique_class_values': ( 'models/embedding/rotational_preset_embedder.html#multimodialpresetembedder.tokens_to_unique_class_values',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialPresetEmbedder.unique_class_values_to_tokens': ( 'models/embedding/rotational_preset_embedder.html#multimodialpresetembedder.unique_class_values_to_tokens',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.MultimodialPresetEmbedderConfig': ( 'models/embedding/rotational_preset_embedder.html#multimodialpresetembedderconfig',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.RotationalMultimodialPresetEmbedder': ( 'models/embedding/rotational_preset_embedder.html#rotationalmultimodialpresetembedder',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.RotationalMultimodialPresetEmbedder.__init__': ( 'models/embedding/rotational_preset_embedder.html#rotationalmultimodialpresetembedder.__init__',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.RotationalMultimodialPresetEmbedder.embed_continuous': ( 'models/embedding/rotational_preset_embedder.html#rotationalmultimodialpresetembedder.embed_continuous',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.RotationalMultimodialPresetEmbedder.invert_continuous': ( 'models/embedding/rotational_preset_embedder.html#rotationalmultimodialpresetembedder.invert_continuous',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.RotationalMultimodialPresetEmbedderTiny': ( 'models/embedding/rotational_preset_embedder.html#rotationalmultimodialpresetembeddertiny',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.RotationalMultimodialPresetEmbedderTiny.__init__': ( 'models/embedding/rotational_preset_embedder.html#rotationalmultimodialpresetembeddertiny.__init__',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.RotationalMultimodialPresetEmbedderTiny.embed_continuous': ( 'models/embedding/rotational_preset_embedder.html#rotationalmultimodialpresetembeddertiny.embed_continuous',
+ 'genQC/models/embedding/rotational_preset_embedder.py'),
+ 'genQC.models.embedding.rotational_preset_embedder.RotationalMultimodialPresetEmbedderTiny.invert_continuous': ( 'models/embedding/rotational_preset_embedder.html#rotationalmultimodialpresetembeddertiny.invert_continuous',
+ 'genQC/models/embedding/rotational_preset_embedder.py')},
'genQC.models.frozen_open_clip': { 'genQC.models.frozen_open_clip.CachedFrozenOpenCLIPEmbedder': ( 'models/frozen_open_clip.html#cachedfrozenopenclipembedder',
'genQC/models/frozen_open_clip.py'),
+ 'genQC.models.frozen_open_clip.CachedFrozenOpenCLIPEmbedder.__init__': ( 'models/frozen_open_clip.html#cachedfrozenopenclipembedder.__init__',
+ 'genQC/models/frozen_open_clip.py'),
'genQC.models.frozen_open_clip.CachedFrozenOpenCLIPEmbedder.forward': ( 'models/frozen_open_clip.html#cachedfrozenopenclipembedder.forward',
'genQC/models/frozen_open_clip.py'),
'genQC.models.frozen_open_clip.CachedFrozenOpenCLIPEmbedder.generate_cache': ( 'models/frozen_open_clip.html#cachedfrozenopenclipembedder.generate_cache',
'genQC/models/frozen_open_clip.py'),
+ 'genQC.models.frozen_open_clip.CachedFrozenOpenCLIPEmbedder.get_token_count': ( 'models/frozen_open_clip.html#cachedfrozenopenclipembedder.get_token_count',
+ 'genQC/models/frozen_open_clip.py'),
'genQC.models.frozen_open_clip.CachedFrozenOpenCLIPEmbedder.look_up_cos_sim_cached_index': ( 'models/frozen_open_clip.html#cachedfrozenopenclipembedder.look_up_cos_sim_cached_index',
'genQC/models/frozen_open_clip.py'),
+ 'genQC.models.frozen_open_clip.CachedFrozenOpenCLIPEmbedderConfig': ( 'models/frozen_open_clip.html#cachedfrozenopenclipembedderconfig',
+ 'genQC/models/frozen_open_clip.py'),
'genQC.models.frozen_open_clip.FrozenOpenCLIPEmbedder': ( 'models/frozen_open_clip.html#frozenopenclipembedder',
'genQC/models/frozen_open_clip.py'),
'genQC.models.frozen_open_clip.FrozenOpenCLIPEmbedder.__init__': ( 'models/frozen_open_clip.html#frozenopenclipembedder.__init__',
@@ -271,8 +424,8 @@
'genQC/models/frozen_open_clip.py'),
'genQC.models.frozen_open_clip.FrozenOpenCLIPEmbedder.tokenize_and_push_to_device': ( 'models/frozen_open_clip.html#frozenopenclipembedder.tokenize_and_push_to_device',
'genQC/models/frozen_open_clip.py'),
- 'genQC.models.frozen_open_clip.FrozenOpenCLIPEmbedder_config': ( 'models/frozen_open_clip.html#frozenopenclipembedder_config',
- 'genQC/models/frozen_open_clip.py')},
+ 'genQC.models.frozen_open_clip.FrozenOpenCLIPEmbedderConfig': ( 'models/frozen_open_clip.html#frozenopenclipembedderconfig',
+ 'genQC/models/frozen_open_clip.py')},
'genQC.models.layers': { 'genQC.models.layers.DownBlock2D': ('models/layers.html#downblock2d', 'genQC/models/layers.py'),
'genQC.models.layers.DownBlock2D.__init__': ( 'models/layers.html#downblock2d.__init__',
'genQC/models/layers.py'),
@@ -312,12 +465,12 @@
'genQC/models/layers.py'),
'genQC.models.layers.ResBlock2D.forward': ( 'models/layers.html#resblock2d.forward',
'genQC/models/layers.py'),
- 'genQC.models.layers.ResBlock2D_Conditional': ( 'models/layers.html#resblock2d_conditional',
- 'genQC/models/layers.py'),
- 'genQC.models.layers.ResBlock2D_Conditional.__init__': ( 'models/layers.html#resblock2d_conditional.__init__',
- 'genQC/models/layers.py'),
- 'genQC.models.layers.ResBlock2D_Conditional.forward': ( 'models/layers.html#resblock2d_conditional.forward',
+ 'genQC.models.layers.ResBlock2DConditional': ( 'models/layers.html#resblock2dconditional',
+ 'genQC/models/layers.py'),
+ 'genQC.models.layers.ResBlock2DConditional.__init__': ( 'models/layers.html#resblock2dconditional.__init__',
'genQC/models/layers.py'),
+ 'genQC.models.layers.ResBlock2DConditional.forward': ( 'models/layers.html#resblock2dconditional.forward',
+ 'genQC/models/layers.py'),
'genQC.models.layers.ResDownBlock2D': ('models/layers.html#resdownblock2d', 'genQC/models/layers.py'),
'genQC.models.layers.ResDownBlock2D.__init__': ( 'models/layers.html#resdownblock2d.__init__',
'genQC/models/layers.py'),
@@ -338,30 +491,170 @@
'genQC/models/layers.py'),
'genQC.models.layers.UpBlock2D.forward': ( 'models/layers.html#upblock2d.forward',
'genQC/models/layers.py')},
- 'genQC.models.transformers': { 'genQC.models.transformers.BasisCrossAttnBlock': ( 'models/transformers.html#basiscrossattnblock',
- 'genQC/models/transformers.py'),
- 'genQC.models.transformers.BasisCrossAttnBlock.__init__': ( 'models/transformers.html#basiscrossattnblock.__init__',
- 'genQC/models/transformers.py'),
- 'genQC.models.transformers.BasisCrossAttnBlock.forward': ( 'models/transformers.html#basiscrossattnblock.forward',
- 'genQC/models/transformers.py'),
- 'genQC.models.transformers.BasisSelfAttnBlock': ( 'models/transformers.html#basisselfattnblock',
- 'genQC/models/transformers.py'),
- 'genQC.models.transformers.BasisSelfAttnBlock.__init__': ( 'models/transformers.html#basisselfattnblock.__init__',
- 'genQC/models/transformers.py'),
- 'genQC.models.transformers.BasisSelfAttnBlock.forward': ( 'models/transformers.html#basisselfattnblock.forward',
- 'genQC/models/transformers.py'),
- 'genQC.models.transformers.SpatialTransformer': ( 'models/transformers.html#spatialtransformer',
- 'genQC/models/transformers.py'),
- 'genQC.models.transformers.SpatialTransformer.__init__': ( 'models/transformers.html#spatialtransformer.__init__',
- 'genQC/models/transformers.py'),
- 'genQC.models.transformers.SpatialTransformer.forward': ( 'models/transformers.html#spatialtransformer.forward',
- 'genQC/models/transformers.py'),
- 'genQC.models.transformers.SpatialTransformerSelfAttn': ( 'models/transformers.html#spatialtransformerselfattn',
- 'genQC/models/transformers.py'),
- 'genQC.models.transformers.SpatialTransformerSelfAttn.__init__': ( 'models/transformers.html#spatialtransformerselfattn.__init__',
- 'genQC/models/transformers.py'),
- 'genQC.models.transformers.SpatialTransformerSelfAttn.forward': ( 'models/transformers.html#spatialtransformerselfattn.forward',
- 'genQC/models/transformers.py')},
+ 'genQC.models.position_encoding': { 'genQC.models.position_encoding.LearnedPositionalEmbedding': ( 'models/position_encoding.html#learnedpositionalembedding',
+ 'genQC/models/position_encoding.py'),
+ 'genQC.models.position_encoding.LearnedPositionalEmbedding.__init__': ( 'models/position_encoding.html#learnedpositionalembedding.__init__',
+ 'genQC/models/position_encoding.py'),
+ 'genQC.models.position_encoding.LearnedPositionalEmbedding._init_weights': ( 'models/position_encoding.html#learnedpositionalembedding._init_weights',
+ 'genQC/models/position_encoding.py'),
+ 'genQC.models.position_encoding.LearnedPositionalEmbedding.forward': ( 'models/position_encoding.html#learnedpositionalembedding.forward',
+ 'genQC/models/position_encoding.py'),
+ 'genQC.models.position_encoding.RotaryPositionalEmbedding': ( 'models/position_encoding.html#rotarypositionalembedding',
+ 'genQC/models/position_encoding.py'),
+ 'genQC.models.position_encoding.RotaryPositionalEmbedding.__init__': ( 'models/position_encoding.html#rotarypositionalembedding.__init__',
+ 'genQC/models/position_encoding.py'),
+ 'genQC.models.position_encoding.RotaryPositionalEmbedding.forward': ( 'models/position_encoding.html#rotarypositionalembedding.forward',
+ 'genQC/models/position_encoding.py'),
+ 'genQC.models.position_encoding.RotaryPositionalEmbedding.rebuild_rope_cache': ( 'models/position_encoding.html#rotarypositionalembedding.rebuild_rope_cache',
+ 'genQC/models/position_encoding.py'),
+ 'genQC.models.position_encoding.RotaryPositionalEmbedding2D': ( 'models/position_encoding.html#rotarypositionalembedding2d',
+ 'genQC/models/position_encoding.py'),
+ 'genQC.models.position_encoding.RotaryPositionalEmbedding2D.__init__': ( 'models/position_encoding.html#rotarypositionalembedding2d.__init__',
+ 'genQC/models/position_encoding.py'),
+ 'genQC.models.position_encoding.RotaryPositionalEmbedding2D.forward': ( 'models/position_encoding.html#rotarypositionalembedding2d.forward',
+ 'genQC/models/position_encoding.py')},
+ 'genQC.models.transformers.attention': { 'genQC.models.transformers.attention.BasisCrossAttnBlock': ( 'models/transformers/attention.html#basiscrossattnblock',
+ 'genQC/models/transformers/attention.py'),
+ 'genQC.models.transformers.attention.BasisCrossAttnBlock.__init__': ( 'models/transformers/attention.html#basiscrossattnblock.__init__',
+ 'genQC/models/transformers/attention.py'),
+ 'genQC.models.transformers.attention.BasisCrossAttnBlock.forward': ( 'models/transformers/attention.html#basiscrossattnblock.forward',
+ 'genQC/models/transformers/attention.py'),
+ 'genQC.models.transformers.attention.BasisSelfAttnBlock': ( 'models/transformers/attention.html#basisselfattnblock',
+ 'genQC/models/transformers/attention.py'),
+ 'genQC.models.transformers.attention.BasisSelfAttnBlock.__init__': ( 'models/transformers/attention.html#basisselfattnblock.__init__',
+ 'genQC/models/transformers/attention.py'),
+ 'genQC.models.transformers.attention.BasisSelfAttnBlock.forward': ( 'models/transformers/attention.html#basisselfattnblock.forward',
+ 'genQC/models/transformers/attention.py'),
+ 'genQC.models.transformers.attention.FeedForwardBlock': ( 'models/transformers/attention.html#feedforwardblock',
+ 'genQC/models/transformers/attention.py'),
+ 'genQC.models.transformers.attention.FeedForwardBlock.__init__': ( 'models/transformers/attention.html#feedforwardblock.__init__',
+ 'genQC/models/transformers/attention.py'),
+ 'genQC.models.transformers.attention.FeedForwardBlock.forward': ( 'models/transformers/attention.html#feedforwardblock.forward',
+ 'genQC/models/transformers/attention.py'),
+ 'genQC.models.transformers.attention.FeedForwardBlock.siglu': ( 'models/transformers/attention.html#feedforwardblock.siglu',
+ 'genQC/models/transformers/attention.py'),
+ 'genQC.models.transformers.attention.SpatialTransformer': ( 'models/transformers/attention.html#spatialtransformer',
+ 'genQC/models/transformers/attention.py'),
+ 'genQC.models.transformers.attention.SpatialTransformer.__init__': ( 'models/transformers/attention.html#spatialtransformer.__init__',
+ 'genQC/models/transformers/attention.py'),
+ 'genQC.models.transformers.attention.SpatialTransformer.forward': ( 'models/transformers/attention.html#spatialtransformer.forward',
+ 'genQC/models/transformers/attention.py'),
+ 'genQC.models.transformers.attention.SpatialTransformerSelfAttn': ( 'models/transformers/attention.html#spatialtransformerselfattn',
+ 'genQC/models/transformers/attention.py'),
+ 'genQC.models.transformers.attention.SpatialTransformerSelfAttn.__init__': ( 'models/transformers/attention.html#spatialtransformerselfattn.__init__',
+ 'genQC/models/transformers/attention.py'),
+ 'genQC.models.transformers.attention.SpatialTransformerSelfAttn.forward': ( 'models/transformers/attention.html#spatialtransformerselfattn.forward',
+ 'genQC/models/transformers/attention.py')},
+ 'genQC.models.transformers.cirdit_multimodal': { 'genQC.models.transformers.cirdit_multimodal.AdaptiveSelfAttnBlock': ( 'models/transformers/cirdit_multimodal.html#adaptiveselfattnblock',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.AdaptiveSelfAttnBlock.__init__': ( 'models/transformers/cirdit_multimodal.html#adaptiveselfattnblock.__init__',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.AdaptiveSelfAttnBlock._init_weights': ( 'models/transformers/cirdit_multimodal.html#adaptiveselfattnblock._init_weights',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.AdaptiveSelfAttnBlock.forward': ( 'models/transformers/cirdit_multimodal.html#adaptiveselfattnblock.forward',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.CirDiT': ( 'models/transformers/cirdit_multimodal.html#cirdit',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.CirDiT.__init__': ( 'models/transformers/cirdit_multimodal.html#cirdit.__init__',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.CirDiT._init_weights': ( 'models/transformers/cirdit_multimodal.html#cirdit._init_weights',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.CirDiT.forward': ( 'models/transformers/cirdit_multimodal.html#cirdit.forward',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.CirDiT.main_pass': ( 'models/transformers/cirdit_multimodal.html#cirdit.main_pass',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.CirDiTConfig': ( 'models/transformers/cirdit_multimodal.html#cirditconfig',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.CoreTransformer': ( 'models/transformers/cirdit_multimodal.html#coretransformer',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.CoreTransformer.__init__': ( 'models/transformers/cirdit_multimodal.html#coretransformer.__init__',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.CoreTransformer.forward': ( 'models/transformers/cirdit_multimodal.html#coretransformer.forward',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.CrossAttnBlock': ( 'models/transformers/cirdit_multimodal.html#crossattnblock',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.CrossAttnBlock.__init__': ( 'models/transformers/cirdit_multimodal.html#crossattnblock.__init__',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.CrossAttnBlock._init_weights': ( 'models/transformers/cirdit_multimodal.html#crossattnblock._init_weights',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.CrossAttnBlock.forward': ( 'models/transformers/cirdit_multimodal.html#crossattnblock.forward',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.FeedForwardBlock': ( 'models/transformers/cirdit_multimodal.html#feedforwardblock',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.FeedForwardBlock.__init__': ( 'models/transformers/cirdit_multimodal.html#feedforwardblock.__init__',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.FeedForwardBlock._init_weights': ( 'models/transformers/cirdit_multimodal.html#feedforwardblock._init_weights',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.FeedForwardBlock.forward': ( 'models/transformers/cirdit_multimodal.html#feedforwardblock.forward',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.FeedForwardBlock.siglu': ( 'models/transformers/cirdit_multimodal.html#feedforwardblock.siglu',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.PackingTransformer': ( 'models/transformers/cirdit_multimodal.html#packingtransformer',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.PackingTransformer.__init__': ( 'models/transformers/cirdit_multimodal.html#packingtransformer.__init__',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.PackingTransformer.forward': ( 'models/transformers/cirdit_multimodal.html#packingtransformer.forward',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.RotaryMultiheadAttention': ( 'models/transformers/cirdit_multimodal.html#rotarymultiheadattention',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.RotaryMultiheadAttention.__init__': ( 'models/transformers/cirdit_multimodal.html#rotarymultiheadattention.__init__',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.RotaryMultiheadAttention._init_weights': ( 'models/transformers/cirdit_multimodal.html#rotarymultiheadattention._init_weights',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.RotaryMultiheadAttention.forward': ( 'models/transformers/cirdit_multimodal.html#rotarymultiheadattention.forward',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.SelfAttnBlock': ( 'models/transformers/cirdit_multimodal.html#selfattnblock',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.SelfAttnBlock.__init__': ( 'models/transformers/cirdit_multimodal.html#selfattnblock.__init__',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.SelfAttnBlock._init_weights': ( 'models/transformers/cirdit_multimodal.html#selfattnblock._init_weights',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.SelfAttnBlock.forward': ( 'models/transformers/cirdit_multimodal.html#selfattnblock.forward',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.TimeEmbedding': ( 'models/transformers/cirdit_multimodal.html#timeembedding',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.TimeEmbedding.__init__': ( 'models/transformers/cirdit_multimodal.html#timeembedding.__init__',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.TimeEmbedding.forward': ( 'models/transformers/cirdit_multimodal.html#timeembedding.forward',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.UnitaryCLIPPartialNoiseCompilationCirDiT': ( 'models/transformers/cirdit_multimodal.html#unitaryclippartialnoisecompilationcirdit',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.UnitaryCLIPPartialNoiseCompilationCirDiT.__init__': ( 'models/transformers/cirdit_multimodal.html#unitaryclippartialnoisecompilationcirdit.__init__',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.UnitaryCLIPPartialNoiseCompilationCirDiT.forward': ( 'models/transformers/cirdit_multimodal.html#unitaryclippartialnoisecompilationcirdit.forward',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.UnitaryCLIPPartialNoiseCompilationCirDiTConfig': ( 'models/transformers/cirdit_multimodal.html#unitaryclippartialnoisecompilationcirditconfig',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.UnpackingTransformer': ( 'models/transformers/cirdit_multimodal.html#unpackingtransformer',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.UnpackingTransformer.__init__': ( 'models/transformers/cirdit_multimodal.html#unpackingtransformer.__init__',
+ 'genQC/models/transformers/cirdit_multimodal.py'),
+ 'genQC.models.transformers.cirdit_multimodal.UnpackingTransformer.forward': ( 'models/transformers/cirdit_multimodal.html#unpackingtransformer.forward',
+ 'genQC/models/transformers/cirdit_multimodal.py')},
+ 'genQC.models.transformers.transformers': { 'genQC.models.transformers.transformers.BasisCrossAttnBlock': ( 'models/transformers/transformers.html#basiscrossattnblock',
+ 'genQC/models/transformers/transformers.py'),
+ 'genQC.models.transformers.transformers.BasisCrossAttnBlock.__init__': ( 'models/transformers/transformers.html#basiscrossattnblock.__init__',
+ 'genQC/models/transformers/transformers.py'),
+ 'genQC.models.transformers.transformers.BasisCrossAttnBlock.forward': ( 'models/transformers/transformers.html#basiscrossattnblock.forward',
+ 'genQC/models/transformers/transformers.py'),
+ 'genQC.models.transformers.transformers.BasisSelfAttnBlock': ( 'models/transformers/transformers.html#basisselfattnblock',
+ 'genQC/models/transformers/transformers.py'),
+ 'genQC.models.transformers.transformers.BasisSelfAttnBlock.__init__': ( 'models/transformers/transformers.html#basisselfattnblock.__init__',
+ 'genQC/models/transformers/transformers.py'),
+ 'genQC.models.transformers.transformers.BasisSelfAttnBlock.forward': ( 'models/transformers/transformers.html#basisselfattnblock.forward',
+ 'genQC/models/transformers/transformers.py'),
+ 'genQC.models.transformers.transformers.SpatialTransformer': ( 'models/transformers/transformers.html#spatialtransformer',
+ 'genQC/models/transformers/transformers.py'),
+ 'genQC.models.transformers.transformers.SpatialTransformer.__init__': ( 'models/transformers/transformers.html#spatialtransformer.__init__',
+ 'genQC/models/transformers/transformers.py'),
+ 'genQC.models.transformers.transformers.SpatialTransformer.forward': ( 'models/transformers/transformers.html#spatialtransformer.forward',
+ 'genQC/models/transformers/transformers.py'),
+ 'genQC.models.transformers.transformers.SpatialTransformerSelfAttn': ( 'models/transformers/transformers.html#spatialtransformerselfattn',
+ 'genQC/models/transformers/transformers.py'),
+ 'genQC.models.transformers.transformers.SpatialTransformerSelfAttn.__init__': ( 'models/transformers/transformers.html#spatialtransformerselfattn.__init__',
+ 'genQC/models/transformers/transformers.py'),
+ 'genQC.models.transformers.transformers.SpatialTransformerSelfAttn.forward': ( 'models/transformers/transformers.html#spatialtransformerselfattn.forward',
+ 'genQC/models/transformers/transformers.py')},
'genQC.models.unet_qc': { 'genQC.models.unet_qc.Decoder': ('models/unet_qc.html#decoder', 'genQC/models/unet_qc.py'),
'genQC.models.unet_qc.Decoder.__init__': ( 'models/unet_qc.html#decoder.__init__',
'genQC/models/unet_qc.py'),
@@ -385,12 +678,12 @@
'genQC/models/unet_qc.py'),
'genQC.models.unet_qc.QC_Cond_UNet._init_weights': ( 'models/unet_qc.html#qc_cond_unet._init_weights',
'genQC/models/unet_qc.py'),
- 'genQC.models.unet_qc.QC_Cond_UNet.embedd_clrs': ( 'models/unet_qc.html#qc_cond_unet.embedd_clrs',
- 'genQC/models/unet_qc.py'),
+ 'genQC.models.unet_qc.QC_Cond_UNet.embed': ( 'models/unet_qc.html#qc_cond_unet.embed',
+ 'genQC/models/unet_qc.py'),
'genQC.models.unet_qc.QC_Cond_UNet.forward': ( 'models/unet_qc.html#qc_cond_unet.forward',
'genQC/models/unet_qc.py'),
- 'genQC.models.unet_qc.QC_Cond_UNet.invert_clr': ( 'models/unet_qc.html#qc_cond_unet.invert_clr',
- 'genQC/models/unet_qc.py'),
+ 'genQC.models.unet_qc.QC_Cond_UNet.invert': ( 'models/unet_qc.html#qc_cond_unet.invert',
+ 'genQC/models/unet_qc.py'),
'genQC.models.unet_qc.QC_Cond_UNet_config': ( 'models/unet_qc.html#qc_cond_unet_config',
'genQC/models/unet_qc.py'),
'genQC.models.unet_qc.UNet_block': ('models/unet_qc.html#unet_block', 'genQC/models/unet_qc.py'),
@@ -410,6 +703,30 @@
'genQC/models/unitary_encoder.py'),
'genQC.models.unitary_encoder.Unitary_encoder_config': ( 'models/unitary_encoder.html#unitary_encoder_config',
'genQC/models/unitary_encoder.py')},
+ 'genQC.pipeline.callbacks': { 'genQC.pipeline.callbacks.Callback': ( 'pipeline/callbacks.html#callback',
+ 'genQC/pipeline/callbacks.py'),
+ 'genQC.pipeline.callbacks.CancelBatchException': ( 'pipeline/callbacks.html#cancelbatchexception',
+ 'genQC/pipeline/callbacks.py'),
+ 'genQC.pipeline.callbacks.CancelEpochException': ( 'pipeline/callbacks.html#cancelepochexception',
+ 'genQC/pipeline/callbacks.py'),
+ 'genQC.pipeline.callbacks.CancelFitException': ( 'pipeline/callbacks.html#cancelfitexception',
+ 'genQC/pipeline/callbacks.py'),
+ 'genQC.pipeline.callbacks.run_cbs': ( 'pipeline/callbacks.html#run_cbs',
+ 'genQC/pipeline/callbacks.py')},
+ 'genQC.pipeline.compilation_diffusion_pipeline': { 'genQC.pipeline.compilation_diffusion_pipeline.DiffusionPipeline_Compilation': ( 'pipeline/compilation_diffusion_pipeline.html#diffusionpipeline_compilation',
+ 'genQC/pipeline/compilation_diffusion_pipeline.py'),
+ 'genQC.pipeline.compilation_diffusion_pipeline.DiffusionPipeline_Compilation.__call__': ( 'pipeline/compilation_diffusion_pipeline.html#diffusionpipeline_compilation.__call__',
+ 'genQC/pipeline/compilation_diffusion_pipeline.py'),
+ 'genQC.pipeline.compilation_diffusion_pipeline.DiffusionPipeline_Compilation.denoising': ( 'pipeline/compilation_diffusion_pipeline.html#diffusionpipeline_compilation.denoising',
+ 'genQC/pipeline/compilation_diffusion_pipeline.py'),
+ 'genQC.pipeline.compilation_diffusion_pipeline.DiffusionPipeline_Compilation.denoising_step': ( 'pipeline/compilation_diffusion_pipeline.html#diffusionpipeline_compilation.denoising_step',
+ 'genQC/pipeline/compilation_diffusion_pipeline.py'),
+ 'genQC.pipeline.compilation_diffusion_pipeline.DiffusionPipeline_Compilation.empty_unitary_fn': ( 'pipeline/compilation_diffusion_pipeline.html#diffusionpipeline_compilation.empty_unitary_fn',
+ 'genQC/pipeline/compilation_diffusion_pipeline.py'),
+ 'genQC.pipeline.compilation_diffusion_pipeline.DiffusionPipeline_Compilation.get_guidance_U': ( 'pipeline/compilation_diffusion_pipeline.html#diffusionpipeline_compilation.get_guidance_u',
+ 'genQC/pipeline/compilation_diffusion_pipeline.py'),
+ 'genQC.pipeline.compilation_diffusion_pipeline.DiffusionPipeline_Compilation.train_step': ( 'pipeline/compilation_diffusion_pipeline.html#diffusionpipeline_compilation.train_step',
+ 'genQC/pipeline/compilation_diffusion_pipeline.py')},
'genQC.pipeline.diffusion_pipeline': { 'genQC.pipeline.diffusion_pipeline.DiffusionPipeline': ( 'pipeline/diffusion_pipeline.html#diffusionpipeline',
'genQC/pipeline/diffusion_pipeline.py'),
'genQC.pipeline.diffusion_pipeline.DiffusionPipeline.CFG': ( 'pipeline/diffusion_pipeline.html#diffusionpipeline.cfg',
@@ -418,14 +735,14 @@
'genQC/pipeline/diffusion_pipeline.py'),
'genQC.pipeline.diffusion_pipeline.DiffusionPipeline.__init__': ( 'pipeline/diffusion_pipeline.html#diffusionpipeline.__init__',
'genQC/pipeline/diffusion_pipeline.py'),
+ 'genQC.pipeline.diffusion_pipeline.DiffusionPipeline.cfg_drop': ( 'pipeline/diffusion_pipeline.html#diffusionpipeline.cfg_drop',
+ 'genQC/pipeline/diffusion_pipeline.py'),
'genQC.pipeline.diffusion_pipeline.DiffusionPipeline.denoising': ( 'pipeline/diffusion_pipeline.html#diffusionpipeline.denoising',
'genQC/pipeline/diffusion_pipeline.py'),
'genQC.pipeline.diffusion_pipeline.DiffusionPipeline.denoising_step': ( 'pipeline/diffusion_pipeline.html#diffusionpipeline.denoising_step',
'genQC/pipeline/diffusion_pipeline.py'),
'genQC.pipeline.diffusion_pipeline.DiffusionPipeline.from_config_file': ( 'pipeline/diffusion_pipeline.html#diffusionpipeline.from_config_file',
'genQC/pipeline/diffusion_pipeline.py'),
- 'genQC.pipeline.diffusion_pipeline.DiffusionPipeline.from_pretrained': ( 'pipeline/diffusion_pipeline.html#diffusionpipeline.from_pretrained',
- 'genQC/pipeline/diffusion_pipeline.py'),
'genQC.pipeline.diffusion_pipeline.DiffusionPipeline.get_guidance_condition': ( 'pipeline/diffusion_pipeline.html#diffusionpipeline.get_guidance_condition',
'genQC/pipeline/diffusion_pipeline.py'),
'genQC.pipeline.diffusion_pipeline.DiffusionPipeline.latent_filling': ( 'pipeline/diffusion_pipeline.html#diffusionpipeline.latent_filling',
@@ -434,6 +751,8 @@
'genQC/pipeline/diffusion_pipeline.py'),
'genQC.pipeline.diffusion_pipeline.DiffusionPipeline.prepare_c_emb': ( 'pipeline/diffusion_pipeline.html#diffusionpipeline.prepare_c_emb',
'genQC/pipeline/diffusion_pipeline.py'),
+ 'genQC.pipeline.diffusion_pipeline.DiffusionPipeline.sample_timesteps_low_variance': ( 'pipeline/diffusion_pipeline.html#diffusionpipeline.sample_timesteps_low_variance',
+ 'genQC/pipeline/diffusion_pipeline.py'),
'genQC.pipeline.diffusion_pipeline.DiffusionPipeline.store_pipeline': ( 'pipeline/diffusion_pipeline.html#diffusionpipeline.store_pipeline',
'genQC/pipeline/diffusion_pipeline.py'),
'genQC.pipeline.diffusion_pipeline.DiffusionPipeline.train_on_epoch': ( 'pipeline/diffusion_pipeline.html#diffusionpipeline.train_on_epoch',
@@ -451,17 +770,68 @@
'genQC.pipeline.diffusion_pipeline_special.DiffusionPipeline_Compilation.get_guidance_U': ( 'pipeline/diffusion_pipeline_special.html#diffusionpipeline_compilation.get_guidance_u',
'genQC/pipeline/diffusion_pipeline_special.py'),
'genQC.pipeline.diffusion_pipeline_special.DiffusionPipeline_Compilation.train_step': ( 'pipeline/diffusion_pipeline_special.html#diffusionpipeline_compilation.train_step',
- 'genQC/pipeline/diffusion_pipeline_special.py'),
- 'genQC.pipeline.diffusion_pipeline_special.DiffusionPipeline_attnPadded': ( 'pipeline/diffusion_pipeline_special.html#diffusionpipeline_attnpadded',
- 'genQC/pipeline/diffusion_pipeline_special.py'),
- 'genQC.pipeline.diffusion_pipeline_special.DiffusionPipeline_attnPadded.train_step': ( 'pipeline/diffusion_pipeline_special.html#diffusionpipeline_attnpadded.train_step',
- 'genQC/pipeline/diffusion_pipeline_special.py')},
- 'genQC.pipeline.pipeline': { 'genQC.pipeline.pipeline.Pipeline': ( 'pipeline/pipeline.html#pipeline',
+ 'genQC/pipeline/diffusion_pipeline_special.py')},
+ 'genQC.pipeline.metrics': { 'genQC.pipeline.metrics.Accuracy': ('pipeline/metrics.html#accuracy', 'genQC/pipeline/metrics.py'),
+ 'genQC.pipeline.metrics.Accuracy._eval': ( 'pipeline/metrics.html#accuracy._eval',
+ 'genQC/pipeline/metrics.py'),
+ 'genQC.pipeline.metrics.Mean': ('pipeline/metrics.html#mean', 'genQC/pipeline/metrics.py'),
+ 'genQC.pipeline.metrics.Mean.__init__': ( 'pipeline/metrics.html#mean.__init__',
+ 'genQC/pipeline/metrics.py'),
+ 'genQC.pipeline.metrics.Mean._eval': ( 'pipeline/metrics.html#mean._eval',
+ 'genQC/pipeline/metrics.py'),
+ 'genQC.pipeline.metrics.Mean.reset_state': ( 'pipeline/metrics.html#mean.reset_state',
+ 'genQC/pipeline/metrics.py'),
+ 'genQC.pipeline.metrics.Mean.result': ( 'pipeline/metrics.html#mean.result',
+ 'genQC/pipeline/metrics.py'),
+ 'genQC.pipeline.metrics.Mean.update_state': ( 'pipeline/metrics.html#mean.update_state',
+ 'genQC/pipeline/metrics.py'),
+ 'genQC.pipeline.metrics.Metric': ('pipeline/metrics.html#metric', 'genQC/pipeline/metrics.py'),
+ 'genQC.pipeline.metrics.Metric.__init__': ( 'pipeline/metrics.html#metric.__init__',
+ 'genQC/pipeline/metrics.py'),
+ 'genQC.pipeline.metrics.Metric.__repr__': ( 'pipeline/metrics.html#metric.__repr__',
+ 'genQC/pipeline/metrics.py'),
+ 'genQC.pipeline.metrics.Metric._eval': ( 'pipeline/metrics.html#metric._eval',
+ 'genQC/pipeline/metrics.py'),
+ 'genQC.pipeline.metrics.Metric.reset_state': ( 'pipeline/metrics.html#metric.reset_state',
+ 'genQC/pipeline/metrics.py'),
+ 'genQC.pipeline.metrics.Metric.result': ( 'pipeline/metrics.html#metric.result',
+ 'genQC/pipeline/metrics.py'),
+ 'genQC.pipeline.metrics.Metric.update_state': ( 'pipeline/metrics.html#metric.update_state',
+ 'genQC/pipeline/metrics.py')},
+ 'genQC.pipeline.multimodal_diffusion_pipeline': { 'genQC.pipeline.multimodal_diffusion_pipeline.MultimodalDiffusionPipeline_ParametrizedCompilation': ( 'pipeline/multimodal_diffusion_pipeline.html#multimodaldiffusionpipeline_parametrizedcompilation',
+ 'genQC/pipeline/multimodal_diffusion_pipeline.py'),
+ 'genQC.pipeline.multimodal_diffusion_pipeline.MultimodalDiffusionPipeline_ParametrizedCompilation.__init__': ( 'pipeline/multimodal_diffusion_pipeline.html#multimodaldiffusionpipeline_parametrizedcompilation.__init__',
+ 'genQC/pipeline/multimodal_diffusion_pipeline.py'),
+ 'genQC.pipeline.multimodal_diffusion_pipeline.MultimodalDiffusionPipeline_ParametrizedCompilation._get_guidance_scales': ( 'pipeline/multimodal_diffusion_pipeline.html#multimodaldiffusionpipeline_parametrizedcompilation._get_guidance_scales',
+ 'genQC/pipeline/multimodal_diffusion_pipeline.py'),
+ 'genQC.pipeline.multimodal_diffusion_pipeline.MultimodalDiffusionPipeline_ParametrizedCompilation.denoising': ( 'pipeline/multimodal_diffusion_pipeline.html#multimodaldiffusionpipeline_parametrizedcompilation.denoising',
+ 'genQC/pipeline/multimodal_diffusion_pipeline.py'),
+ 'genQC.pipeline.multimodal_diffusion_pipeline.MultimodalDiffusionPipeline_ParametrizedCompilation.denoising_step': ( 'pipeline/multimodal_diffusion_pipeline.html#multimodaldiffusionpipeline_parametrizedcompilation.denoising_step',
+ 'genQC/pipeline/multimodal_diffusion_pipeline.py'),
+ 'genQC.pipeline.multimodal_diffusion_pipeline.MultimodalDiffusionPipeline_ParametrizedCompilation.denoising_step_joint': ( 'pipeline/multimodal_diffusion_pipeline.html#multimodaldiffusionpipeline_parametrizedcompilation.denoising_step_joint',
+ 'genQC/pipeline/multimodal_diffusion_pipeline.py'),
+ 'genQC.pipeline.multimodal_diffusion_pipeline.MultimodalDiffusionPipeline_ParametrizedCompilation.denoising_step_single_mode_w': ( 'pipeline/multimodal_diffusion_pipeline.html#multimodaldiffusionpipeline_parametrizedcompilation.denoising_step_single_mode_w',
+ 'genQC/pipeline/multimodal_diffusion_pipeline.py'),
+ 'genQC.pipeline.multimodal_diffusion_pipeline.MultimodalDiffusionPipeline_ParametrizedCompilation.from_config_file': ( 'pipeline/multimodal_diffusion_pipeline.html#multimodaldiffusionpipeline_parametrizedcompilation.from_config_file',
+ 'genQC/pipeline/multimodal_diffusion_pipeline.py'),
+ 'genQC.pipeline.multimodal_diffusion_pipeline.MultimodalDiffusionPipeline_ParametrizedCompilation.params_config': ( 'pipeline/multimodal_diffusion_pipeline.html#multimodaldiffusionpipeline_parametrizedcompilation.params_config',
+ 'genQC/pipeline/multimodal_diffusion_pipeline.py'),
+ 'genQC.pipeline.multimodal_diffusion_pipeline.MultimodalDiffusionPipeline_ParametrizedCompilation.train_step': ( 'pipeline/multimodal_diffusion_pipeline.html#multimodaldiffusionpipeline_parametrizedcompilation.train_step',
+ 'genQC/pipeline/multimodal_diffusion_pipeline.py')},
+ 'genQC.pipeline.pipeline': { 'genQC.pipeline.pipeline.CheckpointCB': ( 'pipeline/pipeline.html#checkpointcb',
+ 'genQC/pipeline/pipeline.py'),
+ 'genQC.pipeline.pipeline.CheckpointCB.__init__': ( 'pipeline/pipeline.html#checkpointcb.__init__',
+ 'genQC/pipeline/pipeline.py'),
+ 'genQC.pipeline.pipeline.CheckpointCB.after_epoch': ( 'pipeline/pipeline.html#checkpointcb.after_epoch',
+ 'genQC/pipeline/pipeline.py'),
+ 'genQC.pipeline.pipeline.Pipeline': ( 'pipeline/pipeline.html#pipeline',
'genQC/pipeline/pipeline.py'),
'genQC.pipeline.pipeline.Pipeline.__call__': ( 'pipeline/pipeline.html#pipeline.__call__',
'genQC/pipeline/pipeline.py'),
'genQC.pipeline.pipeline.Pipeline.__init__': ( 'pipeline/pipeline.html#pipeline.__init__',
'genQC/pipeline/pipeline.py'),
+ 'genQC.pipeline.pipeline.Pipeline._get_parameters': ( 'pipeline/pipeline.html#pipeline._get_parameters',
+ 'genQC/pipeline/pipeline.py'),
'genQC.pipeline.pipeline.Pipeline._reset_opt': ( 'pipeline/pipeline.html#pipeline._reset_opt',
'genQC/pipeline/pipeline.py'),
'genQC.pipeline.pipeline.Pipeline._set_opt_param': ( 'pipeline/pipeline.html#pipeline._set_opt_param',
@@ -488,75 +858,210 @@
'genQC/pipeline/pipeline.py'),
'genQC.pipeline.pipeline.Pipeline.train_step': ( 'pipeline/pipeline.html#pipeline.train_step',
'genQC/pipeline/pipeline.py'),
- 'genQC.pipeline.pipeline.Pipeline_IO': ( 'pipeline/pipeline.html#pipeline_io',
- 'genQC/pipeline/pipeline.py'),
- 'genQC.pipeline.pipeline.Pipeline_IO.from_config_file': ( 'pipeline/pipeline.html#pipeline_io.from_config_file',
- 'genQC/pipeline/pipeline.py'),
- 'genQC.pipeline.pipeline.Pipeline_IO.get_config': ( 'pipeline/pipeline.html#pipeline_io.get_config',
- 'genQC/pipeline/pipeline.py'),
- 'genQC.pipeline.pipeline.Pipeline_IO.params_config': ( 'pipeline/pipeline.html#pipeline_io.params_config',
- 'genQC/pipeline/pipeline.py'),
- 'genQC.pipeline.pipeline.Pipeline_IO.store_pipeline': ( 'pipeline/pipeline.html#pipeline_io.store_pipeline',
- 'genQC/pipeline/pipeline.py')},
- 'genQC.platform.qcircuit_dataset_construction': { 'genQC.platform.qcircuit_dataset_construction.decode_circuit': ( 'platform/qcircuit_dataset_construction.html#decode_circuit',
- 'genQC/platform/qcircuit_dataset_construction.py'),
- 'genQC.platform.qcircuit_dataset_construction.encode_circuit': ( 'platform/qcircuit_dataset_construction.html#encode_circuit',
- 'genQC/platform/qcircuit_dataset_construction.py'),
- 'genQC.platform.qcircuit_dataset_construction.gen_compilation_rndGates_dataset': ( 'platform/qcircuit_dataset_construction.html#gen_compilation_rndgates_dataset',
- 'genQC/platform/qcircuit_dataset_construction.py'),
- 'genQC.platform.qcircuit_dataset_construction.gen_qc_dataset': ( 'platform/qcircuit_dataset_construction.html#gen_qc_dataset',
- 'genQC/platform/qcircuit_dataset_construction.py'),
- 'genQC.platform.qcircuit_dataset_construction.get_rnd_encoded_circuit': ( 'platform/qcircuit_dataset_construction.html#get_rnd_encoded_circuit',
- 'genQC/platform/qcircuit_dataset_construction.py'),
- 'genQC.platform.qcircuit_dataset_construction.get_rnd_encoded_circuits': ( 'platform/qcircuit_dataset_construction.html#get_rnd_encoded_circuits',
- 'genQC/platform/qcircuit_dataset_construction.py'),
- 'genQC.platform.qcircuit_dataset_construction.get_specific_rnd_srv_circuit': ( 'platform/qcircuit_dataset_construction.html#get_specific_rnd_srv_circuit',
- 'genQC/platform/qcircuit_dataset_construction.py'),
- 'genQC.platform.qcircuit_dataset_construction.get_target_control_qubits': ( 'platform/qcircuit_dataset_construction.html#get_target_control_qubits',
- 'genQC/platform/qcircuit_dataset_construction.py')},
- 'genQC.platform.qcircuit_evaluation': { 'genQC.platform.qcircuit_evaluation.extract_gate_number': ( 'platform/qcircuit_evaluation.html#extract_gate_number',
- 'genQC/platform/qcircuit_evaluation.py'),
- 'genQC.platform.qcircuit_evaluation.get_gate_stat_from_circuits': ( 'platform/qcircuit_evaluation.html#get_gate_stat_from_circuits',
- 'genQC/platform/qcircuit_evaluation.py'),
- 'genQC.platform.qcircuit_evaluation.get_gate_stat_from_tensors': ( 'platform/qcircuit_evaluation.html#get_gate_stat_from_tensors',
- 'genQC/platform/qcircuit_evaluation.py'),
- 'genQC.platform.qcircuit_evaluation.sort_into_bins': ( 'platform/qcircuit_evaluation.html#sort_into_bins',
- 'genQC/platform/qcircuit_evaluation.py')},
- 'genQC.platform.qcircuit_metrics': { 'genQC.platform.qcircuit_metrics.Unitary_FrobeniusNorm': ( 'platform/qcircuit_metrics.html#unitary_frobeniusnorm',
- 'genQC/platform/qcircuit_metrics.py'),
- 'genQC.platform.qcircuit_metrics.Unitary_FrobeniusNorm.distance': ( 'platform/qcircuit_metrics.html#unitary_frobeniusnorm.distance',
- 'genQC/platform/qcircuit_metrics.py'),
- 'genQC.platform.qcircuit_metrics.Unitary_FrobeniusNorm.name': ( 'platform/qcircuit_metrics.html#unitary_frobeniusnorm.name',
- 'genQC/platform/qcircuit_metrics.py')},
- 'genQC.platform.qcircuit_util': { 'genQC.platform.qcircuit_util.get_element_matching_indices': ( 'platform/qcircuit_util.html#get_element_matching_indices',
- 'genQC/platform/qcircuit_util.py'),
- 'genQC.platform.qcircuit_util.get_entanglement_bins': ( 'platform/qcircuit_util.html#get_entanglement_bins',
- 'genQC/platform/qcircuit_util.py')},
- 'genQC.platform.simulation.qcircuit_sim': { 'genQC.platform.simulation.qcircuit_sim.gate_pool_to_gate_classes': ( 'platform/simulation/qcircuit_sim.html#gate_pool_to_gate_classes',
- 'genQC/platform/simulation/qcircuit_sim.py'),
- 'genQC.platform.simulation.qcircuit_sim.get_number_of_gate_params': ( 'platform/simulation/qcircuit_sim.html#get_number_of_gate_params',
- 'genQC/platform/simulation/qcircuit_sim.py'),
- 'genQC.platform.simulation.qcircuit_sim.instruction_name_to_qiskit_gate': ( 'platform/simulation/qcircuit_sim.html#instruction_name_to_qiskit_gate',
- 'genQC/platform/simulation/qcircuit_sim.py'),
- 'genQC.platform.simulation.qcircuit_sim.optimize_circuit': ( 'platform/simulation/qcircuit_sim.html#optimize_circuit',
- 'genQC/platform/simulation/qcircuit_sim.py'),
- 'genQC.platform.simulation.qcircuit_sim.plot_svr_stat': ( 'platform/simulation/qcircuit_sim.html#plot_svr_stat',
- 'genQC/platform/simulation/qcircuit_sim.py'),
- 'genQC.platform.simulation.qcircuit_sim.rnd_circuit': ( 'platform/simulation/qcircuit_sim.html#rnd_circuit',
- 'genQC/platform/simulation/qcircuit_sim.py'),
- 'genQC.platform.simulation.qcircuit_sim.schmidt_rank_vector': ( 'platform/simulation/qcircuit_sim.html#schmidt_rank_vector',
- 'genQC/platform/simulation/qcircuit_sim.py')},
- 'genQC.printing': { 'genQC.printing.display_colums': ('printing.html#display_colums', 'genQC/printing.py'),
- 'genQC.printing.ndarray_to_latex': ('printing.html#ndarray_to_latex', 'genQC/printing.py'),
- 'genQC.printing.print_markdown': ('printing.html#print_markdown', 'genQC/printing.py'),
- 'genQC.printing.print_table': ('printing.html#print_table', 'genQC/printing.py'),
- 'genQC.printing.tensor_to_latex': ('printing.html#tensor_to_latex', 'genQC/printing.py')},
+ 'genQC.pipeline.pipeline.PipelineIO': ( 'pipeline/pipeline.html#pipelineio',
+ 'genQC/pipeline/pipeline.py'),
+ 'genQC.pipeline.pipeline.PipelineIO.from_config_file': ( 'pipeline/pipeline.html#pipelineio.from_config_file',
+ 'genQC/pipeline/pipeline.py'),
+ 'genQC.pipeline.pipeline.PipelineIO.from_pretrained': ( 'pipeline/pipeline.html#pipelineio.from_pretrained',
+ 'genQC/pipeline/pipeline.py'),
+ 'genQC.pipeline.pipeline.PipelineIO.get_config': ( 'pipeline/pipeline.html#pipelineio.get_config',
+ 'genQC/pipeline/pipeline.py'),
+ 'genQC.pipeline.pipeline.PipelineIO.params_config': ( 'pipeline/pipeline.html#pipelineio.params_config',
+ 'genQC/pipeline/pipeline.py'),
+ 'genQC.pipeline.pipeline.PipelineIO.store_pipeline': ( 'pipeline/pipeline.html#pipelineio.store_pipeline',
+ 'genQC/pipeline/pipeline.py')},
+ 'genQC.pipeline.unitary_clip_pipeline': { 'genQC.pipeline.unitary_clip_pipeline.UnitaryCLIPPipeline': ( 'pipeline/unitary_clip_pipeline.html#unitaryclippipeline',
+ 'genQC/pipeline/unitary_clip_pipeline.py'),
+ 'genQC.pipeline.unitary_clip_pipeline.UnitaryCLIPPipeline.__call__': ( 'pipeline/unitary_clip_pipeline.html#unitaryclippipeline.__call__',
+ 'genQC/pipeline/unitary_clip_pipeline.py'),
+ 'genQC.pipeline.unitary_clip_pipeline.UnitaryCLIPPipeline.__init__': ( 'pipeline/unitary_clip_pipeline.html#unitaryclippipeline.__init__',
+ 'genQC/pipeline/unitary_clip_pipeline.py'),
+ 'genQC.pipeline.unitary_clip_pipeline.UnitaryCLIPPipeline.from_config_file': ( 'pipeline/unitary_clip_pipeline.html#unitaryclippipeline.from_config_file',
+ 'genQC/pipeline/unitary_clip_pipeline.py'),
+ 'genQC.pipeline.unitary_clip_pipeline.UnitaryCLIPPipeline.get_loss': ( 'pipeline/unitary_clip_pipeline.html#unitaryclippipeline.get_loss',
+ 'genQC/pipeline/unitary_clip_pipeline.py'),
+ 'genQC.pipeline.unitary_clip_pipeline.UnitaryCLIPPipeline.params_config': ( 'pipeline/unitary_clip_pipeline.html#unitaryclippipeline.params_config',
+ 'genQC/pipeline/unitary_clip_pipeline.py'),
+ 'genQC.pipeline.unitary_clip_pipeline.UnitaryCLIPPipeline.store_pipeline': ( 'pipeline/unitary_clip_pipeline.html#unitaryclippipeline.store_pipeline',
+ 'genQC/pipeline/unitary_clip_pipeline.py'),
+ 'genQC.pipeline.unitary_clip_pipeline.UnitaryCLIPPipeline.train_step': ( 'pipeline/unitary_clip_pipeline.html#unitaryclippipeline.train_step',
+ 'genQC/pipeline/unitary_clip_pipeline.py')},
+ 'genQC.platform.backends.base_backend': { 'genQC.platform.backends.base_backend.BaseBackend': ( 'platform/backends/base_backend.html#basebackend',
+ 'genQC/platform/backends/base_backend.py'),
+ 'genQC.platform.backends.base_backend.BaseBackend.backend_to_genqc': ( 'platform/backends/base_backend.html#basebackend.backend_to_genqc',
+ 'genQC/platform/backends/base_backend.py'),
+ 'genQC.platform.backends.base_backend.BaseBackend.draw': ( 'platform/backends/base_backend.html#basebackend.draw',
+ 'genQC/platform/backends/base_backend.py'),
+ 'genQC.platform.backends.base_backend.BaseBackend.genqc_to_backend': ( 'platform/backends/base_backend.html#basebackend.genqc_to_backend',
+ 'genQC/platform/backends/base_backend.py'),
+ 'genQC.platform.backends.base_backend.BaseBackend.get_unitary': ( 'platform/backends/base_backend.html#basebackend.get_unitary',
+ 'genQC/platform/backends/base_backend.py')},
+ 'genQC.platform.backends.circuits_cudaq': { 'genQC.platform.backends.circuits_cudaq.CircuitsCudaqBackend': ( 'platform/backends/circuits_cudaq.html#circuitscudaqbackend',
+ 'genQC/platform/backends/circuits_cudaq.py'),
+ 'genQC.platform.backends.circuits_cudaq.CircuitsCudaqBackend.__init__': ( 'platform/backends/circuits_cudaq.html#circuitscudaqbackend.__init__',
+ 'genQC/platform/backends/circuits_cudaq.py'),
+ 'genQC.platform.backends.circuits_cudaq.CircuitsCudaqBackend._construct_kernel': ( 'platform/backends/circuits_cudaq.html#circuitscudaqbackend._construct_kernel',
+ 'genQC/platform/backends/circuits_cudaq.py'),
+ 'genQC.platform.backends.circuits_cudaq.CircuitsCudaqBackend.backend_to_genqc': ( 'platform/backends/circuits_cudaq.html#circuitscudaqbackend.backend_to_genqc',
+ 'genQC/platform/backends/circuits_cudaq.py'),
+ 'genQC.platform.backends.circuits_cudaq.CircuitsCudaqBackend.check_error_circuit': ( 'platform/backends/circuits_cudaq.html#circuitscudaqbackend.check_error_circuit',
+ 'genQC/platform/backends/circuits_cudaq.py'),
+ 'genQC.platform.backends.circuits_cudaq.CircuitsCudaqBackend.draw': ( 'platform/backends/circuits_cudaq.html#circuitscudaqbackend.draw',
+ 'genQC/platform/backends/circuits_cudaq.py'),
+ 'genQC.platform.backends.circuits_cudaq.CircuitsCudaqBackend.genqc_to_backend': ( 'platform/backends/circuits_cudaq.html#circuitscudaqbackend.genqc_to_backend',
+ 'genQC/platform/backends/circuits_cudaq.py'),
+ 'genQC.platform.backends.circuits_cudaq.CircuitsCudaqBackend.get_unitary': ( 'platform/backends/circuits_cudaq.html#circuitscudaqbackend.get_unitary',
+ 'genQC/platform/backends/circuits_cudaq.py'),
+ 'genQC.platform.backends.circuits_cudaq.ParametrizedCudaqKernel': ( 'platform/backends/circuits_cudaq.html#parametrizedcudaqkernel',
+ 'genQC/platform/backends/circuits_cudaq.py')},
+ 'genQC.platform.backends.circuits_pennylane': { 'genQC.platform.backends.circuits_pennylane.CircuitsPennylaneBackend': ( 'platform/backends/circuits_pennylane.html#circuitspennylanebackend',
+ 'genQC/platform/backends/circuits_pennylane.py'),
+ 'genQC.platform.backends.circuits_pennylane.CircuitsPennylaneBackend.backend_to_genqc': ( 'platform/backends/circuits_pennylane.html#circuitspennylanebackend.backend_to_genqc',
+ 'genQC/platform/backends/circuits_pennylane.py'),
+ 'genQC.platform.backends.circuits_pennylane.CircuitsPennylaneBackend.draw': ( 'platform/backends/circuits_pennylane.html#circuitspennylanebackend.draw',
+ 'genQC/platform/backends/circuits_pennylane.py'),
+ 'genQC.platform.backends.circuits_pennylane.CircuitsPennylaneBackend.genqc_to_backend': ( 'platform/backends/circuits_pennylane.html#circuitspennylanebackend.genqc_to_backend',
+ 'genQC/platform/backends/circuits_pennylane.py'),
+ 'genQC.platform.backends.circuits_pennylane.CircuitsPennylaneBackend.get_unitary': ( 'platform/backends/circuits_pennylane.html#circuitspennylanebackend.get_unitary',
+ 'genQC/platform/backends/circuits_pennylane.py'),
+ 'genQC.platform.backends.circuits_pennylane.ParametrizedPennylaneCircuit': ( 'platform/backends/circuits_pennylane.html#parametrizedpennylanecircuit',
+ 'genQC/platform/backends/circuits_pennylane.py'),
+ 'genQC.platform.backends.circuits_pennylane.instruction_name_to_pennylane_name': ( 'platform/backends/circuits_pennylane.html#instruction_name_to_pennylane_name',
+ 'genQC/platform/backends/circuits_pennylane.py')},
+ 'genQC.platform.backends.circuits_qiskit': { 'genQC.platform.backends.circuits_qiskit.CircuitsQiskitBackend': ( 'platform/backends/circuits_qiskit.html#circuitsqiskitbackend',
+ 'genQC/platform/backends/circuits_qiskit.py'),
+ 'genQC.platform.backends.circuits_qiskit.CircuitsQiskitBackend.backend_to_genqc': ( 'platform/backends/circuits_qiskit.html#circuitsqiskitbackend.backend_to_genqc',
+ 'genQC/platform/backends/circuits_qiskit.py'),
+ 'genQC.platform.backends.circuits_qiskit.CircuitsQiskitBackend.draw': ( 'platform/backends/circuits_qiskit.html#circuitsqiskitbackend.draw',
+ 'genQC/platform/backends/circuits_qiskit.py'),
+ 'genQC.platform.backends.circuits_qiskit.CircuitsQiskitBackend.genqc_to_backend': ( 'platform/backends/circuits_qiskit.html#circuitsqiskitbackend.genqc_to_backend',
+ 'genQC/platform/backends/circuits_qiskit.py'),
+ 'genQC.platform.backends.circuits_qiskit.CircuitsQiskitBackend.get_unitary': ( 'platform/backends/circuits_qiskit.html#circuitsqiskitbackend.get_unitary',
+ 'genQC/platform/backends/circuits_qiskit.py'),
+ 'genQC.platform.backends.circuits_qiskit.CircuitsQiskitBackend.optimize_circuit': ( 'platform/backends/circuits_qiskit.html#circuitsqiskitbackend.optimize_circuit',
+ 'genQC/platform/backends/circuits_qiskit.py'),
+ 'genQC.platform.backends.circuits_qiskit.CircuitsQiskitBackend.randomize_params': ( 'platform/backends/circuits_qiskit.html#circuitsqiskitbackend.randomize_params',
+ 'genQC/platform/backends/circuits_qiskit.py'),
+ 'genQC.platform.backends.circuits_qiskit.CircuitsQiskitBackend.rnd_circuit': ( 'platform/backends/circuits_qiskit.html#circuitsqiskitbackend.rnd_circuit',
+ 'genQC/platform/backends/circuits_qiskit.py'),
+ 'genQC.platform.backends.circuits_qiskit.CircuitsQiskitBackend.schmidt_rank_vector': ( 'platform/backends/circuits_qiskit.html#circuitsqiskitbackend.schmidt_rank_vector',
+ 'genQC/platform/backends/circuits_qiskit.py'),
+ 'genQC.platform.backends.circuits_qiskit.get_number_of_gate_params': ( 'platform/backends/circuits_qiskit.html#get_number_of_gate_params',
+ 'genQC/platform/backends/circuits_qiskit.py'),
+ 'genQC.platform.backends.circuits_qiskit.get_target_control_qubits': ( 'platform/backends/circuits_qiskit.html#get_target_control_qubits',
+ 'genQC/platform/backends/circuits_qiskit.py'),
+ 'genQC.platform.backends.circuits_qiskit.instruction_name_to_qiskit_gate': ( 'platform/backends/circuits_qiskit.html#instruction_name_to_qiskit_gate',
+ 'genQC/platform/backends/circuits_qiskit.py')},
+ 'genQC.platform.circuits_generation': { 'genQC.platform.circuits_generation.CircuitConditionType': ( 'platform/circuits_generation.html#circuitconditiontype',
+ 'genQC/platform/circuits_generation.py'),
+ 'genQC.platform.circuits_generation.generate_circuit_dataset': ( 'platform/circuits_generation.html#generate_circuit_dataset',
+ 'genQC/platform/circuits_generation.py'),
+ 'genQC.platform.circuits_generation.get_rnd_encoded_circuit': ( 'platform/circuits_generation.html#get_rnd_encoded_circuit',
+ 'genQC/platform/circuits_generation.py'),
+ 'genQC.platform.circuits_generation.get_rnd_encoded_circuits': ( 'platform/circuits_generation.html#get_rnd_encoded_circuits',
+ 'genQC/platform/circuits_generation.py')},
+ 'genQC.platform.circuits_instructions': { 'genQC.platform.circuits_instructions.CircuitInstruction': ( 'platform/circuits_instructions.html#circuitinstruction',
+ 'genQC/platform/circuits_instructions.py'),
+ 'genQC.platform.circuits_instructions.CircuitInstructions': ( 'platform/circuits_instructions.html#circuitinstructions',
+ 'genQC/platform/circuits_instructions.py'),
+ 'genQC.platform.circuits_instructions.CircuitInstructions.__init__': ( 'platform/circuits_instructions.html#circuitinstructions.__init__',
+ 'genQC/platform/circuits_instructions.py'),
+ 'genQC.platform.circuits_instructions.CircuitInstructions.__repr__': ( 'platform/circuits_instructions.html#circuitinstructions.__repr__',
+ 'genQC/platform/circuits_instructions.py'),
+ 'genQC.platform.circuits_instructions.CircuitInstructions.add_instruction': ( 'platform/circuits_instructions.html#circuitinstructions.add_instruction',
+ 'genQC/platform/circuits_instructions.py'),
+ 'genQC.platform.circuits_instructions.CircuitInstructions.data': ( 'platform/circuits_instructions.html#circuitinstructions.data',
+ 'genQC/platform/circuits_instructions.py'),
+ 'genQC.platform.circuits_instructions.CircuitInstructions.length': ( 'platform/circuits_instructions.html#circuitinstructions.length',
+ 'genQC/platform/circuits_instructions.py'),
+ 'genQC.platform.circuits_instructions.CircuitInstructions.max_gates': ( 'platform/circuits_instructions.html#circuitinstructions.max_gates',
+ 'genQC/platform/circuits_instructions.py'),
+ 'genQC.platform.circuits_instructions.CircuitInstructions.num_qubits': ( 'platform/circuits_instructions.html#circuitinstructions.num_qubits',
+ 'genQC/platform/circuits_instructions.py'),
+ 'genQC.platform.circuits_instructions.CircuitInstructions.print': ( 'platform/circuits_instructions.html#circuitinstructions.print',
+ 'genQC/platform/circuits_instructions.py')},
+ 'genQC.platform.simulation': { 'genQC.platform.simulation.CircuitBackendType': ( 'platform/simulation.html#circuitbackendtype',
+ 'genQC/platform/simulation.py'),
+ 'genQC.platform.simulation.Simulator': ( 'platform/simulation.html#simulator',
+ 'genQC/platform/simulation.py'),
+ 'genQC.platform.simulation.Simulator.__init__': ( 'platform/simulation.html#simulator.__init__',
+ 'genQC/platform/simulation.py'),
+ 'genQC.platform.simulation.Simulator.backend_to_genqc': ( 'platform/simulation.html#simulator.backend_to_genqc',
+ 'genQC/platform/simulation.py'),
+ 'genQC.platform.simulation.Simulator.genqc_to_backend': ( 'platform/simulation.html#simulator.genqc_to_backend',
+ 'genQC/platform/simulation.py'),
+ 'genQC.platform.simulation.TensorEncodingType': ( 'platform/simulation.html#tensorencodingtype',
+ 'genQC/platform/simulation.py'),
+ 'genQC.platform.simulation.is_circuit_type': ( 'platform/simulation.html#is_circuit_type',
+ 'genQC/platform/simulation.py')},
+ 'genQC.platform.tokenizer.base_tokenizer': { 'genQC.platform.tokenizer.base_tokenizer.BaseTokenizer': ( 'platform/tokenizer/base_tokenizer.html#basetokenizer',
+ 'genQC/platform/tokenizer/base_tokenizer.py'),
+ 'genQC.platform.tokenizer.base_tokenizer.BaseTokenizer.__init__': ( 'platform/tokenizer/base_tokenizer.html#basetokenizer.__init__',
+ 'genQC/platform/tokenizer/base_tokenizer.py'),
+ 'genQC.platform.tokenizer.base_tokenizer.BaseTokenizer.decode': ( 'platform/tokenizer/base_tokenizer.html#basetokenizer.decode',
+ 'genQC/platform/tokenizer/base_tokenizer.py'),
+ 'genQC.platform.tokenizer.base_tokenizer.BaseTokenizer.encode': ( 'platform/tokenizer/base_tokenizer.html#basetokenizer.encode',
+ 'genQC/platform/tokenizer/base_tokenizer.py'),
+ 'genQC.platform.tokenizer.base_tokenizer.BaseTokenizer.tokenize': ( 'platform/tokenizer/base_tokenizer.html#basetokenizer.tokenize',
+ 'genQC/platform/tokenizer/base_tokenizer.py'),
+ 'genQC.platform.tokenizer.base_tokenizer.invert_vocabulary': ( 'platform/tokenizer/base_tokenizer.html#invert_vocabulary',
+ 'genQC/platform/tokenizer/base_tokenizer.py')},
+ 'genQC.platform.tokenizer.circuits_tokenizer': { 'genQC.platform.tokenizer.circuits_tokenizer.CircuitTokenizer': ( 'platform/tokenizer/circuits_tokenizer.html#circuittokenizer',
+ 'genQC/platform/tokenizer/circuits_tokenizer.py'),
+ 'genQC.platform.tokenizer.circuits_tokenizer.CircuitTokenizer.__init__': ( 'platform/tokenizer/circuits_tokenizer.html#circuittokenizer.__init__',
+ 'genQC/platform/tokenizer/circuits_tokenizer.py'),
+ 'genQC.platform.tokenizer.circuits_tokenizer.CircuitTokenizer.decode': ( 'platform/tokenizer/circuits_tokenizer.html#circuittokenizer.decode',
+ 'genQC/platform/tokenizer/circuits_tokenizer.py'),
+ 'genQC.platform.tokenizer.circuits_tokenizer.CircuitTokenizer.encode': ( 'platform/tokenizer/circuits_tokenizer.html#circuittokenizer.encode',
+ 'genQC/platform/tokenizer/circuits_tokenizer.py'),
+ 'genQC.platform.tokenizer.circuits_tokenizer.CircuitTokenizer.get_parametrized_tokens': ( 'platform/tokenizer/circuits_tokenizer.html#circuittokenizer.get_parametrized_tokens',
+ 'genQC/platform/tokenizer/circuits_tokenizer.py'),
+ 'genQC.platform.tokenizer.circuits_tokenizer.CircuitTokenizer.tokenize': ( 'platform/tokenizer/circuits_tokenizer.html#circuittokenizer.tokenize',
+ 'genQC/platform/tokenizer/circuits_tokenizer.py')},
+ 'genQC.platform.tokenizer.tensor_tokenizer': { 'genQC.platform.tokenizer.tensor_tokenizer.GatePairTokenizer': ( 'platform/tokenizer/tensor_tokenizer.html#gatepairtokenizer',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.GatePairTokenizer.__init__': ( 'platform/tokenizer/tensor_tokenizer.html#gatepairtokenizer.__init__',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.GatePairTokenizer.decode': ( 'platform/tokenizer/tensor_tokenizer.html#gatepairtokenizer.decode',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.GatePairTokenizer.encode': ( 'platform/tokenizer/tensor_tokenizer.html#gatepairtokenizer.encode',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.GatePairTokenizer.extract_current_gate_overlap_pairs': ( 'platform/tokenizer/tensor_tokenizer.html#gatepairtokenizer.extract_current_gate_overlap_pairs',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.GatePairTokenizer.extract_new_gate_overlap_pairs': ( 'platform/tokenizer/tensor_tokenizer.html#gatepairtokenizer.extract_new_gate_overlap_pairs',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.GatePairTokenizer.get_topk_pairs': ( 'platform/tokenizer/tensor_tokenizer.html#gatepairtokenizer.get_topk_pairs',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.GatePairTokenizer.learn': ( 'platform/tokenizer/tensor_tokenizer.html#gatepairtokenizer.learn',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.GatePairTokenizer.learn_step': ( 'platform/tokenizer/tensor_tokenizer.html#gatepairtokenizer.learn_step',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.GatePairTokenizer.replace_current_overlap_pairs': ( 'platform/tokenizer/tensor_tokenizer.html#gatepairtokenizer.replace_current_overlap_pairs',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.GatePairTokenizer.standardize_overlap_pairs': ( 'platform/tokenizer/tensor_tokenizer.html#gatepairtokenizer.standardize_overlap_pairs',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.GatePairTokenizer.standardize_vocab_pair': ( 'platform/tokenizer/tensor_tokenizer.html#gatepairtokenizer.standardize_vocab_pair',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.GatePairTokenizer.to': ( 'platform/tokenizer/tensor_tokenizer.html#gatepairtokenizer.to',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.GatePairTokenizer.tokenize': ( 'platform/tokenizer/tensor_tokenizer.html#gatepairtokenizer.tokenize',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.GatePairTokenizer.unpack_col': ( 'platform/tokenizer/tensor_tokenizer.html#gatepairtokenizer.unpack_col',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.get_topk_depth_unpacked': ( 'platform/tokenizer/tensor_tokenizer.html#get_topk_depth_unpacked',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py'),
+ 'genQC.platform.tokenizer.tensor_tokenizer.sort_config': ( 'platform/tokenizer/tensor_tokenizer.html#sort_config',
+ 'genQC/platform/tokenizer/tensor_tokenizer.py')},
'genQC.scheduler.scheduler': { 'genQC.scheduler.scheduler.Scheduler': ( 'scheduler/scheduler.html#scheduler',
'genQC/scheduler/scheduler.py'),
'genQC.scheduler.scheduler.Scheduler.__init__': ( 'scheduler/scheduler.html#scheduler.__init__',
'genQC/scheduler/scheduler.py'),
'genQC.scheduler.scheduler.Scheduler.add_noise': ( 'scheduler/scheduler.html#scheduler.add_noise',
'genQC/scheduler/scheduler.py'),
+ 'genQC.scheduler.scheduler.Scheduler.from_config': ( 'scheduler/scheduler.html#scheduler.from_config',
+ 'genQC/scheduler/scheduler.py'),
+ 'genQC.scheduler.scheduler.Scheduler.from_scheduler': ( 'scheduler/scheduler.html#scheduler.from_scheduler',
+ 'genQC/scheduler/scheduler.py'),
'genQC.scheduler.scheduler.Scheduler.get_config': ( 'scheduler/scheduler.html#scheduler.get_config',
'genQC/scheduler/scheduler.py'),
'genQC.scheduler.scheduler.Scheduler.params_config': ( 'scheduler/scheduler.html#scheduler.params_config',
@@ -565,6 +1070,8 @@
'genQC/scheduler/scheduler.py'),
'genQC.scheduler.scheduler.Scheduler.step': ( 'scheduler/scheduler.html#scheduler.step',
'genQC/scheduler/scheduler.py'),
+ 'genQC.scheduler.scheduler.Scheduler.to': ( 'scheduler/scheduler.html#scheduler.to',
+ 'genQC/scheduler/scheduler.py'),
'genQC.scheduler.scheduler.Scheduler.unsqueeze_vector_to_shape': ( 'scheduler/scheduler.html#scheduler.unsqueeze_vector_to_shape',
'genQC/scheduler/scheduler.py')},
'genQC.scheduler.scheduler_ddim': { 'genQC.scheduler.scheduler_ddim.DDIMScheduler': ( 'scheduler/scheduler_ddim.html#ddimscheduler',
@@ -581,36 +1088,102 @@
'genQC/scheduler/scheduler_ddim.py')},
'genQC.scheduler.scheduler_ddpm': { 'genQC.scheduler.scheduler_ddpm.DDPMScheduler': ( 'scheduler/scheduler_ddpm.html#ddpmscheduler',
'genQC/scheduler/scheduler_ddpm.py'),
+ 'genQC.scheduler.scheduler_ddpm.DDPMScheduler.SNR': ( 'scheduler/scheduler_ddpm.html#ddpmscheduler.snr',
+ 'genQC/scheduler/scheduler_ddpm.py'),
'genQC.scheduler.scheduler_ddpm.DDPMScheduler.__init__': ( 'scheduler/scheduler_ddpm.html#ddpmscheduler.__init__',
'genQC/scheduler/scheduler_ddpm.py'),
'genQC.scheduler.scheduler_ddpm.DDPMScheduler.add_noise': ( 'scheduler/scheduler_ddpm.html#ddpmscheduler.add_noise',
'genQC/scheduler/scheduler_ddpm.py'),
- 'genQC.scheduler.scheduler_ddpm.DDPMScheduler.add_noise_LEdit': ( 'scheduler/scheduler_ddpm.html#ddpmscheduler.add_noise_ledit',
- 'genQC/scheduler/scheduler_ddpm.py'),
+ 'genQC.scheduler.scheduler_ddpm.DDPMScheduler.enforce_zero_terminal_snr': ( 'scheduler/scheduler_ddpm.html#ddpmscheduler.enforce_zero_terminal_snr',
+ 'genQC/scheduler/scheduler_ddpm.py'),
'genQC.scheduler.scheduler_ddpm.DDPMScheduler.params_config': ( 'scheduler/scheduler_ddpm.html#ddpmscheduler.params_config',
'genQC/scheduler/scheduler_ddpm.py'),
'genQC.scheduler.scheduler_ddpm.DDPMScheduler.set_timesteps': ( 'scheduler/scheduler_ddpm.html#ddpmscheduler.set_timesteps',
'genQC/scheduler/scheduler_ddpm.py'),
'genQC.scheduler.scheduler_ddpm.DDPMScheduler.step': ( 'scheduler/scheduler_ddpm.html#ddpmscheduler.step',
'genQC/scheduler/scheduler_ddpm.py'),
- 'genQC.scheduler.scheduler_ddpm.DDPMScheduler.to_device': ( 'scheduler/scheduler_ddpm.html#ddpmscheduler.to_device',
- 'genQC/scheduler/scheduler_ddpm.py'),
+ 'genQC.scheduler.scheduler_ddpm.DDPMScheduler.to': ( 'scheduler/scheduler_ddpm.html#ddpmscheduler.to',
+ 'genQC/scheduler/scheduler_ddpm.py'),
'genQC.scheduler.scheduler_ddpm.DDPMSchedulerOutput': ( 'scheduler/scheduler_ddpm.html#ddpmscheduleroutput',
'genQC/scheduler/scheduler_ddpm.py')},
- 'genQC.util': { 'genQC.util.DataLoaders': ('util.html#dataloaders', 'genQC/util.py'),
- 'genQC.util.DataLoaders.__init__': ('util.html#dataloaders.__init__', 'genQC/util.py'),
- 'genQC.util.MemoryCleaner': ('util.html#memorycleaner', 'genQC/util.py'),
- 'genQC.util.MemoryCleaner._clean_ipython_hist': ( 'util.html#memorycleaner._clean_ipython_hist',
- 'genQC/util.py'),
- 'genQC.util.MemoryCleaner._clean_tb': ('util.html#memorycleaner._clean_tb', 'genQC/util.py'),
- 'genQC.util.MemoryCleaner.purge_mem': ('util.html#memorycleaner.purge_mem', 'genQC/util.py'),
- 'genQC.util.infer_torch_device': ('util.html#infer_torch_device', 'genQC/util.py'),
- 'genQC.util.latents_to_pil': ('util.html#latents_to_pil', 'genQC/util.py'),
- 'genQC.util.normalize_tensor': ('util.html#normalize_tensor', 'genQC/util.py'),
- 'genQC.util.number_of_paramters': ('util.html#number_of_paramters', 'genQC/util.py'),
- 'genQC.util.plot_image_grid': ('util.html#plot_image_grid', 'genQC/util.py'),
- 'genQC.util.savePdf': ('util.html#savepdf', 'genQC/util.py'),
- 'genQC.util.savePng': ('util.html#savepng', 'genQC/util.py'),
- 'genQC.util.saveSvg': ('util.html#savesvg', 'genQC/util.py'),
- 'genQC.util.scale_tensor': ('util.html#scale_tensor', 'genQC/util.py'),
- 'genQC.util.virtual': ('util.html#virtual', 'genQC/util.py')}}}
+ 'genQC.scheduler.scheduler_dpm': { 'genQC.scheduler.scheduler_dpm.DPMScheduler': ( 'scheduler/scheduler_dpm.html#dpmscheduler',
+ 'genQC/scheduler/scheduler_dpm.py'),
+ 'genQC.scheduler.scheduler_dpm.DPMScheduler.__init__': ( 'scheduler/scheduler_dpm.html#dpmscheduler.__init__',
+ 'genQC/scheduler/scheduler_dpm.py'),
+ 'genQC.scheduler.scheduler_dpm.DPMScheduler.params_config': ( 'scheduler/scheduler_dpm.html#dpmscheduler.params_config',
+ 'genQC/scheduler/scheduler_dpm.py'),
+ 'genQC.scheduler.scheduler_dpm.DPMScheduler.step': ( 'scheduler/scheduler_dpm.html#dpmscheduler.step',
+ 'genQC/scheduler/scheduler_dpm.py'),
+ 'genQC.scheduler.scheduler_dpm.DPMSchedulerOutput': ( 'scheduler/scheduler_dpm.html#dpmscheduleroutput',
+ 'genQC/scheduler/scheduler_dpm.py')},
+ 'genQC.utils.async_fn': { 'genQC.utils.async_fn.MemoryMappedArray': ( 'utils/async_fn.html#memorymappedarray',
+ 'genQC/utils/async_fn.py'),
+ 'genQC.utils.async_fn.MemoryMappedArray.__init__': ( 'utils/async_fn.html#memorymappedarray.__init__',
+ 'genQC/utils/async_fn.py'),
+ 'genQC.utils.async_fn.MemoryMappedArray.clean': ( 'utils/async_fn.html#memorymappedarray.clean',
+ 'genQC/utils/async_fn.py'),
+ 'genQC.utils.async_fn.MemoryMappedArray.get_obj': ( 'utils/async_fn.html#memorymappedarray.get_obj',
+ 'genQC/utils/async_fn.py'),
+ 'genQC.utils.async_fn.run_parallel_jobs': ( 'utils/async_fn.html#run_parallel_jobs',
+ 'genQC/utils/async_fn.py')},
+ 'genQC.utils.config_loader': { 'genQC.utils.config_loader.class_to_str': ( 'utils/config_loader.html#class_to_str',
+ 'genQC/utils/config_loader.py'),
+ 'genQC.utils.config_loader.config_to_dict': ( 'utils/config_loader.html#config_to_dict',
+ 'genQC/utils/config_loader.py'),
+ 'genQC.utils.config_loader.get_obj_from_str': ( 'utils/config_loader.html#get_obj_from_str',
+ 'genQC/utils/config_loader.py'),
+ 'genQC.utils.config_loader.instantiate_from_config': ( 'utils/config_loader.html#instantiate_from_config',
+ 'genQC/utils/config_loader.py'),
+ 'genQC.utils.config_loader.load_config': ( 'utils/config_loader.html#load_config',
+ 'genQC/utils/config_loader.py'),
+ 'genQC.utils.config_loader.load_model_state_dict': ( 'utils/config_loader.html#load_model_state_dict',
+ 'genQC/utils/config_loader.py'),
+ 'genQC.utils.config_loader.load_tensor': ( 'utils/config_loader.html#load_tensor',
+ 'genQC/utils/config_loader.py'),
+ 'genQC.utils.config_loader.save_dataclass_yaml': ( 'utils/config_loader.html#save_dataclass_yaml',
+ 'genQC/utils/config_loader.py'),
+ 'genQC.utils.config_loader.save_dict_yaml': ( 'utils/config_loader.html#save_dict_yaml',
+ 'genQC/utils/config_loader.py'),
+ 'genQC.utils.config_loader.store_model_state_dict': ( 'utils/config_loader.html#store_model_state_dict',
+ 'genQC/utils/config_loader.py'),
+ 'genQC.utils.config_loader.store_tensor': ( 'utils/config_loader.html#store_tensor',
+ 'genQC/utils/config_loader.py')},
+ 'genQC.utils.math': { 'genQC.utils.math.gram_schmidt': ('utils/math.html#gram_schmidt', 'genQC/utils/math.py'),
+ 'genQC.utils.math.matrix_power': ('utils/math.html#matrix_power', 'genQC/utils/math.py')},
+ 'genQC.utils.misc_utils': { 'genQC.utils.misc_utils.DataLoaders': ( 'utils/misc_utils.html#dataloaders',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.DataLoaders.__init__': ( 'utils/misc_utils.html#dataloaders.__init__',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.MemoryCleaner': ( 'utils/misc_utils.html#memorycleaner',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.MemoryCleaner._clean_ipython_hist': ( 'utils/misc_utils.html#memorycleaner._clean_ipython_hist',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.MemoryCleaner._clean_tb': ( 'utils/misc_utils.html#memorycleaner._clean_tb',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.MemoryCleaner.free_memory': ( 'utils/misc_utils.html#memorycleaner.free_memory',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.MemoryCleaner.purge_mem': ( 'utils/misc_utils.html#memorycleaner.purge_mem',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.cache_data': ( 'utils/misc_utils.html#cache_data',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.get_element_matching_indices': ( 'utils/misc_utils.html#get_element_matching_indices',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.get_entanglement_bins': ( 'utils/misc_utils.html#get_entanglement_bins',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.infer_torch_device': ( 'utils/misc_utils.html#infer_torch_device',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.latents_to_pil': ( 'utils/misc_utils.html#latents_to_pil',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.normalize_tensor': ( 'utils/misc_utils.html#normalize_tensor',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.number_of_paramters': ( 'utils/misc_utils.html#number_of_paramters',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.plot_image_grid': ( 'utils/misc_utils.html#plot_image_grid',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.savePdf': ('utils/misc_utils.html#savepdf', 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.savePng': ('utils/misc_utils.html#savepng', 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.saveSvg': ('utils/misc_utils.html#savesvg', 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.scale_tensor': ( 'utils/misc_utils.html#scale_tensor',
+ 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.set_seed': ('utils/misc_utils.html#set_seed', 'genQC/utils/misc_utils.py'),
+ 'genQC.utils.misc_utils.virtual': ('utils/misc_utils.html#virtual', 'genQC/utils/misc_utils.py')}}}
diff --git a/genQC/platform/simulation/__init__.py b/genQC/benchmark/__init__.py
similarity index 100%
rename from genQC/platform/simulation/__init__.py
rename to genQC/benchmark/__init__.py
diff --git a/genQC/benchmark/bench_compilation.py b/genQC/benchmark/bench_compilation.py
new file mode 100644
index 0000000..5654230
--- /dev/null
+++ b/genQC/benchmark/bench_compilation.py
@@ -0,0 +1,192 @@
+"""Functions to test and benchmark unitary compilation."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/benchmark/bench_compilation.ipynb.
+
+# %% auto 0
+__all__ = ['sigma_x', 'sigma_y', 'sigma_z', 'SpecialUnitaries', 'qubit_tensor_product', 'BaseHamiltonian', 'IsingHamiltonian',
+ 'XXZHamiltonian']
+
+# %% ../../src/benchmark/bench_compilation.ipynb 2
+from ..imports import *
+
+# %% ../../src/benchmark/bench_compilation.ipynb 5
+class SpecialUnitaries:
+ """Special unitary matrices to benchmark compilation."""
+
+ @staticmethod
+ def QFT(num_qubits: int) -> torch.Tensor:
+ """The Quantum Fourier transform (QFT) unitary for `num_qubits`-qubits."""
+
+ N = 2**num_qubits
+ wN = np.exp(2.0j*np.pi/N)
+
+ U = torch.zeros((N, N), dtype=torch.complex128)
+ for x in range(N):
+ U[:, x] = torch.tensor([np.power(wN, x*k, dtype=complex) for k in range(N)])
+
+ U *= 1.0/np.sqrt(N)
+ return U
+
+# %% ../../src/benchmark/bench_compilation.ipynb 9
+sigma_x = torch.tensor([[0, 1],
+ [1, 0]],
+ dtype=torch.complex128)
+
+sigma_y = torch.tensor([[ 0, -1j],
+ [1j, 0]],
+ dtype=torch.complex128)
+
+sigma_z = torch.tensor([[1, 0],
+ [0, -1]],
+ dtype=torch.complex128)
+
+# %% ../../src/benchmark/bench_compilation.ipynb 11
+def qubit_tensor_product(num_qubits: int, *ops: torch.Tensor, pos: int | Sequence[int]) -> torch.Tensor:
+ """
+ Make tensor product with identities, assumes `ops` placed at `pos` in the tensor product ordering.
+ """
+
+ _ops = [torch.eye(2) for i in range(num_qubits)]
+
+ if isinstance(pos, int):
+ pos = [pos]
+ elif isinstance(pos, Sequence):
+ assert len(pos) == len(ops)
+ else:
+ raise NotImplementedError()
+
+ for pos_i, ops_i in zip(pos, ops):
+ _ops[pos_i] = ops_i
+
+ mat = _ops[0]
+ for op in _ops[1:]:
+ mat = torch.kron(mat, op)
+
+ return mat
+
+# %% ../../src/benchmark/bench_compilation.ipynb 19
+class BaseHamiltonian(abc.ABC):
+ """Base implementation of a Hamiltonian."""
+
+ def __init__(self, device: Optional[str | torch.device] = None) -> None:
+ self.device = default(device, "cpu")
+ self._generate_matrix()
+
+ if not torch.allclose(self.data.adjoint(), self.data):
+ raise RuntimeError("Generated Hamiltonian matrix is not self-adjoint!")
+
+ @abc.abstractmethod
+ def _generate_matrix(self) -> torch.Tensor:
+ """Generates the Hamiltonian matrix into `self.data`."""
+ raise NotImplementedError()
+
+ def get_evolution(self, t: float | torch.Tensor, split_complex_channel: bool = False, dtype: Optional[torch.dtype] = None) -> torch.Tensor:
+ """
+ Assuming `h_bar=1`. Returns the unitary evolution in marix form.
+ """
+ U = torch.linalg.matrix_exp(-1j * t * self.data)
+
+ if split_complex_channel:
+ U = torch.stack([torch.real(U), torch.imag(U)])
+
+ if exists(dtype):
+ U = U.to(dtype)
+
+ return U
+
+# %% ../../src/benchmark/bench_compilation.ipynb 21
+class IsingHamiltonian(BaseHamiltonian):
+ """Implementation of the Ising Hamiltonian on a qubit chain."""
+
+ def __init__(self,
+ h: float,
+ J: float,
+ num_qubits: int,
+ periodic_boundary: bool = True,
+ device: Optional[str | torch.device] = None) -> None:
+ """
+ h: Magnetic field
+ J: Coupling constant
+ """
+ self.h = h
+ self.J = J
+ self.num_qubits = num_qubits
+ self.periodic_boundary = periodic_boundary
+ super().__init__(device)
+
+ def _generate_matrix(self) -> torch.Tensor:
+ """
+ Note: We take big endian convention in placing the `i,j`-sigmas in tensor product ordering.
+ For little endian we need to use `pos = self.num_qubits-i`.
+ """
+
+ N = 2**self.num_qubits
+ ham = torch.zeros((N, N), dtype=torch.complex128)
+
+ pairs = [(i, i+1) for i in range(self.num_qubits-1)]
+
+ if self.periodic_boundary:
+ pairs.append((self.num_qubits-1, 0))
+
+ for (i, j) in pairs:
+ Z_term = qubit_tensor_product(self.num_qubits, sigma_z, sigma_z, pos=[i, j])
+
+ # Coupling + Perturbation
+ ham += -self.J * Z_term
+
+ # Magnetic
+ for i in range(self.num_qubits):
+ ham += -self.h * qubit_tensor_product(self.num_qubits, sigma_x, pos=i)
+
+ self.data = ham.to(self.device)
+
+# %% ../../src/benchmark/bench_compilation.ipynb 29
+class XXZHamiltonian(BaseHamiltonian):
+ """Implementation of the XXZ Hamiltonian on a qubit chain."""
+
+ def __init__(self,
+ h: float,
+ J: float,
+ delta: float,
+ num_qubits: int,
+ periodic_boundary: bool = True,
+ device: Optional[str | torch.device] = None) -> None:
+ """
+ h: Magnetic field
+ J: Coupling constant
+ delta: Perturbation
+ """
+ self.h = h
+ self.J = J
+ self.delta = delta
+ self.num_qubits = num_qubits
+ self.periodic_boundary = periodic_boundary
+ super().__init__(device)
+
+ def _generate_matrix(self) -> torch.Tensor:
+ """
+ Note: We take big endian convention in placing the `i,j`-sigmas in tensor product ordering.
+ For little endian we need to use `pos = self.num_qubits-i`.
+ """
+
+ N = 2**self.num_qubits
+ ham = torch.zeros((N, N), dtype=torch.complex128)
+
+ pairs = [(i, i+1) for i in range(self.num_qubits-1)]
+
+ if self.periodic_boundary:
+ pairs.append((self.num_qubits-1, 0))
+
+ for (i, j) in pairs:
+ X_term = qubit_tensor_product(self.num_qubits, sigma_x, sigma_x, pos=[i, j])
+ Y_term = qubit_tensor_product(self.num_qubits, sigma_y, sigma_y, pos=[i, j])
+ Z_term = qubit_tensor_product(self.num_qubits, sigma_z, sigma_z, pos=[i, j])
+
+ # Coupling + Perturbation
+ ham += -self.J * (X_term + Y_term + self.delta * Z_term)
+
+ # Magnetic
+ for i in range(self.num_qubits):
+ ham += -self.h * qubit_tensor_product(self.num_qubits, sigma_x, pos=i)
+
+ self.data = ham.to(self.device)
diff --git a/genQC/config_loader.py b/genQC/config_loader.py
deleted file mode 100644
index 8db4012..0000000
--- a/genQC/config_loader.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../src/config_loader.ipynb.
-
-# %% auto 0
-__all__ = ['class_to_str', 'load_config', 'config_to_dict', 'save_dataclass_yaml', 'save_dict_yaml', 'get_obj_from_str',
- 'instantiate_from_config', 'load_model_from_config']
-
-# %% ../src/config_loader.ipynb 3
-from .imports import *
-from omegaconf import OmegaConf
-
-# %% ../src/config_loader.ipynb 5
-def class_to_str(cls):
- return str(cls)[8:-2]
-
-# %% ../src/config_loader.ipynb 6
-def load_config(file_path):
- return OmegaConf.load(f"{file_path}")
-
-# %% ../src/config_loader.ipynb 7
-def config_to_dict(config):
- return OmegaConf.to_container(config)
-
-# %% ../src/config_loader.ipynb 8
-def save_dataclass_yaml(data_obj, file_path):
- conf = OmegaConf.structured(data_obj)
- with open(file_path, 'w') as f:
- OmegaConf.save(config=conf, f=f)
-
-# %% ../src/config_loader.ipynb 9
-def save_dict_yaml(dict_obj, file_path):
- conf = OmegaConf.create(dict_obj)
- with open(file_path, 'w') as f:
- OmegaConf.save(config=conf, f=f)
-
-# %% ../src/config_loader.ipynb 14
-def get_obj_from_str(string, reload=False):
- module, cls = string.rsplit(".", 1)
- if reload:
- module_imp = importlib.import_module(module)
- importlib.reload(module_imp)
- return getattr(importlib.import_module(module, package=None), cls)
-
-# %% ../src/config_loader.ipynb 15
-def instantiate_from_config(config):
- if not "target" in config: raise KeyError("Expected key `target` to instantiate.")
- if not "params" in config: print(f"[WARNING] Expected key `params` to instantiate.")
- return get_obj_from_str(config["target"])(**config.get("params", dict()))
-
-# %% ../src/config_loader.ipynb 16
-def load_model_from_config(config, ckpt, device):
-
- print(f"Loading model from {ckpt}")
- pl_sd = torch.load(ckpt, map_location=torch.device(device).type, weights_only=True)
-
- model = instantiate_from_config(config.model)
-
- sd = pl_sd["state_dict"]
- m, u = model.load_state_dict(sd, strict=True)
-
- return model.to(device)
diff --git a/genQC/dataset/balancing.py b/genQC/dataset/balancing.py
new file mode 100644
index 0000000..3669711
--- /dev/null
+++ b/genQC/dataset/balancing.py
@@ -0,0 +1,72 @@
+"""Helper functions used to balance a dataset."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/dataset/balancing.ipynb.
+
+# %% auto 0
+__all__ = ['get_tensor_gate_length', 'add_balance_fn_quantile_qc_length']
+
+# %% ../../src/dataset/balancing.ipynb 2
+from ..imports import *
+import genQC.dataset.dataset_helper as dahe
+
+# %% ../../src/dataset/balancing.ipynb 4
+def get_tensor_gate_length(clr_tensor: torch.Tensor, padding_token: int = 0) -> torch.Tensor:
+ """
+ Returns the gate count of a tokenized circuit.
+ Make sure you use use the correct `padding_token`.
+
+ """
+ assert clr_tensor.dim() == 3, "[b, s, t]"
+
+ red_clr_tensor = (clr_tensor != padding_token).any(dim=1) # [b, t]
+ return torch.count_nonzero(red_clr_tensor, dim=1) # [b]
+
+# %% ../../src/dataset/balancing.ipynb 5
+def add_balance_fn_quantile_qc_length(indices: Union[np.ndarray, torch.Tensor],
+ x: Union[np.ndarray, torch.Tensor],
+ y: Union[np.ndarray, torch.Tensor],
+ *z,
+ padding_token: int = 0,
+ balance_quantile: float = 0.5,
+ device: torch.device = torch.device("cpu"),
+ quantile_length_weights: Optional[Callable[[torch.Tensor, torch.Tensor], torch.Tensor]] = None) -> torch.Tensor:
+ """Balances according to gate length."""
+
+ xb = x[indices].to(device)
+ l = get_tensor_gate_length(xb, padding_token=padding_token).to(device)
+
+ l_uniques, l_uniques_cnt = torch.unique(l, dim=0, return_counts=True)
+
+ #-----------------------------------
+ # samples = torch.min(l_uniques_cnt)
+ # samples = torch.median(l_uniques_cnt)
+ samples = torch.quantile(l_uniques_cnt.float(), balance_quantile, interpolation='nearest', dim=0).to(l_uniques_cnt.dtype)
+ samples = max(samples, 2)
+
+ #-----------------------------------
+ sub_ind = list()
+ for l_unique in l_uniques.to(device):
+ comp = (l==l_unique)
+ ind = comp.nonzero().squeeze().cpu()
+
+ if ind.dim() > 0:
+ if exists(quantile_length_weights):
+ _samples = int(quantile_length_weights(l_unique, samples))
+ else:
+ _samples = samples
+
+ ind = dahe.shuffle_tensor_dataset(ind)
+ ind = ind[:_samples]
+ else:
+ ind = ind[None]
+
+ sub_ind.append(ind)
+
+ sub_ind = torch.cat(sub_ind, dim=0)
+
+ indices = indices[sub_ind]
+
+ if indices.ndim < 1:
+ indices = indices[None]
+
+ return indices
diff --git a/genQC/dataset/cached_qc_dataset.py b/genQC/dataset/cached_dataset.py
similarity index 61%
rename from genQC/dataset/cached_qc_dataset.py
rename to genQC/dataset/cached_dataset.py
index 90e8cf9..88711ae 100644
--- a/genQC/dataset/cached_qc_dataset.py
+++ b/genQC/dataset/cached_dataset.py
@@ -1,20 +1,32 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/dataset/cached_qc_dataset.ipynb.
+"""Classes to create a dataset with cached labels."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/dataset/cached_dataset.ipynb.
# %% auto 0
-__all__ = ['Cached_OpenClip_Dataset']
+__all__ = ['CachedOpenCLIPDatasetConfig', 'CachedOpenCLIPDataset']
-# %% ../../src/dataset/cached_qc_dataset.ipynb 3
+# %% ../../src/dataset/cached_dataset.ipynb 2
from ..imports import *
-from .qc_dataset import Qc_Config_Dataset
-from .config_dataset import Config_Dataset
-from ..config_loader import *
+from .config_dataset import ConfigDataset, ConfigDatasetConfig
+from ..utils.config_loader import *
+
+# %% ../../src/dataset/cached_dataset.ipynb 3
+@dataclass
+class CachedOpenCLIPDatasetConfig(ConfigDatasetConfig):
+ pass
+
+# %% ../../src/dataset/cached_dataset.ipynb 4
+class CachedOpenCLIPDataset(ConfigDataset):
+ """
+ Adds `.caching` to the `ConfigDataset` class.
+
+ Cached dataset that caches the label `y` prompts using the CLIP `text_encoder`. This speeds up training significantly.
+ """
-# %% ../../src/dataset/cached_qc_dataset.ipynb 4
-class Cached_OpenClip_Dataset(Qc_Config_Dataset):
- """Adds `.caching` to the `Quantum circuit dataset` class."""
+ #-----------------------------------
- def x_y_preprocess(self, balance_max, max_samples=None):
- x_proc, y_proc, *z = super().x_y_preprocess(balance_max=balance_max, max_samples=max_samples)
+ def x_y_preprocess(self, balance_max, shuffle=False, max_samples=None, make_unique=True):
+ x_proc, y_proc, *z = super().x_y_preprocess(balance_max=balance_max, shuffle=shuffle, max_samples=max_samples, make_unique=make_unique)
y_proc = self.caching(y_proc)
return x_proc, y_proc, *z
@@ -39,29 +51,21 @@ def caching(self, y_proc, y_on_cpu=False):
if y_on_cpu: y_tok = y_tok.cpu()
- #now for using cache we need the uniques and the corresponding indices of the uniques
- y_uniques, y_ptrs = torch.unique(torch.cat([self.text_encoder.empty_token.to(y_tok.device), y_tok]), dim=0, return_inverse=True)
+ # Now for using cache we need the uniques and the corresponding indices of the uniques
+ y_uniques, y_ptrs = torch.unique(torch.cat([self.text_encoder.empty_token.to(y_tok.device), y_tok], dim=0), dim=0, return_inverse=True)
cached_empty_token_index = y_ptrs[0] #store what index the empty token has
y_ptrs = y_ptrs[1:] #remove the cat empty token
- #use cache
+ # Use cache
print(" - generate_cache")
self.text_encoder.generate_cache(tokens=y_uniques, cached_empty_token_index=cached_empty_token_index, y_on_cpu=y_on_cpu)
- print("[INFO]: Generated cache")
- return y_ptrs
+ print(f"[INFO]: Generated cache, {y_ptrs.shape=}")
+ return y_ptrs.clone()
#-------------------------------------------
def get_dataloaders(self, batch_size, text_encoder, p_valid=0.1, balance_max=None, max_samples=None):
self.text_encoder = text_encoder
- return super().get_dataloaders(batch_size, p_valid, balance_max, max_samples)
-
- #-------------------------------------------
-
- @staticmethod
- def from_config_file(config_path, device: torch.device, save_path: str=None):
- config = load_config(config_path)
- config["target"] = class_to_str(Cached_OpenClip_Dataset)
- return Config_Dataset.from_config(config, device, save_path)
+ return super().get_dataloaders(batch_size, p_valid, balance_max, max_samples)
diff --git a/genQC/dataset/circuits_dataset.py b/genQC/dataset/circuits_dataset.py
new file mode 100644
index 0000000..429071d
--- /dev/null
+++ b/genQC/dataset/circuits_dataset.py
@@ -0,0 +1,297 @@
+"""Dataset for quantum circuits."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/dataset/circuits_dataset.ipynb.
+
+# %% auto 0
+__all__ = ['CircuitsConfigDatasetConfig', 'CircuitsConfigDataset', 'MixedCircuitsConfigDatasetConfig',
+ 'MixedCircuitsConfigDataset']
+
+# %% ../../src/dataset/circuits_dataset.ipynb 2
+from ..imports import *
+from .cached_dataset import CachedOpenCLIPDataset, CachedOpenCLIPDatasetConfig
+from .mixed_cached_dataset import MixedCachedOpenCLIPDataset, MixedCachedOpenCLIPDatasetConfig
+from ..utils.config_loader import *
+from .config_dataset import ConfigDataset
+from .dataset_helper import shuffle_tensor_dataset
+from ..utils.misc_utils import MemoryCleaner
+
+# %% ../../src/dataset/circuits_dataset.ipynb 4
+@dataclass
+class CircuitsConfigDatasetConfig(CachedOpenCLIPDatasetConfig):
+ optimized: bool
+ random_samples: int
+ num_of_qubits: int
+ min_gates: int
+ max_gates: int
+ max_params: int
+ gate_pool: list[str]
+
+# %% ../../src/dataset/circuits_dataset.ipynb 5
+class CircuitsConfigDataset(CachedOpenCLIPDataset):
+ """Dataset for quantum circuits, access `gate_pool` directly and all other paras with `.params_config`"""
+
+ req_params = [f.name for f in dataclasses.fields(CircuitsConfigDatasetConfig)]
+
+ #-----------------------------------
+ def __init__(self, device: torch.device=torch.device("cpu"), **parameters) -> None:
+ super().__init__(device, **parameters)
+
+
+ if isinstance(list(parameters["gate_pool"])[0], str):
+ self.gate_pool = list(parameters["gate_pool"])
+
+ else:
+ try:
+ self.gate_pool = [get_obj_from_str(node) for node in parameters["gate_pool"]]
+ except Exception as er:
+ print(f"[WARNING]: error => {er}")
+ print(f"[WARNING]: gate_pool is passed as str")
+ self.gate_pool = [str(node) for node in parameters["gate_pool"]]
+
+ @property
+ def params_config(self):
+ params_config = super().params_config
+
+ if type(self) == CircuitsConfigDataset:
+ params_config = CircuitsConfigDatasetConfig(**params_config)
+ return params_config
+
+# %% ../../src/dataset/circuits_dataset.ipynb 8
+@dataclass
+class MixedCircuitsConfigDatasetConfig(CircuitsConfigDatasetConfig, MixedCachedOpenCLIPDatasetConfig):
+ pass
+
+# %% ../../src/dataset/circuits_dataset.ipynb 9
+class MixedCircuitsConfigDataset(CircuitsConfigDataset, MixedCachedOpenCLIPDataset):
+ """
+ Dataset that uses multiple cached dataset and combines them with padding, either i) Bucket or ii) Max.
+ Also provides a corresponding `collate_fn` for training.
+ """
+
+ req_params = [f.name for f in dataclasses.fields(MixedCircuitsConfigDatasetConfig)]
+
+ #-----------------------------------
+
+ @property
+ def params_config(self):
+ params_config = super().params_config
+ if type(self) == MixedCircuitsConfigDataset:
+ params_config = MixedCircuitsConfigDatasetConfig(**params_config)
+ return params_config
+
+ #-----------------------------------
+
+ def _get_cut_sizes(self, z):
+ z_0 = torch.max(z[:, 0]) # space
+ z_1 = torch.max(z[:, 1]) # time
+ z_1 = (torch.ceil(z_1 / self.model_scale_factor) * self.model_scale_factor).to(torch.int32)
+ return z_0, z_1
+
+ def _cut(self, x, y, z):
+ z_0, z_1 = self._get_cut_sizes(z)
+
+ x = x[:, :z_0, :z_1] # cut down to max [b, bits, time] of batch
+ return x, y
+
+ def _cut_compilation_params(self, x, y, p, U, z):
+ z_0, z_1 = self._get_cut_sizes(z)
+ bit_exp = 2**z_0
+
+ x = x[:, :z_0, :z_1] # cut down to max [b, bits, time] of batch
+ p = p[:, :, :z_1] # cut down to max [b, nP , time] of batch
+ U = U[:, :, :bit_exp, :bit_exp] # [b, Re/Im, 2^n, 2^n]
+ return x, y, p, U
+
+ #-----------------------------------
+ # BUCKET PADDING, all x,y are already passed as batch
+
+ def cut_padding_Bucket_collate_fn(self, b):
+ """this function is called for training for every batch, order in b is store dict"""
+
+ x, y, z = b[0]
+ x, y = self._cut(x, y, z)
+ return x, y
+
+
+ def cut_padding_Bucket_collate_fn_compilation(self, b):
+ """this function is called for training for every batch"""
+ raise NotImplementedError()
+
+
+ def cut_padding_Bucket_collate_fn_compilation_params(self, b):
+ """this function is called for training for every batch, order in b is store dict"""
+
+ b = b[0] # {'x': 'tensor', 'y': 'numpy', 'params': 'tensor', 'U': 'tensor', 'z': 'tensor'}
+
+ x = b[0]
+ y = b[1]
+ p = b[2]
+ U = b[3]
+ z = b[4]
+
+ #---------------
+
+ x, y, p, U = self._cut_compilation_params(x, y, p, U, z)
+
+ return x, y, p, U
+
+ #-----------------------------------
+ # MAX PADDING, x are passes as sampled list (batch), std collate them
+
+ def cut_padding_collate_fn(self, b):
+ """this function is called for training for every batch"""
+ x, y, z = torch.utils.data.default_collate(b)
+ x, y = self._cut(x, y, z)
+ return x, y
+
+ def cut_padding_collate_fn_compilation(self, b):
+ """this function is called for training for every batch"""
+ raise NotImplementedError()
+
+ def cut_padding_collate_fn_compilation_params(self, b):
+ """this function is called for training for every batch, order in b is store dict"""
+ # {'x': 'tensor', 'y': 'numpy', 'params': 'tensor', 'U': 'tensor', 'z': 'tensor'}
+ x, y, p, U, z = torch.utils.data.default_collate(b)
+ x, y, p, U = self._cut_compilation_params(x, y, p, U, z)
+ return x, y, p, U
+
+ #-----------------------------------
+
+ @staticmethod
+ def _preprocess_dataset(dataset, device, balance_max, max_samples, i, shuffle, make_unique, pad_constant,
+ model_scale_factor, parameters, max_gates, max_qubits):
+
+ dataset = dataset.to(device)
+
+ existing_z_type = dataset.store_dict.pop("z", None) # remove z, as it would mess up `ConfigDataset.x_y_preprocess`, it would be put in `*c`.
+ if exists(existing_z_type):
+ assert existing_z_type == "tensor"
+ z = dataset.z
+ else:
+ z = None
+
+ x, y, *c = ConfigDataset.x_y_preprocess(dataset, balance_max=balance_max, max_samples=max_samples[i], shuffle=shuffle, make_unique=make_unique)
+ x = x.to(device) # [b, s, t]
+
+ print(f" - dataset size after balancing {x.shape[0]}")
+
+ #-------
+ # store original size
+ if not_exists(z):
+ z = torch.zeros((x.shape[0], 2), device=device, dtype=torch.int32)
+ z[:, 0] = max(dataset.params_config.num_of_qubits, 1)
+
+ red_x = torch.sum(x.abs(), dim=1) # [b, t] .. collaps the zeros to get circuit length
+ z[:, 1] = torch.count_nonzero(red_x, dim=1) # [b]
+ z[z[:, 1]==0, 1] = 1
+
+ # Create masks for space and time padding
+ space_mask = torch.arange(x.shape[1], device=x.device).unsqueeze(0) >= z[:, 0].unsqueeze(1)
+ time_mask = torch.arange(x.shape[2], device=x.device).unsqueeze(0) >= z[:, 1].unsqueeze(1)
+
+ # Apply masks to pad_constant to handle both dimensions
+ x = torch.where(space_mask.unsqueeze(2), pad_constant, x)
+ x = torch.where( time_mask.unsqueeze(1), pad_constant, x)
+
+ z[:, 1] = (torch.ceil(z[:, 1] / model_scale_factor) * model_scale_factor).to(torch.int32) #for cut needs multiple
+
+ #-------
+
+ # now pad x, padding is defined from last dim forward!
+ pad = (0, max_gates-dataset.params_config.max_gates, 0, max_qubits-dataset.params_config.num_of_qubits)
+ x = F.pad(x, pad, "constant", pad_constant)
+
+ #-------
+
+ c = MixedCachedOpenCLIPDataset._add_missing_conditions(parameters, dataset, c, x.shape[0], "cpu")
+
+ dataset = dataset.to("cpu") #helps with gpu mem overflowing
+ del dataset
+
+ return x.cpu(), y, z.cpu(), *[ic.cpu() for ic in c]
+
+ @staticmethod
+ def from_datasets(datasets: list[CircuitsConfigDataset], balance_maxes: list, pad_constant, device: torch.device=torch.device("cpu"), bucket_batch_size=None,
+ max_samples=None, shuffle=True, make_unique=True, test_split=0.05, pad_with_memmap=False, **parameters):
+ if pad_constant == 0:
+ print("[WARNING]: >pad_constant == 0<; This could be an error!")
+
+ model_scale_factor = parameters["model_scale_factor"]
+
+ max_qubits = max(dataset.params_config.num_of_qubits for dataset in datasets)
+ max_gates = max(dataset.params_config.max_gates for dataset in datasets)
+ max_gates = int(np.ceil(max_gates /model_scale_factor) * model_scale_factor)
+ max_params = max(dataset.params_config.max_params for dataset in datasets)
+
+ parameters["num_of_qubits"] = max_qubits
+ parameters["max_gates"] = max_gates
+ parameters["max_params"] = max_params
+ parameters["random_samples"] = sum([dataset.params_config.random_samples for dataset in datasets])
+ parameters["min_gates"] = min([dataset.params_config.min_gates for dataset in datasets])
+ parameters["comment"] = f"Generated with 'from_datasets' with {len(datasets)} datasets. Qubits: {[dataset.params_config.num_of_qubits for dataset in datasets]}."
+ parameters["pad_constant"] = pad_constant
+ parameters["bucket_batch_size"] = bucket_batch_size
+
+ parameters["store_dict"] = {}
+ for dataset in datasets:
+ parameters["store_dict"] |= dataset.params_config.store_dict #needs python 3.9 for union of dict
+ parameters["store_dict"]["z"] = "tensor" #add special item
+
+ #-----------------
+
+ xs, ys, zs, cs = MixedCircuitsConfigDataset._preprocess_datasets(datasets, device, balance_maxes, max_samples, shuffle, make_unique, pad_constant,
+ model_scale_factor, parameters, max_gates=max_gates, max_qubits=max_qubits)
+ #-----------------
+
+ has_U = "U" in parameters["store_dict"]
+ has_p = "params" in parameters["store_dict"]
+
+ if bucket_batch_size > 0:
+ collate_fn_name = MixedCircuitsConfigDataset.cut_padding_Bucket_collate_fn.__name__
+ if has_U:
+ collate_fn_name = MixedCircuitsConfigDataset.cut_padding_Bucket_collate_fn_compilation.__name__
+ if has_p:
+ collate_fn_name = MixedCircuitsConfigDataset.cut_padding_Bucket_collate_fn_compilation_params.__name__
+
+ else:
+ collate_fn_name = MixedCircuitsConfigDataset.cut_padding_collate_fn.__name__
+ if has_U:
+ collate_fn_name = MixedCircuitsConfigDataset.cut_padding_collate_fn_compilation.__name__
+ if has_p:
+ collate_fn_name = MixedCircuitsConfigDataset.cut_padding_collate_fn_compilation_params.__name__
+
+ parameters["collate_fn"] = collate_fn_name
+
+ #-----------------
+ if bucket_batch_size > 0:
+ xs, ys, zs, cs = MixedCachedOpenCLIPDataset._reorder_to_buckets(parameters, bucket_batch_size, xs, ys, zs, cs)
+
+ x = torch.cat(xs)
+ y = ys # torch.cat(ys) is wrong, y is list of numpy or str!! not a tensor
+
+ if isinstance(y, list):
+ match parameters["store_dict"]["y"]:
+ case "numpy": y = np.concatenate(y, axis=0)
+ case "tensor": y = torch.cat(y, dim=0)
+ case _: raise NotImplementedError()
+
+ z = torch.cat(zs)
+ c = cs
+
+ #-----------------
+
+ params_pad = (max_params, max_gates)
+ unitary_pad = 2**max_qubits
+
+ ci_list, ci_k_list, memmap_cleans = MixedCachedOpenCLIPDataset._pad_conditions(parameters, bucket_batch_size, c, unitary_pad=unitary_pad, params_pad=params_pad, pad_with_memmap=pad_with_memmap)
+
+ #-----------------
+
+ mixed_CircuitsConfigDataset, mixed_CircuitsConfigDataset_test = \
+ MixedCircuitsConfigDataset._create_train_valid_datasets(device, parameters, test_split, x, y, z, ci_list, ci_k_list, shuffle=shuffle)
+
+ if pad_with_memmap:
+ mixed_CircuitsConfigDataset.memmap_cleans = memmap_cleans
+ mixed_CircuitsConfigDataset_test.memmap_cleans = memmap_cleans
+
+ return mixed_CircuitsConfigDataset, mixed_CircuitsConfigDataset_test
diff --git a/genQC/dataset/config_dataset.py b/genQC/dataset/config_dataset.py
index f32864f..9ab8295 100644
--- a/genQC/dataset/config_dataset.py
+++ b/genQC/dataset/config_dataset.py
@@ -1,28 +1,35 @@
+"""Base class for managing, loading and saving."""
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/dataset/config_dataset.ipynb.
# %% auto 0
-__all__ = ['Config_Dataset_config', 'Config_Dataset']
+__all__ = ['ConfigDatasetConfig', 'ConfigDataset']
# %% ../../src/dataset/config_dataset.ipynb 2
from ..imports import *
-from ..config_loader import *
+from ..utils.config_loader import *
+from .dataset_helper import *
from huggingface_hub import snapshot_download
# %% ../../src/dataset/config_dataset.ipynb 3
@dataclass
-class Config_Dataset_config:
+class ConfigDatasetConfig:
"""Config `dataclass` used for storage."""
store_dict: dict
+ dataset_to_gpu: bool
# %% ../../src/dataset/config_dataset.ipynb 4
-class Config_Dataset():
+class ConfigDataset():
"""Base class for datasets, manages loading and saving."""
- req_params = [f.name for f in dataclasses.fields(Config_Dataset_config)]
- comment = ""
+ req_params = [f.name for f in dataclasses.fields(ConfigDatasetConfig)]
+ comment = ""
+ add_balance_fn = None
- def __init__(self, device: torch.device=torch.device("cpu"), **parameters):
+ def __init__(self, device: torch.device=torch.device("cpu"), save_type=None, **parameters) -> None:
+ self.save_type = default(save_type, "safetensors")
+
req_params = self.req_params
for p in req_params:
if p not in parameters: raise RuntimeError(f"Missing parameter `{p}` in argument `**parameters: dict`")
@@ -54,6 +61,145 @@ def to(self, device: torch.device, excepts=[], **kwargs):
setattr(self, str(k), x)
return self
+
+ def memory_summary(self) -> None:
+ print("##################### Dataset memory summary #####################")
+ print("Name || Type || Memory || Device || Shape")
+ print("---------------------------------------------------------------")
+
+ total_mem = 0.0
+ byte_to_giga = 1 / (1024**3)
+
+ for k,v in self.store_dict.items():
+ mem = 0.0
+ dev = "None"
+ shape = "None"
+ dtype = "None"
+
+ x = getattr(self, str(k))
+
+ if v == "tensor":
+ mem += float(x.dtype.itemsize) * np.prod([s for s in x.shape], dtype=np.double) * byte_to_giga
+ dev = x.device
+ shape = x.shape
+ dtype = x.dtype
+
+ elif v == "tensor_list":
+ dev = []
+ for x_i in x:
+ mem += float(x_i.dtype.itemsize) * np.prod([s for s in x_i.shape], dtype=np.double) * byte_to_giga
+ dev.append(x_i.device)
+ shape = (len(x), x[0].shape)
+ dtype = x[0].dtype
+
+ elif v == "list":
+ shape = (len(x))
+ dtype = "python"
+
+ elif v == "numpy":
+ shape = x.shape
+ dtype = x.dtype
+
+
+ print(f" - [{str(k):>8}] ({str(dtype):>15} {str(v):>8}): {mem:3.4f} GB ({str(dev):6}) | {shape}")
+ total_mem += mem
+
+ print("--------------------------------------")
+ print(f" Total memory used: {total_mem:3.4f} GB ")
+ print("---------------------------------------------------------------")
+
+ #----------------------------
+
+ def x_y_preprocess(self, balance_max=None, shuffle=False, max_samples=None, make_unique=True):
+ z_proc = []
+ for k,v in self.store_dict.items():
+ if k != "x" and k != "y":
+ z_proc.append(getattr(self, k))
+
+ x_proc, y_proc = self.x, self.y
+
+ #---------------------
+ if shuffle:
+ x_proc, y_proc, *z_proc = shuffle_tensor_dataset(x_proc, y_proc, *z_proc)
+
+ if exists(max_samples):
+ x_proc = x_proc[:max_samples]
+ y_proc = y_proc[:max_samples]
+ z_proc = (iz[:max_samples] for iz in z_proc)
+
+ #---------------------
+ t = self.store_dict["y"]
+ if exists(balance_max):
+ if t == "tensor" or t == "numpy": x_proc, y_proc, *z_proc = balance_tensor_dataset(x_proc, y_proc, *z_proc, make_unique=make_unique, shuffle_lables=shuffle,
+ samples=balance_max, add_balance_fn=self.add_balance_fn, njobs=1)
+ else: print(f"[WARNING]: Unsupported y type: `{t}`. Not balancing dataset!")
+ else: print(f"[INFO]: Not balancing dataset! {balance_max=}")
+
+ #---------------------
+ if shuffle:
+ x_proc, y_proc, *z_proc = shuffle_tensor_dataset(x_proc, y_proc, *z_proc)
+
+ return x_proc, y_proc, *z_proc
+
+ def valid_split(self, x, y, *z, p_valid=0.1, y_type=None, split_sequential=False):
+ """
+ split_sequential ... if true split data ordered (valid-train order), else split randomly (the same as shuffle and then seq. split)
+ """
+
+ if split_sequential: ind = torch.arange(x.shape[0])
+ else: ind = torch.randperm(x.shape[0])
+
+ splits = max(int(x.shape[0] * p_valid), 1)
+ ind, ind_valid = ind[splits:], ind[:splits]
+
+ #### Note: advanced indexing always creates copy not view. So we can skip the .clone()
+ x, x_valid = x[ind], x[ind_valid]
+
+ t = y_type if exists(y_type) else self.store_dict["y"]
+ if t == "tensor" : y, y_valid = y[ind], y[ind_valid]
+ elif t == "numpy": y, y_valid = y[ind], y[ind_valid]
+
+ z = list(z)
+ z_valid = [None] * len(z)
+ for i, iz in enumerate(z):
+ # assert tensors for now
+ z[i], z_valid[i] = iz[ind], iz[ind_valid]
+
+ z, z_valid = tuple(z), tuple(z_valid)
+
+ return x, x_valid, y, y_valid, (z, z_valid)
+
+ def get_dataloaders(self, batch_size, p_valid=0.1, balance_max=None, max_samples=None, y_on_cpu=False, shuffle=True):
+ #-------------------------
+ # valid split and to device
+
+ x_proc, y_proc, *z_proc = self.x_y_preprocess(balance_max=balance_max, max_samples=max_samples, shuffle=shuffle)
+ x, x_valid, y, y_valid, (z, z_valid) = self.valid_split(x_proc, y_proc, *z_proc, p_valid=p_valid)
+
+
+ if self.params_config.dataset_to_gpu:
+ x, x_valid = x.to("cuda"), x_valid.to("cuda")
+ z, z_valid = list(iz.to("cuda") for iz in z), list(iz_valid.to("cuda") for iz_valid in z_valid)
+
+ if not y_on_cpu:
+ y, y_valid = y.to("cuda"), y_valid.to("cuda")
+
+ #-------------------------
+ # create dataloaders
+
+ ds = TensorDataset(x, y, *z)
+ ds_valid = TensorDataset(x_valid, y_valid, *z_valid)
+
+ if self.params_config.dataset_to_gpu:
+ train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True)
+ valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True)
+
+ else:
+ train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=12)
+ valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=12)
+
+ self.dataloaders = DataLoaders(train_loader, valid_loader)
+ return self.dataloaders
#----------------------------
@@ -61,16 +207,22 @@ def to(self, device: torch.device, excepts=[], **kwargs):
def params_config(self):
params_config = {}
for p in self.req_params: params_config[p] = getattr(self, p)
+
+ if type(self) == ConfigDataset:
+ params_config = ConfigDatasetConfig(**params_config)
return params_config
-
+
+ #----------------------------
+
def get_config(self, save_path=None, without_metadata=False):
if not without_metadata:
config = {}
config["target"] = class_to_str(type(self))
config["device"] = str(self.device)
config["comment"] = self.comment
- config["save_path"] = self.save_path if hasattr(self, "save_path") else save_path
+ config["save_path"] = self.save_path if hasattr(self, "save_path") and not exists(save_path) else save_path
config["save_datetime"] = datetime.now().strftime("%m/%d/%Y %H:%M:%S")
+ config["save_type"] = self.save_type
config["params"] = self.params_config
else:
config = self.params_config
@@ -79,22 +231,41 @@ def get_config(self, save_path=None, without_metadata=False):
return config
def save_dataset(self, config_path: str, save_path: str):
+ if exists(config_path): os.makedirs(config_path[:config_path.rfind("/")] + "/", exist_ok=True)
+ if exists(save_path): os.makedirs(save_path[:save_path.rfind("/")] + "/", exist_ok=True)
+
config = self.get_config(save_path, without_metadata=False)
save_dict_yaml(config, config_path)
self.store_x_y(save_path)
#----------------------------
+
+ def check_save_type(self, save_path):
+ if exists(self.save_type) and exists(save_path):
+ if not save_path.endswith(f".{self.save_type}"):
+ save_path += f".{self.save_type}"
+ return save_path
def store_x_y(self, path_str):
for k,v in self.store_dict.items():
x = getattr(self, str(k))
- torch.save(x, path_str + f"_{k}.pt")
-
- def load_x_y(self, path_str):
+
+ # torch.save(x, path_str + f"_{k}.pt")
+ store_tensor({"0": x}, self.check_save_type(path_str + f"_{k}"), type=v)
+
+ def load_x_y(self, path_str, device: Optional[torch.device] = None, make_contiguous: bool = True):
self.save_path = path_str
- for k,v in self.store_dict.items():
- x = torch.load(path_str + f"_{k}.pt", weights_only=False)
+ for k,v in self.store_dict.items():
+ # x = torch.load(path_str + f"_{k}.pt", map_location=device)
+ x = load_tensor(self.check_save_type(path_str + f"_{k}"), device, type=v)
+
+ if isinstance(x, dict):
+ x = x["0"]
+
+ if v == "tensor" and make_contiguous:
+ x = x.contiguous() #load memmap into memory
+
setattr(self, str(k), x)
#----------------------------
@@ -115,7 +286,7 @@ def from_config(config, device: torch.device, save_path: Optional[str] = None, m
if "save_path" in config: save_path = config["save_path"]
else: print("[INFO]: Found no key `save_path` path in config and no `save_path` arg provided.")
- if exists(save_path): config_dataset.load_x_y(save_path)
+ if exists(save_path): config_dataset.load_x_y(save_path, device=device, make_contiguous=make_contiguous)
else: print("[INFO]: No save_path` provided. Nothing loaded.")
#--------------------------------
@@ -132,7 +303,7 @@ def from_config_file(cls, config_path, device: torch.device, save_path: Optiona
If this method is called with `ConfigDataset.from_config_file` we use the given `target`, else use the caller class.
"""
config = load_config(config_path)
- if cls is not Config_Dataset:
+ if cls is not ConfigDataset:
config["target"] = class_to_str(cls)
return cls.from_config(config, device, save_path, make_contiguous)
@@ -140,5 +311,11 @@ def from_config_file(cls, config_path, device: torch.device, save_path: Optiona
def from_huggingface(cls, repo_id: str, device: torch.device, **kwargs):
"""Load a dataset directly from Huggingface."""
dataset_path = snapshot_download(repo_id=repo_id, repo_type="dataset", allow_patterns=["*.pt", "*.yaml", "*.safetensors"], **kwargs)
- dataset = cls.from_config_file(config_path=dataset_path+"/config.yaml", device=device, save_path=dataset_path+"/dataset")
+
+ try:
+ name = repo_id.split("/")[-1]
+ dataset = cls.from_config_file(config_path=dataset_path+f"/{name}.yaml", device=device, save_path=dataset_path+f"/{name}")
+ except Exception as e:
+ dataset = cls.from_config_file(config_path=dataset_path+"/config.yaml", device=device, save_path=dataset_path+"/dataset")
+
return dataset
diff --git a/genQC/dataset/dataset_helper.py b/genQC/dataset/dataset_helper.py
index 4affe36..14b2fde 100644
--- a/genQC/dataset/dataset_helper.py
+++ b/genQC/dataset/dataset_helper.py
@@ -1,15 +1,17 @@
+"""Some comonly used functions for datasets."""
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/dataset/dataset_helper.ipynb.
# %% auto 0
-__all__ = ['check_duplicate_in_dataset', 'check_duplicates_in_dataset_python', 'check_duplicates_in_dataset',
- 'shuffle_tensor_dataset', 'get_unique_elements_indices', 'uniquify_tensor_dataset', 'balance_tensor_dataset',
- 'map_old_tensor_to_new']
+__all__ = ['check_duplicate_in_dataset', 'check_duplicates_in_dataset', 'shuffle_tensor_dataset', 'get_unique_elements_indices',
+ 'uniquify_tensor_dataset', 'balance_tensor_dataset']
-# %% ../../src/dataset/dataset_helper.ipynb 3
+# %% ../../src/dataset/dataset_helper.ipynb 2
from ..imports import *
-from ..config_loader import *
+from ..utils.config_loader import *
+from ..utils.async_fn import run_parallel_jobs
-# %% ../../src/dataset/dataset_helper.ipynb 5
+# %% ../../src/dataset/dataset_helper.ipynb 4
def check_duplicate_in_dataset(x, dataset):
"""Check if 'x' is in 'dataset'"""
# x ... [ *]
@@ -18,32 +20,9 @@ def check_duplicate_in_dataset(x, dataset):
comp = (dataset==x)
comp = torch.reshape(comp, [comp.shape[0], -1])
comp = torch.all(comp, dim=1)
-
- num = comp.nonzero().squeeze().numel()
- return bool(num)
+ return comp.any().item()
-# %% ../../src/dataset/dataset_helper.ipynb 6
-def check_duplicates_in_dataset_python(xs, dataset):
- cnt = 0
-
- raise NotImplementedError("")
-
- # f = lambda x: int(check_duplicate_in_dataset(x, dataset))
- # res = async_loop_consumer(f, xs)
- # cnt = sum(res)
-
- comp = []
-
- for i,x in enumerate(xs):
- if check_duplicate_in_dataset(x, dataset):
- #print(f"[INFO] Duplicate in dataset at index={i}")
- comp.append(i)
- cnt += 1
- # print(f"[INFO] Found {cnt}/{xs.shape[0]} duplicates in dataset of {dataset.shape[0]}.")
-
- return cnt, comp
-
-# %% ../../src/dataset/dataset_helper.ipynb 7
+# %% ../../src/dataset/dataset_helper.ipynb 5
def check_duplicates_in_dataset(xs, dataset, return_ind=False, invert=False):
'''
Checks if `xs` is are `dataset`. Boolean `invert` changes if we count duplicates (False) or ones that are not in dataset (True).
@@ -71,39 +50,56 @@ def get_comp(x, dataset):
comp = comp.nonzero()
num = comp.shape[0]
- # except Exception as er:
- # print("[WARNING] check_duplicates_in_dataset:", er)
- # print("We will use python instead.")
- # raise NotImplementedError("")
- # # cnt, comp = check_duplicates_in_dataset_python(xs, dataset)
-
if return_ind: return num, comp.squeeze() #comp is [i_xs, i_dataset] pairs
return num
-# %% ../../src/dataset/dataset_helper.ipynb 11
-def shuffle_tensor_dataset(x, y=None, *z):
+# %% ../../src/dataset/dataset_helper.ipynb 9
+def shuffle_tensor_dataset(x, y=None, *z, cpu_copy=True):
'''Assumes numpy or tensor objects with same length.'''
rand_indx = torch.randperm(x.shape[0])
if exists(y):
assert x.shape[0] == y.shape[0]
- for iz in z: assert x.shape[0] == iz.shape[0]
- return x[rand_indx], y[rand_indx], *(iz[rand_indx] for iz in z)
+ for iz in z: assert x.shape[0] == iz.shape[0]
+
+
+ if cpu_copy:
+
+ def _cpu_array_index(var):
+ if type(var) == np.ndarray:
+ var = var[rand_indx]
+ else:
+ device = var.device
+ var = var.to("cpu")
+ var = var[rand_indx]
+ var[:] = var.to(device)
+ return var
+
+ x = _cpu_array_index(x)
+ y = _cpu_array_index(y)
+ z = (_cpu_array_index(iz) for iz in z)
+
+ return x, y, *z
+
+ else:
+ return x[rand_indx], y[rand_indx], *(iz[rand_indx] for iz in z)
return x[rand_indx]
-# %% ../../src/dataset/dataset_helper.ipynb 12
+# %% ../../src/dataset/dataset_helper.ipynb 10
def get_unique_elements_indices(tensor):
'''Returns indices of unique_elements in `tensor`.'''
tensor_unique, ptrs, cnt = torch.unique(tensor, dim=0, return_inverse=True, return_counts=True)
- _, ind_sorted = torch.sort(ptrs, stable=True) #e.g. gets the index that points to zero at pos [0]
+ _, ind_sorted = torch.sort(ptrs, dim=0, stable=True) #e.g. gets the index that points to zero at pos [0]
- cum_sum = cnt.cumsum(0)
- cum_sum = torch.cat((torch.tensor([0], device=tensor.device), cum_sum[:-1]))
-
- return tensor_unique, ind_sorted[cum_sum]
+ cum_sum = cnt.cumsum(dim=0)
+ cum_sum = torch.cat([torch.tensor([0], device=tensor.device), cum_sum[:-1]], dim=0)
+
+ idx = ind_sorted[cum_sum].cpu()
+
+ return tensor[idx], idx
-# %% ../../src/dataset/dataset_helper.ipynb 13
+# %% ../../src/dataset/dataset_helper.ipynb 11
def uniquify_tensor_dataset(x, y=None, *z):
'''`x` has to be tensor, assumes numpy or tensor obj for `y` and `z`'''
x, x_idx = get_unique_elements_indices(x)
@@ -116,48 +112,66 @@ def uniquify_tensor_dataset(x, y=None, *z):
return x
-# %% ../../src/dataset/dataset_helper.ipynb 14
-def balance_tensor_dataset(x, y, *z, samples: int=None, make_unique: bool=True, y_uniques=None, shuffle_lables: bool=True, add_balance_fn: callable=None):
+# %% ../../src/dataset/dataset_helper.ipynb 12
+def balance_tensor_dataset(x, y, *z, samples: int=None, make_unique: bool=True, y_uniques=None, shuffle_lables: bool=True, add_balance_fn: callable=None, njobs=1):
'''Assumes `x` is tensor and `y` is tensor or numpy.'''
y_type = type(y)
assert y_type in [np.ndarray, torch.Tensor]
+
+ print(f" - balance_tensor_dataset, {njobs=}, number of samples={x.shape[0]}")
#------------------------------
if make_unique:
x, y, *z = uniquify_tensor_dataset(x, y, *z)
assert x.shape[0] == y.shape[0]
+
+ print(f" - uniquify_tensor_dataset, number of samples now {x.shape[0]}")
#bcs unique sorts, we need to shuffle the dataset before picking the first 'samples' entries
x, y, *z = shuffle_tensor_dataset(x, y, *z)
#------------------------------
+
+ search_y = y_uniques if exists(y_uniques) else y
- if y_type == np.ndarray: y_uniques_temp, y_uniques_cnt = np.unique(y, return_counts=True, axis=0)
- else: y_uniques_temp, y_uniques_cnt = torch.unique(y, return_counts=True, dim=0)
+ if y_type == np.ndarray: _, y_ptrs, y_uniques_cnt = np.unique(search_y, return_counts=True, return_inverse=True, axis=0)
+ else: _, y_ptrs, y_uniques_cnt = torch.unique(search_y, return_counts=True, return_inverse=True, dim=0)
- if y_uniques is None: y_uniques = y_uniques_temp
- if samples is None:
+ if not exists(samples):
if y_type == np.ndarray: samples = np.min(y_uniques_cnt) # the actual balancing count
else: samples = torch.min(y_uniques_cnt)
+ print(f" - balancing")
+ # ToDo: make parallel
+
ind = list()
- for y_unique in y_uniques:
+ # for y_unique in tqdm(y_uniques, total=y_uniques.shape[0]):
+ for y_ptr_index in tqdm(range(y_uniques_cnt.shape[0]), total=y_uniques_cnt.shape[0]):
if y_type == np.ndarray:
- comp = (y==y_unique)
+ comp = (y_ptrs==y_ptr_index)
indices = np.squeeze(np.nonzero(comp))
indices = indices if indices.ndim > 0 else indices[None]
- else:
- comp = torch.all(y==y_unique, dim=1)
- indices = comp.nonzero().squeeze().cpu()
+ else:
+ comp = (y_ptrs==y_ptr_index)
+
+ indices = comp.nonzero().squeeze() #.cpu()
indices = indices if indices.dim() > 0 else indices[None]
-
+
#special add balncing, e.g., for circuit length
if add_balance_fn is not None: indices = add_balance_fn(indices, x, y, *z)
-
+
+ if not y_type == np.ndarray: indices = indices.cpu()
+
+ indices = shuffle_tensor_dataset(indices)
+
+ #fixes bug: shuffle_tensor_dataset removes dim if numpy array only has 1 element!
+ if y_type == np.ndarray: indices = indices if indices.ndim > 0 else indices[None]
+ else: indices = indices if indices.dim() > 0 else indices[None]
+
ind.append(indices[:samples]) #limit samples
if y_type == np.ndarray: ind = np.concatenate(ind, axis=0)
@@ -171,23 +185,3 @@ def balance_tensor_dataset(x, y, *z, samples: int=None, make_unique: bool=True,
if shuffle_lables: xb, yb, *zb = shuffle_tensor_dataset(xb, yb, *zb)
return xb, yb, *zb
-
-# %% ../../src/dataset/dataset_helper.ipynb 16
-def map_old_tensor_to_new(x):
- raise DeprecationWarning("[WARNING] There really should be no more old tensors arround .... delete them")
- print("[WARNING] There really should be no more old tensors arround .... delete them")
-
- b, gc, bits, t = x.shape
-
- x = x.reshape((b, gc//3, 3, bits, t)) # [b, g-c, bits, t] -> [b, g, c, bits, t]
- x = torch.argmax(x, dim=2) # [b, g, c, bits, t]-> [b, g, bits, t]
-
- gate = torch.concat([torch.zeros_like(x[:,:1]), x], dim=1) # add zeros for empty token
- gate = torch.argmax(gate, dim=1)
-
- control_target = torch.sum(x, dim=1)
- mapped_tensor = torch.zeros_like(control_target)
- mapped_tensor[control_target==1] = -1
- mapped_tensor[control_target==2] = 1
-
- return gate * mapped_tensor # is now [b, space, time] with elements +-gate_number
diff --git a/genQC/dataset/mixed_cached_dataset.py b/genQC/dataset/mixed_cached_dataset.py
new file mode 100644
index 0000000..0309126
--- /dev/null
+++ b/genQC/dataset/mixed_cached_dataset.py
@@ -0,0 +1,314 @@
+"""Dataset that combines and handles multiple cached datasets."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/dataset/mixed_cached_dataset.ipynb.
+
+# %% auto 0
+__all__ = ['MixedCachedOpenCLIPDatasetConfig', 'MixedCachedOpenCLIPDataset']
+
+# %% ../../src/dataset/mixed_cached_dataset.ipynb 3
+from ..imports import *
+from .cached_dataset import CachedOpenCLIPDataset, CachedOpenCLIPDatasetConfig, ConfigDataset
+from .dataset_helper import *
+from ..utils.misc_utils import DataLoaders, MemoryCleaner
+from tensordict import TensorDict
+
+# %% ../../src/dataset/mixed_cached_dataset.ipynb 4
+@dataclass
+class MixedCachedOpenCLIPDatasetConfig(CachedOpenCLIPDatasetConfig):
+ pad_constant: int
+ collate_fn: str
+ bucket_batch_size: int
+ model_scale_factor: int
+
+# %% ../../src/dataset/mixed_cached_dataset.ipynb 5
+class MixedCachedOpenCLIPDataset(CachedOpenCLIPDataset):
+ """Dataset that uses multiple cached dataset and combines them with padding, either i) Bucket or ii) Max."""
+
+ req_params = [f.name for f in dataclasses.fields(MixedCachedOpenCLIPDatasetConfig)]
+
+ #-----------------------------------
+ @property
+ def params_config(self):
+ params_config = super().params_config
+ if type(self) == MixedCachedOpenCLIPDataset:
+ params_config = MixedCachedOpenCLIPDatasetConfig(**params_config)
+ return params_config
+
+ #-----------------------------------
+ # functions to combine multiple datasets together
+
+ @classmethod
+ def _preprocess_datasets(dataset_cls, datasets, device, balance_maxes, max_samples, shuffle,
+ make_unique, pad_constant, model_scale_factor, parameters, **kwargs):
+ xs = []
+ ys = []
+ zs = []
+ cs = []
+
+ if isinstance(max_samples, int):
+ max_samples = [max_samples] * len(datasets)
+ else:
+ assert isinstance(max_samples, (list, np.ndarray))
+
+ if isinstance(balance_maxes, int):
+ balance_maxes = [balance_maxes] * len(datasets)
+ else:
+ assert isinstance(balance_maxes, (list, np.ndarray))
+
+ for i, (dataset, balance_max) in tqdm(enumerate(zip(datasets, balance_maxes)), total=len(datasets)):
+
+ x, y, z, *c = dataset_cls._preprocess_dataset(dataset, device, balance_max, max_samples, i, shuffle, make_unique, pad_constant, model_scale_factor, parameters, **kwargs)
+ MemoryCleaner.purge_mem()
+
+ #combine datasets
+ xs.append(x.cpu())
+ ys.append(y)
+ zs.append(z.cpu())
+ cs.append([ic.cpu() for ic in c])
+
+ del x
+ del y
+ del z
+ del c
+
+ for k in datasets[i].store_dict.keys():
+ setattr(datasets[i], str(k), None)
+ del dataset
+
+ MemoryCleaner.purge_mem()
+
+ return xs, ys, zs, cs
+
+ @staticmethod
+ def _add_missing_conditions(parameters, dataset, c, batch_size, device):
+ # if c is missing something of the union we set it to a zero tensor, e.g. used for combining SRV with compilation
+ c_temp = []
+ c_temp_index = 0
+
+ for k,v in parameters["store_dict"].items():
+ if k != "x" and k != "y" and k != "z":
+ if k not in dataset.params_config.store_dict:
+ empty_tensor = torch.zeros((1,), device=device)
+
+ if k == "U": #scetchy hardcoded for compilation
+ empty_tensor = torch.zeros((batch_size, 2, 1, 1), device=device) # unitary is [b, Re/Im, 2^n, 2^n]
+
+ c_temp.append(empty_tensor)
+
+ else: # done to conserve the ordering of c args!!!
+ c_temp.append(c[c_temp_index])
+ c_temp_index += 1
+
+ return c_temp
+
+ @staticmethod
+ def _reorder_to_buckets(parameters, bucket_batch_size, xs, ys, zs, cs):
+ for i, (xi,yi,zi, ci) in enumerate(zip(xs, ys, zs, cs)): #cut rest of batch
+ b_mult = int(np.floor(xi.shape[0] / bucket_batch_size) * bucket_batch_size)
+
+ xs[i] = xi[None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *xi.shape[1:]))
+ zs[i] = zi[None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *zi.shape[1:]))
+
+ v = parameters["store_dict"]["y"]
+ if v == "tensor" or v == "numpy":
+ ys[i] = yi[None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *yi.shape[1:]))
+ else: raise NotImplementedError("")
+
+ #----
+ #For U, etc
+ add_ind = 0
+ for k,v in parameters["store_dict"].items():
+ if k != "x" and k != "y" and k != "z":
+ if v == "tensor" or v == "numpy":
+ cs[i][add_ind] = ci[add_ind][None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *ci[add_ind].shape[1:]))
+ else: raise NotImplementedError("")
+ add_ind += 1
+
+ return xs, ys, zs, cs
+
+ @staticmethod
+ def _pad_conditions(parameters, bucket_batch_size, c, unitary_pad=None, params_pad=None, pad_with_memmap=False):
+ ci_list = []
+ ci_k_list = []
+
+ memmap_cleans = [] #TensorDicts and paths we need to delete later
+
+ def _alloc_mem(shape, k, c0_add_ind):
+ # allocating zeros is better memory wise than torch.cat(ci_s) and F.pad(ci, pad, "constant", 0)
+ mem = np.prod(shape) * c0_add_ind.element_size() / (1024*1024*1024)
+ print(f"[INFO]: allocate memory for {k} {shape} on {c0_add_ind.device} approx. {mem:.3f} GB")
+
+ if pad_with_memmap:
+ prefix_path = f"tmp_DELETE_pad_conditions_MixedCachedOpenCLIPDataset_{k}"
+ print(f"[INFO]: (MixedCachedOpenCLIPDataset._pad_conditions): {pad_with_memmap=} allocating TensorDict using memmap_like at {prefix_path}")
+
+ b, *_ = shape
+ tensor_dict = TensorDict({"ci_s": torch.empty(shape, dtype=c0_add_ind.dtype),
+ }, batch_size=[b])
+ tensor_dict = tensor_dict.memmap_like(prefix=prefix_path)
+
+ ci_s = tensor_dict["ci_s"]
+ memmap_cleans.append((tensor_dict, prefix_path))
+ else:
+ ci_s = torch.zeros(shape, device=c0_add_ind.device, dtype=c0_add_ind.dtype)
+
+ return ci_s
+
+ add_ind = 0
+ for k,v in parameters["store_dict"].items():
+ if k != "x" and k != "y" and k != "z":
+
+ if v == "tensor" and k == "U": # hardcoded U padding !!
+ assert exists(unitary_pad) and isinstance(unitary_pad, int)
+
+ n = sum([ci[add_ind].shape[0] for ci in c])
+ if bucket_batch_size > 0: shape = (n, bucket_batch_size, 2, unitary_pad, unitary_pad)
+ else: shape = (n, 2, unitary_pad, unitary_pad)
+
+ ci_s = _alloc_mem(shape, k, c[0][add_ind])
+
+ #tensor product pad, else was zero pad
+ if 1:
+ run_i = 0
+ for i,ci in enumerate(c):
+ ci = ci[add_ind]
+
+ assert ci.shape[-2]==ci.shape[-1]
+ U_side = ci.shape[-2]
+ for jj in range(unitary_pad//U_side):
+ ci_s[run_i:run_i+ci.shape[0], ..., U_side*jj:U_side*(jj+1), U_side*jj:U_side*(jj+1)] = ci.to(ci_s.device)
+
+ run_i += ci.shape[0]
+
+ ci_list.append(ci_s)
+ ci_k_list.append(k)
+
+ add_ind += 1
+ continue
+
+ elif v == "tensor" and k == "params": # hardcoded paramter padding !!
+ assert exists(params_pad) #and len(list(params_pad))==2
+
+ n = sum(ci[add_ind].shape[0] for ci in c)
+ if bucket_batch_size > 0: shape = (n, bucket_batch_size, *params_pad)
+ else: shape = (n, *params_pad)
+
+ ci_s = _alloc_mem(shape, k, c[0][add_ind])
+
+ elif v == "numpy": raise NotImplementedError("")
+ else: raise NotImplementedError("")
+
+
+ run_i = 0
+ for i,ci in enumerate(c):
+ ci = ci[add_ind]
+ ci_s[run_i:run_i+ci.shape[0], ..., :ci.shape[-2], :ci.shape[-1]] = ci
+ run_i += ci.shape[0]
+
+ ci_list.append(ci_s)
+ ci_k_list.append(k)
+
+ add_ind += 1
+
+ return ci_list, ci_k_list, memmap_cleans
+
+ @classmethod
+ def _create_train_valid_datasets(dataset_cls, device, parameters, test_split, x, y, z, ci_list, ci_k_list, shuffle: bool = True):
+ splits = max(int(x.shape[0] * test_split), 1)
+
+ if shuffle:
+ x, y, z, *ci_list = shuffle_tensor_dataset(x, y, z, *ci_list)
+
+ x, x_test = x[splits:], x[:splits]
+ y, y_test = y[splits:], y[:splits]
+ z, z_test = z[splits:], z[:splits]
+
+ print(f"Split: Train {x.shape[0]} - Test {x_test.shape[0]} \n")
+
+ dataset = dataset_cls(device, **parameters)
+ dataset.x = x
+ dataset.y = y
+ dataset.z = z
+
+ dataset_test = dataset_cls(device, **parameters)
+ dataset_test.x = x_test
+ dataset_test.y = y_test
+ dataset_test.z = z_test
+
+ for ci, k in zip(ci_list, ci_k_list):
+ ci, ci_test = ci[splits:], ci[:splits]
+
+ setattr(dataset , str(k), ci)
+ setattr(dataset_test, str(k), ci_test)
+
+ return dataset, dataset_test
+
+ #-----------------------------------
+
+ def get_dataloaders(self, batch_size, text_encoder, p_valid=0.1, y_on_cpu=False, return_tensor_datasets=False, shuffle=True, shuffle_cpu_copy=True, caching=True):
+ #-------------------------
+ # caching
+
+ self.text_encoder = text_encoder
+
+ print("[DEBUG]: run get_dataloaders.x_y_preprocess", flush=True)
+ x_proc, y_proc, *z_proc = ConfigDataset.x_y_preprocess(self,
+ balance_max=None,
+ shuffle=False,
+ max_samples=None,
+ make_unique=False) # ... z_proc is `'z' and all other 'c'
+ if caching:
+ if self.bucket_batch_size <= 0:
+ y_proc = self.caching(y_proc, y_on_cpu=y_on_cpu)
+
+ else:
+ y_proc = self.caching([yi.reshape((-1)) for yi in y_proc], y_on_cpu=y_on_cpu)
+ y_proc = y_proc.reshape((-1, self.bucket_batch_size))
+
+ #-------------------------
+ # valid split and to device
+
+ print("[DEBUG]: run get_dataloaders.valid_split", flush=True)
+ x, x_valid, y, y_valid, (z, z_valid) = self.valid_split(x_proc, y_proc, *z_proc, p_valid=p_valid, y_type="tensor", split_sequential=False)
+
+ if self.params_config.dataset_to_gpu:
+ x, x_valid = x.to("cuda"), x_valid.to("cuda")
+ z, z_valid = list(iz.to("cuda") for iz in z), list(iz_valid.to("cuda") for iz_valid in z_valid)
+
+ if not y_on_cpu:
+ y, y_valid = y.to("cuda"), y_valid.to("cuda")
+
+ #-------------------------
+ # create dataloaders
+
+ ds = TensorDataset(x, y, *z)
+ ds_valid = TensorDataset(x_valid, y_valid, *z_valid)
+
+ if return_tensor_datasets:
+ return ds, ds_valid
+
+ if isinstance(self.collate_fn, str):
+ collate_fn = getattr(self, self.collate_fn, None)
+ else:
+ collate_fn = self.collate_fn
+
+ if not exists(collate_fn):
+ print("[WARNING]: self.collate_fn does not exist, using torch.utils.data.default_collate.")
+ collate_fn = torch.utils.data.default_collate
+
+ if self.params_config.dataset_to_gpu:
+ train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True, collate_fn=collate_fn)
+ valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True, collate_fn=collate_fn)
+
+ else:
+ train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=4, collate_fn=collate_fn)
+ valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=4, collate_fn=collate_fn)
+
+ self.dataloaders = DataLoaders(train_loader, valid_loader)
+ return self.dataloaders
+
+ #-----------------------------------
+
+ @staticmethod
+ def from_datasets(*args, **kwargs):
+ raise NotImplementedError()
diff --git a/genQC/dataset/mixed_cached_qc_dataset.py b/genQC/dataset/mixed_cached_qc_dataset.py
deleted file mode 100644
index 754f2b3..0000000
--- a/genQC/dataset/mixed_cached_qc_dataset.py
+++ /dev/null
@@ -1,556 +0,0 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/dataset/mixed_cached_qc_dataset.ipynb.
-
-# %% auto 0
-__all__ = ['Mixed_Cached_OpenClip_Dataset_config', 'Mixed_Cached_OpenClip_Dataset']
-
-# %% ../../src/dataset/mixed_cached_qc_dataset.ipynb 3
-from ..imports import *
-from .qc_dataset import Qc_Config_Dataset_config, Qc_Config_Dataset
-from .config_dataset import Config_Dataset
-from .cached_qc_dataset import Cached_OpenClip_Dataset
-from ..config_loader import *
-from .dataset_helper import *
-from ..util import DataLoaders
-import dataclasses
-
-# %% ../../src/dataset/mixed_cached_qc_dataset.ipynb 4
-@dataclass
-class Mixed_Cached_OpenClip_Dataset_config(Qc_Config_Dataset_config):
- pad_constant: int
- collate_fn: str
- bucket_batch_size: int
- num_down_scales: int # for flex pad attn mask
-
-# %% ../../src/dataset/mixed_cached_qc_dataset.ipynb 5
-class Mixed_Cached_OpenClip_Dataset(Cached_OpenClip_Dataset):
- """Dataset that uses multiple cached dataset and combines them with padding, either i) Bucket or ii) Max. Also provides a corresponding `collate_fn` for training."""
-
- req_params = [f.name for f in dataclasses.fields(Mixed_Cached_OpenClip_Dataset_config)]
-
- cut_multiple = 4 #needed for proper downscaling!
-
- @property
- def params_config(self):
- params_config = {}
- for p in self.req_params: params_config[p] = getattr(self, p)
- params_config["gate_pool"] = [class_to_str(gate) for gate in params_config["gate_pool"]]
- params_config = Mixed_Cached_OpenClip_Dataset_config(**params_config)
- return params_config
-
- #-----------------------------------
- # CAUSAL ATTENTION PADDING
-
- def flexPadAttn_padding_collate_fn(self, b):
- """this function is called for training for every batch"""
- z_0 = max(x[2][0] for x in b) # space
- z_1 = max(x[2][1] for x in b) # time
-
- #round time to next multiple of 8 for conv layers!
- z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)
-
- #---------------
- # key_padding_mask ... [N, S] -inf where we want no attention
- # we will create here [N, s, t] and then reshaping is easy
- # note this is key pad mask not directly attention mask! we need this for loss masking
- # Nb: add rnd to the padding, so we train with pad and on smaller systems
-
- #we need 3 different ones for the different unet layers
- key_padding_mask = torch.zeros((len(b), z_0, z_1), device=self.device)
-
- padd_rnds = torch.randint(low=0, high=2, size=(len(b),2), dtype=torch.int32) #roll 50/50 if we allow padding
-
- xs=[]
- ys=[]
- for i,((x,y,z), padd_rnd) in enumerate(zip(b, padd_rnds)):
- # for i,(x,y,z) in enumerate(b):
- x = x[:z_0, :z_1] # cut down to max [bits, time] of batch
-
- #-------------------
- space, time = z[0], z[1]
-
- if space < z_0 and padd_rnd[0]: space = torch.randint(low=space, high=z_0+1, size=(1,), dtype=torch.int32)
- if time < z_1 and padd_rnd[1]: time = torch.randint(low=time , high=z_1+1, size=(1,), dtype=torch.int32)
-
- time = (torch.ceil(time / self.cut_multiple) * self.cut_multiple).to(torch.int32)
-
- key_padding_mask[i, space:, :] = float('-inf')
- key_padding_mask[i, :, time:] = float('-inf')
-
- #-------------------
-
- xs.append(x)
- ys.append(y)
-
- key_padding_mask_list = [key_padding_mask]
- for j in range(1, self.num_down_scales):
- key_padding_mask_list.append(F.max_pool1d(key_padding_mask_list[j-1], kernel_size=2))
-
- xs=torch.stack(xs)
- ys=torch.stack(ys)
- return xs, ys, key_padding_mask_list
-
- def flexPadAttn_TimeOnly_padding_collate_fn(self, b):
- """this function is called for training for every batch"""
- z_0 = max(x[2][0] for x in b) # space
- z_1 = max(x[2][1] for x in b) # time
-
- #round time to next multiple of 8 for conv layers!
- z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)
-
- #---------------
- # key_padding_mask ... [N, S] -inf where we want no attention
- # we will create here [N, s, t] and then reshaping is easy
- # note this is key pad mask not directly attention mask! we need this for loss masking
- # Nb: add rnd to the padding, so we train with pad and on smaller systems
-
- #we need 3 different ones for the different unet layers
- key_padding_mask = torch.zeros((len(b), z_0, z_1), device=self.device)
-
- padd_rnds = torch.randint(low=0, high=2, size=(len(b)), dtype=torch.int32) #roll 50/50 if we allow padding
-
- xs=[]
- ys=[]
- for i,((x,y,z), padd_rnd) in enumerate(zip(b, padd_rnds)):
- # for i,(x,y,z) in enumerate(b):
- x = x[:z_0, :z_1] # cut down to max [bits, time] of batch
-
- #-------------------
- time = z[1]
-
- if time < z_1 and padd_rnd: time = torch.randint(low=time , high=z_1+1, size=(1,), dtype=torch.int32)
- time = (torch.ceil(time / self.cut_multiple) * self.cut_multiple).to(torch.int32)
- key_padding_mask[i, :, time:] = float('-inf')
-
- #-------------------
-
- xs.append(x)
- ys.append(y)
-
- key_padding_mask_list = [key_padding_mask]
- for j in range(1, self.num_down_scales):
- key_padding_mask_list.append(F.max_pool1d(key_padding_mask_list[j-1], kernel_size=2))
-
- xs=torch.stack(xs)
- ys=torch.stack(ys)
- return xs, ys, key_padding_mask_list
-
- #-----------------------------------
- # BUCKET PADDING, all x,y are already passed as batch
-
- def cut_padding_Bucket_collate_fn(self, b):
- """this function is called for training for every batch"""
-
- b = b[0]
-
- x = b[0]
- y = b[1]
- z = b[2]
-
- #---------------
-
- z_0 = torch.max(z[:, 0]) # space
- z_1 = torch.max(z[:, 1]) # time
-
- #round time to next multiple of cut_multiple for conv layers!
- z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)
-
- #---------------
-
- x = x[:, :z_0, :z_1] # cut down to max [b, bits, time] of batch
-
- return x, y
-
- def cut_padding_Bucket_collate_fn_compilation(self, b):
- """this function is called for training for every batch"""
-
- b = b[0]
-
- x = b[0]
- y = b[1]
- U = b[2]
- z = b[3]
-
- #---------------
-
- z_0 = torch.max(z[:, 0]) # space
- z_1 = torch.max(z[:, 1]) # time
-
- #round time to next multiple of cut_multiple for conv layers!
- z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)
-
- #---------------
-
- x = x[:, :z_0, :z_1] # cut down to max [b, bits, time] of batch
-
- bit_exp = 2**z_0
- U = U[:, :, :bit_exp, :bit_exp] # [b, Re/Im, 2^n, 2^n]
-
- return x, y, U
-
- def cut_padding_Bucket_collate_fn_compilation_params(self, b):
- """this function is called for training for every batch, order in b is store dict"""
-
- b = b[0] # {'x': 'tensor', 'y': 'numpy', 'params': 'tensor', 'U': 'tensor', 'z': 'tensor'}
-
- x = b[0]
- y = b[1]
- p = b[2]
- U = b[3]
- z = b[4]
-
- #---------------
-
- z_0 = torch.max(z[:, 0]) # space
- z_1 = torch.max(z[:, 1]) # time
-
- #round time to next multiple of cut_multiple for conv layers!
- z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)
-
- #---------------
-
- x = x[:, :z_0, :z_1] # cut down to max [b, bits, time] of batch
-
- p = p[:, :, :z_1]
-
- bit_exp = 2**z_0
- U = U[:, :, :bit_exp, :bit_exp] # [b, Re/Im, 2^n, 2^n]
-
- return x, y, p, U
-
- #-----------------------------------
- # MAX PADDING, x are passes as sampled list (batch), std collate them
-
- def cut_padding_collate_fn(self, b):
- """this function is called for training for every batch"""
- z_0 = max(x[2][0] for x in b) # space
- z_1 = max(x[2][1] for x in b) # time
-
- #round time to next multiple of cut_multiple for conv layers!
- z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)
-
- #---------------
-
- x_sample = b[0][0]
- xs = torch.zeros((len(b), z_0, z_1), dtype=x_sample.dtype, device=x_sample.device)
-
- # xs=[]
- ys=[]
- for i,(x,y,z) in enumerate(b):
- #x = x[:z_0, :z_1] # cut down to max [bits, time] of batch
- xs[i] = x[:z_0, :z_1]
-
- #xs.append(x)
- ys.append(y)
-
- #xs=torch.stack(xs)
- ys=torch.stack(ys)
-
- return xs, ys
-
- def cut_padding_collate_fn_compilation(self, b):
- """this function is called for training for every batch"""
- z_0 = max(x[3][0] for x in b) # space
- z_1 = max(x[3][1] for x in b) # time
-
- #round time to next multiple of cut_multiple for conv layers!
- z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)
-
- bit_exp = 2**z_0
-
- #---------------
-
- x_sample = b[0][0]
- xs = torch.zeros((len(b), z_0, z_1), dtype=x_sample.dtype, device=x_sample.device)
-
- y_sample = b[0][1]
- ys = torch.zeros((len(b), *y_sample.shape), dtype=y_sample.dtype, device=y_sample.device)
-
- U_sample = b[0][2]
- Us = torch.zeros((len(b), 2, bit_exp, bit_exp), dtype=U_sample.dtype, device=U_sample.device)
-
- for i,(x,y,U,z) in enumerate(b):
- xs[i] = x[:z_0, :z_1]
- ys[i] = y
- Us[i] = U[:, :bit_exp, :bit_exp]
-
- return xs, ys, Us
-
- def cut_padding_collate_fn_compilation_params(self, b):
- """this function is called for training for every batch, order in b is store dict"""
- # {'x': 'tensor', 'y': 'numpy', 'params': 'tensor', 'U': 'tensor', 'z': 'tensor'}
-
- z_0 = max(x[4][0] for x in b) # space
- z_1 = max(x[4][1] for x in b) # time
-
- #round time to next multiple of cut_multiple for conv layers!
- z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)
-
- bit_exp = 2**z_0
-
- #---------------
-
- x_sample = b[0][0]
- xs = torch.zeros((len(b), z_0, z_1), dtype=x_sample.dtype, device=x_sample.device)
-
- y_sample = b[0][1]
- ys = torch.zeros((len(b), *y_sample.shape), dtype=y_sample.dtype, device=y_sample.device)
-
- p_sample = b[0][2]
- ps = torch.zeros((len(b), p_sample.shape[-2], z_1), dtype=p_sample.dtype, device=p_sample.device)
-
- U_sample = b[0][3]
- Us = torch.zeros((len(b), 2, bit_exp, bit_exp), dtype=U_sample.dtype, device=U_sample.device)
-
- for i,(x,y,p,U,z) in enumerate(b):
- xs[i] = x[:z_0, :z_1]
- ys[i] = y
- ps[i] = p[:, :z_1]
- Us[i] = U[:, :bit_exp, :bit_exp]
-
- return xs, ys, ps, Us
-
- #-----------------------------------
-
- def get_dataloaders(self, batch_size, text_encoder, p_valid=0.1, y_on_cpu=False):
- self.text_encoder = text_encoder
-
- excepts = []
- if y_on_cpu: excepts.append("y")
- if self.params_config.dataset_to_gpu: self.to("cuda", excepts=excepts)
-
- x_proc, y_proc, *z_proc = Qc_Config_Dataset.x_y_preprocess(self, balance_max=None, shuffle=False) # ... z_proc is `'z' and all other 'c'
-
- if self.bucket_batch_size <= 0:
- y_proc = self.caching(y_proc, y_on_cpu=y_on_cpu)
-
- else:
- y_proc = self.caching([yi.reshape((-1)) for yi in y_proc], y_on_cpu=y_on_cpu)
- y_proc = y_proc.reshape((-1, self.bucket_batch_size))
-
- x_proc, y_proc, *z_proc = shuffle_tensor_dataset(x_proc, y_proc, *z_proc) #only possible after str y is cached as tensor
- x, x_valid, y, y_valid, (z, z_valid) = self.valid_split(x_proc, y_proc, *z_proc, p_valid=p_valid)
-
- ds = TensorDataset(x, y, *z)
- ds_valid = TensorDataset(x_valid, y_valid, *z_valid)
-
- collate_fn = getattr(self, self.collate_fn)
-
- if self.params_config.dataset_to_gpu:
- train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True, collate_fn=collate_fn)
- valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True, collate_fn=collate_fn)
-
- else:
- train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=12, collate_fn=collate_fn)
- valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=12, collate_fn=collate_fn)
-
- self.dataloaders = DataLoaders(train_loader, valid_loader)
- return self.dataloaders
-
- #-----------------------------------
-
- @staticmethod
- def from_datasets(datasets: list[Qc_Config_Dataset], balance_maxes: list, pad_constant, device: torch.device=torch.device("cpu"), bucket_batch_size=None, max_samples=None, **parameters):
- assert pad_constant != 0, "can NOT be 0! and not any other gate!"
-
- xs = []
- ys = []
- zs = []
- cs = []
-
- cut_multiple = Mixed_Cached_OpenClip_Dataset.cut_multiple
-
- max_qubits = max(dataset.params_config.num_of_qubits for dataset in datasets)
- max_gates = max(dataset.params_config.max_gates for dataset in datasets)
- max_gates = int(np.ceil(max_gates /cut_multiple) * cut_multiple)
-
- parameters["num_of_qubits"] = max_qubits
- parameters["max_gates"] = max_gates
- parameters["random_samples"] = sum([dataset.params_config.random_samples for dataset in datasets])
- parameters["min_gates"] = min([dataset.params_config.min_gates for dataset in datasets])
- parameters["comment"] = f"Generated with 'from_datasets' with {len(datasets)} datasets. Qubits: {[dataset.params_config.num_of_qubits for dataset in datasets]}."
- parameters["pad_constant"] = pad_constant
- parameters["bucket_batch_size"] = bucket_batch_size
-
- parameters["store_dict"] = {}
- for dataset in datasets:
- parameters["store_dict"] |= dataset.params_config.store_dict #needs python 3.9 for union of dict
- parameters["store_dict"]["z"] = "tensor" #add special item
-
- if isinstance(max_samples, int):
- max_samples = [max_samples] * len(datasets)
- else:
- assert isinstance(max_samples, (list, np.ndarray))
- max_samples = np.array(max_samples, dtype=int)
-
- if isinstance(balance_maxes, int):
- balance_maxes = [balance_maxes] * len(datasets)
- else:
- assert isinstance(balance_maxes, (list, np.ndarray))
- balance_maxes = np.array(balance_maxes, dtype=int)
-
- for i, (dataset, balance_max) in tqdm(enumerate(zip(datasets,balance_maxes)), total=len(datasets)):
- # do x_y_preprocess now, we can't balance all together with mixed conditions
-
- dataset = dataset.to(device)
-
- x, y, *c = dataset.x_y_preprocess(balance_max=balance_max, max_samples=max_samples[i], shuffle=True)
- x = x.to(device) # [b, s, t]
-
- print(f" - dataset size after balancing {x.shape[0]}")
-
- #-------
- # store original size
- z = torch.zeros((x.shape[0], 2), device=device, dtype=torch.int32)
- z[:, 0] = max(dataset.params_config.num_of_qubits, 1)
-
- red_x = torch.sum(x.abs(), dim=1) # [b, t] .. collaps the zeros to get circuit length
- z[:, 1] = torch.count_nonzero(red_x, dim=1) # [b]
- z[z[:, 1]==0, 1] = 1 # make sure we don*t have 0, so we cheat and set it to 1 (there's only 1 unique zero gate circuit anyways). Needed for padding attn mask
-
- for i in range(x.shape[0]):
- x[i, z[i, 0]:, :] = pad_constant
- x[i, :, z[i, 1]:] = pad_constant
-
- z[:, 1] = (torch.ceil(z[:, 1] / cut_multiple) * cut_multiple).to(torch.int32) #for cut needs multiple
-
- #-------
- # now pad x, padding is defined from last dim forward!
- pad = (0, max_gates-dataset.params_config.max_gates, 0, max_qubits-dataset.params_config.num_of_qubits)
- x = F.pad(x, pad, "constant", pad_constant)
-
- # if c is missing something of the union we set it to a zero tensor
- for k,v in parameters["store_dict"].items():
- if k != "x" and k != "y" and k != "z":
-
- if k not in dataset.params_config.store_dict:
- empty_tensor = torch.zeros((1,), device=device)
-
- if k == "U": #scetchy hardcoded for compilation
- empty_tensor = torch.zeros((x.shape[0], 2, 1, 1), device=device) # unitary is [b, Re/Im, 2^n, 2^n]
-
- assert len(c) == 0
- c.append(empty_tensor) #scetchy bcs if c is not empty we could break ordering!!!
-
- #combine datasets
- xs.append(x.cpu())
- ys.append(y)
- zs.append(z)
- cs.append([*c])
-
- dataset = dataset.to("cpu") #helps with gpu mem overflowing
- #-----------------
-
- has_U = "U" in parameters["store_dict"]
- has_p = "params" in parameters["store_dict"]
-
- if bucket_batch_size > 0:
- collate_fn_name = Mixed_Cached_OpenClip_Dataset.cut_padding_Bucket_collate_fn.__name__
- if has_U:
- collate_fn_name = Mixed_Cached_OpenClip_Dataset.cut_padding_Bucket_collate_fn_compilation.__name__
- if has_p:
- collate_fn_name = Mixed_Cached_OpenClip_Dataset.cut_padding_Bucket_collate_fn_compilation_params.__name__
-
- else:
- collate_fn_name = Mixed_Cached_OpenClip_Dataset.cut_padding_collate_fn.__name__
- if has_U:
- collate_fn_name = Mixed_Cached_OpenClip_Dataset.cut_padding_collate_fn_compilation.__name__
- if has_p:
- collate_fn_name = Mixed_Cached_OpenClip_Dataset.cut_padding_collate_fn_compilation_params.__name__
-
- parameters["collate_fn"] = collate_fn_name
-
- #-----------------
- if bucket_batch_size > 0:
- for i, (xi,yi,zi, ci) in enumerate(zip(xs, ys, zs, cs)): #cut rest of batch
- b_mult = int(np.floor(xi.shape[0] / bucket_batch_size) * bucket_batch_size)
-
- xs[i] = xi[None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *xi.shape[1:]))
- zs[i] = zi[None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *zi.shape[1:]))
-
- t = parameters["store_dict"]["y"]
- if v == "tensor" or v == "numpy":
- ys[i] = yi[None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *yi.shape[1:]))
- else: raise NotImplementedError("")
-
- #----
- #For U, etc
- add_ind = 0
- for k,v in parameters["store_dict"].items():
- if k != "x" and k != "y" and k != "z":
- if v == "tensor" or v == "numpy":
- cs[i][add_ind] = ci[add_ind][None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *ci[add_ind].shape[1:]))
- else: raise NotImplementedError("")
- add_ind += 1
-
- x = torch.cat(xs)
- y = ys # torch.cat(ys) is wrong, y is list of numpy or str!! not a tensor
- z = torch.cat(zs)
- c = cs
-
- #-----------------
-
- mixed_Cached_OpenClip_Dataset = Mixed_Cached_OpenClip_Dataset(device, **parameters)
- mixed_Cached_OpenClip_Dataset.x = x
- mixed_Cached_OpenClip_Dataset.y = y
- mixed_Cached_OpenClip_Dataset.z = z
-
- add_ind = 0
- for k,v in parameters["store_dict"].items():
- if k != "x" and k != "y" and k != "z":
-
- if v == "tensor" and k == "U": # hardcoded U padding !!
-
- n = sum([ci[add_ind].shape[0] for ci in c])
- if bucket_batch_size > 0: shape = (n, bucket_batch_size, 2, 2**max_qubits, 2**max_qubits)
- else: shape = (n, 2, 2**max_qubits, 2**max_qubits)
-
- # allocating zeros is better memory wise than torch.cat(ci_s) and F.pad(ci, pad, "constant", 0)
- mem = np.prod(shape) * c[0][add_ind].element_size() * 1e-9
- print(f"[INFO]: allocate memory for {k} {shape} on {c[0][add_ind].device} approx. {mem:.3f} GB")
- ci_s = torch.zeros(shape, device=c[0][add_ind].device)
-
- run_i = 0
- for i,ci in enumerate(c):
- ci = ci[add_ind]
- if bucket_batch_size > 0: ci_s[run_i:run_i+ci.shape[0], :, :, :ci.shape[-2], :ci.shape[-1]] = ci
- else: ci_s[run_i:run_i+ci.shape[0], :, :ci.shape[-2], :ci.shape[-1]] = ci
- run_i += ci.shape[0]
-
- elif v == "tensor" and k == "params": # hardcoded paramter padding !!
-
- max_params = max(ci[add_ind].shape[-2] for ci in c)
-
- n = sum(ci[add_ind].shape[0] for ci in c)
- if bucket_batch_size > 0: shape = (n, bucket_batch_size, max_params, max_gates)
- else: shape = (n, max_params, max_gates)
-
- # allocating zeros is better memory wise than torch.cat(ci_s) and F.pad(ci, pad, "constant", 0)
- mem = np.prod(shape) * c[0][add_ind].element_size() * 1e-9
- print(f"[INFO]: allocate memory for {k} {shape} on {c[0][add_ind].device} approx. {mem:.3f} GB")
- ci_s = torch.zeros(shape, device=c[0][add_ind].device)
-
- run_i = 0
- for i,ci in enumerate(c):
- ci = ci[add_ind]
- if bucket_batch_size > 0: ci_s[run_i:run_i+ci.shape[0], :, :ci.shape[-2], :ci.shape[-1]] = ci
- else: ci_s[run_i:run_i+ci.shape[0], :ci.shape[-2], :ci.shape[-1]] = ci
- run_i += ci.shape[0]
-
- elif v == "numpy": raise NotImplementedError("")
- else: raise NotImplementedError("")
-
- setattr(mixed_Cached_OpenClip_Dataset, str(k), ci_s)
- add_ind += 1
-
- return mixed_Cached_OpenClip_Dataset
-
- #------------------------------------
-
- # def plot_example(self): print("plot_example not implemented for Mixed_Cached_OpenClip_Dataset")
- # def plot_distribution(self): print("plot_distribution not implemented for Mixed_Cached_OpenClip_Dataset")
-
- @staticmethod
- def from_config_file(config_path, device: torch.device, save_path: str=None):
- config = load_config(config_path)
- config["target"] = class_to_str(Mixed_Cached_OpenClip_Dataset)
- return Config_Dataset.from_config(config, device, save_path)
diff --git a/genQC/dataset/qc_dataset.py b/genQC/dataset/qc_dataset.py
deleted file mode 100644
index 077004d..0000000
--- a/genQC/dataset/qc_dataset.py
+++ /dev/null
@@ -1,170 +0,0 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/dataset/qc_dataset.ipynb.
-
-# %% auto 0
-__all__ = ['Qc_Config_Dataset_config', 'Qc_Config_Dataset']
-
-# %% ../../src/dataset/qc_dataset.ipynb 3
-from ..imports import *
-from .config_dataset import Config_Dataset, Config_Dataset_config
-from ..config_loader import *
-from .dataset_helper import *
-from ..platform.qcircuit_dataset_construction import decode_circuit
-from ..platform.simulation.qcircuit_sim import schmidt_rank_vector, instruction_name_to_qiskit_gate
-import qiskit.quantum_info as qi
-
-# %% ../../src/dataset/qc_dataset.ipynb 4
-@dataclass
-class Qc_Config_Dataset_config(Config_Dataset_config):
- optimized: bool
- dataset_to_gpu: bool
- random_samples: int
- num_of_qubits: int
- min_gates: int
- max_gates: int
- gate_pool: list[str]
-
-# %% ../../src/dataset/qc_dataset.ipynb 5
-class Qc_Config_Dataset(Config_Dataset):
- """Dataset for quantum circuits, access `gate_pool` directly and all other paras with `.params_config`"""
-
- req_params = [f.name for f in dataclasses.fields(Qc_Config_Dataset_config)]
- add_balance_fn = None
-
- def __init__(self, device: torch.device=torch.device("cpu"), **parameters):
- super().__init__(device, **parameters)
- self.gate_pool = parameters["gate_pool"] #[get_obj_from_str(gate) for gate in parameters["gate_pool"]]
-
- @property
- def params_config(self):
- params_config = super().params_config
- #params_config["gate_pool"] = [class_to_str(gate) for gate in params_config["gate_pool"]]
- params_config = Qc_Config_Dataset_config(**params_config)
- return params_config
-
- #----------------------------
-
- def x_y_preprocess(self, balance_max=None, shuffle=False, max_samples=None):
- #params_config = self.params_config
- #if params_config.dataset_to_gpu: self.to("cuda")
-
- z_proc = []
- for k,v in self.store_dict.items():
- if k != "x" and k != "y":
- z_proc.append(getattr(self, k))
-
- x_proc, y_proc = self.x, self.y
-
- #---------------------
- if shuffle:
- x_proc, y_proc, *z_proc = shuffle_tensor_dataset(x_proc, y_proc, *z_proc)
-
- if exists(max_samples):
- x_proc = x_proc[:max_samples]
- y_proc = y_proc[:max_samples]
- z_proc = (iz[:max_samples] for iz in z_proc)
-
- #---------------------
- t = self.store_dict["y"]
- if exists(balance_max):
- if t == "tensor" or t == "numpy": x_proc, y_proc, *z_proc = balance_tensor_dataset(x_proc, y_proc, *z_proc, make_unique=True,
- samples=balance_max, add_balance_fn=self.add_balance_fn)
- else: print(f"[WARNING]: Unsupported y type: `{t}`. Not balancing dataset!")
- else: print(f"[INFO]: Not balancing dataset! {balance_max=}")
-
- #---------------------
- if shuffle:
- x_proc, y_proc, *z_proc = shuffle_tensor_dataset(x_proc, y_proc, *z_proc)
-
- return x_proc, y_proc, *z_proc
-
- def valid_split(self, x, y, *z, p_valid=0.1):
- splits = max(int(x.shape[0] * p_valid), 1)
- x, x_valid = x[splits:].clone(), x[:splits].clone()
-
- t = self.store_dict["y"]
- if t == "tensor" : y, y_valid = y[splits:].clone(), y[:splits].clone()
- elif t == "numpy": y, y_valid = y[splits:] , y[:splits]
-
- else: raise NotImplementedError("Not implemented")
-
- try:
- z = list(iz[splits:].clone() for iz in z)
- z_valid = list(iz[:splits].clone() for iz in z)
- except:
- z = list(iz[splits:] for iz in z)
- z_valid = list(iz[:splits] for iz in z)
-
- return x, x_valid, y, y_valid, (z, z_valid)
-
- def get_dataloaders(self, batch_size, p_valid=0.1, balance_max=None, max_samples=None, y_on_cpu=False):
-
- excepts = []
- if y_on_cpu: excepts.append("y")
- if self.params_config.dataset_to_gpu: self.to("cuda", excepts=excepts)
-
- x_proc, y_proc, *z_proc = self.x_y_preprocess(balance_max=balance_max, max_samples=max_samples)
- x, x_valid, y, y_valid, (z, z_valid) = self.valid_split(x_proc, y_proc, *z_proc, p_valid=p_valid)
-
- ds = TensorDataset(x, y, *z)
- ds_valid = TensorDataset(x_valid, y_valid, *z_valid)
-
- if self.params_config.dataset_to_gpu:
- train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True)
- valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True)
-
- else:
- train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=12)
- valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=12)
-
- self.dataloaders = DataLoaders(train_loader, valid_loader)
- return self.dataloaders
-
- #----------------------------
-
- def plot_example(self):
- params_config = self.params_config
- enc_tensor = self.x[0]
-
- while enc_tensor.dim()>2: enc_tensor=enc_tensor[0]
-
- params = None
- if hasattr(self, "params"): params=self.params[0]
-
- if isinstance(self.gate_pool[0], str):
- gate_pool = [instruction_name_to_qiskit_gate(gate) for gate in self.gate_pool]
- else:
- gate_pool = self.gate_pool
-
- qc = decode_circuit(enc_tensor, gate_pool, params_tensor=params)
-
- t = self.store_dict["y"]
- if t == "tensor" : label = self.y[0].cpu().tolist()
- elif t == "tensor_list":
- print("Not implemented")
- return
- else :
- label = self.y[0]#.tolist()
- while len(label.shape)>0: label=label[0]
-
- print(f"Label: ``{label}`` SRV is: {schmidt_rank_vector(qi.DensityMatrix(qc))}")
- display(qc.draw("mpl", plot_barriers=False))
-
- def plot_distribution(self):
- if hasattr(self, "dataloaders"): x, y, *z = self.dataloaders.train.dataset.tensors
- else: x, y = self.x, self.y
-
- t = self.store_dict["y"]
- if t == "tensor" : data={"svr":[iy for iy in y.cpu().tolist()]}
- elif t == "numpy": data={"svr":[iy for iy in y.tolist()]}
- else: # list tensor_list
- print("Not implemented")
- return
-
- print("Train dataset (x, y):", x.shape, y.shape)
- print("Train uniques x :", torch.unique(x, dim=0).shape)
-
- #real data distribution
- df = pd.DataFrame(data)
- cnts = df['svr'].value_counts(normalize=True)
- for n,v in zip(cnts.index, cnts.values): print(f"{n}: {v*100:.1f}%")
- ax = df['svr'].value_counts().plot(kind='bar')
diff --git a/genQC/imports.py b/genQC/imports.py
index 4d01c4f..a5dc530 100644
--- a/genQC/imports.py
+++ b/genQC/imports.py
@@ -4,14 +4,19 @@
#------------------------------------
# Python
-import math, itertools, functools, copy, asyncio, time, importlib, datetime, importlib, os, dataclasses, platform
+import math, itertools, functools, copy, asyncio, time, importlib, datetime, importlib, \
+ os, dataclasses, platform, sys, subprocess, pathlib, ast, weakref, enum, abc, \
+ typing, random
+
from datetime import datetime
from PIL import Image
from dataclasses import dataclass, asdict, is_dataclass
-from typing import Union, Optional, TypeVar, Callable, Any
+from typing import Union, Optional, TypeVar, Callable, Any, List, Tuple, Iterable, Sequence
+
import numpy as np
-import pandas as pd
+np.set_printoptions(edgeitems=40, linewidth=200, formatter=dict(float=lambda x: "%.3g" % x))
+
import scipy
import matplotlib.pyplot as plt
@@ -21,14 +26,12 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
-import torchvision
-from torchvision import datasets
from torch.utils.data import DataLoader, TensorDataset
torch.set_printoptions(linewidth=200)
#------------------------------------
-# runtime
+# Runtime
def in_colab():
"Check if the code is running in Google Colaboratory"
@@ -52,16 +55,31 @@ def in_notebook():
IN_NOTEBOOK = in_notebook()
-if IN_NOTEBOOK: from tqdm.notebook import trange, tqdm
-else: from tqdm import trange, tqdm
+if IN_NOTEBOOK:
+ from tqdm.notebook import trange, tqdm
+else:
+ from tqdm import trange, tqdm
#------------------------------------
-# python commons
+# Python commons
+
+from inspect import isfunction, ismethod
-def exists(val): return val is not None
-def default(val, d):
- if exists(val): return val
- return d() if isfunction(d) else d
+def exists(val):
+ return val is not None
+
+def not_exists(val):
+ return val is None
+
+def default(val, default_value):
+ if exists(val):
+ return val
+ return default_value() if isfunction(default_value) else default_value
#------------------------------------
-# ....
+# Fail-safe
+
+if not IN_NOTEBOOK:
+ def display(*args, **kwargs):
+ pass
+
diff --git a/genQC/inference/eval_metrics.py b/genQC/inference/eval_metrics.py
new file mode 100644
index 0000000..8d2d001
--- /dev/null
+++ b/genQC/inference/eval_metrics.py
@@ -0,0 +1,59 @@
+"""Different metrics used for evaluation."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/inference/eval_metrics.ipynb.
+
+# %% auto 0
+__all__ = ['BaseNorm', 'UnitaryFrobeniusNorm', 'UnitaryInfidelityNorm']
+
+# %% ../../src/inference/eval_metrics.ipynb 2
+from ..imports import *
+from scipy.stats import unitary_group
+
+# %% ../../src/inference/eval_metrics.ipynb 4
+class BaseNorm(abc.ABC):
+ """Base class for norms."""
+
+ @staticmethod
+ @abc.abstractmethod
+ def distance(approx_U: torch.Tensor, target_U: torch.Tensor) -> torch.Tensor: raise NotImplementedError()
+
+ @staticmethod
+ @abc.abstractmethod
+ def name() -> str: raise NotImplementedError()
+
+# %% ../../src/inference/eval_metrics.ipynb 6
+class UnitaryFrobeniusNorm(BaseNorm):
+ """
+ The Frobenius-Norm for unitaries: defined in https://arxiv.org/pdf/2106.05649.pdf.
+ """
+
+ def __call__(self, approx_U: torch.Tensor, target_U: torch.Tensor) -> torch.Tensor:
+ return Unitary_FrobeniusNorm.distance(approx_U, target_U)
+
+ @staticmethod
+ def distance(approx_U: torch.Tensor, target_U: torch.Tensor) -> torch.Tensor:
+ d = 0.5 * torch.linalg.matrix_norm((approx_U-target_U), ord="fro")**2
+ return d
+
+ @staticmethod
+ def name() -> str: return "Frobenius-Norm"
+
+# %% ../../src/inference/eval_metrics.ipynb 7
+class UnitaryInfidelityNorm(BaseNorm):
+ """
+ The Infidelity-Norm for unitaries: defined in https://link.aps.org/accepted/10.1103/PhysRevA.95.042318, TABLE I: 1.
+ """
+
+ def __call__(self, approx_U: torch.Tensor, target_U: torch.Tensor) -> torch.Tensor:
+ return Unitary_infidelity.distance(approx_U, target_U)
+
+ @staticmethod
+ def distance(approx_U: torch.Tensor, target_U: torch.Tensor) -> torch.Tensor:
+ """Supports batched intputs, can be used as loss. Input shapes [b, n, n] or [n, n]."""
+ d = torch.matmul(torch.transpose(target_U, -2, -1).conj(), approx_U) # out [b, n, n] or [n, n]
+ d = torch.diagonal(d, offset=0, dim1=-2, dim2=-1).sum(-1) # do partial (batched) trace, out [b, n] or [n]
+ d = 1.0 - (d / target_U.shape[-1]).abs().square()
+ return d
+
+ @staticmethod
+ def name() -> str: return "Unitary-Infidelity"
diff --git a/genQC/inference/evaluation_helper.py b/genQC/inference/evaluation_helper.py
new file mode 100644
index 0000000..6c6d6b5
--- /dev/null
+++ b/genQC/inference/evaluation_helper.py
@@ -0,0 +1,27 @@
+"""Handy helper functions for model evaluations."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/inference/evaluation_helper.ipynb.
+
+# %% auto 0
+__all__ = ['get_srvs', 'get_unitaries']
+
+# %% ../../src/inference/evaluation_helper.ipynb 2
+from ..imports import *
+from ..utils.async_fn import run_parallel_jobs
+from ..platform.simulation import Simulator
+
+# %% ../../src/inference/evaluation_helper.ipynb 4
+def get_srvs(simulator: Simulator, backend_obj_list: Sequence, n_jobs: int = 1, **kwargs):
+ """Returns SRVs of a given list of backen objects `backend_obj_list`."""
+ def _f(backend_obj):
+ return simulator.backend.schmidt_rank_vector(backend_obj, **kwargs)
+
+ return run_parallel_jobs(_f, backend_obj_list, n_jobs)
+
+# %% ../../src/inference/evaluation_helper.ipynb 6
+def get_unitaries(simulator: Simulator, backend_obj_list: Sequence, n_jobs: int = 1, **kwargs):
+ """Returns unitaries of a given list of backen objects `backend_obj_list`."""
+ def _f(backend_obj):
+ return simulator.backend.get_unitary(backend_obj, **kwargs)
+
+ return run_parallel_jobs(_f, backend_obj_list, n_jobs)
diff --git a/genQC/inference/infer_compilation.py b/genQC/inference/infer_compilation.py
deleted file mode 100644
index 23fae46..0000000
--- a/genQC/inference/infer_compilation.py
+++ /dev/null
@@ -1,369 +0,0 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/inference/infer_compilation.ipynb.
-
-# %% auto 0
-__all__ = ['split_U_to_tensor', 'get_new_unitary_indices', 'get_new_unitary_indices_batch', 'generate_comp_tensors',
- 'check_correct_gates', 'check_correct_unitary_exact', 'check_correct_unitary_distance', 'get_gate_and_U_acc',
- 'test_comp_acc', 'test_comp_acc_on_testset', 'test_comp_acc_on_rnd_samples', 'plot_hist_overview']
-
-# %% ../../src/inference/infer_compilation.ipynb 2
-from ..imports import *
-from ..util import *
-from .infer_misc import *
-from .infer_gate_hist import get_tensor_gate_length
-import genQC.platform.qcircuit_dataset_construction as data_con
-from ..dataset.dataset_helper import check_duplicates_in_dataset, uniquify_tensor_dataset, shuffle_tensor_dataset
-from ..platform.simulation.qcircuit_sim import instruction_name_to_qiskit_gate
-
-from joblib import Parallel, delayed
-import qiskit.quantum_info as qi
-
-# %% ../../src/inference/infer_compilation.ipynb 4
-def split_U_to_tensor(U: np.ndarray):
- U_r, U_i = torch.Tensor(np.real(U)), torch.Tensor(np.imag(U))
- U = torch.stack([U_r, U_i], dim=0)
- return U
-
-# %% ../../src/inference/infer_compilation.ipynb 5
-def get_new_unitary_indices(Us, dataset, silent=False):
- if type(Us) == list:
- Us = torch.stack([split_U_to_tensor(U) for U in Us]) #numpy to torch
-
- if not silent:
- print(f"- Checking {Us.shape[0]} unitaries for duplicates in dataset, {torch.unique(Us, dim=0).shape[0]} given unitaries are unique.")
-
- # need to check uniques only
- Us_dataset = torch.unique(dataset.U, dim=0)
-
- # to vecs
- Us = torch.reshape(Us , [Us.shape[0] , -1]).to(Us_dataset.device)
- Us_dataset = torch.reshape(Us_dataset, [Us_dataset.shape[0], -1])
-
- #---------------
-
- #check
- comp = ( Us_dataset.unsqueeze(dim=0) == Us.unsqueeze(dim=1) ) # gives [num of Us, num of dataset, ch]
- comp = torch.all(comp, dim=-1) # gives [num of Us, num of dataset]
-
- #reduce
- comp = torch.all(comp==False, dim=1) # gives indices that ARE NOT in datset
- # comp = torch.any(comp, dim=1) # gives indices that ARE in datset
-
- #get indices
- comp = comp.nonzero().squeeze(dim=1)
-
- if not silent:
- print(f"- Checked {Us.shape[0]} given unitaries with dataset. Returned indices of {comp.shape[0]} not in dataset unitaries.")
-
- return comp.cpu()
-
-# %% ../../src/inference/infer_compilation.ipynb 6
-def get_new_unitary_indices_batch(Us, dataset, auto_batch_size=32, silent=False, n_jobs=1):
- if type(Us) == list:
- Us = torch.stack([split_U_to_tensor(U) for U in Us]) #numpy to torch
-
- if not silent:
- print(f"- Checking {Us.shape[0]} unitaries for duplicates in dataset, {torch.unique(Us, dim=0).shape[0]} given unitaries are unique.")
-
- #----------------------------------------
- samples = Us.shape[0]
- num_batches = int(np.ceil(samples/auto_batch_size))
-
- Us_chunks = Us.chunk(num_batches)
-
- indices = []
-
- if n_jobs > 1:
- f = lambda Us_chunk: get_new_unitary_indices(Us_chunk, dataset, silent=True)
- indices = Parallel(n_jobs=n_jobs)(delayed(f)(Us_chunk) for Us_chunk in Us_chunks)
-
- else:
- for Us_chunk in Us_chunks:
- comp = get_new_unitary_indices(Us_chunk, dataset, silent=True)
- indices.append(comp)
-
- indices = torch.cat(indices)
-
- if not silent:
- print(f"- Checked {samples} given unitaries with dataset. Returned indices of {indices.shape[0]} not in dataset unitaries.")
-
- return indices
-
-# %% ../../src/inference/infer_compilation.ipynb 8
-def generate_comp_tensors(pipeline, prompt, U, samples, system_size, num_of_qubits, max_gates, g, no_bar=True, unique=False, auto_batch_size=512):
- #----------------------
- #prepare condtions
-
- prompt = str(prompt)
- c = pipeline.text_encoder.tokenize_and_push_to_device(prompt)
-
- U = U.unsqueeze(0).to(pipeline.device)
- if system_size > num_of_qubits:
- n = 2**system_size
- pad = (0, n-U.shape[-1], 0, n-U.shape[-2])
- U = F.pad(U, pad, "constant", 0)
-
- #----------------------
- #sample and post process to tensor encodings
-
- batch_samples = [auto_batch_size] * int(np.floor(samples/auto_batch_size))
- if samples % auto_batch_size > 0: batch_samples.append(samples % auto_batch_size)
- if len(batch_samples) == 0: batch_samples.append(samples)
-
- out_tensor_list = []
- for batch_sample in batch_samples:
-
- c_batch = c.repeat(batch_sample, *[1]*(c.dim()-1))
- U_batch = U.repeat(batch_sample, *[1]*(U.dim()-1))
-
- latents = torch.randn((c_batch.shape[0], pipeline.model.clr_dim, system_size, max_gates))
- out_tensor = pipeline(latents=latents, c=c_batch, U=U_batch, g=g, no_bar=no_bar)
- out_tensor_list.append(out_tensor)
-
- out_tensor = torch.cat(out_tensor_list)
- # out_tensor = pipeline(latents=latents, c=c, U=U, g=g, no_bar=no_bar)
-
- out_tensor = pipeline.model.invert_clr(out_tensor)
- out_tensor = out_tensor[:, :num_of_qubits]
-
- if unique: out_tensor = torch.unique(out_tensor, dim=0)
-
- if not no_bar: print(f"[INFO]: (generate_comp_tensors) Generated {'unique_cnt ' if unique else ''}{out_tensor.shape[0]} tensors")
-
- return out_tensor
-
-# %% ../../src/inference/infer_compilation.ipynb 10
-def check_correct_gates(qc, num_of_qubits, gate_pool, max_gates, allowed_gate_clrs):
- tensor = data_con.encode_circuit(qc, num_of_qubits, data_con.gate_pool_to_gate_classes(gate_pool), max_gates)
- gen_gate_clrs = torch.unique(tensor.abs()).tolist()
- gate_corr = set(gen_gate_clrs).issubset(set(allowed_gate_clrs)) # are gates correct?
- return gate_corr
-
-# %% ../../src/inference/infer_compilation.ipynb 11
-def check_correct_unitary_exact(qc, U):
- is_U = qi.Operator(qc).to_matrix()
- is_U = split_U_to_tensor(is_U)
-
- u_corr = torch.allclose(is_U, U) # is U correct?
- return u_corr
-
-# %% ../../src/inference/infer_compilation.ipynb 12
-def check_correct_unitary_distance(qc, target_U, norms):
- is_U = qi.Operator(qc).to_matrix()
- is_U = torch.complex(torch.Tensor(np.real(is_U)), torch.Tensor(np.imag(is_U)))
-
- target_U = torch.complex(target_U[0], target_U[1])
-
- d = []
- for norm in norms:
- u_dist = norm.distance(is_U, target_U).item()
- d.append(u_dist)
-
- return d
-
-# %% ../../src/inference/infer_compilation.ipynb 13
-def get_gate_and_U_acc(out_tensor, allowed_gate_clrs, U, gate_pool, num_of_qubits, max_gates, norms=[], no_bar=True):
-
- if isinstance(gate_pool[0], str):
- gate_pool = [instruction_name_to_qiskit_gate(gate) for gate in gate_pool]
-
- #-------------------------
- #decode
- qc_list, error_cnt = convert_tensors_to_circuits(out_tensor, gate_pool)
- if not no_bar: print(f"Error circuits: {error_cnt}")
-
- #-------------------------
- acc = [] # combinded acc
- gate_acc = [] # only gates acc
- u_acc = [] # only U acc
- u_norms = [] # list of tuple(norms) for every qc
-
- comb_corr_qc = []
- gate_corr_qc = []
- u_corr_qc = []
-
- #only check circuits that are non-error!
- for qc in qc_list:
-
- #---------------
- # check if in out_tensor only color that correspond to the condtion gate_pool
- gate_corr = check_correct_gates(qc, num_of_qubits, gate_pool, max_gates, allowed_gate_clrs)
-
- #---------------
- # check unitary
- u_corr = check_correct_unitary_exact(qc, U) # true or false
- u_norm = check_correct_unitary_distance(qc, U, norms) # metrics values list
-
- #---------------
- acc.append(gate_corr and u_corr)
- gate_acc.append(gate_corr)
- u_acc.append(u_corr)
- u_norms.append(u_norm)
-
- if gate_corr and u_corr: comb_corr_qc.append(qc)
- if gate_corr: gate_corr_qc.append(qc)
- if u_corr: u_corr_qc.append(qc)
-
- #average accuracy over sample
- acc = np.mean(acc).item()
- gate_acc = np.mean(gate_acc).item()
- u_acc = np.mean(u_acc).item()
-
- return acc, gate_acc, u_acc, np.array(u_norms), error_cnt, comb_corr_qc, gate_corr_qc, u_corr_qc, qc_list
-
-# %% ../../src/inference/infer_compilation.ipynb 15
-def test_comp_acc(pipeline, samples, system_size, gate_pool, num_of_qubits, max_gates, g, str_cond_to_gate_indices: callable, Us, ys, train_dataset=None, norms=[]):
-
- if exists(train_dataset):
- not_dups_ind = get_new_unitary_indices_batch(Us, train_dataset)
- Us = [Us[i] for i in not_dups_ind]
- ys = [ys[i] for i in not_dups_ind]
-
- #--------------------
- acc_s = []
- gate_acc_s = []
- u_acc_s = []
- u_norms_s = []
- uniques_cnt_s = []
- error_cnt_s = []
-
- num_found_distinct_circuits_s = []
-
- for U,y in tqdm(zip(Us,ys), total=len(Us)):
-
- allowed_gate_clrs = str_cond_to_gate_indices(y)
- if isinstance(U, np.ndarray):
- U = split_U_to_tensor(U)
-
- out_tensor = generate_comp_tensors(pipeline, y, U, samples, system_size, num_of_qubits, max_gates, g, unique=False)
- outs = get_gate_and_U_acc(out_tensor, allowed_gate_clrs, U, gate_pool, num_of_qubits, max_gates, norms)
-
- acc, gate_acc, u_acc, u_norms, error_cnt, comb_corr_qc, gate_corr_qc, u_corr_qc, qc_list = outs
-
- if len(qc_list) > 0:
- uniques_cnt = torch.stack([data_con.encode_circuit(qc, num_of_qubits, data_con.gate_pool_to_gate_classes(gate_pool), max_gates) for qc in qc_list]).unique(dim=0).shape[0] #how many uniques in sample (not counting erroro circuits)
- # uniques_cnt = out_tensor.shape[0] - error_cnt #was with unique acc definition
- else:
- uniques_cnt = 0
-
- if len(comb_corr_qc) > 0:
- num_found_distinct_circuits = torch.stack([data_con.encode_circuit(qc, num_of_qubits, data_con.gate_pool_to_gate_classes(gate_pool), max_gates) for qc in comb_corr_qc]).unique(dim=0).shape[0] #how many distinct exact solutions we have
- else:
- num_found_distinct_circuits = 0
-
- #--------------------
- acc_s.append(acc)
- gate_acc_s.append(gate_acc)
- u_acc_s.append(u_acc)
- u_norms_s.append(u_norms)
- uniques_cnt_s.append(uniques_cnt)
- error_cnt_s.append(error_cnt)
- num_found_distinct_circuits_s.append(num_found_distinct_circuits)
-
- solved_tasks = np.count_nonzero(num_found_distinct_circuits_s)
- print(f"Solved {solved_tasks} correctly (at least one qc) that is {100*solved_tasks/len(num_found_distinct_circuits_s):0.2f}%")
-
- return acc_s, gate_acc_s, u_acc_s, u_norms_s, uniques_cnt_s, error_cnt_s, num_found_distinct_circuits_s
-
-# %% ../../src/inference/infer_compilation.ipynb 16
-def test_comp_acc_on_testset(pipeline, samples, num_of_U, system_size, gate_pool, num_of_qubits, max_gates, g, str_cond_to_gate_indices: callable,
- prompt_mod: callable, test_dataset, train_dataset=None, norms=[], fix_y=None):
- '''returns: acc_s, gate_acc_s, u_acc_s, uniques_cnt_s, error_cnt_s, num_found_circuits_s, task_qc_len_s'''
-
- if hasattr(test_dataset, "z"): # mixed dataset has padding but a z record!
- Us, ys, zs = uniquify_tensor_dataset(test_dataset.U, test_dataset.y, test_dataset.z)
- Us, ys, zs = shuffle_tensor_dataset(Us, ys, zs)
- Us, ys, zs = Us[:num_of_U], ys[:num_of_U], zs[:num_of_U]
- task_qc_len_s = zs[:, 1]
-
- else: # not mixed dataset has no padding
- Us, ys, xs = uniquify_tensor_dataset(test_dataset.U, test_dataset.y, test_dataset.x)
- Us, ys, xs = shuffle_tensor_dataset(Us, ys, xs)
- Us, ys, xs = Us[:num_of_U], ys[:num_of_U], xs[:num_of_U]
- task_qc_len_s = get_tensor_gate_length(xs)
-
- if exists(fix_y): ys = [fix_y for y in ys]
- else: ys = [prompt_mod(y) for y in ys]
-
-
- print(f"Picked {Us.shape[0]} unitaries from test set")
- print(f"Sample task: {ys[0]}")
- print(Us[0])
- print(xs[0])
-
- out = test_comp_acc(pipeline, samples, system_size, gate_pool, num_of_qubits, max_gates, g, str_cond_to_gate_indices, Us.cpu(), ys, train_dataset, norms)
- return *out, task_qc_len_s
-
-# %% ../../src/inference/infer_compilation.ipynb 17
-def test_comp_acc_on_rnd_samples(pipeline, samples, num_of_U, system_size, gate_pool, num_of_qubits, max_gates, g, str_cond_to_gate_indices: callable,
- prompt_mod: callable, # takes a single prompt and returns it modified
- rnd_min_gates, rnd_max_gates,
- train_dataset=None, norms=[], fix_y=None):
- '''returns: acc_s, gate_acc_s, u_acc_s, uniques_cnt_s, error_cnt_s, num_found_circuits_s, task_qc_len_s'''
-
- enc_t, ys, Us = data_con.gen_compilation_rndGates_dataset(num_of_U, num_of_qubits, rnd_min_gates, rnd_max_gates, gate_pool)
- task_qc_len_s = get_tensor_gate_length(enc_t) #should give a complexity meassure, longer circuits have a more complex unitary to compile?
-
- if exists(fix_y): ys = [fix_y for y in ys]
- else: ys = [prompt_mod(y) for y in ys]
-
- print(f"Sample task: {ys[0]}")
- print(split_U_to_tensor(Us[0]))
- print(enc_t[0])
-
- out = test_comp_acc(pipeline, samples, system_size, gate_pool, num_of_qubits, max_gates, g, str_cond_to_gate_indices, Us, ys, train_dataset, norms)
- return *out, task_qc_len_s
-
-# %% ../../src/inference/infer_compilation.ipynb 19
-def plot_hist_overview(out_tuple, num_of_samples_per_U, rnd_min_gates, rnd_max_gates, max_gates, num_of_qubits):
- acc_s, gate_acc_s, u_acc_s, u_norms_s, uniques_cnt_s, error_cnt_s, num_found_circuits_s, task_qc_len_s = out_tuple
-
- if not exists(rnd_min_gates): rnd_min_gates = ""
- if not exists(rnd_max_gates): rnd_max_gates = ""
-
- fig, axs = plt.subplots(2, 3, figsize=(13, 6.4), squeeze=False, constrained_layout=True)
- fig.suptitle(f"Histogram of compilation accuracies (Unitary cnt={len(acc_s)}, samples_per_U={num_of_samples_per_U} {rnd_min_gates=} {rnd_max_gates=} {max_gates=} qubits={num_of_qubits})")
-
- n = 20
- density = False
- bins = np.linspace(0,1, n+1)
-
- #-----------------
- plt.sca(axs[0, 0])
- plt.title("Combined accuracy")
- plt.xlabel(r"Accuracy")
- plt.ylabel(r"Bin population" if density==False else "Accuracy distribution")
- plt.hist(acc_s, density=density, bins=n*4)
-
- #-----------------
- plt.sca(axs[0, 1])
- plt.title("Unitary accuracy")
- plt.xlabel(r"Accuracy")
- plt.hist(u_acc_s, density=density, bins=bins)
-
- #-----------------
- plt.sca(axs[0, 2])
- plt.title("Gate accuracy")
- plt.xlabel(r"Accuracy")
- plt.hist(gate_acc_s, density=density, bins=bins)
-
- #-----------------
- plt.sca(axs[1, 0])
- plt.title("Generated unique circuits")
- plt.ylabel(r"Bin population" if density==False else "Number distribution")
- plt.xlabel(r"Number of unique circuits")
- plt.hist(uniques_cnt_s, density=density, bins=n)
-
- #-----------------
- plt.sca(axs[1, 1])
- plt.title("Generated error circuits")
- plt.xlabel(r"Number of error circuits")
- plt.hist(error_cnt_s, density=density, bins=n)
-
- #-----------------
- plt.sca(axs[1, 2])
- plt.title("Absolute number of distinct correct circuits")
- plt.xlabel(r"Number of found circuits")
- plt.hist(num_found_circuits_s, density=density, bins=n*4)
-
- #-----------------
- plt.show()
diff --git a/genQC/inference/infer_gate_hist.py b/genQC/inference/infer_gate_hist.py
deleted file mode 100644
index 8c6b06b..0000000
--- a/genQC/inference/infer_gate_hist.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/inference/infer_gate_hist.ipynb.
-
-# %% auto 0
-__all__ = ['get_tensor_gate_length', 'get_circuit_gate_length']
-
-# %% ../../src/inference/infer_gate_hist.ipynb 2
-from ..imports import *
-
-# %% ../../src/inference/infer_gate_hist.ipynb 4
-def get_tensor_gate_length(clr_tensor, padding_token=0):
- '''Careful with padding tokens!'''
- assert clr_tensor.dim() == 3 #[b, s, t]
-
- collabsed_clr_tensor = (clr_tensor != padding_token).to(torch.int8)
- red_clr_tensor = torch.sum(collabsed_clr_tensor, dim=1) # [b, t]
- return torch.count_nonzero(red_clr_tensor, dim=1) # [b]
-
-# %% ../../src/inference/infer_gate_hist.ipynb 5
-def get_circuit_gate_length(qcs):
- lengths = torch.zeros(len(qcs), dtype=int)
- for i,qc in enumerate(qcs):
- if hasattr(qc, "data"):
- lengths[i] = len(qc.data)
- return lengths
diff --git a/genQC/inference/infer_misc.py b/genQC/inference/infer_misc.py
deleted file mode 100644
index d6caf85..0000000
--- a/genQC/inference/infer_misc.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/inference/infer_misc.ipynb.
-
-# %% auto 0
-__all__ = ['get_rnd_gatepool_subset', 'convert_tensors_to_circuits']
-
-# %% ../../src/inference/infer_misc.ipynb 2
-from ..imports import *
-from ..platform.qcircuit_dataset_construction import decode_circuit, gate_pool_to_gate_classes
-from ..platform.simulation.qcircuit_sim import instruction_name_to_qiskit_gate
-
-# %% ../../src/inference/infer_misc.ipynb 4
-def get_rnd_gatepool_subset(gate_pool, min_sub_gate_pool_cnt=2):
- rng = np.random.default_rng()
-
- n = len(gate_pool) + 1
- c_range = np.arange(n-1)
-
- sub_gate_pool_cnt = rng.integers(min_sub_gate_pool_cnt, n)
- sub_gate_pool_ind = rng.choice(c_range, size=sub_gate_pool_cnt, replace=False)
- sub_gate_pool = [gate_pool[ind] for ind in sub_gate_pool_ind] # pick random subeset of gates
-
- return sub_gate_pool
-
-# %% ../../src/inference/infer_misc.ipynb 6
-def convert_tensors_to_circuits(out_tensor, gate_pool, params_tensor=None, place_barrier=False):
- if isinstance(gate_pool[0], str):
- gate_pool = [instruction_name_to_qiskit_gate(gate) for gate in gate_pool]
-
- error_cnt = 0
- qc_list = []
-
- if not exists(params_tensor):
- params_tensor = [None]*out_tensor.shape[0]
-
- #TODO: para this loop
-
- for i,(enc_tensor,p) in enumerate(zip(out_tensor, params_tensor)):
- try:
- qc = decode_circuit(enc_tensor=enc_tensor, gate_pool=gate_pool, place_barrier=place_barrier, params_tensor=p)
-
- except Exception as e:
- error_cnt += 1
- # print(e)
- continue
-
- qc_list.append(qc)
-
- return qc_list, error_cnt
diff --git a/genQC/inference/infer_srv.py b/genQC/inference/infer_srv.py
deleted file mode 100644
index 5d9cc0f..0000000
--- a/genQC/inference/infer_srv.py
+++ /dev/null
@@ -1,512 +0,0 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/inference/infer_srv.ipynb.
-
-# %% auto 0
-__all__ = ['get_all_srvs', 'generate_srv_tensors', 'convert_tensors_to_srvs', 'get_srv_accuracy', 'true_sample_bin_dist',
- 'test_srv_clr_distribution_bin_samples', 'test_srv_clr_distribution', 'test_guidance_dep',
- 'test_srv_acc_vs_length', 'test_srv_acc_vs_maxLength', 'test_srv_length_distribution',
- 'plot_srv_clr_distribution_hist', 'plot_srv_clr_distribution_bin_accuracy', 'plot_guidance_dep',
- 'plot_srv_acc_vs_length', 'plot_srv_acc_vs_maxLength']
-
-# %% ../../src/inference/infer_srv.ipynb 2
-from ..imports import *
-from .infer_misc import *
-from .infer_gate_hist import get_circuit_gate_length
-from ..platform.qcircuit_util import get_entanglement_bins
-from ..platform.simulation.qcircuit_sim import schmidt_rank_vector
-from .infer_compilation import generate_comp_tensors
-
-from joblib import Parallel, delayed
-import qiskit.quantum_info as qi
-
-# %% ../../src/inference/infer_srv.ipynb 4
-def get_all_srvs(num_of_qubits):
- srvs = [x for x in itertools.product(*([[1,2]]*num_of_qubits))]
- srvs = np.array(srvs, dtype=int)[np.sum(srvs, axis=1)!=num_of_qubits+1].tolist()
- srvs = sorted(srvs, key=lambda x: sum(x))
- return srvs
-
-# %% ../../src/inference/infer_srv.ipynb 5
-def generate_srv_tensors(pipeline, prompt, samples, system_size, num_of_qubits, max_gates, g, no_bar=True, unique=False, auto_batch_size=512):
- if samples==0:
- out_tensor = torch.zeros((0, system_size, max_gates))
- return out_tensor
-
- #----------------------
- #prepare condtions
-
- prompt = str(prompt)
- c = pipeline.text_encoder.tokenize_and_push_to_device(prompt)
-
- #----------------------
- #sample and post process to tensor encodings
-
- batch_samples = [auto_batch_size] * int(np.floor(samples/auto_batch_size))
- if samples % auto_batch_size > 0: batch_samples.append(samples % auto_batch_size)
- if len(batch_samples) == 0: batch_samples.append(samples)
-
- out_tensor_list = []
- for batch_sample in batch_samples:
-
- c_batch = c.repeat(batch_sample, *[1]*(c.dim()-1))
-
- latents = torch.randn((c_batch.shape[0], pipeline.model.clr_dim, system_size, max_gates))
- out_tensor = pipeline(latents=latents, c=c_batch, g=g, no_bar=no_bar, enable_guidance=True)
- out_tensor_list.append(out_tensor)
-
- out_tensor = torch.cat(out_tensor_list)
- out_tensor = pipeline.model.invert_clr(out_tensor)
- out_tensor = out_tensor[:, :num_of_qubits]
-
- if unique: out_tensor = torch.unique(out_tensor, dim=0)
-
- if not no_bar: print(f"[INFO]: (generate_srv_tensors) Generated {'unique_cnt ' if unique else ''}{out_tensor.shape[0]} tensors")
-
- return out_tensor
-
-# %% ../../src/inference/infer_srv.ipynb 7
-def convert_tensors_to_srvs(out_tensor, gate_pool, sort_srv=False, place_barrier=False, n_jobs=1):
- qc_list, error_cnt = convert_tensors_to_circuits(out_tensor, gate_pool=gate_pool, place_barrier=place_barrier)
-
- srv_list = []
-
- #---------------------------------------------
- # This is a bottle-neck for more qubits, speed up with async
-
- if n_jobs > 1:
- assert sort_srv == False
-
- f = lambda qc: schmidt_rank_vector(qi.DensityMatrix(qc))
- # srv_list = Parallel(n_jobs=n_jobs, prefer="threads")(delayed(f)(qc) for qc in qc_list) #prefer="threads"
- srv_list = Parallel(n_jobs=n_jobs)(delayed(f)(qc) for qc in qc_list)
-
- else:
- for qc in qc_list:
- srv = schmidt_rank_vector(qi.DensityMatrix(qc))
-
- if sort_srv: srv = sorted(srv)
- srv_list.append(srv)
-
- return qc_list, error_cnt, srv_list
-
-# %% ../../src/inference/infer_srv.ipynb 9
-def get_srv_accuracy(srv_list, target_srv):
- if not isinstance(srv_list , (torch.Tensor, torch.IntTensor, torch.FloatTensor, torch.LongTensor)): srv_list = torch.tensor(srv_list)
- if not isinstance(target_srv, (torch.Tensor, torch.IntTensor, torch.FloatTensor, torch.LongTensor)): target_srv = torch.tensor(target_srv, device=srv_list.device)
-
- srv_uniques, srv_uniques_cnt = torch.unique(srv_list, dim=0, return_counts=True)
-
- if srv_uniques.numel() == 0: return 0
-
- comp = torch.all(target_srv==srv_uniques, dim=1)
- index = comp.nonzero().squeeze()
-
- if index.dim() == 0: correct_srv_percentage = srv_uniques_cnt[index]/srv_uniques_cnt.sum()
- else: correct_srv_percentage = 0
-
- return correct_srv_percentage
-
-# %% ../../src/inference/infer_srv.ipynb 11
-def true_sample_bin_dist(samples_per_bin, bin_size):
- true_samples = [max(samples_per_bin//bin_size, 1) for i in range(bin_size)]
-
- if samples_per_bin-sum(true_samples) > 0:
- true_samples[0] += (samples_per_bin-sum(true_samples))
-
- # assert sum(true_samples)==samples_per_bin
- # assert len(true_samples)==bin_size
-
- # print(f"{true_samples=}")
-
- return true_samples
-
-# %% ../../src/inference/infer_srv.ipynb 12
-def test_srv_clr_distribution_bin_samples(pipeline, samples_per_bin, system_size, num_of_qubits, max_gates, g, gate_pool, silent=False, device="cpu", U=None, prompt_mod: callable=lambda c: c,
- only_diag=False, n_jobs=1):
- dist_srvs = get_all_srvs(num_of_qubits)
- cond_srvs = dist_srvs
-
- values = torch.zeros((len(cond_srvs), len(dist_srvs)), device=device)
-
- #---------------------
-
- ent_bins, ent_labels = get_entanglement_bins(num_of_qubits)
-
- i = 0
-
- for ent_bin in tqdm(ent_bins, total=len(ent_bins)):
-
- true_samples = true_sample_bin_dist(samples_per_bin, len(ent_bin))
-
- for ind,srv in tqdm(enumerate(ent_bin), total=len(ent_bin)):
- if exists(U): out_tensor = generate_comp_tensors(pipeline, prompt_mod(srv), U, true_samples[ind], system_size, num_of_qubits, max_gates, g=g, unique=False)
- else: out_tensor = generate_srv_tensors( pipeline, prompt_mod(srv), true_samples[ind], system_size, num_of_qubits, max_gates, g=g, unique=False)
-
- qc_list, error_cnt, svr_list = convert_tensors_to_srvs(out_tensor, gate_pool, n_jobs=n_jobs)
-
- if only_diag:
- values[i, i] = get_srv_accuracy(svr_list, srv)
- else:
- for j, dist_srv in enumerate(dist_srvs):
- values[i, j] = get_srv_accuracy(svr_list, dist_srv)
-
- if not silent:
- print(f"{cond_srv}: unique_cnt {out_tensor.unique(dim=0).shape[0]} error_cnt {error_cnt} acc {values[i, i]:.2f}")
-
- i += 1
-
- return values
-
-# %% ../../src/inference/infer_srv.ipynb 13
-def test_srv_clr_distribution(pipeline, samples_per_srv, system_size, num_of_qubits, max_gates, g, gate_pool, silent=False, device="cpu", U=None, prompt_mod: callable=lambda c: c,
- dist_srvs=None, cond_srvs=None, only_diag=False, n_jobs=1):
- if not exists(dist_srvs):
- dist_srvs = get_all_srvs(num_of_qubits)
-
- if not exists(cond_srvs):
- cond_srvs = dist_srvs
-
- values = torch.zeros((len(cond_srvs), len(dist_srvs)), device=device)
-
- #---------------------
-
- for i, cond_srv in tqdm(enumerate(cond_srvs), total=len(cond_srvs)):
-
- if exists(U): out_tensor = generate_comp_tensors(pipeline, prompt_mod(cond_srv), U, samples_per_srv, system_size, num_of_qubits, max_gates, g=g, unique=False)
- else: out_tensor = generate_srv_tensors( pipeline, prompt_mod(cond_srv), samples_per_srv, system_size, num_of_qubits, max_gates, g=g, unique=False)
-
- qc_list, error_cnt, svr_list = convert_tensors_to_srvs(out_tensor, gate_pool, n_jobs=n_jobs)
-
- if only_diag:
- values[i, i] = get_srv_accuracy(svr_list, srv)
- else:
- for j, dist_srv in enumerate(dist_srvs):
- values[i, j] = get_srv_accuracy(svr_list, dist_srv)
-
- if not silent:
- print(f"{cond_srv}: unique_cnt {out_tensor.unique(dim=0).shape[0]} error_cnt {error_cnt} acc {values[i, i]:.2f}")
-
- return values
-
-# %% ../../src/inference/infer_srv.ipynb 14
-def test_guidance_dep(pipeline, srvs, samples, system_size, num_of_qubits, max_gates, gs, gate_pool, prompt_mod: callable=lambda c: c, U=None, n_jobs=1):
- guidance_dep_out = []
-
- for srv in srvs:
- unique_percentage_list = []
- error_cnt_percentage_list = []
- correct_srv_percentage_list = []
-
- for g in tqdm(gs):
- if exists(U): out_tensor = generate_comp_tensors(pipeline, prompt_mod(srv), U, samples, system_size, num_of_qubits, max_gates, g=g, unique=False)
- else: out_tensor = generate_srv_tensors( pipeline, prompt_mod(srv), samples, system_size, num_of_qubits, max_gates, g=g, unique=False)
-
- #---------------------------------
- #calculate the copy percentage, dataset and sample?
-
- unique_percentage = out_tensor.unique(dim=0).shape[0]/out_tensor.shape[0]
- unique_percentage_list.append(unique_percentage)
-
- #---------------------------------
- #decode tensors, get srv
-
- qc_list, error_cnt, svr_list = convert_tensors_to_srvs(out_tensor, gate_pool, n_jobs=n_jobs)
- error_cnt_percentage_list.append(error_cnt/out_tensor.shape[0])
-
- #---------------------------------
- #record the correct number
-
- correct_srv_percentage = get_srv_accuracy(svr_list, srv)
- correct_srv_percentage_list.append(correct_srv_percentage)
-
- guidance_dep_out.append((unique_percentage_list, error_cnt_percentage_list, correct_srv_percentage_list))
-
- return guidance_dep_out
-
-# %% ../../src/inference/infer_srv.ipynb 15
-def test_srv_acc_vs_length(pipeline, samples, system_size, num_of_qubits, max_gates, g, gate_pool, prompt_mod: callable=lambda c: c, U=None, n_jobs=1):
- ent_bins, ent_labels = get_entanglement_bins(num_of_qubits)
-
- ent_ls = []
- ent_accs = []
- ent_cnts = []
-
- for ent_bin in tqdm(ent_bins, total=len(ent_bins)):
- ls_acc = dict() #keep track over bins
- ls_cnt = dict()
-
- true_samples = true_sample_bin_dist(samples_per_bin, len(ent_bin))
-
- for ind,srv in enumerate(ent_bin):
- if exists(U): out_tensor = generate_comp_tensors(pipeline, prompt_mod(srv), U, true_samples[ind], system_size, num_of_qubits, max_gates, g=g, unique=False)
- else: out_tensor = generate_srv_tensors( pipeline, prompt_mod(srv), true_samples[ind], system_size, num_of_qubits, max_gates, g=g, unique=False)
-
- qc_list, error_cnt, svr_list = convert_tensors_to_srvs(out_tensor, gate_pool, n_jobs=n_jobs)
-
- lengths = get_circuit_gate_length(qc_list) #work in qc space to check only non errors
-
- if lengths.numel() < 1: continue
-
- for l in lengths.unique(): #range(lengths.min(), lengths.max()):
- indices = (lengths==l).nonzero().squeeze()
-
- if indices.numel() > 0:
- srvs = torch.tensor(svr_list)[indices]
- if indices.dim() == 0: srvs = srvs.unsqueeze(0)
-
- acc = get_srv_accuracy(srvs, srv)
-
- #----------
- t = ls_acc.pop(l, [])
- t.append(acc)
- ls_acc[l] = t
-
- t = ls_cnt.pop(l, 0)
- t += srvs.shape[0]
- ls_cnt[l] = t
-
- ls = sorted(ls_acc) # sorted keys (l)
- accs = [np.mean(ls_acc[l]) for l in ls] # average acc per l
- cnts = [np.sum(ls_cnt[l]) for l in ls]
-
- ent_ls.append(ls)
- ent_accs.append(accs)
- ent_cnts.append(cnts)
-
- return ent_ls, ent_accs, ent_cnts, ent_labels
-
-# %% ../../src/inference/infer_srv.ipynb 16
-def test_srv_acc_vs_maxLength(pipeline, samples_per_bin, system_size, num_of_qubits, max_gates_list, g, gate_pool, prompt_mod: callable=lambda c: c, U=None, n_jobs=1):
- ent_bins, ent_labels = get_entanglement_bins(num_of_qubits)
-
- ent_accs = []
- for ent_bin in tqdm(ent_bins, total=len(ent_bins)):
-
- true_samples = true_sample_bin_dist(samples_per_bin, len(ent_bin))
-
- bin_accs = []
- for max_gates in max_gates_list:
-
- accs = []
- for ind,srv in enumerate(ent_bin):
- if exists(U): out_tensor = generate_comp_tensors(pipeline, prompt_mod(srv), U, true_samples[ind], system_size, num_of_qubits, max_gates, g=g, unique=False)
- else: out_tensor = generate_srv_tensors( pipeline, prompt_mod(srv), true_samples[ind], system_size, num_of_qubits, max_gates, g=g, unique=False)
-
- qc_list, error_cnt, svr_list = convert_tensors_to_srvs(out_tensor, gate_pool, n_jobs=n_jobs)
-
- acc = get_srv_accuracy(svr_list, srv)
-
- accs.append(acc)
- bin_accs.append(np.mean(accs))
- ent_accs.append(bin_accs)
-
- return ent_accs, ent_labels
-
-# %% ../../src/inference/infer_srv.ipynb 17
-def test_srv_length_distribution(pipeline, samples_per_bin, system_size, num_of_qubits, max_gates, g, gate_pool, silent=False, U=None, prompt_mod: callable=lambda c: c, n_jobs=1):
- ent_bins, ent_labels = get_entanglement_bins(num_of_qubits)
-
- ls = []
-
- for ent_bin in tqdm(ent_bins, total=len(ent_bins)):
-
- true_samples = true_sample_bin_dist(samples_per_bin, len(ent_bin))
-
- bin_ls = []
-
- for ind,srv in tqdm(enumerate(ent_bin), total=len(ent_bin)):
- if exists(U): out_tensor = generate_comp_tensors(pipeline, prompt_mod(srv), U, true_samples[ind], system_size, num_of_qubits, max_gates, g=g, unique=False)
- else: out_tensor = generate_srv_tensors( pipeline, prompt_mod(srv), true_samples[ind], system_size, num_of_qubits, max_gates, g=g, unique=False)
-
- qc_list, error_cnt, svr_list = convert_tensors_to_srvs(out_tensor, gate_pool, n_jobs=n_jobs)
-
- qc_ls = get_circuit_gate_length(qc_list) #tensor [qcs]
- bin_ls.append(qc_ls)
-
- ls.append(torch.cat(bin_ls))
-
- return ls #[ent_bins, num_of_non_err_samples]
-
-# %% ../../src/inference/infer_srv.ipynb 19
-def plot_srv_clr_distribution_hist(values, samples, num_of_qubits, save=False, dist_srvs=None, cond_srvs=None):
- if not exists(dist_srvs):
- dist_srvs = get_all_srvs(num_of_qubits)
-
- if not exists(cond_srvs):
- cond_srvs = dist_srvs
-
- n = len(dist_srvs)
- values = values.cpu()
-
- fig = plt.figure(figsize=(12,12))#, constrained_layout=True)
- plt.title(f"Generated samples per condition: {samples}")
- plt.ylabel(r"Condition")
- plt.xlabel(r"Generated distribution")
-
- #--------------------------------------------
- if num_of_qubits < 6 or 0:
- plt.yticks(range(len(cond_srvs)), [str(b) for b in cond_srvs])
- plt.xticks(range(n), [str(b) for b in dist_srvs], rotation=90 if n>3 else 0)
- else:
- plt.yticks([])
- plt.xticks([])
-
- #--------------------------------------------
- plt.imshow(values, vmin=0, vmax=1)
- # plt.imshow(values.cpu(), norm="log")
- plt.colorbar()
-
- #--------------------------------------------
- #print acc
- x_shift = 1*40 if num_of_qubits==5 else 0
- if num_of_qubits < 4:
- for i in range(n):
- plt.text(x_shift+i, i, f"{values[i, i]:0.2f}", color='black', ha='center', va='center', fontsize="large")
-
- #--------------------------------------------
- #draw rects
- off = 0.5
- for i in range(2, num_of_qubits):
- w = scipy.special.comb(num_of_qubits, i, exact=True)
- plt.gca().add_patch(plt.Rectangle((off, off), w, w, ls="-", ec="white", fc="none")) #, transform=plt.gca().transAxes))
- off += w
-
- #--------------------------------------------
- #print average acc for rects
- off = 0
- for i in [0]+list(range(2, num_of_qubits+1)):
- w = scipy.special.comb(num_of_qubits, i, exact=True)
- d1 = off
- d2 = d1 + w
- mean_acc = values[d1:d2, d1:d2].diag().mean()
- plt.text(off+2*w/3, off+w/7, f"{mean_acc:0.2f}", color='red', ha='center', va='center', fontsize="x-large")
- off += w
-
- #--------------------------------------------
- if save:
- plt.savefig('plot_srv_clr_distribution_hist.svg', bbox_inches='tight')
-
- plt.show()
-
-# %% ../../src/inference/infer_srv.ipynb 20
-def plot_srv_clr_distribution_bin_accuracy(values, samples, num_of_qubits, save=False, plot_percentages=False, trainSet_srv=None):
- values = values.cpu().diag()
- ent_bins, ent_labels = get_entanglement_bins(num_of_qubits)
-
- n = sum(len(srvs) for srvs in ent_bins)
- x = np.arange(n) # the label locations
- width = 0.8
-
- #------------------------
- fig = plt.figure(figsize=(6.6, 4), constrained_layout=True)
- # plt.title(f"Generated samples per condition: {samples}", fontsize=14)
- plt.ylabel(r"Accuracy", fontsize=25)
- plt.yticks(fontsize=14)
- plt.xticks([])
-
- i = 0
- for j,(label, srvs) in enumerate(zip(ent_labels, ent_bins)):
- label = f"{sum(srvs[0])-num_of_qubits}"
- incre = len(srvs)
- rects = plt.bar(x[i:i+incre], values[i:i+incre], width, label=label)
- i += incre
- if plot_percentages: plt.gca().bar_label(rects, padding=3, fmt="%0.2f")
-
- ncols = len(ent_labels)//2+1 if len(ent_labels) > 5 else len(ent_labels)
- leg1 = plt.legend(loc="lower center", fontsize=14, ncols=ncols, title="# of entangled qubits:", title_fontsize=14,bbox_to_anchor=(0.5, 1.01))
- ax = fig.add_artist(leg1)
-
- if exists(trainSet_srv):
- if trainSet_srv.shape[-1]==num_of_qubits:
- srvs = []
- for s in ent_bins: srvs.extend(s)
-
- dataset_percentages = [get_srv_accuracy(trainSet_srv, srv).cpu() for srv in srvs]
- xmin = x - width*0.55
- xmax = x + width*0.55
- ag = plt.hlines(dataset_percentages, xmin, xmax, label="Random sampling" , color="black", linestyle="-", linewidths=2.3)
-
- plt.legend(handles=[ag], fontsize=14, frameon=False)
-
- ymin, ymax = plt.ylim()
- plt.ylim(ymin, ymax+0.04)
-
- if save:
- plt.savefig(f"plot_srv_clr_distribution_bin_accuracy.svg", bbox_inches='tight', transparent=True)
-
- plt.show()
-
-# %% ../../src/inference/infer_srv.ipynb 21
-def plot_guidance_dep(srvs, gs, guidance_dep_out, samples, save=False):
- assert len(srvs) == len(guidance_dep_out)
-
- n = len(srvs)
- fig, axs = plt.subplots(1, n, figsize=(12, 5), squeeze=False, constrained_layout=True)
- fig.suptitle(fr"Generated {samples} samples per $g$ and SRV")
-
- for i,srv in enumerate(srvs):
- unique_percentage_list, error_cnt_percentage_list, correct_srv_percentage_list = guidance_dep_out[i]
-
- #---------------------------------
- #plot now gs vs the numbers
-
- plt.sca(axs[0, i])
- plt.xlabel(r"Guidance scale $g$")
- plt.title(f"SRV = {srv}")
- plt.plot(gs, unique_percentage_list , label="Unique tensors percentage")
- plt.plot(gs, error_cnt_percentage_list , label="Error circuits percentage")
- plt.plot(gs, correct_srv_percentage_list, label="Correct SRV percentage")
-
- if i == (n-1): plt.legend()
-
- if save:
- plt.savefig("plot_guidance_dep.svg", bbox_inches='tight', transparent=True)
-
- plt.show()
-
-# %% ../../src/inference/infer_srv.ipynb 22
-def plot_srv_acc_vs_length(ent_ls, ent_accs, ent_cnts, ent_labels, samples, plot_dist=True, save=False):
- fig, axs = plt.subplots(2 if plot_dist else 1, 1, figsize=(12, 7), squeeze=False, constrained_layout=True)
-
- #-------------------
- plt.sca(axs[0,0])
- plt.title(f"Generated samples per entanglement: {samples}")
- plt.ylabel("Accuracy")
- plt.xlabel("Gate number")
- for i,ent_label in enumerate(ent_labels):
- plt.plot(ent_ls[i], ent_accs[i], label=f"{ent_label}")
- plt.legend()
-
- #-------------------
- if plot_dist:
- plt.sca(axs[1,0])
- plt.title(f"Used samples per l to calculate accuracy, should match gate distribution")
- plt.ylabel("Used samples")
- plt.xlabel("Gate number")
- for i,ent_label in enumerate(ent_labels):
- plt.plot(ent_ls[i], ent_cnts[i], label=f"{ent_label}")
- plt.legend()
-
- #-------------------
- if save:
- plt.savefig('plot_srv_acc_vs_length.svg', bbox_inches='tight')
-
- plt.show()
-
-# %% ../../src/inference/infer_srv.ipynb 23
-def plot_srv_acc_vs_maxLength(ent_accs, ent_labels, max_gates_list, samples, plot_dist=True, save=False):
- fig = plt.figure(figsize=(12, 4), constrained_layout=True)
-
- plt.title(f"Generated samples per maxGates per entanglement: {samples}")
- plt.ylabel("Accuracy")
- plt.xlabel("Max number of gates / tensor size")
- plt.xticks(max_gates_list)
-
- for ent_acc,ent_label in zip(ent_accs, ent_labels):
- plt.plot(max_gates_list, ent_acc, label=f"{ent_label}")
-
- plt.legend()
-
- if save:
- plt.savefig('plot_srv_acc_vs_length.svg', bbox_inches='tight')
-
- plt.show()
diff --git a/genQC/inference/sampling.py b/genQC/inference/sampling.py
new file mode 100644
index 0000000..497e81b
--- /dev/null
+++ b/genQC/inference/sampling.py
@@ -0,0 +1,340 @@
+"""Sampling functions for model inference."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/inference/sampling.ipynb.
+
+# %% auto 0
+__all__ = ['get_batch_samples', 'batched_sampling', 'prepare_prompts', 'generate_tensors', 'generate_compilation_tensors',
+ 'decode_tensors_to_backend']
+
+# %% ../../src/inference/sampling.ipynb 2
+from ..imports import *
+from ..utils.async_fn import run_parallel_jobs
+from ..platform.simulation import Simulator
+from ..platform.tokenizer.base_tokenizer import BaseTokenizer
+from ..pipeline.pipeline import Pipeline
+
+# %% ../../src/inference/sampling.ipynb 4
+def get_batch_samples(samples: int, auto_batch_size: int = 512) -> list[int]:
+ batch_samples = [auto_batch_size] * int(np.floor(samples/auto_batch_size))
+
+ if samples % auto_batch_size > 0:
+ batch_samples.append(samples % auto_batch_size)
+
+ if len(batch_samples) == 0:
+ batch_samples.append(samples)
+
+ assert sum(batch_samples) == samples
+ return batch_samples
+
+# %% ../../src/inference/sampling.ipynb 5
+def batched_sampling(pipeline: Pipeline,
+ cond_kwargs: dict[str, torch.Tensor],
+ samples: int,
+ system_size: int,
+ num_of_qubits: int,
+ max_gates: int,
+ g: float = 1.0,
+ init_latents: Optional[torch.Tensor] = None,
+ no_bar: bool = True,
+ unique: bool = False,
+ auto_batch_size: int = 512,
+ enable_params: bool = True,
+ reduce_spatial: bool = True,
+ return_predicted_x0: bool = False):
+
+ """ e.g. cond_kwargs.keys = {"c", "micro_cond", "negative_c", "U"} """
+
+ assert "c" in cond_kwargs
+
+ c_in = cond_kwargs["c"].shape[0]
+ if c_in == 1:
+ # Same conditions for all samples
+ for cond in cond_kwargs.values():
+ assert cond.shape[0] == 1
+
+ cond_kwargs = {kw : val.repeat(auto_batch_size, *[1]*(val.dim()-1))
+ for kw, val in cond_kwargs.items()}
+
+ else:
+ # Different conditions for all samples
+ for cond in cond_kwargs.values():
+ assert cond.shape[0] == samples
+
+ cond_kwargs = {kw:val.to(pipeline.device)
+ for kw, val in cond_kwargs.items()}
+
+ #----------------------------------------
+ if exists(init_latents):
+ assert init_latents.shape[0] == samples
+ init_latents = init_latents.to(pipeline.device)
+
+ #----------------------------------------
+
+ # Sample and post process to tensor encodings
+ batch_samples = get_batch_samples(samples=samples, auto_batch_size=auto_batch_size)
+
+ #----------------------------------------
+
+ off = 0
+ out_tensor_list = []
+ predicted_x0_list = []
+
+ for batch_sample in batch_samples:
+ #------------
+ if c_in == 1:
+ # Same conditions for all samples
+ _cond_kwargs = {kw:val[:batch_sample]
+ for kw, val in cond_kwargs.items()}
+ else:
+ # Different conditions for all samples
+ _cond_kwargs = {kw:val[off:off+batch_sample]
+ for kw, val in cond_kwargs.items()}
+
+ #------------
+ if exists(init_latents):
+ latents = init_latents[off:off+batch_sample]
+
+ else:
+ if pipeline.embedder.channel_last:
+ latents = torch.randn((batch_sample, system_size, max_gates, pipeline.model.params_config.clr_dim))
+ else:
+ latents = torch.randn((batch_sample, pipeline.model.params_config.clr_dim, system_size, max_gates))
+
+ off += batch_sample
+
+ #------------
+ out_tensor = pipeline.denoising(latents=latents,
+ g=g,
+ no_bar=no_bar,
+ # enable_guidance=True,
+ return_predicted_x0=return_predicted_x0,
+ **_cond_kwargs)
+
+ if return_predicted_x0:
+ out_tensor, predicted_x0 = out_tensor
+
+ out_tensor_list.append(out_tensor)
+
+ if return_predicted_x0:
+ # predicted_x0 ... [timesteps, *out_tensor.shape]
+ predicted_x0_list.append(predicted_x0)
+
+ #----------------------------------------
+
+ out_tensor_raw = torch.cat(out_tensor_list).to(pipeline.device)
+
+ if return_predicted_x0:
+ predicted_x0_raw = torch.cat(predicted_x0_list, dim=1).to(pipeline.device)
+
+ if enable_params: out_tensor, params = pipeline.embedder.invert(out_tensor_raw, reduce_spatial=reduce_spatial)
+ else: out_tensor = pipeline.embedder.invert(out_tensor_raw)
+
+ #----------------------------------------
+
+ out_tensor = out_tensor[:, :num_of_qubits]
+
+ if unique:
+ if enable_params:
+ raise NotImplementedError("We have unique and enable_params enabled, how should we handle that?")
+ out_tensor = torch.unique(out_tensor, dim=0)
+
+ if not no_bar: print(f"[INFO]: (generate_comp_tensors) Generated {'unique_cnt ' if unique else ''}{out_tensor.shape[0]} tensors")
+
+ if enable_params:
+ if return_predicted_x0:
+ return out_tensor, params, predicted_x0_raw
+ return out_tensor, params
+
+ elif return_predicted_x0:
+ return out_tensor, predicted_x0_raw
+
+ return out_tensor
+
+# %% ../../src/inference/sampling.ipynb 6
+def prepare_prompts(pipeline: Pipeline,
+ prompt: str | Sequence[str],
+ negative_prompt: Optional[str | Sequence[str]] = None):
+
+ # Prepare conditions
+ c = pipeline.text_encoder.tokenize_and_push_to_device(prompt)
+
+ if exists(negative_prompt):
+ negative_c = pipeline.text_encoder.tokenize_and_push_to_device(negative_prompt)
+ assert negative_c.shape[0] == 1
+ else:
+ negative_c = None
+
+ return c, negative_c
+
+# %% ../../src/inference/sampling.ipynb 8
+def generate_tensors(pipeline: Pipeline,
+ prompt: str | Sequence[str],
+ samples: int,
+ system_size: int,
+ num_of_qubits: int,
+ max_gates: int,
+ g: float = 1.0,
+ init_latents: Optional[torch.Tensor] = None,
+ no_bar: bool = True,
+ unique: bool = False,
+ auto_batch_size: int = 512,
+ enable_params: bool = False,
+ reduce_spatial: bool = True,
+ return_predicted_x0: bool = False,
+ negative_prompt: Optional[str | Sequence[str]] = None,
+ micro_cond: Optional[torch.Tensor] = None) -> torch.Tensor:
+
+ if exists(micro_cond):
+ raise NotImplementedError()
+
+ # Prepare conditions
+ c, negative_c = prepare_prompts(pipeline, prompt, negative_prompt)
+
+ cond_kwargs = {"c":c}
+ if exists(negative_c): cond_kwargs["negative_c"] = negative_c
+ if exists(micro_cond): cond_kwargs["micro_cond"] = micro_cond
+
+ # Perform sampling
+ out = batched_sampling(pipeline=pipeline,
+ cond_kwargs=cond_kwargs,
+ samples=samples,
+ system_size=system_size,
+ num_of_qubits=num_of_qubits,
+ max_gates=max_gates,
+ g=g,
+ init_latents=init_latents,
+ no_bar=no_bar,
+ unique=unique,
+ auto_batch_size=auto_batch_size,
+ enable_params=enable_params,
+ reduce_spatial=reduce_spatial,
+ return_predicted_x0=return_predicted_x0)
+ return out
+
+# %% ../../src/inference/sampling.ipynb 9
+def generate_compilation_tensors(pipeline: Pipeline,
+ prompt: str | Sequence[str],
+ U: torch.Tensor,
+ samples: int,
+ system_size: int,
+ num_of_qubits: int,
+ max_gates: int,
+ g: float = 1.0,
+ tensor_prod_pad: bool = True,
+ init_latents: Optional[torch.Tensor] = None,
+ no_bar: bool = True,
+ unique: bool = False,
+ auto_batch_size: int = 512,
+ enable_params: bool = True,
+ reduce_spatial: bool = True,
+ return_predicted_x0: bool = False,
+ negative_prompt: Optional[str | Sequence[str]] = None,
+ negative_u: Optional[torch.Tensor] = None,
+ micro_cond: Optional[torch.Tensor] = None) -> torch.Tensor:
+ """
+ Samples tensor encodings from the DM for the given sample parameters.
+
+ What kind of unitary padding we have depends on what we used for model training, so it depends on the concrete model weights.
+ """
+
+ if torch.is_complex(U):
+ U = torch.stack([U.real, U.imag], dim=-3)
+
+ if exists(micro_cond):
+ raise NotImplementedError()
+
+ # Prepare conditions
+ c, negative_c = prepare_prompts(pipeline, prompt, negative_prompt)
+
+ cond_kwargs = {"c":c}
+ if exists(negative_c): cond_kwargs["negative_c"] = negative_c
+ if exists(micro_cond): cond_kwargs["micro_cond"] = micro_cond
+
+ def tensor_pad(U):
+ # Prepare unitary condition
+ assert U.dim() in [3, 4]
+ if U.dim() == 3:
+ # [2, N, N] to [1, 2, N, N]
+ U = U.unsqueeze(0)
+
+ if system_size > num_of_qubits:
+ N = 2**system_size
+
+ if tensor_prod_pad:
+ # Pad with identity tensor product, assume Big Endian
+
+ U_pad = torch.zeros((U.shape[0], 2, N, N), device=U.device, dtype=U.dtype)
+
+ U_side = U.shape[-1]
+ for jj in range(N//U_side):
+ _slice = slice(U_side * jj, U_side * (jj+1))
+ U_pad[..., _slice, _slice] = U
+
+ U = U_pad
+
+ else:
+ # zero pad
+ pad = (0, N-U.shape[-1], 0, N-U.shape[-2])
+ U = F.pad(U, pad, "constant", 0)
+ return U
+
+ cond_kwargs["U"] = tensor_pad(U)
+ if exists(negative_u):
+ cond_kwargs["negative_u"] = tensor_pad(negative_u)
+
+ # Perform sampling
+ out = batched_sampling(pipeline=pipeline,
+ cond_kwargs=cond_kwargs,
+ samples=samples,
+ system_size=system_size,
+ num_of_qubits=num_of_qubits,
+ max_gates=max_gates,
+ g=g,
+ init_latents=init_latents,
+ no_bar=no_bar,
+ unique=unique,
+ auto_batch_size=auto_batch_size,
+ enable_params=enable_params,
+ reduce_spatial=reduce_spatial,
+ return_predicted_x0=return_predicted_x0)
+ return out
+
+# %% ../../src/inference/sampling.ipynb 11
+def decode_tensors_to_backend(simulator: Simulator,
+ tokenizer: BaseTokenizer,
+ tensors: torch.Tensor,
+ params: Optional[torch.Tensor] = None,
+ silent: bool = True,
+ n_jobs: int = 1,
+ filter_errs: bool = True) -> tuple[Sequence[any], int]:
+ tensors = tensors.cpu()
+
+ if exists(params):
+ params = params.cpu()
+ iter_pack = zip(tensors, params)
+ _decode = lambda x, p: tokenizer.decode(x, p)
+
+ else:
+ iter_pack = zip(tensors, )
+ _decode = lambda x: tokenizer.decode(x)
+
+ def _f(iter_vars):
+ try:
+ instructions = _decode(*iter_vars)
+ backend_obj = simulator.backend.genqc_to_backend(instructions, place_barriers=False)
+ return backend_obj
+ except Exception as err:
+ if silent: return None
+ raise err
+
+ pot_qcs = run_parallel_jobs(_f, iter_pack, n_jobs)
+
+ if filter_errs:
+ backend_obj_list = [pot_qc for pot_qc in pot_qcs if exists(pot_qc)]
+ err_cnt = sum(1 for pot_qc in pot_qcs if not_exists(pot_qc))
+ assert len(backend_obj_list) + err_cnt == len(pot_qcs)
+ else:
+ backend_obj_list = pot_qcs
+ err_cnt = None
+
+ return backend_obj_list, err_cnt
diff --git a/genQC/models/clip/__init__.py b/genQC/models/clip/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/genQC/models/clip/frozen_open_clip.py b/genQC/models/clip/frozen_open_clip.py
new file mode 100644
index 0000000..941d78a
--- /dev/null
+++ b/genQC/models/clip/frozen_open_clip.py
@@ -0,0 +1,239 @@
+"""Interface to the [OpenCLIP](https://github.com/mlfoundations/open_clip) library."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../src/models/clip/frozen_open_clip.ipynb.
+
+# %% auto 0
+__all__ = ['FrozenOpenCLIPEmbedderConfig', 'FrozenOpenCLIPEmbedder', 'CachedFrozenOpenCLIPEmbedderConfig',
+ 'CachedFrozenOpenCLIPEmbedder']
+
+# %% ../../../src/models/clip/frozen_open_clip.ipynb 2
+from ...imports import *
+from ..config_model import ConfigModel
+from ...utils.async_fn import run_parallel_jobs
+from ...utils.misc_utils import infer_torch_device
+import open_clip
+
+# %% ../../../src/models/clip/frozen_open_clip.ipynb 5
+@dataclass
+class FrozenOpenCLIPEmbedderConfig:
+ arch: str
+ version: str
+ #device: str
+ max_length: int
+ freeze: bool
+ layer: str
+
+# %% ../../../src/models/clip/frozen_open_clip.ipynb 6
+class FrozenOpenCLIPEmbedder(ConfigModel):
+ """Loads and freezes the [OpenCLIP](https://github.com/mlfoundations/open_clip) transformer encoder for text prompts."""
+
+ LAYERS = [
+ # "pooled",
+ "last",
+ "penultimate"
+ ]
+
+ njobs = 1
+
+ def __init__(self, arch="ViT-B-32", version="datacomp_xl_s13b_b90k", max_length=77, freeze=True, layer="penultimate", **kwargs):
+ super().__init__(**kwargs)
+
+ assert layer in self.LAYERS
+ self.params_config = FrozenOpenCLIPEmbedderConfig(arch, version, max_length, freeze, layer)
+
+ model, _, _ = open_clip.create_model_and_transforms(arch, device="cpu", pretrained=version)
+ self.device = "cpu"
+
+ del model.visual
+ self.model = model
+ # self.to(device)
+
+ self.tokenizer = open_clip.get_tokenizer(arch)
+ assert torch.numel(self.tokenizer("test"))
+
+ assert max_length <= 77 # max set by the clip
+ self.max_length = max_length
+
+ if freeze: self.freeze()
+
+ self.layer = layer
+ if self.layer == "last": self.layer_idx = 0
+ elif self.layer == "penultimate": self.layer_idx = 1
+ else: raise NotImplementedError()
+
+ #create empty token, can also be, e.g., A nice picture
+ self.empty_token = self.tokenize_and_push_to_device("")
+
+ def freeze(self, freeze: bool = True):
+ super().freeze(freeze=freeze)
+
+ for param in self.model.parameters():
+ param.requires_grad = not freeze
+
+ def to(self, device):
+ self.model = self.model.to(device)
+ self.device = device
+ return self
+
+ @torch.inference_mode()
+ def tokenize_and_push_to_device(self, text, to_device=True):
+ if self.njobs > 1:
+
+ tokens_list = run_parallel_jobs(self.tokenizer, np.array_split(text, self.njobs), self.njobs)
+ tokens = torch.cat(tokens_list, dim=0)
+
+ else:
+ # tokens = open_clip.tokenize(text)
+ tokens = self.tokenizer(text)
+
+ if to_device:
+ tokens = tokens.to(self.device)
+ return tokens
+
+ @torch.inference_mode()
+ def forward(self, c, **kwargs):
+ return self.encode_with_transformer(c)
+
+ @torch.inference_mode()
+ def encode_with_transformer(self, text):
+ cast_dtype = self.model.transformer.get_cast_dtype()
+
+ x = self.model.token_embedding(text).to(cast_dtype) # [batch_size, n_ctx, d_model]
+ x = x + self.model.positional_embedding[None, :x.shape[1]].to(cast_dtype)
+
+ if not self.model.transformer.batch_first:
+ x = x.permute(1, 0, 2) # NLD -> LND
+
+ x = self.text_transformer_forward(x, attn_mask=self.model.attn_mask)
+
+ if not self.model.transformer.batch_first:
+ x = x.permute(1, 0, 2) # LND -> NLD
+
+ x = self.model.ln_final(x) # [batch_size, n_ctx, transformer.width]
+
+ return x
+
+ @torch.inference_mode()
+ def text_transformer_forward(self, x: torch.Tensor, attn_mask=None):
+ for i, r in enumerate(self.model.transformer.resblocks):
+ if i == len(self.model.transformer.resblocks) - self.layer_idx:
+ break
+ #if self.model.transformer.grad_checkpointing and not torch.jit.is_scripting():
+ #x = checkpoint(r, x, attn_mask)
+ #else:
+
+ x = r(x, attn_mask=attn_mask)
+
+ return x
+
+ #--------------------------------------------------------------
+
+ def get_config(self, save_path=None, without_metadata=False):
+ return super().get_config(save_path=None, without_metadata=without_metadata)
+
+ def store_model(self, config_path: str, save_path: str=None, without_metadata=False):
+ super().store_model(config_path, save_path=None, without_metadata=without_metadata)
+
+ @staticmethod
+ def from_config(config, device: torch.device, save_path: str=None):
+ config["save_path"] = None
+ return ConfigModel.from_config(config, device, save_path=None)
+
+# %% ../../../src/models/clip/frozen_open_clip.ipynb 17
+@dataclass
+class CachedFrozenOpenCLIPEmbedderConfig(FrozenOpenCLIPEmbedderConfig):
+ enable_cache_token_limit: bool
+
+# %% ../../../src/models/clip/frozen_open_clip.ipynb 18
+class CachedFrozenOpenCLIPEmbedder(FrozenOpenCLIPEmbedder):
+ """Adds caching support to `FrozenOpenCLIPEmbedder`."""
+
+ def __init__(self, arch="ViT-B-32", version="datacomp_xl_s13b_b90k", max_length=77, freeze=True, layer="penultimate", enable_cache_token_limit: bool = True, **kwargs):
+ super().__init__(arch=arch, version=version, max_length=max_length, freeze=freeze, layer=layer, **kwargs)
+ self.enable_cache_token_limit = enable_cache_token_limit
+
+ self.params_config = CachedFrozenOpenCLIPEmbedderConfig(arch, version, max_length, freeze, layer, enable_cache_token_limit)
+
+ def get_token_count(self, tokens, padding_token=0):
+ # tokens .. [b, seq]
+ collabsed_tokens = (tokens != padding_token).to(torch.int32)
+ return torch.count_nonzero(collabsed_tokens, dim=-1) # [b]
+
+ @torch.inference_mode()
+ def generate_cache(self, str_list: list=None, tokens=None, cached_empty_token_index=None, b_size=2048, y_on_cpu=False):
+ self.cached_empty_token_index = cached_empty_token_index
+ if exists(str_list): self.cached_tokens = self.tokenize_and_push_to_device(str_list)
+ elif exists(tokens): self.cached_tokens = tokens
+ else: raise RuntimeError("please provide str_list or tokens")
+
+ # note: we need to split the tokens in batches for forward pass, n gets large
+ # cached_tokens [n, 77] ... int
+ # cached_embeddings [n, 77, 512] ... float
+
+ if self.enable_cache_token_limit:
+ self.max_length = self.get_token_count(self.cached_tokens).max().item()
+ self.params_config.max_length = self.max_length
+ self.params_config.enable_cache_token_limit = self.enable_cache_token_limit
+ print(f"[INFO]: - `generate_cache` infered a TOKEN limit of {self.max_length}")
+
+ #self.cached_tokens = self.cached_tokens[:, :self.max_length]
+
+ n = self.cached_tokens.shape[0]
+
+ n_chunks = int(np.ceil(n / b_size))
+
+ in_device = self.cached_tokens.device
+
+ last_ind = 0
+ for i, cached_tokens in tqdm(enumerate(self.cached_tokens.chunk(n_chunks)), total=n_chunks):
+
+ x = super().forward(cached_tokens.to(self.device)) # ... [b, seq, ch]
+
+ if i == 0:
+ mem = n * x.shape[1] * x.shape[2] * x.element_size() * 1e-9
+ print(f"[INFO]: caching trying to allocate memory {(n, x.shape[1], x.shape[2])} on {'cpu' if y_on_cpu else self.device}, approx. {mem:.3f} GB")
+ self.cached_embeddings = torch.zeros((n, x.shape[1], x.shape[2]), device="cpu" if y_on_cpu else self.device, dtype=x.dtype) # alloc huge memory !!
+
+ self.cached_embeddings[last_ind:last_ind+x.shape[0]] = x.to(self.cached_embeddings.device)
+
+ last_ind += x.shape[0]
+
+ if self.enable_cache_token_limit:
+ self.cached_embeddings = self.cached_embeddings[:, :self.max_length]
+
+ if not y_on_cpu:
+ self.cached_embeddings = self.cached_embeddings.to(in_device)
+
+ @torch.inference_mode()
+ def look_up_cos_sim_cached_index(self, str_list: list=None, tokens=None):
+ if exists(str_list): tokens = self.tokenize_and_push_to_device(str_list)
+ else: raise RuntimeError("please provide str_list or tokens")
+
+ emb = super().forward(tokens.to(self.device))
+ c_emb = self.cached_embeddings
+ #-----------------
+ # do cos sim search
+
+ emb = emb.flatten(start_dim=1) # [m, seq*ch]
+ c_emb = c_emb.flatten(start_dim=1) # [n, seq*ch]
+
+ norm_emb = emb / torch.linalg.vector_norm( emb, dim=1, keepdim=True)
+ norm_c_emb = c_emb / torch.linalg.vector_norm(c_emb, dim=1, keepdim=True)
+
+ sim = torch.matmul(norm_c_emb, norm_emb.T) # matmul out is [n, m]
+ max_idx = torch.argmax(sim, dim=0) # reduce the c_emb dim, [m]
+
+ return max_idx
+
+ # @torch.inference_mode()
+ def forward(self, c, **kwargs):
+ in_device = c.device
+
+ if c.dim() == 1: c_emb = self.cached_embeddings[c.to(self.cached_embeddings.device)].to(in_device) #list of ints
+ elif c.dim() == 2: c_emb = super().forward(c.to(self.device)) #tokenized input
+ else: raise NotImplementedError("")
+
+ if self.enable_cache_token_limit:
+ c_emb = c_emb[:, :self.max_length]
+
+ return c_emb
diff --git a/genQC/models/clip/unitary_clip.py b/genQC/models/clip/unitary_clip.py
new file mode 100644
index 0000000..fca32cd
--- /dev/null
+++ b/genQC/models/clip/unitary_clip.py
@@ -0,0 +1,686 @@
+"""Contrastive pre-training of an unitary encoder"""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../src/models/clip/unitary_clip.ipynb.
+
+# %% auto 0
+__all__ = ['RotaryMultiheadAttention', 'FeedForwardBlock', 'UnitaryEncoderAttnBlock', 'UnitaryTextEncoderConfig',
+ 'UnitaryTextEncoder', 'SelfAttnBlock', 'PackingTransformer', 'CoreTransformer', 'CircuitEncoderConfig',
+ 'CircuitEncoder', 'UnitaryCLIPConfig', 'UnitaryCLIP']
+
+# %% ../../../src/models/clip/unitary_clip.ipynb 2
+from ...imports import *
+from ..config_model import ConfigModel
+import genQC.models.transformers.attention as attn
+import genQC.models.layers as layers
+from ..position_encoding import LearnedPositionalEmbedding, RotaryPositionalEmbedding, RotaryPositionalEmbedding2D
+
+# %% ../../../src/models/clip/unitary_clip.ipynb 4
+class RotaryMultiheadAttention(nn.Module):
+ """
+ MultiheadAttention described in the paper: Attention Is All You Need (https://arxiv.org/abs/1706.03762).
+ We add a rotary position encoding (RoPE).
+
+ The attention core is `F.scaled_dot_attention` from pytorch.
+ Could be switched to `https://github.com/Dao-AILab/flash-attention` or `xFormers`.
+ """
+
+ def __init__(self,
+ in_dim: int,
+ embed_dim: int,
+ num_heads: int,
+ bias: bool = True,
+ p_rope: float = 1.0,
+ max_seq_len: int = 4096,
+ base_rope: float = 10_000,
+ enable_qk_norm: bool = False) -> None:
+
+ super().__init__()
+
+ self.num_heads = num_heads
+ self.bias = bias
+ self.head_dim = embed_dim // num_heads
+
+ self.q_proj = nn.Linear(in_dim, embed_dim, bias=bias)
+ self.k_proj = nn.Linear(in_dim, embed_dim, bias=bias)
+ self.v_proj = nn.Linear(in_dim, embed_dim, bias=bias)
+
+ self.out_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
+
+ self.enable_qk_norm = enable_qk_norm
+ if self.enable_qk_norm:
+ self.q_norm = nn.RMSNorm(self.head_dim)
+ self.k_norm = nn.RMSNorm(self.head_dim)
+
+ self.rope = RotaryPositionalEmbedding(head_dim=self.head_dim, p=p_rope, max_seq_len=max_seq_len, base=base_rope)
+
+ self._init_weights()
+
+ def _init_weights(self) -> None:
+ nn.init.xavier_normal_(self.q_proj.weight)
+ nn.init.xavier_normal_(self.k_proj.weight)
+ nn.init.xavier_normal_(self.v_proj.weight)
+ nn.init.xavier_normal_(self.out_proj.weight)
+
+ if self.bias:
+ nn.init.zeros_(self.q_proj.bias)
+ nn.init.zeros_(self.k_proj.bias)
+ nn.init.zeros_(self.v_proj.bias)
+ nn.init.zeros_(self.out_proj.bias)
+
+
+ def forward(self, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor, pos_idx: Optional[torch.Tensor] = None) -> torch.Tensor:
+ """
+ Assumes batch first. When `pos_idx` is provided we use RoPE, else NOT!
+
+ Shapes:
+ query ... [b, n1, c]
+ key/value ... [b, n2, c]
+ """
+
+ assert key.shape == value.shape
+
+ b, n1, _ = query.shape
+ _, n2, _ = key.shape
+
+ q = self.q_proj(query)
+ k = self.k_proj(key)
+ v = self.v_proj(value)
+
+ q = q.view(b, n1, self.num_heads, self.head_dim)
+ k = k.view(b, n2, self.num_heads, self.head_dim)
+ v = v.view(b, n2, self.num_heads, self.head_dim)
+
+ if self.enable_qk_norm:
+ q = self.q_norm(q)
+ k = self.k_norm(k)
+
+ if exists(pos_idx):
+ q = self.rope(q, pos_idx=pos_idx)
+ k = self.rope(k, pos_idx=pos_idx)
+
+ # scaled_dot_product_attention takes [b, num_heads, seq, head_dim]
+ q = q.permute((0, 2, 1, 3))
+ k = k.permute((0, 2, 1, 3))
+ v = v.permute((0, 2, 1, 3))
+
+ # see https://pytorch.org/docs/stable/generated/torch.nn.functional.scaled_dot_product_attention.html
+ attn = F.scaled_dot_product_attention(query=q,
+ key=k,
+ value=v,
+ attn_mask=None,
+ dropout_p=0.0,
+ is_causal=False,
+ scale=None,
+ #enable_gqa=False
+ )
+
+ # back to [b, seq, num_heads, head_dim]
+ attn = attn.permute((0, 2, 1, 3))
+
+ # pack heads together
+ attn = attn.reshape(b, n1, self.num_heads * self.head_dim)
+ attn = self.out_proj(attn)
+ return attn
+
+# %% ../../../src/models/clip/unitary_clip.ipynb 5
+class FeedForwardBlock(nn.Module):
+ """
+ A small dense feed-forward network as used in `transformers`. Assumes channel last.
+ Inspired by https://arxiv.org/pdf/2401.11605 and added
+ from https://arxiv.org/pdf/2002.05202 a modification to SiGLU structure.
+ """
+
+ def __init__(self, in_dim: int, hidden_dim: int, dropout: float = 0.0) -> None:
+ super().__init__()
+ self.hidden_dim = hidden_dim
+ self.proj_in = nn.Linear(in_dim, 2*hidden_dim) # factor two for GLU part split
+ self.proj_out = nn.Linear(hidden_dim, in_dim)
+ self.act = nn.SiLU()
+ self.drop = nn.Dropout(dropout)
+
+ self._init_weights()
+
+ def _init_weights(self) -> None:
+ nn.init.zeros_(self.proj_out.bias)
+ # nn.init.xavier_normal_(self.proj_out.weight)
+
+ def siglu(self, x: torch.Tensor) -> torch.Tensor:
+ x = self.proj_in(x)
+ return x[..., :self.hidden_dim] * self.act(x[..., self.hidden_dim:])
+
+ #@torch.compile
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = self.siglu(x)
+ x = self.drop(x)
+ x = self.proj_out(x)
+ return x
+
+# %% ../../../src/models/clip/unitary_clip.ipynb 7
+class UnitaryEncoderAttnBlock(nn.Module):
+ """A self-attention block with 2d-RoPE."""
+
+ def __init__(self,
+ ch: int,
+ y_emb_size: int,
+ num_heads: int,
+ dropout: float = 0.0,
+ p_rope: float = 1.0,
+ base_rope: float = 10_000) -> None:
+ super().__init__()
+
+ self.self_att = RotaryMultiheadAttention(in_dim=ch+y_emb_size, embed_dim=ch, num_heads=num_heads, p_rope=p_rope, base_rope=base_rope)
+ self.self_att.rope = RotaryPositionalEmbedding2D(head_dim=self.self_att.head_dim, p=p_rope, base=base_rope)
+
+ self.ff = FeedForwardBlock(in_dim=ch, hidden_dim=2*ch)
+ self.norm_self = nn.RMSNorm(ch)
+ self.norm_ff = nn.RMSNorm(ch)
+ self.drop = nn.Dropout(dropout)
+
+ self._init_weights()
+
+ def _init_weights(self) -> None:
+
+ # note a bonus of res-pos-norm is that we can init as identity!
+ nn.init.zeros_(self.norm_self.weight)
+ nn.init.zeros_(self.norm_ff.weight)
+
+ def forward(self, x: torch.Tensor, y_emb: torch.Tensor, pos_idx: torch.Tensor) -> torch.Tensor:
+ """
+ Assumes batch first.
+
+ Shapes:
+ x ... [b, n, ch1]
+ y_emb ... [b, n, ch2]
+ pos_idx ... [b, n, 2] or [n, 2]
+ """
+
+ # Self-attention part
+ self_out = torch.cat([x, y_emb], dim=-1)
+ self_out = self.self_att(query=self_out, key=self_out, value=self_out, pos_idx=pos_idx)
+ self_out = self.norm_self(self_out)
+ self_out = self.drop(self_out) + x
+
+ # Feed-Forward part
+ feed_out = self.ff(self_out)
+ feed_out = self.norm_ff(feed_out)
+ feed_out = self.drop(feed_out) + self_out
+ return feed_out
+
+# %% ../../../src/models/clip/unitary_clip.ipynb 8
+@dataclass
+class UnitaryTextEncoderConfig:
+ text_embed_ch: int
+ text_encoding_ch: int
+ text_attn_num_heads: int
+ text_attn_depth: int
+
+ unitary_encoding_ch: int
+ unitary_downscale_factor: int
+
+ main_num_heads: int
+ main_depth: int
+
+ use_rope: bool
+ p_rope: float
+ base_rope: float
+ dropout: float
+
+# %% ../../../src/models/clip/unitary_clip.ipynb 9
+class UnitaryTextEncoder(ConfigModel):
+ def __init__(self,
+ text_embed_ch: int,
+ text_encoding_ch: int,
+ text_attn_num_heads: int,
+ text_attn_depth: int,
+ unitary_encoding_ch: int,
+ unitary_downscale_factor: int,
+ main_num_heads: int,
+ main_depth: int,
+ use_rope: bool,
+ p_rope: float,
+ base_rope: float,
+ dropout: float) -> None:
+ """
+ text_embed_ch ... number of channels of the input text encodings `y_emb`
+
+ The text channels `text_encoding_ch` are concatenated with the unitary channels `unitary_encoding_ch`.
+ """
+ super().__init__()
+
+ self.params_config = UnitaryTextEncoderConfig(text_embed_ch=text_embed_ch,
+ text_encoding_ch=text_encoding_ch,
+ text_attn_num_heads=text_attn_num_heads,
+ text_attn_depth=text_attn_depth,
+ unitary_encoding_ch=unitary_encoding_ch,
+ unitary_downscale_factor=unitary_downscale_factor,
+ main_num_heads=main_num_heads,
+ main_depth=main_depth,
+ use_rope=use_rope,
+ p_rope=p_rope,
+ base_rope=base_rope,
+ dropout=dropout)
+
+ # Text pre-process
+ self.text_proj = nn.Linear(text_embed_ch, text_encoding_ch)
+ self.text_norm = nn.RMSNorm(text_encoding_ch)
+
+ self.text_attn_blocks = nn.ModuleList([attn.BasisSelfAttnBlock(ch=text_encoding_ch,
+ num_heads=text_attn_num_heads,
+ dropout=dropout,
+ batch_first=True)
+ for d in range(text_attn_depth)
+ ])
+
+ # Unitary pre-process
+ self.unitary_proj = nn.Conv2d(2, unitary_encoding_ch, kernel_size=1, stride=1, padding="same")
+ self.unitary_downscale = nn.PixelUnshuffle(unitary_downscale_factor)
+ self.unitary_downscale_factor = unitary_downscale_factor
+
+ self.use_rope = use_rope
+ if not self.use_rope:
+ self.unitary_pos_enc = layers.PositionalEncoding2D(d_model=unitary_encoding_ch, freq_factor=1_000)
+
+ # Main transformer
+ self.encoding_ch = unitary_encoding_ch * (unitary_downscale_factor**2)
+
+ self.transformer_blocks = nn.ModuleList([UnitaryEncoderAttnBlock(ch=self.encoding_ch,
+ y_emb_size=text_encoding_ch,
+ num_heads=main_num_heads,
+ dropout=dropout,
+ p_rope=p_rope,
+ base_rope=base_rope)
+ for d in range(main_depth)
+ ])
+
+ self.norm_final = nn.RMSNorm(self.encoding_ch)
+
+ print(f"[INFO]: Creating `UnitaryTextEncoder` with `{unitary_downscale_factor=}` and `encoding_ch={self.encoding_ch}`.")
+ self._init_weights()
+
+ def _init_weights(self) -> None:
+ # nn.init.xavier_normal_(self.text_proj.weight)
+ # nn.init.xavier_normal_(self.unitary_proj.weight)
+
+ nn.init.zeros_(self.text_proj.bias)
+ nn.init.zeros_(self.unitary_proj.bias)
+
+ def preproc_text(self, y_emb):
+ y_emb = self.text_proj(y_emb) # ... [batch, seq_y, text_encoding_ch]
+
+ for text_attn_block in self.text_attn_blocks:
+ y_emb = text_attn_block(y_emb)
+
+ return y_emb
+
+ def preproc_unitary(self, U):
+ u_emb = self.unitary_proj(U) # ... [batch, unitary_encoding_ch, N, N]
+ if not self.use_rope:
+ u_emb = self.unitary_pos_enc(u_emb)
+ u_emb = self.unitary_downscale(u_emb) # ... [batch, unitary_encoding_ch * r^2, N/r, N/r]
+
+ # Reshape and permute from image to sentence shape
+ b, ch, *_ = u_emb.shape
+ u_emb = torch.reshape(u_emb, (b, ch, -1)) # to [batch, unitary_encoding_ch * r^2, (N/r)^2]
+ u_emb = torch.permute(u_emb, (0, 2, 1)) # to [batch, (N/r)^2, unitary_encoding_ch * r^2]
+
+ return u_emb
+
+ def forward(self, y_emb: torch.Tensor, U: torch.Tensor, pool: bool = False, penultimate: bool = False) -> torch.Tensor:
+ """
+ penultimate_output = False ... take all attn layers
+ penultimate_output = True ... skip the last attn layers
+
+ Shapes:
+ y_emb ... [b, seq, text_embed_ch]
+ U ... [b, 2, N, N]
+ """
+
+ # Pre-process multimodial inputs
+ x = self.preproc_unitary(U) # ... [batch, seq_u, unitary_encoding_ch * r^2]
+ y_emb = self.preproc_text(y_emb) # ... [batch, seq_y, text_encoding_ch]
+
+ y_emb = y_emb.mean(dim=1, keepdim=True) # ... [batch, 1, text_encoding_ch]
+ y_emb = self.text_norm(y_emb)
+ y_emb = y_emb.expand(x.shape[0], x.shape[1], -1) # ... [batch, seq_u, text_encoding_ch]
+
+ # Main transformer pass
+ if self.use_rope:
+ N = U.shape[-1] // self.unitary_downscale_factor
+ pos = torch.arange(N).expand(N, -1)
+ pos_idx = torch.stack([pos.T, pos], dim=-1).reshape(-1, 2) # ... [seq_u, 2]
+ else:
+ pos_idx = None
+
+ if not penultimate:
+ for transformer_block in self.transformer_blocks:
+ x = transformer_block(x, y_emb=y_emb, pos_idx=pos_idx)
+
+ else:
+ for transformer_block in self.transformer_blocks[:-1]:
+ x = transformer_block(x, y_emb=y_emb, pos_idx=pos_idx)
+
+ if pool:
+ x = torch.mean(x, dim=1) # [batch, ch]
+
+ x = self.norm_final(x)
+ return x
+
+# %% ../../../src/models/clip/unitary_clip.ipynb 11
+class SelfAttnBlock(nn.Module):
+ """A self-attention block with RoPE."""
+
+ def __init__(self, ch: int, num_heads: int, dropout: float = 0.0, p_rope: float = 1.0, base_rope: float = 10_000) -> None:
+ super().__init__()
+
+ self.self_att = RotaryMultiheadAttention(in_dim=ch, embed_dim=ch, num_heads=num_heads, p_rope=p_rope, base_rope=base_rope)
+
+ self.ff = FeedForwardBlock(in_dim=ch, hidden_dim=2*ch, dropout=dropout)
+ self.norm_self = nn.RMSNorm(ch)
+ self.norm_ff = nn.RMSNorm(ch)
+ self.drop = nn.Dropout(dropout)
+
+ self._init_weights()
+
+ def _init_weights(self) -> None:
+
+ # note a bonus of res-pos-norm is that we can init as identity!
+ nn.init.zeros_(self.norm_self.weight)
+ nn.init.zeros_(self.norm_ff.weight)
+
+ def forward(self, x: torch.Tensor, pos_idx: torch.Tensor) -> torch.Tensor:
+ """
+ Assumes batch first.
+
+ Shapes:
+ x ... [b, n, ch]
+ pos_idx ... [b, n]
+ """
+
+ # Self-attention part
+ self_out = x
+ self_out = self.self_att(query=self_out, key=self_out, value=self_out, pos_idx=pos_idx)
+ self_out = self.norm_self(self_out)
+ self_out = self.drop(self_out) + x
+
+ # Feed-Forward part
+ feed_out = self.ff(self_out)
+ feed_out = self.norm_ff(feed_out)
+ feed_out = self.drop(feed_out) + self_out
+ return feed_out
+
+# %% ../../../src/models/clip/unitary_clip.ipynb 12
+class PackingTransformer(ConfigModel):
+ """
+ The first stage packing/unpacking transformers of the CirDiT model.
+ Applies a RoPE for time dimension only, not on spatial dimension.
+ """
+
+ def __init__(self,
+ ch: int,
+ depth: int,
+ num_heads: int,
+ dropout: float = 0.0,
+ p_rope: float = 1.0,
+ base_rope: float = 10_000) -> None:
+ super().__init__()
+
+ self.blocks = nn.ModuleList([
+ SelfAttnBlock(ch=ch,
+ num_heads=num_heads,
+ dropout=dropout,
+ p_rope=p_rope,
+ base_rope=base_rope)
+ for d in range(depth)
+ ])
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ """
+ Shapes:
+ x ... [b, s, t, ch]
+ """
+
+ b, s, t, ch = x.shape
+
+ # create pos_idx such that they only depend on the time position
+ pos_idx = torch.arange(t, device=x.device, dtype=torch.int32).expand(b, s, -1)
+ pos_idx = pos_idx.reshape(b, -1)
+
+ # flatten spatial and time into seq
+ x = x.reshape(b, s*t, ch)
+
+ for block in self.blocks:
+ x = block(x=x, pos_idx=pos_idx)
+
+ # undo flatten
+ x = x.reshape(b, s, t, ch)
+
+ return x
+
+# %% ../../../src/models/clip/unitary_clip.ipynb 13
+class CoreTransformer(nn.Module):
+ """
+ The main transformer of the `CirDiT` model.
+ Applies a RoPE for time dimension.
+ """
+
+ def __init__(self,
+ ch: int,
+ depth: int,
+ num_heads: int,
+ dropout: float = 0.0,
+ p_rope: float = 1.0,
+ base_rope: float = 10_000) -> None:
+ super().__init__()
+
+ self.blocks = nn.ModuleList([
+ SelfAttnBlock(ch=ch,
+ num_heads=num_heads,
+ dropout=dropout,
+ p_rope=p_rope,
+ base_rope=base_rope)
+ for d in range(depth)
+ ])
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ """
+ Shapes:
+ x ... [b, t, ch]
+ """
+
+ pos_idx = torch.arange(x.shape[1], device=x.device, dtype=torch.int32)
+
+ for block in self.blocks:
+ x = block(x=x, pos_idx=pos_idx)
+
+ return x
+
+# %% ../../../src/models/clip/unitary_clip.ipynb 14
+@dataclass
+class CircuitEncoderConfig:
+ embedder_config: dict
+
+ ch_packing: int
+ ch_core: int
+
+ depth_packing: int
+ depth_core: int
+
+ num_heads_packing: int
+ num_heads_core: int
+
+ dropout: float
+ p_rope: float
+ base_rope: float
+
+# %% ../../../src/models/clip/unitary_clip.ipynb 15
+class CircuitEncoder(ConfigModel):
+ def __init__(self,
+ embedder_config: Optional[dict],
+ ch_packing: int,
+ ch_core: int,
+ depth_packing: int,
+ depth_core: int,
+ num_heads_packing: int,
+ num_heads_core: int,
+ dropout: float = 0.0,
+ p_rope: float = 1.0,
+ base_rope: float = 10_000,
+ embedder: Optional[nn.Module] = None) -> None:
+ super().__init__()
+
+ if exists(embedder):
+ self.embedder = embedder
+ embedder_config = self.embedder.get_config(None)
+ else:
+ assert exists(embedder_config)
+
+ self.params_config = CircuitEncoderConfig(embedder_config=embedder_config,
+ ch_packing=ch_packing,
+ ch_core=ch_core,
+ depth_packing=depth_packing,
+ depth_core=depth_core,
+ num_heads_packing=num_heads_packing,
+ num_heads_core=num_heads_core,
+ dropout=dropout,
+ p_rope=p_rope,
+ base_rope=base_rope)
+
+ if not_exists(embedder):
+ self.embedder = ConfigModel.from_config(embedder_config, device=None, silent=True)
+
+ self.packing = PackingTransformer(ch=ch_packing,
+ depth=depth_packing,
+ num_heads=num_heads_packing,
+ dropout=dropout,
+ p_rope=p_rope,
+ base_rope=base_rope)
+
+ self.core = CoreTransformer(ch=ch_core,
+ depth=depth_core,
+ num_heads=num_heads_core,
+ dropout=dropout,
+ p_rope=p_rope,
+ base_rope=base_rope)
+
+ self.encoding_ch = ch_core
+
+ self.proj_in = nn.Linear(self.embedder.embedding_dim, ch_packing)
+ self.core_proj = nn.Linear(ch_packing, ch_core)
+
+ self.norm_packing = nn.RMSNorm(ch_packing)
+ self.norm_core = nn.RMSNorm(ch_core)
+ self.norm_final = nn.RMSNorm(ch_core)
+
+ self.qubit_pos_enc = LearnedPositionalEmbedding(dim=ch_packing, max_seq_len=64) #here max number of qubits
+
+ self._init_weights()
+
+ def _init_weights(self) -> None:
+ nn.init.orthogonal_(self.core_proj.weight)
+ nn.init.zeros_(self.core_proj.bias)
+
+ def forward(self, tokens: torch.Tensor, params: torch.Tensor, pool: bool = False) -> torch.Tensor:
+ # Embed the circuits
+ x = self.embedder(h=tokens, w=params)
+
+ # Pre-process circuit and add pos-encoding
+ b, s, t, _ = x.shape
+
+ x = self.proj_in(x)
+ x = self.qubit_pos_enc(x)
+
+ # Pack spatial into tokens
+ x = self.norm_packing(x)
+ x = self.packing(x=x)
+
+ # Downsample, reduce spatial, ... [b, t, ch_core]
+ x_main = x.mean(dim=1)
+ x_main = self.core_proj(x_main)
+
+ # Core transformer
+ x_main = self.norm_core(x_main)
+ x_main = self.core(x=x_main)
+
+ if pool:
+ x_main = torch.mean(x_main, dim=1) # [b, ch]
+
+ x_main = self.norm_final(x_main)
+ return x_main
+
+# %% ../../../src/models/clip/unitary_clip.ipynb 17
+@dataclass
+class UnitaryCLIPConfig:
+ text_encoder_config: dict
+ clip_embed_size: int
+
+# %% ../../../src/models/clip/unitary_clip.ipynb 18
+class UnitaryCLIP(ConfigModel):
+
+ def __init__(self,
+ text_encoder_config: Optional[dict],
+ unitary_text_encoder: UnitaryTextEncoder,
+ circuit_encoder: CircuitEncoder,
+ clip_embed_size: int,
+ text_encoder: Optional[nn.Module] = None) -> None:
+ super().__init__()
+
+ if exists(text_encoder):
+ self.text_encoder = text_encoder
+ text_encoder_config = self.text_encoder.get_config(None)
+ else:
+ assert exists(text_encoder_config)
+
+ self.params_config = UnitaryCLIPConfig(text_encoder_config=text_encoder_config,
+ clip_embed_size=clip_embed_size)
+
+ if not_exists(text_encoder):
+ if "device" in text_encoder_config:
+ device = text_encoder_config["device"]
+ else:
+ device = "cpu"
+
+ self.text_encoder = ConfigModel.from_config(text_encoder_config, device=device, silent=True)
+
+ self.unitary_text_encoder = unitary_text_encoder
+ self.circuit_encoder = circuit_encoder
+
+ self.unitary_text_proj = nn.Linear(self.unitary_text_encoder.encoding_ch, clip_embed_size)
+ self.circuit_proj = nn.Linear(self.circuit_encoder.encoding_ch , clip_embed_size)
+ self.temperature = torch.nn.Parameter(torch.zeros(1))
+
+ self._init_weights()
+
+ def _init_weights(self) -> None:
+ initrange = 0.1
+ self.unitary_text_proj.bias.data.zero_()
+ self.unitary_text_proj.weight.data.uniform_(-initrange, initrange)
+ self.circuit_proj.bias.data.zero_()
+ self.circuit_proj.weight.data.uniform_(-initrange, initrange)
+
+ def forward(self, tokens: torch.Tensor, params: torch.Tensor, y: torch.Tensor, U: torch.Tensor) -> torch.Tensor:
+
+ y_emb = self.text_encoder(y, pool=False)
+
+ ut_enc = self.unitary_text_encoder(y_emb=y_emb, U=U, pool=True)
+ ut_enc = self.unitary_text_proj(ut_enc) # out [b, embed_size]
+ ut_enc = F.normalize(ut_enc, dim=-1)
+
+ #------------
+
+ qc_enc = self.circuit_encoder(tokens=tokens, params=params, pool=True)
+ qc_enc = self.circuit_proj(qc_enc) # out [b, embed_size]
+ qc_enc = F.normalize(qc_enc, dim=-1)
+
+ #------------
+
+ scores = torch.matmul(ut_enc, qc_enc.T) * torch.exp(self.temperature) #[b, b]
+
+ #scores is: I=unitary_text T=circuit
+ #--------------------------------
+ #| I1*T1 I1*T2 I1*T3 ...
+ #| I2*T1
+ #| I3*T1
+ # ...
+ #--------------------------------
+
+ return scores
diff --git a/genQC/models/config_model.py b/genQC/models/config_model.py
index 6b39be8..e300091 100644
--- a/genQC/models/config_model.py
+++ b/genQC/models/config_model.py
@@ -1,27 +1,51 @@
+"""Model base class that handles loading and storing from/to config files."""
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/models/config_model.ipynb.
# %% auto 0
-__all__ = ['Config_Model']
+__all__ = ['ConfigModel']
-# %% ../../src/models/config_model.ipynb 3
+# %% ../../src/models/config_model.ipynb 2
from ..imports import *
-from ..config_loader import *
-from ..util import *
+from ..utils.config_loader import *
+from ..utils.misc_utils import *
from datetime import datetime
-# %% ../../src/models/config_model.ipynb 5
-class Config_Model(nn.Module):
+# %% ../../src/models/config_model.ipynb 4
+class ConfigModel(nn.Module):
"""A basic `nn.Module` with IO functionality."""
- def __init__(self): super().__init__()
- #---------------------
+ def __init__(self, save_type=None):
+ super().__init__()
+ self.save_type = default(save_type, "safetensors")
+ def freeze(self, freeze: bool = True):
+ if freeze: self.eval()
+ else: self.train()
+
+ for param in self.parameters():
+ param.requires_grad = not freeze
+
+ #Todo: add a debose/debug log here
+
+ def unfreeze(self):
+ self.freeze(False)
+
+ #---------------------
+
+ def check_save_type(self, save_path):
+ if exists(self.save_type) and exists(save_path):
+ if not save_path.endswith(f".{self.save_type}"):
+ save_path += f".{self.save_type}"
+ return save_path
+
def get_config(self, save_path=None, without_metadata=False):
if not without_metadata:
config = {}
config["target"] = class_to_str(type(self))
- config["save_path"] = save_path
+ config["save_path"] = self.check_save_type(self.save_path) if hasattr(self, "save_path") and not exists(save_path) else self.check_save_type(save_path)
config["save_datetime"] = datetime.now().strftime("%m/%d/%Y %H:%M:%S")
+ config["save_type"] = self.save_type
config["params"] = self.params_config
else:
config = self.params_config
@@ -30,41 +54,82 @@ def get_config(self, save_path=None, without_metadata=False):
return config
def store_model(self, config_path: str=None, save_path: str=None, without_metadata=False):
-
+
config = self.get_config(save_path, without_metadata)
if exists(config_path):
if without_metadata: save_dataclass_yaml(config, config_path)
else : save_dict_yaml(config, config_path)
-
+
if exists(save_path):
- torch.save(self.state_dict(), save_path)
+ store_model_state_dict(self.state_dict(), self.check_save_type(save_path))
#---------------------
@staticmethod
- def from_config(config, device: torch.device, save_path: str=None):
+ def from_config(config, device: torch.device, save_path: str=None, verbose=True, silent=False, freeze: Optional[bool] = None):
"""Use this if we have a loaded config. Maybe within other classes (e.g. pipeline and nested models)"""
+
+ _config = copy.deepcopy(config)
- model = instantiate_from_config(config)
- model = model.to(device)
- print(f"[INFO]: `{class_to_str(type(model))}` instantiated from given config on {device}.")
+ if exists(device): _config["device"] = device # for loading sub-models
+ else: device = _config.pop("device", "cpu")
+
+ if exists(freeze):
+ _freeze = freeze
+
+ else:
+ if "is_frozen" in _config:
+ _freeze = _config.pop("is_frozen", None)
+ if not_exists(_freeze):
+ raise RuntimeError(f"The `is_frozen` flag in `config` is invalid. Please provide a boolean. `is_frozen` is: {freeze}")
+ else:
+ _freeze = True
+ #print(f"[INFO]: `{class_to_str(type(model))}`. No valid `is_frozen` flag in `config`. Model is frozen by default.")
+ #--------------------------------
+ # instantiate model
+ model = instantiate_from_config(_config)
+ model = model.to(device)
+ if not silent: print(f"[INFO]: `{class_to_str(type(model))}` instantiated from given `config` on {device}.")
+
#--------------------------------
- if not exists(save_path):
- if "save_path" in config:
- save_path = config["save_path"]
+ # load pretrained weights
+
+ model.save_type = _config.pop("save_type", None)
+
+ if exists(model.save_type):
+ if not exists(save_path):
+ if "save_path" in _config:
+ save_path = model.check_save_type(_config["save_path"])
+
+
+ if exists(save_path):
+ state_dict = load_model_state_dict(model.check_save_type(save_path), device)
+
+ m, u = model.load_state_dict(state_dict, strict=False)
+
+ if len(m) + len(u) > 0 and verbose:
+ print(f"[WARNING]: missing keys: {m}")
+ print(f"[WARNING]: unexpected keys: {u}")
+
else:
- print("[INFO]: Found no key `save_path` path in config.")
-
- if exists(save_path):
- model.load_state_dict(torch.load(save_path, map_location=torch.device(device).type, weights_only=True), strict=True)
+ if not silent: print(f"[INFO]: `{class_to_str(type(model))}`. No `save_path` provided. Found no key `save_path` in `config`. No state dict loaded.")
+ else:
+ if not silent: print(f"[INFO]: `{class_to_str(type(model))}`. Found no key `save_type` in `config`. No state dict loaded.")
+
+ #--------------------------------
+ # freeze
+
+ if exists(_freeze):
+ model.freeze(_freeze)
+ if not silent: print(f"[INFO]: `{class_to_str(type(model))}`. Freeze model: {_freeze}")
else:
- print(f"[INFO]: `{class_to_str(type(model))}`. No save_path` provided. No state dict loaded.")
+ if not silent: print(f"[INFO]: `{class_to_str(type(model))}`. No valid `is_frozen` flag in `config`. Model is frozen by default.")
return model
@staticmethod
def from_config_file(config_path, device: torch.device, save_path: str=None):
config = load_config(config_path)
- return Config_Model.from_config(config, device, save_path)
+ return ConfigModel.from_config(config, device, save_path)
diff --git a/genQC/models/embedding/__init__.py b/genQC/models/embedding/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/genQC/models/embedding/base_embedder.py b/genQC/models/embedding/base_embedder.py
new file mode 100644
index 0000000..8e22ad7
--- /dev/null
+++ b/genQC/models/embedding/base_embedder.py
@@ -0,0 +1,31 @@
+"""Class for base embedder."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../src/models/embedding/base_embedder.ipynb.
+
+# %% auto 0
+__all__ = ['BaseEmbedder']
+
+# %% ../../../src/models/embedding/base_embedder.ipynb 2
+from ...imports import *
+from ..config_model import ConfigModel
+
+# %% ../../../src/models/embedding/base_embedder.ipynb 4
+class BaseEmbedder(ConfigModel, abc.ABC):
+ def __init__(self) -> None:
+ super().__init__()
+
+ # Note: While using DDP with huggingface-Accelerate we noticed
+ # the fixed weights didn't get synced if there is no parameter
+ # that requires a gradient. So we add a dummy to make sure
+ # all model instances/nodes have the same embedder!
+ self.dummy_parameter = torch.tensor(0.0)
+ self.dummy_parameter = nn.Parameter(self.dummy_parameter)
+
+ def forward(self, *args, **kwargs):
+ return self.embed(*args, **kwargs)
+
+ @abc.abstractmethod
+ def embed(self, x): pass
+
+ @abc.abstractmethod
+ def invert(self, x): pass
diff --git a/genQC/models/embedding/rotational_preset_embedder.py b/genQC/models/embedding/rotational_preset_embedder.py
new file mode 100644
index 0000000..daeb6ed
--- /dev/null
+++ b/genQC/models/embedding/rotational_preset_embedder.py
@@ -0,0 +1,700 @@
+"""Class for a rotational preset embedder."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../src/models/embedding/rotational_preset_embedder.ipynb.
+
+# %% auto 0
+__all__ = ['MultimodialEmbedder', 'MultimodialPresetEmbedderConfig', 'MultimodialPresetEmbedder',
+ 'RotationalMultimodialPresetEmbedder', 'RotationalMultimodialPresetEmbedderTiny']
+
+# %% ../../../src/models/embedding/rotational_preset_embedder.ipynb 2
+from ...imports import *
+from ...utils.math import gram_schmidt
+from .base_embedder import BaseEmbedder
+
+# %% ../../../src/models/embedding/rotational_preset_embedder.ipynb 4
+class MultimodialEmbedder(BaseEmbedder):
+
+ def __init__(self, zero_sum_space: bool) -> None:
+ super().__init__()
+
+ self.zero_sum_space = zero_sum_space
+
+ h_mean, h_std = torch.tensor(0.0), torch.tensor(1.0)
+ w_mean, w_std = torch.tensor(0.0), torch.tensor(1.0)
+
+ self.register_buffer('h_mean', h_mean)
+ self.register_buffer('h_std', h_std)
+
+ self.register_buffer('w_mean', w_mean)
+ self.register_buffer('w_std', w_std)
+
+ def set_scaling(self, h: torch.Tensor, w: torch.Tensor) -> None:
+ self.h_mean, self.h_std = torch.tensor(0.0), torch.tensor(1.0)
+ self.w_mean, self.w_std = torch.tensor(0.0), torch.tensor(1.0)
+
+ return #disbled; not needed for new emb initialization
+
+ x = self.embed(h, w)
+
+ if not self.channel_last:
+ x_h = x[:, :self.clr_dim]
+ x_w = x[:, self.clr_dim:]
+ else:
+ x_h = x[..., :self.clr_dim]
+ x_w = x[..., self.clr_dim:]
+
+ self.h_mean, self.h_std = x_h.mean(), x_h.std()
+ self.w_mean, self.w_std = x_w.mean(), x_w.std()
+
+ def scale_emb(self, x_emb: torch.Tensor) -> torch.Tensor:
+ # x_emb .. [b, ch, s, t]
+
+ # mean
+ if not self.zero_sum_space:
+ if not self.channel_last:
+ x_emb[:, :self.clr_dim] -= self.h_mean
+ x_emb[:, self.clr_dim:] -= self.w_mean
+ else:
+ x_emb[..., :self.clr_dim] -= self.h_mean
+ x_emb[..., self.clr_dim:] -= self.w_mean
+
+ # variance
+ if not self.channel_last:
+ x_emb[:, :self.clr_dim] /= self.h_std
+ x_emb[:, self.clr_dim:] /= self.w_std
+ else:
+ x_emb[..., :self.clr_dim] /= self.h_std
+ x_emb[..., self.clr_dim:] /= self.w_std
+
+ return x_emb
+
+ def invert_scale_emb(self, x_emb: torch.Tensor) -> torch.Tensor:
+ # x_emb .. [b, ch, s, t]
+
+ # variance
+ if not self.channel_last:
+ x_emb[:, :self.clr_dim] *= self.h_std
+ x_emb[:, self.clr_dim:] *= self.w_std
+ else:
+ x_emb[..., :self.clr_dim] *= self.h_std
+ x_emb[..., self.clr_dim:] *= self.w_std
+
+ # mean
+ if not self.zero_sum_space:
+ if not self.channel_last:
+ x_emb[:, :self.clr_dim] += self.h_mean
+ x_emb[:, self.clr_dim:] += self.w_mean
+ else:
+ x_emb[..., :self.clr_dim] += self.h_mean
+ x_emb[..., self.clr_dim:] += self.w_mean
+
+ return x_emb
+
+# %% ../../../src/models/embedding/rotational_preset_embedder.ipynb 6
+@dataclass
+class MultimodialPresetEmbedderConfig:
+ clr_dim: int
+ num_clrs: int
+ params_dim: int
+ num_params_per_clr: int
+ zero_sum_space: bool
+ explicit_node_type_embeddings: bool
+ channel_last: bool
+ parametrized_tokens: Optional[list[int]] = None
+ unique_class_values: Optional[list[int]] = None
+
+# %% ../../../src/models/embedding/rotational_preset_embedder.ipynb 7
+class MultimodialPresetEmbedder(MultimodialEmbedder):
+ """
+ Embedder class for multimodial discrete and continuous data, e.g. parametrized gates/actions.
+ Embeddings are fixed and not trained.
+ """
+
+ def __init__(self,
+ clr_dim: int,
+ num_clrs: int,
+ params_dim: int,
+ num_params_per_clr: int,
+ zero_sum_space: bool,
+ explicit_node_type_embeddings: bool = True,
+ channel_last: bool = True,
+ parametrized_tokens: Optional[list[int]] = None,
+ unique_class_values: Optional[list[int]] = None) -> None:
+ """
+ Note `explicit_node_type_embeddings` means we convert the `+-k` to all postive, but there are often unsused connection types. For instance, `1=H` the minus node is never used.
+
+ To improve this and reduce the `clr_dim`, we can provide `unique_values` which are the only tokens that actually appear.
+ """
+ super().__init__(zero_sum_space=zero_sum_space)
+
+
+ if exists(unique_class_values):
+ assert isinstance(unique_class_values, list)
+ self.unique_class_values_tensor = torch.tensor(unique_class_values)
+
+ explicit_node_type_embeddings = False
+
+ print(f"[INFO]: provided `unique_class_values` ({unique_class_values}), enforcing `num_clrs=len(unique_class_values)={len(unique_class_values)}`.")
+ num_clrs = len(unique_class_values)
+
+ self.explicit_node_type_embeddings = explicit_node_type_embeddings
+ self.channel_last = channel_last
+ self.parametrized_tokens = parametrized_tokens
+ self.unique_class_values = unique_class_values
+
+ if (num_params_per_clr*num_clrs) > params_dim and num_params_per_clr > 0:
+ print(f"[WARNING]: For `num_params_per_clr` larger 0, we need at least a `params_dim` (is {params_dim}) of"
+ f" `num_params_per_clr*num_clrs` (is {num_params_per_clr*num_clrs}),"
+ f" automatically setting `params_dim` to {num_params_per_clr*num_clrs} to inforce this!")
+
+ params_dim = num_params_per_clr*num_clrs
+
+ if self.zero_sum_space and ((num_params_per_clr*num_clrs) + 1) > params_dim and num_params_per_clr > 0:
+ print(f"[WARNING]: `params_dim` is set to the minimum `num_params_per_clr*num_clrs`={num_params_per_clr*num_clrs},"
+ f" but for `{zero_sum_space=}` we need one more dimension, automatically setting it to"
+ f" `num_params_per_clr*num_clrs+1` {num_params_per_clr*num_clrs+1}.")
+
+ params_dim = num_params_per_clr*num_clrs + 1
+
+ if self.zero_sum_space:
+ if self.explicit_node_type_embeddings and ((num_clrs*2 - 2) + 1) > clr_dim:
+ print(f"[WARNING]: `clr_dim` is set to {clr_dim} and `{explicit_node_type_embeddings=}`,"
+ f" but for `{zero_sum_space=}` we need one more dimension than the number of tokens `(num_clrs*2 - 2)` (is {(num_clrs*2 - 2)}),"
+ f" automatically setting it to `clr_dim=(num_clrs*2 - 2) + 1` {(num_clrs*2 - 2) + 1}.")
+
+ # has empty and padd tokens, these only have the plus branch (so -2)!
+ clr_dim = (num_clrs*2 - 2) + 1
+
+ elif (num_clrs + 1) > clr_dim:
+ print(f"[WARNING]: `clr_dim` is set to {clr_dim} and `{explicit_node_type_embeddings=}`,"
+ f" but for `{zero_sum_space=}` we need one more dimension than the number of tokens `num_clrs` (is {num_clrs}),"
+ f" automatically setting it to `clr_dim=num_clrs+1` {num_clrs+1}.")
+
+ clr_dim = num_clrs + 1
+
+ self.clr_dim = clr_dim
+ self.num_clrs = num_clrs
+ self.params_dim = params_dim
+ self.num_params_per_clr = num_params_per_clr
+
+ self._num_discrete_embeddings = self.num_clrs
+ self._num_param_embeddings = self.num_params_per_clr * self.num_clrs
+ self.embedding_dim = self.clr_dim + self.params_dim
+
+ if self.explicit_node_type_embeddings:
+ # use distinct embeddings for +-k and not just +-v
+ # has empty and padd tokens, these only have the plus branch (so -2)!
+ self._num_discrete_embeddings = self.num_clrs*2 - 2
+
+ self.num_embeddings = self._num_discrete_embeddings + self._num_param_embeddings
+ self.emb_clr = nn.Embedding(num_embeddings=self.num_embeddings, embedding_dim=self.embedding_dim)
+ print(f"[INFO]: Created `nn.Embedding` with a total of {self.num_embeddings} vectors in a {self.embedding_dim} dimensional space.")
+
+ self.params_config = MultimodialPresetEmbedderConfig(clr_dim=self.clr_dim,
+ num_clrs=self.num_clrs,
+ params_dim=self.params_dim,
+ num_params_per_clr=self.num_params_per_clr,
+ zero_sum_space=self.zero_sum_space,
+ explicit_node_type_embeddings=self.explicit_node_type_embeddings,
+ channel_last=self.channel_last,
+ parametrized_tokens=self.parametrized_tokens)
+
+ self._init_weights(zero_sum_space=self.zero_sum_space)
+
+ def _init_weights(self, zero_sum_space) -> None:
+ self.emb_clr.weight.requires_grad = False
+
+ _dtype = self.emb_clr.weight.dtype
+ self.emb_clr = self.emb_clr.to(torch.float64)
+
+ # keep spaces ortho with clr
+ self.emb_clr.weight.data.zero_()
+ nn.init.orthogonal_(self.emb_clr.weight.data[:self._num_discrete_embeddings, :self.clr_dim])
+ nn.init.orthogonal_(self.emb_clr.weight.data[self._num_discrete_embeddings:, self.clr_dim:])
+
+ if zero_sum_space:
+ assert self._num_discrete_embeddings < self.clr_dim, f"{self._num_discrete_embeddings} < {self.clr_dim}"
+ if self._num_param_embeddings > 0:
+ assert self._num_param_embeddings < self.params_dim, f"{self._num_param_embeddings} < {self.params_dim}"
+
+ # Convert to zero-sum space
+ self.emb_clr.weight.data[:self._num_discrete_embeddings, :self.clr_dim] -= torch.mean(self.emb_clr.weight.data[:self._num_discrete_embeddings, :self.clr_dim], dim=-1, keepdim=True)
+ if self._num_param_embeddings > 0:
+ self.emb_clr.weight.data[self._num_discrete_embeddings:, self.clr_dim:] -= torch.mean(self.emb_clr.weight.data[self._num_discrete_embeddings:, self.clr_dim:], dim=-1, keepdim=True)
+
+ # Orthonormalization that conserves zero-sum space
+ self.emb_clr.weight.data[:self._num_discrete_embeddings, :self.clr_dim] = gram_schmidt(self.emb_clr.weight.data[:self._num_discrete_embeddings, :self.clr_dim])
+ if self._num_param_embeddings > 0:
+ self.emb_clr.weight.data[self._num_discrete_embeddings:, self.clr_dim:] = gram_schmidt(self.emb_clr.weight.data[self._num_discrete_embeddings:, self.clr_dim:])
+
+ self.emb_clr.weight.data[:self._num_discrete_embeddings, :self.clr_dim] /= torch.std(self.emb_clr.weight.data[:self._num_discrete_embeddings, :self.clr_dim], dim=-1, keepdim=True, correction=0)
+ if self._num_param_embeddings > 0:
+ self.emb_clr.weight.data[self._num_discrete_embeddings:, self.clr_dim:] /= torch.std(self.emb_clr.weight.data[self._num_discrete_embeddings:, self.clr_dim:], dim=-1, keepdim=True, correction=0)
+
+ self.emb_clr = self.emb_clr.to(_dtype)
+
+ def print_emb_matrix(self) -> None:
+ print(self.emb_clr.weight.data)
+
+ #-----------------------------------------------
+
+ def tokens_to_unique_class_values(self, x: torch.Tensor) -> torch.Tensor:
+ if exists(self.unique_class_values):
+ self.unique_class_values_tensor = self.unique_class_values_tensor.to(x.device)
+ return torch.searchsorted(self.unique_class_values_tensor, x)
+ return x
+
+ def unique_class_values_to_tokens(self, x: torch.Tensor) -> torch.Tensor:
+ if exists(self.unique_class_values):
+ self.unique_class_values_tensor = self.unique_class_values_tensor.to(x.device)
+ return self.unique_class_values_tensor[x]
+ return x
+
+ #-----------------------------------------------
+
+ def embed_discrete(self, h: torch.Tensor) -> torch.Tensor:
+
+ if self.unique_class_values:
+ # tokens are already correct
+ tokens = h
+ x_emb = self.emb_clr(tokens)
+
+ elif self.explicit_node_type_embeddings:
+ # e.g. num_clrs=4: [-2, -1, zero, 1, 2, padd] to all positive [0, 1, 2 (zero), 3, 4, 5 (padd)]
+ tokens = h
+ x_emb = self.emb_clr(tokens + (self.num_clrs-2))
+
+ else:
+ sign = torch.sign(h + 0.1) #trick: add 0.1 so that the sign of 0 is +1, else the 0 token would be all 0s.
+ tokens = torch.abs(h)
+
+ x_emb = self.emb_clr(tokens)
+ x_emb = x_emb * sign.unsqueeze(-1) # [b, s, t, ch]
+
+ return x_emb, tokens
+
+
+ def embed(self, h: torch.Tensor, w: torch.Tensor) -> torch.Tensor:
+ """
+ sample from p(x0|h, w)
+ h discrete
+ w cont
+ """
+
+ x_emb, tokens = self.embed_discrete(h)
+
+ v_p = self.embed_continuous(w, tokens)
+ x_emb += v_p
+
+ if not self.channel_last:
+ # contiguous important for multi-node cluster
+ x_emb = torch.permute(x_emb, (0, 3, 1, 2)).contiguous() # to [b, ch, s, t]
+
+ return x_emb
+
+ #-----------------------------------------------
+
+ def get_discrete_sim(self, x: torch.Tensor) -> torch.Tensor:
+ #collaps clr to gate ... use cos sim
+
+ clrs = self.emb_clr.weight.detach()[:self._num_discrete_embeddings] # is [clr_num, clr_dim]
+
+ model_device = clrs.device
+ x = x.to(model_device)
+
+ # to shape [b*space*time, clr_dim]
+ x_flat = x.reshape(-1, x.shape[-1])
+
+ #normalize for cos sim
+ norm_clr = F.normalize( clrs[:, :self.clr_dim], dim=1) #clrs / torch.linalg.vector_norm( clrs, dim=1, keepdim=True) #torch.linalg.vector_norm( clrs[:, :self.clr_dim], dim=1, keepdim=True)
+ norm_x_flat = F.normalize(x_flat[:, :self.clr_dim], dim=1) #x_flat / torch.linalg.vector_norm(x_flat, dim=1, keepdim=True) #torch.linalg.vector_norm(x_flat[:, :self.clr_dim], dim=1, keepdim=True)
+
+ #matmul out is [clr_num, b*space*time] = [clr_num, clr_dim] x [b*space*time, clr_dim].T
+ sim = torch.matmul(norm_clr, norm_x_flat.T)
+
+ return sim
+
+ @torch.inference_mode()
+ def invert_discrete(self, x: torch.Tensor, return_sim: bool = False, finite_temperature: bool = False) -> torch.Tensor:
+ #collaps clr to gate ... use cos sim
+
+ input_device = x.device
+
+ if not self.channel_last:
+ x = x.permute(0, 2, 3, 1)
+
+ #sim out is [clr_num, b*space*time]
+ sim = self.get_discrete_sim(x)
+
+ if self.explicit_node_type_embeddings or self.unique_class_values:
+ #get highest similarity
+ if finite_temperature:
+ _cat = torch.distributions.categorical.Categorical(logits=sim.transpose(-1, -2))
+ scores_flat = _cat.sample()
+ else:
+ scores_flat = torch.argmax(sim, dim=0) #reduce the clr_num dim
+
+ if self.explicit_node_type_embeddings:
+ scores_flat = scores_flat - (self.num_clrs-2)
+
+ else:
+ #get highest abs(similarity) and sign of it
+ abs_sim = sim.abs()
+
+ if finite_temperature:
+ _cat = torch.distributions.categorical.Categorical(logits=abs_sim.transpose(-1, -2))
+ max_idx = _cat.sample()
+ else:
+ max_idx = torch.argmax(abs_sim, dim=0) #reduce the clr_num dim
+
+ sign = torch.sign(sim[max_idx, torch.arange(x_flat.shape[0])])
+ scores_flat = max_idx * sign
+
+ # back to [b, space, time]
+ scores = scores_flat.reshape(x.shape[0], x.shape[1], x.shape[2]).to(torch.int64)
+ scores = scores.to(input_device)
+
+ if return_sim:
+ return scores, sim
+ return scores
+
+ @torch.inference_mode()
+ def invert(self, x: torch.Tensor, reduce_spatial: bool = True) -> torch.Tensor:
+ """sample from p(h, w|x0)"""
+
+ pred_tokens = self.invert_discrete(x)
+ pred_params = self.invert_continuous(x, pred_tokens, reduce_spatial=reduce_spatial)
+
+ pred_tokens = self.unique_class_values_to_tokens(pred_tokens)
+
+ return pred_tokens, pred_params
+
+ #-----------------------------------------------
+
+ def _prepare_params(self, tokens: torch.Tensor, w: torch.Tensor) -> torch.Tensor:
+ tokens = tokens.abs()
+
+ # w ... [b, nP, s or 1, t]
+
+ if self.parametrized_tokens:
+ # Force all non parameterized embeddings to all zero or random lambdas !
+ pmask = self.get_parametrized_mask(tokens).unsqueeze(1) # [b, 1, s, t]
+ rnd_w = torch.zeros((w.shape[0], w.shape[1], pmask.shape[2], w.shape[3]), device=w.device)
+ w_m = torch.where(pmask, w, rnd_w)
+
+ else:
+ # this does not include padding tokens!
+ pmask = (tokens > 0).unsqueeze(1)
+ w_m = torch.where(pmask, w, 0.0) # ... [b, nP, s, t]
+
+ return w_m
+
+ def _reduce_params_spatial(self, tokens: torch.Tensor, params: torch.Tensor) -> torch.Tensor:
+ tokens = tokens.abs()
+
+ if self.parametrized_tokens:
+ #check if not param gate
+ mask = self.get_parametrized_mask(tokens).unsqueeze(1).float() # ... [b, 1, s, t]
+ else:
+ #check if not empty token
+ mask = (tokens > 0).unsqueeze(1).float() # ... [b, 1, s, t]
+
+ # to catch all zero tokens at t, compute how many we have per timestep
+ red_mask = mask.sum(-2) # ... [b, 1, t]
+ red_mask = torch.where(red_mask > 0.0, red_mask, 1.0)
+
+ params = (params*mask).sum(-2) / red_mask # ... [b, nP, s, t] to [b, nP, t] average over s, ignore masked positions
+ return params
+
+ def get_parametrized_mask(self, tokens: torch.Tensor) -> torch.Tensor:
+
+ parametrized_tokens = torch.tensor(self.parametrized_tokens, device=tokens.device)
+
+ if exists(self.unique_class_values):
+ parametrized_tokens = self.tokens_to_unique_class_values(parametrized_tokens)
+
+ pmask = torch.isin(tokens.abs(), parametrized_tokens)
+
+ return pmask
+
+# %% ../../../src/models/embedding/rotational_preset_embedder.ipynb 9
+class RotationalMultimodialPresetEmbedder(MultimodialPresetEmbedder):
+
+ def __init__(self,
+ clr_dim: int,
+ num_clrs: int,
+ params_dim: int,
+ num_params_per_clr: int,
+ zero_sum_space: bool,
+ explicit_node_type_embeddings: bool = True,
+ channel_last: bool = True,
+ parametrized_tokens: Optional[list[int]] = None,
+ unique_class_values: Optional[list[int]] = None
+ ) -> None:
+
+ self.channel_last = channel_last
+ self.parametrized_tokens = parametrized_tokens
+
+ if (2*num_params_per_clr*num_clrs) > params_dim and num_params_per_clr > 0:
+ print(f"[WARNING]: We need at least a `params_dim` (is {params_dim}) of `2*num_params_per_clr*num_clrs` (is {2*num_params_per_clr*num_clrs}),"
+ f" automatically setting `params_dim` to {2*num_params_per_clr*num_clrs} to inforce this!")
+
+ params_dim = 2*num_params_per_clr*num_clrs
+
+ if zero_sum_space and (2*num_params_per_clr*num_clrs+1) > params_dim and num_params_per_clr > 0:
+ print(f"[WARNING]: `params_dim` is set to the minimum `2*num_params_per_clr*num_clrs`={2*num_params_per_clr*num_clrs},"
+ f" but for `{zero_sum_space=}` we need one more dimension, automatically setting it to"
+ f" `2*num_params_per_clr*num_clrs+1` {2*num_params_per_clr*num_clrs+1}.")
+
+ params_dim = 2*num_params_per_clr*num_clrs + 1
+
+ super().__init__(clr_dim=clr_dim,
+ num_clrs=num_clrs,
+ params_dim=params_dim,
+ num_params_per_clr=2*num_params_per_clr, # pass factor 2 to create more embeddings for cos-sin encoding
+ zero_sum_space=zero_sum_space,
+ explicit_node_type_embeddings=explicit_node_type_embeddings,
+ channel_last=channel_last,
+ parametrized_tokens=parametrized_tokens,
+ unique_class_values=unique_class_values)
+
+ self.num_params_per_clr = num_params_per_clr # remove the factor 2
+ self._num_param_embeddings = self.num_params_per_clr * self.num_clrs
+ self.nP = num_params_per_clr
+
+ self.params_config = MultimodialPresetEmbedderConfig(clr_dim=self.clr_dim,
+ num_clrs=self.num_clrs,
+ params_dim=self.params_dim,
+ num_params_per_clr=self.num_params_per_clr,
+ zero_sum_space=self.zero_sum_space,
+ explicit_node_type_embeddings=self.explicit_node_type_embeddings,
+ channel_last=self.channel_last,
+ parametrized_tokens=self.parametrized_tokens,
+ unique_class_values=self.unique_class_values)
+
+
+ def embed_continuous(self, w: torch.Tensor, tokens: torch.Tensor) -> torch.Tensor:
+ # take care that v_empty stays that! not apply params to all bits only to a [s,t] pos
+ # params ... [b, nP, t]
+ # w ... qc=[b, nP, t] mbqc=[b, nP, s, t]
+
+ tokens = tokens.abs()
+
+ if w.dim() == 3:
+ w = w.unsqueeze(2) # to [b, nP, 1, t]
+
+
+ w_m = self._prepare_params(tokens, w)
+
+ w_m = w_m.unsqueeze(-1) # ... [b, nP, s, t, 1]
+ w_m = w_m * torch.pi # [-1, 1] to [-pi, pi]
+
+ # first pick starting points of indices
+ # then add a numerator for all the number of paramters
+ # then add a numerator for cos-sin vectors
+
+ #Note: .view(-1, 1, 1) introduces some numeric variances in 1e-07 range, but should be faster!
+ indices = self._num_discrete_embeddings + tokens * self.nP * 2 # ... [b, s, t]
+ indices = indices.unsqueeze(1) + torch.arange(self.nP, device=indices.device).view(-1, 1, 1) * 2 # ... [b, nP, s, t]
+ indices = indices.unsqueeze(1) + torch.arange(2, device=indices.device).view(-1, 1, 1, 1) # ... [b, 2, nP, s, t]
+ p_clrs = self.emb_clr(indices).contiguous() # ... [b, 2, nP, s, t, ch]
+
+ v_p = torch.cos(w_m)*p_clrs[:, 0] + torch.sin(w_m)*p_clrs[:, 1] # ... [b, nP, s, t, ch]
+ v_p = torch.sum(v_p, dim=1) # ... [b, s, t, ch]
+
+ return v_p
+
+ @torch.inference_mode()
+ def invert_continuous(self, x: torch.Tensor, tokens: torch.Tensor, reduce_spatial: bool = True) -> torch.Tensor:
+ """reduce_spatial=True for circuits, False for mbqc"""
+
+ model_device = self.emb_clr.weight.device
+ input_device = x.device
+
+ if not self.channel_last:
+ x = x.permute(0, 2, 3, 1) # to [b, s, t, ch]
+ x = x.unsqueeze(1).unsqueeze(1) # to [b, 1, 1, s, t, ch]
+
+ x = x.to(model_device)
+ tokens = tokens.to(model_device).abs()
+
+ #-----
+ # params should [b, nP, max_gates]
+ # x ... [b, ch, s, t]
+ # tokens ... [b, , s, t]
+
+ #Note: .view(-1, 1, 1) introduces some numeric variances in 1e-07 range, but should be faster!
+ indices = self._num_discrete_embeddings + tokens * self.nP * 2 # ... [b, s, t]
+ indices = indices.unsqueeze(1) + torch.arange(self.nP, device=indices.device).view(-1, 1, 1) * 2 # ... [b, nP, s, t]
+ indices = indices.unsqueeze(1) + torch.arange(2, device=indices.device).view(-1, 1, 1, 1) # ... [b, 2, nP, s, t]
+ p_clrs = self.emb_clr(indices).contiguous() # ... [b, 2, nP, s, t, ch]
+
+ overlaps = (x * p_clrs).sum(-1) # ... [b, 2, nP, s, t]
+ params = torch.arctan2(overlaps[:, 1], overlaps[:, 0]) # ... [b, nP, s, t]
+ params = params / torch.pi # [-pi, pi] to [-1, 1]
+
+ # now reduce spatial s, average over non empty token s
+ if reduce_spatial:
+ params = self._reduce_params_spatial(tokens, params)
+
+ return params.to(input_device)
+
+# %% ../../../src/models/embedding/rotational_preset_embedder.ipynb 12
+class RotationalMultimodialPresetEmbedderTiny(MultimodialPresetEmbedder):
+ """Mostly the same as `RotationalMultimodialPresetEmbedder`, but the param embedding is not depending on the tokens."""
+
+ def __init__(self,
+ clr_dim: int,
+ num_clrs: int,
+ params_dim: int,
+ num_params_per_clr: int,
+ zero_sum_space: bool,
+ explicit_node_type_embeddings: bool = True,
+ channel_last: bool = True,
+ parametrized_tokens: Optional[list[int]] = None,
+ unique_class_values: Optional[list[int]] = None
+ ) -> None:
+ super(MultimodialPresetEmbedder, self).__init__(zero_sum_space=zero_sum_space) # call grandparent class
+
+ if exists(unique_class_values):
+ assert isinstance(unique_class_values, list)
+ self.unique_class_values_tensor = torch.tensor(unique_class_values)
+
+ explicit_node_type_embeddings = False
+
+ print(f"[INFO]: provided `unique_class_values` ({unique_class_values}), enforcing `num_clrs=len(unique_class_values)={len(unique_class_values)}`.")
+ num_clrs = len(unique_class_values)
+
+ self.zero_sum_space = zero_sum_space
+ self.explicit_node_type_embeddings = explicit_node_type_embeddings
+ self.channel_last = channel_last
+ self.parametrized_tokens = parametrized_tokens
+ self.unique_class_values = unique_class_values
+ # assert exists(parametrized_tokens)
+
+ if (2*num_params_per_clr) > params_dim and num_params_per_clr > 0:
+ print(f"[WARNING]: We need at least a `params_dim` (is {params_dim}) of `2*num_params_per_clr` (is {2*num_params_per_clr}),"
+ f" automatically setting `params_dim` to {2*num_params_per_clr} to inforce this!")
+
+ params_dim = 2*num_params_per_clr
+
+ if self.zero_sum_space and (2*num_params_per_clr+1) > params_dim and num_params_per_clr > 0:
+ print(f"[WARNING]: `params_dim` is set to the minimum `2*num_params_per_clr`={2*num_params_per_clr},"
+ f" but for `{zero_sum_space=}` we need one more dimension, automatically setting it to"
+ f" `2*num_params_per_clr+1` {2*num_params_per_clr+1}.")
+
+ params_dim = 2*num_params_per_clr + 1
+
+ if self.zero_sum_space:
+ if self.explicit_node_type_embeddings and ((num_clrs*2 - 2) + 1) > clr_dim:
+ print(f"[WARNING]: `clr_dim` is set to {clr_dim} and `{explicit_node_type_embeddings=}`,"
+ f" but for `{zero_sum_space=}` we need one more dimension than the number of tokens `(num_clrs*2 - 2)` (is {(num_clrs*2 - 2)}),"
+ f" automatically setting it to `clr_dim=(num_clrs*2 - 2) + 1` {(num_clrs*2 - 2) + 1}.")
+
+ # has empty and padd tokens, these only have the plus branch (so -2)!
+ clr_dim = (num_clrs*2 - 2) + 1
+
+ elif (num_clrs + 1) > clr_dim:
+ print(f"[WARNING]: `clr_dim` is set to {clr_dim} and `{explicit_node_type_embeddings=}`,"
+ f" but for `{zero_sum_space=}` we need one more dimension than the number of tokens `num_clrs` (is {num_clrs}),"
+ f" automatically setting it to `clr_dim=num_clrs+1` {num_clrs+1}.")
+
+ clr_dim = num_clrs + 1
+
+ self.clr_dim = clr_dim
+ self.num_clrs = num_clrs
+ self.params_dim = params_dim
+ self.num_params_per_clr = num_params_per_clr
+ self.nP = num_params_per_clr
+
+ self._num_discrete_embeddings = self.num_clrs
+ self._num_param_embeddings = self.num_params_per_clr * 2
+ self.embedding_dim = self.clr_dim + self.params_dim
+
+ if self.explicit_node_type_embeddings:
+ # use distinct embeddings for +-k and not just +-v
+ # has empty and padd tokens, these only have the plus branch (so -2)!
+ self._num_discrete_embeddings = self.num_clrs*2 - 2
+
+ self.num_embeddings = self._num_discrete_embeddings + self._num_param_embeddings
+ self.emb_clr = nn.Embedding(num_embeddings=self.num_embeddings, embedding_dim=self.embedding_dim)
+ print(f"[INFO]: Created `nn.Embedding` with a total of {self.num_embeddings} vectors in a {self.embedding_dim} dimensional space.")
+
+ self.params_config = MultimodialPresetEmbedderConfig(clr_dim=self.clr_dim,
+ num_clrs=self.num_clrs,
+ params_dim=self.params_dim,
+ num_params_per_clr=self.num_params_per_clr,
+ zero_sum_space=self.zero_sum_space,
+ explicit_node_type_embeddings=self.explicit_node_type_embeddings,
+ channel_last=self.channel_last,
+ parametrized_tokens=self.parametrized_tokens,
+ unique_class_values=self.unique_class_values)
+
+ self._init_weights(zero_sum_space=self.zero_sum_space)
+
+ def embed_continuous(self, w: torch.Tensor, tokens: torch.Tensor) -> torch.Tensor:
+ # take care that v_empty stays that! not apply params to all bits only to a [s,t] pos
+ # params ... [b, nP, t]
+ # w ... qc=[b, nP, t] mbqc=[b, nP, s, t]
+
+ tokens = tokens.abs()
+
+ if w.dim() == 3:
+ w = w.unsqueeze(2) # to [b, nP, 1, t]
+
+ w_m = self._prepare_params(tokens, w)
+
+ w_m = w_m.unsqueeze(-1) # ... [b, nP, s, t, 1]
+ w_m = w_m * torch.pi # [-1, 1] to [-pi, pi]
+
+ # first pick starting points of indices
+ # then add a numerator for all the number of paramters
+ # then add a numerator for cos-sin vectors
+
+ #Note: .view(-1, 1, 1) introduces some numeric variances in 1e-07 range, but should be faster!
+ indices = torch.full_like(tokens, self._num_discrete_embeddings) #+ 0 * tokens * self.nP * 2 # ... [b, s, t]
+ indices = indices.unsqueeze(1) + torch.arange(self.nP, device=indices.device).view(-1, 1, 1) * 2 # ... [b, nP, s, t]
+ indices = indices.unsqueeze(1) + torch.arange(2, device=indices.device).view(-1, 1, 1, 1) # ... [b, 2, nP, s, t]
+ p_clrs = self.emb_clr(indices).contiguous() # ... [b, 2, nP, s, t, ch]
+
+ # This cos-sin combination conserves mean and variance of the embeddings
+ v_p = torch.cos(w_m)*p_clrs[:, 0] + torch.sin(w_m)*p_clrs[:, 1] # ... [b, nP, s, t, ch]
+ v_p = torch.sum(v_p, dim=1) # ... [b, s, t, ch]
+
+ return v_p
+
+ @torch.inference_mode()
+ def invert_continuous(self, x: torch.Tensor, tokens: torch.Tensor, reduce_spatial: bool = True) -> torch.Tensor:
+ """reduce_spatial=True for circuits, False for mbqc"""
+
+ model_device = self.emb_clr.weight.device
+ input_device = x.device
+
+ if not self.channel_last:
+ x = x.permute(0, 2, 3, 1) # to [b, s, t, ch]
+ x = x.unsqueeze(1).unsqueeze(1) # to [b, 1, 1, s, t, ch]
+
+ x = x.to(model_device)
+ tokens = tokens.to(model_device).abs()
+
+ #-----
+ # params should [b, nP, max_gates]
+ # x ... [b, ch, s, t]
+ # tokens ... [b, , s, t]
+
+ #Note: .view(-1, 1, 1) introduces some numeric variances in 1e-07 range, but should be faster!
+ indices = torch.full_like(tokens, self._num_discrete_embeddings) #+ 0 * tokens * self.nP * 2 # ... [b, s, t]
+ indices = indices.unsqueeze(1) + torch.arange(self.nP, device=indices.device).view(-1, 1, 1) * 2 # ... [b, nP, s, t]
+ indices = indices.unsqueeze(1) + torch.arange(2, device=indices.device).view(-1, 1, 1, 1) # ... [b, 2, nP, s, t]
+ p_clrs = self.emb_clr(indices).contiguous() # ... [b, 2, nP, s, t, ch]
+
+ # Note we dont need to normalize x as this norm cancels in the fraction of arctan2(y/x)
+ overlaps = (x * p_clrs).sum(-1) # ... [b, 2, nP, s, t]
+ params = torch.arctan2(overlaps[:, 1], overlaps[:, 0]) # ... [b, nP, s, t]
+ params = params / torch.pi # [-pi, pi] to [-1, 1]
+
+ # now reduce spatial s, average over non empty token s
+ if reduce_spatial:
+ params = self._reduce_params_spatial(tokens, params)
+
+ return params.to(input_device)
diff --git a/genQC/models/frozen_open_clip.py b/genQC/models/frozen_open_clip.py
index 609d75c..81cc6e4 100644
--- a/genQC/models/frozen_open_clip.py
+++ b/genQC/models/frozen_open_clip.py
@@ -1,25 +1,30 @@
+"""Interface to the [OpenCLIP](https://github.com/mlfoundations/open_clip) library."""
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/models/frozen_open_clip.ipynb.
# %% auto 0
-__all__ = ['FrozenOpenCLIPEmbedder_config', 'FrozenOpenCLIPEmbedder', 'CachedFrozenOpenCLIPEmbedder']
+__all__ = ['FrozenOpenCLIPEmbedderConfig', 'FrozenOpenCLIPEmbedder', 'CachedFrozenOpenCLIPEmbedderConfig',
+ 'CachedFrozenOpenCLIPEmbedder']
# %% ../../src/models/frozen_open_clip.ipynb 2
from ..imports import *
-from .config_model import Config_Model
+from .config_model import ConfigModel
+from ..utils.async_fn import run_parallel_jobs
+from ..utils.misc_utils import infer_torch_device
import open_clip
-# %% ../../src/models/frozen_open_clip.ipynb 4
+# %% ../../src/models/frozen_open_clip.ipynb 5
@dataclass
-class FrozenOpenCLIPEmbedder_config:
+class FrozenOpenCLIPEmbedderConfig:
arch: str
version: str
- device: str
+ #device: str
max_length: int
freeze: bool
layer: str
-# %% ../../src/models/frozen_open_clip.ipynb 5
-class FrozenOpenCLIPEmbedder(Config_Model):
+# %% ../../src/models/frozen_open_clip.ipynb 6
+class FrozenOpenCLIPEmbedder(ConfigModel):
"""Loads and freezes the [OpenCLIP](https://github.com/mlfoundations/open_clip) transformer encoder for text prompts."""
LAYERS = [
@@ -28,18 +33,23 @@ class FrozenOpenCLIPEmbedder(Config_Model):
"penultimate"
]
- def __init__(self, arch="ViT-H-14", version="laion2b_s32b_b79k", device="cpu", max_length=77, freeze=True, layer="penultimate"):
- super().__init__()
+ njobs = 1
+
+ def __init__(self, arch="ViT-B-32", version="datacomp_xl_s13b_b90k", max_length=77, freeze=True, layer="penultimate", **kwargs):
+ super().__init__()
+
assert layer in self.LAYERS
- self.params_config = FrozenOpenCLIPEmbedder_config(arch, version, device, max_length, freeze, layer)
+ self.params_config = FrozenOpenCLIPEmbedderConfig(arch, version, max_length, freeze, layer)
- model, _, _ = open_clip.create_model_and_transforms(arch, device=torch.device(device), pretrained=version)
- del model.visual
+ model, _, _ = open_clip.create_model_and_transforms(arch, device="cpu", pretrained=version)
+ self.device = "cpu"
+ del model.visual
self.model = model
- self.to(device)
-
+ # self.to(device)
+
self.tokenizer = open_clip.get_tokenizer(arch)
+ assert torch.numel(self.tokenizer("test"))
assert max_length <= 77 # max set by the clip
self.max_length = max_length
@@ -54,33 +64,37 @@ def __init__(self, arch="ViT-H-14", version="laion2b_s32b_b79k", device="cpu", m
#create empty token, can also be, e.g., A nice picture
self.empty_token = self.tokenize_and_push_to_device("")
- def freeze(self):
- self.model = self.model.eval()
-
- for param in self.parameters():
- param.requires_grad = False
-
+ def freeze(self, freeze: bool = True):
+ super().freeze(freeze=freeze)
+
for param in self.model.parameters():
- param.requires_grad = False
+ param.requires_grad = not freeze
def to(self, device):
self.model = self.model.to(device)
self.device = device
return self
- @torch.no_grad()
+ @torch.inference_mode()
def tokenize_and_push_to_device(self, text, to_device=True):
- # tokens = open_clip.tokenize(text)
- tokens = self.tokenizer(text)
+ if self.njobs > 1:
+
+ tokens_list = run_parallel_jobs(self.tokenizer, np.array_split(text, self.njobs), self.njobs)
+ tokens = torch.cat(tokens_list, dim=0)
+
+ else:
+ # tokens = open_clip.tokenize(text)
+ tokens = self.tokenizer(text)
+
if to_device:
tokens = tokens.to(self.device)
return tokens
- @torch.no_grad()
+ @torch.inference_mode()
def forward(self, c, **kwargs):
return self.encode_with_transformer(c)
- @torch.no_grad()
+ @torch.inference_mode()
def encode_with_transformer(self, text):
cast_dtype = self.model.transformer.get_cast_dtype()
@@ -99,13 +113,17 @@ def encode_with_transformer(self, text):
return x
- @torch.no_grad()
+ @torch.inference_mode()
def text_transformer_forward(self, x: torch.Tensor, attn_mask=None):
for i, r in enumerate(self.model.transformer.resblocks):
if i == len(self.model.transformer.resblocks) - self.layer_idx:
break
-
- x = r(x, attn_mask=attn_mask)
+ #if self.model.transformer.grad_checkpointing and not torch.jit.is_scripting():
+ #x = checkpoint(r, x, attn_mask)
+ #else:
+
+ x = r(x, attn_mask=attn_mask)
+
return x
#--------------------------------------------------------------
@@ -119,14 +137,30 @@ def store_model(self, config_path: str, save_path: str=None, without_metadata=Fa
@staticmethod
def from_config(config, device: torch.device, save_path: str=None):
config["save_path"] = None
- return Config_Model.from_config(config, device, save_path=None)
+ return ConfigModel.from_config(config, device, save_path=None)
# %% ../../src/models/frozen_open_clip.ipynb 17
+@dataclass
+class CachedFrozenOpenCLIPEmbedderConfig(FrozenOpenCLIPEmbedderConfig):
+ enable_cache_token_limit: bool
+
+# %% ../../src/models/frozen_open_clip.ipynb 18
class CachedFrozenOpenCLIPEmbedder(FrozenOpenCLIPEmbedder):
"""Adds caching support to `FrozenOpenCLIPEmbedder`."""
+
+ def __init__(self, arch="ViT-B-32", version="datacomp_xl_s13b_b90k", max_length=77, freeze=True, layer="penultimate", enable_cache_token_limit: bool = True, **kwargs):
+ super().__init__(arch=arch, version=version, max_length=max_length, freeze=freeze, layer=layer, **kwargs)
+ self.enable_cache_token_limit = enable_cache_token_limit
+
+ self.params_config = CachedFrozenOpenCLIPEmbedderConfig(arch, version, max_length, freeze, layer, enable_cache_token_limit)
+
+ def get_token_count(self, tokens, padding_token=0):
+ # tokens .. [b, seq]
+ collabsed_tokens = (tokens != padding_token).to(torch.int32)
+ return torch.count_nonzero(collabsed_tokens, dim=-1) # [b]
- @torch.no_grad()
- def generate_cache(self, str_list: list=None, tokens=None, cached_empty_token_index=0, b_size=2048, y_on_cpu=False):
+ @torch.inference_mode()
+ def generate_cache(self, str_list: list=None, tokens=None, cached_empty_token_index=None, b_size=2048, y_on_cpu=False):
self.cached_empty_token_index = cached_empty_token_index
if exists(str_list): self.cached_tokens = self.tokenize_and_push_to_device(str_list)
elif exists(tokens): self.cached_tokens = tokens
@@ -136,6 +170,14 @@ def generate_cache(self, str_list: list=None, tokens=None, cached_empty_token_in
# cached_tokens [n, 77] ... int
# cached_embeddings [n, 77, 512] ... float
+ if self.enable_cache_token_limit:
+ self.max_length = self.get_token_count(self.cached_tokens).max().item()
+ self.params_config.max_length = self.max_length
+ self.params_config.enable_cache_token_limit = self.enable_cache_token_limit
+ print(f"[INFO]: - `generate_cache` infered a TOKEN limit of {self.max_length}")
+
+ #self.cached_tokens = self.cached_tokens[:, :self.max_length]
+
n = self.cached_tokens.shape[0]
n_chunks = int(np.ceil(n / b_size))
@@ -145,7 +187,7 @@ def generate_cache(self, str_list: list=None, tokens=None, cached_empty_token_in
last_ind = 0
for i, cached_tokens in tqdm(enumerate(self.cached_tokens.chunk(n_chunks)), total=n_chunks):
- x = super().forward(cached_tokens.to(self.device))
+ x = super().forward(cached_tokens.to(self.device)) # ... [b, seq, ch]
if i == 0:
mem = n * x.shape[1] * x.shape[2] * x.element_size() * 1e-9
@@ -155,11 +197,14 @@ def generate_cache(self, str_list: list=None, tokens=None, cached_empty_token_in
self.cached_embeddings[last_ind:last_ind+x.shape[0]] = x.to(self.cached_embeddings.device)
last_ind += x.shape[0]
-
+
+ if self.enable_cache_token_limit:
+ self.cached_embeddings = self.cached_embeddings[:, :self.max_length]
+
if not y_on_cpu:
- self.cached_embeddings = self.cached_embeddings.to(in_device)
+ self.cached_embeddings = self.cached_embeddings.to(in_device)
- @torch.no_grad()
+ @torch.inference_mode()
def look_up_cos_sim_cached_index(self, str_list: list=None, tokens=None):
if exists(str_list): tokens = self.tokenize_and_push_to_device(str_list)
else: raise RuntimeError("please provide str_list or tokens")
@@ -180,10 +225,15 @@ def look_up_cos_sim_cached_index(self, str_list: list=None, tokens=None):
return max_idx
- @torch.no_grad()
+ # @torch.inference_mode()
def forward(self, c, **kwargs):
in_device = c.device
- if c.dim() == 1: return self.cached_embeddings[c.to(self.cached_embeddings.device)].to(in_device) #list of ints
- elif c.dim() == 2: return super().forward(c, **kwargs) #tokenized input
+ if c.dim() == 1: c_emb = self.cached_embeddings[c.to(self.cached_embeddings.device)].to(in_device) #list of ints
+ elif c.dim() == 2: c_emb = super().forward(c.to(self.device)) #tokenized input
else: raise NotImplementedError("")
+
+ if self.enable_cache_token_limit:
+ c_emb = c_emb[:, :self.max_length]
+
+ return c_emb
diff --git a/genQC/models/layers.py b/genQC/models/layers.py
index 2ececf6..5746af7 100644
--- a/genQC/models/layers.py
+++ b/genQC/models/layers.py
@@ -1,14 +1,16 @@
+"""Common model layers."""
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/models/layers.ipynb.
# %% auto 0
-__all__ = ['DownBlock2D', 'UpBlock2D', 'ResDownBlock2D', 'ResUpBlock2D', 'ResBlock2D', 'ResBlock2D_Conditional', 'FeedForward',
+__all__ = ['DownBlock2D', 'UpBlock2D', 'ResDownBlock2D', 'ResUpBlock2D', 'ResBlock2D', 'ResBlock2DConditional', 'FeedForward',
'PositionalEncoding', 'TimeEmbedding', 'PositionalEncodingTransposed', 'PositionalEncoding2D',
'PositionalEncoding2DSpaceOnly']
-# %% ../../src/models/layers.ipynb 3
+# %% ../../src/models/layers.ipynb 2
from ..imports import *
-# %% ../../src/models/layers.ipynb 5
+# %% ../../src/models/layers.ipynb 4
class DownBlock2D(nn.Module):
"""A 2d down scale block."""
def __init__(self, in_ch, out_ch, kernel_size=2, stride=2, padding=0, use_conv=True):
@@ -29,7 +31,7 @@ def forward(self, x):
x = self.convId(x)
return x
-# %% ../../src/models/layers.ipynb 6
+# %% ../../src/models/layers.ipynb 5
class UpBlock2D(nn.Module):
"""A 2d up scale block."""
def __init__(self, in_ch, out_ch, kernel_size=2, stride=2, padding=0, use_conv=True):
@@ -37,7 +39,10 @@ def __init__(self, in_ch, out_ch, kernel_size=2, stride=2, padding=0, use_conv=T
self.use_conv = use_conv
self.up_sample = nn.Upsample(scale_factor=kernel_size)
if self.use_conv:
- self.conv1 = nn.Conv2d(in_ch, out_ch, kernel_size=(1,3), stride=1, padding="same")
+ if kernel_size==(1,2): kernel_size_conv = (1,3)
+ else: kernel_size_conv = 3
+
+ self.conv1 = nn.Conv2d(in_ch, out_ch, kernel_size=kernel_size_conv, stride=1, padding="same")
else:
self.convId = nn.Conv2d(in_ch, out_ch, kernel_size=1, stride=1, padding="same") if in_ch!=out_ch else nn.Identity()
@@ -49,7 +54,7 @@ def forward(self, x):
x = self.convId(x)
return x
-# %% ../../src/models/layers.ipynb 7
+# %% ../../src/models/layers.ipynb 6
class ResDownBlock2D(nn.Module):
"""A 2d residual down scale block."""
def __init__(self, in_ch, out_ch, kernel_size=2, stride=2, padding=0):
@@ -69,7 +74,7 @@ def forward(self, x):
r2 = self.down(r2)
return self.act(r1 + r2)
-# %% ../../src/models/layers.ipynb 8
+# %% ../../src/models/layers.ipynb 7
class ResUpBlock2D(nn.Module):
"""A 2d residual up scale block."""
def __init__(self, in_ch, out_ch, kernel_size=2, stride=2, padding=0):
@@ -89,17 +94,17 @@ def forward(self, x):
r2 = self.up(r2)
return self.act(r1 + r2)
-# %% ../../src/models/layers.ipynb 10
+# %% ../../src/models/layers.ipynb 9
class ResBlock2D(nn.Module):
"""A 2d residual block."""
- def __init__(self, in_ch, out_ch, kernel_size, skip=True):
+ def __init__(self, in_ch, out_ch, kernel_size, skip=True, num_groups=32):
super().__init__()
self.act = nn.SiLU()
self.conv1 = nn.Conv2d( in_ch, out_ch, kernel_size=kernel_size, stride=1, padding ="same")
self.conv2 = nn.Conv2d(out_ch, out_ch, kernel_size=kernel_size, stride=1, padding ="same")
- self.norm1 = torch.nn.GroupNorm(num_groups=32, num_channels=in_ch) #, eps=1e-6, affine=True)
- self.norm2 = torch.nn.GroupNorm(num_groups=32, num_channels=out_ch) #, eps=1e-6, affine=True)
+ self.norm1 = torch.nn.GroupNorm(num_groups=num_groups, num_channels=in_ch) #, eps=1e-6, affine=True)
+ self.norm2 = torch.nn.GroupNorm(num_groups=num_groups, num_channels=out_ch) #, eps=1e-6, affine=True)
self.skip = skip
if self.skip:
@@ -123,8 +128,8 @@ def forward(self, x):
return self.skip_connection(x) + h
-# %% ../../src/models/layers.ipynb 11
-class ResBlock2D_Conditional(nn.Module):
+# %% ../../src/models/layers.ipynb 10
+class ResBlock2DConditional(nn.Module):
"""A 2d residual block with input of a time-step $t$ embedding."""
def __init__(self, in_ch, out_ch, t_emb_size, kernel_size, skip=True):
super().__init__()
@@ -162,7 +167,7 @@ def forward(self, x, t_emb):
return self.skip_connection(x) + h
-# %% ../../src/models/layers.ipynb 13
+# %% ../../src/models/layers.ipynb 12
class FeedForward(nn.Module):
"""A small dense feed-forward network as used in `transformers`."""
def __init__(self, in_ch, out_ch, inner_mult=1):
@@ -177,18 +182,19 @@ def forward(self, x):
x = self.proj2(x)
return x
-# %% ../../src/models/layers.ipynb 16
+# %% ../../src/models/layers.ipynb 15
class PositionalEncoding(nn.Module):
"""An absolute pos encoding layer."""
- def __init__(self, d_model: int, dropout: float = 0.0, max_len: int = 5000):
+ def __init__(self, d_model: int, dropout: float = 0.0, max_len: int = 5000, freq_factor: float = 10000.0):
super().__init__()
self.dropout = nn.Dropout(p=dropout)
position = torch.arange(max_len).unsqueeze(1)
- div_term = torch.exp(torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model))
+ div_term = torch.exp(torch.arange(0, d_model, 2) * (-math.log(freq_factor) / d_model))
pe = torch.zeros(max_len, d_model)
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
+ pe = pe.contiguous()
self.register_buffer('pe', pe)
def forward(self, x: torch.Tensor) -> torch.Tensor:
@@ -199,11 +205,11 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
x = x + self.pe[None, :x.size(1)]
return self.dropout(x)
-# %% ../../src/models/layers.ipynb 17
+# %% ../../src/models/layers.ipynb 16
class TimeEmbedding(PositionalEncoding):
"""A time embedding layer"""
- def __init__(self, d_model: int, dropout: float = 0.0, max_len: int = 5000):
- super().__init__(d_model, dropout, max_len)
+ def __init__(self, d_model: int, dropout: float = 0.0, max_len: int = 5000, freq_factor: float = 10000.0):
+ super().__init__(d_model, dropout, max_len, freq_factor)
self.ff = FeedForward(d_model, d_model)
def forward(self, t: torch.Tensor):
@@ -211,11 +217,11 @@ def forward(self, t: torch.Tensor):
x = self.ff(x)
return self.dropout(x)
-# %% ../../src/models/layers.ipynb 18
+# %% ../../src/models/layers.ipynb 17
class PositionalEncodingTransposed(PositionalEncoding):
- def __init__(self, d_model: int, dropout: float = 0.0, max_len: int = 5000):
- super().__init__(d_model, dropout, max_len)
- self.pe = torch.permute(self.pe, (1, 0)) # [max_len, d_model] to [d_model, max_len]
+ def __init__(self, d_model: int, dropout: float = 0.0, max_len: int = 5000, freq_factor: float = 10000.0):
+ super().__init__(d_model, dropout, max_len, freq_factor)
+ self.pe = torch.permute(self.pe, (1, 0)).contiguous() # [max_len, d_model] to [d_model, max_len]
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
@@ -225,23 +231,23 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
x = x + self.pe[None, :, :x.size(2)]
return self.dropout(x)
-# %% ../../src/models/layers.ipynb 19
+# %% ../../src/models/layers.ipynb 18
class PositionalEncoding2D(PositionalEncodingTransposed):
"""A 2D absolute pos encoding layer."""
- def __init__(self, d_model: int, dropout: float = 0.0, max_len: int = 5000):
- super().__init__(d_model=d_model//2, dropout=dropout, max_len=max_len)
+ def __init__(self, d_model: int, dropout: float = 0.0, max_len: int = 5000, freq_factor: float = 10000.0):
+ super().__init__(d_model=d_model//2, dropout=dropout, max_len=max_len, freq_factor=freq_factor)
self.d_model_half = d_model//2
# self.proj = nn.Conv2d(d_model, d_model, kernel_size=1, stride=1, padding ="same")
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
Arguments:
- x: Tensor, shape ``[batch_size, gate_color, space , time]``
+ x: Tensor, shape ``[batch_size, gate_color, space, time]``
"""
p1 = self.pe[None, :, :x.size(2), None] #space encoding
p2 = self.pe[None, :, None, :x.size(3)] #time encoding
-
+
x[:, :self.d_model_half] = x[:, :self.d_model_half] + p1
x[:, self.d_model_half:] = x[:, self.d_model_half:] + p2
@@ -249,10 +255,10 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.dropout(x)
-# %% ../../src/models/layers.ipynb 20
+# %% ../../src/models/layers.ipynb 19
class PositionalEncoding2DSpaceOnly(PositionalEncodingTransposed):
- def __init__(self, d_model: int, dropout: float = 0.0, max_len: int = 5000):
- super().__init__(d_model=d_model, dropout=dropout, max_len=max_len)
+ def __init__(self, d_model: int, dropout: float = 0.0, max_len: int = 5000, freq_factor: float = 10000.0):
+ super().__init__(d_model=d_model, dropout=dropout, max_len=max_len, freq_factor=freq_factor)
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
diff --git a/genQC/models/position_encoding.py b/genQC/models/position_encoding.py
new file mode 100644
index 0000000..effcec6
--- /dev/null
+++ b/genQC/models/position_encoding.py
@@ -0,0 +1,148 @@
+"""Implementation of special position encodings."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/models/position_encoding.ipynb.
+
+# %% auto 0
+__all__ = ['RotaryPositionalEmbedding', 'RotaryPositionalEmbedding2D', 'LearnedPositionalEmbedding']
+
+# %% ../../src/models/position_encoding.ipynb 2
+from ..imports import *
+
+# %% ../../src/models/position_encoding.ipynb 4
+class RotaryPositionalEmbedding(nn.Module):
+ """
+ This class implements the Rotary Positional Embeddings (RoPE),
+ proposed in https://arxiv.org/abs/2104.09864.
+
+ Code adjusted from https://github.com/pytorch/torchtune/blob/main/torchtune/modules/position_embeddings.py
+ > Copyright (c) Meta Platforms, Inc. and affiliates.
+ > All rights reserved.
+
+ Additionally adds p-RoPE from https://openreview.net/pdf?id=GtvuNrk58a
+ Note: p=0 coincides with NoPE, while the case p=1 with RoPE
+ """
+
+ def __init__(self, head_dim: int, p: float = 1.0, max_seq_len: int = 4096, base: float = 10_000) -> None:
+ super().__init__()
+ self.head_dim = head_dim
+ self.p = p
+ self.base = base # max_wavelength; the lowest frequencies rotate at roughly 1/base radians per token; i.e. we can resolve 2pi*base tokens
+ self.max_seq_len = max_seq_len
+
+ self.rope_angles = int(self.p * (self.head_dim//2)) #division factor two is for cos-sin split
+ self.nope_angles = self.head_dim//2 - self.rope_angles
+
+ timescale = self.base ** (2.0 * torch.arange(0, self.head_dim//2, dtype=torch.float32) / self.head_dim)
+ timescale[self.rope_angles:] = torch.full((self.nope_angles,), fill_value=torch.inf, dtype=torch.float32)
+
+ theta = 1.0 / timescale
+
+ self.register_buffer("theta", theta, persistent=False)
+ self.rebuild_rope_cache(self.max_seq_len)
+
+ def rebuild_rope_cache(self, max_seq_len: int = 4096) -> None:
+
+ # Create position indexes [0, 1, ..., max_seq_len - 1]
+ seq_idx = torch.arange(max_seq_len, dtype=self.theta.dtype, device=self.theta.device)
+
+ # Outer product of theta and position index: output shape [max_seq_len, head_dim//2]
+ idx_theta = torch.einsum("i, j -> ij", seq_idx, self.theta)
+
+ # cache includes both the cos and sin: output shape [max_seq_len, head_dim//2, 2]
+ rope_cache = torch.stack([torch.cos(idx_theta), torch.sin(idx_theta)], dim=-1)
+ self.register_buffer("rope_cache", rope_cache, persistent=False)
+
+ def forward(self, x: torch.Tensor, pos_idx: Optional[torch.Tensor] = None) -> torch.Tensor:
+ """
+ Shape:
+ x ... [b, seq, n_heads, head_dim]
+ pos_idx ... [b, seq] or [seq]
+ """
+
+ seq_len = x.size(1)
+ xshaped = x.type_as(self.rope_cache).reshape(*x.shape[:-1], -1, 2) #split head_dim [b, seq, n_h, head_dim/2, 2]
+
+ # extract the values based on whether pos_idx is set or not, shape [seq, head_dim/2, 2]
+ if exists(pos_idx):
+ rope_cache = self.rope_cache[pos_idx]
+
+ # reshape the cache to [b, seq, 1, head_dim/2, 2]
+ rope_cache = rope_cache.view(-1, seq_len, 1, xshaped.size(3), 2)
+
+ else:
+ rope_cache = self.rope_cache[:seq_len]
+
+ # reshape the cache to [1, seq, 1, head_dim/2, 2]
+ rope_cache = rope_cache.view(1, seq_len, 1, xshaped.size(3), 2)
+
+ # out has shape [b, seq, n_h, head_dim/2, 2]
+ x_out = torch.stack(
+ [
+ xshaped[..., 0] * rope_cache[..., 0] - xshaped[..., 1] * rope_cache[..., 1], # x cos - y sin
+ xshaped[..., 1] * rope_cache[..., 0] + xshaped[..., 0] * rope_cache[..., 1], # x sin + y cos
+ ], dim=-1)
+
+ # flatten to shape [b, seq, n_h, head_dim]
+ x_out = x_out.flatten(3)
+ return x_out.type_as(x)
+
+# %% ../../src/models/position_encoding.ipynb 7
+class RotaryPositionalEmbedding2D(nn.Module):
+
+ def __init__(self, head_dim: int, p: float = 1.0, max_seq_len: int = 4096, base: float = 10_000) -> None:
+ super().__init__()
+ self.rope = RotaryPositionalEmbedding(head_dim=head_dim//2, p=p, max_seq_len=max_seq_len, base=base)
+
+ def forward(self, x: torch.Tensor, pos_idx: torch.Tensor) -> torch.Tensor:
+ """
+ The tensor `pos_idx` specifies the x and y coordinates of sequence elements of x.
+
+ Shape:
+ x ... [b, seq, n_heads, head_dim]
+ pos_idx ... [b, seq, 2] or [seq, 2]
+ """
+
+ xshaped = x.reshape(*x.shape[:-1], -1, 2) #split head_dim [b, seq, n_h, head_dim/2, 2]
+
+ x_out = torch.cat(
+ [
+ self.rope(xshaped[..., 0], pos_idx=pos_idx[..., 0]), # coord 1
+ self.rope(xshaped[..., 1], pos_idx=pos_idx[..., 1]), # coord 2
+ ], dim=-1)
+
+ return x_out
+
+# %% ../../src/models/position_encoding.ipynb 10
+class LearnedPositionalEmbedding(nn.Module):
+ """
+ This class implements a Learned Positional Embedding, e.g. used for spatial circuit dimension.
+ """
+
+ def __init__(self, dim: int, max_seq_len: int = 64) -> None:
+ super().__init__()
+
+ self.dim = dim
+ self.max_seq_len = max_seq_len
+
+ _pos_encoding = torch.zeros((self.max_seq_len, self.dim), dtype=torch.float32)
+ self.pos_encoding = nn.Parameter(_pos_encoding)
+
+ self._init_weights()
+
+ def _init_weights(self) -> None:
+ nn.init.orthogonal_(self.pos_encoding)
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ """
+ Assumes channel last.
+
+ Shapes:
+ x ... [b, s, t, ch]
+ """
+
+ seq_len = x.shape[1]
+
+ pos_encoding = self.pos_encoding[:seq_len].view(1, seq_len, 1, -1) # to [1, s, 1, ch]
+ x = x + pos_encoding
+
+ return x
diff --git a/genQC/models/transformers/__init__.py b/genQC/models/transformers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/genQC/models/transformers/attention.py b/genQC/models/transformers/attention.py
new file mode 100644
index 0000000..0b723ba
--- /dev/null
+++ b/genQC/models/transformers/attention.py
@@ -0,0 +1,160 @@
+"""Common transformer and attention blocks."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../src/models/transformers/attention.ipynb.
+
+# %% auto 0
+__all__ = ['FeedForwardBlock', 'BasisSelfAttnBlock', 'BasisCrossAttnBlock', 'SpatialTransformerSelfAttn', 'SpatialTransformer']
+
+# %% ../../../src/models/transformers/attention.ipynb 2
+from ...imports import *
+
+# %% ../../../src/models/transformers/attention.ipynb 4
+class FeedForwardBlock(nn.Module):
+ """
+ A small dense feed-forward network as used in `transformers`. Assumes channel last.
+ Inspired by https://arxiv.org/pdf/2401.11605.
+ From https://arxiv.org/pdf/2002.05202 a modification to SiGLU
+ """
+
+ def __init__(self, in_dim: int, hidden_dim: int, dropout: float = 0.0) -> None:
+ super().__init__()
+ self.hidden_dim = hidden_dim
+ self.proj_in = nn.Linear(in_dim, 2*hidden_dim) # factor two for GLU part split
+ self.proj_out = nn.Linear(hidden_dim, in_dim)
+ self.act = nn.SiLU()
+ self.drop = nn.Dropout(dropout)
+
+ def siglu(self, x: torch.Tensor) -> torch.Tensor:
+ x = self.proj_in(x)
+ return x[..., :self.hidden_dim] * self.act(x[..., self.hidden_dim:])
+
+ #@torch.compile
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = self.siglu(x)
+ x = self.drop(x)
+ x = self.proj_out(x)
+ return x
+
+# %% ../../../src/models/transformers/attention.ipynb 6
+class BasisSelfAttnBlock(nn.Module):
+ """A self attention block, i.e. a `transformer` encoder."""
+ def __init__(self, ch, num_heads, dropout=0.0, batch_first=False):
+ super().__init__()
+ self.self_att = nn.MultiheadAttention(ch, num_heads=num_heads, batch_first=batch_first) #[t, b, c]
+ self.ff = FeedForwardBlock(ch, 2*ch)
+ self.norm1 = nn.LayerNorm(ch)
+ self.norm2 = nn.LayerNorm(ch)
+ self.drop = nn.Dropout(dropout)
+
+ def forward(self, x, attn_mask=None, key_padding_mask=None, need_weights=False):
+ #x ... [ t, batch, ch]
+
+ self_out = self.norm1(x)
+ self_out, _ = self.self_att(self_out, key=self_out, value=self_out, attn_mask=attn_mask, key_padding_mask=key_padding_mask, need_weights=need_weights)
+ self_out = self.drop(self_out) + x
+
+ feed_out = self.norm2(self_out)
+ feed_out = self.ff(feed_out)
+ feed_out = self.drop(feed_out) + self_out
+
+ return feed_out
+
+# %% ../../../src/models/transformers/attention.ipynb 7
+class BasisCrossAttnBlock(nn.Module):
+ """A cross attention block, i.e. a `transformer` decoder."""
+ def __init__(self, ch, num_heads, dropout=0.0, batch_first=False):
+ super().__init__()
+ self.self_att = nn.MultiheadAttention(ch, num_heads=num_heads, batch_first=batch_first) #[t, b, c]
+ self.cross_att = nn.MultiheadAttention(ch, num_heads=num_heads, batch_first=batch_first)
+ self.ff = FeedForwardBlock(ch, 2*ch)
+ self.norm1 = nn.LayerNorm(ch)
+ self.norm2 = nn.LayerNorm(ch)
+ self.norm3 = nn.LayerNorm(ch)
+ self.drop = nn.Dropout(dropout)
+
+ def forward(self, x, c_emb, attn_mask=None, key_padding_mask=None, need_weights=False):
+ #x ... [ t, batch, ch]
+ #c_emb ... [seq, batch, ch]
+
+ self_out = self.norm1(x)
+ self_out, _ = self.self_att(self_out, key=self_out, value=self_out, attn_mask=attn_mask, key_padding_mask=key_padding_mask, need_weights=need_weights)
+ self_out = self.drop(self_out) + x
+
+ cross_out = self.norm2(self_out)
+ cross_out, _ = self.cross_att(cross_out, key=c_emb, value=c_emb, need_weights=need_weights)
+ cross_out = self.drop(cross_out) + self_out
+
+ feed_out = self.norm3(cross_out)
+ feed_out = self.ff(feed_out)
+ feed_out = self.drop(feed_out) + cross_out
+
+ return feed_out
+
+# %% ../../../src/models/transformers/attention.ipynb 9
+class SpatialTransformerSelfAttn(nn.Module):
+ """A spatial residual `transformer`, only uses self-attention."""
+ def __init__(self, ch, num_heads, depth, dropout=0.0, num_groups=32):
+ super().__init__()
+ self.norm = torch.nn.GroupNorm(num_groups=num_groups, num_channels=ch, eps=1e-6, affine=True)
+ self.transformer_blocks = nn.ModuleList([BasisSelfAttnBlock(ch=ch, num_heads=num_heads, dropout=dropout) for d in range(depth)])
+
+ def forward(self, x, attn_mask=None, key_padding_mask=None):
+ #x ... [batch, ch, space, time]
+ #c_emb ... [batch, seq, ch]
+ b, ch, space, time = x.shape
+
+ x_in = x
+
+ #-------------------------
+ x = self.norm(x)
+
+ x = torch.reshape(x, (b, ch, space*time))
+ x = torch.permute(x, (2, 0, 1))#.contiguous() # to [t, batch, ch]
+
+ #-------------------------
+ for transformer_block in self.transformer_blocks:
+ x = transformer_block(x, attn_mask, key_padding_mask)
+
+ #-------------------------
+
+ x = torch.permute(x, (1, 2, 0)) # back to [batch, ch, t]
+ x = torch.reshape(x, (b, ch, space, time))#.contiguous()
+
+ return x + x_in
+
+# %% ../../../src/models/transformers/attention.ipynb 10
+class SpatialTransformer(nn.Module):
+ """A spatial residual `transformer`, uses self- and cross-attention on conditional input."""
+
+ def __init__(self, ch, cond_emb_size, num_heads, depth, dropout=0.0, num_groups=32):
+ super().__init__()
+ self.cat_proj = nn.Linear(cond_emb_size, ch)
+ self.norm = torch.nn.GroupNorm(num_groups=num_groups, num_channels=ch, eps=1e-6, affine=True)
+ self.transformer_blocks = nn.ModuleList([BasisCrossAttnBlock(ch=ch, num_heads=num_heads, dropout=dropout) for d in range(depth)])
+
+ def forward(self, x, c_emb, attn_mask=None, key_padding_mask=None):
+ #x ... [batch, ch, space, time]
+ #c_emb ... [batch, seq, ch]
+ b, ch, space, time = x.shape
+
+ x_in = x
+
+ #-------------------------
+ x = self.norm(x)
+
+ x = torch.reshape(x, (b, ch, space*time))
+ x = torch.permute(x, (2, 0, 1))#.contiguous() # to [t, batch, ch]
+
+ c_emb = self.cat_proj(c_emb)
+ c_emb = torch.permute(c_emb, (1, 0, 2))#.contiguous() # to [seq, batch, ch]
+
+ #-------------------------
+ for transformer_block in self.transformer_blocks:
+ x = transformer_block(x, c_emb, attn_mask, key_padding_mask)
+
+ #-------------------------
+
+ x = torch.permute(x, (1, 2, 0)) # back to [batch, ch, t]
+ x = torch.reshape(x, (b, ch, space, time))#.contiguous()
+
+ return x + x_in
diff --git a/genQC/models/transformers/cirdit_multimodal.py b/genQC/models/transformers/cirdit_multimodal.py
new file mode 100644
index 0000000..93ffbc8
--- /dev/null
+++ b/genQC/models/transformers/cirdit_multimodal.py
@@ -0,0 +1,759 @@
+"""The multimodal circuit generation model: *Circuit Diffusion Transformer* (CirDiT)."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../src/models/transformers/cirdit_multimodal.ipynb.
+
+# %% auto 0
+__all__ = ['RotaryMultiheadAttention', 'FeedForwardBlock', 'SelfAttnBlock', 'AdaptiveSelfAttnBlock', 'CrossAttnBlock',
+ 'CoreTransformer', 'PackingTransformer', 'UnpackingTransformer', 'TimeEmbedding', 'CirDiTConfig', 'CirDiT',
+ 'UnitaryCLIPPartialNoiseCompilationCirDiTConfig', 'UnitaryCLIPPartialNoiseCompilationCirDiT']
+
+# %% ../../../src/models/transformers/cirdit_multimodal.ipynb 2
+from ...imports import *
+from ..config_model import *
+from ..position_encoding import RotaryPositionalEmbedding, LearnedPositionalEmbedding
+from ..layers import PositionalEncoding
+
+# %% ../../../src/models/transformers/cirdit_multimodal.ipynb 4
+class RotaryMultiheadAttention(nn.Module):
+ """
+ MultiheadAttention described in the paper: Attention Is All You Need (https://arxiv.org/abs/1706.03762).
+ We add a rotary position encoding (RoPE).
+
+ The attention core is `F.scaled_dot_attention` from pytorch.
+ Could be switched to `https://github.com/Dao-AILab/flash-attention` or `xFormers`.
+ """
+
+ def __init__(self,
+ in_dim: int,
+ embed_dim: int,
+ num_heads: int,
+ bias: bool = True,
+ p_rope: float = 1.0,
+ max_seq_len: int = 4096,
+ base_rope: float = 10_000,
+ enable_qk_norm: bool = False) -> None:
+
+ super().__init__()
+
+ self.num_heads = num_heads
+ self.bias = bias
+ self.head_dim = embed_dim // num_heads
+
+ self.q_proj = nn.Linear(in_dim, embed_dim, bias=bias)
+ self.k_proj = nn.Linear(in_dim, embed_dim, bias=bias)
+ self.v_proj = nn.Linear(in_dim, embed_dim, bias=bias)
+
+ self.out_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
+
+ self.enable_qk_norm = enable_qk_norm
+ if self.enable_qk_norm:
+ self.q_norm = nn.RMSNorm(self.head_dim)
+ self.k_norm = nn.RMSNorm(self.head_dim)
+
+ self.rope = RotaryPositionalEmbedding(head_dim=self.head_dim, p=p_rope, max_seq_len=max_seq_len, base=base_rope)
+
+ self._init_weights()
+
+ def _init_weights(self) -> None:
+ nn.init.xavier_normal_(self.q_proj.weight)
+ nn.init.xavier_normal_(self.k_proj.weight)
+ nn.init.xavier_normal_(self.v_proj.weight)
+ nn.init.xavier_normal_(self.out_proj.weight)
+
+ if self.bias:
+ nn.init.zeros_(self.q_proj.bias)
+ nn.init.zeros_(self.k_proj.bias)
+ nn.init.zeros_(self.v_proj.bias)
+ nn.init.zeros_(self.out_proj.bias)
+
+
+ def forward(self, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor, pos_idx: Optional[torch.Tensor] = None) -> torch.Tensor:
+ """
+ Assumes batch first. When `pos_idx` is provided we use RoPE, else NOT!
+
+ Shapes:
+ query ... [b, n1, c]
+ key/value ... [b, n2, c]
+ """
+
+ assert key.shape == value.shape
+
+ b, n1, _ = query.shape
+ _, n2, _ = key.shape
+
+ q = self.q_proj(query)
+ k = self.k_proj(key)
+ v = self.v_proj(value)
+
+ q = q.view(b, n1, self.num_heads, self.head_dim)
+ k = k.view(b, n2, self.num_heads, self.head_dim)
+ v = v.view(b, n2, self.num_heads, self.head_dim)
+
+ if self.enable_qk_norm:
+ q = self.q_norm(q)
+ k = self.k_norm(k)
+
+ if exists(pos_idx):
+ q = self.rope(q, pos_idx=pos_idx)
+ k = self.rope(k, pos_idx=pos_idx)
+
+ # scaled_dot_product_attention takes [b, num_heads, seq, head_dim]
+ q = q.permute((0, 2, 1, 3))
+ k = k.permute((0, 2, 1, 3))
+ v = v.permute((0, 2, 1, 3))
+
+ # see https://pytorch.org/docs/stable/generated/torch.nn.functional.scaled_dot_product_attention.html
+ attn = F.scaled_dot_product_attention(query=q,
+ key=k,
+ value=v,
+ attn_mask=None,
+ dropout_p=0.0,
+ is_causal=False,
+ scale=None,
+ #enable_gqa=False
+ )
+
+ # back to [b, seq, num_heads, head_dim]
+ attn = attn.permute((0, 2, 1, 3))
+
+ # pack heads together
+ attn = attn.reshape(b, n1, self.num_heads * self.head_dim)
+ attn = self.out_proj(attn)
+ return attn
+
+# %% ../../../src/models/transformers/cirdit_multimodal.ipynb 6
+class FeedForwardBlock(nn.Module):
+ """
+ A small dense feed-forward network as used in `transformers`. Assumes channel last.
+ Inspired by https://arxiv.org/pdf/2401.11605 and added
+ from https://arxiv.org/pdf/2002.05202 a modification to SiGLU structure.
+ """
+
+ def __init__(self,
+ in_dim: int,
+ hidden_dim: int,
+ out_dim: Optional[int] = None,
+ dropout: float = 0.0) -> None:
+ super().__init__()
+ out_dim = default(out_dim, in_dim)
+
+ self.hidden_dim = hidden_dim
+ self.proj_in = nn.Linear(in_dim, 2*hidden_dim) # factor two for GLU part split
+ self.proj_out = nn.Linear(hidden_dim, out_dim)
+ self.act = nn.SiLU()
+ self.drop = nn.Dropout(dropout)
+
+ self._init_weights()
+
+ def _init_weights(self) -> None:
+ nn.init.zeros_(self.proj_out.bias)
+ # nn.init.xavier_normal_(self.proj_out.weight)
+
+ def siglu(self, x: torch.Tensor) -> torch.Tensor:
+ x = self.proj_in(x)
+ return x[..., :self.hidden_dim] * self.act(x[..., self.hidden_dim:])
+
+ #@torch.compile
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = self.siglu(x)
+ x = self.drop(x)
+ x = self.proj_out(x)
+ return x
+
+# %% ../../../src/models/transformers/cirdit_multimodal.ipynb 7
+class SelfAttnBlock(nn.Module):
+ """A self-attention block which includes the time condition `t_emb`, see https://arxiv.org/pdf/2312.02139."""
+
+ def __init__(self, ch: int, t_emb_size: int, num_heads: int, dropout: float = 0.0, p_rope: float = 1.0, base_rope: float = 10_000) -> None:
+ super().__init__()
+
+ self.self_att = RotaryMultiheadAttention(in_dim=ch+t_emb_size, embed_dim=ch, num_heads=num_heads, p_rope=p_rope, base_rope=base_rope)
+
+ self.ff = FeedForwardBlock(in_dim=ch, hidden_dim=2*ch)
+ self.norm_self = nn.RMSNorm(ch)
+ self.norm_ff = nn.RMSNorm(ch)
+ self.drop = nn.Dropout(dropout)
+
+ self._init_weights()
+
+ def _init_weights(self) -> None:
+
+ # note a bonus of res-pos-norm is that we can init as identity!
+ nn.init.zeros_(self.norm_self.weight)
+ nn.init.zeros_(self.norm_ff.weight)
+
+ def forward(self, x: torch.Tensor, t_emb: torch.Tensor, pos_idx: torch.Tensor) -> torch.Tensor:
+ """
+ Assumes batch first.
+
+ Shapes:
+ x ... [b, n, ch]
+ t_emb ... [b, 1, t_emb_size]
+ pos_idx ... [b, n] or [n]
+ """
+
+ t_emb_self = t_emb.expand(x.shape[0], x.shape[1], -1)
+
+ # Self-attention part
+ self_out = torch.cat([x, t_emb_self], dim=-1) # concat time tokens
+ self_out = self.self_att(query=self_out, key=self_out, value=self_out, pos_idx=pos_idx)
+ self_out = self.norm_self(self_out)
+ self_out = self.drop(self_out) + x
+
+ # Feed-Forward part
+ feed_out = self.ff(self_out)
+ feed_out = self.norm_ff(feed_out)
+ feed_out = self.drop(feed_out) + self_out
+ return feed_out
+
+# %% ../../../src/models/transformers/cirdit_multimodal.ipynb 8
+class AdaptiveSelfAttnBlock(nn.Module):
+ """A self-attention block which includes the time condition `t_emb`, see https://arxiv.org/pdf/2312.02139."""
+
+ def __init__(self,
+ ch: int,
+ mod_ch: int,
+ t_emb_size: int,
+ num_heads: int,
+ dropout: float = 0.0,
+ p_rope: float = 1.0,
+ base_rope: float = 10_000) -> None:
+ super().__init__()
+
+ self.self_att = RotaryMultiheadAttention(in_dim=ch+t_emb_size, embed_dim=ch, num_heads=num_heads, p_rope=p_rope, base_rope=base_rope)
+
+ self.ff = FeedForwardBlock(in_dim=ch, hidden_dim=2*ch)
+ self.norm_self = nn.RMSNorm(ch)
+ self.norm_ff = nn.RMSNorm(ch)
+ self.drop = nn.Dropout(dropout)
+
+ self.adaRMS_modulation = nn.Linear(mod_ch, 6*ch)
+
+ self._init_weights()
+
+ def _init_weights(self) -> None:
+ nn.init.zeros_(self.adaRMS_modulation.bias)
+
+ def forward(self, x: torch.Tensor, mod: torch.Tensor, t_emb: torch.Tensor, pos_idx: torch.Tensor) -> torch.Tensor:
+ """
+ Assumes batch first.
+
+ Shapes:
+ x ... [b, n, ch]
+ mod ... [b, n, mod_ch]
+ t_emb ... [b, 1, t_emb_size]
+ pos_idx ... [b, n] or [n]
+ """
+
+ scale_att, shift_att, gate_attn, scale_mlp, shift_mlp, gate_mlp = self.adaRMS_modulation(mod).chunk(6, dim=-1)
+
+ t_emb_self = t_emb.expand(x.shape[0], x.shape[1], -1)
+
+ # Self-attention part
+ self_out = x * (1.0 + scale_att) + shift_att
+ self_out = torch.cat([self_out, t_emb_self], dim=-1) # concat time tokens
+ self_out = self.self_att(query=self_out, key=self_out, value=self_out, pos_idx=pos_idx)
+ self_out = self.norm_self(self_out) * gate_attn.tanh()
+ self_out = self.drop(self_out) + x
+
+ # Feed-Forward part
+ feed_out = self_out * (1.0 + scale_mlp) + shift_mlp
+ feed_out = self.ff(feed_out)
+ feed_out = self.norm_ff(feed_out) * gate_mlp.tanh()
+ feed_out = self.drop(feed_out) + self_out
+ return feed_out
+
+# %% ../../../src/models/transformers/cirdit_multimodal.ipynb 9
+class CrossAttnBlock(nn.Module):
+ """A cross-attention block which includes the time condition `t_emb`, see https://arxiv.org/pdf/2312.02139"""
+
+ def __init__(self, ch: int, t_emb_size: int, num_heads: int, dropout: float = 0.0, p_rope: float = 1.0, base_rope: float = 10_000) -> None:
+ super().__init__()
+
+ self.self_att = RotaryMultiheadAttention(in_dim=ch+t_emb_size, embed_dim=ch, num_heads=num_heads, p_rope=p_rope, base_rope=base_rope)
+ self.multi_att = RotaryMultiheadAttention(in_dim=ch+t_emb_size, embed_dim=ch, num_heads=num_heads, p_rope=p_rope, base_rope=base_rope)
+
+ self.ff = FeedForwardBlock(in_dim=ch, hidden_dim=2*ch)
+ self.norm_self = nn.RMSNorm(ch)
+ self.norm_multi = nn.RMSNorm(ch)
+ self.norm_ff = nn.RMSNorm(ch)
+ self.drop = nn.Dropout(dropout)
+
+ self._init_weights()
+
+ def _init_weights(self) -> None:
+ nn.init.zeros_(self.norm_self.weight)
+ nn.init.zeros_(self.norm_multi.weight)
+ nn.init.zeros_(self.norm_ff.weight)
+
+ def forward(self, x: torch.Tensor, c_emb: torch.Tensor, t_emb: torch.Tensor, pos_idx: torch.Tensor) -> torch.Tensor:
+ """
+ Assumes batch first.
+
+ Shapes:
+ x ... [b, n1, ch]
+ c_emb ... [b, n2, ch]
+ t_emb ... [b, 1, t_emb_size]
+ pos_idx ... [b, n1] or [n1]
+ """
+
+ t_emb_self = t_emb.expand( x.shape[0], x.shape[1] , -1)
+ t_emb_multi = t_emb.expand(c_emb.shape[0], x.shape[1] + c_emb.shape[1], -1)
+
+ # Self-attention part
+ self_out = torch.cat([x, t_emb_self], dim=-1) # concat time tokens
+ self_out = self.self_att(query=self_out, key=self_out, value=self_out, pos_idx=pos_idx)
+ self_out = self.norm_self(self_out)
+ self_out = self.drop(self_out) + x
+
+ # Multimodial-attention part
+ multi_out = torch.cat([self_out, c_emb], dim=1) # concat latents with condition ... [b, n1+n2, ch]
+
+ multi_out = torch.cat([multi_out, t_emb_multi], dim=-1) # concat time tokens
+ multi_out = self.multi_att(query=multi_out, key=multi_out, value=multi_out, pos_idx=None)
+
+ multi_out, multi_out_gate = multi_out[:, :x.shape[1]], multi_out[:, x.shape[1]:]
+ multi_out_gate = multi_out_gate.mean(dim=1, keepdim=True) # ... [b, 1, ch]
+
+ multi_out = self.norm_multi(multi_out) * multi_out_gate.tanh()
+ multi_out = self.drop(multi_out) + self_out
+
+ # Feed-Forward part
+ feed_out = self.ff(multi_out)
+ feed_out = self.norm_ff(feed_out)
+ feed_out = self.drop(feed_out) + multi_out
+ return feed_out
+
+# %% ../../../src/models/transformers/cirdit_multimodal.ipynb 11
+class CoreTransformer(nn.Module):
+ """
+ The main transformer of the CirDiT model, intakes time (attn-concat) and condition encodings (cross-attn).
+ Applies a RoPE for time dimension.
+ """
+
+ def __init__(self,
+ ch: int,
+ c_emb_size: int,
+ t_emb_size: int,
+ depth: int,
+ num_heads: int,
+ dropout: float = 0.0,
+ p_rope: float = 1.0,
+ base_rope: float = 10_000) -> None:
+ super().__init__()
+
+ self.norm = nn.RMSNorm(ch)
+
+ self.c_proj = nn.Linear(c_emb_size, ch)
+ self.blocks = nn.ModuleList([
+ CrossAttnBlock(ch=ch,
+ t_emb_size=t_emb_size,
+ num_heads=num_heads,
+ dropout=dropout,
+ p_rope=p_rope,
+ base_rope=base_rope)
+ for d in range(depth)
+ ])
+
+ def forward(self, x: torch.Tensor, c_emb: torch.Tensor, t_emb: torch.Tensor) -> torch.Tensor:
+ """
+ Shapes:
+ x ... [b, t, ch]
+ c_emb ... [b, seq, c_emb_size]
+ t_emb ... [b, 1, t_emb_size]
+ """
+
+ c_emb = self.c_proj(c_emb)
+ pos_idx = torch.arange(x.shape[1], device=x.device, dtype=torch.int32)
+
+ x = self.norm(x)
+
+ for block in self.blocks:
+ x = block(x=x, c_emb=c_emb, t_emb=t_emb, pos_idx=pos_idx)
+
+ return x
+
+# %% ../../../src/models/transformers/cirdit_multimodal.ipynb 13
+class PackingTransformer(ConfigModel):
+ """
+ The first stage packing/unpacking transformers of the CirDiT model, intakes time (attn-concat).
+ Applies a RoPE for time dimension only, not on spatial dimension.
+ """
+
+ def __init__(self,
+ ch: int,
+ t_emb_size: int,
+ depth: int,
+ num_heads: int,
+ dropout: float = 0.0,
+ p_rope: float = 1.0,
+ base_rope: float = 10_000) -> None:
+ super().__init__()
+
+ self.norm = nn.RMSNorm(ch)
+ self.blocks = nn.ModuleList([
+ SelfAttnBlock(ch=ch,
+ t_emb_size=t_emb_size,
+ num_heads=num_heads,
+ dropout=dropout,
+ p_rope=p_rope,
+ base_rope=base_rope)
+ for d in range(depth)
+ ])
+
+ def forward(self, x: torch.Tensor, t_emb: torch.Tensor, return_penultimate: bool = False) -> torch.Tensor:
+ """
+ Shapes:
+ x ... [b, s, t, ch]
+ t_emb ... [b, 1, t_emb_size]
+ """
+
+ b, s, t, ch = x.shape
+
+ # create pos_idx such that they only depend on the time position
+ pos_idx = torch.arange(t, device=x.device, dtype=torch.int32).expand(b, s, -1)
+ pos_idx = pos_idx.reshape(b, -1)
+
+ # flatten spatial and time into seq
+ x = x.reshape(b, s*t, ch)
+ x = self.norm(x)
+
+ if return_penultimate:
+ for block in self.blocks[:-1]:
+ x = block(x=x, t_emb=t_emb, pos_idx=pos_idx)
+
+ penultimate = x
+ x = self.blocks[-1](x=x, t_emb=t_emb, pos_idx=pos_idx)
+
+ else:
+ for block in self.blocks:
+ x = block(x=x, t_emb=t_emb, pos_idx=pos_idx)
+
+ # undo flatten
+ x = x.reshape(b, s, t, ch)
+
+ if return_penultimate:
+ penultimate = penultimate.reshape(b, s, t, ch)
+ return x, penultimate
+
+ return x
+
+# %% ../../../src/models/transformers/cirdit_multimodal.ipynb 14
+class UnpackingTransformer(ConfigModel):
+ """
+ The first stage packing/unpacking transformers of the CirDiT model, intakes time (attn-concat).
+ Applies a RoPE for time dimension only, not on spatial dimension.
+ """
+
+ def __init__(self,
+ ch: int,
+ mod_ch: int,
+ t_emb_size: int,
+ depth: int,
+ num_heads: int,
+ dropout: float = 0.0,
+ p_rope: float = 1.0,
+ base_rope: float = 10_000) -> None:
+ super().__init__()
+
+ self.norm = nn.RMSNorm(ch)
+ self.blocks = nn.ModuleList([
+ AdaptiveSelfAttnBlock(ch=ch,
+ mod_ch=mod_ch,
+ t_emb_size=t_emb_size,
+ num_heads=num_heads,
+ dropout=dropout,
+ p_rope=p_rope,
+ base_rope=base_rope)
+ for d in range(depth)
+ ])
+
+ def forward(self, x: torch.Tensor, mod: torch.Tensor, t_emb: torch.Tensor) -> torch.Tensor:
+ """
+ Shapes:
+ x ... [b, s, t, ch]
+ t_emb ... [b, 1, t_emb_size]
+ """
+
+ b, s, t, ch = x.shape
+ *_, mod_ch = mod.shape
+
+ # create pos_idx such that they only depend on the time position
+ pos_idx = torch.arange(t, device=x.device, dtype=torch.int32).expand(b, s, -1)
+ pos_idx = pos_idx.reshape(b, -1)
+
+ # flatten spatial and time into seq
+ x = x.reshape(b, s*t, ch)
+ mod = mod.reshape(b, s*t, mod_ch).contiguous()
+
+ x = self.norm(x)
+
+ for block in self.blocks:
+ x = block(x=x, mod=mod, t_emb=t_emb, pos_idx=pos_idx)
+
+ # undo flatten
+ x = x.reshape(b, s, t, ch)
+ return x
+
+# %% ../../../src/models/transformers/cirdit_multimodal.ipynb 16
+class TimeEmbedding(PositionalEncoding):
+ """A time embedding layer."""
+
+ def __init__(self, d_model: int,
+ dropout: float = 0.0,
+ max_len: int = 5000,
+ freq_factor: float = 10_000.0) -> None:
+ super().__init__(d_model=d_model, dropout=dropout, max_len=max_len, freq_factor=freq_factor)
+
+ self.ff = FeedForwardBlock(in_dim=d_model, hidden_dim=2*d_model)
+
+ def forward(self, t: torch.Tensor) -> torch.Tensor:
+ x = self.pe[t]
+ x = self.ff(x)
+ return self.dropout(x)
+
+# %% ../../../src/models/transformers/cirdit_multimodal.ipynb 18
+@dataclass
+class CirDiTConfig:
+ clr_dim: int
+ ch_packing: int
+ ch_core: int
+ c_emb_size: int
+ t_emb_size: int
+ depth_packing: int
+ depth_core: int
+ num_heads_packing: int
+ num_heads_core: int
+ dropout: float
+ p_rope: float
+ base_rope: float
+
+# %% ../../../src/models/transformers/cirdit_multimodal.ipynb 19
+class CirDiT(ConfigModel):
+ """
+ The proposed Circuit Diffusion Transformer (CirDiT).
+ """
+
+ def __init__(self,
+ clr_dim: int,
+ ch_packing: int,
+ ch_core: int,
+ c_emb_size: int,
+ t_emb_size: int,
+ depth_packing: int,
+ depth_core: int,
+ num_heads_packing: int,
+ num_heads_core: int,
+ dropout: float = 0.0,
+ p_rope: float = 1.0,
+ base_rope: float = 10_000) -> None:
+ super().__init__()
+
+ self.ch_packing = ch_packing
+ self.ch_core = ch_core
+
+ self.params_config = CirDiTConfig(clr_dim=clr_dim,
+ ch_packing=ch_packing,
+ ch_core=ch_core,
+ c_emb_size=c_emb_size,
+ t_emb_size=t_emb_size,
+ depth_packing=depth_packing,
+ depth_core=depth_core,
+ num_heads_packing=num_heads_packing,
+ num_heads_core=num_heads_core,
+ dropout=dropout,
+ p_rope=p_rope,
+ base_rope=base_rope)
+
+ self.packing = PackingTransformer(ch=ch_packing,
+ t_emb_size=t_emb_size,
+ depth=depth_packing,
+ num_heads=num_heads_packing,
+ dropout=dropout,
+ p_rope=p_rope,
+ base_rope=base_rope)
+
+ self.unpacking = UnpackingTransformer(ch=ch_packing,
+ mod_ch=ch_core,
+ t_emb_size=t_emb_size,
+ depth=depth_packing,
+ num_heads=num_heads_packing,
+ dropout=dropout,
+ p_rope=p_rope,
+ base_rope=base_rope)
+
+ self.core = CoreTransformer(ch=ch_core,
+ c_emb_size=c_emb_size,
+ t_emb_size=t_emb_size,
+ depth=depth_core,
+ num_heads=num_heads_core,
+ dropout=dropout,
+ p_rope=p_rope,
+ base_rope=base_rope)
+
+ self.proj_in = nn.Linear(clr_dim, ch_packing)
+ self.proj_out = nn.Linear(ch_packing, clr_dim)
+ self.core_proj = nn.Linear(ch_packing, ch_core)
+
+ self.t_emb = TimeEmbedding(d_model=t_emb_size, max_len=5000) #here max number of timetseps
+ self.qubit_pos_enc = LearnedPositionalEmbedding(dim=ch_packing, max_seq_len=64) #here max number of qubits
+
+ self._init_weights()
+
+ def _init_weights(self) -> None:
+ nn.init.orthogonal_(self.core_proj.weight)
+ nn.init.zeros_(self.core_proj.bias)
+ nn.init.zeros_(self.proj_out.bias)
+
+ def main_pass(self, x: torch.Tensor, t_emb: torch.Tensor, c_emb: torch.Tensor) -> torch.Tensor:
+ b, s, t, _ = x.shape
+
+ x = self.proj_in(x)
+ x = self.qubit_pos_enc(x)
+
+ # Pack spatial into tokens
+ x_main, x = self.packing(x=x, t_emb=t_emb, return_penultimate=True)
+
+ # Downsample, reduce spatial, ... [b, t, ch_core]
+ x_main = x_main.mean(dim=1)
+ x_main = self.core_proj(x_main)
+
+ # Core transformer
+ x_main = self.core(x=x_main, c_emb=c_emb, t_emb=t_emb) - x_main # subtraction such that if core=ident at init we cancel the signal
+ x_main = x_main.unsqueeze(1).expand(b, s, t, self.ch_core)
+
+ # Unpack tokens into spatial
+ x = self.unpacking(x=x, mod=x_main, t_emb=t_emb)
+ x = self.proj_out(x)
+
+ return x
+
+ def forward(self, x: torch.Tensor, t: torch.Tensor, c_emb: torch.Tensor, micro_cond: Optional[torch.Tensor] = None) -> torch.Tensor:
+ """
+ Assumes a `channel_last` embedding of circuits.
+
+ Shapes:
+ x ... [b, s, t, ch]
+ t ... [b]
+ c_emb ... [b, seq, c_emb_size]
+ micro_cond ... [b]
+ """
+
+ t_emb = self.t_emb(t) #.detach()
+ t_emb = t_emb.unsqueeze(1) # to [b, 1, ch]
+
+ x = self.main_pass(x, t_emb, c_emb)
+ return x
+
+# %% ../../../src/models/transformers/cirdit_multimodal.ipynb 21
+@dataclass
+class UnitaryCLIPPartialNoiseCompilationCirDiTConfig(CirDiTConfig):
+ unitary_encoder_config: dict
+
+# %% ../../../src/models/transformers/cirdit_multimodal.ipynb 22
+class UnitaryCLIPPartialNoiseCompilationCirDiT(CirDiT):
+ """Extends `CirDiT` to the multimodal unitary compilation model."""
+
+ def __init__(self,
+ clr_dim: int,
+ ch_packing: int,
+ ch_core: int,
+ c_emb_size: int,
+ t_emb_size: int,
+ depth_packing: int,
+ depth_core: int,
+ num_heads_packing: int,
+ num_heads_core: int,
+ dropout: float = 0.0,
+ p_rope: float = 1.0,
+ base_rope: float = 10_000,
+ unitary_encoder_config: Optional[dict] = None,
+ unitary_encoder: Optional[nn.Module] = None) -> None:
+
+ super().__init__(clr_dim=clr_dim,
+ ch_packing=ch_packing,
+ ch_core=ch_core,
+ c_emb_size=c_emb_size,
+ t_emb_size=t_emb_size,
+ depth_packing=depth_packing,
+ depth_core=depth_core,
+ num_heads_packing=num_heads_packing,
+ num_heads_core=num_heads_core,
+ dropout=dropout,
+ p_rope=p_rope,
+ base_rope=base_rope)
+
+ if exists(unitary_encoder_config): #load a trained encoder
+ self.unitary_encoder = ConfigModel.from_config(unitary_encoder_config, device=None, silent=True)
+
+ elif exists(unitary_encoder):
+ self.unitary_encoder = unitary_encoder
+ unitary_encoder_config = self.unitary_encoder.get_config()
+
+ unitary_encoder_config = {"target": unitary_encoder_config["target"],
+ "params": unitary_encoder_config["params"]}
+
+ else:
+ raise RuntimeError("Provide either `unitary_encoder_config` to load a pretrained encoder or a `unitary_encoder` model directly!`")
+
+ self.params_config = UnitaryCLIPPartialNoiseCompilationCirDiTConfig(
+ clr_dim=clr_dim,
+ ch_packing=ch_packing,
+ ch_core=ch_core,
+ c_emb_size=c_emb_size,
+ t_emb_size=t_emb_size,
+ depth_packing=depth_packing,
+ depth_core=depth_core,
+ num_heads_packing=num_heads_packing,
+ num_heads_core=num_heads_core,
+ dropout=dropout,
+ p_rope=p_rope,
+ base_rope=base_rope,
+ unitary_encoder_config=unitary_encoder_config
+ )
+
+ #--------
+
+ self.empty_cond = nn.Parameter(torch.randn((1, 1, c_emb_size)))
+
+ self.t_emb = TimeEmbedding(d_model=t_emb_size, max_len=5000) #here max number of timetseps
+ self.t_emb2 = TimeEmbedding(d_model=t_emb_size, max_len=5000) #here max number of timetseps
+
+ def forward(self,
+ x: torch.Tensor,
+ t_h: torch.Tensor,
+ t_w: torch.Tensor,
+ c_emb: torch.Tensor,
+ U: torch.Tensor,
+ rnd: Optional[torch.Tensor] = None) -> torch.Tensor:
+ """
+ Assumes a channel_last embedding of circuits.
+
+ Shapes:
+ x ... [b, s, t, ch]
+ t_h ... [b]
+ t_w ... [b]
+ c_emb ... [b, seq, c_emb_size]
+ U ... [b, 2, N, N]
+ rnd ... [b]
+ """
+
+ t_emb = self.t_emb(t_h) + self.t_emb2(t_w)
+ t_emb = t_emb.unsqueeze(1) # to [b, 1, ch]
+
+ #------
+
+ u_emb = self.unitary_encoder(y_emb=c_emb, U=U, penultimate=True).detach() # [batch, seq1+seq2, ch]
+
+ if not_exists(rnd):
+ # one means we dont drop, so U is not all zero
+ rnd = 1-torch.isclose(U, torch.zeros_like(U)).all(dim=(1, 2, 3)).type(torch.int64)
+ rnd = rnd.view(-1, 1, 1)
+
+ # Note: we ignore text drop and unitary drop, we replace all with a learned uncond token here
+ u_emb = u_emb * rnd + (1-rnd) * self.empty_cond.expand(u_emb.shape)
+
+ #------
+
+ x = self.main_pass(x, t_emb, u_emb)
+ return x
diff --git a/genQC/models/transformers.py b/genQC/models/transformers/transformers.py
similarity index 93%
rename from genQC/models/transformers.py
rename to genQC/models/transformers/transformers.py
index a27ed32..633e634 100644
--- a/genQC/models/transformers.py
+++ b/genQC/models/transformers/transformers.py
@@ -1,13 +1,13 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/models/transformers.ipynb.
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../src/models/transformers/transformers.ipynb.
# %% auto 0
__all__ = ['BasisSelfAttnBlock', 'BasisCrossAttnBlock', 'SpatialTransformerSelfAttn', 'SpatialTransformer']
-# %% ../../src/models/transformers.ipynb 2
-from ..imports import *
+# %% ../../../src/models/transformers/transformers.ipynb 2
+from ...imports import *
import genQC.models.layers as layers
-# %% ../../src/models/transformers.ipynb 4
+# %% ../../../src/models/transformers/transformers.ipynb 4
class BasisSelfAttnBlock(nn.Module):
"""A self attention block, i.e. a `transformer` encoder."""
def __init__(self, ch, num_heads, dropout=0):
@@ -32,7 +32,7 @@ def forward(self, x, attn_mask=None, key_padding_mask=None, need_weights=False):
return feed_out
-# %% ../../src/models/transformers.ipynb 5
+# %% ../../../src/models/transformers/transformers.ipynb 5
class BasisCrossAttnBlock(nn.Module):
"""A cross attention block, i.e. a `transformer` decoder."""
def __init__(self, ch, cond_emb_size, num_heads, dropout=0.0):
@@ -63,7 +63,7 @@ def forward(self, x, c_emb, attn_mask=None, key_padding_mask=None, need_weights=
return feed_out
-# %% ../../src/models/transformers.ipynb 7
+# %% ../../../src/models/transformers/transformers.ipynb 7
class SpatialTransformerSelfAttn(nn.Module):
"""A spatial residual `transformer`, only uses self-attention."""
def __init__(self, ch, num_heads, depth, dropout=0.0):
@@ -98,7 +98,7 @@ def forward(self, x, attn_mask=None, key_padding_mask=None):
return x + x_in
-# %% ../../src/models/transformers.ipynb 8
+# %% ../../../src/models/transformers/transformers.ipynb 8
class SpatialTransformer(nn.Module):
"""A spatial residual `transformer`, uses self- and cross-attention on conditional input."""
diff --git a/genQC/models/unet_qc.py b/genQC/models/unet_qc.py
index 228f8ce..e81c20f 100644
--- a/genQC/models/unet_qc.py
+++ b/genQC/models/unet_qc.py
@@ -6,9 +6,9 @@
# %% ../../src/models/unet_qc.ipynb 3
from ..imports import *
-from .config_model import Config_Model
+from .config_model import ConfigModel
import genQC.models.layers as layers
-import genQC.models.transformers as transformers
+import genQC.models.transformers.transformers as transformers
from .unitary_encoder import Unitary_encoder, Unitary_encoder_config
# %% ../../src/models/unet_qc.ipynb 5
@@ -19,7 +19,7 @@ def __init__(self, ch_in, ch_out, t_emb_size, cond_emb_size, num_heads=8, num_re
self.resBlocks = nn.ModuleList()
for i in range(num_res_blocks):
- self.resBlocks.append(layers.ResBlock2D_Conditional(ch_in, ch_out, t_emb_size, kernel_size=(1, 3)))
+ self.resBlocks.append(layers.ResBlock2DConditional(ch_in, ch_out, t_emb_size, kernel_size=(1, 3)))
ch_in = ch_out
self.transformer_depth = transformer_depth
@@ -111,14 +111,16 @@ class QC_Cond_UNet_config:
transformer_depths: list[int]
# %% ../../src/models/unet_qc.ipynb 10
-class QC_Cond_UNet(Config_Model):
+class QC_Cond_UNet(ConfigModel):
"""Conditional U-Net model for quantum circuits. Implemets `embedd_clrs` and `invert_clr` functions to embed and decode color-tensors."""
+
+ channel_last = False
def __init__(self, model_features=[32,32,64], clr_dim=8, num_clrs=8, t_emb_size=128, cond_emb_size=512,
num_heads=[8,8,2], num_res_blocks=[2, 2, 4], transformer_depths=[1,2,1]):
super().__init__()
-
+
self.clr_dim = clr_dim
self.num_clrs = num_clrs
@@ -160,7 +162,7 @@ def _init_weights(self):
#--------------------------------------------
- def embedd_clrs(self, x):
+ def embed(self, x):
sign = torch.sign(x + 0.1) #trick: add 0.1 so that the sign of 0 is +1, else the 0 token would be all 0s.
clr = self.emb_clr(torch.abs(x))
x = clr * sign[:, :, :, None]
@@ -168,7 +170,7 @@ def embedd_clrs(self, x):
return x
@torch.no_grad()
- def invert_clr(self, x):
+ def invert(self, x):
#collaps clr to gate ... use cos sim
clrs = self.emb_clr.weight.detach() # is [clr_num, clr_dim]
@@ -201,7 +203,7 @@ def invert_clr(self, x):
#--------------------------------------------
- def forward(self, x, t, c_emb, attn_mask=None, key_padding_mask=None):
+ def forward(self, x, t, c_emb, attn_mask=None, key_padding_mask=None, **kwargs):
if attn_mask is None: attn_mask = [None] * len(self.enc_chs)
if key_padding_mask is None: key_padding_mask = [None] * len(self.enc_chs)
@@ -235,8 +237,8 @@ def __init__(self, model_features=[32,32,64], clr_dim=8, num_clrs=8, t_emb_size=
self.unitary_encoder = Unitary_encoder(**unitary_encoder_config)
self.params_config = QC_Compilation_UNet_config(model_features, self.clr_dim, self.num_clrs, self.t_emb_size, self.cond_emb_size, num_heads, num_res_blocks, transformer_depths, self.unitary_encoder.params_config)
- def forward(self, x, t, c_emb, U, attn_mask=None, key_padding_mask=None):
+ def forward(self, x, t, c_emb, U, attn_mask=None, key_padding_mask=None, **kwargs):
u_emb = self.unitary_encoder(U) # [batch, seq2, ch]
c_emb = torch.cat([c_emb, u_emb], dim=1) # [batch, seq1+seq2, ch]
- out = super().forward(x, t, c_emb, attn_mask, key_padding_mask)
+ out = super().forward(x, t, c_emb, attn_mask, key_padding_mask, **kwargs)
return out
diff --git a/genQC/models/unitary_encoder.py b/genQC/models/unitary_encoder.py
index 0e60d62..12600a9 100644
--- a/genQC/models/unitary_encoder.py
+++ b/genQC/models/unitary_encoder.py
@@ -5,9 +5,9 @@
# %% ../../src/models/unitary_encoder.ipynb 2
from ..imports import *
-from .config_model import Config_Model
+from .config_model import ConfigModel
import genQC.models.layers as layers
-import genQC.models.transformers as transformers
+import genQC.models.transformers.transformers as transformers
# %% ../../src/models/unitary_encoder.ipynb 4
@dataclass
@@ -19,7 +19,7 @@ class Unitary_encoder_config:
dropout: float
# %% ../../src/models/unitary_encoder.ipynb 5
-class Unitary_encoder(Config_Model):
+class Unitary_encoder(ConfigModel):
"""Encoder for unitary conditions."""
def __init__(self, cond_emb_size, model_features=None, num_heads=8, transformer_depths=[4, 4], dropout=0.1):
super().__init__()
diff --git a/genQC/pipeline/callbacks.py b/genQC/pipeline/callbacks.py
new file mode 100644
index 0000000..78776f5
--- /dev/null
+++ b/genQC/pipeline/callbacks.py
@@ -0,0 +1,23 @@
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/pipeline/callbacks.ipynb.
+
+# %% auto 0
+__all__ = ['CancelFitException', 'CancelBatchException', 'CancelEpochException', 'Callback', 'run_cbs']
+
+# %% ../../src/pipeline/callbacks.ipynb 2
+from ..imports import *
+from operator import attrgetter, itemgetter
+
+# %% ../../src/pipeline/callbacks.ipynb 4
+class CancelFitException(Exception): pass
+class CancelBatchException(Exception): pass
+class CancelEpochException(Exception): pass
+
+# %% ../../src/pipeline/callbacks.ipynb 5
+class Callback(): order=0
+
+# %% ../../src/pipeline/callbacks.ipynb 6
+def run_cbs(cbs, method_nm, pipeline=None):
+ if not exists(cbs): return
+ for cb in sorted(cbs, key=attrgetter('order')):
+ method = getattr(cb, method_nm, None)
+ if method: method(pipeline)
diff --git a/genQC/pipeline/compilation_diffusion_pipeline.py b/genQC/pipeline/compilation_diffusion_pipeline.py
new file mode 100644
index 0000000..7b396e5
--- /dev/null
+++ b/genQC/pipeline/compilation_diffusion_pipeline.py
@@ -0,0 +1,104 @@
+"""Special extension to `DiffusionPipeline`."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/pipeline/compilation_diffusion_pipeline.ipynb.
+
+# %% auto 0
+__all__ = ['DiffusionPipeline_Compilation']
+
+# %% ../../src/pipeline/compilation_diffusion_pipeline.ipynb 2
+from ..imports import *
+from .diffusion_pipeline import DiffusionPipeline
+
+# %% ../../src/pipeline/compilation_diffusion_pipeline.ipynb 4
+class DiffusionPipeline_Compilation(DiffusionPipeline):
+ """A special `DiffusionPipeline` that accounts for unitary conditions, i.e. compilation."""
+
+ #------------------------------------
+
+ @torch.no_grad()
+ def __call__(self, latents, c, U, g, negative_c=None, negative_u=None, no_bar=False):
+
+ latents = latents.to(self.device)
+ c = c.to(self.device)
+ U = U.to(self.device)
+
+ return self.denoising(latents, c=c, U=U, negative_c=negative_c, negative_u=negative_u, enable_guidance=True, g=g, no_bar=no_bar)
+
+ #------------------------------------
+
+ def empty_unitary_fn(self, U):
+ # U ... [b , 2, n, n]
+
+ u = torch.zeros_like(U)
+ return u
+
+ def get_guidance_U(self, U: torch.Tensor, enable_guidance: bool = True, negative_u: Optional[torch.Tensor] = None):
+ if not exists(U): return U
+ U = U.to(self.device)
+ if enable_guidance:
+ if exists(negative_u): u = negative_u.to(self.device)
+ else: u = self.empty_unitary_fn(U).to(self.device)
+ U = torch.cat([u, U])
+ return U
+
+ @torch.no_grad()
+ def denoising(self, latents, c, U, negative_c=None, negative_u=None, enable_guidance=True, g=1.0, t_start_index=0, no_bar=False, return_predicted_x0=False):
+ U = self.get_guidance_U(U, enable_guidance, negative_u)
+ return super().denoising(latents, c, negative_c, enable_guidance, g, t_start_index=t_start_index,
+ no_bar=no_bar, return_predicted_x0=return_predicted_x0, U=U)
+
+ def denoising_step(self, latents: torch.Tensor, ts: Union[int, torch.IntTensor], c_emb: torch.Tensor=None, enable_guidance=False, g=7.5, U: torch.Tensor=None):
+ if enable_guidance:
+ x = torch.cat([latents] * 2) #uses batch layer combine here
+
+ if ts.numel() > 1: chunk_ts = torch.cat([ts] * 2)
+ else: chunk_ts = ts
+
+ eps_u, eps_c = self.model(x, chunk_ts, c_emb, U=U).chunk(2)
+
+ eps = self.CFG(eps_u, eps_c, g)
+
+ else:
+ eps = self.model(latents, ts, c_emb, U=U)
+
+ x = self.scheduler.step(eps, ts, latents)
+ return x.prev_sample, x.pred_original_sample
+
+ #------------------------------------
+
+ def train_step(self, data, train, **kwargs):
+ latents, y, U = data
+ b, s, t = latents.shape
+
+ #start async memcpy
+ latents = latents.to(self.device, non_blocking=self.non_blocking)
+ latents = self.embedder.embed(latents)
+
+ #do the cond embedding with CLIP
+ y = y.to(self.device, non_blocking=self.non_blocking)
+ U = U.to(self.device, non_blocking=self.non_blocking)
+
+ if self.enable_guidance_train and train:
+ rnd_y, rnd_U = torch.empty((2*b,), device=self.device).bernoulli_(p=1.0-self.guidance_train_p).type(torch.int64).chunk(2, dim=0)
+
+ y = self.cfg_drop(y, self.empty_token_fn(y) , rnd_y)
+ U = self.cfg_drop(U, self.empty_unitary_fn(U), rnd_U)
+
+
+ y_emb = self.text_encoder(y, pool=False)
+
+ #sample timesteps
+ timesteps = torch.randint(low=0, high=self.scheduler.num_train_timesteps, size=(b,), device=self.device, dtype=torch.int64)
+
+ #forward noising
+ noise = torch.randn(latents.shape, device=self.device)
+ noisy_latents = self.scheduler.add_noise(latents, noise, timesteps, train=train)
+
+ #predict eps
+ eps = self.model(noisy_latents, timesteps, y_emb, U=U)
+
+ #comp mse
+ loss = self.loss_fn(eps, noise)
+
+ #log the loss
+ return loss
diff --git a/genQC/pipeline/diffusion_pipeline.py b/genQC/pipeline/diffusion_pipeline.py
index d2fb1c7..68266c4 100644
--- a/genQC/pipeline/diffusion_pipeline.py
+++ b/genQC/pipeline/diffusion_pipeline.py
@@ -7,10 +7,8 @@
from ..imports import *
from ..scheduler.scheduler import Scheduler
from .pipeline import Pipeline
-from ..config_loader import *
-from ..models.config_model import Config_Model
-
-from huggingface_hub import snapshot_download
+from ..utils.config_loader import *
+from ..models.config_model import ConfigModel
# %% ../../src/pipeline/diffusion_pipeline.ipynb 3
class DiffusionPipeline(Pipeline):
@@ -21,6 +19,7 @@ def __init__(self,
scheduler: Scheduler,
model: nn.Module,
text_encoder: nn.Module,
+ embedder: nn.Module, # clr embeddings or a VAE for latent diffusion
device: torch.device,
enable_guidance_train = True,
guidance_train_p = 0.1,
@@ -28,20 +27,24 @@ def __init__(self,
):
super().__init__(model, device)
self.scheduler = scheduler
- self.scheduler.to_device(device)
+ self.scheduler.to(device)
self.text_encoder = text_encoder
- self.text_encoder.eval()
+ # self.text_encoder.eval()
+ self.trainables.append(self.text_encoder)
+
+ self.embedder = embedder
+ self.trainables.append(self.embedder)
self.enable_guidance_train = enable_guidance_train
self.guidance_train_p = guidance_train_p
self.cached_text_enc = cached_text_enc
self.empty_token = self.text_encoder.empty_token
-
+
if cached_text_enc:
def cached_empty_token_fn(c):
- if c.dim() == 1: return self.text_encoder.cached_empty_token_index # yields then a list of ints
+ if c.dim() == 1: return self.text_encoder.cached_empty_token_index.expand(c.shape) # yields then a list of ints
elif c.dim() == 2: return self.empty_token.expand(c.shape) # tokenized input
else: raise NotImplementedError("")
@@ -51,15 +54,16 @@ def cached_empty_token_fn(c):
self.empty_token_fn = lambda c: self.empty_token.expand(c.shape) # for own clip
#------------------------------------
-
+
add_config = {}
def params_config(self, save_path: str):
params_config = {}
params_config["scheduler"] = self.scheduler.get_config()
- params_config["model"] = self.model.get_config(save_path=save_path+"model.pt")
- params_config["text_encoder"] = self.text_encoder.get_config(save_path=save_path+"text_encoder.pt")
+ params_config["model"] = self.model.get_config(save_path=save_path+"model")
+ params_config["text_encoder"] = self.text_encoder.get_config(save_path=save_path+"text_encoder")
+ params_config["embedder"] = self.embedder.get_config(save_path=save_path+"embedder")
params_config["device"] = str(self.device)
params_config["enable_guidance_train"] = self.enable_guidance_train
@@ -75,57 +79,72 @@ def store_pipeline(self, config_path: str, save_path: str):
save_dict_yaml(config, config_path+"config.yaml")
#only store weights of these submodels
- self.model.store_model(config_path=None, save_path=save_path+"model.pt")
- self.text_encoder.store_model(config_path=None, save_path=save_path+"text_encoder.pt")
+ self.model.store_model(config_path=None, save_path=save_path+"model")
+ self.text_encoder.store_model(config_path=None, save_path=save_path+"text_encoder")
+ self.embedder.store_model(config_path=None, save_path=save_path+"embedder")
@staticmethod
- def from_config_file(config_path, device: torch.device):
+ def from_config_file(config_path, device: torch.device, save_path: Optional[str] = None):
config = load_config(config_path+"config.yaml")
config = config_to_dict(config)
+ def _get_save_path(config_save_path, appendix):
+ _save_path = default(save_path, config_path) + appendix
+ if "save_path" in config_save_path:
+ if exists(config_save_path["save_path"]):
+ _save_path = config_save_path["save_path"]
+ else:
+ config_save_path.pop("save_path")
+ return _save_path
+
if exists(device):
config["params"]["device"] = device
- config["params"]["scheduler"]["params"]["device"] = device
-
- config["params"]["scheduler"] = instantiate_from_config(config["params"]["scheduler"])
-
- model_path = config_path+"model.pt" if config["params"]["model"]["save_path"] is None else config["params"]["model"]["save_path"]
- config["params"]["model"] = Config_Model.from_config(config["params"]["model"], device, model_path)
- config["params"]["text_encoder"] = Config_Model.from_config(config["params"]["text_encoder"], device, config["params"]["text_encoder"]["save_path"])
+ config["params"]["scheduler"] = Scheduler.from_config(config["params"]["scheduler"], device, _get_save_path(config["params"]["scheduler"], ""))
+
+ config["params"]["model"] = ConfigModel.from_config(config["params"]["model"], device, _get_save_path(config["params"]["model"], "model"))
+ config["params"]["text_encoder"] = ConfigModel.from_config(config["params"]["text_encoder"], device, _get_save_path(config["params"]["text_encoder"], "text_encoder"))
+
+ if "embedder" in config["params"]:
+ config["params"]["embedder"] = ConfigModel.from_config(config["params"]["embedder"], device, _get_save_path(config["params"]["embedder"], "embedder"))
+ else:
+ config["params"]["embedder"] = config["params"]["model"] #for legacy loading model
+
add_config = config["params"].pop("add_config", None)
pipeline = instantiate_from_config(config)
if exists(pipeline.add_config):
- pipeline.gate_pool = [gate for gate in add_config["dataset"]["params"]["gate_pool"]]
pipeline.add_config = add_config
-
- return pipeline
-
+
+ params = add_config["dataset"]["params"]
+
+ if "gate_pool" in params:
+ # pipeline.gate_pool = [get_obj_from_str(gate) for gate in params["gate_pool"]]
+ pipeline.gate_pool = [gate for gate in params["gate_pool"]]
- @classmethod
- def from_pretrained(cls, repo_id: str, device: torch.device, **kwargs):
- """Load a model pipeline directly from Huggingface."""
- model_path = snapshot_download(repo_id=repo_id, repo_type="model", allow_patterns=["*.pt", "*.yaml", "*.safetensors"], **kwargs)
- pipeline = cls.from_config_file(model_path+"/", device)
return pipeline
-
+
#------------------------------------
# Inference functions
-
- @torch.no_grad()
- def __call__(self, latents=None, c=None, seed=None, timesteps=None, no_bar=False, enable_guidance=True, g=7.5):
+
+ # @torch.no_grad()
+ @torch.inference_mode()
+ def __call__(self, latents=None, c=None, negative_c=None, seed=None, timesteps=None, no_bar=False, enable_guidance=True, g=7.5, micro_cond=None):
if exists(seed): torch.manual_seed(seed)
if exists(timesteps): self.scheduler.set_timesteps(self.timesteps)
+
+ self.text_encoder.eval()
+ self.model.eval()
latents = latents.to(self.device)
- x0 = self.denoising(latents, c=c, no_bar=no_bar, enable_guidance=enable_guidance, g=g)
+ x0 = self.denoising(latents, c=c, negative_c=negative_c, no_bar=no_bar, enable_guidance=enable_guidance, g=g, micro_cond=micro_cond)
return x0
- @torch.no_grad()
- def latent_filling(self, org_latents: torch.Tensor, mask: torch.Tensor, c=None, enable_guidance=True, g=7.5,
+ # @torch.no_grad()
+ @torch.inference_mode()
+ def latent_filling(self, org_latents: torch.Tensor, mask: torch.Tensor, c=None, negative_c=None, enable_guidance=True, g=7.5,
t_start_index=0, no_bar=False, return_predicted_x0=False, **kwargs):
"""mask: area with ones is going to be filled"""
if mask.dim() == 4: assert list(org_latents.shape) == list(mask.shape) # diff mask per sample and channel
@@ -135,9 +154,9 @@ def latent_filling(self, org_latents: torch.Tensor, mask: torch.Tensor, c=None,
self.model.eval()
self.text_encoder.eval()
- self.scheduler.to_device(self.device)
+ self.scheduler.to(self.device)
- c_emb = self.prepare_c_emb(c, enable_guidance, **kwargs)
+ c_emb = self.prepare_c_emb(c, enable_guidance, negative_c, **kwargs)
org_latents = org_latents.to(self.device, non_blocking=self.non_blocking)
@@ -186,74 +205,82 @@ def latent_filling(self, org_latents: torch.Tensor, mask: torch.Tensor, c=None,
#------------------------------------
# Helper functions
- def get_guidance_condition(self, c, enable_guidance):
+ def get_guidance_condition(self, c: torch.Tensor, enable_guidance: bool = True, negative_c: Optional[torch.Tensor] = None):
if not exists(c): return c
c = c.to(self.device)
if enable_guidance:
- u = self.empty_token_fn(c).to(self.device)
+ if exists(negative_c): u = negative_c.to(self.device)
+ else: u = self.empty_token_fn(c).to(self.device)
c = torch.cat([u, c])
- c = c.type(torch.int64)
+ c = c.type(torch.int64) #to token dtype
return c
- def prepare_c_emb(self, c, enable_guidance, **kwargs):
- c = self.get_guidance_condition(c, enable_guidance)
+ def prepare_c_emb(self, c: torch.Tensor, enable_guidance: bool = True, negative_c: Optional[torch.Tensor] = None, **kwargs):
+ c = self.get_guidance_condition(c, enable_guidance, negative_c)
c_emb = self.text_encoder(c, pool=False)
return c_emb
- @torch.no_grad()
- def denoising(self, latents: torch.Tensor, c=None, enable_guidance=True, g=7.5, t_start_index=0, no_bar=False, return_predicted_x0=False, **kwargs):
+ # @torch.no_grad()
+ @torch.inference_mode()
+ def denoising(self, latents: torch.Tensor, c=None, negative_c=None, enable_guidance=True, g=7.5, t_start_index=0, no_bar=False,
+ return_predicted_x0=False, micro_cond=None, **kwargs):
self.model.eval()
self.text_encoder.eval()
- self.scheduler.to_device(self.device)
+ self.scheduler.to(self.device)
- c_emb = self.prepare_c_emb(c, enable_guidance, **kwargs)
+ c_emb = self.prepare_c_emb(c, enable_guidance, negative_c, **kwargs)
latents = latents.to(self.device, non_blocking=self.non_blocking)
if return_predicted_x0: predicted_x0 = list()
for i, t in enumerate(tqdm(self.scheduler.timesteps[t_start_index:], disable=no_bar)):
- timesteps = (torch.ones((1)) * t).type(torch.int64).to(self.device, non_blocking=self.non_blocking)
-
- latents, x0 = self.denoising_step(latents, timesteps, c_emb=c_emb, enable_guidance=enable_guidance, g=g, **kwargs)
-
- if return_predicted_x0: predicted_x0.append(x0.cpu())
-
- if return_predicted_x0: return latents.cpu(), predicted_x0
- return latents.cpu()
-
- # @torch.no_grad()
- def denoising_step(self, latents: torch.Tensor, ts: Union[int, torch.IntTensor], c_emb: torch.Tensor=None, enable_guidance=True, g=7.5, **kwargs):
+ timesteps = torch.tensor([t], device=self.device)
+
+ latents, x0 = self.denoising_step(latents, timesteps, c_emb=c_emb, enable_guidance=enable_guidance, g=g, micro_cond=micro_cond, **kwargs)
+
+ if return_predicted_x0:
+ predicted_x0.append(x0)
+
+ if return_predicted_x0:
+ predicted_x0 = torch.stack(predicted_x0, dim=0) # [timesteps, *latents.shape]
+ return latents, predicted_x0
+
+ return latents
+
+ def denoising_step(self, latents: torch.Tensor, ts: Union[int, torch.IntTensor], c_emb: torch.Tensor=None, enable_guidance=True, g=7.5, micro_cond=None, **kwargs):
if enable_guidance:
x = torch.cat([latents] * 2) #uses batch layer combine here
if ts.numel() > 1: chunk_ts = torch.cat([ts] * 2)
else: chunk_ts = ts
-
- eps_u, eps_c = self.model(x, chunk_ts, c_emb).chunk(2)
+
+ eps_u, eps_c = self.model(x, chunk_ts, c_emb, micro_cond=micro_cond).chunk(2)
eps = self.CFG(eps_u, eps_c, g)
-
+
+ x = self.scheduler.step(eps, ts, latents, uncond_model_output=eps_u)
+
else:
eps = self.model(latents, ts, c_emb)
-
- x = self.scheduler.step(eps, ts, latents)
+ x = self.scheduler.step(eps, ts, latents)
+
return x.prev_sample, x.pred_original_sample
- guidance_sample_mode = "rescaled" # one of: normal, fastai, rescaled
+ guidance_sample_mode = "normal" # one of: normal, fastai, rescaled
def CFG(self, eps_u, eps_c, g):
"""Apply Classifier-free-guidance sampling"""
dim = list(range(1, eps_u.dim())) # reduce all but batches
- if self.guidance_sample_mode == "normal": # from https://arxiv.org/pdf/2207.12598.pdf, w=g+1
+ if self.guidance_sample_mode == "normal": # from https://arxiv.org/pdf/2207.12598.pdf, w=g+1 s=g+1
eps = eps_u + g * (eps_c-eps_u)
elif self.guidance_sample_mode == "fastai": # from fastAi less 11
eps = eps_u + g*(eps_c-eps_u) * torch.linalg.vector_norm(eps_u, dim=dim, keepdim=True) / torch.linalg.vector_norm(eps_c-eps_u, dim=dim, keepdim=True)
eps = eps * torch.linalg.vector_norm(eps_u, dim=dim, keepdim=True) / torch.linalg.vector_norm(eps, dim=dim, keepdim=True)
- elif self.guidance_sample_mode == "rescaled": # from https://arxiv.org/pdf/2305.08891.pd
+ elif self.guidance_sample_mode == "rescaled": # from https://arxiv.org/pdf/2305.08891.pdf
phi = 0.7
eps_cfg = eps_u + g * (eps_c-eps_u)
@@ -267,36 +294,55 @@ def CFG(self, eps_u, eps_c, g):
#------------------------------------
# Training functions
+ def sample_timesteps_low_variance(self, b: int, scheduler: Scheduler, shuffle: bool = False, continuous_time: bool = False) -> torch.Tensor:
+ """Low variance sampling, see https://arxiv.org/abs/2406.07524 and originaly https://arxiv.org/abs/2107.00630."""
+
+ start = torch.linspace(0, 1.0-1.0/b, b, device=self.device, dtype=torch.float32)
+ ts = start + torch.rand_like(start) / b
+
+ if continuous_time:
+ ts = ts.clamp(0., 1.)
+ else:
+ ts = (ts * scheduler.num_train_timesteps).floor().clamp(0, scheduler.num_train_timesteps-1).to(torch.int64)
+
+ if shuffle:
+ return ts[torch.randperm(b)]
+ return ts
+
def train_on_epoch(self, data_loader: DataLoader, train=True):
- self.scheduler.to_device(self.device, non_blocking=self.non_blocking)
+ self.scheduler.to(self.device, non_blocking=self.non_blocking)
super().train_on_epoch(data_loader, train)
- #@torch.autocast(device_type=device.type)
- def train_step(self, data, **kwargs):
+ def cfg_drop(self, y, y_drop, rnd):
+ """A value of `rnd` one means we take `y`. A value of `rnd` zero means we drop `y` and use `empty_token_fn`."""
+ rnd = self.scheduler.unsqueeze_vector_to_shape(rnd, y.shape) # e.g. [b, 1, 1]
+ y = y * rnd + (1-rnd) * y_drop
+ return y
+
+ def train_step(self, data, train, **kwargs):
latents, y = data
b, s, t = latents.shape
#start async memcpy
latents = latents.to(self.device, non_blocking=self.non_blocking)
- latents = self.model.embedd_clrs(latents) #this is only new tensor
-
+ latents = self.embedder.embed(latents)
+
#do the cond embedding with CLIP
y = y.to(self.device, non_blocking=self.non_blocking)
+ U = U.to(self.device, non_blocking=self.non_blocking)
- if self.enable_guidance_train:
- rnd = torch.rand((b,), device=self.device)
- rnd = (rnd > self.guidance_train_p).type(torch.int64) # todo: change to bernoulli dist fn
- rnd = self.scheduler.unsqueeze_vector_to_shape(rnd, y.shape) # e.g. [b, 1, 1]
- y = y * rnd + (1-rnd) * self.empty_token_fn(y)
-
+ if self.enable_guidance_train and train:
+ rnd_y = torch.empty((b,), device=self.device).bernoulli_(p=1.0-self.guidance_train_p).type(torch.int64)
+ y = self.cfg_drop(y, self.empty_token_fn(y), rnd_y)
+
y_emb = self.text_encoder(y, pool=False)
-
+
#sample timesteps
timesteps = torch.randint(low=0, high=self.scheduler.num_train_timesteps, size=(b,), device=self.device, dtype=torch.int64)
#forward noising
noise = torch.randn(latents.shape, device=self.device)
- noisy_latents = self.scheduler.add_noise(latents, noise, timesteps)
+ noisy_latents = self.scheduler.add_noise(latents, noise, timesteps, train=train)
#predict eps
eps = self.model(noisy_latents, timesteps, y_emb)
diff --git a/genQC/pipeline/diffusion_pipeline_special.py b/genQC/pipeline/diffusion_pipeline_special.py
index cbec969..6ef189c 100644
--- a/genQC/pipeline/diffusion_pipeline_special.py
+++ b/genQC/pipeline/diffusion_pipeline_special.py
@@ -1,60 +1,12 @@
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/pipeline/diffusion_pipeline_special.ipynb.
# %% auto 0
-__all__ = ['DiffusionPipeline_attnPadded', 'DiffusionPipeline_Compilation']
+__all__ = ['DiffusionPipeline_Compilation']
# %% ../../src/pipeline/diffusion_pipeline_special.ipynb 2
from ..imports import *
from .diffusion_pipeline import DiffusionPipeline
-# %% ../../src/pipeline/diffusion_pipeline_special.ipynb 3
-class DiffusionPipeline_attnPadded(DiffusionPipeline):
- """A special `DiffusionPipeline` with attention masking."""
- def train_step(self, data, **kwargs):
- latents, y, key_padding_mask_list = data
- b, s, t = latents.shape
-
- #start async memcpy
- loss_mask = (key_padding_mask_list[0].to(self.device, non_blocking=self.non_blocking)>-1.0).float().unsqueeze(1)
-
- shaped_mask = []
- for key_padding_mask in key_padding_mask_list:
- key_padding_mask = key_padding_mask.to(self.device, non_blocking=self.non_blocking)
- key_padding_mask = key_padding_mask.reshape((b, -1)) #from [b, s, t] to [b, -1] aka [N, L]
- shaped_mask.append(key_padding_mask)
-
- latents = latents.to(self.device, non_blocking=self.non_blocking)
- latents = self.model.embedd_clrs(latents) #this is only new tensor
- self.scheduler.to_device(self.device, non_blocking=self.non_blocking)
-
- #do the cond embedding with CLIP
- y = y.to(self.device, non_blocking=self.non_blocking)
-
- if self.enable_guidance_train:
- rnd = torch.rand((b,), device=self.device)
- rnd = (rnd > self.guidance_train_p).type(torch.int64) # todo: change to bernoulli dist fn
- rnd = self.scheduler.unsqueeze_vector_to_shape(rnd, y.shape) # e.g. [b, 1, 1]
- y = y * rnd + (1-rnd) * self.empty_token_fn(y)
-
- y_emb = self.text_encoder(y, pool=False)
-
- #sample timesteps
- timesteps = torch.randint(low=0, high=self.scheduler.num_train_timesteps, size=(b,), device=self.device, dtype=torch.int64)
-
- #forward noising
- noise = torch.randn(latents.shape, device=self.device)
- noisy_latents = self.scheduler.add_noise(latents, noise, timesteps)
-
- #predict eps
- eps = self.model(noisy_latents, timesteps, y_emb, key_padding_mask=shaped_mask)
-
- #comp mse
- loss = self.loss_fn(eps*loss_mask, noise*loss_mask)
- # loss = self.loss_fn(eps, noise)
-
- #log the loss
- return loss
-
# %% ../../src/pipeline/diffusion_pipeline_special.ipynb 4
class DiffusionPipeline_Compilation(DiffusionPipeline):
"""A special `DiffusionPipeline` that accounts for unitary conditions, i.e. compilation."""
@@ -82,12 +34,12 @@ def get_guidance_U(self, U, enable_guidance):
return U
@torch.no_grad()
- def denoising(self, latents, c, U, enable_guidance, g, no_bar=False, return_predicted_x0=False):
+ def denoising(self, latents, c, U, enable_guidance=True, g=0, no_bar=False, return_predicted_x0=False):
U = self.get_guidance_U(U, enable_guidance)
# self.unitary_encoder.eval()
- return super().denoising(latents, c, enable_guidance, g, no_bar=no_bar, return_predicted_x0=return_predicted_x0, U=U)
+ return super().denoising(latents, c, enable_guidance=enable_guidance, g=g, no_bar=no_bar, return_predicted_x0=return_predicted_x0, U=U)
- def denoising_step(self, latents: torch.Tensor, ts: Union[int, torch.IntTensor], c_emb: torch.Tensor=None, enable_guidance=False, g=7.5, U: torch.Tensor=None):
+ def denoising_step(self, latents: torch.Tensor, ts: Union[int, torch.IntTensor], c_emb: torch.Tensor=None, enable_guidance=False, g=7.5, U: torch.Tensor=None, micro_cond=None):
if enable_guidance:
x = torch.cat([latents] * 2) #uses batch layer combine here
diff --git a/genQC/metrics.py b/genQC/pipeline/metrics.py
similarity index 52%
rename from genQC/metrics.py
rename to genQC/pipeline/metrics.py
index 918db94..b10ca4d 100644
--- a/genQC/metrics.py
+++ b/genQC/pipeline/metrics.py
@@ -1,49 +1,58 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../src/metrics.ipynb.
+"""Definition of metrics used during training."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/pipeline/metrics.ipynb.
# %% auto 0
__all__ = ['Metric', 'Mean', 'Accuracy']
-# %% ../src/metrics.ipynb 3
-from .imports import *
-from .util import virtual
+# %% ../../src/pipeline/metrics.ipynb 2
+from ..imports import *
-# %% ../src/metrics.ipynb 4
-class Metric:
- """Base metric class."""
+# %% ../../src/pipeline/metrics.ipynb 3
+class Metric(abc.ABC):
+ """Base metric class."""
def __init__(self, name: str, device):
self.name = name
self.device = torch.device(device)
- self.reset_state()
+ self.reset_state()
def __repr__(self): return f"{self.name}={self.result()}"
-
def update_state(self, inp, tar=None): self.empty=False
def reset_state(self): self.empty=True
- @virtual
+ @abc.abstractmethod
def _eval(self, inp, tar): pass
- @virtual
+
+ @abc.abstractmethod
def result(self): pass
-# %% ../src/metrics.ipynb 5
+# %% ../../src/pipeline/metrics.ipynb 4
class Mean(Metric):
- """Mean metric, used for loss .."""
- def __init__(self, name: str, device): super().__init__(name, device)
+ """Mean metric, used for loss."""
+
+ def __init__(self, name: str, device):
+ super().__init__(name, device)
+
@torch.inference_mode()
- def update_state(self, inp: torch.Tensor, tar: torch.Tensor=None, weight: float=1):
+ def update_state(self, inp: torch.Tensor, tar: torch.Tensor = None, weight: float = 1):
super().update_state(inp, tar)
val = self._eval(inp, tar)
self.weighted_sum += torch.sum(val * weight)
- self.weight += weight * torch.numel(val)
+ self.weight += weight * torch.numel(val)
+
@torch.inference_mode()
def reset_state(self):
super().reset_state()
self.weighted_sum = torch.tensor(0.0, device=self.device)
self.weight = torch.tensor(0.0, device=self.device)
- def _eval(self, inp, tar): return inp
+
+ def _eval(self, inp, tar):
+ return inp
+
@torch.inference_mode()
- def result(self): return (self.weighted_sum/self.weight).cpu()
+ def result(self):
+ return (self.weighted_sum/self.weight).cpu()
-# %% ../src/metrics.ipynb 6
+# %% ../../src/pipeline/metrics.ipynb 5
class Accuracy(Mean):
"""Accuracy metric."""
@torch.inference_mode()
diff --git a/genQC/pipeline/multimodal_diffusion_pipeline.py b/genQC/pipeline/multimodal_diffusion_pipeline.py
new file mode 100644
index 0000000..ea7d27e
--- /dev/null
+++ b/genQC/pipeline/multimodal_diffusion_pipeline.py
@@ -0,0 +1,410 @@
+"""Multimodal extension to `DiffusionPipeline`."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/pipeline/multimodal_diffusion_pipeline.ipynb.
+
+# %% auto 0
+__all__ = ['MultimodalDiffusionPipeline_ParametrizedCompilation']
+
+# %% ../../src/pipeline/multimodal_diffusion_pipeline.ipynb 2
+from ..imports import *
+from .compilation_diffusion_pipeline import DiffusionPipeline_Compilation
+
+from ..scheduler.scheduler import Scheduler
+from ..utils.config_loader import *
+from ..models.config_model import ConfigModel
+
+# %% ../../src/pipeline/multimodal_diffusion_pipeline.ipynb 4
+class MultimodalDiffusionPipeline_ParametrizedCompilation(DiffusionPipeline_Compilation):
+ """A special `DiffusionPipeline_Compilation` that accounts for multimodal parametrized gates."""
+
+ def __init__(self, *args, scheduler_w, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.scheduler_w = scheduler_w
+ self.scheduler_w.to(self.device)
+
+ def params_config(self, *args, **kwargs):
+ params_config = super().params_config(*args, **kwargs)
+ params_config["scheduler_w"] = self.scheduler_w.get_config()
+ return params_config
+
+ @staticmethod
+ def from_config_file(config_path, device: torch.device, save_path: Optional[str] = None):
+ config = load_config(config_path+"config.yaml")
+ config = config_to_dict(config)
+
+ def _get_save_path(config_save_path, appendix):
+
+ _save_path = default(save_path, config_path) + appendix
+ if "save_path" in config_save_path:
+ if exists(config_save_path["save_path"]):
+ _save_path = config_save_path["save_path"]
+ else:
+ config_save_path.pop("save_path")
+ return _save_path
+
+ if exists(device):
+ config["params"]["device"] = device
+ config["params"]["scheduler"]["params"]["device"] = device
+
+ config["params"]["scheduler"] = Scheduler.from_config(config["params"]["scheduler"] , device, _get_save_path(config["params"]["scheduler"] , ""))
+ config["params"]["scheduler_w"] = Scheduler.from_config(config["params"]["scheduler_w"], device, _get_save_path(config["params"]["scheduler_w"], ""))
+
+ config["params"]["model"] = ConfigModel.from_config(config["params"]["model"], device, _get_save_path(config["params"]["model"], "model"))
+ config["params"]["text_encoder"] = ConfigModel.from_config(config["params"]["text_encoder"], device, _get_save_path(config["params"]["text_encoder"], "text_encoder"))
+ config["params"]["embedder"] = ConfigModel.from_config(config["params"]["embedder"], device, _get_save_path(config["params"]["embedder"], "embedder"))
+
+ add_config = config["params"].pop("add_config", None)
+
+ pipeline = instantiate_from_config(config)
+
+ if exists(pipeline.add_config):
+ pipeline.add_config = add_config
+
+ params = add_config["dataset"]["params"]
+
+ if "gate_pool" in params:
+ # pipeline.gate_pool = [get_obj_from_str(gate) for gate in params["gate_pool"]]
+ pipeline.gate_pool = [gate for gate in params["gate_pool"]]
+
+ return pipeline
+
+ #------------------------------------
+
+ # @torch.no_grad()
+ @torch.inference_mode()
+ def denoising(self, latents, c, U, negative_c=None, negative_u=None, enable_guidance=True, g=1.0, t_start_index=0, no_bar=False, return_predicted_x0=False):
+ return super().denoising(latents=latents, c=c, U=U, negative_c=negative_c, negative_u=negative_u, enable_guidance=enable_guidance, g=g, t_start_index=t_start_index,
+ no_bar=no_bar, return_predicted_x0=return_predicted_x0)
+
+ #------------------------------------
+
+ sample_type = "joint"
+
+ def denoising_step(self,
+ latents: torch.Tensor,
+ ts: Union[int, torch.IntTensor],
+ c_emb: torch.Tensor = None,
+ enable_guidance = False,
+ g: float = 7.5,
+ U: torch.Tensor = None,
+ **kwargs) -> Tuple[torch.Tensor, torch.Tensor]:
+
+ match self.sample_type:
+ case "joint":
+ x_tm1, x0 = self.denoising_step_joint(latents, ts, c_emb, enable_guidance, g, U)
+
+ case "w":
+ # Here the single mode denoising functions
+ x_tm1, x0 = self.denoising_step_single_mode_w(latents, ts, c_emb, enable_guidance, g, U)
+
+ case _:
+ raise NotImplementedError("")
+
+ return x_tm1, x0
+
+ #------------------------------------
+ # Cleaned steps
+
+ def _get_guidance_scales(self, g: float, ts_h: torch.Tensor, ts_w: torch.Tensor):
+ g_h , g_w = g, g
+ lambda_h, lambda_w = g, g
+
+ if hasattr(self, "g_h"):
+ if isinstance(self.g_h, Callable):
+ assert ts_h.numel() == 1
+ g_h = self.g_h(ts_h)
+ else:
+ g_h = self.g_h
+
+ if hasattr(self, "g_w"):
+ if isinstance(self.g_w, Callable):
+ assert ts_w.numel() == 1
+ g_w = self.g_w(ts_w)
+ else:
+ g_w = self.g_w
+
+ if hasattr(self, "lambda_h"):
+ if isinstance(self.lambda_h, Callable):
+ assert ts_h.numel() == 1
+ lambda_h = self.lambda_h(ts_h)
+ else:
+ lambda_h = self.lambda_h
+
+ if hasattr(self, "lambda_w"):
+ if isinstance(self.lambda_w, Callable):
+ assert ts_w.numel() == 1
+ lambda_w = self.lambda_w(ts_w)
+ else:
+ lambda_w = self.lambda_w
+
+ return g_h, g_w, lambda_h, lambda_w
+
+ def denoising_step_joint(self,
+ latents: torch.Tensor,
+ ts: Union[int, torch.IntTensor],
+ c_emb: torch.Tensor = None,
+ enable_guidance = False,
+ g: float = 7.5,
+ U: torch.Tensor = None,
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
+
+ # Prepare variables
+ g_h, g_w, lambda_h, lambda_w = self._get_guidance_scales(g, ts_h=ts, ts_w=ts)
+
+ # assert enable_guidance
+ c_emb_u, c_emb_c = c_emb.chunk(2)
+ U_u , U_c = U.chunk(2)
+
+ ts_expanded = ts.expand(latents.shape[0])
+ T_h_expanded = torch.ones_like(ts_expanded) * (self.scheduler.num_train_timesteps-1)
+ T_w_expanded = torch.ones_like(ts_expanded) * (self.scheduler_w.num_train_timesteps-1)
+
+ # Get latents of modes
+ noisy_latents = torch.randn_like(latents)
+ latents_h, latents_w = latents[..., :self.embedder.clr_dim], latents[..., self.embedder.clr_dim:]
+ noisy_latents_h, noisy_latents_w = noisy_latents[..., :self.embedder.clr_dim], noisy_latents[..., self.embedder.clr_dim:]
+
+ # Get all combinations
+ latents_chunked_h = torch.cat([
+ latents_h, # sh_h
+ latents_h, # sh_hw
+ latents_h, # sh_hwc
+
+ noisy_latents_h, # sw_w
+ latents_h, # sw_hw
+ latents_h, # sw_hwc
+ ])
+
+ latents_chunked_w = torch.cat([
+ noisy_latents_w, # sh_h
+ latents_w, # sh_hw
+ latents_w, # sh_hwc
+
+ latents_w, # sw_w
+ latents_w, # sw_hw
+ latents_w, # sw_hwc
+ ])
+
+ t_h_chunked = torch.cat([
+ ts_expanded, # sh_h
+ ts_expanded, # sh_hw
+ ts_expanded, # sh_hwc
+
+ T_h_expanded, # sw_w
+ ts_expanded, # sw_hw
+ ts_expanded, # sw_hwc
+ ])
+
+ t_w_chunked = torch.cat([
+ T_w_expanded, # sh_h
+ ts_expanded, # sh_hw
+ ts_expanded, # sh_hwc
+
+ ts_expanded, # sw_w
+ ts_expanded, # sw_hw
+ ts_expanded, # sw_hwc
+ ])
+
+ c_emb_chunked = torch.cat([
+ c_emb_u, # sh_h
+ c_emb_u, # sh_hw
+ c_emb_c, # sh_hwc
+
+ c_emb_u, # sw_w
+ c_emb_u, # sw_hw
+ c_emb_c, # sw_hwc
+ ])
+
+ U_chunked = torch.cat([
+ U_u, # sh_h
+ U_u, # sh_hw
+ U_c, # sh_hwc
+
+ U_u, # sw_w
+ U_u, # sw_hw
+ U_c, # sw_hwc
+ ])
+
+ # Make all predictions we need
+ latents_chunked = torch.cat([latents_chunked_h, latents_chunked_w], dim=-1)
+
+ pred = self.model(latents_chunked, t_h=t_h_chunked, t_w=t_w_chunked, c_emb=c_emb_chunked, U=U_chunked)
+ pred_h, pred_w = pred[..., :self.embedder.clr_dim], pred[..., self.embedder.clr_dim:]
+
+ sh_h, sh_hw, sh_hwc, _, _, _ = pred_h.chunk(6)
+ _, _, _, sw_w, sw_hw, sw_hwc = pred_w.chunk(6)
+
+ # Combine into CFG
+ sh_bar = sh_h + g_h * (sh_hw - sh_h) + lambda_h * (sh_hwc - sh_hw)
+ sw_bar = sw_w + g_w * (sw_hw - sw_w) + lambda_w * (sw_hwc - sw_hw)
+
+ # Do denoise step with CFG++
+ x_h = self.scheduler.step(sh_bar, ts, latents_h, uncond_model_output=sh_h)
+ x_w = self.scheduler_w.step(sw_bar, ts, latents_w, uncond_model_output=sw_w)
+
+ return torch.cat([x_h.prev_sample, x_w.prev_sample], dim=-1), torch.cat([x_h.pred_original_sample, x_w.pred_original_sample], dim=-1)
+
+ #------------------------------------
+
+ def denoising_step_single_mode_w(self,
+ latents: torch.Tensor,
+ ts: Union[int, torch.IntTensor],
+ c_emb: torch.Tensor = None,
+ enable_guidance = False,
+ g: float = 7.5,
+ U: torch.Tensor = None
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
+
+ assert enable_guidance # TODO: remove this
+
+ chunk_latents = torch.cat([latents] * 2, dim=0)
+
+ if ts.numel() > 1: chunk_ts = torch.cat([ts] * 2, dim=0)
+ else: chunk_ts = ts
+
+ T = torch.ones_like(chunk_ts) * (self.scheduler.num_train_timesteps-1)
+ TZero = torch.zeros_like(chunk_ts)
+
+ #------------------------
+ # 1. Get: s(h|w), s(w|h) and s(h|w,c), s(w|h,c)
+ # Note here we set t_h=0
+
+ def f1(chunk_latents, chunk_ts):
+ x = chunk_latents.clone()
+
+ s_hw, s_hwc = self.model(x, t_h=TZero, t_w=chunk_ts, c_emb=c_emb, U=U).chunk(2)
+
+ sw_hw, sw_hwc = s_hw[..., self.embedder.clr_dim:], s_hwc[..., self.embedder.clr_dim:]
+
+ return sw_hw, sw_hwc
+
+ #------------------------
+ # 2. Get: s(w), s(w|c)
+
+ def f2(chunk_latents, chunk_ts):
+ x = chunk_latents.clone()
+ x[..., :self.embedder.clr_dim] = torch.randn_like(x[..., :self.embedder.clr_dim]) #remove h
+
+ s_w, s_wc = self.model(x, t_h=T, t_w=chunk_ts, c_emb=c_emb, U=U).chunk(2)
+
+ sw_w, sw_wc = s_w[..., self.embedder.clr_dim:], s_wc[..., self.embedder.clr_dim:]
+
+ return sw_w, sw_wc
+
+ #------------------------------------------------
+
+ sw_hw, sw_hwc = f1(chunk_latents, chunk_ts)
+ sw_w, sw_wc = f2(chunk_latents, chunk_ts)
+
+ g_w = g
+
+ if hasattr(self, "g_w"):
+ if isinstance(self.g_w, Callable):
+ assert ts.numel() == 1
+ g_w = self.g_w(chunk_ts)
+ else:
+ g_w = self.g_w
+
+ gamma_w = g_w #was no/2
+ lambda_w = g_w
+
+ if hasattr(self, "lambda_w"):
+ if isinstance(self.lambda_w, Callable):
+ assert ts.numel() == 1
+ lambda_w = self.lambda_w(chunk_ts)
+ else:
+ lambda_w = self.lambda_w
+
+ sw_bar = sw_w + gamma_w * (sw_hw - sw_w) + lambda_w * (sw_hwc - sw_hw)
+
+ latents_h, latents_w = latents[..., :self.embedder.clr_dim], latents[..., self.embedder.clr_dim:]
+
+ #CFG++
+ x_h = latents_h
+ x_w = self.scheduler_w.step(sw_bar, ts, latents_w, uncond_model_output=sw_w)
+
+ return torch.cat([x_h, x_w.prev_sample], dim=-1), torch.cat([x_h, x_w.pred_original_sample], dim=-1)
+
+ #------------------------------------
+
+ def train_step(self, data, train, **kwargs):
+ target_tokens, y, params, U = data
+ b, s, t = target_tokens.shape
+
+ #start async memcpy
+ target_tokens = target_tokens.to(self.device, non_blocking=self.non_blocking)
+ params = params.to(self.device, non_blocking=self.non_blocking)
+
+ latents = self.embedder(h=target_tokens, w=params)
+
+ #do the cond embedding with CLIP
+ U = U.to(torch.float32)
+
+ y = y.to(self.device, non_blocking=self.non_blocking)
+ U = U.to(self.device, non_blocking=self.non_blocking)
+
+ if self.enable_guidance_train and train: #CFG training
+ rnd = torch.empty((b,), device=self.device).bernoulli_(p=1.0-self.guidance_train_p).type(torch.int64)
+
+ y_drop = self.cfg_drop(y, self.empty_token_fn(y) , rnd)
+ U_drop = self.cfg_drop(U, self.empty_unitary_fn(U), rnd)
+
+ else:
+ rnd = torch.ones((b,), dtype=torch.int64, device=self.device)
+ y_drop, U_drop = y, U
+
+ y_emb = self.text_encoder(y_drop, pool=False)
+
+ #--------------------
+
+ shuffle = torch.tensor(0, dtype=bool).bernoulli_(p=0.95)
+
+ timesteps_h = self.sample_timesteps_low_variance(b, self.scheduler)
+ timesteps_w = self.sample_timesteps_low_variance(b, self.scheduler_w, shuffle=shuffle)
+
+
+ noise = torch.randn_like(latents)
+ noisy_latents_h = self.scheduler.add_noise( latents[..., :self.embedder.clr_dim], noise[..., :self.embedder.clr_dim], timesteps_h, train=train)
+ noisy_latents_w = self.scheduler_w.add_noise(latents[..., self.embedder.clr_dim:], noise[..., self.embedder.clr_dim:], timesteps_w, train=train)
+
+ noisy_latents = torch.cat([noisy_latents_h, noisy_latents_w], dim=-1)
+
+ #--------------------
+ model_output = self.model(x=noisy_latents, t_h=timesteps_h, t_w=timesteps_w, c_emb=y_emb, U=U_drop, rnd=rnd)
+
+ #--------------------
+
+ if self.scheduler.prediction_type == "epsilon":
+ pred_target = noise
+ raise NotImplementedError()
+
+ elif self.scheduler.prediction_type == "v-type":
+ alphas_cumprod_h = self.scheduler.unsqueeze_vector_to_shape(self.scheduler.alphas_cumprod[timesteps_h], latents.shape)
+ alphas_cumprod_w = self.scheduler_w.unsqueeze_vector_to_shape(self.scheduler_w.alphas_cumprod[timesteps_w], latents.shape)
+
+ pred_target_h = alphas_cumprod_h.sqrt() * noise[..., :self.embedder.clr_dim] - (1-alphas_cumprod_h).sqrt() * latents[..., :self.embedder.clr_dim]
+ pred_target_w = alphas_cumprod_w.sqrt() * noise[..., self.embedder.clr_dim:] - (1-alphas_cumprod_w).sqrt() * latents[..., self.embedder.clr_dim:]
+
+ else:
+ raise NotImplementedError(f"{self.scheduler.prediction_type} does is not implemented for {self.__class__}")
+
+ #--------------------
+
+ t_h = timesteps_h / (self.scheduler.num_train_timesteps-1)
+ # t_h = torch.sin(t_h*(torch.pi/2))**2
+ # t_h = torch.sin(t_h*(torch.pi/2))
+ # -> else linear
+
+ t_h = self.scheduler.unsqueeze_vector_to_shape(t_h, latents.shape)
+ SNR_h = (1.0-t_h) / (t_h+1e-8) + 1e-8 # flip prob to snr
+ mse_loss_weight_h = (1.0 - alphas_cumprod_h) * F.sigmoid(SNR_h.log())
+
+ SNR_w = alphas_cumprod_w / (1.0-alphas_cumprod_w+1e-8) + 1e-8
+
+ #comp mse
+ mse_flat = lambda out, target: (out-target).square().mean(dim=list(range(1, len(out.shape))))
+ loss_h = mse_flat(model_output[..., :self.embedder.clr_dim], pred_target_h.detach()) * mse_loss_weight_h.squeeze().detach()
+ loss_w = mse_flat(model_output[..., self.embedder.clr_dim:], pred_target_w.detach()) * mse_loss_weight_w.squeeze().detach()
+
+ loss = loss_h.mean() + loss_w.mean()
+ return loss
diff --git a/genQC/pipeline/pipeline.py b/genQC/pipeline/pipeline.py
index cab0d87..54c4e0b 100644
--- a/genQC/pipeline/pipeline.py
+++ b/genQC/pipeline/pipeline.py
@@ -1,20 +1,39 @@
+"""Basic PyTorch pipeline for general training."""
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/pipeline/pipeline.ipynb.
# %% auto 0
-__all__ = ['Pipeline_IO', 'Pipeline']
+__all__ = ['Loss', 'CheckpointCB', 'PipelineIO', 'Pipeline']
-# %% ../../src/pipeline/pipeline.ipynb 3
+# %% ../../src/pipeline/pipeline.ipynb 2
from ..imports import *
-from ..util import virtual, number_of_paramters, DataLoaders
-from ..metrics import *
-from ..config_loader import *
+from ..utils.misc_utils import DataLoaders
+from .metrics import *
+from ..utils.config_loader import *
+from .callbacks import run_cbs, Callback
+
+from huggingface_hub import snapshot_download
+
+# %% ../../src/pipeline/pipeline.ipynb 4
+Loss = Callable[[torch.Tensor, torch.Tensor], torch.Tensor]
# %% ../../src/pipeline/pipeline.ipynb 5
-nn.Loss = Callable[[torch.Tensor, torch.Tensor], torch.Tensor]
+class CheckpointCB(Callback):
+ def __init__(self, ck_interval=None, ck_path=None):
+ super().__init__()
+ self.ck_interval = ck_interval
+ self.ck_path = ck_path
+
+ def after_epoch(self, pipeline):
+ if exists(self.ck_interval) and exists(self.ck_path):
+ if (pipeline.epoch%self.ck_interval) == 0 and pipeline.epoch>0:
+ store_dir = f"{self.ck_path}ck_{pipeline.epoch}/"
+ pipeline.store_pipeline(config_path=store_dir, save_path=store_dir)
-# %% ../../src/pipeline/pipeline.ipynb 8
-class Pipeline_IO:
+# %% ../../src/pipeline/pipeline.ipynb 7
+class PipelineIO(abc.ABC):
"""A class providing basic IO functionality."""
+
def get_config(self, save_path: str, without_metadata=False):
params_config = self.params_config(save_path)
@@ -40,9 +59,10 @@ def get_config(self, save_path: str, without_metadata=False):
self.config = config
return config
- @virtual
+ @abc.abstractmethod
def params_config(self, save_path: str): return None
-
+
+ @abc.abstractmethod
def store_pipeline(self, config_path: str, save_path: str):
if exists(config_path): os.makedirs(config_path, exist_ok=True)
if exists(save_path):
@@ -50,42 +70,67 @@ def store_pipeline(self, config_path: str, save_path: str):
if hasattr(self, "fit_losses"): np.savetxt(save_path + "fit_losses.txt", self.fit_losses)
if hasattr(self, "fit_valid_losses"): np.savetxt(save_path + "fit_valid_losses.txt", self.fit_valid_losses)
- @virtual
@staticmethod
+ @abc.abstractmethod
def from_config_file(config_path, device: torch.device, save_path: str=None): return None
-# %% ../../src/pipeline/pipeline.ipynb 9
-class Pipeline(Pipeline_IO):
- """A `Pipeline_IO` class providing basic pytorch model training functionality."""
+ @classmethod
+ def from_pretrained(cls, repo_id: str, device: torch.device, use_auth_token: bool = False, **kwargs):
+ """Load a model pipeline directly from Huggingface."""
+ model_path = snapshot_download(repo_id=repo_id, repo_type="model", allow_patterns=["*.pt", "*.yaml", "*.safetensors"], use_auth_token=use_auth_token, **kwargs)
+ pipeline = cls.from_config_file(model_path+"/", device)
+ return pipeline
+
+# %% ../../src/pipeline/pipeline.ipynb 8
+class Pipeline(PipelineIO):
+ """A `PipelineIO` class providing basic pytorch model training functionality."""
def __init__(self,
model: nn.Module,
device: torch.device):
- self.model = model
+ self.model = model.to(device)
self.device = device
-
+
+ self.trainables = []
+ self.trainables.append(self.model)
+
#------------------------------------
- @virtual
+ @abc.abstractmethod
def __call__(self, inp): pass
- @virtual
+ @abc.abstractmethod
def train_step(self, data, train=True, **kwargs): pass
#------------------------------------
-
- def compile(self, optim_fn: type(torch.optim.Optimizer), loss_fn: nn.Loss, metrics: Union[Metric, list[Metric]]=None, lr=None, **kwargs):
+
+ def _get_parameters(self):
+ parameters = itertools.chain(*[trainable.parameters() for trainable in self.trainables])
+ return parameters
+
+ def compile(self, optim_fn: type(torch.optim.Optimizer), loss_fn: Loss, metrics: Union[Metric, list[Metric]]=None, lr=None, cbs=None, compile_model=False, **kwargs):
self.loss_fn = loss_fn()
self.optim_fn = optim_fn
- self.optimizer = optim_fn(self.model.parameters(), lr=lr, **kwargs) if lr else None
+
+ if lr: self._reset_opt(lr, **kwargs)
+ else: self.optimizer = None
metrics = {m.name:m for m in metrics} if metrics else {}
#metrics |= {f"{m.name}_valid":m for m in metrics.values()}
metrics["loss"] = Mean("loss", self.device)
metrics["loss_valid"] = Mean("loss_valid", self.device)
- self.metrics = metrics
-
- def _reset_opt(self, lr, **kwargs): self.optimizer = self.optim_fn(self.model.parameters(), lr, **kwargs)
+ self.metrics = metrics
+ self.cbs = cbs
+
+ if platform.system() == "Linux" and compile_model:
+ print("[INFO]: Linux, compile model with torch")
+ torch._dynamo.reset()
+ #self.model = torch.compile(self.model) #, fullgraph=True, mode ="max-autotune")
+
+ for model in self.trainables:
+ model.compile()
+
+ def _reset_opt(self, lr, **kwargs): self.optimizer = self.optim_fn(self._get_parameters(), lr=lr, **kwargs)
def _set_opt_param(self, lr, **kwargs):
'''at least lr: Does not reset existing optimizer, only changes learn rate.'''
@@ -109,21 +154,26 @@ def train_on_batch(self, data, train=True):
#backprob
loss.backward()
+ # torch.nn.utils.clip_grad_norm_(self.model.parameters(), 1)
+
#update weights
self.optimizer.step()
return loss.detach()
def train_on_epoch(self, data_loader: DataLoader, train=True):
- self.model.train(train)
+ # self.model.train(train)
+ for model in self.trainables:
+ model.train(train)
mode = "" if train else "_valid"
-
+
with self.progress_bar(total=len(data_loader), epoch=self.epoch, unit=" batch") as batch_prgb:
- for batch, data in enumerate(data_loader):
+ for self.batch, data in enumerate(data_loader):
+
loss = self.train_on_batch(data, train=train)
self.metrics["loss"+mode].update_state(loss)
-
+
if train:
self.fit_losses.append(loss.item())
if self.lr_sched: self.lr_sched.step()
@@ -131,27 +181,33 @@ def train_on_epoch(self, data_loader: DataLoader, train=True):
#pack up metrics
self.out_metric_dict = {m.name:m.result().tolist() for m in self.metrics.values() if not m.empty}
self.end_batch_metrics(batch_prgb, **self.out_metric_dict)
-
+ # run_cbs(self.cbs, "after_batch", self) # e.g. if max-number of batches is needed
+
#run on train and one on valid
def fit(self, num_epochs: int, data_loaders: DataLoaders, lr: float=None, lr_sched=None, log_summary=True):
if not hasattr(self, "loss_fn"): raise RuntimeError("'compile' has to be called first")
- self._set_opt_param(lr=lr)
- if lr_sched: self.lr_sched = lr_sched(self.optimizer)
- else: self.lr_sched = None
-
+ self._set_opt_param(lr=lr)
+ if not hasattr(self, "lr_sched"):
+ if lr_sched: self.lr_sched = lr_sched(self.optimizer)
+ else: self.lr_sched = None
+
+ self.epoch = 0
self.num_epochs = num_epochs
- self.epochs = range(num_epochs)
+
self.fit_losses = []
self.fit_valid_losses = []
self.batch_size = data_loaders.train.batch_size
self.dataset_size_train = len(data_loaders.train)
if data_loaders.valid: self.dataset_size_valid = len(data_loaders.valid)
+
+ run_cbs(self.cbs, "before_fit", self)
+
+ self.epochs = range(self.epoch, num_epochs) #after callback so we could resume training on a specific self.epoch
-
- with self.progress_bar(total=num_epochs, desc="Fit", unit=" epoch") as epoch_prgb:
+ with self.progress_bar(total=len(self.epochs), desc="Fit", unit=" epoch") as epoch_prgb:
for self.epoch in self.epochs:
-
+
#reset all metrics
for m in self.metrics.values(): m.reset_state()
@@ -165,23 +221,36 @@ def fit(self, num_epochs: int, data_loaders: DataLoaders, lr: float=None, lr_sch
self.out_metric_dict["loss_valid"] ])
self.end_epoch_metrics(epoch_prgb, **self.out_metric_dict)
-
+ run_cbs(self.cbs, "after_epoch", self)
+
self.fit_summary(log_summary=log_summary)
+ run_cbs(self.cbs, "after_fit", self)
#------------------------------------
- def summary(self): print("Number of model parameters:", number_of_paramters(self.model))
+ def summary(self):
+
+ cnt_params = lambda parameters: sum([p.numel() for p in parameters])
+
+ s = "Pipeline stats of explicit trainables"
+
+ for trainable in self.trainables:
+ name = str(trainable.__class__)
+ all_params = trainable.parameters()
+ trainable_params = filter(lambda p: p.requires_grad, trainable.parameters())
+ s += "\n" + f" - {name}: Total={cnt_params(all_params):0.2e} Trainable={cnt_params(trainable_params):0.2e}"
+ return s
def fit_summary(self, figsize=(12,2), log_summary=True, return_fig=False):
- fig = plt.figure(figsize=figsize, constrained_layout=True)
- plt.xlabel("Batches")
+ fig = plt.figure(figsize=figsize, constrained_layout=True, dpi=150)
+ plt.xlabel("Number of batches / update steps")
plt.ylabel("Loss")
if log_summary: plt.yscale('log')
plt.plot(self.fit_losses, label="train")
if len(self.fit_valid_losses) > 0:
data = np.array(self.fit_valid_losses)
- plt.plot(data[:,0],data[:,1], label="valid", color="tab:orange")
- plt.plot(data[:,0],data[:,1], ".", color="tab:orange")
+ plt.plot(data[:, 0],data[:, 1], label="valid", color="tab:orange")
+ plt.plot(data[:, 0],data[:, 1], ".", color="tab:orange")
plt.legend()
if return_fig: return fig
plt.show()
@@ -223,4 +292,3 @@ def end_epoch_metrics(self, prgb:tqdm, epoch: int=None, **metrics): self.end_pro
def end_batch_metrics(self, prgb:tqdm, batch: int=None, **metrics): self.end_progress_bar_iteration(prgb, False, "Batch", batch, **metrics)
#------------------------------------
-
diff --git a/genQC/pipeline/unitary_clip_pipeline.py b/genQC/pipeline/unitary_clip_pipeline.py
new file mode 100644
index 0000000..031f95c
--- /dev/null
+++ b/genQC/pipeline/unitary_clip_pipeline.py
@@ -0,0 +1,128 @@
+"""Pipeline for contrastive pre-training of an unitary encoder"""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/pipeline/unitary_clip_pipeline.ipynb.
+
+# %% auto 0
+__all__ = ['UnitaryCLIPPipeline']
+
+# %% ../../src/pipeline/unitary_clip_pipeline.ipynb 2
+from ..imports import *
+from .pipeline import Pipeline
+from ..utils.config_loader import *
+from ..models.config_model import ConfigModel
+
+# %% ../../src/pipeline/unitary_clip_pipeline.ipynb 3
+class UnitaryCLIPPipeline(Pipeline):
+ def __init__(self,
+ model: nn.Module,
+ device: torch.device) -> None:
+ super().__init__(model, device)
+
+ self.loss = nn.CrossEntropyLoss()
+ self.device = device
+
+ #------------------------------------
+
+ add_config = {}
+
+ def params_config(self, save_path: str) -> dict:
+ params_config = {}
+
+ params_config["model"] = self.model.get_config(save_path=save_path+"model")
+ params_config["unitary_text_encoder"] = self.model.unitary_text_encoder.get_config(save_path=None)
+ params_config["circuit_encoder"] = self.model.circuit_encoder.get_config(save_path=None)
+
+ params_config["device"] = str(self.device)
+ params_config["add_config"] = self.add_config
+
+ return params_config
+
+ def store_pipeline(self, config_path: str, save_path: str):
+ super().store_pipeline(config_path, save_path)
+ config = self.get_config(save_path)
+ save_dict_yaml(config, config_path+"config.yaml")
+
+ self.model.store_model(config_path=None, save_path=save_path+"model")
+
+ @staticmethod
+ def from_config_file(config_path, device: torch.device, save_path: str=None):
+ config = load_config(config_path+"config.yaml")
+ config = config_to_dict(config)
+
+ def _get_save_path(config_save_path, appendix):
+ _save_path = default(save_path, config_path) + appendix
+ if "save_path" in config_save_path:
+ _save_path = config_save_path["save_path"]
+ return _save_path
+
+ if exists(device):
+ config["params"]["device"] = device
+ config["params"]["model"]["params"]["text_encoder_config"]["device"] = device
+
+ unitary_text_encoder = ConfigModel.from_config(config["params"].pop("unitary_text_encoder", None), device, None)
+ circuit_encoder = ConfigModel.from_config(config["params"].pop("circuit_encoder", None), device, None)
+
+ config["params"]["model"]["params"]["unitary_text_encoder"] = unitary_text_encoder
+ config["params"]["model"]["params"]["circuit_encoder"] = circuit_encoder
+ config["params"]["model"] = ConfigModel.from_config(config["params"]["model"], device, _get_save_path(config["params"]["model"], "model"))
+
+ add_config = config["params"].pop("add_config", None)
+
+ pipeline = instantiate_from_config(config)
+
+ if exists(pipeline.add_config):
+ pipeline.add_config = add_config
+
+ return pipeline
+
+ #------------------------------------
+ # Inference functions
+
+ @torch.no_grad()
+ def __call__(self, tokens: torch.Tensor, params: torch.Tensor, y: torch.Tensor, U: torch.Tensor, softmax=True) -> torch.Tensor:
+ #compute the score of img-label pairs for classification!!
+ self.model.eval()
+
+ scores = self.model(tokens=tokens, params=params, y=y, U=U) #[b, b]
+
+ if softmax:
+ scores = F.softmax(scores, dim-1)
+
+ return scores
+
+ #------------------------------------
+ # Training functions
+
+ def get_loss(self, tokens: torch.Tensor, params: torch.Tensor, y: torch.Tensor, U: torch.Tensor) -> torch.Tensor:
+
+ scores = self.model(tokens=tokens, params=params, y=y, U=U) #[b, b]
+
+ #scores is: I=unitary_text T=circuit
+ #--------------------------------
+ #| I1*T1 I1*T2 I1*T3 ...
+ #| I2*T1
+ #| I3*T1
+ # ...
+ #--------------------------------
+
+ target = torch.arange(scores.shape[0], device=scores.device)
+
+ loss_unitary_text = self.loss(scores , target)
+ loss_circuit = self.loss(scores.T, target)
+
+ #symmetric loss
+ loss = (loss_unitary_text + loss_circuit) / 2.0
+
+ return loss
+
+ def train_step(self, data, **kwargs):
+ tokens, y, params, U = data
+
+ tokens = tokens.to(self.device)
+ params = params.to(self.device)
+ y = y.to(self.device)
+ U = U.to(torch.float32).to(self.device)
+
+ loss = self.get_loss(tokens=tokens, params=params, y=y, U=U)
+
+ return loss
diff --git a/genQC/platform/backends/__init__.py b/genQC/platform/backends/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/genQC/platform/backends/base_backend.py b/genQC/platform/backends/base_backend.py
new file mode 100644
index 0000000..253131d
--- /dev/null
+++ b/genQC/platform/backends/base_backend.py
@@ -0,0 +1,31 @@
+"""Base class of corresponding backends."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../src/platform/backends/base_backend.ipynb.
+
+# %% auto 0
+__all__ = ['BaseBackend']
+
+# %% ../../../src/platform/backends/base_backend.ipynb 2
+from ...imports import *
+
+# %% ../../../src/platform/backends/base_backend.ipynb 3
+class BaseBackend(abc.ABC):
+ """Backends implement at least these functions."""
+
+ BASIC_BACKEND_TYPE = type[Any]
+
+ @abc.abstractmethod
+ def backend_to_genqc(self, *args, **kwargs):
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def genqc_to_backend(self, *args, **kwargs):
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def get_unitary(self, *args, **kwargs):
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def draw(self, *args, **kwargs) -> None:
+ raise NotImplementedError()
diff --git a/genQC/inference/export_cudaq.py b/genQC/platform/backends/circuits_cudaq.py
similarity index 50%
rename from genQC/inference/export_cudaq.py
rename to genQC/platform/backends/circuits_cudaq.py
index d0bc009..289da03 100644
--- a/genQC/inference/export_cudaq.py
+++ b/genQC/platform/backends/circuits_cudaq.py
@@ -1,64 +1,48 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/inference/export_cudaq.ipynb.
+"""[CUDA-Q](https://github.com/NVIDIA/cuda-quantum) based quantum circuit backend."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../src/platform/backends/circuits_cudaq.ipynb.
# %% auto 0
-__all__ = ['backend', 'CircuitInstruction', 'CircuitInstructions', 'CircuitsCudaqBackend', 'tensor_to_instructions',
- 'genqc_to_cudaq']
+__all__ = ['ParametrizedCudaqKernel', 'CircuitsCudaqBackend']
+
+# %% ../../../src/platform/backends/circuits_cudaq.ipynb 2
+from ...imports import *
+from .base_backend import BaseBackend
+from ..circuits_instructions import CircuitInstructions
-# %% ../../src/inference/export_cudaq.ipynb 2
-from ..imports import *
-from typing import Sequence, List, Optional
import cudaq
-# %% ../../src/inference/export_cudaq.ipynb 4
+# %% ../../../src/platform/backends/circuits_cudaq.ipynb 4
@dataclass
-class CircuitInstruction():
- name: str
- control_nodes: Sequence[int]
- target_nodes: Sequence[int]
- params: Sequence[float]
-
-# %% ../../src/inference/export_cudaq.ipynb 5
-class CircuitInstructions():
- def __init__(self, tensor_shape: torch.Size) -> None:
- assert len(tensor_shape) == 2 # ... [qubits, time]
- self.tensor_shape = tensor_shape
- self._instructions = []
- self.instruction_names_set = set()
-
- def add_instruction(self,
- name: str,
- control_nodes: Sequence[int],
- target_nodes: Sequence[int],
- params: Sequence[float]) -> None:
- self.instruction_names_set.add(name)
- self._instructions.append(CircuitInstruction(name, control_nodes, target_nodes, params))
+class ParametrizedCudaqKernel:
+ kernel: cudaq.kernel
+ params: list[float] # currently only support 1 angle per gate
- @property
- def data(self) -> List[CircuitInstruction]: return self._instructions
+# %% ../../../src/platform/backends/circuits_cudaq.ipynb 6
+class CircuitsCudaqBackend(BaseBackend):
- @property
- def length(self) -> int: return len(self._instructions)
-
- @property
- def num_qubits(self) -> int: return self.tensor_shape[0]
+ BASIC_BACKEND_TYPE = type[cudaq.kernel]
- @property
- def max_gates(self) -> int: return self.tensor_shape[1]
+ def __init__(self, target: str = "qpp-cpu") -> None:
+ cudaq.reset_target()
+ cudaq.set_target(target) # 'nvidia'
- def __repr__(self) -> str: return str(self._instructions)
+ def backend_to_genqc(self):
+ raise NotImplementedError("Not implemeted cudaq to genQC.")
- def print(self) -> None:
- for instruction in self.data:
- print(instruction)
-
-# %% ../../src/inference/export_cudaq.ipynb 7
-class CircuitsCudaqBackend():
-
- BASIC_BACKEND_TYPE = type[cudaq.kernel]
-
# Has to match with insides of belows kernel
- KERNEL_VOCABULARY = {"h":1, "cx":2, "z":3, "x":4, "y":5, "ccx":6, "swap":7}
-
+ KERNEL_VOCABULARY = {"h":1,
+ "cx":2,
+ "z":3,
+ "x":4,
+ "y":5,
+ "ccx":6,
+ "swap":7,
+ "rx":8,
+ "ry":9,
+ "rz":10,
+ "cp":11,}
+
def _construct_kernel(self,
gate_list: List[str],
target_1_nodes_list: List[int],
@@ -79,7 +63,9 @@ def place_gate_kernel(gate: int,
target_1: int,
target_2: int,
control_1: int,
- control_2: int):
+ control_2: int,
+ theta: float):
+
if gate == 1: h(qvector[target_1])
elif gate == 2: cx(qvector[control_1], qvector[target_1])
elif gate == 3: z(qvector[target_1])
@@ -87,12 +73,23 @@ def place_gate_kernel(gate: int,
elif gate == 5: y(qvector[target_1])
elif gate == 6: x.ctrl(qvector[control_1], qvector[control_2], qvector[target_1])
elif gate == 7: swap(qvector[target_1], qvector[target_2])
+
+ elif gate == 8: rx(theta, qvector[target_1])
+ elif gate == 9: ry(theta, qvector[target_1])
+ elif gate == 10: rz(theta, qvector[target_1])
+
+ elif gate == 11:
+ # R1 applies the unitary transformation; i.e. it is a phase gate
+ # R1(λ) = | 1 0 |
+ # | 0 exp(iλ) |
+ r1.ctrl(theta, qvector[target_1], qvector[target_2])
+
@cudaq.kernel
- def kernel(input_state: List[complex]):
+ def kernel(input_state: list[complex], thetas: list[float]):
qvector = cudaq.qvector(input_state)
for i in range(num_gates):
- place_gate_kernel(gate_list[i], qvector, target_1_nodes_list[i], target_2_nodes_list[i], control_1_nodes_list[i], control_2_nodes_list[i])
+ place_gate_kernel(gate_list[i], qvector, target_1_nodes_list[i], target_2_nodes_list[i], control_1_nodes_list[i], control_2_nodes_list[i], thetas[i])
return kernel
@@ -105,7 +102,7 @@ def check_error_circuit(self,
if gate not in self.KERNEL_VOCABULARY:
raise NotImplementedError(f"Unknown gate {gate}, not in `self.KERNEL_VOCABULARY`.")
- if gate in ["h", "z", "x", "y"]:
+ if gate in ["h", "z", "x", "y", "rx", "ry", "rz"]:
if num_target_nodes != 1 or num_control_nodes !=0: return False
elif gate in ["cx"]:
@@ -114,18 +111,27 @@ def check_error_circuit(self,
elif gate in ["ccx"]:
if num_target_nodes != 1 or num_control_nodes !=2: return False
- elif gate in ["swap"]:
+ elif gate in ["swap", "cp"]:
if num_target_nodes != 2 or num_control_nodes !=0: return False
else:
raise NotImplementedError(f"Unknown gate {gate}, implemetation is faulty!")
return True
-
-
- def export_cudaq(self, instructions: CircuitInstructions) -> cudaq.kernel:
+
+ def genqc_to_backend(self, instructions: CircuitInstructions) -> cudaq.kernel:
"""Convert given genQC `CircuitInstructions` to a `cudaq.kernel`."""
+ _params = torch.tensor([
+ instruction.params if instruction.params else torch.nan
+ for instruction in instructions.data
+ ]) # ... [seq, nP]
+
+ assert _params.shape[1] == 1 #only support nP=1 for now
+ _params = _params.squeeze()
+
+ #--------------------
+
# num_qubits = instructions.num_qubits
num_gates = instructions.length
@@ -144,9 +150,6 @@ def export_cudaq(self, instructions: CircuitInstructions) -> cudaq.kernel:
gate = instruction.name.lower()
control_nodes = instruction.control_nodes
target_nodes = instruction.target_nodes
-
- if len(instruction.params) > 0:
- raise NotImplementedError(f"Only support non parametrized gates currently.")
num_target_nodes = len(target_nodes)
num_control_nodes = len(control_nodes)
@@ -167,11 +170,14 @@ def export_cudaq(self, instructions: CircuitInstructions) -> cudaq.kernel:
control_2_nodes_list[i] = control_nodes[1]
#--------------------
- kernel= self._construct_kernel(gate_list, target_1_nodes_list, target_2_nodes_list, control_1_nodes_list, control_2_nodes_list)
- return kernel
+ _kernel = self._construct_kernel(gate_list, target_1_nodes_list, target_2_nodes_list, control_1_nodes_list, control_2_nodes_list)
+
+ return ParametrizedCudaqKernel(kernel=_kernel, params=_params.tolist())
- def get_unitary(self, kernel: cudaq.kernel, num_qubits: int) -> np.ndarray:
+ def get_unitary(self, parametrizedCudaqKernel: ParametrizedCudaqKernel, num_qubits: int) -> np.ndarray:
"""Return the unitary matrix of a `cudaq.kernel`. Currently relies on simulation, could change in future releases of cudaq."""
+
+ kernel, thetas = parametrizedCudaqKernel.kernel, parametrizedCudaqKernel.params
N = 2**num_qubits
U = np.zeros((N, N), dtype=np.complex128)
@@ -180,7 +186,7 @@ def get_unitary(self, kernel: cudaq.kernel, num_qubits: int) -> np.ndarray:
state_j = np.zeros((N), dtype=np.complex128)
state_j[j] = 1
- U[:, j] = np.array(cudaq.get_state(kernel, state_j), copy=False)
+ U[:, j] = np.array(cudaq.get_state(kernel, state_j, thetas), copy=False)
return U
@@ -189,52 +195,3 @@ def draw(self, kernel: cudaq.kernel, num_qubits: int, **kwargs) -> None:
c = [0] * (2**num_qubits)
c[0] = 1
print(cudaq.draw(kernel, c))
-
-# %% ../../src/inference/export_cudaq.ipynb 9
-def tensor_to_instructions(tensor: torch.Tensor,
- vocabulary_inverse: dict,
- params_tensor: Optional[torch.Tensor] = None,
- params_4pi_normalization: bool = True,
- sign_labels: dict = {"control_nodes":-1, "target_nodes":+1}) -> CircuitInstructions:
- """Convert a given `torch.Tensor` to `CircuitInstructions`."""
-
- assert tensor.dim() == 2, f"{tensor.shape=}"
- num_of_qubits, time = tensor.shape
-
- instructions = CircuitInstructions(tensor_shape=tensor.shape)
-
- for t in range(time):
- enc_time_slice = tensor[:, t] # contains all bits at time t
-
- for gate_index, gate in vocabulary_inverse.items():
-
- target_nodes = (enc_time_slice == (sign_labels["target_nodes"] * gate_index)).nonzero(as_tuple=True)[0]
- control_nodes = (enc_time_slice == (sign_labels["control_nodes"] * gate_index)).nonzero(as_tuple=True)[0]
-
- if target_nodes.nelement() > 0:
- params = []
- if exists(params_tensor):
- params = params_tensor[:, t]
- if params_4pi_normalization:
- params = (params+1.0) * 2.0*np.pi # [-1, 1] to [0, 4pi]
- params = params.tolist()
-
- instructions.add_instruction(gate, control_nodes.tolist(), target_nodes.tolist(), params)
-
- break #break on first hit, per def only one gate allowed per t
-
- elif control_nodes.nelement() > 0: # no target but control means error
- raise RuntimeError("target_nodes.nelement() <= 0 but control_nodes.nelement() > 0")
-
- #else we are fine with tensors that have time steps with no action!
-
- return instructions
-
-# %% ../../src/inference/export_cudaq.ipynb 10
-backend = CircuitsCudaqBackend()
-
-def genqc_to_cudaq(tensor: torch.Tensor, vocabulary_inverse: dict) -> cudaq.kernel:
- """Convert given `torch.Tensor` to a `cudaq.kernel`."""
- instructions = tensor_to_instructions(tensor, vocabulary_inverse)
- kernel = backend.export_cudaq(instructions)
- return kernel
diff --git a/genQC/platform/backends/circuits_pennylane.py b/genQC/platform/backends/circuits_pennylane.py
new file mode 100644
index 0000000..7515d5b
--- /dev/null
+++ b/genQC/platform/backends/circuits_pennylane.py
@@ -0,0 +1,118 @@
+"""[PennyLane](https://pennylane.ai/) based quantum circuit backend."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../src/platform/backends/circuits_pennylane.ipynb.
+
+# %% auto 0
+__all__ = ['instruction_name_to_pennylane_name', 'ParametrizedPennylaneCircuit', 'CircuitsPennylaneBackend']
+
+# %% ../../../src/platform/backends/circuits_pennylane.ipynb 2
+from ...imports import *
+from .base_backend import BaseBackend
+from ..circuits_instructions import CircuitInstructions
+from ..tokenizer.base_tokenizer import Vocabulary
+from ...utils.config_loader import get_obj_from_str
+
+import pennylane as qml
+import pennylane.ops as pennylane_ops
+
+# %% ../../../src/platform/backends/circuits_pennylane.ipynb 4
+def instruction_name_to_pennylane_name(name: str) -> str:
+ """Maps instruction names to PennyLane names."""
+
+ _maps = {
+ "ccx": "Toffoli",
+ "cp": "CPhase",
+ "cx": "CNOT",
+ }
+
+ if name in _maps:
+ name = _maps[name]
+ else:
+ name = name.upper()
+
+ return name
+
+# %% ../../../src/platform/backends/circuits_pennylane.ipynb 5
+@dataclass
+class ParametrizedPennylaneCircuit:
+ circuit: qml.QNode
+ params: torch.Tensor
+
+# %% ../../../src/platform/backends/circuits_pennylane.ipynb 7
+class CircuitsPennylaneBackend(BaseBackend):
+ """A backend for [PennyLane](https://pennylane.ai/)."""
+
+ def backend_to_genqc(self, qc: ParametrizedPennylaneCircuit, ignore_barriers: bool = True) -> CircuitInstructions:
+ """Convert a given Pennylane `ParametrizedPennylaneCircuit` to genQC `CircuitInstructions`."""
+ raise NotImplementedError()
+
+ def genqc_to_backend(self,
+ instructions: CircuitInstructions,
+ flip_qubit_order: bool = True,
+ place_barriers: bool = False,
+ ignore_errors: bool = False,
+ place_error_placeholders: bool = False) -> ParametrizedPennylaneCircuit:
+ """
+ Convert given genQC `CircuitInstructions` to a `ParametrizedPennylaneCircuit`.
+ - flip_qubit_order ... e.g. needed when using little-endian definition.
+ """
+
+ _params = torch.tensor([
+ instruction.params if instruction.params else torch.nan
+ for instruction in instructions.data
+ ]) # ... [seq, nP]
+
+ assert _params.shape[1] == 1 #only support nP=1 for now
+ _params = _params.squeeze() # swap so we have batched [1, seq]
+
+ N = instructions.num_qubits
+ dev = qml.device("default.qubit", wires=N)
+
+ @qml.qnode(dev, interface='torch')
+ def _circuit(params):
+ for i, instruction in enumerate(instructions.data):
+
+ _name = instruction_name_to_pennylane_name(instruction.name)
+
+ op = getattr(pennylane_ops, _name)
+
+ # The first wire provided corresponds to the control qubit.
+ # e.g. is qml.H(0)
+ _wires = (*instruction.control_nodes, *instruction.target_nodes)
+
+ if flip_qubit_order:
+ _wires = [N-n-1 for n in _wires]
+
+ try:
+ if op.num_params > 0:
+ op(params[i], wires=_wires)
+ else:
+ op(wires=_wires)
+ except Exception as err:
+ if ignore_errors: continue
+ elif place_error_placeholders:
+ qml.Identity(wires=_wires)
+ raise err
+
+ if place_barriers: qml.Barrier(wires=list(range(N)))
+
+ # dummy return, as we only care about the unitary
+ # return qml.expval(qml.PauliZ(0))
+ return qml.state()
+
+ #run once to test for errors
+ try:
+ _circuit(_params)
+ except Exception as err:
+ raise err
+
+ return ParametrizedPennylaneCircuit(circuit=_circuit, params=_params)
+
+ def get_unitary(self, qc: ParametrizedPennylaneCircuit) -> torch.Tensor:
+ """Return the unitary matrix of a `ParametrizedPennylaneCircuit`."""
+ return qml.matrix(qc.circuit)(qc.params)
+
+ def draw(self, qc: ParametrizedPennylaneCircuit, style:str = "black_white", **kwargs) -> None:
+ """Draw the given Pennylane `ParametrizedPennylaneCircuit`"""
+ fig, ax = qml.draw_mpl(qc.circuit, decimals=2, show_all_wires=True, style=style, **kwargs)(qc.params.cpu().numpy())
+ return fig
diff --git a/genQC/platform/backends/circuits_qiskit.py b/genQC/platform/backends/circuits_qiskit.py
new file mode 100644
index 0000000..2c30599
--- /dev/null
+++ b/genQC/platform/backends/circuits_qiskit.py
@@ -0,0 +1,205 @@
+"""[Qiskit](https://github.com/Qiskit/qiskit) based quantum circuit backend."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../src/platform/backends/circuits_qiskit.ipynb.
+
+# %% auto 0
+__all__ = ['get_number_of_gate_params', 'instruction_name_to_qiskit_gate', 'get_target_control_qubits', 'CircuitsQiskitBackend']
+
+# %% ../../../src/platform/backends/circuits_qiskit.ipynb 2
+from ...imports import *
+from .base_backend import BaseBackend
+from ..circuits_instructions import CircuitInstructions
+from ..tokenizer.base_tokenizer import Vocabulary
+from ...utils.config_loader import get_obj_from_str
+
+import qiskit.circuit.library as ql
+import qiskit.quantum_info as qi
+
+from qiskit import QuantumCircuit, transpile
+from qiskit.circuit.gate import Gate
+
+# %% ../../../src/platform/backends/circuits_qiskit.ipynb 4
+def get_number_of_gate_params(gate_cls: type[Gate]) -> int:
+ # python: gives you the number of any arguments BEFORE *args, minus the ones that have a default, -1 for self parameter of classes
+ return gate_cls.__init__.__code__.co_argcount - len(gate_cls.__init__.__defaults__) - 1
+
+# %% ../../../src/platform/backends/circuits_qiskit.ipynb 6
+def instruction_name_to_qiskit_gate(name: str) -> Gate:
+ match name:
+ case "swap": name = "Swap"
+ case "cp": name = "CPhase"
+ case _: name = name.upper()
+
+ return get_obj_from_str(f"qiskit.circuit.library.standard_gates.{name}Gate")
+
+# %% ../../../src/platform/backends/circuits_qiskit.ipynb 7
+def get_target_control_qubits(qc: QuantumCircuit, gate: Gate) -> Tuple[List[int], List[int]]:
+ """Get the target and control qubits of a Qiskit `Gate` of a `QuantumCircuit`."""
+
+ acts_on_cnt = gate.operation.num_qubits
+ acts_on = [qc.find_bit(qubit).index for qubit in gate.qubits] # order: (*control_qubits, *target_qubits)
+
+ assert acts_on_cnt == len(acts_on), "error in: acts_on_cnt == len(acts_on)"
+
+ num_ctrl_qubits = gate.operation.num_ctrl_qubits if hasattr(gate.operation, "num_ctrl_qubits") else 0
+ num_targ_qubits = acts_on_cnt - num_ctrl_qubits
+
+ control_qubits, target_qubits = acts_on[:-num_targ_qubits], acts_on[-num_targ_qubits:]
+ return control_qubits, target_qubits
+
+# %% ../../../src/platform/backends/circuits_qiskit.ipynb 9
+class CircuitsQiskitBackend(BaseBackend):
+
+ BASIC_BACKEND_TYPE = type[QuantumCircuit]
+
+ def backend_to_genqc(self, qc: QuantumCircuit, ignore_barriers: bool = True) -> CircuitInstructions:
+ """Convert a given Qiskit `QuantumCircuit` to genQC `CircuitInstructions`."""
+
+ if ignore_barriers:
+ gates = []
+ for gate in qc.data:
+ if gate.operation.name != "barrier":
+ gates.append(gate)
+ else:
+ gates = qc.data
+
+ instructions = CircuitInstructions(tensor_shape=torch.Size([qc.num_qubits, len(gates)]))
+
+ for gate in gates:
+ control_qubits, target_qubits = get_target_control_qubits(qc, gate)
+
+ #Correction to qiskit v1.3.1
+ #`cp` is symmetric but qiskit uses
+ #`cp(theta, control_qubit, target_qubit)` target and control
+ #We make it only target connections like `swap` gates
+ if gate.operation.name == "cp":
+ target_qubits.extend(control_qubits)
+ control_qubits = []
+
+ instructions.add_instruction(gate.operation.name, control_qubits, target_qubits, gate.operation.params)
+
+ return instructions
+
+ def genqc_to_backend(self,
+ instructions: CircuitInstructions,
+ place_barriers: bool = True,
+ ignore_errors: bool = False,
+ place_error_placeholders: bool = False) -> QuantumCircuit:
+ """Convert given genQC `CircuitInstructions` to a Qiskit `QuantumCircuit`."""
+
+ gate_classes = {name:instruction_name_to_qiskit_gate(name) for name in instructions.instruction_names_set}
+ qc = QuantumCircuit(instructions.num_qubits)
+
+ for instruction in instructions.data:
+ gate_cls = gate_classes[instruction.name]
+ num_of_paramters = get_number_of_gate_params(gate_cls)
+
+ control_qubits, target_qubits = instruction.control_nodes, instruction.target_nodes
+ params = instruction.params[:num_of_paramters]
+
+ try:
+ qc.append(gate_cls(*params), [*control_qubits, *target_qubits], [])
+ except Exception as err:
+ if ignore_errors: continue
+ elif place_error_placeholders:
+ qc.append(ql.UnitaryGate(np.eye(2**instructions.num_qubits), label="Err"), range(instructions.num_qubits))
+ # qc.append(ql.UnitaryGate(np.eye(2), label="Err"), [0])
+ continue
+ raise err
+
+ if place_barriers: qc.barrier()
+
+ return qc
+
+ def get_unitary(self, qc: QuantumCircuit, remove_global_phase: bool = True) -> np.ndarray:
+ """Return the unitary matrix of a `QuantumCircuit`."""
+ U = qi.Operator(qc).to_matrix().astype(np.complex128)
+ if remove_global_phase:
+ U *= np.exp(-1j * qc.global_phase)
+ return U
+
+ def schmidt_rank_vector(self, qc: Optional[QuantumCircuit] = None, densityMatrix: Optional[qi.DensityMatrix] = None) -> List[int]:
+ """Return the SRV of a `qi.DensityMatrix`."""
+
+ if not exists(densityMatrix):
+ densityMatrix = qi.DensityMatrix(qc)
+
+ systems_cnt = len(densityMatrix.dims())
+ total_trace = set(range(systems_cnt))
+ rank_vector = []
+
+ for i in range(systems_cnt):
+ trace = list(total_trace - {i})
+ red_densityMatrix = qi.partial_trace(densityMatrix, trace)
+ # r = np.count_nonzero(np.linalg.eigvals(red_densityMatrix) > 1e-14) # was slower during testing
+ r = np.linalg.matrix_rank(red_densityMatrix, hermitian=True).item()
+ rank_vector.append(r)
+
+ return rank_vector
+
+ def optimize_circuit(self,
+ qc: QuantumCircuit,
+ vocabulary: Vocabulary,
+ optimization_level: int = 1,
+ silent: bool = True) -> QuantumCircuit:
+ """Use `qiskit.compiler.transpile` to optimize a circuit."""
+
+ if optimization_level == 0:
+ return qc
+
+ while optimization_level > 0:
+ try:
+ qc_opt = transpile(qc, optimization_level=optimization_level, basis_gates=vocabulary)
+ return qc_opt
+
+ except Exception as er:
+ if not silent: print(er)
+ pass
+
+ optimization_level -= 1
+
+ return qc
+
+ def rnd_circuit(self,
+ num_of_qubits: int,
+ num_of_gates:int,
+ gate_pool: Union[Sequence[Gate], Sequence[str]],
+ rng: np.random.Generator) -> QuantumCircuit:
+ """Create a random `QuantumCircuit`."""
+
+ qc = QuantumCircuit(num_of_qubits)
+ gate_indices = rng.choice(len(gate_pool), num_of_gates)
+
+ gate_pool = list(gate_pool)
+ if isinstance(gate_pool[0], str):
+ gate_pool = [instruction_name_to_qiskit_gate(gate) for gate in gate_pool]
+
+ for gate_index in gate_indices:
+ gate_cls = gate_pool[gate_index]
+ num_of_paramters = get_number_of_gate_params(gate_cls)
+ params = rng.uniform(low=0, high=4.0*np.pi, size=num_of_paramters) if num_of_paramters > 0 else []
+
+ gate = gate_cls(*params)
+ act_qubits = rng.choice(num_of_qubits, gate.num_qubits, replace=False) # order: (*act_qubits)=(*control_qubits, *target_qubits)
+ qc.append(gate, [*act_qubits], [])
+
+ return qc
+
+ def randomize_params(self, qc: QuantumCircuit, rng: np.random.Generator) -> QuantumCircuit:
+ """Randomize all parameters of a `QuantumCircuit`. This creates a new `QuantumCircuit` and therefore deletes global phase."""
+
+ qc_new = QuantumCircuit(qc.num_qubits)
+
+ for gate in qc.data:
+ gate_cls = instruction_name_to_qiskit_gate(gate.operation.name)
+ control_qubits, target_qubits = get_target_control_qubits(qc, gate)
+ params = rng.uniform(low=0, high=4.0*np.pi, size=len(gate.operation.params))
+
+ qc_new.append(gate_cls(*params), [*control_qubits, *target_qubits], [])
+
+ return qc_new
+
+ def draw(self, qc: QuantumCircuit, **kwargs) -> None:
+ """Draw the given `QuantumCircuit` using Qiskit."""
+ return qc.draw("mpl", **kwargs)
+ # plt.show()
diff --git a/genQC/platform/circuits_generation.py b/genQC/platform/circuits_generation.py
new file mode 100644
index 0000000..495f936
--- /dev/null
+++ b/genQC/platform/circuits_generation.py
@@ -0,0 +1,306 @@
+"""Functions to create a quantum circuit dataset."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/platform/circuits_generation.ipynb.
+
+# %% auto 0
+__all__ = ['CircuitConditionType', 'get_rnd_encoded_circuit', 'get_rnd_encoded_circuits', 'generate_circuit_dataset']
+
+# %% ../../src/platform/circuits_generation.ipynb 2
+from ..imports import *
+from .backends.base_backend import BaseBackend
+from .tokenizer.circuits_tokenizer import CircuitTokenizer, Vocabulary
+from ..dataset.dataset_helper import get_unique_elements_indices
+from ..utils.async_fn import MemoryMappedArray, Parallel, delayed
+
+# %% ../../src/platform/circuits_generation.ipynb 4
+class CircuitConditionType(enum.Enum):
+ SRV = enum.auto()
+ UNITARY = enum.auto()
+
+# %% ../../src/platform/circuits_generation.ipynb 5
+def get_rnd_encoded_circuit(backend: BaseBackend,
+ tokenizer: CircuitTokenizer,
+ condition: CircuitConditionType,
+ num_of_qubits: int,
+ gate_pool: Optional[Sequence[str]],
+ min_gates: int,
+ max_gates: int,
+ rng: np.random.Generator,
+ optimized: bool = True,
+ post_randomize_params: bool = True,
+ return_params: bool = True) -> Tuple[Any, torch.Tensor, ...]:
+ """Generate a random circuit with corresponding condition."""
+
+ gate_pool = default(gate_pool, tokenizer.vocabulary)
+
+ qc = backend.rnd_circuit(num_of_qubits, rng.integers(min_gates, max_gates+1), gate_pool, rng)
+
+ if optimized:
+ qc = backend.optimize_circuit(qc, gate_pool)
+
+ if post_randomize_params:
+ qc = backend.randomize_params(qc, rng)
+
+ match condition:
+ case CircuitConditionType.SRV:
+ condition = torch.tensor(backend.schmidt_rank_vector(qc))
+
+ case CircuitConditionType.UNITARY:
+ U = backend.get_unitary(qc)
+ U_r, U_i = torch.from_numpy(np.real(U)), torch.from_numpy(np.imag(U))
+ condition = torch.stack([U_r, U_i], dim=0)
+
+ case _: raise NotImplementedError(f"Not implemented given condition: {condition}")
+
+ instructions = backend.backend_to_genqc(qc)
+ enc_tuple = tokenizer.encode(instructions, max_gates, return_params_tensor=return_params) # qc_tensor, params_tensor
+
+ return qc, condition, *enc_tuple
+
+# %% ../../src/platform/circuits_generation.ipynb 6
+def get_rnd_encoded_circuits(backend: BaseBackend,
+ tokenizer: CircuitTokenizer,
+ condition: CircuitConditionType,
+ samples: int,
+ num_of_qubits: int,
+ min_gates: int,
+ max_gates: int,
+ min_sub_gate_pool_cnt: int = 1,
+ max_sub_gate_pool_cnt: Optional[int] = None,
+ fixed_sub_gate_pool: Optional[Sequence[str]] = None,
+ max_num_params: Optional[int] = None,
+ filter_unique: bool = True,
+ optimized: bool = True,
+ post_randomize_params: bool = True,
+ return_params: bool = True,
+ silent: bool = False) -> Tuple[torch.Tensor, ...]:
+ """
+ Generate ´samples´ number of random circuits with corresponding condition.
+ Creates prompts for conditioning.
+ """
+
+ if condition not in [CircuitConditionType.SRV, CircuitConditionType.UNITARY]:
+ raise NotImplementedError(f"Not implemented {condition}")
+
+ sub_gate_pool = fixed_sub_gate_pool
+ gate_pool = list(tokenizer.vocabulary)
+
+ rng = np.random.default_rng()
+ n = len(gate_pool) + 1
+ c_range = np.arange(n-1)
+
+ if exists(max_sub_gate_pool_cnt):
+ max_sub_gate_pool_cnt = max(min_sub_gate_pool_cnt, min(max_sub_gate_pool_cnt+1, n))
+ else:
+ max_sub_gate_pool_cnt = n
+
+ #------------------
+ # Generate single circuits sequentially
+
+ x = []
+ y = []
+
+ if condition is CircuitConditionType.UNITARY:
+ u = []
+
+ if return_params:
+ p = [] # Note: params is of different size -> keep list
+
+ for i in tqdm(range(samples), disable=silent):
+
+ if not exists(fixed_sub_gate_pool):
+ sub_gate_pool_cnt = rng.integers(min_sub_gate_pool_cnt, max_sub_gate_pool_cnt)
+ sub_gate_pool_ind = rng.choice(c_range, size=sub_gate_pool_cnt, replace=False)
+
+ #NOTE: with this we have always the same ordering of the prompt gates!!
+ sub_gate_pool_ind = np.sort(sub_gate_pool_ind)
+
+ sub_gate_pool = [gate_pool[ind] for ind in sub_gate_pool_ind]
+
+ val = get_rnd_encoded_circuit(backend=backend,
+ tokenizer=tokenizer,
+ condition=condition,
+ num_of_qubits=num_of_qubits,
+ gate_pool=sub_gate_pool,
+ min_gates=min_gates,
+ max_gates=max_gates,
+ rng=rng,
+ optimized=optimized,
+ post_randomize_params=post_randomize_params,
+ return_params=return_params)
+
+ if return_params:
+ _, cond, qc_tensor, params_tensor = val
+ p.append(params_tensor)
+ else:
+ _, cond, qc_tensor = val
+
+ x.append(qc_tensor)
+
+ match condition:
+ case CircuitConditionType.SRV:
+ label = f"Generate SRV: {cond.tolist()}"
+
+ case CircuitConditionType.UNITARY:
+ label = f"Compile using: {[str(gate) for gate in sub_gate_pool]}"
+ u.append(cond)
+
+ case _: raise NotImplementedError(f"Not implemented given condition: {condition}")
+
+ y.append(label)
+
+ #------------------
+ # Make tensors unique and combine tensors and arrays
+
+ x = torch.stack(x, dim=0)
+ y = np.array(y)
+
+ if condition is CircuitConditionType.UNITARY:
+ u = torch.stack(u, dim=0)
+
+ if filter_unique:
+ tensor_unique, tensor_indices = get_unique_elements_indices(x)
+
+ x = x[tensor_indices]
+ y = y[tensor_indices]
+ if return_params: p = [p[i] for i in tensor_indices.tolist()]
+ if condition is CircuitConditionType.UNITARY: u = u[tensor_indices]
+
+ if not silent:
+ print(f"[INFO]: Generated unique circuits: {tensor_unique.shape[0]}.")
+
+ if not exists(max_num_params):
+ p_max_para = max(pi.shape[0] for pi in p)
+ p_min_value = min(pi.min() if pi.numel()>0 else 0 for pi in p)
+ p_max_value = max(pi.max() if pi.numel()>0 else 0 for pi in p)
+ if not silent: print(f"[INFO]: No max_num_params provided, infered {p_max_para=}, {p_min_value=} and {p_max_value=}.")
+ else:
+ if not silent: print(f"[INFO]: Using provided {max_num_params=}.")
+ p_max_para = max_num_params
+
+ p_t = torch.zeros((len(p), p_max_para, max_gates))
+ for i,pi in enumerate(p):
+ p_t[i, :pi.shape[0], :pi.shape[1]] = pi
+ p = p_t
+
+ if return_params:
+ return x, y, u, p
+ return x, y, u
+
+# %% ../../src/platform/circuits_generation.ipynb 8
+def generate_circuit_dataset(backend: BaseBackend,
+ tokenizer: CircuitTokenizer,
+ condition: CircuitConditionType,
+ total_samples: int,
+ num_of_qubits: int,
+ min_gates: int,
+ max_gates: int,
+ batch_samples: int = 128,
+ n_jobs: int = 1,
+ unitary_dtype: torch.dtype = torch.float16,
+ min_sub_gate_pool_cnt: int = 1,
+ max_sub_gate_pool_cnt: Optional[int] = None,
+ fixed_sub_gate_pool: Optional[Sequence[str]] = None,
+ max_num_params: Optional[int] = None,
+ filter_unique: bool = True,
+ optimized: bool = True,
+ post_randomize_params: bool = True,
+ return_params: bool = True) -> Tuple[torch.Tensor, ...]:
+ """
+ Generates ´samples´ number of random circuits with corresponding condition.
+ Supports large scale dataset with large unitaries. Uses memory mapping and parallelization.
+
+ - ´unitary_dtype´ only relevant for ´condition=CircuitConditionType.UNITARY´
+ """
+
+ if condition not in [CircuitConditionType.UNITARY]:
+ raise NotImplementedError(f"Not implemented {condition=}")
+
+ if not return_params:
+ raise NotImplementedError(f"Not implemented {return_params=}")
+
+ total_samples = int(total_samples)
+ batch_samples = min(int(batch_samples), total_samples)
+ njobs = max(min(n_jobs, total_samples//batch_samples), 1)
+
+ #------------------
+ # Check data sizes
+
+ gen_data = functools.partial(get_rnd_encoded_circuits,
+ backend=backend,
+ tokenizer=tokenizer,
+ condition=condition,
+ samples=batch_samples,
+ num_of_qubits=num_of_qubits,
+ min_gates=min_gates,
+ max_gates=max_gates,
+ min_sub_gate_pool_cnt=min_sub_gate_pool_cnt,
+ max_sub_gate_pool_cnt=max_sub_gate_pool_cnt,
+ fixed_sub_gate_pool=fixed_sub_gate_pool,
+ max_num_params=max_num_params,
+ filter_unique=filter_unique,
+ optimized=optimized,
+ post_randomize_params=post_randomize_params,
+ return_params=return_params,
+ silent=True)
+
+ x, y, u, p = gen_data()
+ x_global = torch.zeros((total_samples, *x.shape[1:]), dtype=x.dtype)
+ y_global = np.empty(total_samples, dtype=y.dtype)
+ u_global = torch.zeros((total_samples, 2, u.shape[-2], u.shape[-1]), dtype=unitary_dtype)
+ p_global = torch.zeros((total_samples, *p.shape[1:]), dtype=p.dtype)
+
+ #------------------
+ # Run memory mapped parallel generation
+
+ def _f(idx, x_map, y_map, u_map, p_map):
+ x, y, u, p = gen_data()
+
+ off = x.shape[0]
+ idx *= batch_samples
+
+ x_map[idx:idx+off] = x
+ y_map[idx:idx+off] = y
+ u_map[idx:idx+off] = u
+ p_map[idx:idx+off] = p
+
+
+ def _scope():
+ x_map = MemoryMappedArray(x_global)
+ y_map = MemoryMappedArray(y_global, type="numpy")
+ u_map = MemoryMappedArray(u_global)
+ p_map = MemoryMappedArray(p_global)
+
+ with Parallel(n_jobs=n_jobs) as parallel:
+ loop_set = range(int(np.floor(total_samples/batch_samples)))
+ _ = parallel(delayed(_f)(idx, x_map.obj_memmap, y_map.obj_memmap, u_map.obj_memmap, p_map.obj_memmap) for idx in loop_set)
+
+ return x_map.get_obj(), y_map.get_obj(), u_map.get_obj(), p_map.get_obj()
+
+ (x_global, x_file), (y_global, y_file), (u_global, u_file), (p_global, p_file) = _scope()
+
+ MemoryMappedArray.clean([x_file, y_file, u_file, p_file])
+
+ #------------------
+ # Collect results and remove the holes
+
+ x_global_nonzero = torch.logical_not((x_global==0).all(-1).all(-1)).nonzero(as_tuple=True)
+ print(f"[INFO]: Generated {x_global_nonzero[0].shape[0]} valid circuits.")
+
+ # complex indexing makes copy not view
+ x_global = x_global[x_global_nonzero]#.contiguous().clone()
+ u_global = u_global[x_global_nonzero]#.contiguous().clone()
+ p_global = p_global[x_global_nonzero]#.contiguous().clone()
+ y_global = y_global[x_global_nonzero] #np.ascontiguousarray(y_global[x_global_nonzero])
+
+ if filter_unique:
+ tensor_unique, tensor_indices = get_unique_elements_indices(x_global)
+
+ x_global = x_global[tensor_indices]
+ y_global = y_global[tensor_indices]
+ u_global = u_global[tensor_indices]
+ p_global = p_global[tensor_indices]
+
+ print(f"[INFO]: After filtering unique circuits: {x_global.shape[0]}.")
+
+ return x_global, y_global, u_global, p_global
diff --git a/genQC/platform/circuits_instructions.py b/genQC/platform/circuits_instructions.py
new file mode 100644
index 0000000..05615f5
--- /dev/null
+++ b/genQC/platform/circuits_instructions.py
@@ -0,0 +1,54 @@
+"""Classes for quantum circuit instructions."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/platform/circuits_instructions.ipynb.
+
+# %% auto 0
+__all__ = ['CircuitInstruction', 'CircuitInstructions']
+
+# %% ../../src/platform/circuits_instructions.ipynb 2
+from ..imports import *
+
+# %% ../../src/platform/circuits_instructions.ipynb 4
+@dataclass
+class CircuitInstruction():
+ """Basic quantum circuit instruction."""
+ name: str
+ control_nodes: Sequence[int]
+ target_nodes: Sequence[int]
+ params: Sequence[float]
+
+# %% ../../src/platform/circuits_instructions.ipynb 5
+class CircuitInstructions():
+ """Basic quantum circuit instruction handler."""
+
+ def __init__(self, tensor_shape: torch.Size) -> None:
+ assert len(tensor_shape) == 2 # ... [qubits, time]
+ self.tensor_shape = tensor_shape
+ self._instructions = []
+ self.instruction_names_set = set()
+
+ def add_instruction(self,
+ name: str,
+ control_nodes: Sequence[int],
+ target_nodes: Sequence[int],
+ params: Sequence[float]) -> None:
+ self.instruction_names_set.add(name)
+ self._instructions.append(CircuitInstruction(name, control_nodes, target_nodes, params))
+
+ @property
+ def data(self) -> List[CircuitInstruction]: return self._instructions
+
+ @property
+ def length(self) -> int: return len(self._instructions)
+
+ @property
+ def num_qubits(self) -> int: return self.tensor_shape[0]
+
+ @property
+ def max_gates(self) -> int: return self.tensor_shape[1]
+
+ def __repr__(self) -> str: return str(self._instructions)
+
+ def print(self) -> None:
+ for instruction in self.data:
+ print(instruction)
diff --git a/genQC/platform/qcircuit_dataset_construction.py b/genQC/platform/qcircuit_dataset_construction.py
deleted file mode 100644
index 429b93a..0000000
--- a/genQC/platform/qcircuit_dataset_construction.py
+++ /dev/null
@@ -1,223 +0,0 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/platform/qcircuit_dataset_construction.ipynb.
-
-# %% auto 0
-__all__ = ['get_target_control_qubits', 'encode_circuit', 'decode_circuit', 'get_rnd_encoded_circuit', 'get_rnd_encoded_circuits',
- 'gen_qc_dataset', 'get_specific_rnd_srv_circuit', 'gen_compilation_rndGates_dataset']
-
-# %% ../../src/platform/qcircuit_dataset_construction.ipynb 3
-from ..imports import *
-from .simulation.qcircuit_sim import *
-import genQC.dataset.dataset_helper as dahe
-
-import qiskit.quantum_info as qi
-from qiskit import QuantumCircuit
-from qiskit.circuit.gate import Gate
-import qiskit.circuit.library as ql
-
-# %% ../../src/platform/qcircuit_dataset_construction.ipynb 5
-def get_target_control_qubits(qc: QuantumCircuit, gate: Gate):
- acts_on_cnt = gate.operation.num_qubits
- acts_on = [qc.find_bit(qubit).index for qubit in gate.qubits] # order: (*control_qubits, *target_qubits)
-
- assert acts_on_cnt == len(acts_on), "error in: acts_on_cnt == len(acts_on)"
-
- num_ctrl_qubits = gate.operation.num_ctrl_qubits if hasattr(gate.operation, "num_ctrl_qubits") else 0
- num_targ_qubits = acts_on_cnt - num_ctrl_qubits
-
- control_qubits, target_qubits = acts_on[:-num_targ_qubits], acts_on[-num_targ_qubits:]
-
- return control_qubits, target_qubits
-
-# %% ../../src/platform/qcircuit_dataset_construction.ipynb 6
-def encode_circuit(qc: QuantumCircuit, num_of_qubits, gate_classes: dict, max_gates: int, sign_labels={"control_qubits":-1, "target_qubits":+1}, return_params=False):
- # circuit tensor
- # [qbits, time] .. in +- gate_number
- # 0 for empty
-
- tensor = torch.zeros((num_of_qubits, max_gates), dtype=torch.int32)
- params = []
-
- for t, gate in enumerate(qc.data):
- params.append(gate.operation.params)
-
- gate_id = gate_classes[gate.operation.name] #for new tensor just use this as the abs(T) and then assign the sign dep on the c/t
-
- control_qubits, target_qubits = get_target_control_qubits(qc, gate)
-
- for bit in control_qubits:
- tensor[bit, t] = gate_id * sign_labels["control_qubits"]
-
- for bit in target_qubits:
- tensor[bit, t] = gate_id * sign_labels["target_qubits"]
-
- if return_params:
- num_of_max_params = max(len(para) for para in params)
- params_tensor = torch.zeros((num_of_max_params, max_gates), dtype=torch.float32)
-
- for t, para in enumerate(params):
- params_tensor[:len(para), t] = torch.tensor(para)
-
- return tensor, params_tensor
-
- return tensor
-
-# %% ../../src/platform/qcircuit_dataset_construction.ipynb 7
-def decode_circuit(enc_tensor: torch.Tensor, gate_pool: list[Gate], place_barrier=True, sign_labels={"control_qubits":-1, "target_qubits":+1}, params_tensor=None):
- # should have dim 2, [bits, sequence]
- #minus ... control_qubits
- #plus ... target_qubits
-
- assert enc_tensor.ndim == 2, f"{enc_tensor.shape=}"
- num_of_qubits, time = enc_tensor.shape
-
- gate_qiskit_classes = {(i+1):x for i,x in enumerate(gate_pool)}
-
- qc = QuantumCircuit(num_of_qubits)
-
- for t in range(time):
- enc_time_slice = enc_tensor[:, t] # only contains all bits at time t
-
- for gate_index,gate_qiskit_class in gate_qiskit_classes.items():
- target_qubits = (enc_time_slice == (sign_labels["target_qubits"] *gate_index)).nonzero()
- control_qubits = (enc_time_slice == (sign_labels["control_qubits"]*gate_index)).nonzero()
-
- if target_qubits.nelement() > 0:
- num_of_paramters = get_number_of_gate_params(gate_qiskit_class)
- if exists(params_tensor) and num_of_paramters > 0 : params = params_tensor[:num_of_paramters, t].tolist()
- else: params = [0] * num_of_paramters
-
- qc.append(gate_qiskit_class(*params), [*control_qubits.tolist(), *target_qubits.tolist()], [])
- if place_barrier: qc.barrier()
- break #break on first hit, per def only one gate allowed per t
-
- elif control_qubits.nelement() > 0: #no target but control means error
- raise RuntimeError("control_qubits.nelement() > 0")
- #else we are fine with tensor that have time steps with no action!
-
- return qc
-
-# %% ../../src/platform/qcircuit_dataset_construction.ipynb 10
-def get_rnd_encoded_circuit(num_of_qubits, min_gates, max_gates, gate_pool, gate_classes, rng, optimized=True, return_params=False):
- qc = rnd_circuit(num_of_qubits, rng.integers(min_gates, max_gates+1), gate_pool, rng)
- if optimized: qc = optimize_circuit(qc, gate_pool)
- svr = schmidt_rank_vector(qi.DensityMatrix(qc))
-
- if return_params:
- qc_tensor, params_tensor = encode_circuit(qc, num_of_qubits, gate_classes, max_gates, return_params=return_params)
- return qc, qc_tensor, svr, params_tensor
-
- qc_tensor = encode_circuit(qc, num_of_qubits, gate_classes, max_gates, return_params=return_params)
- return qc, qc_tensor, svr
-
-# %% ../../src/platform/qcircuit_dataset_construction.ipynb 11
-def get_rnd_encoded_circuits(samples, num_of_qubits=3, min_gates=3, max_gates=10, gate_pool=[ql.HGate, ql.CXGate], optimized=True, silent=False, return_params=False):
- gate_classes = gate_pool_to_gate_classes(gate_pool) #{x().name:(i+1) for i,x in enumerate(gate_pool)}
-
- rng = np.random.default_rng()
-
- data = []
- label = []
- params = []
-
- for i in tqdm(range(samples), disable=silent):
- if return_params:
- qc, qc_tensor, svr, params_tensor = get_rnd_encoded_circuit(num_of_qubits, min_gates, max_gates, gate_pool, gate_classes, rng, optimized, return_params=return_params)
- params.append(params_tensor)
-
- else:
- qc, qc_tensor, svr = get_rnd_encoded_circuit(num_of_qubits, min_gates, max_gates, gate_pool, gate_classes, rng, optimized, return_params=return_params)
-
- data.append(qc_tensor)
- label.append(svr)
-
- if return_params: return data, label, params
- return data, label
-
-# %% ../../src/platform/qcircuit_dataset_construction.ipynb 13
-def gen_qc_dataset(samples, num_of_qubits, min_gates, max_gates, gate_pool, optimized, silent=False):
- tensor, srv = get_rnd_encoded_circuits(samples, num_of_qubits, min_gates, max_gates, gate_pool, optimized, silent)
-
- # make sure we have unique circuits
- tensor = torch.stack(tensor, dim=0)
- tensor_unique, tensor_indices = dahe.get_unique_elements_indices(tensor)
-
- if not silent: print(f"Generated unique circuits: {tensor_unique.shape[0]}")
-
- #--------------------------
- #select uniques only
-
- x = tensor[tensor_indices]
- y = torch.Tensor(srv).type(torch.int32)[tensor_indices] #leave as tensor, treat as 2D condition, combine cond into one large (each cat)
-
- return x,y
-
-# %% ../../src/platform/qcircuit_dataset_construction.ipynb 15
-def get_specific_rnd_srv_circuit(srv, requested_length, gate_pool, max_i=2000, silent=True, fix_length_after_optimizing=True, requested_length_tolerance=0):
- rng = np.random.default_rng()
-
- num_of_qubits = len(srv)
- is_srv = None
-
- if requested_length < sum(srv)-num_of_qubits: return None #not possible to generate this srv, to few gates
-
- i = 0
- while is_srv != srv: # brute-force sample a SRV
- qc = rnd_circuit(num_of_qubits, requested_length, gate_pool, rng)
- qc = optimize_circuit(qc, gate_pool)
-
- if i > max_i:
- if not silent: print(f"Max i reached: {srv=} {requested_length=} {requested_length_tolerance=} {max_i=}")
- return None #raise RuntimeError("max i reached")
- i += 1
-
- #---------------
- if fix_length_after_optimizing and len(qc.data) < requested_length-requested_length_tolerance:
- continue
-
- is_srv = schmidt_rank_vector(qi.DensityMatrix(qc))
-
- return qc
-
-# %% ../../src/platform/qcircuit_dataset_construction.ipynb 17
-def gen_compilation_rndGates_dataset(samples, num_of_qubits, min_gates, max_gates, gate_pool, min_sub_gate_pool_cnt=1, silent=False):
- '''Samples rnd circuit with a rnd subset of gates and return qc with gate label and unitary'''
-
- gate_classes = {x().name:(i+1) for i,x in enumerate(gate_pool)} #+1 for empty! global gate classes so we fix the indices! 1...H 2...CX so on
-
- #-------------------------------
-
- rng = np.random.default_rng()
- n = len(gate_pool) + 1
- c_range = np.arange(n-1)
-
- tensor = []
- label = []
- U = []
-
- for i in tqdm(range(samples), disable=silent):
- sub_gate_pool_cnt = rng.integers(min_sub_gate_pool_cnt, n)
- sub_gate_pool_ind = rng.choice(c_range, size=sub_gate_pool_cnt, replace=False)
- sub_gate_pool = [gate_pool[ind] for ind in sub_gate_pool_ind] # pick random subeset of gates
-
- qc, qc_tensor, svr = get_rnd_encoded_circuit(num_of_qubits, min_gates, max_gates, sub_gate_pool, gate_classes, rng, optimized=True)
-
- tensor.append(qc_tensor)
- label.append(f"Compile using: {[x().name for x in sub_gate_pool]}")
- U.append(qi.Operator(qc).to_matrix())
-
- #-------------------------------
-
- # make sure we have unique circuits
- tensor = torch.stack(tensor, dim=0)
- tensor_unique, tensor_indices = dahe.get_unique_elements_indices(tensor)
-
- if not silent: print(f"generated unique circuits: {tensor_unique.shape[0]}")
-
- #--------------------------
- #select uniques only
-
- x = tensor[tensor_indices]
- y = [label[i] for i in tensor_indices.tolist()]
- U = [ U[i] for i in tensor_indices.tolist()]
-
- return x,y,U
diff --git a/genQC/platform/qcircuit_evaluation.py b/genQC/platform/qcircuit_evaluation.py
deleted file mode 100644
index 4a8882a..0000000
--- a/genQC/platform/qcircuit_evaluation.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/platform/qcircuit_evaluation.ipynb.
-
-# %% auto 0
-__all__ = ['sort_into_bins', 'extract_gate_number', 'get_gate_stat_from_tensors', 'get_gate_stat_from_circuits']
-
-# %% ../../src/platform/qcircuit_evaluation.ipynb 2
-from ..imports import *
-from .qcircuit_dataset_construction import *
-from .simulation.qcircuit_sim import schmidt_rank_vector, optimize_circuit
-
-import qiskit.quantum_info as qi
-from qiskit import QuantumCircuit
-
-# %% ../../src/platform/qcircuit_evaluation.ipynb 4
-def sort_into_bins(x, y, y_uniques):
-
- x_binned = []
- y_binned = []
-
- for y_unique in y_uniques:
-
- comp = torch.all(y==y_unique, dim=-1)
- indices = comp.nonzero().squeeze()
-
- x_binned.append(x[indices])
- y_binned.append(y[indices])
-
- y_bins = [y[0] for y in y_binned]
-
- return x_binned, y_binned, y_bins
-
-# %% ../../src/platform/qcircuit_evaluation.ipynb 5
-def extract_gate_number(qc: QuantumCircuit, gate_pool, max_gates):
- gate_classes = {"empty":0} | {x().name:i+1 for i,x in enumerate(gate_pool)}
-
- gate_cnt = np.zeros(len(gate_classes), dtype=int)
-
- if hasattr(qc, "data"):
- for t, gate in enumerate(qc.data):
- gate_id = gate_classes[gate.operation.name]
- gate_cnt[gate_id] += 1
-
- gate_cnt[0] = max_gates - sum(gate_cnt[1:])
-
- return gate_cnt, gate_classes
-
-# %% ../../src/platform/qcircuit_evaluation.ipynb 6
-def get_gate_stat_from_tensors(tensors, gate_pool):
- for i,tensor in tqdm(enumerate(tensors), total=tensors.shape[0]):
- qc = decode_circuit(tensor, gate_pool)
-
- t_gate_cnts, gate_dict = extract_gate_number(qc, gate_pool, max_gates=tensor.shape[1])
-
- if i > 0: gate_cnts = np.vstack([gate_cnts, t_gate_cnts])
- else: gate_cnts = t_gate_cnts
-
- return gate_cnts, gate_dict
-
-# %% ../../src/platform/qcircuit_evaluation.ipynb 7
-def get_gate_stat_from_circuits(qcs: list, gate_pool, max_gates):
- for i,qc in tqdm(enumerate(qcs), total=len(qcs)):
-
- t_gate_cnts, gate_dict = extract_gate_number(qc, gate_pool, max_gates)
-
- if i > 0: gate_cnts = np.vstack([gate_cnts, t_gate_cnts])
- else: gate_cnts = t_gate_cnts
-
- return gate_cnts, gate_dict
diff --git a/genQC/platform/qcircuit_metrics.py b/genQC/platform/qcircuit_metrics.py
deleted file mode 100644
index 00dd884..0000000
--- a/genQC/platform/qcircuit_metrics.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/platform/qcircuit_metrics.ipynb.
-
-# %% auto 0
-__all__ = ['Unitary_FrobeniusNorm']
-
-# %% ../../src/platform/qcircuit_metrics.ipynb 3
-from ..imports import *
-
-# %% ../../src/platform/qcircuit_metrics.ipynb 5
-class Unitary_FrobeniusNorm:
- #defined in https://arxiv.org/pdf/2106.05649.pdf
-
- @staticmethod
- def distance(approx_U: torch.tensor, target_U: torch.tensor):
- d = 0.5 * torch.linalg.matrix_norm((approx_U-target_U), ord="fro")**2
- return d
-
- @staticmethod
- def name():
- return "Frobenius-Norm"
diff --git a/genQC/platform/qcircuit_util.py b/genQC/platform/qcircuit_util.py
deleted file mode 100644
index 747f91e..0000000
--- a/genQC/platform/qcircuit_util.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/platform/qcircuit_util.ipynb.
-
-# %% auto 0
-__all__ = ['get_element_matching_indices', 'get_entanglement_bins']
-
-# %% ../../src/platform/qcircuit_util.ipynb 2
-from ..imports import *
-
-# %% ../../src/platform/qcircuit_util.ipynb 4
-def get_element_matching_indices(a, b):
- """Compares (2d) `a` with `b`. Returns the indices of `b`, where a element of `a` matches with `b`."""
- # Expand dimensions of a to match the shape of b for element-wise comparison
- expanded_a = a.unsqueeze(0).expand(b.shape[0], *a.shape) # [b0, a0, a1]
- expanded_b = b.unsqueeze(1) # [b0, 1, b1]
-
- # Compare all vector entries of a with all vectors of b
- matches = torch.all(expanded_a == expanded_b, dim=-1)
-
- matching_indices = torch.nonzero(torch.any(matches, dim=1)).squeeze()
-
- if matching_indices.dim() == 0: matching_indices = torch.tensor([matching_indices])
-
- return matching_indices
-
-# %% ../../src/platform/qcircuit_util.ipynb 5
-def get_entanglement_bins(num_of_qubits):
- """Returns all SRV sorted in entangle bins which correspond to a number of entangled qubits."""
- dist_srvs = [x for x in itertools.product(*([[1,2]]*num_of_qubits))]
- dist_srvs = np.array(dist_srvs, dtype=int)[np.sum(dist_srvs, axis=1)!=num_of_qubits+1].tolist()
- dist_srvs = sorted(dist_srvs, key=lambda x: sum(x))
- dist_srvs = np.array(dist_srvs)
-
- entangle = [1] + [scipy.special.comb(num_of_qubits, i, exact=True) for i in range(2, num_of_qubits)]
-
- entanglement_bins = np.split(dist_srvs, np.cumsum(entangle))
-
- ent_bits = [f"{sum(n[0])-num_of_qubits} qubit entangled" for n in entanglement_bins]
-
- return [x.tolist() for x in entanglement_bins], ent_bits
diff --git a/genQC/platform/simulation.py b/genQC/platform/simulation.py
new file mode 100644
index 0000000..a4be881
--- /dev/null
+++ b/genQC/platform/simulation.py
@@ -0,0 +1,55 @@
+"""Class to load and run corresponding backends."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/platform/simulation.ipynb.
+
+# %% auto 0
+__all__ = ['GenericBackendType', 'CircuitBackendType', 'TensorEncodingType', 'is_circuit_type', 'Simulator']
+
+# %% ../../src/platform/simulation.ipynb 2
+from ..imports import *
+from .backends.base_backend import BaseBackend
+
+# %% ../../src/platform/simulation.ipynb 4
+class CircuitBackendType(enum.Enum):
+ QISKIT = enum.auto()
+ CUDAQ = enum.auto()
+ PENNYLANE = enum.auto()
+
+GenericBackendType = Union[CircuitBackendType]
+
+# %% ../../src/platform/simulation.ipynb 5
+class TensorEncodingType(enum.Enum):
+ CIRCUIT = enum.auto()
+
+def is_circuit_type(backend_type): return backend_type in CircuitBackendType
+
+# %% ../../src/platform/simulation.ipynb 7
+class Simulator():
+ """Basic class for handling backend types."""
+
+ def __init__(self, backend: GenericBackendType, *args, **kwargs) -> BaseBackend:
+ match backend:
+ case CircuitBackendType.QISKIT:
+ from genQC.platform.backends.circuits_qiskit import CircuitsQiskitBackend
+ backend = CircuitsQiskitBackend(*args, **kwargs)
+
+ case CircuitBackendType.CUDAQ:
+ from genQC.platform.backends.circuits_cudaq import CircuitsCudaqBackend
+ backend = CircuitsCudaqBackend(*args, **kwargs)
+
+ case CircuitBackendType.PENNYLANE:
+ from genQC.platform.backends.circuits_pennylane import CircuitsPennylaneBackend
+ backend = CircuitsPennylaneBackend(*args, **kwargs)
+
+ case _:
+ raise NotImplementedError(f"Not implemented given backend: {backend}")
+
+ self.backend = backend
+
+
+ def backend_to_genqc(self, *args, **kwargs):
+ return self.backend.backend_to_genqc(*args, **kwargs)
+
+
+ def genqc_to_backend(self, *args, **kwargs):
+ return self.backend.genqc_to_backend(*args, **kwargs)
diff --git a/genQC/platform/simulation/qcircuit_sim.py b/genQC/platform/simulation/qcircuit_sim.py
deleted file mode 100644
index 3084670..0000000
--- a/genQC/platform/simulation/qcircuit_sim.py
+++ /dev/null
@@ -1,102 +0,0 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../src/platform/simulation/qcircuit_sim.ipynb.
-
-# %% auto 0
-__all__ = ['get_number_of_gate_params', 'gate_pool_to_gate_classes', 'instruction_name_to_qiskit_gate', 'schmidt_rank_vector',
- 'rnd_circuit', 'optimize_circuit', 'plot_svr_stat']
-
-# %% ../../../src/platform/simulation/qcircuit_sim.ipynb 2
-from ...imports import *
-from ...config_loader import *
-
-import qiskit.quantum_info as qi
-from qiskit import QuantumCircuit, transpile
-from qiskit.circuit.gate import Gate
-import qiskit.circuit.library as ql
-
-# %% ../../../src/platform/simulation/qcircuit_sim.ipynb 4
-def get_number_of_gate_params(gate_cls):
- return gate_cls.__init__.__code__.co_argcount - len(gate_cls.__init__.__defaults__) - 1 # python: gives you the number of any arguments BEFORE *args, minus ones that have a default, -1 for self parameter of classes
-
-# %% ../../../src/platform/simulation/qcircuit_sim.ipynb 5
-def gate_pool_to_gate_classes(gate_pool: list[Gate]):
- """Creates a vocabulary from a gate pool."""
- classes = {}
-
- for i,cls in enumerate(gate_pool):
- num_of_paramters = get_number_of_gate_params(cls)
- name = cls(*[0]*num_of_paramters).name
- classes[name] = (i+1)
-
- return classes
-
-# %% ../../../src/platform/simulation/qcircuit_sim.ipynb 6
-def instruction_name_to_qiskit_gate(name: str) -> Gate:
- match name:
- case "swap": name = "Swap"
- case "cp": name = "CPhase"
- case _: name = name.upper()
-
- return get_obj_from_str(f"qiskit.circuit.library.standard_gates.{name}Gate")
-
-# %% ../../../src/platform/simulation/qcircuit_sim.ipynb 7
-def schmidt_rank_vector(densityMatrix: qi.DensityMatrix):
- """Return the SRV of a `qi.DensityMatrix`."""
- systems_cnt = len(densityMatrix.dims())
- total_trace = set(range(systems_cnt))
- rank_vector = []
-
- for i in range(systems_cnt):
- trace = list(total_trace - {i})
- red_densityMatrix = qi.partial_trace(densityMatrix, trace)
- # r = np.count_nonzero(np.linalg.eigvals(red_densityMatrix) > 1e-14) # was slower during testing
- r = np.linalg.matrix_rank(red_densityMatrix, hermitian=True).item()
- rank_vector.append(r)
-
- return rank_vector
-
-# %% ../../../src/platform/simulation/qcircuit_sim.ipynb 8
-def rnd_circuit(num_of_qubits, num_of_gates, gate_pool: list[Gate], rng):
- """Create a random circuit."""
- qc = QuantumCircuit(num_of_qubits)
- gate_indices = rng.choice(len(gate_pool), num_of_gates)
-
- for gate_index in gate_indices:
- gate_qiskit_class = gate_pool[gate_index]
-
- num_of_paramters = get_number_of_gate_params(gate_qiskit_class)
- params = rng.uniform(low=0, high=2*np.pi, size=num_of_paramters) if num_of_paramters > 0 else [] # random between 0 and 2pi
-
- gate = gate_qiskit_class(*params)
- act_qubits = rng.choice(num_of_qubits, gate.num_qubits, replace=False) # order: (*act_qubits)=(*control_qubits, *target_qubits)
- qc.append(gate, [*act_qubits], [])
-
- return qc
-
-# %% ../../../src/platform/simulation/qcircuit_sim.ipynb 9
-def optimize_circuit(qc: QuantumCircuit, gate_pool: list[Gate], optimization_level=2):
- """Use qiskit.compiler.transpile to optimize a circuit."""
- basis_gates = gate_pool_to_gate_classes(gate_pool).keys()
-
- while optimization_level > 0:
- try:
- qc_opt = transpile(qc, optimization_level=optimization_level, basis_gates=basis_gates) #target=target
- return qc_opt
- except Exception as er: pass
-
- optimization_level -= 1
-
- return qc
-
-# %% ../../../src/platform/simulation/qcircuit_sim.ipynb 11
-def plot_svr_stat(num_of_qubits, min_gates, max_gates, gs, samples, sort=False, opt=True, rng=np.random.default_rng()):
- svr_list = list()
- for i in range(samples):
- qc = rnd_circuit(num_of_qubits, rng.integers(min_gates, max_gates+1), gs, rng)
- if opt: qc = optimize_circuit(qc, gs)
- svr = schmidt_rank_vector(qi.DensityMatrix(qc))
- if sort: svr = sorted(svr)
- svr_list.append(svr)
- df = pd.DataFrame(data={"svr":svr_list})
- cnts = df['svr'].value_counts(normalize=True)
- for n,v in zip(cnts.index, cnts.values): print(f"{n}: {v*100:.1f}%")
- df['svr'].value_counts().plot(kind='bar')
diff --git a/genQC/platform/tokenizer/__init__.py b/genQC/platform/tokenizer/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/genQC/platform/tokenizer/base_tokenizer.py b/genQC/platform/tokenizer/base_tokenizer.py
new file mode 100644
index 0000000..09f79c8
--- /dev/null
+++ b/genQC/platform/tokenizer/base_tokenizer.py
@@ -0,0 +1,36 @@
+"""Base class of corresponding tokenizers."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../src/platform/tokenizer/base_tokenizer.ipynb.
+
+# %% auto 0
+__all__ = ['Vocabulary', 'VocabularyInverse', 'invert_vocabulary', 'BaseTokenizer']
+
+# %% ../../../src/platform/tokenizer/base_tokenizer.ipynb 2
+from ...imports import *
+
+# %% ../../../src/platform/tokenizer/base_tokenizer.ipynb 3
+Vocabulary = dict[str, int] | dict[Any, int]
+VocabularyInverse = dict[int, str] | dict[int, Any]
+
+def invert_vocabulary(vocabulary: Vocabulary) -> VocabularyInverse:
+ vocabulary_inverse = {token:gate for gate, token in vocabulary.items()}
+ return vocabulary_inverse
+
+# %% ../../../src/platform/tokenizer/base_tokenizer.ipynb 4
+class BaseTokenizer(abc.ABC):
+
+ def __init__(self, vocabulary: Vocabulary) -> None:
+ self.vocabulary = vocabulary
+ self.vocabulary_inverse = invert_vocabulary(vocabulary)
+
+ @abc.abstractmethod
+ def tokenize(self, *args, **kwargs):
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def encode(self, *args, **kwargs):
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def decode(self, *args, **kwargs):
+ raise NotImplementedError()
diff --git a/genQC/platform/tokenizer/circuits_tokenizer.py b/genQC/platform/tokenizer/circuits_tokenizer.py
new file mode 100644
index 0000000..d36de3e
--- /dev/null
+++ b/genQC/platform/tokenizer/circuits_tokenizer.py
@@ -0,0 +1,143 @@
+"""Class to tokenize quantum circuits. Encode and decode quantum circuits into and from tensor representations."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../src/platform/tokenizer/circuits_tokenizer.ipynb.
+
+# %% auto 0
+__all__ = ['CircuitTokenizer']
+
+# %% ../../../src/platform/tokenizer/circuits_tokenizer.ipynb 2
+from ...imports import *
+from .base_tokenizer import BaseTokenizer, Vocabulary
+from ..circuits_instructions import CircuitInstructions
+
+# %% ../../../src/platform/tokenizer/circuits_tokenizer.ipynb 3
+class CircuitTokenizer(BaseTokenizer):
+
+ def __init__(self, vocabulary: Vocabulary, sign_labels: Optional[dict[str, int]] = None) -> None:
+ if 0 in vocabulary.values():
+ print(f"[WARNING]: The value 0 is reserved for background tokens, i.e. qubit time position which are not effected by gates.")
+ print(f"[WARNING]: Automatically incrementing all vocabulary values by one ...")
+ vocabulary = {k:v+1 for k,v in vocabulary.items()}
+ assert 0 not in vocabulary.values()
+
+ super().__init__(vocabulary)
+ self.sign_labels = default(sign_labels, {"control_nodes": -1, "target_nodes": +1})
+
+ def tokenize(self, instructions: CircuitInstructions) -> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]:
+ """Convert given instructions to a tensor. Identical to `CircuitTokenizer.encode`."""
+ return self.encode(instructions=instructions)
+
+ def encode(self,
+ instructions: CircuitInstructions,
+ max_gates: Optional[int] = None,
+ return_params_tensor: bool = True,
+ params_4pi_normalization: bool = True,
+ randomize_params: bool = False) -> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]:
+ """Convert given `CircuitInstructions` to a `torch.Tensor`."""
+
+ assert len(instructions.tensor_shape) == 2
+ num_of_qubits, time = instructions.tensor_shape
+ max_gates = default(max_gates, time)
+
+ tensor = torch.zeros((num_of_qubits, max_gates), dtype=torch.int32)
+ params = []
+
+ for t, instruction in zip(range(max_gates), instructions.data): # this way we limit the number of gates even if there are more instructions
+
+ if instruction.name not in self.vocabulary: raise Warning(f"`{instruction.name}` not in vocabulary.")
+
+ params.append(instruction.params)
+
+ gate_id = self.vocabulary[instruction.name]
+
+ control_qubits, target_qubits = instruction.control_nodes, instruction.target_nodes
+
+ for bit in control_qubits:
+ tensor[bit, t] = gate_id * self.sign_labels["control_nodes"]
+
+ for bit in target_qubits:
+ tensor[bit, t] = gate_id * self.sign_labels["target_nodes"]
+
+ if return_params_tensor:
+ num_of_max_params = max([0] + [len(para) for para in params])
+ params_tensor = torch.zeros((num_of_max_params, max_gates), dtype=torch.float32)
+
+ for t, para in enumerate(params):
+ para = torch.tensor(para)
+
+ if randomize_params:
+ para = 2.0*torch.rand_like(para) - 1.0 # rnd [-1, 1]
+
+ elif params_4pi_normalization:
+ para = para % (4.0*np.pi) # limit to [0, 4pi]
+ para = (para-2.0*np.pi) / (2.0*np.pi) # [0, 4pi] to [-1, +1]
+
+ params_tensor[:len(para), t] = para
+
+ return tensor, params_tensor
+ return tensor
+
+ def decode(self,
+ tensor: torch.Tensor,
+ params_tensor: Optional[torch.Tensor] = None,
+ params_4pi_normalization: bool = True,
+ ignore_errors: bool = False,
+ place_error_placeholders: bool = False) -> CircuitInstructions:
+ """Convert a given `torch.Tensor` to `CircuitInstructions`."""
+
+ assert tensor.dim() == 2, f"{tensor.shape=}"
+ num_of_qubits, time = tensor.shape
+
+ instructions = CircuitInstructions(tensor_shape=tensor.shape)
+
+ for t in range(time):
+ enc_time_slice = tensor[:, t] # contains all bits at time t
+
+ _gate_placed = False
+
+ for gate_index, gate in self.vocabulary_inverse.items():
+
+ target_nodes = (enc_time_slice == (self.sign_labels["target_nodes"] * gate_index)).nonzero(as_tuple=True)[0]
+ control_nodes = (enc_time_slice == (self.sign_labels["control_nodes"] * gate_index)).nonzero(as_tuple=True)[0]
+
+ _gate_placed = False
+
+ if target_nodes.nelement() > 0:
+ params = []
+ if exists(params_tensor):
+ params = params_tensor[:, t]
+ if params_4pi_normalization:
+ params = (params+1.0) * 2.0*np.pi # [-1, 1] to [0, 4pi]
+ params = params.tolist()
+
+ instructions.add_instruction(gate, control_nodes.tolist(), target_nodes.tolist(), params)
+ _gate_placed = True
+
+ break #break on first hit, per def only one gate allowed per t
+
+ elif control_nodes.nelement() > 0: # no target but control means error
+ if not ignore_errors:
+ raise RuntimeError("target_nodes.nelement() <= 0 but control_nodes.nelement() > 0")
+
+ if not _gate_placed and place_error_placeholders:
+ # note we place a h gate with no qubits, so this is always an error
+ instructions.add_instruction("h", [], [], [])
+
+ #else # we are fine with tensors that have time steps with no action!
+
+ return instructions
+
+ @staticmethod
+ def get_parametrized_tokens(vocabulary: Vocabulary) -> List[int]:
+ parametrized_names = "rx ry rz phase cp crx cry crz u u2 u3".split()
+ non_parametrized_names = "x y z h cx cy cz ch ccx swap s sdg t tdg".split()
+
+ parametrized_tokens = []
+ for name, token in vocabulary.items():
+
+ if name in parametrized_names:
+ parametrized_tokens.append(token)
+ elif name not in non_parametrized_names:
+ raise NotImplementedError(f"Unknown gate {name}! Please add it to the known list.")
+
+ return parametrized_tokens
diff --git a/genQC/platform/tokenizer/tensor_tokenizer.py b/genQC/platform/tokenizer/tensor_tokenizer.py
new file mode 100644
index 0000000..21497ed
--- /dev/null
+++ b/genQC/platform/tokenizer/tensor_tokenizer.py
@@ -0,0 +1,362 @@
+"""Class to further tokenize tensor representations."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../src/platform/tokenizer/tensor_tokenizer.ipynb.
+
+# %% auto 0
+__all__ = ['GatePairTokenizer', 'sort_config', 'get_topk_depth_unpacked']
+
+# %% ../../../src/platform/tokenizer/tensor_tokenizer.ipynb 2
+from ...imports import *
+from .base_tokenizer import BaseTokenizer, Vocabulary
+
+# %% ../../../src/platform/tokenizer/tensor_tokenizer.ipynb 4
+class GatePairTokenizer(BaseTokenizer):
+
+ def __init__(self, unique_class_values, zero_token, padding_token, device):
+ super().__init__({})
+
+ self.padding_token = padding_token
+ self.not_gates_tokens = torch.tensor([zero_token, padding_token]).to(device)
+
+ self.current_tokens = torch.tensor(unique_class_values, device=device)
+ self._current_depth = 0
+
+ self.token_lookup = {} #reduced forms, used for gadget extraction
+ self.token_lookup_raw = {} #the raw form, used for encoding
+
+ self.token_depth = {tok:0 for tok in self.current_tokens.cpu().tolist()}
+ self.token_cnts = {}
+
+ def learn(self, tensors, max_depth, max_iters):
+ # loop over get bets and then replace
+
+ current_tensor = tensors
+ self._current_depth = 0
+
+ for i in tqdm(range(max_iters), total=max_iters):
+
+ overlap_pairs = self.extract_new_gate_overlap_pairs(current_tensor)
+ overlap_pairs_std_form = self.standardize_overlap_pairs(overlap_pairs)
+
+ top_pairs, topv = self.get_topk_pairs(overlap_pairs_std_form, k=1)
+ top_pair = top_pairs[0]
+
+ if top_pair.abs().sum() < 1:
+ print("break: top_pair.abs().sum() < 1")
+ break
+
+ if topv < 2:
+ print("break: no more pair with cnt > 1")
+ break
+
+ current_tensor = self.learn_step(current_tensor, top_pair, topv=topv)
+
+ current_max_depth = max(self.token_depth.values())
+ if current_max_depth > max_depth:
+ print(f"break: max_depth {max_depth} reached")
+ break
+
+ print("break: max_iters reached")
+ return current_tensor
+
+ def to(self, device):
+ self.not_gates_tokens = self.not_gates_tokens.to(device)
+ self.current_tokens = self.current_tokens.to(device)
+
+ for k, v in self.token_lookup.items():
+ self.token_lookup[k] = self.token_lookup[k].to(device)
+
+ for k, v in self.token_lookup_raw.items():
+ self.token_lookup_raw[k] = self.token_lookup_raw[k].to(device)
+
+ return self
+
+ def tokenize(self, tensors):
+ """Identical to `GatePairTokenizer.encode`."""
+ return self.encode(tensors=tensors)
+
+ #---------------------------------------
+ # Replace pairs with new tokens
+
+ def learn_step(self, current_tensor, top_pair, new_tokens: Optional[torch.Tensor] = None, topv: Optional[torch.Tensor] = None):
+
+ top_pair_reduced = top_pair[top_pair.abs().sum(-1)>0].unique_consecutive(dim=0)
+
+ if not_exists(new_tokens):
+ new_tokens = self.current_tokens.max() + 1 + torch.arange(top_pair_reduced.shape[0], device=current_tensor.device)
+ self.current_tokens = torch.cat([self.current_tokens, new_tokens])
+
+ key = tuple(new_tokens.cpu().tolist())
+ self.token_lookup[key] = top_pair_reduced #top_pair[top_pair.sum(-1)>0]
+ self.token_lookup_raw[key] = top_pair
+ self.token_cnts[key] = topv
+
+ _current_depth = max(self.token_depth[k] for k in top_pair_reduced.flatten().cpu().tolist()) + 1
+
+ for tok in new_tokens.cpu().tolist():
+ self.token_depth[tok] = _current_depth
+
+ if _current_depth > self._current_depth:
+ self._current_depth = _current_depth
+ print(f"New depth reached {self._current_depth}")
+ else:
+ assert top_pair_reduced.shape[0] == new_tokens.shape[0]
+
+ # 1) Replace one all even pairs
+ current_overlap_pairs = self.extract_current_gate_overlap_pairs(current_tensor, odd_pairs=False)
+ current_tensor = self.replace_current_overlap_pairs(current_tensor, current_overlap_pairs, top_pair, top_pair_reduced, new_tokens, odd_pairs=False)
+
+ # 2) Then Replace one all odd pairs
+ current_overlap_pairs = self.extract_current_gate_overlap_pairs(current_tensor, odd_pairs=True)
+ current_tensor = self.replace_current_overlap_pairs(current_tensor, current_overlap_pairs, top_pair, top_pair_reduced, new_tokens, odd_pairs=True)
+
+ return current_tensor
+
+ def extract_current_gate_overlap_pairs(self, current_tensor, odd_pairs: bool = True):
+ # Extract overlap_pairs
+ # ToDo optimize loops
+
+ seq = current_tensor.shape[-1]
+ seq_half = seq // 2
+ assert seq % 2 == 0
+
+ overlap_pairs = []
+ for current_tensor_i in current_tensor:
+ _overlap_pairs = []
+
+ if odd_pairs:
+ for t in range(seq_half-1):
+ _overlap_pairs.append(current_tensor_i[:, 1+2*t:1+2*(t+1)])
+ else:
+ for t in range(seq_half):
+ _overlap_pairs.append(current_tensor_i[:, 2*t:2*(t+1)])
+
+ overlap_pairs.append(torch.stack(_overlap_pairs))
+
+ overlap_pairs = torch.stack(overlap_pairs)
+ return overlap_pairs
+
+ def replace_current_overlap_pairs(self, current_tensor, overlap_pairs, top_pair, top_pair_reduced, new_tokens, odd_pairs):
+
+ overlap_pairs_std = self.standardize_overlap_pairs(overlap_pairs)
+ is_top_overlap_pair = (overlap_pairs_std==top_pair).all(dim=(-1,-2), keepdim=False)
+
+ new_tensor = torch.full_like(current_tensor, self.padding_token)
+
+ for i in range(is_top_overlap_pair.shape[0]): #ToDo: this loop can be put in parallel! is batch dim
+ t = 1 if odd_pairs else 0
+
+ for j in range(is_top_overlap_pair.shape[1]):
+
+ if is_top_overlap_pair[i, j]: #replace
+
+ new_col = torch.zeros((current_tensor.shape[1]), dtype=overlap_pairs.dtype, device=overlap_pairs.device)
+
+ for new_token, top_pair_reduced_i in zip(new_tokens, top_pair_reduced):
+ ind = (overlap_pairs[i, j]==top_pair_reduced_i).all(-1)
+ new_col = torch.where(ind, new_token, new_col)
+
+ new_col = new_col.unsqueeze(-1)
+
+ tp1 = t + 1
+
+ else: #just copy old
+ new_col = overlap_pairs[i, j]
+ tp1 = t + 2
+
+ new_tensor[i, :, t:tp1] = new_col
+ t = tp1
+
+ if odd_pairs:
+ # copy first and last col
+ new_tensor[..., 0] = current_tensor[..., 0]
+ new_tensor[..., -1] = current_tensor[..., -1]
+
+ return new_tensor
+
+ #---------------------------------------
+ # Find new pairs
+
+ def extract_new_gate_overlap_pairs(self, current_tensor):
+ #current_tensor = current_tensor.abs()
+
+ isgate_token = 1 - torch.isin(current_tensor.abs(), self.not_gates_tokens.to(current_tensor.device)).int()
+
+ # These are postions of the pairs (therefore shape-1) in which we have an overlap
+ overlaps = isgate_token[..., :-1] + isgate_token[..., 1:]
+ overlaps = (overlaps>1).int()
+
+ # Number of overlaps two gates have! we can say here only take 2 overlaps, or min 2, or min 1, eg.. -> 0 means parallel!!
+ overlaps_cnt = torch.count_nonzero(overlaps, dim=1)
+ overlaps_ind = (overlaps_cnt>0)
+
+ # Extract overlap_pairs
+ # ToDo optimize loops
+
+ overlap_pairs = []
+ for current_tensor_i, overlaps_ind_i in zip(current_tensor, overlaps_ind):
+ for t in range(current_tensor_i.shape[-1]-1):
+ if overlaps_ind_i[t]:
+ overlap_pairs.append(current_tensor_i[:, t:t+2])
+
+ overlap_pairs = torch.stack(overlap_pairs)
+ return overlap_pairs
+
+ def standardize_overlap_pairs(self, overlap_pairs):
+ # Now we convert to std form, where the 1st gate gives the main order and the 2nd the secondory, this should remove all(?) redundant combinations!
+
+ # 1) sort inner SECOND gate such that gate 2 is always on top
+ inner_sorted_gate2, inner_sorted_gate2_indices = torch.sort(overlap_pairs[..., 1], dim=-1, descending=True, stable=False)
+ inner_sorted_gate1 = torch.gather(overlap_pairs[..., 0], dim=-1, index=inner_sorted_gate2_indices)
+
+ inner_overlap_pairs = torch.stack((inner_sorted_gate1, inner_sorted_gate2), dim=-1)
+
+ # 2) sort outer FISRT gate such that gate 1 is always on top, NOTE WE NEED STABLE SORT TO CONSERVE INNER ORDER
+ outer_sorted_gate1, outer_sorted_gate1_indices = torch.sort(inner_overlap_pairs[..., 0], dim=-1, descending=True, stable=True)
+ outer_sorted_gate2 = torch.gather(inner_overlap_pairs[..., 1], dim=-1, index=outer_sorted_gate1_indices)
+
+ overlap_pairs_std_form = torch.stack((outer_sorted_gate1, outer_sorted_gate2), dim=-1)
+
+ return overlap_pairs_std_form.contiguous()
+
+ def get_topk_pairs(self, overlap_pairs, k):
+ # Now we can easily count the unique valid pairs!
+ pot_pairs, pot_pairs_cnts = overlap_pairs.unique(dim=0, return_counts=True)
+
+ # Get topk best pairs
+ topv, topi = torch.topk(pot_pairs_cnts, k)
+ top_pairs = pot_pairs[topi]
+
+ return top_pairs, topv
+
+ #---------------------------------------
+ # Encoding
+
+ def encode(self, tensors):
+ # just replay all the pair replacements from learn, i.e. the vocab
+
+ s = tensors.shape[1]
+ current_tensor = tensors
+
+ for new_tokens, top_pair in tqdm(self.token_lookup_raw.items()):
+ top_pair = self.standardize_vocab_pair(top_pair, s, sort=True)
+ new_tokens = torch.tensor(new_tokens, device=top_pair.device, dtype=top_pair.dtype)
+
+ current_tensor = self.learn_step(current_tensor, top_pair, new_tokens=new_tokens)
+
+ return current_tensor
+
+ def standardize_vocab_pair(self, vocab_pair, s, sort: bool = True):
+
+ if vocab_pair.shape[0]<2: # repeat for special gadgets which have full symetric sequential connection
+ vocab_pair = vocab_pair.repeat(2, 1)
+
+ vocab_pair = F.pad(vocab_pair, [0, 0, 0, s-vocab_pair.shape[0]]) # pad to full systemsize to have nice plotting
+
+ if sort:
+ vocab_pair = self.standardize_overlap_pairs(vocab_pair)
+
+ return vocab_pair.contiguous()
+
+ #---------------------------------------
+ # Decoding
+
+ def unpack_col(self, col):
+ # col is [s, 1]
+ s, _ = col.shape
+
+ current_tokens = col.unique()
+ current_tokens = current_tokens[current_tokens!=0]
+ k = tuple(current_tokens.tolist())
+
+ if k in self.token_lookup:
+
+ # Unpack one col
+ unpacked = torch.zeros((s, 2), dtype=col.dtype, device=col.device)
+ new_config = self.token_lookup[k]
+
+ for current_token, new_config_i in zip(current_tokens, new_config):
+ ind = (col==current_token)
+ unpacked = torch.where(ind, new_config_i, unpacked)
+
+ # Repeat unpacking for both new cols
+ col1, col2 = unpacked.chunk(2, dim=-1)
+
+ unpacked1 = self.unpack_col(col1)
+ unpacked2 = self.unpack_col(col2)
+
+ unpacked = torch.cat([unpacked1, unpacked2], dim=-1)
+ return unpacked
+
+ return col
+
+ def decode(self, tensor, cut_padding: bool = False):
+ # split into cols we unpack, then recursively
+ # tensor ... [s, t]
+ assert tensor.dim() == 2
+
+ cols = tensor.chunk(tensor.shape[-1], dim=-1)
+ unpacked = torch.cat([self.unpack_col(col) for col in cols], dim=-1)
+
+ if cut_padding:
+ # Cut from right as this was added padding in packing
+ unpacked = unpacked[..., :tensor.shape[-1]]
+
+ return unpacked
+
+# %% ../../../src/platform/tokenizer/tensor_tokenizer.ipynb 6
+def sort_config(vocab_config):
+ """Sort a vocab_config for nicer plotting."""
+
+ t = vocab_config.shape[-1]
+ all_inds = set(range(t))
+
+ # Sort one ind, gather the rest
+ for i in reversed(range(t)):
+ gather_inds = all_inds - {i}
+
+ sorted_gates = [None] * t
+
+ sorted_gate_i, sorted_gate_i_indices = torch.sort(vocab_config[..., i], dim=-1, descending=True, stable=True)
+ sorted_gates[i] = sorted_gate_i
+
+ for gather_ind in gather_inds:
+ sorted_gates[gather_ind] = torch.gather(vocab_config[..., gather_ind], dim=-1, index=sorted_gate_i_indices)
+
+ vocab_config = torch.stack(sorted_gates, dim=-1)
+
+ return vocab_config
+
+# %% ../../../src/platform/tokenizer/tensor_tokenizer.ipynb 7
+def get_topk_depth_unpacked(gate_pair_tokenizer, s, use_raw=False, standardize=True):
+ """Useful for plotting."""
+
+ # Sort into depths
+ unpacked_vocab_configs_depths = {}
+ unpacked_vocab_configs_cnts_depths = {}
+
+ if use_raw:
+ iters = zip(gate_pair_tokenizer.token_lookup_raw.items(), gate_pair_tokenizer.token_cnts.values())
+ else:
+ iters = zip(gate_pair_tokenizer.token_lookup.items(), gate_pair_tokenizer.token_cnts.values())
+
+ for (vocab_tokens, vocab_config), vocab_config_cnts in tqdm(iters, total=len(gate_pair_tokenizer.token_cnts)):
+
+ tok = vocab_tokens[0]
+ token_depth = gate_pair_tokenizer.token_depth[tok]
+
+ if standardize:
+ vocab_config = gate_pair_tokenizer.standardize_vocab_pair(vocab_config, s, sort=False)
+ unpacked_vocab_config = gate_pair_tokenizer.decode(vocab_config)
+
+ #--------
+ unpacked_vocab_config = sort_config(unpacked_vocab_config)
+
+ if token_depth not in unpacked_vocab_configs_depths:
+ unpacked_vocab_configs_depths[token_depth] = []
+ unpacked_vocab_configs_cnts_depths[token_depth] = []
+
+ unpacked_vocab_configs_depths[token_depth].append(unpacked_vocab_config)
+ unpacked_vocab_configs_cnts_depths[token_depth].append(vocab_config_cnts)
+
+ return unpacked_vocab_configs_depths, unpacked_vocab_configs_cnts_depths
diff --git a/genQC/printing.py b/genQC/printing.py
deleted file mode 100644
index 0ba6250..0000000
--- a/genQC/printing.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../src/printing.ipynb.
-
-# %% auto 0
-__all__ = ['display_colums', 'ndarray_to_latex', 'tensor_to_latex', 'print_markdown', 'print_table']
-
-# %% ../src/printing.ipynb 3
-from .imports import *
-from ipywidgets import widgets
-if IN_NOTEBOOK: from IPython.display import Markdown
-
-# %% ../src/printing.ipynb 4
-def display_colums(display_list, num_col=3):
-
- outputs = [widgets.Output() for i in range(num_col)]
-
- for i in range(len(display_list)//num_col+1):
-
- ds = display_list[i*num_col:(i+1)*num_col]
-
- for d,output in zip(ds,outputs):
- with output:
- display(d)
-
- columns = widgets.HBox(outputs)
- display(columns)
-
-# %% ../src/printing.ipynb 8
-def ndarray_to_latex(arr):
- """Returns a LaTeX `{pmatrix*}[r]` as a string"""
- if len(arr.shape) > 2: raise ValueError('pmatrix can at most display two dimensions')
- lines = str(arr).replace('[', '').replace(']', '').splitlines()
- rv = [r'\begin{pmatrix*}[r]']
- rv += [' ' + ' & '.join(l.split()) + r'\\' for l in lines]
- rv += [r'\end{pmatrix*}']
- return '\n'.join(rv)
-
-# %% ../src/printing.ipynb 9
-def tensor_to_latex(tensor):
- """Returns a `LaTeX {pmatrix*}[r]` as a string """
- if len(tensor.shape) > 2: raise ValueError('pmatrix can at most display two dimensions')
- lines = str(tensor.numpy()).replace('[', '').replace(']', '').splitlines()
- rv = [r'\begin{pmatrix*}[r]']
- rv += [' ' + ' & '.join(l.split()) + r'\\' for l in lines]
- rv += [r'\end{pmatrix*}']
- return '\n'.join(rv)
-
-# %% ../src/printing.ipynb 11
-def print_markdown(text, print_raw=False):
- if IN_NOTEBOOK and not print_raw: display(Markdown(text))
- else: print(text)
-
-# %% ../src/printing.ipynb 14
-def print_table(col_headings: list, data: np.array, row_headings=None, print_raw=False):
- assert len(col_headings) == data.shape[1]
- if row_headings is not None: assert len(row_headings) == data.shape[0]
-
- #--------------------------------
- head = ""
- if row_headings is not None: head = "| " + head
-
- for col_heading in col_headings: head += f"|{col_heading}"
- head += "|\n"
-
- #--------------------------------
- seperator = ""
- if row_headings is not None: seperator = "|--"
-
- for col_heading in col_headings: seperator += "|--"
- seperator += "|\n"
-
- #--------------------------------
- body = ""
- for i, row in enumerate(data):
- body_row = ""
- for x in row:
- body_row += f"|{x:.2f}"
-
- if row_headings is not None:
- body_row = f"|{row_headings[i]}" + body_row
-
- body += body_row + "|\n"
-
- #--------------------------------
- table = head + seperator + body
-
- print_markdown(table, print_raw)
diff --git a/genQC/scheduler/scheduler.py b/genQC/scheduler/scheduler.py
index fc8c98a..6269434 100644
--- a/genQC/scheduler/scheduler.py
+++ b/genQC/scheduler/scheduler.py
@@ -1,30 +1,54 @@
+"""Base class for schedulers."""
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/scheduler/scheduler.ipynb.
# %% auto 0
__all__ = ['Scheduler']
-# %% ../../src/scheduler/scheduler.ipynb 3
+# %% ../../src/scheduler/scheduler.ipynb 2
from ..imports import *
-from ..util import virtual
-from ..config_loader import *
+from ..utils.config_loader import *
-# %% ../../src/scheduler/scheduler.ipynb 4
-class Scheduler:
+# %% ../../src/scheduler/scheduler.ipynb 3
+class Scheduler(abc.ABC):
"""Base class for all diffusion schedulers"""
+
def __init__(self):
pass
- @virtual
+ @abc.abstractmethod
def set_timesteps(self): pass
- @virtual
+ @abc.abstractmethod
def step(self): pass
- @virtual
+ @abc.abstractmethod
def add_noise(self): pass
+ @abc.abstractmethod
+ def to(self): pass
#---------------------------------------
+ @staticmethod
+ def from_config(config, device: torch.device, save_path: str=None, verbose=True, silent=False):
+ """Use this if we have a loaded config."""
+
+ _config = copy.deepcopy(config)
+
+ if exists(device): _config["device"] = device # for loading sub-models
+ else: device = _config.pop("device", "cpu")
+
+ if "beta_schedule" in _config["params"]:
+ beta_schedule = _config["params"]["beta_schedule"]
+
+ if "path:" in beta_schedule:
+ _config["params"]["beta_schedule"] = "path:" + save_path + beta_schedule[len("path:"):]
+
+ scheduler = instantiate_from_config(_config)
+ return scheduler
+
+ #---------------------------------------
+
def get_config(self, without_metadata=False):
if not without_metadata:
config = {}
@@ -37,13 +61,20 @@ def get_config(self, without_metadata=False):
return config
@property
- @virtual
+ @abc.abstractmethod
def params_config(self): return None
#---------------------------------------
def unsqueeze_vector_to_shape(self, vec, shape):
- vec = vec.flatten()
- while len(vec.shape) < len(shape):
- vec = vec.unsqueeze(-1)
- return vec
+ return vec.view(*vec.shape, *([1] * (len(shape)-len(vec.shape))) )
+
+ #---------------------------------------
+
+ @classmethod
+ def from_scheduler(cls, scheduler, **kwargs):
+ _kwargs = scheduler.params_config
+ _kwargs = _kwargs | kwargs
+
+ new_scheduler = cls(**_kwargs)
+ return new_scheduler
diff --git a/genQC/scheduler/scheduler_ddim.py b/genQC/scheduler/scheduler_ddim.py
index 5e2742c..9dc654f 100644
--- a/genQC/scheduler/scheduler_ddim.py
+++ b/genQC/scheduler/scheduler_ddim.py
@@ -1,20 +1,24 @@
+"""Denoising diffusion implicit models [(DDIM)](https://arxiv.org/abs/2010.02502)."""
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/scheduler/scheduler_ddim.ipynb.
# %% auto 0
__all__ = ['DDIMSchedulerOutput', 'DDIMScheduler']
-# %% ../../src/scheduler/scheduler_ddim.ipynb 3
+# %% ../../src/scheduler/scheduler_ddim.ipynb 2
from ..imports import *
from .scheduler_ddpm import DDPMScheduler
-# %% ../../src/scheduler/scheduler_ddim.ipynb 4
+# %% ../../src/scheduler/scheduler_ddim.ipynb 3
@dataclass
class DDIMSchedulerOutput:
prev_sample: torch.FloatTensor
pred_original_sample: Optional[torch.FloatTensor] = None
-
+
+# %% ../../src/scheduler/scheduler_ddim.ipynb 4
class DDIMScheduler(DDPMScheduler):
"""A `Scheduler` implementing [(DDIM)](https://arxiv.org/abs/2010.02502)."""
+
def __init__(self,
device: Union[str, torch.device],
num_train_timesteps: int = 1000,
@@ -22,9 +26,11 @@ def __init__(self,
beta_end: float = 0.02,
beta_schedule: str = "linear",
input_perturbation = 0.1,
+ prediction_type = "epsilon",
+ enable_zero_terminal_snr = True,
eta: float = 0
):
- super().__init__(device, num_train_timesteps, beta_start, beta_end, beta_schedule, input_perturbation)
+ super().__init__(device, num_train_timesteps, beta_start, beta_end, beta_schedule, input_perturbation, prediction_type, enable_zero_terminal_snr)
self.eta = eta
#for stable diff ddim
@@ -43,8 +49,8 @@ def params_config(self):
#------------------------------------
# Inference functions
- def set_timesteps(self, num_inference_steps: int):
- super().set_timesteps(num_inference_steps)
+ def set_timesteps(self, num_inference_steps: Optional[int] = None, timesteps: Optional[torch.Tensor] = None):
+ super().set_timesteps(num_inference_steps=num_inference_steps, timesteps=timesteps)
self.timesteps += self.steps_offset
clamp_style = None # one of: None, "static", "dynamic"
@@ -52,11 +58,13 @@ def set_timesteps(self, num_inference_steps: int):
def step(self,
model_output: torch.FloatTensor,
timesteps: Union[int, torch.IntTensor],
- sample: torch.FloatTensor
+ sample: torch.FloatTensor,
+ uncond_model_output: torch.FloatTensor = None # for CFG++
) -> DDIMSchedulerOutput:
"""Denoising step"""
prev_timesteps = timesteps - self.num_train_timesteps // self.num_inference_steps
+ # prev_timestep = torch.clamp(prev_timestep, 0, self.num_train_timesteps - 1) # NEW
#get variance sched
alphas_cumprod = self.unsqueeze_vector_to_shape(self.alphas_cumprod[timesteps], sample.shape)
@@ -70,10 +78,29 @@ def step(self,
#calc vars
betas_cumprod = 1.0 - alphas_cumprod
betas_cumprod_tm1 = 1.0 - alphas_cumprod_tm1
+
+ uncond_model_output = default(uncond_model_output, model_output)
+
+ if self.prediction_type == "epsilon":
+ #estimate predicted sample
+ x0 = (sample - betas_cumprod.sqrt() * model_output) / alphas_cumprod.sqrt()
+ eps = uncond_model_output #model_output
- #estimate predicted sample
- x0 = (sample - betas_cumprod.sqrt() * model_output) / alphas_cumprod.sqrt()
+ elif self.prediction_type == "v-type":
+ a = alphas_cumprod.sqrt()
+ b = betas_cumprod.sqrt()
+
+ x0 = a * sample - b * model_output
+ # eps = a * model_output + b * sample
+ eps = a * uncond_model_output + b * sample
+
+ elif self.prediction_type == "x0":
+ x0 = model_output
+ eps = (sample - alphas_cumprod.sqrt() * uncond_model_output) / betas_cumprod.sqrt()
+ else:
+ raise NotImplementedError(f"{self.prediction_type} is not implemented for {self.__class__}.step()")
+
if self.clamp_style == None: pass
elif self.clamp_style == "static": x0 = torch.clamp(x0, -1, 1)
elif self.clamp_style == "dynamic": raise NotImplementedError("clamp_style == 'dynamic'")
@@ -85,14 +112,12 @@ def step(self,
std = self.eta * variance.sqrt()
#direction to xt
- dir_xt = ( betas_cumprod_tm1 - std.square() ).sqrt() * model_output
+ dir_xt = (betas_cumprod_tm1 - std.square()).sqrt() * eps
#sample noise
- noise = torch.randn(model_output.shape, device=self.device)
-
+ noise = torch.randn_like(x0)
+
#estimate the prev sample
xtm1 = alphas_cumprod_tm1.sqrt() * x0 + dir_xt + std * noise
- # print(f"{timesteps=} {prev_timesteps=} ;;; x0: {x0.mean()}+-{x0.std()} xtm1: {xtm1.mean()}+-{xtm1.std()}")
-
return DDIMSchedulerOutput(prev_sample=xtm1, pred_original_sample=x0)
diff --git a/genQC/scheduler/scheduler_ddpm.py b/genQC/scheduler/scheduler_ddpm.py
index fd9c4e3..ac3d79d 100644
--- a/genQC/scheduler/scheduler_ddpm.py
+++ b/genQC/scheduler/scheduler_ddpm.py
@@ -1,18 +1,22 @@
+"""Denoising diffusion probabilistic models [(DDPM)](https://arxiv.org/abs/2006.11239): reverse beta is fixed and diagonal."""
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/scheduler/scheduler_ddpm.ipynb.
# %% auto 0
__all__ = ['DDPMSchedulerOutput', 'DDPMScheduler']
-# %% ../../src/scheduler/scheduler_ddpm.ipynb 3
+# %% ../../src/scheduler/scheduler_ddpm.ipynb 2
from ..imports import *
from .scheduler import Scheduler
+from ..utils.config_loader import load_tensor
-# %% ../../src/scheduler/scheduler_ddpm.ipynb 4
+# %% ../../src/scheduler/scheduler_ddpm.ipynb 3
@dataclass
class DDPMSchedulerOutput:
prev_sample: torch.FloatTensor
pred_original_sample: Optional[torch.FloatTensor] = None
-
+
+# %% ../../src/scheduler/scheduler_ddpm.ipynb 4
class DDPMScheduler(Scheduler):
"""A `Scheduler` implementing [(DDPM)](https://arxiv.org/abs/2006.11239)"""
@@ -24,55 +28,82 @@ def __init__(self,
beta_start: float = 0.0001,
beta_end: float = 0.02,
beta_schedule: str = "linear",
- input_perturbation = 0.1
+ input_perturbation = 0.1,
+ prediction_type = "epsilon",
+ enable_zero_terminal_snr = True
):
super().__init__()
self.device = device
self.num_train_timesteps = torch.tensor(num_train_timesteps)
self.num_inference_steps = torch.tensor(num_train_timesteps)
- self.beta_start = beta_start
- self.beta_end = beta_end
+ self.beta_start = beta_start
+ self.beta_end = beta_end
self.beta_schedule = beta_schedule
-
+
self.timesteps = torch.from_numpy(np.arange(0, num_train_timesteps)[::-1].copy().astype(np.int64)) #careful is defined reversed for easy denoising looping
-
+
+ self.input_perturbation = input_perturbation # Input Perturbation Reduces Exposure Bias in Diffusion Models, https://arxiv.org/pdf/2301.11706.pdf
+ self.prediction_type = prediction_type # one of "epsilon", "v-type", "x0", "mu"
+
+ if self.prediction_type not in ["epsilon", "v-type", "x0"]:
+ raise NotImplementedError(f"{self.prediction_type} does is not implemented for {self.__class__}")
+
+ #-----------
+
if beta_schedule == "linear":
self.betas = torch.linspace(beta_start, beta_end, num_train_timesteps, dtype=torch.float32)
- elif beta_schedule == "linear_sqrt":
+ elif beta_schedule == "linear_sqrt": #LDM
self.betas = torch.linspace(beta_start ** 0.5, beta_end ** 0.5, num_train_timesteps, dtype=torch.float32) ** 2
- elif beta_schedule == "cos_alpha": #cosine-based-variance
- f = lambda t: np.cos((t/self.num_train_timesteps + 0.008)*np.pi/2.016)**2
+ elif beta_schedule == "cos_alpha": #cosine-based-variance
+ #print("[INFO]: using cos_alpha beta-schedule, ignoring beta_start and beta_end!")
+ f = lambda t: np.cos((t/self.num_train_timesteps + 0.008)*np.pi/2.016)**2 # is alpha_bar
+ _betas = []
+ for i in range(self.num_train_timesteps):
+ b = 1.0-(f(i+1.0)/f(i))
+ if not enable_zero_terminal_snr: # v-type allows zero terminal SNR
+ b = min(b, 0.999) # clipping disables zero terminal SNR
+ _betas.append(b)
+ self.betas = torch.tensor(_betas, dtype=torch.float32)
+
+ elif beta_schedule == "cos_alpha4": #cosine-based-variance
+ #print("[INFO]: using cos_alpha4 beta-schedule, ignoring beta_start and beta_end!")
+ f = lambda t: np.cos((t/self.num_train_timesteps + 0.008)*np.pi/2.016)**4 # is alpha_bar
_betas = []
- for i in range(self.num_train_timesteps):
- _betas.append(min(1.0-(f(i+1.0)/f(i)),0.999))
- self.betas = torch.tensor(_betas, dtype=torch.float32)
+ for i in range(self.num_train_timesteps):
+ b = 1.0-(f(i+1.0)/f(i))
+ if not enable_zero_terminal_snr: # v-type allows zero terminal SNR
+ b = min(b, 0.999) # clipping disables zero terminal SNR
+ _betas.append(b)
+ self.betas = torch.tensor(_betas, dtype=torch.float32)
+
+ elif "path:" in beta_schedule:
+ _save_path = beta_schedule[len("path:"):]
+ self.betas = load_tensor(save_path=_save_path, device=device)["0"]
+
+ print(f"[INFO]: Loaded beta_schedule ({beta_schedule}).")
+
else:
- raise NotImplementedError(f"{beta_schedule} does is not implemented for {self.__class__}")
-
+ raise NotImplementedError(f"{beta_schedule} is not implemented for {self.__class__}")
+
+ #-----------
+
+ if (self.prediction_type in ["v-type", "x0"]) and enable_zero_terminal_snr and (beta_schedule not in ["cos_alpha", "laplace"]): # v-type allows zero terminal SNR
+ self.betas = self.enforce_zero_terminal_snr(self.betas)
+
+ #-----------
+
self.sigmas = torch.sqrt(self.betas)
-
self.alphas = 1.0 - self.betas
- self.alphas_cumprod = torch.cumprod(self.alphas, dim=0)
-
- ## Is this used anymore?
- self.sqrt_alphas = torch.sqrt(self.alphas)
- self.sqrt_alphas_cumprod = torch.sqrt(self.alphas_cumprod)
- self.sqrt_one_minus_alphas_cumprod = torch.sqrt(1 - self.alphas_cumprod)
- ##
-
+ self.alphas_cumprod = torch.cumprod(self.alphas, dim=0) #only do cumprod witj alphas, as betas will go within precision to zero
+
#----------
- # Input Perturbation Reduces Exposure Bias in Diffusion Models
- # https://arxiv.org/pdf/2301.11706.pdf
-
- self.input_perturbation = input_perturbation
- #----------
+ self.to(self.device)
+
- self.to_device(self.device)
-
@property
def params_config(self):
params_config = {}
@@ -81,32 +112,68 @@ def params_config(self):
params_config["beta_start"] = self.beta_start
params_config["beta_end"] = self.beta_end
params_config["beta_schedule"] = self.beta_schedule
- params_config["input_perturbation"] = self.input_perturbation
+ params_config["input_perturbation"] = self.input_perturbation
+ params_config["prediction_type"] = self.prediction_type
return params_config
- def to_device(self, device: Union[str, torch.device], non_blocking=False):
- #non_blocking = self.non_blocking
-
+ def to(self, device: Union[str, torch.device], non_blocking=False):
self.device = device
self.alphas_cumprod = self.alphas_cumprod.to(device, non_blocking=non_blocking)
- self.sqrt_alphas_cumprod = self.sqrt_alphas_cumprod.to(device, non_blocking=non_blocking)
- self.sqrt_one_minus_alphas_cumprod = self.sqrt_one_minus_alphas_cumprod.to(device, non_blocking=non_blocking)
self.sigmas = self.sigmas.to(device, non_blocking=non_blocking)
- self.sqrt_alphas = self.sqrt_alphas.to(device, non_blocking=non_blocking)
self.betas = self.betas.to(device, non_blocking=non_blocking)
self.num_train_timesteps = self.num_train_timesteps.to(device, non_blocking=non_blocking)
self.num_inference_steps = self.num_inference_steps.to(device, non_blocking=non_blocking)
-
+ return self
+
+ #------------------------------------
+
+ @property
+ def SNR(self):
+ alphas_bar = self.alphas_cumprod
+ betas_bar = 1.0 - alphas_bar
+ return alphas_bar / betas_bar
+
#------------------------------------
# Inference functions
+
+ def enforce_zero_terminal_snr(self, betas):
+ # Algorithm 1 in https://arxiv.org/pdf/2305.08891.pdf
+
+ # Convert betas to alphas_bar_sqrt
+ alphas = 1 - betas
+ alphas_bar = alphas.cumprod(0)
+ alphas_bar_sqrt = alphas_bar.sqrt()
+
+ # Store old values.
+ alphas_bar_sqrt_0 = alphas_bar_sqrt[0].clone()
+ alphas_bar_sqrt_T = alphas_bar_sqrt[-1].clone()
+ # Shift so last timestep is zero.
+ alphas_bar_sqrt -= alphas_bar_sqrt_T
+ # Scale so first timestep is back to old value.
+ alphas_bar_sqrt *= alphas_bar_sqrt_0 / (alphas_bar_sqrt_0 - alphas_bar_sqrt_T)
+
+ # Convert alphas_bar_sqrt to betas
+ alphas_bar = alphas_bar_sqrt ** 2
+ alphas = alphas_bar[1:] / alphas_bar[:-1]
+ alphas = torch.cat([alphas_bar[0:1], alphas])
+ betas = 1 - alphas
+ return betas
+
+ def set_timesteps(self, num_inference_steps: Optional[int] = None, timesteps: Optional[torch.Tensor] = None):
+ if exists(num_inference_steps):
+ if num_inference_steps >= self.num_train_timesteps: raise ValueError("num_inference_steps >= self.num_train_timesteps")
+ self.num_inference_steps = torch.tensor(num_inference_steps)
+ step_ratio = self.num_train_timesteps // self.num_inference_steps
+ timesteps = (np.arange(0, num_inference_steps) * step_ratio.item()).round()[::-1].copy().astype(np.int64)
+ self.timesteps = torch.from_numpy(timesteps)
+
+ elif exists(timesteps):
+ self.num_inference_steps = torch.tensor(timesteps.shape[0])
+ self.timesteps = timesteps.clone()
+
+ else:
+ raise RuntimeError("provide `num_inference_steps` or `timesteps`")
- def set_timesteps(self, num_inference_steps: int):
- if num_inference_steps >= self.num_train_timesteps: raise ValueError("num_inference_steps >= self.num_train_timesteps")
- self.num_inference_steps = torch.tensor(num_inference_steps)
- step_ratio = self.num_train_timesteps // self.num_inference_steps
- timesteps = (np.arange(0, num_inference_steps) * step_ratio.item()).round()[::-1].copy().astype(np.int64)
- self.timesteps = torch.from_numpy(timesteps)
-
def step(self,
model_output: torch.FloatTensor,
timesteps: Union[int, torch.IntTensor],
@@ -114,46 +181,44 @@ def step(self,
) -> DDPMSchedulerOutput:
"""Denoising step"""
- sqrt_alphas_cumprod = self.unsqueeze_vector_to_shape(self.sqrt_alphas_cumprod[timesteps], sample.shape)
+ sqrt_alphas_cumprod = self.unsqueeze_vector_to_shape(self.sqrt_alphas_cumprod[timesteps], sample.shape)
sqrt_one_minus_alphas_cumprod = self.unsqueeze_vector_to_shape(self.sqrt_one_minus_alphas_cumprod[timesteps], sample.shape)
-
+
sigmas = self.unsqueeze_vector_to_shape(self.sigmas[timesteps], sample.shape)
sqrt_alphas = self.unsqueeze_vector_to_shape(self.sqrt_alphas[timesteps], sample.shape)
betas = self.unsqueeze_vector_to_shape(self.betas[timesteps], sample.shape)
-
- non_zero_t = (timesteps!=0).float()
-
- #estimate the final img
- x0 = (sample - sqrt_one_minus_alphas_cumprod * model_output) / sqrt_alphas_cumprod #DDPM eq.15
-
+
+ if self.prediction_type == "epsilon":
+ #estimate the final img
+ x0 = (sample - sqrt_one_minus_alphas_cumprod * model_output) / sqrt_alphas_cumprod #DDPM eq.15
+
+ xt_coeff = betas / sqrt_one_minus_alphas_cumprod
+ mu_t = (sample - xt_coeff * model_output) / sqrt_alphas
+
+ elif self.prediction_type == "v-type":
+ x0 = sqrt_alphas_cumprod * sample - sqrt_one_minus_alphas_cumprod * model_output
+
+ prev_timesteps = timesteps - self.num_train_timesteps // self.num_inference_steps
+ alphas_cumprod_tm1 = self.unsqueeze_vector_to_shape(self.alphas_cumprod[prev_timesteps], sample.shape)
+
+ non_zero_tm1 = (prev_timesteps>=0.0).float()
+ non_zero_tm1 = self.unsqueeze_vector_to_shape(non_zero_tm1, sample.shape)
+ alphas_cumprod_tm1 = alphas_cumprod_tm1 * non_zero_tm1 + (1.0 - non_zero_tm1) * self.alphas_cumprod[0]
+
+ mu_t = (betas * alphas_cumprod_tm1.sqrt() * x0 + sqrt_alphas * (1.0-alphas_cumprod_tm1) * sample) / sqrt_one_minus_alphas_cumprod
+
+ else:
+ raise NotImplementedError(f"{self.prediction_type} is not implemented for {self.__class__}.step()")
+
#less noisy latent
+ non_zero_t = (timesteps>0).float()
noise = torch.randn(sample.shape, device=self.device)
noise = noise * non_zero_t.reshape(-1, 1, 1, 1)
-
- xt_coeff = betas / sqrt_one_minus_alphas_cumprod
- xt = (sample - xt_coeff * model_output) / sqrt_alphas + sigmas * noise
+
+ xt = mu_t + sigmas * noise
return DDPMSchedulerOutput(prev_sample=xt, pred_original_sample=x0)
- def add_noise_LEdit(self, original_samples: torch.FloatTensor):
- # LEDITS: Real Image Editing with DDPM Inversion and Semantic Guidance; Note: SEGA (Semantic Guidance) is just multiple negative promts with a pixel based weight
- # https://arxiv.org/pdf/2307.00522.pdf
-
- noisy_latents = []
- noises = []
-
- noisy_latent_t = original_samples
-
- for t in self.timesteps[::-1]: #start from no noise and diffuse in non analytic fashion
- noise_t = torch.randn_like(noise)
- alpha_t = self.unsqueeze_vector_to_shape(self.alphas[t], original_samples.shape)
- noisy_latent_t = torch.sqrt(alpha_t) * noisy_latent_t + torch.sqrt(1.0 - alpha_t) * noise_t
-
- noises.append(noise_t)
- noisy_latents.append(noisy_latent_t)
-
- return noisy_latents[::-1], noises[::-1] # invert to self.timestep definition
-
#------------------------------------
# Training functions
@@ -161,12 +226,13 @@ def add_noise(self,
original_samples: torch.FloatTensor,
noise: torch.FloatTensor,
timesteps: torch.IntTensor,
+ train: bool=False
) -> torch.FloatTensor:
- alphas_cumprod = self.unsqueeze_vector_to_shape(self.alphas_cumprod[timesteps], original_samples.shape)
- noisy_latents = torch.sqrt(alphas_cumprod) * original_samples + torch.sqrt(1.0 - alphas_cumprod) * noise
+ alphas_cumprod = self.unsqueeze_vector_to_shape(self.alphas_cumprod[timesteps], original_samples.shape)
+ noisy_latents = torch.sqrt(alphas_cumprod) * original_samples + torch.sqrt(1.0 - alphas_cumprod) * noise # F^2
- if self.input_perturbation is not None:
+ if exists(self.input_perturbation) and train:
noisy_latents = noisy_latents + torch.sqrt(1.0 - alphas_cumprod) * torch.randn_like(noise) * self.input_perturbation
- return noisy_latents
+ return noisy_latents
diff --git a/genQC/scheduler/scheduler_dpm.py b/genQC/scheduler/scheduler_dpm.py
new file mode 100644
index 0000000..31bb173
--- /dev/null
+++ b/genQC/scheduler/scheduler_dpm.py
@@ -0,0 +1,125 @@
+"""DPM-Solver++: Fast Solver for Guided Sampling of Diffusion Probabilistic Models [(DPM-Solver)](https://arxiv.org/abs/2206.00927) [(DPM-Solver++)](https://arxiv.org/abs/2211.01095)."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/scheduler/scheduler_dpm.ipynb.
+
+# %% auto 0
+__all__ = ['DPMSchedulerOutput', 'DPMScheduler']
+
+# %% ../../src/scheduler/scheduler_dpm.ipynb 2
+from ..imports import *
+from .scheduler_ddpm import DDPMScheduler
+
+# %% ../../src/scheduler/scheduler_dpm.ipynb 3
+@dataclass
+class DPMSchedulerOutput:
+ prev_sample: torch.FloatTensor
+ pred_original_sample: Optional[torch.FloatTensor] = None
+
+# %% ../../src/scheduler/scheduler_dpm.ipynb 4
+class DPMScheduler(DDPMScheduler):
+ """A `Scheduler` implementing [(DPM-Solver++)](https://arxiv.org/abs/2211.01095)."""
+
+ def __init__(self,
+ device: Union[str, torch.device],
+ num_train_timesteps: int = 1000,
+ beta_start: float = 0.0001,
+ beta_end: float = 0.02,
+ beta_schedule: str = "linear",
+ input_perturbation = 0.1,
+ prediction_type = "epsilon",
+ enable_zero_terminal_snr = True,
+ solver_order: int = 2,
+ **kwargs
+ ) -> None:
+ super().__init__(device, num_train_timesteps, beta_start, beta_end, beta_schedule, input_perturbation, prediction_type, enable_zero_terminal_snr)
+
+ self.solver_order = solver_order
+ if self.solver_order != 2:
+ raise NotImplementedError(f"{self.solver_order=} is not implemented for {self.__class__}")
+
+ @property
+ def params_config(self):
+ params_config = super().params_config
+ params_config["solver_order"] = self.solver_order
+ return params_config
+
+ #------------------------------------
+ # Inference functions
+
+ def step(self,
+ model_output: torch.FloatTensor,
+ timesteps: torch.IntTensor,
+ sample: torch.FloatTensor,
+ uncond_model_output: torch.FloatTensor = None, # for CFG++
+ ) -> DPMSchedulerOutput:
+ """
+ Denoising step of DPM-Solver++(2M) (Lu et al., 2022b),
+ implemeted as CFG++ variant (CFG++, https://arxiv.org/pdf/2406.08070)
+ """
+
+ uncond_model_output = default(uncond_model_output, model_output)
+
+ assert timesteps.numel() == 1
+
+ # note: here we enforce the sampling to be strictly defined by self.timesteps
+ is_warmup_step = (self.timesteps[0] == timesteps)
+ # is_last_step = (self.timesteps[-1] == timesteps)
+
+ alphas_cumprod = self.unsqueeze_vector_to_shape(self.alphas_cumprod[timesteps], sample.shape)
+ betas_cumprod = 1.0 - alphas_cumprod
+
+ prev_timesteps = timesteps - self.num_train_timesteps // self.num_inference_steps
+ prev_timesteps = prev_timesteps.clamp(0, self.num_train_timesteps-1)
+
+ alphas_cumprod_tm1 = self.unsqueeze_vector_to_shape(self.alphas_cumprod[prev_timesteps], sample.shape)
+ betas_cumprod_tm1 = 1.0 - alphas_cumprod_tm1
+
+ # ---------
+ if self.prediction_type == "v-type":
+ a = alphas_cumprod.sqrt()
+ b = betas_cumprod.sqrt()
+
+ x0 = a * sample - b * model_output
+ x0_uncond = a * sample - b * uncond_model_output
+
+ elif self.prediction_type == "x0":
+ x0 = model_output
+ x0_uncond = uncond_model_output
+
+ else:
+ raise NotImplementedError(f"{self.prediction_type} is not implemented for {self.__class__}.step()")
+
+ # ---------
+ solver_order = self.solver_order
+ # mod here for adyptive adjust, if needed
+ if solver_order == 2:
+ pass
+
+ else:
+ raise NotImplementedError(f"{solver_order} is not implemented for {self.__class__}")
+
+ # ---------
+
+ lambda_t = 0.5 * torch.log(alphas_cumprod / betas_cumprod)
+ lambda_tm1 = 0.5 * torch.log(alphas_cumprod_tm1 / betas_cumprod_tm1)
+
+ h_tm1 = lambda_tm1 - lambda_t
+
+ if is_warmup_step:
+ x_dir = alphas_cumprod_tm1.sqrt() * (x0 - torch.exp(-h_tm1) * x0_uncond)
+
+ else:
+ r_tm1 = self.last_h_tm1 / h_tm1
+
+ sqrt_alphas_cumprod_tm1 = alphas_cumprod_tm1.sqrt()
+ exp_mhtm1 = torch.exp(-h_tm1)
+
+ x_dir = sqrt_alphas_cumprod_tm1 * x0 - sqrt_alphas_cumprod_tm1 * exp_mhtm1 * x0_uncond + sqrt_alphas_cumprod_tm1 * (0.5/r_tm1) * (x0_uncond-self.last_x0_uncond) * (1.0-exp_mhtm1)
+
+ xtm1 = (betas_cumprod_tm1/betas_cumprod).sqrt() * sample + x_dir
+
+ # is needed for multistesp integration of DPM
+ self.last_x0_uncond = x0_uncond
+ self.last_h_tm1 = h_tm1
+
+ return DPMSchedulerOutput(prev_sample=xtm1, pred_original_sample=x0)
diff --git a/genQC/util.py b/genQC/util.py
deleted file mode 100644
index bc1dec0..0000000
--- a/genQC/util.py
+++ /dev/null
@@ -1,128 +0,0 @@
-# AUTOGENERATED! DO NOT EDIT! File to edit: ../src/util.ipynb.
-
-# %% auto 0
-__all__ = ['MemoryCleaner', 'virtual', 'DataLoaders', 'infer_torch_device', 'number_of_paramters', 'normalize_tensor',
- 'scale_tensor', 'savePdf', 'savePng', 'saveSvg', 'plot_image_grid', 'latents_to_pil']
-
-# %% ../src/util.ipynb 3
-from .imports import *
-import gc, sys, traceback
-
-# %% ../src/util.ipynb 5
-class MemoryCleaner():
- """CLass with static methods to clean (gpu) memory."""
-
- @staticmethod
- def _clean_ipython_hist():
- # Code in this function mainly copied from IPython source
- if not 'get_ipython' in globals(): return
- ip = get_ipython()
- user_ns = ip.user_ns
- ip.displayhook.flush()
- pc = ip.displayhook.prompt_count + 1
- for n in range(1, pc): user_ns.pop('_i'+repr(n),None)
- user_ns.update(dict(_i='',_ii='',_iii=''))
- hm = ip.history_manager
- hm.input_hist_parsed[:] = [''] * pc
- hm.input_hist_raw[:] = [''] * pc
- hm._i = hm._ii = hm._iii = hm._i00 = ''
-
- @staticmethod
- def _clean_tb():
- if hasattr(sys, 'last_traceback'):
- traceback.clear_frames(sys.last_traceback)
- delattr(sys, 'last_traceback')
- if hasattr(sys, 'last_type'): delattr(sys, 'last_type')
- if hasattr(sys, 'last_value'): delattr(sys, 'last_value')
-
- @staticmethod
- def purge_mem():
- """Clear all. Purge all memory."""
- MemoryCleaner._clean_tb()
- MemoryCleaner._clean_ipython_hist()
- gc.collect()
- torch.cuda.empty_cache()
-
-# %% ../src/util.ipynb 7
-def virtual(f: callable) -> callable:
- '''Decorator to enfore subclass method implementations and raises error at method calls.'''
- @functools.wraps(f)
- def inner(self, *args, **kwargs): raise NotImplementedError(f"Virtual method {f.__name__} needs to be implemented by subclass {self.__class__.__name__}.")
- return inner
-
-# %% ../src/util.ipynb 10
-class DataLoaders:
- """Combines train and valid `DataLoader`."""
- def __init__(self, *dls: list[DataLoader]): self.train, self.valid = dls[:2]
-
-# %% ../src/util.ipynb 11
-def infer_torch_device():
- if torch.cuda.is_available():
- torch.backends.cudnn.benchmark = True
-
- dev_cap = torch.cuda.get_device_capability()
-
- if dev_cap[0] >= 8: # AMPERE
- print(f"[INFO]: Cuda device has a capability of {dev_cap[0]}.{dev_cap[1]} (>= 8), allowing tf32 matmul.")
- torch.backends.cuda.matmul.allow_tf32 = True
-
- return torch.device("cuda")
- return torch.device("cpu")
-
-# %% ../src/util.ipynb 13
-def number_of_paramters(model: nn.Module): return sum([p.flatten().shape[0] for p in model.parameters()])
-
-# %% ../src/util.ipynb 14
-def normalize_tensor(t: torch.Tensor):
- """[0,1] to [-1,1]"""
- return t * 2.0 - 1.0
-
-def scale_tensor(t: torch.Tensor):
- """[-1,1] to [0,1]"""
- return (t / 2.0 + 0.5).clamp(0.0, 1.0)
-
-# %% ../src/util.ipynb 16
-def savePdf(filename): plt.savefig(filename + '.pdf', bbox_inches='tight')
-def savePng(filename): plt.savefig(filename + '.png', bbox_inches='tight')
-def saveSvg(filename): plt.savefig(filename + '.svg', bbox_inches='tight')
-
-# %% ../src/util.ipynb 17
-def plot_image_grid(imgs: Union[list, np.array, torch.Tensor], labels: list=None, labels_fs="medium",
- figsize=(16, 4), cols=8, cmap="Greys", show_colorbar=False, **imshow_kwargs):
- if type(imgs) is list: n = len(imgs)
- elif type(imgs) is np.ndarray: n = imgs.shape[0]
- elif type(imgs) is torch.Tensor: n = imgs.shape[0]
- else: raise NotImplementedError("err type:", type(imgs))
-
- if n == 0: return
-
- cols = min(n, cols)
- rows = math.ceil(n/cols)
-
- fig, axs = plt.subplots(rows, cols, figsize=figsize, squeeze=False, constrained_layout=True)
- for i, (r, c) in enumerate(itertools.product(range(rows), range(cols))):
- plt.sca(axs[r,c])
- plt.axis('off')
-
- if i >= n: continue
-
- if labels is not None: plt.title(labels[i], fontsize=labels_fs)
- p = plt.imshow(imgs[i], cmap=cmap, **imshow_kwargs) #cmap ignored for RGB
- if show_colorbar: plt.colorbar(p)
-
- plt.show()
-
-# %% ../src/util.ipynb 19
-def latents_to_pil(latents:torch.Tensor, channels=None):
- if channels is None:
- channels = latents.shape[1] if len(latents.shape) > 3 else 1
-
- images = scale_tensor(latents)
- images = images.detach().cpu().permute(0, 2, 3, 1).numpy()
-
- if channels == 1: images = images[:, :, :, 0]
-
- images = (images * 255).round().astype(np.uint8)
-
- pil_images = [Image.fromarray(image) for image in images]
- return pil_images
diff --git a/genQC/utils/__init__.py b/genQC/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/genQC/utils/async_fn.py b/genQC/utils/async_fn.py
new file mode 100644
index 0000000..57267b6
--- /dev/null
+++ b/genQC/utils/async_fn.py
@@ -0,0 +1,60 @@
+"""Basic functions for async executions."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/utils/async_fn.ipynb.
+
+# %% auto 0
+__all__ = ['run_parallel_jobs', 'MemoryMappedArray']
+
+# %% ../../src/utils/async_fn.ipynb 2
+from ..imports import *
+from joblib import Parallel, delayed
+
+from tensordict.tensordict import MemoryMappedTensor
+import tempfile
+
+# %% ../../src/utils/async_fn.ipynb 4
+def run_parallel_jobs(f: callable, loop_set, n_jobs: int = 1):
+ if n_jobs > 1: res = Parallel(n_jobs=n_jobs)(delayed(f)(x) for x in loop_set)
+ else: res = [f(x) for x in loop_set]
+ return res
+
+# %% ../../src/utils/async_fn.ipynb 6
+class MemoryMappedArray():
+ def __init__(self, obj, type="tensor"):
+ self.obj = obj
+ self.type = type
+ assert type in ["tensor", "numpy"]
+
+ with tempfile.NamedTemporaryFile(delete=False) as file:
+ # Note can bes simplified with python 3.12 as we can set delete=true, and delete_on_close=True, so it will be kept and we dont need to delete
+ # see https://docs.python.org/3.12/library/tempfile.html
+
+ self.temporaryFileName = file.name
+ file.close()
+
+ if self.type == "numpy":
+ self.obj_memmap = np.memmap(filename=self.temporaryFileName, dtype=obj.dtype, mode='w+', shape=obj.shape)
+ self.obj_memmap[:] = self.obj[:]
+ self.obj_memmap.flush()
+
+ elif self.type == "tensor":
+ self.obj_memmap = MemoryMappedTensor.from_tensor(self.obj.cpu(), filename=self.temporaryFileName, existsok=True)
+
+ else:
+ raise NotImplementedError()
+
+ def get_obj(self):
+ if self.type == "numpy":
+ self.obj = self.obj_memmap.copy()
+
+ elif self.type == "tensor":
+ self.obj = self.obj_memmap.contiguous().clone().to(self.obj.device)
+
+ del self.obj_memmap
+ return self.obj, self.temporaryFileName
+
+ @staticmethod
+ def clean(temp_files):
+ for temp_file in temp_files:
+ try: os.remove(temp_file)
+ except Exception as e: print(f"[ERROR]: {e}")
diff --git a/genQC/utils/config_loader.py b/genQC/utils/config_loader.py
new file mode 100644
index 0000000..c0f7f40
--- /dev/null
+++ b/genQC/utils/config_loader.py
@@ -0,0 +1,120 @@
+"""Functions to load and store models and datasets."""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/utils/config_loader.ipynb.
+
+# %% auto 0
+__all__ = ['class_to_str', 'load_config', 'config_to_dict', 'save_dataclass_yaml', 'save_dict_yaml', 'get_obj_from_str',
+ 'instantiate_from_config', 'store_model_state_dict', 'load_model_state_dict', 'store_tensor', 'load_tensor']
+
+# %% ../../src/utils/config_loader.ipynb 3
+from ..imports import *
+from omegaconf import OmegaConf
+
+from safetensors.torch import load_file as load_safetensors
+from safetensors.torch import save_file as save_safetensors
+from safetensors.numpy import load_file as load_safetensors_np
+from safetensors.numpy import save_file as save_safetensors_np
+from safetensors import safe_open
+
+# %% ../../src/utils/config_loader.ipynb 5
+def class_to_str(cls):
+ return str(cls)[8:-2]
+
+# %% ../../src/utils/config_loader.ipynb 6
+def load_config(file_path):
+ return OmegaConf.load(f"{file_path}")
+
+# %% ../../src/utils/config_loader.ipynb 7
+def config_to_dict(config):
+ return OmegaConf.to_container(config)
+
+# %% ../../src/utils/config_loader.ipynb 8
+def save_dataclass_yaml(data_obj, file_path):
+ conf = OmegaConf.structured(data_obj)
+ with open(file_path, 'w') as f:
+ OmegaConf.save(config=conf, f=f)
+
+# %% ../../src/utils/config_loader.ipynb 9
+def save_dict_yaml(dict_obj, file_path):
+ conf = OmegaConf.create(dict_obj)
+ with open(file_path, 'w') as f:
+ OmegaConf.save(config=conf, f=f)
+
+# %% ../../src/utils/config_loader.ipynb 14
+def get_obj_from_str(string, reload=False, invalidate_cache=True):
+ module, cls = string.rsplit(".", 1)
+ if invalidate_cache:
+ importlib.invalidate_caches()
+ if reload:
+ module_imp = importlib.import_module(module)
+ importlib.reload(module_imp)
+ return getattr(importlib.import_module(module, package=None), cls)
+
+# %% ../../src/utils/config_loader.ipynb 15
+def instantiate_from_config(config):
+ if not "target" in config: raise KeyError("Expected key `target` to instantiate.")
+ if not "params" in config: print("[WARNING] Expected key `params` to instantiate.")
+ return get_obj_from_str(config["target"])(**config.get("params", dict()))
+
+# %% ../../src/utils/config_loader.ipynb 17
+def store_model_state_dict(state_dict, save_path):
+ print(f"[INFO]: Saving model to `{save_path}`.")
+
+ if save_path.endswith("ckpt") or save_path.endswith("pt"):
+ torch.save(state_dict, save_path)
+
+ elif save_path.endswith("safetensors"):
+ save_safetensors(state_dict, save_path)
+
+ else:
+ raise NotImplementedError(f"unknown filetype: {save_path}")
+
+# %% ../../src/utils/config_loader.ipynb 18
+def load_model_state_dict(save_path, device):
+ print(f"[INFO]: Loading model from `{save_path}` onto device: {device}.")
+
+ if save_path.endswith("ckpt") or save_path.endswith("pt"):
+ state_dict = torch.load(save_path, map_location=torch.device(device).type, weights_only=True)
+
+ elif save_path.endswith("safetensors"):
+ state_dict = load_safetensors(save_path, device=torch.device(device).type)
+
+ else:
+ raise NotImplementedError(f"unknown filetype: {save_path}")
+
+ return state_dict
+
+# %% ../../src/utils/config_loader.ipynb 21
+def store_tensor(tensor, save_path, type="tensor"):
+ print(f"[INFO]: Saving tensor to `{save_path}`.")
+
+ if type=="numpy" and save_path.endswith("safetensors"):
+ save_path = save_path.replace(".safetensors", ".pt")
+
+ if save_path.endswith("ckpt") or save_path.endswith("pt") or type=="numpy":
+ # serializing a string larger than 4 GiB requires pickle protocol 4 or higher; Protocol version 5 was added in Python 3.8.
+ torch.save(tensor, save_path, pickle_protocol=5)
+
+ elif save_path.endswith("safetensors") and type=="tensor":
+ save_safetensors(tensor, save_path)
+
+ else:
+ raise NotImplementedError(f"unknown filetype: {save_path} or unknown type {type}")
+
+# %% ../../src/utils/config_loader.ipynb 22
+def load_tensor(save_path, device, type="tensor"):
+ print(f"[INFO]: Loading tensor from `{save_path}` onto device: {device}.")
+
+ if type=="numpy" and save_path.endswith("safetensors"):
+ save_path = save_path.replace(".safetensors", ".pt")
+
+ if save_path.endswith("ckpt") or save_path.endswith("pt") or type=="numpy":
+ tensor = torch.load(save_path, map_location=torch.device(device).type, weights_only=False)
+
+ elif save_path.endswith("safetensors") and type=="tensor":
+ tensor = load_safetensors(save_path, device=torch.device(device).type)
+
+ else:
+ raise NotImplementedError(f"unknown filetype: {save_path} or unknown type {type}")
+
+ return tensor
diff --git a/genQC/utils/math.py b/genQC/utils/math.py
new file mode 100644
index 0000000..c2d1563
--- /dev/null
+++ b/genQC/utils/math.py
@@ -0,0 +1,41 @@
+"""Miscellaneous math and algorithm code"""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/utils/math.ipynb.
+
+# %% auto 0
+__all__ = ['matrix_power', 'gram_schmidt']
+
+# %% ../../src/utils/math.ipynb 2
+from ..imports import *
+
+# %% ../../src/utils/math.ipynb 4
+def matrix_power(x: torch.Tensor, p: float) -> torch.Tensor:
+ """
+ Power of a matrix using Eigenspace Decomposition. Assuming decomposition of `x` exists.
+ """
+
+ vals, vecs = torch.linalg.eig(x)
+ vals_pow = torch.pow(vals, p)
+ matrix_pow = torch.matmul(vecs, torch.matmul(torch.diag(vals_pow), torch.inverse(vecs)))
+
+ return matrix_pow
+
+# %% ../../src/utils/math.ipynb 6
+def gram_schmidt(X: torch.Tensor):
+ """
+ Perform Gram–Schmidt orthonormalization on the vectors given by the rows of matrix X.
+ """
+ assert X.dim() == 2, "Only 2-dim tensor supported."
+
+ X_type = X.dtype
+ X = X.to(torch.float64)
+ Q = []
+ for q in X:
+ # Take the current row vector
+ # Subtract projec+tions onto existing basis vectors
+ for v in Q:
+ q = q - torch.dot(q, v) * v
+ # Normalize the vector
+ q = q / torch.norm(q)
+ Q.append(q)
+ return torch.stack(Q).to(X_type)
diff --git a/genQC/utils/misc_utils.py b/genQC/utils/misc_utils.py
new file mode 100644
index 0000000..08350e3
--- /dev/null
+++ b/genQC/utils/misc_utils.py
@@ -0,0 +1,235 @@
+"""Miscellaneous util code"""
+
+# AUTOGENERATED! DO NOT EDIT! File to edit: ../../src/utils/misc_utils.ipynb.
+
+# %% auto 0
+__all__ = ['MemoryCleaner', 'virtual', 'cache_data', 'DataLoaders', 'infer_torch_device', 'number_of_paramters',
+ 'normalize_tensor', 'scale_tensor', 'savePdf', 'savePng', 'saveSvg', 'plot_image_grid', 'latents_to_pil',
+ 'set_seed', 'get_element_matching_indices', 'get_entanglement_bins']
+
+# %% ../../src/utils/misc_utils.ipynb 2
+from ..imports import *
+import gc, traceback, inspect
+
+# %% ../../src/utils/misc_utils.ipynb 4
+class MemoryCleaner():
+ """CLass with static methods to clean (gpu) memory."""
+
+ @staticmethod
+ def _clean_ipython_hist():
+ # Code in this function mainly copied from IPython source
+ if not 'get_ipython' in globals(): return
+ ip = get_ipython()
+ user_ns = ip.user_ns
+ ip.displayhook.flush()
+ pc = ip.displayhook.prompt_count + 1
+ for n in range(1, pc): user_ns.pop('_i'+repr(n),None)
+ user_ns.update(dict(_i='',_ii='',_iii=''))
+ hm = ip.history_manager
+ hm.input_hist_parsed[:] = [''] * pc
+ hm.input_hist_raw[:] = [''] * pc
+ hm._i = hm._ii = hm._iii = hm._i00 = ''
+
+ @staticmethod
+ def _clean_tb():
+ if hasattr(sys, 'last_traceback'):
+ traceback.clear_frames(sys.last_traceback)
+ delattr(sys, 'last_traceback')
+ if hasattr(sys, 'last_type'): delattr(sys, 'last_type')
+ if hasattr(sys, 'last_value'): delattr(sys, 'last_value')
+
+ @staticmethod
+ def purge_mem():
+ """Clear all. Purge all memory."""
+ MemoryCleaner._clean_tb()
+ MemoryCleaner._clean_ipython_hist()
+ gc.collect()
+ torch.cuda.empty_cache()
+
+ @staticmethod
+ def free_memory(to_delete: list):
+ """Remove objs of `to_delete` from namespace"""
+ calling_namespace = inspect.currentframe().f_back
+ for _var in to_delete:
+ del _var
+ calling_namespace.f_locals.pop(_var, None)
+ gc.collect()
+ torch.cuda.empty_cache()
+
+# %% ../../src/utils/misc_utils.ipynb 6
+def virtual(f: callable) -> callable:
+ '''Decorator to enfore subclass method implementations and raises error at method calls.'''
+ @functools.wraps(f)
+ def inner(self, *args, **kwargs): raise NotImplementedError(f"Virtual method {f.__name__} needs to be implemented by subclass {self.__class__.__name__}.")
+ return inner
+
+# %% ../../src/utils/misc_utils.ipynb 8
+def cache_data(file_name, force_recompute):
+ """
+ A decorator that memorizes the result of a function and stores it.
+ Note, if the function or its arguments change we ignore it, we only check if the file exists!
+
+ Parameters:
+ - file_name (str): The name of the file to store the memoized results.
+ - force_recompute (bool): If True, existing cache is ignored.
+ """
+
+ #-------------------
+ def load():
+ if os.path.exists(file_name) and not force_recompute:
+ return torch.load(file_name)
+ return None
+
+ #-------------------
+ def save(cache):
+ if exists(cache):
+ os.makedirs(file_name[:file_name.rfind("/")] + "/", exist_ok=True)
+ torch.save(cache, file_name)
+
+ #-------------------
+ def decorator(func: callable) -> callable:
+ @functools.wraps(func)
+ def inner(*args, **kwargs):
+
+ cache = load()
+
+ if not exists(cache): # run function normally
+ print(f"Computing: {func.__name__}")
+ cache = func(*args, **kwargs)
+
+ save(cache)
+ print(f"Result saved")
+
+ else: # loaded already from cache
+ print(f"Result retrieved from cache: {func.__name__}")
+
+ return cache
+ return inner
+ return decorator
+
+# %% ../../src/utils/misc_utils.ipynb 10
+class DataLoaders:
+ """Combines train and valid `DataLoader` objects."""
+ def __init__(self, *dls: list[DataLoader]): self.train, self.valid = dls[:2]
+
+# %% ../../src/utils/misc_utils.ipynb 11
+def infer_torch_device():
+ if torch.cuda.is_available():
+ torch.backends.cudnn.benchmark = True
+
+ dev_cap = torch.cuda.get_device_capability()
+
+ if dev_cap[0] >= 8: # AMPERE and up
+ print(f"[INFO]: Cuda device has a capability of {dev_cap[0]}.{dev_cap[1]} (>= 8), allowing tf32 matmul.")
+ torch.backends.cuda.matmul.allow_tf32 = True
+ torch.backends.cudnn.allow_tf32 = True
+
+ return torch.device("cuda")
+ return torch.device("cpu")
+
+# %% ../../src/utils/misc_utils.ipynb 13
+def number_of_paramters(model: nn.Module): return sum([p.flatten().shape[0] for p in model.parameters()])
+
+# %% ../../src/utils/misc_utils.ipynb 14
+def normalize_tensor(t: torch.Tensor):
+ """[0,1] to [-1,1]"""
+ return t * 2.0 - 1.0
+
+def scale_tensor(t: torch.Tensor):
+ """[-1,1] to [0,1]"""
+ return (t / 2.0 + 0.5).clamp(0.0, 1.0)
+
+# %% ../../src/utils/misc_utils.ipynb 16
+def savePdf(filename): plt.savefig(filename + '.pdf', bbox_inches='tight')
+def savePng(filename): plt.savefig(filename + '.png', bbox_inches='tight')
+def saveSvg(filename): plt.savefig(filename + '.svg', bbox_inches='tight')
+
+# %% ../../src/utils/misc_utils.ipynb 17
+def plot_image_grid(imgs: Union[list, np.array, torch.Tensor], labels: list=None, labels_fs="medium",
+ figsize=(16, 4), cols=8, cmap="Greys", show_colorbar=False, **imshow_kwargs):
+ if type(imgs) is list: n = len(imgs)
+ elif type(imgs) is np.ndarray: n = imgs.shape[0]
+ elif type(imgs) is torch.Tensor: n = imgs.shape[0]
+ else: raise NotImplementedError("err type:", type(imgs))
+
+ if n == 0: return
+
+ cols = min(n, cols)
+ rows = math.ceil(n/cols)
+
+ fig, axs = plt.subplots(rows, cols, figsize=figsize, squeeze=False, constrained_layout=True)
+ for i, (r, c) in enumerate(itertools.product(range(rows), range(cols))):
+ plt.sca(axs[r,c])
+ plt.axis('off')
+
+ if i >= n: continue
+
+ if labels is not None: plt.title(labels[i], fontsize=labels_fs)
+ p = plt.imshow(imgs[i], cmap=cmap, **imshow_kwargs) #cmap ignored for RGB
+ if show_colorbar: plt.colorbar(p)
+
+ plt.show()
+
+# %% ../../src/utils/misc_utils.ipynb 19
+def latents_to_pil(latents:torch.Tensor, channels=None):
+ if channels is None:
+ channels = latents.shape[1] if len(latents.shape) > 3 else 1
+
+ images = scale_tensor(latents)
+ images = images.detach().cpu().permute(0, 2, 3, 1).numpy()
+
+ if channels == 1: images = images[:, :, :, 0]
+
+ images = (images * 255).round().astype(np.uint8)
+
+ pil_images = [Image.fromarray(image) for image in images]
+ return pil_images
+
+# %% ../../src/utils/misc_utils.ipynb 21
+def set_seed(seed: int):
+ """Sets a seed to pytorch, numpy and python. Additionally sets cuda flags."""
+
+ torch.manual_seed(seed)
+ np.random.seed(seed)
+ random.seed(seed)
+
+ # see https://pytorch.org/docs/stable/notes/randomness.html
+ torch.backends.cudnn.benchmark = False
+ torch.backends.cudnn.deterministic = True
+ torch.use_deterministic_algorithms(True)
+
+ # see https://docs.nvidia.com/cuda/cublas/index.html#results-reproducibility
+ os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"
+
+# %% ../../src/utils/misc_utils.ipynb 22
+def get_element_matching_indices(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
+ """Compares (2d) `a` with `b`. Returns the indices of `b`, where a element of `a` matches with `b`."""
+ # Expand dimensions of a to match the shape of b for element-wise comparison
+ expanded_a = a.unsqueeze(0).expand(b.shape[0], *a.shape) # [b0, a0, a1]
+ expanded_b = b.unsqueeze(1) # [b0, 1, b1]
+
+ # Compare all vector entries of a with all vectors of b
+ matches = torch.all(expanded_a == expanded_b, dim=-1)
+
+ matching_indices = torch.nonzero(torch.any(matches, dim=1)).squeeze()
+
+ if matching_indices.dim() == 0: matching_indices = torch.tensor([matching_indices])
+
+ return matching_indices
+
+# %% ../../src/utils/misc_utils.ipynb 23
+def get_entanglement_bins(num_of_qubits: int) -> Tuple[List[List], List[str]]:
+ """Returns all SRV sorted in entangle bins, corresponding to a number of entangled qubits."""
+
+ dist_srvs = [x for x in itertools.product(*([[1,2]]*num_of_qubits))]
+ dist_srvs = np.array(dist_srvs, dtype=int)[np.sum(dist_srvs, axis=1)!=num_of_qubits+1].tolist()
+ dist_srvs = sorted(dist_srvs, key=lambda x: sum(x))
+ dist_srvs = np.array(dist_srvs)
+
+ entangle = [1] + [scipy.special.comb(num_of_qubits, i, exact=True) for i in range(2, num_of_qubits)]
+
+ entanglement_bins = np.split(dist_srvs, np.cumsum(entangle))
+
+ ent_bits = [f"{sum(n[0])-num_of_qubits} qubit entangled" for n in entanglement_bins]
+
+ return [x.tolist() for x in entanglement_bins], ent_bits
diff --git a/get_started_files/figure-commonmark/cell-3-output-1.png b/get_started_files/figure-commonmark/cell-3-output-1.png
new file mode 100644
index 0000000..14303bf
Binary files /dev/null and b/get_started_files/figure-commonmark/cell-3-output-1.png differ
diff --git a/index_files/figure-commonmark/cell-3-output-2.png b/index_files/figure-commonmark/cell-3-output-2.png
deleted file mode 100644
index 23dd0f3..0000000
Binary files a/index_files/figure-commonmark/cell-3-output-2.png and /dev/null differ
diff --git a/pyproject.toml b/pyproject.toml
index f2c07bf..7df2707 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,3 +1,11 @@
[build-system]
requires = ["setuptools>=64.0"]
build-backend = "setuptools.build_meta"
+
+[project]
+name="genQC"
+requires-python=">=3.12"
+dynamic = [ "keywords", "description", "version", "dependencies", "optional-dependencies", "readme", "license", "authors", "classifiers", "entry-points", "scripts", "urls"]
+
+[tool.uv]
+cache-keys = [{ file = "pyproject.toml" }, { file = "settings.ini" }, { file = "setup.py" }]
\ No newline at end of file
diff --git a/saves/qc_unet_config_Compilation_3_qubit/config.yaml b/saves/qc_unet_config_Compilation_3_qubit/config.yaml
deleted file mode 100644
index 9da2141..0000000
--- a/saves/qc_unet_config_Compilation_3_qubit/config.yaml
+++ /dev/null
@@ -1,77 +0,0 @@
-target: genQC.pipeline.diffusion_pipeline_special.DiffusionPipeline_Compilation
-params:
- scheduler:
- target: genQC.scheduler.scheduler_ddim.DDIMScheduler
- params:
- device: cpu
- num_train_timesteps: 1000
- beta_start: 0.0001
- beta_end: 0.02
- beta_schedule: cos_alpha
- input_perturbation: 0.1
- eta: 1
- model:
- target: genQC.models.unet_qc.QC_Compilation_UNet
- save_path: null
- params:
- model_features:
- - 128
- - 128
- - 256
- clr_dim: 8
- num_clrs: 8
- t_emb_size: 256
- cond_emb_size: 512
- num_heads:
- - 8
- - 8
- - 2
- num_res_blocks:
- - 2
- - 2
- - 4
- transformer_depths:
- - 1
- - 2
- - 1
- unitary_encoder_config:
- cond_emb_size: 512
- model_features:
- - 2
- - 32
- - 64
- - 512
- num_heads: 8
- transformer_depths:
- - 2
- - 2
- dropout: 0.2
- text_encoder:
- target: genQC.models.frozen_open_clip.CachedFrozenOpenCLIPEmbedder
- save_path: null
- params:
- arch: ViT-B-32
- version: laion2b_s34b_b79k
- device: cpu
- max_length: 77
- freeze: true
- layer: penultimate
- device: cpu
- enable_guidance_train: true
- guidance_train_p: 0.1
- cached_text_enc: true
- add_config:
- dataset:
- params:
- num_of_qubits: 3
- min_gates: 2
- max_gates: 12
- gate_pool:
- - h
- - cx
- - z
- - x
- - ccx
- - swap
- pad_constant: 7
-
diff --git a/saves/qc_unet_config_Compilation_3_qubit/model.pt b/saves/qc_unet_config_Compilation_3_qubit/model.pt
deleted file mode 100644
index 5d0b913..0000000
Binary files a/saves/qc_unet_config_Compilation_3_qubit/model.pt and /dev/null differ
diff --git a/saves/qc_unet_config_SRV_3to8_qubit/config.yaml b/saves/qc_unet_config_SRV_3to8_qubit/config.yaml
deleted file mode 100644
index 9a8065c..0000000
--- a/saves/qc_unet_config_SRV_3to8_qubit/config.yaml
+++ /dev/null
@@ -1,58 +0,0 @@
-target: genQC.pipeline.diffusion_pipeline.DiffusionPipeline
-params:
- scheduler:
- target: genQC.scheduler.scheduler_ddim.DDIMScheduler
- params:
- device: cpu
- num_train_timesteps: 1000
- beta_start: 0.0001
- beta_end: 0.02
- beta_schedule: cos_alpha
- input_perturbation: 0.1
- eta: 1
- model:
- target: genQC.models.unet_qc.QC_Cond_UNet
- save_path: null
- params:
- model_features:
- - 128
- - 128
- - 256
- clr_dim: 4
- num_clrs: 4
- t_emb_size: 256
- cond_emb_size: 512
- num_heads:
- - 8
- - 8
- - 2
- num_res_blocks:
- - 2
- - 2
- - 4
- transformer_depths:
- - 1
- - 2
- - 1
- text_encoder:
- target: genQC.models.frozen_open_clip.CachedFrozenOpenCLIPEmbedder
- save_path: null
- params:
- arch: ViT-B-32
- version: laion2b_s34b_b79k
- device: cpu
- max_length: 77
- freeze: true
- layer: penultimate
- device: cpu
- enable_guidance_train: true
- guidance_train_p: 0.1
- cached_text_enc: true
- add_config:
- dataset:
- comment: 'Generated with ''from_datasets'' with 6 datasets. Qubits: [3, 4, 5,6, 7, 8].'
- params:
- gate_pool:
- - h
- - cx
- pad_constant: 3
diff --git a/saves/qc_unet_config_SRV_3to8_qubit/model.pt b/saves/qc_unet_config_SRV_3to8_qubit/model.pt
deleted file mode 100644
index 5721c1b..0000000
Binary files a/saves/qc_unet_config_SRV_3to8_qubit/model.pt and /dev/null differ
diff --git a/settings.ini b/settings.ini
index b3e438a..e62ae21 100644
--- a/settings.ini
+++ b/settings.ini
@@ -3,9 +3,9 @@
### Python library ###
repo = genQC
lib_name = %(repo)s
-version = 0.1.1
-min_python = 3.10
-license = apache2
+version = 0.2.0
+min_python = 3.12
+license = apache2
black_formatting = False
### nbdev ###
@@ -24,17 +24,19 @@ doc_baseurl = /%(repo)s
git_url = https://github.com/%(user)s/%(repo)s
title = %(lib_name)s
+readme_nb = get_started.ipynb
+
custom_quarto_yml = True
custom_sidebar = True
### PyPI ###
author = Florian Fuerrutter
author_email = f.fuerrutter@gmail.com
-copyright = 2024 onwards, %(author)s
+copyright = 2025 onwards, %(author)s
audience = Developers
-description = Generating quantum circuits with diffusion models
-keywords = quantum-information diffusion-models generative-models
+description = Generative quantum circuits
+keywords = quantum-information diffusion-model generative-model
language = English
status = 3
-requirements = torch numpy matplotlib scipy pandas omegaconf qiskit tqdm joblib open_clip_torch ipywidgets pylatexenc huggingface_hub
-dev_requirements = jupyterlab nbdev cudaq
+requirements = torch numpy matplotlib scipy omegaconf qiskit tqdm joblib open_clip_torch ipywidgets pylatexenc safetensors tensordict huggingface_hub
+dev_requirements = jupyterlab nbdev cudaq pennylane
diff --git a/setup.py b/setup.py
index e3281ae..d8de687 100644
--- a/setup.py
+++ b/setup.py
@@ -1,11 +1,14 @@
-from pkg_resources import parse_version
+import shlex
from configparser import ConfigParser
-import setuptools, shlex
+
+import setuptools
+from pkg_resources import parse_version
+
assert parse_version(setuptools.__version__)>=parse_version('36.2')
# note: all settings are in settings.ini; edit there, not here
config = ConfigParser(delimiters=['='])
-config.read('settings.ini', encoding='utf-8')
+config.read('settings.ini', encoding="utf-8")
cfg = config['DEFAULT']
cfg_keys = 'version description keywords author author_email'.split()
@@ -18,17 +21,21 @@
'mit': ('MIT License', 'OSI Approved :: MIT License'),
'gpl2': ('GNU General Public License v2', 'OSI Approved :: GNU General Public License v2 (GPLv2)'),
'gpl3': ('GNU General Public License v3', 'OSI Approved :: GNU General Public License v3 (GPLv3)'),
+ 'agpl3': ('GNU Affero General Public License v3', 'OSI Approved :: GNU Affero General Public License (AGPLv3)'),
'bsd3': ('BSD License', 'OSI Approved :: BSD License'),
}
-statuses = [ '1 - Planning', '2 - Pre-Alpha', '3 - Alpha',
+statuses = [ '0 - Pre-Planning', '1 - Planning', '2 - Pre-Alpha', '3 - Alpha',
'4 - Beta', '5 - Production/Stable', '6 - Mature', '7 - Inactive' ]
-py_versions = '3.6 3.7 3.8 3.9 3.10'.split()
+py_versions = '3.7 3.8 3.9 3.10 3.12 3.13'.split()
-requirements = shlex.split(cfg.get('requirements', ''))
+requirements = ['packaging']
+requirements += shlex.split(cfg.get('requirements', ''))
if cfg.get('pip_requirements'): requirements += shlex.split(cfg.get('pip_requirements', ''))
min_python = cfg['min_python']
lic = licenses.get(cfg['license'].lower(), (cfg['license'], None))
dev_requirements = (cfg.get('dev_requirements') or '').split()
+project_urls = {}
+if cfg.get('doc_host'): project_urls["Documentation"] = cfg['doc_host'] + cfg.get('doc_baseurl', '')
setuptools.setup(
name = cfg['lib_name'],
@@ -45,13 +52,12 @@
extras_require={ 'dev': dev_requirements },
dependency_links = cfg.get('dep_links','').split(),
python_requires = '>=' + cfg['min_python'],
- long_description = open('README.md', encoding='utf-8').read(),
+ long_description = open('README.md', encoding="utf8").read(),
long_description_content_type = 'text/markdown',
zip_safe = False,
entry_points = {
'console_scripts': cfg.get('console_scripts','').split(),
'nbdev': [f'{cfg.get("lib_path")}={cfg.get("lib_path")}._modidx:d']
},
- **setup_cfg)
-
-
+ project_urls = project_urls,
+ **setup_cfg)
\ No newline at end of file
diff --git a/src/404.qmd b/src/404.qmd
new file mode 100644
index 0000000..e242c18
--- /dev/null
+++ b/src/404.qmd
@@ -0,0 +1,13 @@
+---
+title: Page Not Found
+---
+
+The page you requested cannot be found (perhaps it was moved or renamed).
+
+$$
+\begin{equation*}
+ \langle \;\text{you}\;|\;\text{this page}\;|\;\text{you}\;\rangle \;= \;\text{?}
+\end{equation*}
+$$
+
+You may want to try searching to find the page's new location.
\ No newline at end of file
diff --git a/src/_quarto.yml b/src/_quarto.yml
index 35d10bb..7c8c5ea 100644
--- a/src/_quarto.yml
+++ b/src/_quarto.yml
@@ -3,76 +3,86 @@ project:
format:
html:
- # page-layout: full
+ page-layout: full
theme:
- light: simplex
+ light: [simplex, webpage/custom.scss]
# dark: darkl
- css: styles.css
+ css: webpage/styles.css
+ mainfont: "Lexend"
toc: true
code-copy: true
code-overflow: wrap
grid:
content-mode: standard
- sidebar-width: "350px"
+ sidebar-width: "380px"
body-width: "1100px"
- margin-width: "250px"
+ margin-width: "300px" #"250px"
+ gutter-width: 2.5rem
website:
- favicon: "assets/logo.png"
+ page-footer:
+ center: "Copyright 2025, Florian Fürrutter"
+ favicon: "webpage/assets/logo.png"
open-graph: true
- repo-actions: [issue]
+ repo-actions: [issue, source]
back-to-top-navigation: true
page-navigation: true
navbar:
- logo: "assets/logo.png"
- background: "#5cb4c1"
+ logo: "webpage/assets/logo.png"
+ logo-alt: "genQC logo"
search: true
left:
- - icon: file-text
- href: "https://arxiv.org/abs/2311.02041"
- text: "paper-arxiv"
+ - text: "Overview"
+ icon: list-ul
+ href: index.qmd
+
+ - text: "Get Started"
+ icon: cursor-fill
+ href: get_started.ipynb
+
+ - text: "Tutorials"
+ icon: cup-hot
+ href: examples/tutorials.qmd
+
+ - text: "API Reference"
+ icon: file-text
+ href: webpage/api_reference.qmd
+
+ - text: Research
+ icon: book-half
+ href: webpage/research.qmd
+
right:
- icon: github
href: "https://github.com/FlorianFuerrutter/genQC"
- text: code repository
+ text: Code Repository
sidebar:
- collapse-level: 2
- style: "floating"
- background: "#dde8ea"
- foreground: "#674ea7"
-
- contents:
- - text: "genQC · Generative Quantum Circuits"
- href: index.ipynb
- - section: Examples
- contents: examples/*
-
- - section: Lib
- contents:
- - section: Pipeline
- contents: pipeline/*
- - section: Scheduler
- contents: scheduler/*
- - section: Inference
- contents: inference/*
- - section: Models
- contents: models/*
- - section: Dataset
- contents: dataset/*
- - section: Platform
- contents:
- - auto: platform/*
- - section: Simulation
- contents: platform/simulation/*
- - section: Miscellaneous functions
- contents:
- - auto: /*.ipynb
-
- - text: "Release notes"
- href: RELEASES.md
+ #-------------------------------------
+ - title: "Tutorials"
+ collapse-level: 1
+ header: "Tutorials"
+ style: "floating"
+ contents:
+ - text: "Tutorials Overview"
+ href: examples/tutorials.qmd
+ - text: "---"
+ - auto: examples/**/!(tutorials.qmd)*{.qmd,.ipynb}
+
+ #-------------------------------------
+ - title: "API Reference"
+ collapse-level: 1
+ header: "API Reference"
+ style: "floating"
+ contents:
+ - text: "Modules Overview"
+ href: webpage/api_reference.qmd
+ - text: "Release notes"
+ href: RELEASES.md
+ - text: "---"
+ - auto: "/!(webpage|examples)/**/*"
metadata-files: [nbdev.yml]
diff --git a/src/benchmark/bench_compilation.ipynb b/src/benchmark/bench_compilation.ipynb
new file mode 100644
index 0000000..7d14ceb
--- /dev/null
+++ b/src/benchmark/bench_compilation.ipynb
@@ -0,0 +1,750 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "77b00dc5-b55a-4d1c-a364-b4226779b409",
+ "metadata": {},
+ "source": [
+ "# Compilation benchmark\n",
+ "\n",
+ "> Functions to test and benchmark unitary compilation."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a36b0d5a-5131-439c-82f5-bc551ecb24e1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| default_exp benchmark.bench_compilation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a593dee3-594e-4760-a02d-db5559f5f25f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "from genQC.imports import *"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7f7792a9-71d4-4443-93db-8b6361a42d76",
+ "metadata": {},
+ "source": [
+ "## Special unitaries"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "584cfa35-3391-4d21-a130-773b8f701d86",
+ "metadata": {},
+ "source": [
+ "#### Quantum Fourier transform\n",
+ "\n",
+ "$$\n",
+ "\\begin{equation}\n",
+ " \\mathrm{QFT}: |x\\rangle \\mapsto \\frac{1}{\\sqrt{N}} \\sum_{k=0}^{N-1} \\omega_N^{xk}\\;|k\\rangle,\n",
+ "\\end{equation}\n",
+ "$$\n",
+ "where\n",
+ "$$\n",
+ "\\begin{equation}\n",
+ " \\omega_N=\\exp{\\frac{2\\pi i}{N}} \\quad\\text{and}\\quad N=2^{\\text{qubits}}.\n",
+ "\\end{equation}\n",
+ "$$"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e9337291-4d9e-4524-8fe8-9d198a2abb24",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "class SpecialUnitaries:\n",
+ " \"\"\"Special unitary matrices to benchmark compilation.\"\"\"\n",
+ " \n",
+ " @staticmethod\n",
+ " def QFT(num_qubits: int) -> torch.Tensor:\n",
+ " \"\"\"The Quantum Fourier transform (QFT) unitary for `num_qubits`-qubits.\"\"\"\n",
+ " \n",
+ " N = 2**num_qubits\n",
+ " wN = np.exp(2.0j*np.pi/N)\n",
+ "\n",
+ " U = torch.zeros((N, N), dtype=torch.complex128) \n",
+ " for x in range(N):\n",
+ " U[:, x] = torch.tensor([np.power(wN, x*k, dtype=complex) for k in range(N)])\n",
+ "\n",
+ " U *= 1.0/np.sqrt(N) \n",
+ " return U"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0b263212-b4e5-4d66-b2dd-d0eb44b4ec91",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# test QFT for N=4\n",
+ "QFT_2_qubits = 0.5 * torch.tensor([[1, 1, 1, 1],\n",
+ " [1, 1j, -1, -1j],\n",
+ " [1, -1, 1, -1],\n",
+ " [1, -1j, -1, 1j]], dtype=torch.complex128)\n",
+ "\n",
+ "assert torch.allclose(SpecialUnitaries.QFT(num_qubits=2), QFT_2_qubits)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7420fa00-b96b-4ed9-b465-ee8015abf665",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "tensor([[ 0.3540+0.0000j, 0.3540+0.0000j, 0.3540+0.0000j, 0.3540+0.0000j, 0.3540+0.0000j, 0.3540+0.0000j, 0.3540+0.0000j, 0.3540+0.0000j],\n",
+ " [ 0.3540+0.0000j, 0.2500+0.2500j, 0.0000+0.3540j, -0.2500+0.2500j, -0.3540+0.0000j, -0.2500-0.2500j, 0.0000-0.3540j, 0.2500-0.2500j],\n",
+ " [ 0.3540+0.0000j, 0.0000+0.3540j, -0.3540+0.0000j, 0.0000-0.3540j, 0.3540+0.0000j, 0.0000+0.3540j, -0.3540+0.0000j, 0.0000-0.3540j],\n",
+ " [ 0.3540+0.0000j, -0.2500+0.2500j, 0.0000-0.3540j, 0.2500+0.2500j, -0.3540+0.0000j, 0.2500-0.2500j, 0.0000+0.3540j, -0.2500-0.2500j],\n",
+ " [ 0.3540+0.0000j, -0.3540+0.0000j, 0.3540+0.0000j, -0.3540+0.0000j, 0.3540+0.0000j, -0.3540+0.0000j, 0.3540+0.0000j, -0.3540+0.0000j],\n",
+ " [ 0.3540+0.0000j, -0.2500-0.2500j, 0.0000+0.3540j, 0.2500-0.2500j, -0.3540+0.0000j, 0.2500+0.2500j, 0.0000-0.3540j, -0.2500+0.2500j],\n",
+ " [ 0.3540+0.0000j, 0.0000-0.3540j, -0.3540+0.0000j, 0.0000+0.3540j, 0.3540+0.0000j, 0.0000-0.3540j, -0.3540+0.0000j, 0.0000+0.3540j],\n",
+ " [ 0.3540+0.0000j, 0.2500-0.2500j, 0.0000-0.3540j, -0.2500-0.2500j, -0.3540+0.0000j, -0.2500+0.2500j, 0.0000+0.3540j, 0.2500+0.2500j]], dtype=torch.complex128)"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "np.round(SpecialUnitaries.QFT(3), 3)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "acdc565a-f01b-4e1e-8cc7-ffba730f46b2",
+ "metadata": {},
+ "source": [
+ "## Hamiltonian evolutions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "86c32afa-32ad-4a52-9aa6-144d8f76f1f2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "sigma_x = torch.tensor([[0, 1],\n",
+ " [1, 0]],\n",
+ " dtype=torch.complex128)\n",
+ "\n",
+ "sigma_y = torch.tensor([[ 0, -1j],\n",
+ " [1j, 0]],\n",
+ " dtype=torch.complex128)\n",
+ "\n",
+ "sigma_z = torch.tensor([[1, 0],\n",
+ " [0, -1]],\n",
+ " dtype=torch.complex128)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c9c7d034-6310-4f42-a626-686880060cd1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "assert torch.allclose(sigma_x@sigma_x, torch.eye(2, dtype=torch.complex128))\n",
+ "assert torch.allclose(sigma_y@sigma_y, torch.eye(2, dtype=torch.complex128))\n",
+ "assert torch.allclose(sigma_z@sigma_z, torch.eye(2, dtype=torch.complex128))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "485317ad-e46c-4652-bd2b-89c0c03afea2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "def qubit_tensor_product(num_qubits: int, *ops: torch.Tensor, pos: int | Sequence[int]) -> torch.Tensor:\n",
+ " \"\"\"\n",
+ " Make tensor product with identities, assumes `ops` placed at `pos` in the tensor product ordering.\n",
+ " \"\"\"\n",
+ "\n",
+ " _ops = [torch.eye(2) for i in range(num_qubits)]\n",
+ "\n",
+ " if isinstance(pos, int):\n",
+ " pos = [pos]\n",
+ " elif isinstance(pos, Sequence):\n",
+ " assert len(pos) == len(ops)\n",
+ " else:\n",
+ " raise NotImplementedError()\n",
+ "\n",
+ " for pos_i, ops_i in zip(pos, ops):\n",
+ " _ops[pos_i] = ops_i\n",
+ " \n",
+ " mat = _ops[0]\n",
+ " for op in _ops[1:]:\n",
+ " mat = torch.kron(mat, op)\n",
+ "\n",
+ " return mat"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f5bddb3e-39fc-496f-9cdc-6cc1423c0b49",
+ "metadata": {},
+ "source": [
+ "$\\sigma_x \\otimes I$:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "26bf121b-5a83-4ef3-b222-3ffe79dc4fea",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "tensor([[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],\n",
+ " [0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],\n",
+ " [1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],\n",
+ " [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j]], dtype=torch.complex128)"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "qubit_tensor_product(2, sigma_x, pos=0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2f43cbd2-e966-46cc-99f5-8f36af63636f",
+ "metadata": {},
+ "source": [
+ "$I \\otimes \\sigma_x$:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a467fa4e-182f-47df-a4e5-50d98ff98e81",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "tensor([[0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],\n",
+ " [1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],\n",
+ " [0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],\n",
+ " [0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j]], dtype=torch.complex128)"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "qubit_tensor_product(2, sigma_x, pos=-1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ae425eac-ae09-4658-8e45-eaf370f6e7ed",
+ "metadata": {},
+ "source": [
+ "$\\sigma_z \\otimes \\sigma_z$:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c716c26d-86d2-42fd-9414-b5ef833cd000",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "tensor([[ 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],\n",
+ " [ 0.+0.j, -1.+0.j, 0.+0.j, -0.+0.j],\n",
+ " [ 0.+0.j, 0.+0.j, -1.+0.j, -0.+0.j],\n",
+ " [ 0.+0.j, -0.+0.j, -0.+0.j, 1.-0.j]], dtype=torch.complex128)"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "qubit_tensor_product(2, sigma_z, sigma_z, pos=[0, 1])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e7f9d723-3105-47b5-8a6a-0ee492061e21",
+ "metadata": {},
+ "source": [
+ "#### Base Hamiltonian"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ecb265cc-521a-44fd-bf8d-c97b46938932",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "class BaseHamiltonian(abc.ABC):\n",
+ " \"\"\"Base implementation of a Hamiltonian.\"\"\"\n",
+ "\n",
+ " def __init__(self, device: Optional[str | torch.device] = None) -> None:\n",
+ " self.device = default(device, \"cpu\")\n",
+ " self._generate_matrix()\n",
+ " \n",
+ " if not torch.allclose(self.data.adjoint(), self.data):\n",
+ " raise RuntimeError(\"Generated Hamiltonian matrix is not self-adjoint!\")\n",
+ " \n",
+ " @abc.abstractmethod\n",
+ " def _generate_matrix(self) -> torch.Tensor:\n",
+ " \"\"\"Generates the Hamiltonian matrix into `self.data`.\"\"\"\n",
+ " raise NotImplementedError()\n",
+ "\n",
+ " def get_evolution(self, t: float | torch.Tensor, split_complex_channel: bool = False, dtype: Optional[torch.dtype] = None) -> torch.Tensor:\n",
+ " \"\"\"\n",
+ " Assuming `h_bar=1`. Returns the unitary evolution in marix form.\n",
+ " \"\"\"\n",
+ " U = torch.linalg.matrix_exp(-1j * t * self.data)\n",
+ "\n",
+ " if split_complex_channel:\n",
+ " U = torch.stack([torch.real(U), torch.imag(U)])\n",
+ "\n",
+ " if exists(dtype):\n",
+ " U = U.to(dtype)\n",
+ " \n",
+ " return U"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5135f0f1-c46d-4ba7-b27c-d665e5df66d7",
+ "metadata": {},
+ "source": [
+ "#### Ising Hamiltonian\n",
+ "\n",
+ "Defined as\n",
+ "$$\n",
+ "H = -J \\sum_{\\langle i, j \\rangle} \\sigma_i^z \\sigma_j^z - h \\sum_i \\sigma_i^x,\n",
+ "$$\n",
+ "where $J$ is the coupling constant and $h$ a magnetic field."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8e1da512-6e2f-4eb4-aade-5cb822b6e6f9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "class IsingHamiltonian(BaseHamiltonian):\n",
+ " \"\"\"Implementation of the Ising Hamiltonian on a qubit chain.\"\"\"\n",
+ " \n",
+ " def __init__(self, \n",
+ " h: float, \n",
+ " J: float, \n",
+ " num_qubits: int, \n",
+ " periodic_boundary: bool = True,\n",
+ " device: Optional[str | torch.device] = None) -> None:\n",
+ " \"\"\"\n",
+ " h: Magnetic field \n",
+ " J: Coupling constant \n",
+ " \"\"\"\n",
+ " self.h = h\n",
+ " self.J = J \n",
+ " self.num_qubits = num_qubits\n",
+ " self.periodic_boundary = periodic_boundary\n",
+ " super().__init__(device)\n",
+ " \n",
+ " def _generate_matrix(self) -> torch.Tensor:\n",
+ " \"\"\"\n",
+ " Note: We take big endian convention in placing the `i,j`-sigmas in tensor product ordering.\n",
+ " For little endian we need to use `pos = self.num_qubits-i`.\n",
+ " \"\"\"\n",
+ " \n",
+ " N = 2**self.num_qubits\n",
+ " ham = torch.zeros((N, N), dtype=torch.complex128)\n",
+ "\n",
+ " pairs = [(i, i+1) for i in range(self.num_qubits-1)]\n",
+ " \n",
+ " if self.periodic_boundary:\n",
+ " pairs.append((self.num_qubits-1, 0))\n",
+ "\n",
+ " for (i, j) in pairs:\n",
+ " Z_term = qubit_tensor_product(self.num_qubits, sigma_z, sigma_z, pos=[i, j])\n",
+ "\n",
+ " # Coupling + Perturbation\n",
+ " ham += -self.J * Z_term\n",
+ "\n",
+ " # Magnetic\n",
+ " for i in range(self.num_qubits):\n",
+ " ham += -self.h * qubit_tensor_product(self.num_qubits, sigma_x, pos=i)\n",
+ "\n",
+ " self.data = ham.to(self.device)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "77a808ae-11db-448e-b52b-bd83f0299c12",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "tensor([[-2.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],\n",
+ " [ 0.+0.j, 2.+0.j, 0.+0.j, 0.+0.j],\n",
+ " [ 0.+0.j, 0.+0.j, 2.+0.j, 0.+0.j],\n",
+ " [ 0.+0.j, 0.+0.j, 0.+0.j, -2.+0.j]], dtype=torch.complex128)"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "hamiltonian = IsingHamiltonian(h=0, J=1, num_qubits=2)\n",
+ "hamiltonian.data"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "23dbc4f0-e61f-46f0-9624-eb8e60c2a59e",
+ "metadata": {},
+ "source": [
+ "Eigenvalues of this Hamiltonian:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1cd6ff54-0f31-44a1-bbfa-d5753b1a8c55",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "tensor([-2., -2., 2., 2.], dtype=torch.float64)"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "torch.linalg.eigvalsh(hamiltonian.data)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "dd201def-ba78-4262-b733-3484a0d916f6",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "tensor([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],\n",
+ " [0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],\n",
+ " [0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],\n",
+ " [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j]], dtype=torch.complex128)"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "e, v = torch.linalg.eigh(hamiltonian.data)\n",
+ "v"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6935b595-89f5-4734-a80f-44d0342c33a1",
+ "metadata": {},
+ "source": [
+ "And the evolution unitary is:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "715e07a0-f2bd-41ed-9d53-7bf370eebb9c",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "tensor([[0.5000+0.8660j, 0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j],\n",
+ " [0.0000+0.0000j, 0.5000-0.8660j, 0.0000+0.0000j, 0.0000+0.0000j],\n",
+ " [0.0000+0.0000j, 0.0000+0.0000j, 0.5000-0.8660j, 0.0000+0.0000j],\n",
+ " [0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.5000+0.8660j]], dtype=torch.complex128)"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "hamiltonian.get_evolution(t=np.pi/6)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3b01a2f2-14fc-453a-9076-8442b7a51c6a",
+ "metadata": {},
+ "source": [
+ "#### XXZ Hamiltonian\n",
+ "\n",
+ "Defined as\n",
+ "$$\n",
+ "H = -J \\sum_{\\langle i, j \\rangle} ( \\sigma_i^x \\sigma_j^x + \\sigma_i^y \\sigma_j^y + \\Delta \\sigma_i^z \\sigma_j^z ) - h \\sum_i \\sigma_i^x,\n",
+ "$$\n",
+ "where $J$ is the coupling constant, $\\Delta$ a perturbation and $h$ a magnetic field."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "bcff4597-de9d-436e-908a-ff683e868478",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "class XXZHamiltonian(BaseHamiltonian):\n",
+ " \"\"\"Implementation of the XXZ Hamiltonian on a qubit chain.\"\"\"\n",
+ " \n",
+ " def __init__(self, \n",
+ " h: float, \n",
+ " J: float, \n",
+ " delta: float, \n",
+ " num_qubits: int, \n",
+ " periodic_boundary: bool = True,\n",
+ " device: Optional[str | torch.device] = None) -> None:\n",
+ " \"\"\"\n",
+ " h: Magnetic field \n",
+ " J: Coupling constant \n",
+ " delta: Perturbation\n",
+ " \"\"\"\n",
+ " self.h = h\n",
+ " self.J = J \n",
+ " self.delta = delta\n",
+ " self.num_qubits = num_qubits\n",
+ " self.periodic_boundary = periodic_boundary\n",
+ " super().__init__(device)\n",
+ " \n",
+ " def _generate_matrix(self) -> torch.Tensor:\n",
+ " \"\"\"\n",
+ " Note: We take big endian convention in placing the `i,j`-sigmas in tensor product ordering.\n",
+ " For little endian we need to use `pos = self.num_qubits-i`.\n",
+ " \"\"\"\n",
+ " \n",
+ " N = 2**self.num_qubits\n",
+ " ham = torch.zeros((N, N), dtype=torch.complex128)\n",
+ "\n",
+ " pairs = [(i, i+1) for i in range(self.num_qubits-1)]\n",
+ " \n",
+ " if self.periodic_boundary:\n",
+ " pairs.append((self.num_qubits-1, 0))\n",
+ "\n",
+ " for (i, j) in pairs:\n",
+ " X_term = qubit_tensor_product(self.num_qubits, sigma_x, sigma_x, pos=[i, j])\n",
+ " Y_term = qubit_tensor_product(self.num_qubits, sigma_y, sigma_y, pos=[i, j])\n",
+ " Z_term = qubit_tensor_product(self.num_qubits, sigma_z, sigma_z, pos=[i, j])\n",
+ "\n",
+ " # Coupling + Perturbation\n",
+ " ham += -self.J * (X_term + Y_term + self.delta * Z_term)\n",
+ "\n",
+ " # Magnetic\n",
+ " for i in range(self.num_qubits):\n",
+ " ham += -self.h * qubit_tensor_product(self.num_qubits, sigma_x, pos=i)\n",
+ "\n",
+ " self.data = ham.to(self.device)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b4bb213f-bd28-4b4e-97a4-f8f285596394",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "tensor([[-2.+0.j, -1.+0.j, -1.+0.j, 0.+0.j],\n",
+ " [-1.+0.j, 2.+0.j, -4.+0.j, -1.+0.j],\n",
+ " [-1.+0.j, -4.+0.j, 2.+0.j, -1.+0.j],\n",
+ " [ 0.+0.j, -1.+0.j, -1.+0.j, -2.+0.j]], dtype=torch.complex128)"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "hamiltonian = XXZHamiltonian(h=1, J=1, delta=1, num_qubits=2)\n",
+ "hamiltonian.data"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9f261b77-79ba-41fc-b47a-eeff4c05a7d7",
+ "metadata": {},
+ "source": [
+ "Eigenvalues of this Hamiltonian:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "db06d406-5477-44a5-aec8-f7fab7895130",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "tensor([-4.0000e+00, -2.0000e+00, 8.8818e-16, 6.0000e+00], dtype=torch.float64)"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "torch.linalg.eigvalsh(hamiltonian.data)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "85448086-ac18-4fa4-a887-8887ac062d19",
+ "metadata": {},
+ "source": [
+ "And the evolution unitary is:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0c9a6406-65f2-41c0-8eb2-26479e4df2f8",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "tensor([[ 0.3750+0.6495j, -0.3750+0.2165j, -0.3750+0.2165j, -0.1250-0.2165j],\n",
+ " [-0.3750+0.2165j, -0.3750+0.2165j, 0.6250+0.2165j, -0.3750+0.2165j],\n",
+ " [-0.3750+0.2165j, 0.6250+0.2165j, -0.3750+0.2165j, -0.3750+0.2165j],\n",
+ " [-0.1250-0.2165j, -0.3750+0.2165j, -0.3750+0.2165j, 0.3750+0.6495j]], dtype=torch.complex128)"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "hamiltonian.get_evolution(t=np.pi/6)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d29002e5-db43-4562-86bc-b2cb0ec8ab57",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "tensor([[[ 0.3750, -0.3750, -0.3750, -0.1250],\n",
+ " [-0.3750, -0.3750, 0.6250, -0.3750],\n",
+ " [-0.3750, 0.6250, -0.3750, -0.3750],\n",
+ " [-0.1250, -0.3750, -0.3750, 0.3750]],\n",
+ "\n",
+ " [[ 0.6495, 0.2165, 0.2165, -0.2165],\n",
+ " [ 0.2165, 0.2165, 0.2165, 0.2165],\n",
+ " [ 0.2165, 0.2165, 0.2165, 0.2165],\n",
+ " [-0.2165, 0.2165, 0.2165, 0.6495]]], dtype=torch.float64)"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "hamiltonian.get_evolution(t=np.pi/6, split_complex_channel=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a12db28d-f69a-41bd-863d-9d2cd12494df",
+ "metadata": {},
+ "source": [
+ "# Export -"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7eea527e-0aa8-4814-a413-c5e580d51969",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| hide\n",
+ "import nbdev; nbdev.nbdev_export()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "python3",
+ "language": "python",
+ "name": "python3"
+ },
+ "widgets": {
+ "application/vnd.jupyter.widget-state+json": {
+ "state": {},
+ "version_major": 2,
+ "version_minor": 0
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/src/config_loader.ipynb b/src/config_loader.ipynb
deleted file mode 100644
index 8c94d19..0000000
--- a/src/config_loader.ipynb
+++ /dev/null
@@ -1,247 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "id": "a8980c24-d62e-462b-ba89-3195cfdcc374",
- "metadata": {},
- "source": [
- "# Config loader"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "1bb62f14-03c7-4d64-b1b9-f1d3ae309b01",
- "metadata": {},
- "source": [
- "Code using `omegaconf` to handle IO."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "a8832bdd-f61c-44e1-8619-a9cb352ba768",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| default_exp config_loader"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "06272f6f-b4e3-4504-a90a-feebbf6ad821",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "from genQC.imports import *\n",
- "from omegaconf import OmegaConf"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "9b6c0b5e-4779-4c4a-98e9-46a3dca8bee6",
- "metadata": {},
- "source": [
- "## IO"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "cb173637-3d18-4f94-8b95-76cda4117b1e",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "def class_to_str(cls):\n",
- " return str(cls)[8:-2]"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "e0d5bc35-cc53-42fb-8fcd-8f2bc66c7c9b",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "def load_config(file_path):\n",
- " return OmegaConf.load(f\"{file_path}\")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "b105c04a-66d1-4450-8ee0-87aae618e60a",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "def config_to_dict(config):\n",
- " return OmegaConf.to_container(config)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "e3d81e5c-cf3d-4152-ab66-acd6e42ec3c9",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "def save_dataclass_yaml(data_obj, file_path):\n",
- " conf = OmegaConf.structured(data_obj)\n",
- " with open(file_path, 'w') as f:\n",
- " OmegaConf.save(config=conf, f=f)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "bf89bbfc-9d4e-442f-96ea-db1ab99505e9",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "def save_dict_yaml(dict_obj, file_path):\n",
- " conf = OmegaConf.create(dict_obj)\n",
- " with open(file_path, 'w') as f:\n",
- " OmegaConf.save(config=conf, f=f)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "ef21ca53-aa2c-4faa-877f-a9b39eeb8ff4",
- "metadata": {},
- "source": [
- "Test"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "ed2b17fc-e4d9-4967-89cb-4a0bb28e39a2",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "{'target': 'omegaconf.omegaconf.OmegaConf', 'clr_dim': 80, 'features': [1, 2, 3]}"
- ]
- },
- "execution_count": null,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "@dataclass\n",
- "class MyConfig: \n",
- " target:str = class_to_str(OmegaConf)\n",
- " clr_dim: int = 80\n",
- " features: list[int]=None\n",
- " \n",
- "c = MyConfig()\n",
- "c.features = [1,2,3]\n",
- "\n",
- "OmegaConf.structured(c)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "a3cbe4ce-7e90-413b-b55e-e07a9eeb6d8f",
- "metadata": {},
- "source": [
- "## Object config load"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "3398beb4-8b77-4a8b-9075-b3f6a9775bcd",
- "metadata": {},
- "source": [
- "Mostly taken from: https://github.com/Stability-AI/stablediffusion"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "6547d020-5764-4379-92b2-583d8f6f4bc5",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "def get_obj_from_str(string, reload=False):\n",
- " module, cls = string.rsplit(\".\", 1)\n",
- " if reload:\n",
- " module_imp = importlib.import_module(module)\n",
- " importlib.reload(module_imp)\n",
- " return getattr(importlib.import_module(module, package=None), cls)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "ffc7fa40-81aa-42ed-ac23-8562ffdc8e4f",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "def instantiate_from_config(config):\n",
- " if not \"target\" in config: raise KeyError(\"Expected key `target` to instantiate.\")\n",
- " if not \"params\" in config: print(f\"[WARNING] Expected key `params` to instantiate.\")\n",
- " return get_obj_from_str(config[\"target\"])(**config.get(\"params\", dict()))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "36032308-bd0e-4409-9db0-9d89fc258e5a",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "def load_model_from_config(config, ckpt, device):\n",
- " \n",
- " print(f\"Loading model from {ckpt}\")\n",
- " pl_sd = torch.load(ckpt, map_location=torch.device(device).type, weights_only=True)\n",
- " \n",
- " model = instantiate_from_config(config.model)\n",
- " \n",
- " sd = pl_sd[\"state_dict\"]\n",
- " m, u = model.load_state_dict(sd, strict=True)\n",
- " \n",
- " return model.to(device)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "f41f26a8-ac40-4e91-8c0e-1ef07a0fd4f4",
- "metadata": {},
- "source": [
- "# Export -"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "a0474216-8e0c-4ba7-9a37-571ac7d8e82c",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| hide\n",
- "import nbdev; nbdev.nbdev_export()"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "python3",
- "language": "python",
- "name": "python3"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/src/dataset/balancing.ipynb b/src/dataset/balancing.ipynb
new file mode 100644
index 0000000..23e4603
--- /dev/null
+++ b/src/dataset/balancing.ipynb
@@ -0,0 +1,157 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "a8980c24-d62e-462b-ba89-3195cfdcc374",
+ "metadata": {},
+ "source": [
+ "# Dataset balancing\n",
+ "\n",
+ "> Helper functions used to balance a dataset."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a8832bdd-f61c-44e1-8619-a9cb352ba768",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| default_exp dataset.balancing"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "06272f6f-b4e3-4504-a90a-feebbf6ad821",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "from genQC.imports import *\n",
+ "import genQC.dataset.dataset_helper as dahe"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7132df6f-d099-40e0-95a8-9d735211b2dc",
+ "metadata": {},
+ "source": [
+ "## Qircuit length balancing"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e57ce23e-30fc-434f-9443-3cf97f507b89",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "def get_tensor_gate_length(clr_tensor: torch.Tensor, padding_token: int = 0) -> torch.Tensor:\n",
+ " \"\"\"\n",
+ " Returns the gate count of a tokenized circuit.\n",
+ " Make sure you use use the correct `padding_token`.\n",
+ " \n",
+ " \"\"\"\n",
+ " assert clr_tensor.dim() == 3, \"[b, s, t]\"\n",
+ " \n",
+ " red_clr_tensor = (clr_tensor != padding_token).any(dim=1) # [b, t]\n",
+ " return torch.count_nonzero(red_clr_tensor, dim=1) # [b]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "36032308-bd0e-4409-9db0-9d89fc258e5a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "def add_balance_fn_quantile_qc_length(indices: Union[np.ndarray, torch.Tensor], \n",
+ " x: Union[np.ndarray, torch.Tensor], \n",
+ " y: Union[np.ndarray, torch.Tensor], \n",
+ " *z, \n",
+ " padding_token: int = 0,\n",
+ " balance_quantile: float = 0.5,\n",
+ " device: torch.device = torch.device(\"cpu\"),\n",
+ " quantile_length_weights: Optional[Callable[[torch.Tensor, torch.Tensor], torch.Tensor]] = None) -> torch.Tensor:\n",
+ " \"\"\"Balances according to gate length.\"\"\"\n",
+ " \n",
+ " xb = x[indices].to(device)\n",
+ " l = get_tensor_gate_length(xb, padding_token=padding_token).to(device)\n",
+ " \n",
+ " l_uniques, l_uniques_cnt = torch.unique(l, dim=0, return_counts=True)\n",
+ "\n",
+ " #-----------------------------------\n",
+ " # samples = torch.min(l_uniques_cnt)\n",
+ " # samples = torch.median(l_uniques_cnt)\n",
+ " samples = torch.quantile(l_uniques_cnt.float(), balance_quantile, interpolation='nearest', dim=0).to(l_uniques_cnt.dtype)\n",
+ " samples = max(samples, 2)\n",
+ "\n",
+ " #-----------------------------------\n",
+ " sub_ind = list() \n",
+ " for l_unique in l_uniques.to(device): \n",
+ " comp = (l==l_unique)\n",
+ " ind = comp.nonzero().squeeze().cpu()\n",
+ " \n",
+ " if ind.dim() > 0:\n",
+ " if exists(quantile_length_weights):\n",
+ " _samples = int(quantile_length_weights(l_unique, samples))\n",
+ " else:\n",
+ " _samples = samples\n",
+ " \n",
+ " ind = dahe.shuffle_tensor_dataset(ind) \n",
+ " ind = ind[:_samples]\n",
+ " else:\n",
+ " ind = ind[None]\n",
+ " \n",
+ " sub_ind.append(ind)\n",
+ "\n",
+ " sub_ind = torch.cat(sub_ind, dim=0)\n",
+ " \n",
+ " indices = indices[sub_ind]\n",
+ " \n",
+ " if indices.ndim < 1: \n",
+ " indices = indices[None]\n",
+ " \n",
+ " return indices"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f41f26a8-ac40-4e91-8c0e-1ef07a0fd4f4",
+ "metadata": {},
+ "source": [
+ "# Export -"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a0474216-8e0c-4ba7-9a37-571ac7d8e82c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| hide\n",
+ "import nbdev; nbdev.nbdev_export()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "python3",
+ "language": "python",
+ "name": "python3"
+ },
+ "widgets": {
+ "application/vnd.jupyter.widget-state+json": {
+ "state": {},
+ "version_major": 2,
+ "version_minor": 0
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/src/dataset/cached_qc_dataset.ipynb b/src/dataset/cached_dataset.ipynb
similarity index 52%
rename from src/dataset/cached_qc_dataset.ipynb
rename to src/dataset/cached_dataset.ipynb
index 5015d3f..a6d5234 100644
--- a/src/dataset/cached_qc_dataset.ipynb
+++ b/src/dataset/cached_dataset.ipynb
@@ -5,39 +5,45 @@
"id": "a8980c24-d62e-462b-ba89-3195cfdcc374",
"metadata": {},
"source": [
- "# Cached quantum circuit dataset"
+ "# Cached dataset\n",
+ "\n",
+ "> Classes to create a dataset with cached labels."
]
},
{
- "cell_type": "markdown",
- "id": "21762ddf-229e-4e48-aab6-b897c30ba1a4",
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a8832bdd-f61c-44e1-8619-a9cb352ba768",
"metadata": {},
+ "outputs": [],
"source": [
- "Quantum circuit dataset that caches the `y` prompts using the CLIP encoder. This speeds up training significantly!"
+ "#| default_exp dataset.cached_dataset"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "a8832bdd-f61c-44e1-8619-a9cb352ba768",
+ "id": "06272f6f-b4e3-4504-a90a-feebbf6ad821",
"metadata": {},
"outputs": [],
"source": [
- "#| default_exp dataset.cached_qc_dataset"
+ "#| export\n",
+ "from genQC.imports import *\n",
+ "from genQC.dataset.config_dataset import ConfigDataset, ConfigDatasetConfig\n",
+ "from genQC.utils.config_loader import *"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "06272f6f-b4e3-4504-a90a-feebbf6ad821",
+ "id": "ac8e640e-c614-4d52-b772-c173b2682ad9",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
- "from genQC.imports import *\n",
- "from genQC.dataset.qc_dataset import Qc_Config_Dataset\n",
- "from genQC.dataset.config_dataset import Config_Dataset\n",
- "from genQC.config_loader import *"
+ "@dataclass\n",
+ "class CachedOpenCLIPDatasetConfig(ConfigDatasetConfig):\n",
+ " pass"
]
},
{
@@ -48,11 +54,17 @@
"outputs": [],
"source": [
"#| export\n",
- "class Cached_OpenClip_Dataset(Qc_Config_Dataset):\n",
- " \"\"\"Adds `.caching` to the `Quantum circuit dataset` class.\"\"\"\n",
+ "class CachedOpenCLIPDataset(ConfigDataset):\n",
+ " \"\"\"\n",
+ " Adds `.caching` to the `ConfigDataset` class.\n",
" \n",
- " def x_y_preprocess(self, balance_max, max_samples=None):\n",
- " x_proc, y_proc, *z = super().x_y_preprocess(balance_max=balance_max, max_samples=max_samples) \n",
+ " Cached dataset that caches the label `y` prompts using the CLIP `text_encoder`. This speeds up training significantly.\n",
+ " \"\"\"\n",
+ "\n",
+ " #-----------------------------------\n",
+ " \n",
+ " def x_y_preprocess(self, balance_max, shuffle=False, max_samples=None, make_unique=True):\n",
+ " x_proc, y_proc, *z = super().x_y_preprocess(balance_max=balance_max, shuffle=shuffle, max_samples=max_samples, make_unique=make_unique) \n",
" y_proc = self.caching(y_proc)\n",
" return x_proc, y_proc, *z\n",
" \n",
@@ -77,74 +89,24 @@
" if y_on_cpu: y_tok = y_tok.cpu()\n",
" \n",
" \n",
- " #now for using cache we need the uniques and the corresponding indices of the uniques\n",
- " y_uniques, y_ptrs = torch.unique(torch.cat([self.text_encoder.empty_token.to(y_tok.device), y_tok]), dim=0, return_inverse=True)\n",
+ " # Now for using cache we need the uniques and the corresponding indices of the uniques\n",
+ " y_uniques, y_ptrs = torch.unique(torch.cat([self.text_encoder.empty_token.to(y_tok.device), y_tok], dim=0), dim=0, return_inverse=True)\n",
" \n",
" cached_empty_token_index = y_ptrs[0] #store what index the empty token has \n",
" y_ptrs = y_ptrs[1:] #remove the cat empty token\n",
" \n",
- " #use cache\n",
+ " # Use cache\n",
" print(\" - generate_cache\") \n",
" self.text_encoder.generate_cache(tokens=y_uniques, cached_empty_token_index=cached_empty_token_index, y_on_cpu=y_on_cpu)\n",
" \n",
- " print(\"[INFO]: Generated cache\") \n",
- " return y_ptrs\n",
+ " print(f\"[INFO]: Generated cache, {y_ptrs.shape=}\") \n",
+ " return y_ptrs.clone()\n",
" \n",
" #-------------------------------------------\n",
" \n",
" def get_dataloaders(self, batch_size, text_encoder, p_valid=0.1, balance_max=None, max_samples=None):\n",
" self.text_encoder = text_encoder \n",
- " return super().get_dataloaders(batch_size, p_valid, balance_max, max_samples)\n",
- " \n",
- " #-------------------------------------------\n",
- " \n",
- " @staticmethod\n",
- " def from_config_file(config_path, device: torch.device, save_path: str=None):\n",
- " config = load_config(config_path)\n",
- " config[\"target\"] = class_to_str(Cached_OpenClip_Dataset) \n",
- " return Config_Dataset.from_config(config, device, save_path)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "9d0e389a-3567-4974-ae7a-a02b15760fb5",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| hide\n",
- "\n",
- "# #| export\n",
- "# class Cached_QcClip_Dataset(Qc_Config_Dataset):\n",
- "# def x_y_preprocess(self, balance_max, use_new_tensor):\n",
- "# x_proc, y_proc = super().x_y_preprocess(balance_max, use_new_tensor)\n",
- " \n",
- "# #-------------------------------------------\n",
- "# print(\"[INFO]: Generate cache\") \n",
- " \n",
- "# #now for using cache we need the uniques nad the corresponding indices of the uniques\n",
- "# empty_token = self.text_encoder.empty_token.expand(y_proc.shape)[:1] # [1, ...]\n",
- " \n",
- "# y_uniques, y_ptrs = torch.unique(torch.cat([empty_token, y_proc]), dim=0, return_inverse=True)\n",
- " \n",
- "# cached_empty_token_index = y_ptrs[0] #store what index the empty token has \n",
- "# y_ptrs = y_ptrs[1:] #remove the cat empty token\n",
- "\n",
- "# #use cache\n",
- "# self.text_encoder.generate_cache(tokens=y_uniques, cached_empty_token_index=cached_empty_token_index)\n",
- " \n",
- "# print(\"[INFO]: Generated cache\") \n",
- "# return x_proc, y_ptrs\n",
- " \n",
- "# def get_dataloaders(self, batch_size, text_encoder, p_valid=0.1, balance_max=None, use_new_tensor=True):\n",
- "# self.text_encoder = text_encoder \n",
- "# return super().get_dataloaders(batch_size, p_valid, balance_max, use_new_tensor)\n",
- " \n",
- "# @staticmethod\n",
- "# def from_config_file(config_path, device: torch.device, save_path: str=None):\n",
- "# config = load_config(config_path)\n",
- "# config[\"target\"] = class_to_str(Cached_QcClip_Dataset) \n",
- "# return Config_Dataset.from_config(config, device, save_path)"
+ " return super().get_dataloaders(batch_size, p_valid, balance_max, max_samples) "
]
},
{
diff --git a/src/dataset/circuits_dataset.ipynb b/src/dataset/circuits_dataset.ipynb
new file mode 100644
index 0000000..8c87bbc
--- /dev/null
+++ b/src/dataset/circuits_dataset.ipynb
@@ -0,0 +1,438 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "a8980c24-d62e-462b-ba89-3195cfdcc374",
+ "metadata": {},
+ "source": [
+ "# Quantum circuit dataset\n",
+ "\n",
+ "> Dataset for quantum circuits."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a8832bdd-f61c-44e1-8619-a9cb352ba768",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| default_exp dataset.circuits_dataset"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "06272f6f-b4e3-4504-a90a-feebbf6ad821",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "from genQC.imports import *\n",
+ "from genQC.dataset.cached_dataset import CachedOpenCLIPDataset, CachedOpenCLIPDatasetConfig\n",
+ "from genQC.dataset.mixed_cached_dataset import MixedCachedOpenCLIPDataset, MixedCachedOpenCLIPDatasetConfig\n",
+ "from genQC.utils.config_loader import *\n",
+ "from genQC.dataset.config_dataset import ConfigDataset\n",
+ "from genQC.dataset.dataset_helper import shuffle_tensor_dataset\n",
+ "from genQC.utils.misc_utils import MemoryCleaner"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "93281557-359a-4b89-906d-36ebfc72bf98",
+ "metadata": {},
+ "source": [
+ "## Simple Dataset"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "016fc327-f986-4d69-b5f0-1b39466fb528",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "@dataclass\n",
+ "class CircuitsConfigDatasetConfig(CachedOpenCLIPDatasetConfig):\n",
+ " optimized: bool\n",
+ " random_samples: int \n",
+ " num_of_qubits: int \n",
+ " min_gates: int \n",
+ " max_gates: int \n",
+ " max_params: int\n",
+ " gate_pool: list[str]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "36032308-bd0e-4409-9db0-9d89fc258e5a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "class CircuitsConfigDataset(CachedOpenCLIPDataset):\n",
+ " \"\"\"Dataset for quantum circuits, access `gate_pool` directly and all other paras with `.params_config`\"\"\"\n",
+ " \n",
+ " req_params = [f.name for f in dataclasses.fields(CircuitsConfigDatasetConfig)]\n",
+ "\n",
+ " #-----------------------------------\n",
+ " def __init__(self, device: torch.device=torch.device(\"cpu\"), **parameters) -> None:\n",
+ " super().__init__(device, **parameters) \n",
+ "\n",
+ " \n",
+ " if isinstance(list(parameters[\"gate_pool\"])[0], str):\n",
+ " self.gate_pool = list(parameters[\"gate_pool\"])\n",
+ " \n",
+ " else:\n",
+ " try:\n",
+ " self.gate_pool = [get_obj_from_str(node) for node in parameters[\"gate_pool\"]] \n",
+ " except Exception as er:\n",
+ " print(f\"[WARNING]: error => {er}\")\n",
+ " print(f\"[WARNING]: gate_pool is passed as str\")\n",
+ " self.gate_pool = [str(node) for node in parameters[\"gate_pool\"]] \n",
+ " \n",
+ " @property\n",
+ " def params_config(self):\n",
+ " params_config = super().params_config \n",
+ " \n",
+ " if type(self) == CircuitsConfigDataset:\n",
+ " params_config = CircuitsConfigDatasetConfig(**params_config)\n",
+ " return params_config "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e6322ed9-c703-41df-88a3-6b163c051af1",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'target': '__main__.CircuitsConfigDataset',\n",
+ " 'device': 'cpu',\n",
+ " 'comment': '',\n",
+ " 'save_path': None,\n",
+ " 'save_datetime': '06/01/2025 11:31:35',\n",
+ " 'save_type': 'safetensors',\n",
+ " 'params': CircuitsConfigDatasetConfig(store_dict={'x': 'tensor', 'y': 'tensor_list'}, dataset_to_gpu=None, optimized=None, random_samples=None, num_of_qubits=None, min_gates=None, max_gates=None, max_params=None, gate_pool=['qiskit.circuit.library.standard_gates.h.HGate', 'qiskit.circuit.library.standard_gates.x.CXGate'])}"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "init = {k:None for k in CircuitsConfigDataset.req_params}\n",
+ "init[\"gate_pool\"] = [\"qiskit.circuit.library.standard_gates.h.HGate\",\n",
+ " \"qiskit.circuit.library.standard_gates.x.CXGate\"]\n",
+ "init[\"store_dict\"] = {\"x\":\"tensor\", \"y\":\"tensor_list\"}\n",
+ "\n",
+ "a = CircuitsConfigDataset(**init)\n",
+ "a.get_config()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d85fedcd-5a95-4466-956f-055d887fe773",
+ "metadata": {},
+ "source": [
+ "## Mixed Dataset"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "75c9a200-f9eb-42f9-b3c3-e074e377737a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "@dataclass\n",
+ "class MixedCircuitsConfigDatasetConfig(CircuitsConfigDatasetConfig, MixedCachedOpenCLIPDatasetConfig):\n",
+ " pass"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e804a8d7-dcf3-40e4-83a5-cd98207c8dea",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "class MixedCircuitsConfigDataset(CircuitsConfigDataset, MixedCachedOpenCLIPDataset):\n",
+ " \"\"\"\n",
+ " Dataset that uses multiple cached dataset and combines them with padding, either i) Bucket or ii) Max.\n",
+ " Also provides a corresponding `collate_fn` for training.\n",
+ " \"\"\"\n",
+ "\n",
+ " req_params = [f.name for f in dataclasses.fields(MixedCircuitsConfigDatasetConfig)]\n",
+ "\n",
+ " #-----------------------------------\n",
+ " \n",
+ " @property\n",
+ " def params_config(self):\n",
+ " params_config = super().params_config \n",
+ " if type(self) == MixedCircuitsConfigDataset:\n",
+ " params_config = MixedCircuitsConfigDatasetConfig(**params_config)\n",
+ " return params_config \n",
+ "\n",
+ " #-----------------------------------\n",
+ "\n",
+ " def _get_cut_sizes(self, z):\n",
+ " z_0 = torch.max(z[:, 0]) # space\n",
+ " z_1 = torch.max(z[:, 1]) # time\n",
+ " z_1 = (torch.ceil(z_1 / self.model_scale_factor) * self.model_scale_factor).to(torch.int32)\n",
+ " return z_0, z_1\n",
+ " \n",
+ " def _cut(self, x, y, z): \n",
+ " z_0, z_1 = self._get_cut_sizes(z)\n",
+ " \n",
+ " x = x[:, :z_0, :z_1] # cut down to max [b, bits, time] of batch\n",
+ " return x, y\n",
+ " \n",
+ " def _cut_compilation_params(self, x, y, p, U, z): \n",
+ " z_0, z_1 = self._get_cut_sizes(z)\n",
+ " bit_exp = 2**z_0\n",
+ " \n",
+ " x = x[:, :z_0, :z_1] # cut down to max [b, bits, time] of batch\n",
+ " p = p[:, :, :z_1] # cut down to max [b, nP , time] of batch \n",
+ " U = U[:, :, :bit_exp, :bit_exp] # [b, Re/Im, 2^n, 2^n]\n",
+ " return x, y, p, U\n",
+ " \n",
+ " #-----------------------------------\n",
+ " # BUCKET PADDING, all x,y are already passed as batch\n",
+ " \n",
+ " def cut_padding_Bucket_collate_fn(self, b): \n",
+ " \"\"\"this function is called for training for every batch, order in b is store dict\"\"\" \n",
+ "\n",
+ " x, y, z = b[0]\n",
+ " x, y = self._cut(x, y, z)\n",
+ " return x, y \n",
+ "\n",
+ " \n",
+ " def cut_padding_Bucket_collate_fn_compilation(self, b): \n",
+ " \"\"\"this function is called for training for every batch\"\"\" \n",
+ " raise NotImplementedError()\n",
+ "\n",
+ "\n",
+ " def cut_padding_Bucket_collate_fn_compilation_params(self, b): \n",
+ " \"\"\"this function is called for training for every batch, order in b is store dict\"\"\" \n",
+ " \n",
+ " b = b[0] # {'x': 'tensor', 'y': 'numpy', 'params': 'tensor', 'U': 'tensor', 'z': 'tensor'}\n",
+ " \n",
+ " x = b[0]\n",
+ " y = b[1] \n",
+ " p = b[2]\n",
+ " U = b[3]\n",
+ " z = b[4]\n",
+ " \n",
+ " #---------------\n",
+ " \n",
+ " x, y, p, U = self._cut_compilation_params(x, y, p, U, z)\n",
+ " \n",
+ " return x, y, p, U\n",
+ " \n",
+ " #-----------------------------------\n",
+ " # MAX PADDING, x are passes as sampled list (batch), std collate them\n",
+ " \n",
+ " def cut_padding_collate_fn(self, b): \n",
+ " \"\"\"this function is called for training for every batch\"\"\" \n",
+ " x, y, z = torch.utils.data.default_collate(b)\n",
+ " x, y = self._cut(x, y, z)\n",
+ " return x, y \n",
+ "\n",
+ " def cut_padding_collate_fn_compilation(self, b):\n",
+ " \"\"\"this function is called for training for every batch\"\"\" \n",
+ " raise NotImplementedError()\n",
+ " \n",
+ " def cut_padding_collate_fn_compilation_params(self, b):\n",
+ " \"\"\"this function is called for training for every batch, order in b is store dict\"\"\" \n",
+ " # {'x': 'tensor', 'y': 'numpy', 'params': 'tensor', 'U': 'tensor', 'z': 'tensor'}\n",
+ " x, y, p, U, z = torch.utils.data.default_collate(b)\n",
+ " x, y, p, U = self._cut_compilation_params(x, y, p, U, z) \n",
+ " return x, y, p, U \n",
+ " \n",
+ " #-----------------------------------\n",
+ " \n",
+ " @staticmethod\n",
+ " def _preprocess_dataset(dataset, device, balance_max, max_samples, i, shuffle, make_unique, pad_constant, \n",
+ " model_scale_factor, parameters, max_gates, max_qubits):\n",
+ "\n",
+ " dataset = dataset.to(device)\n",
+ "\n",
+ " existing_z_type = dataset.store_dict.pop(\"z\", None) # remove z, as it would mess up `ConfigDataset.x_y_preprocess`, it would be put in `*c`.\n",
+ " if exists(existing_z_type):\n",
+ " assert existing_z_type == \"tensor\"\n",
+ " z = dataset.z\n",
+ " else:\n",
+ " z = None\n",
+ " \n",
+ " x, y, *c = ConfigDataset.x_y_preprocess(dataset, balance_max=balance_max, max_samples=max_samples[i], shuffle=shuffle, make_unique=make_unique) \n",
+ " x = x.to(device) # [b, s, t] \n",
+ " \n",
+ " print(f\" - dataset size after balancing {x.shape[0]}\")\n",
+ "\n",
+ " #-------\n",
+ " # store original size\n",
+ " if not_exists(z):\n",
+ " z = torch.zeros((x.shape[0], 2), device=device, dtype=torch.int32)\n",
+ " z[:, 0] = max(dataset.params_config.num_of_qubits, 1)\n",
+ " \n",
+ " red_x = torch.sum(x.abs(), dim=1) # [b, t] .. collaps the zeros to get circuit length\n",
+ " z[:, 1] = torch.count_nonzero(red_x, dim=1) # [b] \n",
+ " z[z[:, 1]==0, 1] = 1 \n",
+ "\n",
+ " # Create masks for space and time padding\n",
+ " space_mask = torch.arange(x.shape[1], device=x.device).unsqueeze(0) >= z[:, 0].unsqueeze(1)\n",
+ " time_mask = torch.arange(x.shape[2], device=x.device).unsqueeze(0) >= z[:, 1].unsqueeze(1)\n",
+ "\n",
+ " # Apply masks to pad_constant to handle both dimensions\n",
+ " x = torch.where(space_mask.unsqueeze(2), pad_constant, x)\n",
+ " x = torch.where( time_mask.unsqueeze(1), pad_constant, x)\n",
+ " \n",
+ " z[:, 1] = (torch.ceil(z[:, 1] / model_scale_factor) * model_scale_factor).to(torch.int32) #for cut needs multiple\n",
+ "\n",
+ " #-------\n",
+ " \n",
+ " # now pad x, padding is defined from last dim forward! \n",
+ " pad = (0, max_gates-dataset.params_config.max_gates, 0, max_qubits-dataset.params_config.num_of_qubits) \n",
+ " x = F.pad(x, pad, \"constant\", pad_constant)\n",
+ " \n",
+ " #-------\n",
+ "\n",
+ " c = MixedCachedOpenCLIPDataset._add_missing_conditions(parameters, dataset, c, x.shape[0], \"cpu\")\n",
+ "\n",
+ " dataset = dataset.to(\"cpu\") #helps with gpu mem overflowing\n",
+ " del dataset\n",
+ " \n",
+ " return x.cpu(), y, z.cpu(), *[ic.cpu() for ic in c]\n",
+ " \n",
+ " @staticmethod\n",
+ " def from_datasets(datasets: list[CircuitsConfigDataset], balance_maxes: list, pad_constant, device: torch.device=torch.device(\"cpu\"), bucket_batch_size=None, \n",
+ " max_samples=None, shuffle=True, make_unique=True, test_split=0.05, pad_with_memmap=False, **parameters):\n",
+ " if pad_constant == 0:\n",
+ " print(\"[WARNING]: >pad_constant == 0<; This could be an error!\")\n",
+ " \n",
+ " model_scale_factor = parameters[\"model_scale_factor\"]\n",
+ " \n",
+ " max_qubits = max(dataset.params_config.num_of_qubits for dataset in datasets)\n",
+ " max_gates = max(dataset.params_config.max_gates for dataset in datasets)\n",
+ " max_gates = int(np.ceil(max_gates /model_scale_factor) * model_scale_factor)\n",
+ " max_params = max(dataset.params_config.max_params for dataset in datasets)\n",
+ " \n",
+ " parameters[\"num_of_qubits\"] = max_qubits\n",
+ " parameters[\"max_gates\"] = max_gates\n",
+ " parameters[\"max_params\"] = max_params\n",
+ " parameters[\"random_samples\"] = sum([dataset.params_config.random_samples for dataset in datasets])\n",
+ " parameters[\"min_gates\"] = min([dataset.params_config.min_gates for dataset in datasets])\n",
+ " parameters[\"comment\"] = f\"Generated with 'from_datasets' with {len(datasets)} datasets. Qubits: {[dataset.params_config.num_of_qubits for dataset in datasets]}.\"\n",
+ " parameters[\"pad_constant\"] = pad_constant\n",
+ " parameters[\"bucket_batch_size\"] = bucket_batch_size\n",
+ " \n",
+ " parameters[\"store_dict\"] = {}\n",
+ " for dataset in datasets:\n",
+ " parameters[\"store_dict\"] |= dataset.params_config.store_dict #needs python 3.9 for union of dict \n",
+ " parameters[\"store_dict\"][\"z\"] = \"tensor\" #add special item\n",
+ "\n",
+ " #-----------------\n",
+ " \n",
+ " xs, ys, zs, cs = MixedCircuitsConfigDataset._preprocess_datasets(datasets, device, balance_maxes, max_samples, shuffle, make_unique, pad_constant, \n",
+ " model_scale_factor, parameters, max_gates=max_gates, max_qubits=max_qubits) \n",
+ " #-----------------\n",
+ "\n",
+ " has_U = \"U\" in parameters[\"store_dict\"]\n",
+ " has_p = \"params\" in parameters[\"store_dict\"]\n",
+ " \n",
+ " if bucket_batch_size > 0:\n",
+ " collate_fn_name = MixedCircuitsConfigDataset.cut_padding_Bucket_collate_fn.__name__\n",
+ " if has_U: \n",
+ " collate_fn_name = MixedCircuitsConfigDataset.cut_padding_Bucket_collate_fn_compilation.__name__\n",
+ " if has_p: \n",
+ " collate_fn_name = MixedCircuitsConfigDataset.cut_padding_Bucket_collate_fn_compilation_params.__name__\n",
+ " \n",
+ " else:\n",
+ " collate_fn_name = MixedCircuitsConfigDataset.cut_padding_collate_fn.__name__ \n",
+ " if has_U: \n",
+ " collate_fn_name = MixedCircuitsConfigDataset.cut_padding_collate_fn_compilation.__name__\n",
+ " if has_p: \n",
+ " collate_fn_name = MixedCircuitsConfigDataset.cut_padding_collate_fn_compilation_params.__name__\n",
+ "\n",
+ " parameters[\"collate_fn\"] = collate_fn_name\n",
+ " \n",
+ " #-----------------\n",
+ " if bucket_batch_size > 0:\n",
+ " xs, ys, zs, cs = MixedCachedOpenCLIPDataset._reorder_to_buckets(parameters, bucket_batch_size, xs, ys, zs, cs)\n",
+ " \n",
+ " x = torch.cat(xs)\n",
+ " y = ys # torch.cat(ys) is wrong, y is list of numpy or str!! not a tensor\n",
+ " \n",
+ " if isinstance(y, list): \n",
+ " match parameters[\"store_dict\"][\"y\"]:\n",
+ " case \"numpy\": y = np.concatenate(y, axis=0)\n",
+ " case \"tensor\": y = torch.cat(y, dim=0)\n",
+ " case _: raise NotImplementedError()\n",
+ " \n",
+ " z = torch.cat(zs)\n",
+ " c = cs\n",
+ " \n",
+ " #-----------------\n",
+ "\n",
+ " params_pad = (max_params, max_gates)\n",
+ " unitary_pad = 2**max_qubits\n",
+ " \n",
+ " ci_list, ci_k_list, memmap_cleans = MixedCachedOpenCLIPDataset._pad_conditions(parameters, bucket_batch_size, c, unitary_pad=unitary_pad, params_pad=params_pad, pad_with_memmap=pad_with_memmap)\n",
+ " \n",
+ " #----------------- \n",
+ "\n",
+ " mixed_CircuitsConfigDataset, mixed_CircuitsConfigDataset_test = \\\n",
+ " MixedCircuitsConfigDataset._create_train_valid_datasets(device, parameters, test_split, x, y, z, ci_list, ci_k_list, shuffle=shuffle)\n",
+ "\n",
+ " if pad_with_memmap:\n",
+ " mixed_CircuitsConfigDataset.memmap_cleans = memmap_cleans\n",
+ " mixed_CircuitsConfigDataset_test.memmap_cleans = memmap_cleans\n",
+ " \n",
+ " return mixed_CircuitsConfigDataset, mixed_CircuitsConfigDataset_test"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f41f26a8-ac40-4e91-8c0e-1ef07a0fd4f4",
+ "metadata": {},
+ "source": [
+ "# Export -"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a0474216-8e0c-4ba7-9a37-571ac7d8e82c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| hide\n",
+ "import nbdev; nbdev.nbdev_export()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "python3",
+ "language": "python",
+ "name": "python3"
+ },
+ "widgets": {
+ "application/vnd.jupyter.widget-state+json": {
+ "state": {},
+ "version_major": 2,
+ "version_minor": 0
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/src/dataset/config_dataset.ipynb b/src/dataset/config_dataset.ipynb
index 5441fe6..c226f2b 100644
--- a/src/dataset/config_dataset.ipynb
+++ b/src/dataset/config_dataset.ipynb
@@ -5,7 +5,9 @@
"id": "a8980c24-d62e-462b-ba89-3195cfdcc374",
"metadata": {},
"source": [
- "# Config dataset"
+ "# Config dataset\n",
+ "\n",
+ "> Base class for managing, loading and saving."
]
},
{
@@ -27,7 +29,8 @@
"source": [
"#| export\n",
"from genQC.imports import *\n",
- "from genQC.config_loader import *\n",
+ "from genQC.utils.config_loader import *\n",
+ "from genQC.dataset.dataset_helper import *\n",
"\n",
"from huggingface_hub import snapshot_download"
]
@@ -41,9 +44,10 @@
"source": [
"#| export\n",
"@dataclass\n",
- "class Config_Dataset_config:\n",
+ "class ConfigDatasetConfig:\n",
" \"\"\"Config `dataclass` used for storage.\"\"\"\n",
- " store_dict: dict "
+ " store_dict: dict \n",
+ " dataset_to_gpu: bool"
]
},
{
@@ -54,13 +58,16 @@
"outputs": [],
"source": [
"#| export\n",
- "class Config_Dataset(): \n",
+ "class ConfigDataset(): \n",
" \"\"\"Base class for datasets, manages loading and saving.\"\"\"\n",
" \n",
- " req_params = [f.name for f in dataclasses.fields(Config_Dataset_config)]\n",
- " comment = \"\"\n",
+ " req_params = [f.name for f in dataclasses.fields(ConfigDatasetConfig)]\n",
+ " comment = \"\"\n",
+ " add_balance_fn = None\n",
" \n",
- " def __init__(self, device: torch.device=torch.device(\"cpu\"), **parameters):\n",
+ " def __init__(self, device: torch.device=torch.device(\"cpu\"), save_type=None, **parameters) -> None:\n",
+ " self.save_type = default(save_type, \"safetensors\")\n",
+ " \n",
" req_params = self.req_params \n",
" for p in req_params:\n",
" if p not in parameters: raise RuntimeError(f\"Missing parameter `{p}` in argument `**parameters: dict`\") \n",
@@ -92,6 +99,145 @@
" setattr(self, str(k), x)\n",
" \n",
" return self\n",
+ "\n",
+ " def memory_summary(self) -> None:\n",
+ " print(\"##################### Dataset memory summary #####################\")\n",
+ " print(\"Name || Type || Memory || Device || Shape\")\n",
+ " print(\"---------------------------------------------------------------\")\n",
+ " \n",
+ " total_mem = 0.0\n",
+ " byte_to_giga = 1 / (1024**3)\n",
+ " \n",
+ " for k,v in self.store_dict.items(): \n",
+ " mem = 0.0\n",
+ " dev = \"None\"\n",
+ " shape = \"None\"\n",
+ " dtype = \"None\"\n",
+ " \n",
+ " x = getattr(self, str(k))\n",
+ " \n",
+ " if v == \"tensor\":\n",
+ " mem += float(x.dtype.itemsize) * np.prod([s for s in x.shape], dtype=np.double) * byte_to_giga\n",
+ " dev = x.device\n",
+ " shape = x.shape\n",
+ " dtype = x.dtype\n",
+ " \n",
+ " elif v == \"tensor_list\": \n",
+ " dev = []\n",
+ " for x_i in x:\n",
+ " mem += float(x_i.dtype.itemsize) * np.prod([s for s in x_i.shape], dtype=np.double) * byte_to_giga\n",
+ " dev.append(x_i.device)\n",
+ " shape = (len(x), x[0].shape)\n",
+ " dtype = x[0].dtype\n",
+ " \n",
+ " elif v == \"list\": \n",
+ " shape = (len(x))\n",
+ " dtype = \"python\"\n",
+ " \n",
+ " elif v == \"numpy\": \n",
+ " shape = x.shape\n",
+ " dtype = x.dtype\n",
+ "\n",
+ " \n",
+ " print(f\" - [{str(k):>8}] ({str(dtype):>15} {str(v):>8}): {mem:3.4f} GB ({str(dev):6}) | {shape}\")\n",
+ " total_mem += mem\n",
+ " \n",
+ " print(\"--------------------------------------\")\n",
+ " print(f\" Total memory used: {total_mem:3.4f} GB \")\n",
+ " print(\"---------------------------------------------------------------\")\n",
+ "\n",
+ " #----------------------------\n",
+ " \n",
+ " def x_y_preprocess(self, balance_max=None, shuffle=False, max_samples=None, make_unique=True):\n",
+ " z_proc = []\n",
+ " for k,v in self.store_dict.items(): \n",
+ " if k != \"x\" and k != \"y\":\n",
+ " z_proc.append(getattr(self, k))\n",
+ " \n",
+ " x_proc, y_proc = self.x, self.y\n",
+ " \n",
+ " #---------------------\n",
+ " if shuffle:\n",
+ " x_proc, y_proc, *z_proc = shuffle_tensor_dataset(x_proc, y_proc, *z_proc)\n",
+ " \n",
+ " if exists(max_samples):\n",
+ " x_proc = x_proc[:max_samples]\n",
+ " y_proc = y_proc[:max_samples]\n",
+ " z_proc = (iz[:max_samples] for iz in z_proc) \n",
+ " \n",
+ " #---------------------\n",
+ " t = self.store_dict[\"y\"]\n",
+ " if exists(balance_max): \n",
+ " if t == \"tensor\" or t == \"numpy\": x_proc, y_proc, *z_proc = balance_tensor_dataset(x_proc, y_proc, *z_proc, make_unique=make_unique, shuffle_lables=shuffle, \n",
+ " samples=balance_max, add_balance_fn=self.add_balance_fn, njobs=1) \n",
+ " else: print(f\"[WARNING]: Unsupported y type: `{t}`. Not balancing dataset!\")\n",
+ " else: print(f\"[INFO]: Not balancing dataset! {balance_max=}\")\n",
+ " \n",
+ " #---------------------\n",
+ " if shuffle:\n",
+ " x_proc, y_proc, *z_proc = shuffle_tensor_dataset(x_proc, y_proc, *z_proc)\n",
+ " \n",
+ " return x_proc, y_proc, *z_proc\n",
+ " \n",
+ " def valid_split(self, x, y, *z, p_valid=0.1, y_type=None, split_sequential=False):\n",
+ " \"\"\"\n",
+ " split_sequential ... if true split data ordered (valid-train order), else split randomly (the same as shuffle and then seq. split)\n",
+ " \"\"\"\n",
+ " \n",
+ " if split_sequential: ind = torch.arange(x.shape[0])\n",
+ " else: ind = torch.randperm(x.shape[0]) \n",
+ " \n",
+ " splits = max(int(x.shape[0] * p_valid), 1) \n",
+ " ind, ind_valid = ind[splits:], ind[:splits]\n",
+ "\n",
+ " #### Note: advanced indexing always creates copy not view. So we can skip the .clone()\n",
+ " x, x_valid = x[ind], x[ind_valid]\n",
+ " \n",
+ " t = y_type if exists(y_type) else self.store_dict[\"y\"]\n",
+ " if t == \"tensor\" : y, y_valid = y[ind], y[ind_valid] \n",
+ " elif t == \"numpy\": y, y_valid = y[ind], y[ind_valid]\n",
+ " \n",
+ " z = list(z)\n",
+ " z_valid = [None] * len(z)\n",
+ " for i, iz in enumerate(z):\n",
+ " # assert tensors for now\n",
+ " z[i], z_valid[i] = iz[ind], iz[ind_valid]\n",
+ " \n",
+ " z, z_valid = tuple(z), tuple(z_valid)\n",
+ " \n",
+ " return x, x_valid, y, y_valid, (z, z_valid)\n",
+ " \n",
+ " def get_dataloaders(self, batch_size, p_valid=0.1, balance_max=None, max_samples=None, y_on_cpu=False, shuffle=True):\n",
+ " #-------------------------\n",
+ " # valid split and to device\n",
+ " \n",
+ " x_proc, y_proc, *z_proc = self.x_y_preprocess(balance_max=balance_max, max_samples=max_samples, shuffle=shuffle) \n",
+ " x, x_valid, y, y_valid, (z, z_valid) = self.valid_split(x_proc, y_proc, *z_proc, p_valid=p_valid)\n",
+ "\n",
+ "\n",
+ " if self.params_config.dataset_to_gpu:\n",
+ " x, x_valid = x.to(\"cuda\"), x_valid.to(\"cuda\")\n",
+ " z, z_valid = list(iz.to(\"cuda\") for iz in z), list(iz_valid.to(\"cuda\") for iz_valid in z_valid)\n",
+ "\n",
+ " if not y_on_cpu:\n",
+ " y, y_valid = y.to(\"cuda\"), y_valid.to(\"cuda\")\n",
+ "\n",
+ " #-------------------------\n",
+ " # create dataloaders\n",
+ " \n",
+ " ds = TensorDataset(x, y, *z)\n",
+ " ds_valid = TensorDataset(x_valid, y_valid, *z_valid)\n",
+ " \n",
+ " if self.params_config.dataset_to_gpu: \n",
+ " train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True)\n",
+ " valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True)\n",
+ "\n",
+ " else: \n",
+ " train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=12)\n",
+ " valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=12)\n",
+ "\n",
+ " self.dataloaders = DataLoaders(train_loader, valid_loader) \n",
+ " return self.dataloaders\n",
" \n",
" #----------------------------\n",
" \n",
@@ -99,16 +245,22 @@
" def params_config(self):\n",
" params_config = {} \n",
" for p in self.req_params: params_config[p] = getattr(self, p)\n",
+ " \n",
+ " if type(self) == ConfigDataset:\n",
+ " params_config = ConfigDatasetConfig(**params_config)\n",
" return params_config \n",
- " \n",
+ "\n",
+ " #----------------------------\n",
+ " \n",
" def get_config(self, save_path=None, without_metadata=False):\n",
" if not without_metadata: \n",
" config = {}\n",
" config[\"target\"] = class_to_str(type(self))\n",
" config[\"device\"] = str(self.device)\n",
" config[\"comment\"] = self.comment\n",
- " config[\"save_path\"] = self.save_path if hasattr(self, \"save_path\") else save_path\n",
+ " config[\"save_path\"] = self.save_path if hasattr(self, \"save_path\") and not exists(save_path) else save_path\n",
" config[\"save_datetime\"] = datetime.now().strftime(\"%m/%d/%Y %H:%M:%S\")\n",
+ " config[\"save_type\"] = self.save_type\n",
" config[\"params\"] = self.params_config \n",
" else:\n",
" config = self.params_config \n",
@@ -117,22 +269,41 @@
" return config\n",
" \n",
" def save_dataset(self, config_path: str, save_path: str):\n",
+ " if exists(config_path): os.makedirs(config_path[:config_path.rfind(\"/\")] + \"/\", exist_ok=True)\n",
+ " if exists(save_path): os.makedirs(save_path[:save_path.rfind(\"/\")] + \"/\", exist_ok=True)\n",
+ " \n",
" config = self.get_config(save_path, without_metadata=False)\n",
" save_dict_yaml(config, config_path) \n",
" self.store_x_y(save_path) \n",
" \n",
" #----------------------------\n",
+ "\n",
+ " def check_save_type(self, save_path):\n",
+ " if exists(self.save_type) and exists(save_path):\n",
+ " if not save_path.endswith(f\".{self.save_type}\"):\n",
+ " save_path += f\".{self.save_type}\"\n",
+ " return save_path\n",
" \n",
" def store_x_y(self, path_str): \n",
" for k,v in self.store_dict.items(): \n",
" x = getattr(self, str(k))\n",
- " torch.save(x, path_str + f\"_{k}.pt\")\n",
- " \n",
- " def load_x_y(self, path_str):\n",
+ "\n",
+ " # torch.save(x, path_str + f\"_{k}.pt\")\n",
+ " store_tensor({\"0\": x}, self.check_save_type(path_str + f\"_{k}\"), type=v)\n",
+ " \n",
+ " def load_x_y(self, path_str, device: Optional[torch.device] = None, make_contiguous: bool = True):\n",
" self.save_path = path_str\n",
" \n",
- " for k,v in self.store_dict.items(): \n",
- " x = torch.load(path_str + f\"_{k}.pt\", weights_only=False)\n",
+ " for k,v in self.store_dict.items(): \n",
+ " # x = torch.load(path_str + f\"_{k}.pt\", map_location=device)\n",
+ " x = load_tensor(self.check_save_type(path_str + f\"_{k}\"), device, type=v)\n",
+ "\n",
+ " if isinstance(x, dict):\n",
+ " x = x[\"0\"]\n",
+ "\n",
+ " if v == \"tensor\" and make_contiguous:\n",
+ " x = x.contiguous() #load memmap into memory\n",
+ " \n",
" setattr(self, str(k), x)\n",
" \n",
" #----------------------------\n",
@@ -153,7 +324,7 @@
" if \"save_path\" in config: save_path = config[\"save_path\"]\n",
" else: print(\"[INFO]: Found no key `save_path` path in config and no `save_path` arg provided.\")\n",
" \n",
- " if exists(save_path): config_dataset.load_x_y(save_path)\n",
+ " if exists(save_path): config_dataset.load_x_y(save_path, device=device, make_contiguous=make_contiguous)\n",
" else: print(\"[INFO]: No save_path` provided. Nothing loaded.\")\n",
"\n",
" #--------------------------------\n",
@@ -170,7 +341,7 @@
" If this method is called with `ConfigDataset.from_config_file` we use the given `target`, else use the caller class.\n",
" \"\"\"\n",
" config = load_config(config_path)\n",
- " if cls is not Config_Dataset:\n",
+ " if cls is not ConfigDataset:\n",
" config[\"target\"] = class_to_str(cls) \n",
" return cls.from_config(config, device, save_path, make_contiguous)\n",
"\n",
@@ -178,7 +349,13 @@
" def from_huggingface(cls, repo_id: str, device: torch.device, **kwargs): \n",
" \"\"\"Load a dataset directly from Huggingface.\"\"\"\n",
" dataset_path = snapshot_download(repo_id=repo_id, repo_type=\"dataset\", allow_patterns=[\"*.pt\", \"*.yaml\", \"*.safetensors\"], **kwargs) \n",
- " dataset = cls.from_config_file(config_path=dataset_path+\"/config.yaml\", device=device, save_path=dataset_path+\"/dataset\") \n",
+ "\n",
+ " try:\n",
+ " name = repo_id.split(\"/\")[-1]\n",
+ " dataset = cls.from_config_file(config_path=dataset_path+f\"/{name}.yaml\", device=device, save_path=dataset_path+f\"/{name}\") \n",
+ " except Exception as e:\n",
+ " dataset = cls.from_config_file(config_path=dataset_path+\"/config.yaml\", device=device, save_path=dataset_path+\"/dataset\") \n",
+ " \n",
" return dataset "
]
},
@@ -207,6 +384,13 @@
"display_name": "python3",
"language": "python",
"name": "python3"
+ },
+ "widgets": {
+ "application/vnd.jupyter.widget-state+json": {
+ "state": {},
+ "version_major": 2,
+ "version_minor": 0
+ }
}
},
"nbformat": 4,
diff --git a/src/dataset/dataset_helper.ipynb b/src/dataset/dataset_helper.ipynb
index 6ea643c..bd6d3ce 100644
--- a/src/dataset/dataset_helper.ipynb
+++ b/src/dataset/dataset_helper.ipynb
@@ -5,15 +5,9 @@
"id": "a8980c24-d62e-462b-ba89-3195cfdcc374",
"metadata": {},
"source": [
- "# Dataset helper functions"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "3d455168-ceb0-4c95-a7d5-3307cf3fb0dd",
- "metadata": {},
- "source": [
- "Some comonly used functions for datasets."
+ "# Dataset helper functions\n",
+ "\n",
+ "> Some comonly used functions for datasets."
]
},
{
@@ -35,7 +29,8 @@
"source": [
"#| export\n",
"from genQC.imports import *\n",
- "from genQC.config_loader import *"
+ "from genQC.utils.config_loader import *\n",
+ "from genQC.utils.async_fn import run_parallel_jobs"
]
},
{
@@ -62,39 +57,7 @@
" comp = (dataset==x) \n",
" comp = torch.reshape(comp, [comp.shape[0], -1]) \n",
" comp = torch.all(comp, dim=1)\n",
- " \n",
- " num = comp.nonzero().squeeze().numel() \n",
- " return bool(num)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "9369f5e5-3545-49d3-b03a-543cf4620e1d",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| hide\n",
- "#| export\n",
- "def check_duplicates_in_dataset_python(xs, dataset):\n",
- " cnt = 0\n",
- " \n",
- " raise NotImplementedError(\"\")\n",
- " \n",
- " # f = lambda x: int(check_duplicate_in_dataset(x, dataset))\n",
- " # res = async_loop_consumer(f, xs)\n",
- " # cnt = sum(res)\n",
- " \n",
- " comp = []\n",
- " \n",
- " for i,x in enumerate(xs):\n",
- " if check_duplicate_in_dataset(x, dataset):\n",
- " #print(f\"[INFO] Duplicate in dataset at index={i}\")\n",
- " comp.append(i)\n",
- " cnt += 1 \n",
- " # print(f\"[INFO] Found {cnt}/{xs.shape[0]} duplicates in dataset of {dataset.shape[0]}.\")\n",
- " \n",
- " return cnt, comp"
+ " return comp.any().item() "
]
},
{
@@ -132,12 +95,6 @@
" comp = comp.nonzero()\n",
" num = comp.shape[0] \n",
" \n",
- " # except Exception as er:\n",
- " # print(\"[WARNING] check_duplicates_in_dataset:\", er)\n",
- " # print(\"We will use python instead.\")\n",
- " # raise NotImplementedError(\"\")\n",
- " # # cnt, comp = check_duplicates_in_dataset_python(xs, dataset)\n",
- " \n",
" if return_ind: return num, comp.squeeze() #comp is [i_xs, i_dataset] pairs\n",
" return num"
]
@@ -198,14 +155,35 @@
"outputs": [],
"source": [
"#| export\n",
- "def shuffle_tensor_dataset(x, y=None, *z):\n",
+ "def shuffle_tensor_dataset(x, y=None, *z, cpu_copy=True):\n",
" '''Assumes numpy or tensor objects with same length.'''\n",
" rand_indx = torch.randperm(x.shape[0])\n",
" \n",
" if exists(y):\n",
" assert x.shape[0] == y.shape[0] \n",
- " for iz in z: assert x.shape[0] == iz.shape[0] \n",
- " return x[rand_indx], y[rand_indx], *(iz[rand_indx] for iz in z)\n",
+ " for iz in z: assert x.shape[0] == iz.shape[0] \n",
+ "\n",
+ "\n",
+ " if cpu_copy:\n",
+ "\n",
+ " def _cpu_array_index(var): \n",
+ " if type(var) == np.ndarray:\n",
+ " var = var[rand_indx]\n",
+ " else:\n",
+ " device = var.device\n",
+ " var = var.to(\"cpu\")\n",
+ " var = var[rand_indx]\n",
+ " var[:] = var.to(device) \n",
+ " return var\n",
+ "\n",
+ " x = _cpu_array_index(x)\n",
+ " y = _cpu_array_index(y)\n",
+ " z = (_cpu_array_index(iz) for iz in z)\n",
+ " \n",
+ " return x, y, *z\n",
+ "\n",
+ " else:\n",
+ " return x[rand_indx], y[rand_indx], *(iz[rand_indx] for iz in z)\n",
" \n",
" return x[rand_indx]"
]
@@ -221,12 +199,14 @@
"def get_unique_elements_indices(tensor):\n",
" '''Returns indices of unique_elements in `tensor`.'''\n",
" tensor_unique, ptrs, cnt = torch.unique(tensor, dim=0, return_inverse=True, return_counts=True)\n",
- " _, ind_sorted = torch.sort(ptrs, stable=True) #e.g. gets the index that points to zero at pos [0]\n",
+ " _, ind_sorted = torch.sort(ptrs, dim=0, stable=True) #e.g. gets the index that points to zero at pos [0]\n",
" \n",
- " cum_sum = cnt.cumsum(0)\n",
- " cum_sum = torch.cat((torch.tensor([0], device=tensor.device), cum_sum[:-1]))\n",
- " \n",
- " return tensor_unique, ind_sorted[cum_sum]"
+ " cum_sum = cnt.cumsum(dim=0)\n",
+ " cum_sum = torch.cat([torch.tensor([0], device=tensor.device), cum_sum[:-1]], dim=0)\n",
+ "\n",
+ " idx = ind_sorted[cum_sum].cpu()\n",
+ " \n",
+ " return tensor[idx], idx"
]
},
{
@@ -258,47 +238,65 @@
"outputs": [],
"source": [
"#| export\n",
- "def balance_tensor_dataset(x, y, *z, samples: int=None, make_unique: bool=True, y_uniques=None, shuffle_lables: bool=True, add_balance_fn: callable=None):\n",
+ "def balance_tensor_dataset(x, y, *z, samples: int=None, make_unique: bool=True, y_uniques=None, shuffle_lables: bool=True, add_balance_fn: callable=None, njobs=1):\n",
" '''Assumes `x` is tensor and `y` is tensor or numpy.'''\n",
" \n",
" y_type = type(y)\n",
" assert y_type in [np.ndarray, torch.Tensor]\n",
+ "\n",
+ " print(f\" - balance_tensor_dataset, {njobs=}, number of samples={x.shape[0]}\")\n",
" \n",
" #------------------------------\n",
" \n",
" if make_unique:\n",
" x, y, *z = uniquify_tensor_dataset(x, y, *z)\n",
" assert x.shape[0] == y.shape[0]\n",
+ "\n",
+ " print(f\" - uniquify_tensor_dataset, number of samples now {x.shape[0]}\")\n",
" \n",
" #bcs unique sorts, we need to shuffle the dataset before picking the first 'samples' entries\n",
" x, y, *z = shuffle_tensor_dataset(x, y, *z) \n",
" \n",
" #------------------------------\n",
+ "\n",
+ " search_y = y_uniques if exists(y_uniques) else y\n",
" \n",
- " if y_type == np.ndarray: y_uniques_temp, y_uniques_cnt = np.unique(y, return_counts=True, axis=0)\n",
- " else: y_uniques_temp, y_uniques_cnt = torch.unique(y, return_counts=True, dim=0)\n",
+ " if y_type == np.ndarray: _, y_ptrs, y_uniques_cnt = np.unique(search_y, return_counts=True, return_inverse=True, axis=0)\n",
+ " else: _, y_ptrs, y_uniques_cnt = torch.unique(search_y, return_counts=True, return_inverse=True, dim=0)\n",
" \n",
- " if y_uniques is None: y_uniques = y_uniques_temp\n",
- " if samples is None: \n",
+ " if not exists(samples): \n",
" if y_type == np.ndarray: samples = np.min(y_uniques_cnt) # the actual balancing count\n",
" else: samples = torch.min(y_uniques_cnt)\n",
" \n",
+ " print(f\" - balancing\")\n",
+ " # ToDo: make parallel \n",
+ " \n",
" ind = list() \n",
- " for y_unique in y_uniques:\n",
+ " # for y_unique in tqdm(y_uniques, total=y_uniques.shape[0]): \n",
+ " for y_ptr_index in tqdm(range(y_uniques_cnt.shape[0]), total=y_uniques_cnt.shape[0]):\n",
" \n",
" if y_type == np.ndarray:\n",
- " comp = (y==y_unique)\n",
+ " comp = (y_ptrs==y_ptr_index)\n",
" indices = np.squeeze(np.nonzero(comp))\n",
" indices = indices if indices.ndim > 0 else indices[None]\n",
" \n",
- " else:\n",
- " comp = torch.all(y==y_unique, dim=1)\n",
- " indices = comp.nonzero().squeeze().cpu()\n",
+ " else: \n",
+ " comp = (y_ptrs==y_ptr_index)\n",
+ " \n",
+ " indices = comp.nonzero().squeeze() #.cpu()\n",
" indices = indices if indices.dim() > 0 else indices[None]\n",
- " \n",
+ "\n",
" #special add balncing, e.g., for circuit length\n",
" if add_balance_fn is not None: indices = add_balance_fn(indices, x, y, *z)\n",
- " \n",
+ "\n",
+ " if not y_type == np.ndarray: indices = indices.cpu()\n",
+ "\n",
+ " indices = shuffle_tensor_dataset(indices) \n",
+ "\n",
+ " #fixes bug: shuffle_tensor_dataset removes dim if numpy array only has 1 element! \n",
+ " if y_type == np.ndarray: indices = indices if indices.ndim > 0 else indices[None]\n",
+ " else: indices = indices if indices.dim() > 0 else indices[None]\n",
+ " \n",
" ind.append(indices[:samples]) #limit samples\n",
" \n",
" if y_type == np.ndarray: ind = np.concatenate(ind, axis=0)\n",
@@ -314,44 +312,6 @@
" return xb, yb, *zb"
]
},
- {
- "cell_type": "markdown",
- "id": "70824159-6cd4-49eb-a4e6-b6dc3ebdfebe",
- "metadata": {},
- "source": [
- "#| hide\n",
- "## Converters"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "7c7464a6-6a5f-4e4d-8f15-bc7bd8cc991e",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| hide\n",
- "#| export\n",
- "def map_old_tensor_to_new(x):\n",
- " raise DeprecationWarning(\"[WARNING] There really should be no more old tensors arround .... delete them\")\n",
- " print(\"[WARNING] There really should be no more old tensors arround .... delete them\")\n",
- " \n",
- " b, gc, bits, t = x.shape\n",
- " \n",
- " x = x.reshape((b, gc//3, 3, bits, t)) # [b, g-c, bits, t] -> [b, g, c, bits, t] \n",
- " x = torch.argmax(x, dim=2) # [b, g, c, bits, t]-> [b, g, bits, t] \n",
- " \n",
- " gate = torch.concat([torch.zeros_like(x[:,:1]), x], dim=1) # add zeros for empty token \n",
- " gate = torch.argmax(gate, dim=1)\n",
- " \n",
- " control_target = torch.sum(x, dim=1)\n",
- " mapped_tensor = torch.zeros_like(control_target)\n",
- " mapped_tensor[control_target==1] = -1\n",
- " mapped_tensor[control_target==2] = 1\n",
- " \n",
- " return gate * mapped_tensor # is now [b, space, time] with elements +-gate_number"
- ]
- },
{
"cell_type": "markdown",
"id": "f41f26a8-ac40-4e91-8c0e-1ef07a0fd4f4",
diff --git a/src/dataset/mixed_cached_dataset.ipynb b/src/dataset/mixed_cached_dataset.ipynb
new file mode 100644
index 0000000..79b5419
--- /dev/null
+++ b/src/dataset/mixed_cached_dataset.ipynb
@@ -0,0 +1,399 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "a8980c24-d62e-462b-ba89-3195cfdcc374",
+ "metadata": {},
+ "source": [
+ "# Mixed cached dataset\n",
+ "\n",
+ "> Dataset that combines and handles multiple cached datasets."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "21cae8fe-2a9d-4588-80f2-0a8c8def322b",
+ "metadata": {},
+ "source": [
+ "This is useful for multiple qubits. Here we also handle paddings."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a8832bdd-f61c-44e1-8619-a9cb352ba768",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| default_exp dataset.mixed_cached_dataset"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "06272f6f-b4e3-4504-a90a-feebbf6ad821",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "from genQC.imports import *\n",
+ "from genQC.dataset.cached_dataset import CachedOpenCLIPDataset, CachedOpenCLIPDatasetConfig, ConfigDataset\n",
+ "from genQC.dataset.dataset_helper import *\n",
+ "from genQC.utils.misc_utils import DataLoaders, MemoryCleaner\n",
+ "from tensordict import TensorDict"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "119077c9-999b-44f7-8099-79037503d7e7",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "@dataclass\n",
+ "class MixedCachedOpenCLIPDatasetConfig(CachedOpenCLIPDatasetConfig):\n",
+ " pad_constant: int\n",
+ " collate_fn: str\n",
+ " bucket_batch_size: int\n",
+ " model_scale_factor: int"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0037efb5-d3a9-46e4-94d1-3dd80297e934",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| export\n",
+ "class MixedCachedOpenCLIPDataset(CachedOpenCLIPDataset): \n",
+ " \"\"\"Dataset that uses multiple cached dataset and combines them with padding, either i) Bucket or ii) Max.\"\"\"\n",
+ " \n",
+ " req_params = [f.name for f in dataclasses.fields(MixedCachedOpenCLIPDatasetConfig)]\n",
+ "\n",
+ " #-----------------------------------\n",
+ " @property\n",
+ " def params_config(self):\n",
+ " params_config = super().params_config \n",
+ " if type(self) == MixedCachedOpenCLIPDataset:\n",
+ " params_config = MixedCachedOpenCLIPDatasetConfig(**params_config)\n",
+ " return params_config \n",
+ " \n",
+ " #-----------------------------------\n",
+ " # functions to combine multiple datasets together\n",
+ "\n",
+ " @classmethod\n",
+ " def _preprocess_datasets(dataset_cls, datasets, device, balance_maxes, max_samples, shuffle, \n",
+ " make_unique, pad_constant, model_scale_factor, parameters, **kwargs):\n",
+ " xs = []\n",
+ " ys = []\n",
+ " zs = []\n",
+ " cs = []\n",
+ " \n",
+ " if isinstance(max_samples, int):\n",
+ " max_samples = [max_samples] * len(datasets)\n",
+ " else:\n",
+ " assert isinstance(max_samples, (list, np.ndarray))\n",
+ "\n",
+ " if isinstance(balance_maxes, int):\n",
+ " balance_maxes = [balance_maxes] * len(datasets)\n",
+ " else:\n",
+ " assert isinstance(balance_maxes, (list, np.ndarray))\n",
+ " \n",
+ " for i, (dataset, balance_max) in tqdm(enumerate(zip(datasets, balance_maxes)), total=len(datasets)):\n",
+ "\n",
+ " x, y, z, *c = dataset_cls._preprocess_dataset(dataset, device, balance_max, max_samples, i, shuffle, make_unique, pad_constant, model_scale_factor, parameters, **kwargs)\n",
+ " MemoryCleaner.purge_mem()\n",
+ " \n",
+ " #combine datasets\n",
+ " xs.append(x.cpu()) \n",
+ " ys.append(y)\n",
+ " zs.append(z.cpu()) \n",
+ " cs.append([ic.cpu() for ic in c])\n",
+ "\n",
+ " del x\n",
+ " del y\n",
+ " del z\n",
+ " del c\n",
+ " \n",
+ " for k in datasets[i].store_dict.keys(): \n",
+ " setattr(datasets[i], str(k), None)\n",
+ " del dataset\n",
+ " \n",
+ " MemoryCleaner.purge_mem()\n",
+ "\n",
+ " return xs, ys, zs, cs\n",
+ " \n",
+ " @staticmethod\n",
+ " def _add_missing_conditions(parameters, dataset, c, batch_size, device):\n",
+ " # if c is missing something of the union we set it to a zero tensor, e.g. used for combining SRV with compilation\n",
+ " c_temp = []\n",
+ " c_temp_index = 0\n",
+ " \n",
+ " for k,v in parameters[\"store_dict\"].items(): \n",
+ " if k != \"x\" and k != \"y\" and k != \"z\": \n",
+ " if k not in dataset.params_config.store_dict:\n",
+ " empty_tensor = torch.zeros((1,), device=device)\n",
+ " \n",
+ " if k == \"U\": #scetchy hardcoded for compilation\n",
+ " empty_tensor = torch.zeros((batch_size, 2, 1, 1), device=device) # unitary is [b, Re/Im, 2^n, 2^n]\n",
+ " \n",
+ " c_temp.append(empty_tensor) \n",
+ " \n",
+ " else: # done to conserve the ordering of c args!!!\n",
+ " c_temp.append(c[c_temp_index])\n",
+ " c_temp_index += 1\n",
+ "\n",
+ " return c_temp\n",
+ "\n",
+ " @staticmethod\n",
+ " def _reorder_to_buckets(parameters, bucket_batch_size, xs, ys, zs, cs):\n",
+ " for i, (xi,yi,zi, ci) in enumerate(zip(xs, ys, zs, cs)): #cut rest of batch \n",
+ " b_mult = int(np.floor(xi.shape[0] / bucket_batch_size) * bucket_batch_size) \n",
+ " \n",
+ " xs[i] = xi[None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *xi.shape[1:])) \n",
+ " zs[i] = zi[None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *zi.shape[1:]))\n",
+ " \n",
+ " v = parameters[\"store_dict\"][\"y\"]\n",
+ " if v == \"tensor\" or v == \"numpy\": \n",
+ " ys[i] = yi[None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *yi.shape[1:])) \n",
+ " else: raise NotImplementedError(\"\")\n",
+ " \n",
+ " #----\n",
+ " #For U, etc\n",
+ " add_ind = 0\n",
+ " for k,v in parameters[\"store_dict\"].items(): \n",
+ " if k != \"x\" and k != \"y\" and k != \"z\": \n",
+ " if v == \"tensor\" or v == \"numpy\": \n",
+ " cs[i][add_ind] = ci[add_ind][None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *ci[add_ind].shape[1:])) \n",
+ " else: raise NotImplementedError(\"\") \n",
+ " add_ind += 1 \n",
+ "\n",
+ " return xs, ys, zs, cs\n",
+ "\n",
+ " @staticmethod\n",
+ " def _pad_conditions(parameters, bucket_batch_size, c, unitary_pad=None, params_pad=None, pad_with_memmap=False):\n",
+ " ci_list = []\n",
+ " ci_k_list = []\n",
+ "\n",
+ " memmap_cleans = [] #TensorDicts and paths we need to delete later\n",
+ " \n",
+ " def _alloc_mem(shape, k, c0_add_ind):\n",
+ " # allocating zeros is better memory wise than torch.cat(ci_s) and F.pad(ci, pad, \"constant\", 0)\n",
+ " mem = np.prod(shape) * c0_add_ind.element_size() / (1024*1024*1024)\n",
+ " print(f\"[INFO]: allocate memory for {k} {shape} on {c0_add_ind.device} approx. {mem:.3f} GB\")\n",
+ "\n",
+ " if pad_with_memmap:\n",
+ " prefix_path = f\"tmp_DELETE_pad_conditions_MixedCachedOpenCLIPDataset_{k}\"\n",
+ " print(f\"[INFO]: (MixedCachedOpenCLIPDataset._pad_conditions): {pad_with_memmap=} allocating TensorDict using memmap_like at {prefix_path}\")\n",
+ " \n",
+ " b, *_ = shape\n",
+ " tensor_dict = TensorDict({\"ci_s\": torch.empty(shape, dtype=c0_add_ind.dtype),\n",
+ " }, batch_size=[b])\n",
+ " tensor_dict = tensor_dict.memmap_like(prefix=prefix_path)\n",
+ " \n",
+ " ci_s = tensor_dict[\"ci_s\"]\n",
+ " memmap_cleans.append((tensor_dict, prefix_path))\n",
+ " else:\n",
+ " ci_s = torch.zeros(shape, device=c0_add_ind.device, dtype=c0_add_ind.dtype) \n",
+ " \n",
+ " return ci_s\n",
+ "\n",
+ " add_ind = 0\n",
+ " for k,v in parameters[\"store_dict\"].items(): \n",
+ " if k != \"x\" and k != \"y\" and k != \"z\": \n",
+ " \n",
+ " if v == \"tensor\" and k == \"U\": # hardcoded U padding !!\n",
+ " assert exists(unitary_pad) and isinstance(unitary_pad, int)\n",
+ " \n",
+ " n = sum([ci[add_ind].shape[0] for ci in c])\n",
+ " if bucket_batch_size > 0: shape = (n, bucket_batch_size, 2, unitary_pad, unitary_pad)\n",
+ " else: shape = (n, 2, unitary_pad, unitary_pad)\n",
+ " \n",
+ " ci_s = _alloc_mem(shape, k, c[0][add_ind]) \n",
+ "\n",
+ " #tensor product pad, else was zero pad\n",
+ " if 1:\n",
+ " run_i = 0\n",
+ " for i,ci in enumerate(c):\n",
+ " ci = ci[add_ind] \n",
+ "\n",
+ " assert ci.shape[-2]==ci.shape[-1]\n",
+ " U_side = ci.shape[-2]\n",
+ " for jj in range(unitary_pad//U_side): \n",
+ " ci_s[run_i:run_i+ci.shape[0], ..., U_side*jj:U_side*(jj+1), U_side*jj:U_side*(jj+1)] = ci.to(ci_s.device) \n",
+ " \n",
+ " run_i += ci.shape[0]\n",
+ " \n",
+ " ci_list.append(ci_s)\n",
+ " ci_k_list.append(k)\n",
+ " \n",
+ " add_ind += 1\n",
+ " continue\n",
+ " \n",
+ " elif v == \"tensor\" and k == \"params\": # hardcoded paramter padding !!\n",
+ " assert exists(params_pad) #and len(list(params_pad))==2\n",
+ " \n",
+ " n = sum(ci[add_ind].shape[0] for ci in c)\n",
+ " if bucket_batch_size > 0: shape = (n, bucket_batch_size, *params_pad)\n",
+ " else: shape = (n, *params_pad)\n",
+ " \n",
+ " ci_s = _alloc_mem(shape, k, c[0][add_ind]) \n",
+ " \n",
+ " elif v == \"numpy\": raise NotImplementedError(\"\") \n",
+ " else: raise NotImplementedError(\"\") \n",
+ " \n",
+ " \n",
+ " run_i = 0\n",
+ " for i,ci in enumerate(c):\n",
+ " ci = ci[add_ind] \n",
+ " ci_s[run_i:run_i+ci.shape[0], ..., :ci.shape[-2], :ci.shape[-1]] = ci \n",
+ " run_i += ci.shape[0]\n",
+ "\n",
+ " ci_list.append(ci_s)\n",
+ " ci_k_list.append(k)\n",
+ " \n",
+ " add_ind += 1\n",
+ "\n",
+ " return ci_list, ci_k_list, memmap_cleans\n",
+ " \n",
+ " @classmethod\n",
+ " def _create_train_valid_datasets(dataset_cls, device, parameters, test_split, x, y, z, ci_list, ci_k_list, shuffle: bool = True):\n",
+ " splits = max(int(x.shape[0] * test_split), 1)\n",
+ "\n",
+ " if shuffle:\n",
+ " x, y, z, *ci_list = shuffle_tensor_dataset(x, y, z, *ci_list)\n",
+ "\n",
+ " x, x_test = x[splits:], x[:splits]\n",
+ " y, y_test = y[splits:], y[:splits]\n",
+ " z, z_test = z[splits:], z[:splits]\n",
+ "\n",
+ " print(f\"Split: Train {x.shape[0]} - Test {x_test.shape[0]} \\n\")\n",
+ " \n",
+ " dataset = dataset_cls(device, **parameters) \n",
+ " dataset.x = x\n",
+ " dataset.y = y\n",
+ " dataset.z = z\n",
+ " \n",
+ " dataset_test = dataset_cls(device, **parameters) \n",
+ " dataset_test.x = x_test\n",
+ " dataset_test.y = y_test\n",
+ " dataset_test.z = z_test\n",
+ " \n",
+ " for ci, k in zip(ci_list, ci_k_list): \n",
+ " ci, ci_test = ci[splits:], ci[:splits]\n",
+ " \n",
+ " setattr(dataset , str(k), ci)\n",
+ " setattr(dataset_test, str(k), ci_test)\n",
+ " \n",
+ " return dataset, dataset_test\n",
+ " \n",
+ " #-----------------------------------\n",
+ " \n",
+ " def get_dataloaders(self, batch_size, text_encoder, p_valid=0.1, y_on_cpu=False, return_tensor_datasets=False, shuffle=True, shuffle_cpu_copy=True, caching=True):\n",
+ " #-------------------------\n",
+ " # caching\n",
+ " \n",
+ " self.text_encoder = text_encoder\n",
+ "\n",
+ " print(\"[DEBUG]: run get_dataloaders.x_y_preprocess\", flush=True)\n",
+ " x_proc, y_proc, *z_proc = ConfigDataset.x_y_preprocess(self, \n",
+ " balance_max=None, \n",
+ " shuffle=False, \n",
+ " max_samples=None, \n",
+ " make_unique=False) # ... z_proc is `'z' and all other 'c'\n",
+ " if caching:\n",
+ " if self.bucket_batch_size <= 0: \n",
+ " y_proc = self.caching(y_proc, y_on_cpu=y_on_cpu)\n",
+ " \n",
+ " else: \n",
+ " y_proc = self.caching([yi.reshape((-1)) for yi in y_proc], y_on_cpu=y_on_cpu)\n",
+ " y_proc = y_proc.reshape((-1, self.bucket_batch_size))\n",
+ " \n",
+ " #-------------------------\n",
+ " # valid split and to device\n",
+ "\n",
+ " print(\"[DEBUG]: run get_dataloaders.valid_split\", flush=True)\n",
+ " x, x_valid, y, y_valid, (z, z_valid) = self.valid_split(x_proc, y_proc, *z_proc, p_valid=p_valid, y_type=\"tensor\", split_sequential=False)\n",
+ "\n",
+ " if self.params_config.dataset_to_gpu:\n",
+ " x, x_valid = x.to(\"cuda\"), x_valid.to(\"cuda\")\n",
+ " z, z_valid = list(iz.to(\"cuda\") for iz in z), list(iz_valid.to(\"cuda\") for iz_valid in z_valid)\n",
+ "\n",
+ " if not y_on_cpu:\n",
+ " y, y_valid = y.to(\"cuda\"), y_valid.to(\"cuda\")\n",
+ "\n",
+ " #-------------------------\n",
+ " # create dataloaders\n",
+ "\n",
+ " ds = TensorDataset(x, y, *z)\n",
+ " ds_valid = TensorDataset(x_valid, y_valid, *z_valid)\n",
+ "\n",
+ " if return_tensor_datasets:\n",
+ " return ds, ds_valid\n",
+ "\n",
+ " if isinstance(self.collate_fn, str):\n",
+ " collate_fn = getattr(self, self.collate_fn, None)\n",
+ " else:\n",
+ " collate_fn = self.collate_fn\n",
+ " \n",
+ " if not exists(collate_fn):\n",
+ " print(\"[WARNING]: self.collate_fn does not exist, using torch.utils.data.default_collate.\")\n",
+ " collate_fn = torch.utils.data.default_collate\n",
+ "\n",
+ " if self.params_config.dataset_to_gpu: \n",
+ " train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True, collate_fn=collate_fn)\n",
+ " valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True, collate_fn=collate_fn)\n",
+ "\n",
+ " else: \n",
+ " train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=4, collate_fn=collate_fn)\n",
+ " valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=4, collate_fn=collate_fn)\n",
+ "\n",
+ " self.dataloaders = DataLoaders(train_loader, valid_loader) \n",
+ " return self.dataloaders\n",
+ " \n",
+ " #-----------------------------------\n",
+ " \n",
+ " @staticmethod\n",
+ " def from_datasets(*args, **kwargs):\n",
+ " raise NotImplementedError()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f41f26a8-ac40-4e91-8c0e-1ef07a0fd4f4",
+ "metadata": {},
+ "source": [
+ "# Export -"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a0474216-8e0c-4ba7-9a37-571ac7d8e82c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| hide\n",
+ "import nbdev; nbdev.nbdev_export()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "python3",
+ "language": "python",
+ "name": "python3"
+ },
+ "widgets": {
+ "application/vnd.jupyter.widget-state+json": {
+ "state": {},
+ "version_major": 2,
+ "version_minor": 0
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/src/dataset/mixed_cached_qc_dataset.ipynb b/src/dataset/mixed_cached_qc_dataset.ipynb
deleted file mode 100644
index fb778fb..0000000
--- a/src/dataset/mixed_cached_qc_dataset.ipynb
+++ /dev/null
@@ -1,641 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "id": "a8980c24-d62e-462b-ba89-3195cfdcc374",
- "metadata": {},
- "source": [
- "# Mixed cached dataset"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "21cae8fe-2a9d-4588-80f2-0a8c8def322b",
- "metadata": {},
- "source": [
- "Dataset that combines and handles multiple cached datasets, e.g. for multiple qubits. Here we also handle paddings."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "a8832bdd-f61c-44e1-8619-a9cb352ba768",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| default_exp dataset.mixed_cached_qc_dataset"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "06272f6f-b4e3-4504-a90a-feebbf6ad821",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "from genQC.imports import *\n",
- "from genQC.dataset.qc_dataset import Qc_Config_Dataset_config, Qc_Config_Dataset\n",
- "from genQC.dataset.config_dataset import Config_Dataset\n",
- "from genQC.dataset.cached_qc_dataset import Cached_OpenClip_Dataset\n",
- "from genQC.config_loader import *\n",
- "from genQC.dataset.dataset_helper import *\n",
- "from genQC.util import DataLoaders\n",
- "import dataclasses"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "119077c9-999b-44f7-8099-79037503d7e7",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "@dataclass\n",
- "class Mixed_Cached_OpenClip_Dataset_config(Qc_Config_Dataset_config):\n",
- " pad_constant: int\n",
- " collate_fn: str\n",
- " bucket_batch_size: int\n",
- " num_down_scales: int # for flex pad attn mask"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "0037efb5-d3a9-46e4-94d1-3dd80297e934",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "class Mixed_Cached_OpenClip_Dataset(Cached_OpenClip_Dataset): \n",
- " \"\"\"Dataset that uses multiple cached dataset and combines them with padding, either i) Bucket or ii) Max. Also provides a corresponding `collate_fn` for training.\"\"\"\n",
- " \n",
- " req_params = [f.name for f in dataclasses.fields(Mixed_Cached_OpenClip_Dataset_config)]\n",
- " \n",
- " cut_multiple = 4 #needed for proper downscaling!\n",
- " \n",
- " @property\n",
- " def params_config(self):\n",
- " params_config = {}\n",
- " for p in self.req_params: params_config[p] = getattr(self, p) \n",
- " params_config[\"gate_pool\"] = [class_to_str(gate) for gate in params_config[\"gate_pool\"]]\n",
- " params_config = Mixed_Cached_OpenClip_Dataset_config(**params_config)\n",
- " return params_config \n",
- " \n",
- " #-----------------------------------\n",
- " # CAUSAL ATTENTION PADDING\n",
- "\n",
- " def flexPadAttn_padding_collate_fn(self, b): \n",
- " \"\"\"this function is called for training for every batch\"\"\"\n",
- " z_0 = max(x[2][0] for x in b) # space\n",
- " z_1 = max(x[2][1] for x in b) # time\n",
- " \n",
- " #round time to next multiple of 8 for conv layers!\n",
- " z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)\n",
- " \n",
- " #---------------\n",
- " # key_padding_mask ... [N, S] -inf where we want no attention\n",
- " # we will create here [N, s, t] and then reshaping is easy\n",
- " # note this is key pad mask not directly attention mask! we need this for loss masking\n",
- " # Nb: add rnd to the padding, so we train with pad and on smaller systems\n",
- " \n",
- " #we need 3 different ones for the different unet layers \n",
- " key_padding_mask = torch.zeros((len(b), z_0, z_1), device=self.device) \n",
- " \n",
- " padd_rnds = torch.randint(low=0, high=2, size=(len(b),2), dtype=torch.int32) #roll 50/50 if we allow padding\n",
- " \n",
- " xs=[]\n",
- " ys=[]\n",
- " for i,((x,y,z), padd_rnd) in enumerate(zip(b, padd_rnds)):\n",
- " # for i,(x,y,z) in enumerate(b):\n",
- " x = x[:z_0, :z_1] # cut down to max [bits, time] of batch\n",
- " \n",
- " #------------------- \n",
- " space, time = z[0], z[1]\n",
- " \n",
- " if space < z_0 and padd_rnd[0]: space = torch.randint(low=space, high=z_0+1, size=(1,), dtype=torch.int32) \n",
- " if time < z_1 and padd_rnd[1]: time = torch.randint(low=time , high=z_1+1, size=(1,), dtype=torch.int32) \n",
- " \n",
- " time = (torch.ceil(time / self.cut_multiple) * self.cut_multiple).to(torch.int32)\n",
- " \n",
- " key_padding_mask[i, space:, :] = float('-inf') \n",
- " key_padding_mask[i, :, time:] = float('-inf') \n",
- " \n",
- " #------------------- \n",
- " \n",
- " xs.append(x)\n",
- " ys.append(y)\n",
- " \n",
- " key_padding_mask_list = [key_padding_mask]\n",
- " for j in range(1, self.num_down_scales):\n",
- " key_padding_mask_list.append(F.max_pool1d(key_padding_mask_list[j-1], kernel_size=2)) \n",
- " \n",
- " xs=torch.stack(xs)\n",
- " ys=torch.stack(ys) \n",
- " return xs, ys, key_padding_mask_list \n",
- " \n",
- " def flexPadAttn_TimeOnly_padding_collate_fn(self, b): \n",
- " \"\"\"this function is called for training for every batch\"\"\"\n",
- " z_0 = max(x[2][0] for x in b) # space\n",
- " z_1 = max(x[2][1] for x in b) # time\n",
- " \n",
- " #round time to next multiple of 8 for conv layers!\n",
- " z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)\n",
- " \n",
- " #---------------\n",
- " # key_padding_mask ... [N, S] -inf where we want no attention\n",
- " # we will create here [N, s, t] and then reshaping is easy\n",
- " # note this is key pad mask not directly attention mask! we need this for loss masking\n",
- " # Nb: add rnd to the padding, so we train with pad and on smaller systems\n",
- " \n",
- " #we need 3 different ones for the different unet layers \n",
- " key_padding_mask = torch.zeros((len(b), z_0, z_1), device=self.device) \n",
- " \n",
- " padd_rnds = torch.randint(low=0, high=2, size=(len(b)), dtype=torch.int32) #roll 50/50 if we allow padding\n",
- " \n",
- " xs=[]\n",
- " ys=[]\n",
- " for i,((x,y,z), padd_rnd) in enumerate(zip(b, padd_rnds)):\n",
- " # for i,(x,y,z) in enumerate(b):\n",
- " x = x[:z_0, :z_1] # cut down to max [bits, time] of batch\n",
- " \n",
- " #------------------- \n",
- " time = z[1]\n",
- " \n",
- " if time < z_1 and padd_rnd: time = torch.randint(low=time , high=z_1+1, size=(1,), dtype=torch.int32) \n",
- " time = (torch.ceil(time / self.cut_multiple) * self.cut_multiple).to(torch.int32) \n",
- " key_padding_mask[i, :, time:] = float('-inf') \n",
- " \n",
- " #------------------- \n",
- " \n",
- " xs.append(x)\n",
- " ys.append(y)\n",
- " \n",
- " key_padding_mask_list = [key_padding_mask]\n",
- " for j in range(1, self.num_down_scales):\n",
- " key_padding_mask_list.append(F.max_pool1d(key_padding_mask_list[j-1], kernel_size=2)) \n",
- " \n",
- " xs=torch.stack(xs)\n",
- " ys=torch.stack(ys) \n",
- " return xs, ys, key_padding_mask_list \n",
- "\n",
- " #-----------------------------------\n",
- " # BUCKET PADDING, all x,y are already passed as batch\n",
- " \n",
- " def cut_padding_Bucket_collate_fn(self, b): \n",
- " \"\"\"this function is called for training for every batch\"\"\" \n",
- " \n",
- " b = b[0]\n",
- " \n",
- " x = b[0]\n",
- " y = b[1]\n",
- " z = b[2]\n",
- " \n",
- " #---------------\n",
- " \n",
- " z_0 = torch.max(z[:, 0]) # space\n",
- " z_1 = torch.max(z[:, 1]) # time\n",
- " \n",
- " #round time to next multiple of cut_multiple for conv layers!\n",
- " z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)\n",
- " \n",
- " #--------------- \n",
- " \n",
- " x = x[:, :z_0, :z_1] # cut down to max [b, bits, time] of batch\n",
- " \n",
- " return x, y\n",
- "\n",
- " def cut_padding_Bucket_collate_fn_compilation(self, b): \n",
- " \"\"\"this function is called for training for every batch\"\"\" \n",
- " \n",
- " b = b[0]\n",
- " \n",
- " x = b[0]\n",
- " y = b[1] \n",
- " U = b[2]\n",
- " z = b[3]\n",
- " \n",
- " #---------------\n",
- " \n",
- " z_0 = torch.max(z[:, 0]) # space\n",
- " z_1 = torch.max(z[:, 1]) # time\n",
- " \n",
- " #round time to next multiple of cut_multiple for conv layers!\n",
- " z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)\n",
- " \n",
- " #--------------- \n",
- " \n",
- " x = x[:, :z_0, :z_1] # cut down to max [b, bits, time] of batch\n",
- " \n",
- " bit_exp = 2**z_0\n",
- " U = U[:, :, :bit_exp, :bit_exp] # [b, Re/Im, 2^n, 2^n]\n",
- " \n",
- " return x, y, U\n",
- "\n",
- " def cut_padding_Bucket_collate_fn_compilation_params(self, b): \n",
- " \"\"\"this function is called for training for every batch, order in b is store dict\"\"\" \n",
- " \n",
- " b = b[0] # {'x': 'tensor', 'y': 'numpy', 'params': 'tensor', 'U': 'tensor', 'z': 'tensor'}\n",
- " \n",
- " x = b[0]\n",
- " y = b[1] \n",
- " p = b[2]\n",
- " U = b[3]\n",
- " z = b[4]\n",
- " \n",
- " #---------------\n",
- " \n",
- " z_0 = torch.max(z[:, 0]) # space\n",
- " z_1 = torch.max(z[:, 1]) # time\n",
- " \n",
- " #round time to next multiple of cut_multiple for conv layers!\n",
- " z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)\n",
- " \n",
- " #--------------- \n",
- " \n",
- " x = x[:, :z_0, :z_1] # cut down to max [b, bits, time] of batch\n",
- "\n",
- " p = p[:, :, :z_1]\n",
- " \n",
- " bit_exp = 2**z_0\n",
- " U = U[:, :, :bit_exp, :bit_exp] # [b, Re/Im, 2^n, 2^n]\n",
- " \n",
- " return x, y, p, U\n",
- " \n",
- " #-----------------------------------\n",
- " # MAX PADDING, x are passes as sampled list (batch), std collate them\n",
- " \n",
- " def cut_padding_collate_fn(self, b): \n",
- " \"\"\"this function is called for training for every batch\"\"\" \n",
- " z_0 = max(x[2][0] for x in b) # space\n",
- " z_1 = max(x[2][1] for x in b) # time\n",
- " \n",
- " #round time to next multiple of cut_multiple for conv layers!\n",
- " z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)\n",
- " \n",
- " #--------------- \n",
- "\n",
- " x_sample = b[0][0]\n",
- " xs = torch.zeros((len(b), z_0, z_1), dtype=x_sample.dtype, device=x_sample.device)\n",
- " \n",
- " # xs=[]\n",
- " ys=[]\n",
- " for i,(x,y,z) in enumerate(b):\n",
- " #x = x[:z_0, :z_1] # cut down to max [bits, time] of batch\n",
- " xs[i] = x[:z_0, :z_1]\n",
- " \n",
- " #xs.append(x)\n",
- " ys.append(y)\n",
- " \n",
- " #xs=torch.stack(xs)\n",
- " ys=torch.stack(ys) \n",
- " \n",
- " return xs, ys \n",
- "\n",
- " def cut_padding_collate_fn_compilation(self, b):\n",
- " \"\"\"this function is called for training for every batch\"\"\" \n",
- " z_0 = max(x[3][0] for x in b) # space\n",
- " z_1 = max(x[3][1] for x in b) # time\n",
- " \n",
- " #round time to next multiple of cut_multiple for conv layers!\n",
- " z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)\n",
- "\n",
- " bit_exp = 2**z_0\n",
- " \n",
- " #--------------- \n",
- "\n",
- " x_sample = b[0][0]\n",
- " xs = torch.zeros((len(b), z_0, z_1), dtype=x_sample.dtype, device=x_sample.device)\n",
- "\n",
- " y_sample = b[0][1]\n",
- " ys = torch.zeros((len(b), *y_sample.shape), dtype=y_sample.dtype, device=y_sample.device)\n",
- "\n",
- " U_sample = b[0][2]\n",
- " Us = torch.zeros((len(b), 2, bit_exp, bit_exp), dtype=U_sample.dtype, device=U_sample.device)\n",
- " \n",
- " for i,(x,y,U,z) in enumerate(b):\n",
- " xs[i] = x[:z_0, :z_1]\n",
- " ys[i] = y\n",
- " Us[i] = U[:, :bit_exp, :bit_exp]\n",
- " \n",
- " return xs, ys, Us \n",
- "\n",
- " def cut_padding_collate_fn_compilation_params(self, b):\n",
- " \"\"\"this function is called for training for every batch, order in b is store dict\"\"\" \n",
- " # {'x': 'tensor', 'y': 'numpy', 'params': 'tensor', 'U': 'tensor', 'z': 'tensor'}\n",
- " \n",
- " z_0 = max(x[4][0] for x in b) # space\n",
- " z_1 = max(x[4][1] for x in b) # time\n",
- " \n",
- " #round time to next multiple of cut_multiple for conv layers!\n",
- " z_1 = (torch.ceil(z_1 / self.cut_multiple) * self.cut_multiple).to(torch.int32)\n",
- "\n",
- " bit_exp = 2**z_0\n",
- " \n",
- " #--------------- \n",
- "\n",
- " x_sample = b[0][0]\n",
- " xs = torch.zeros((len(b), z_0, z_1), dtype=x_sample.dtype, device=x_sample.device)\n",
- "\n",
- " y_sample = b[0][1]\n",
- " ys = torch.zeros((len(b), *y_sample.shape), dtype=y_sample.dtype, device=y_sample.device)\n",
- "\n",
- " p_sample = b[0][2]\n",
- " ps = torch.zeros((len(b), p_sample.shape[-2], z_1), dtype=p_sample.dtype, device=p_sample.device)\n",
- " \n",
- " U_sample = b[0][3]\n",
- " Us = torch.zeros((len(b), 2, bit_exp, bit_exp), dtype=U_sample.dtype, device=U_sample.device)\n",
- " \n",
- " for i,(x,y,p,U,z) in enumerate(b):\n",
- " xs[i] = x[:z_0, :z_1]\n",
- " ys[i] = y\n",
- " ps[i] = p[:, :z_1]\n",
- " Us[i] = U[:, :bit_exp, :bit_exp]\n",
- " \n",
- " return xs, ys, ps, Us \n",
- " \n",
- " #-----------------------------------\n",
- "\n",
- " def get_dataloaders(self, batch_size, text_encoder, p_valid=0.1, y_on_cpu=False):\n",
- " self.text_encoder = text_encoder\n",
- " \n",
- " excepts = []\n",
- " if y_on_cpu: excepts.append(\"y\")\n",
- " if self.params_config.dataset_to_gpu: self.to(\"cuda\", excepts=excepts)\n",
- " \n",
- " x_proc, y_proc, *z_proc = Qc_Config_Dataset.x_y_preprocess(self, balance_max=None, shuffle=False) # ... z_proc is `'z' and all other 'c'\n",
- " \n",
- " if self.bucket_batch_size <= 0: \n",
- " y_proc = self.caching(y_proc, y_on_cpu=y_on_cpu)\n",
- " \n",
- " else: \n",
- " y_proc = self.caching([yi.reshape((-1)) for yi in y_proc], y_on_cpu=y_on_cpu)\n",
- " y_proc = y_proc.reshape((-1, self.bucket_batch_size))\n",
- " \n",
- " x_proc, y_proc, *z_proc = shuffle_tensor_dataset(x_proc, y_proc, *z_proc) #only possible after str y is cached as tensor\n",
- " x, x_valid, y, y_valid, (z, z_valid) = self.valid_split(x_proc, y_proc, *z_proc, p_valid=p_valid)\n",
- " \n",
- " ds = TensorDataset(x, y, *z)\n",
- " ds_valid = TensorDataset(x_valid, y_valid, *z_valid)\n",
- "\n",
- " collate_fn = getattr(self, self.collate_fn)\n",
- " \n",
- " if self.params_config.dataset_to_gpu: \n",
- " train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True, collate_fn=collate_fn)\n",
- " valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True, collate_fn=collate_fn)\n",
- "\n",
- " else: \n",
- " train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=12, collate_fn=collate_fn)\n",
- " valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=12, collate_fn=collate_fn)\n",
- "\n",
- " self.dataloaders = DataLoaders(train_loader, valid_loader) \n",
- " return self.dataloaders\n",
- " \n",
- " #-----------------------------------\n",
- " \n",
- " @staticmethod\n",
- " def from_datasets(datasets: list[Qc_Config_Dataset], balance_maxes: list, pad_constant, device: torch.device=torch.device(\"cpu\"), bucket_batch_size=None, max_samples=None, **parameters):\n",
- " assert pad_constant != 0, \"can NOT be 0! and not any other gate!\"\n",
- " \n",
- " xs = []\n",
- " ys = []\n",
- " zs = []\n",
- " cs = []\n",
- " \n",
- " cut_multiple = Mixed_Cached_OpenClip_Dataset.cut_multiple\n",
- " \n",
- " max_qubits = max(dataset.params_config.num_of_qubits for dataset in datasets)\n",
- " max_gates = max(dataset.params_config.max_gates for dataset in datasets)\n",
- " max_gates = int(np.ceil(max_gates /cut_multiple) * cut_multiple)\n",
- " \n",
- " parameters[\"num_of_qubits\"] = max_qubits\n",
- " parameters[\"max_gates\"] = max_gates\n",
- " parameters[\"random_samples\"] = sum([dataset.params_config.random_samples for dataset in datasets])\n",
- " parameters[\"min_gates\"] = min([dataset.params_config.min_gates for dataset in datasets])\n",
- " parameters[\"comment\"] = f\"Generated with 'from_datasets' with {len(datasets)} datasets. Qubits: {[dataset.params_config.num_of_qubits for dataset in datasets]}.\"\n",
- " parameters[\"pad_constant\"] = pad_constant\n",
- " parameters[\"bucket_batch_size\"] = bucket_batch_size\n",
- " \n",
- " parameters[\"store_dict\"] = {}\n",
- " for dataset in datasets:\n",
- " parameters[\"store_dict\"] |= dataset.params_config.store_dict #needs python 3.9 for union of dict \n",
- " parameters[\"store_dict\"][\"z\"] = \"tensor\" #add special item\n",
- "\n",
- " if isinstance(max_samples, int):\n",
- " max_samples = [max_samples] * len(datasets)\n",
- " else:\n",
- " assert isinstance(max_samples, (list, np.ndarray))\n",
- " max_samples = np.array(max_samples, dtype=int)\n",
- "\n",
- " if isinstance(balance_maxes, int):\n",
- " balance_maxes = [balance_maxes] * len(datasets)\n",
- " else:\n",
- " assert isinstance(balance_maxes, (list, np.ndarray))\n",
- " balance_maxes = np.array(balance_maxes, dtype=int)\n",
- " \n",
- " for i, (dataset, balance_max) in tqdm(enumerate(zip(datasets,balance_maxes)), total=len(datasets)):\n",
- " # do x_y_preprocess now, we can't balance all together with mixed conditions\n",
- " \n",
- " dataset = dataset.to(device)\n",
- " \n",
- " x, y, *c = dataset.x_y_preprocess(balance_max=balance_max, max_samples=max_samples[i], shuffle=True) \n",
- " x = x.to(device) # [b, s, t] \n",
- " \n",
- " print(f\" - dataset size after balancing {x.shape[0]}\")\n",
- "\n",
- " #-------\n",
- " # store original size\n",
- " z = torch.zeros((x.shape[0], 2), device=device, dtype=torch.int32)\n",
- " z[:, 0] = max(dataset.params_config.num_of_qubits, 1)\n",
- " \n",
- " red_x = torch.sum(x.abs(), dim=1) # [b, t] .. collaps the zeros to get circuit length\n",
- " z[:, 1] = torch.count_nonzero(red_x, dim=1) # [b] \n",
- " z[z[:, 1]==0, 1] = 1 # make sure we don*t have 0, so we cheat and set it to 1 (there's only 1 unique zero gate circuit anyways). Needed for padding attn mask \n",
- " \n",
- " for i in range(x.shape[0]):\n",
- " x[i, z[i, 0]:, :] = pad_constant\n",
- " x[i, :, z[i, 1]:] = pad_constant\n",
- " \n",
- " z[:, 1] = (torch.ceil(z[:, 1] / cut_multiple) * cut_multiple).to(torch.int32) #for cut needs multiple\n",
- "\n",
- " #-------\n",
- " # now pad x, padding is defined from last dim forward! \n",
- " pad = (0, max_gates-dataset.params_config.max_gates, 0, max_qubits-dataset.params_config.num_of_qubits) \n",
- " x = F.pad(x, pad, \"constant\", pad_constant)\n",
- " \n",
- " # if c is missing something of the union we set it to a zero tensor\n",
- " for k,v in parameters[\"store_dict\"].items(): \n",
- " if k != \"x\" and k != \"y\" and k != \"z\":\n",
- " \n",
- " if k not in dataset.params_config.store_dict:\n",
- " empty_tensor = torch.zeros((1,), device=device)\n",
- " \n",
- " if k == \"U\": #scetchy hardcoded for compilation\n",
- " empty_tensor = torch.zeros((x.shape[0], 2, 1, 1), device=device) # unitary is [b, Re/Im, 2^n, 2^n]\n",
- " \n",
- " assert len(c) == 0\n",
- " c.append(empty_tensor) #scetchy bcs if c is not empty we could break ordering!!!\n",
- " \n",
- " #combine datasets\n",
- " xs.append(x.cpu()) \n",
- " ys.append(y)\n",
- " zs.append(z) \n",
- " cs.append([*c])\n",
- "\n",
- " dataset = dataset.to(\"cpu\") #helps with gpu mem overflowing\n",
- " #-----------------\n",
- "\n",
- " has_U = \"U\" in parameters[\"store_dict\"]\n",
- " has_p = \"params\" in parameters[\"store_dict\"]\n",
- " \n",
- " if bucket_batch_size > 0:\n",
- " collate_fn_name = Mixed_Cached_OpenClip_Dataset.cut_padding_Bucket_collate_fn.__name__\n",
- " if has_U: \n",
- " collate_fn_name = Mixed_Cached_OpenClip_Dataset.cut_padding_Bucket_collate_fn_compilation.__name__\n",
- " if has_p: \n",
- " collate_fn_name = Mixed_Cached_OpenClip_Dataset.cut_padding_Bucket_collate_fn_compilation_params.__name__\n",
- " \n",
- " else:\n",
- " collate_fn_name = Mixed_Cached_OpenClip_Dataset.cut_padding_collate_fn.__name__ \n",
- " if has_U: \n",
- " collate_fn_name = Mixed_Cached_OpenClip_Dataset.cut_padding_collate_fn_compilation.__name__\n",
- " if has_p: \n",
- " collate_fn_name = Mixed_Cached_OpenClip_Dataset.cut_padding_collate_fn_compilation_params.__name__\n",
- "\n",
- " parameters[\"collate_fn\"] = collate_fn_name\n",
- " \n",
- " #-----------------\n",
- " if bucket_batch_size > 0:\n",
- " for i, (xi,yi,zi, ci) in enumerate(zip(xs, ys, zs, cs)): #cut rest of batch \n",
- " b_mult = int(np.floor(xi.shape[0] / bucket_batch_size) * bucket_batch_size) \n",
- " \n",
- " xs[i] = xi[None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *xi.shape[1:])) \n",
- " zs[i] = zi[None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *zi.shape[1:]))\n",
- " \n",
- " t = parameters[\"store_dict\"][\"y\"]\n",
- " if v == \"tensor\" or v == \"numpy\": \n",
- " ys[i] = yi[None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *yi.shape[1:])) \n",
- " else: raise NotImplementedError(\"\")\n",
- " \n",
- " #----\n",
- " #For U, etc\n",
- " add_ind = 0\n",
- " for k,v in parameters[\"store_dict\"].items(): \n",
- " if k != \"x\" and k != \"y\" and k != \"z\": \n",
- " if v == \"tensor\" or v == \"numpy\": \n",
- " cs[i][add_ind] = ci[add_ind][None, :b_mult].reshape((b_mult//bucket_batch_size, bucket_batch_size, *ci[add_ind].shape[1:])) \n",
- " else: raise NotImplementedError(\"\") \n",
- " add_ind += 1 \n",
- " \n",
- " x = torch.cat(xs)\n",
- " y = ys # torch.cat(ys) is wrong, y is list of numpy or str!! not a tensor\n",
- " z = torch.cat(zs)\n",
- " c = cs\n",
- " \n",
- " #----------------- \n",
- " \n",
- " mixed_Cached_OpenClip_Dataset = Mixed_Cached_OpenClip_Dataset(device, **parameters) \n",
- " mixed_Cached_OpenClip_Dataset.x = x\n",
- " mixed_Cached_OpenClip_Dataset.y = y\n",
- " mixed_Cached_OpenClip_Dataset.z = z\n",
- " \n",
- " add_ind = 0\n",
- " for k,v in parameters[\"store_dict\"].items(): \n",
- " if k != \"x\" and k != \"y\" and k != \"z\": \n",
- " \n",
- " if v == \"tensor\" and k == \"U\": # hardcoded U padding !!\n",
- " \n",
- " n = sum([ci[add_ind].shape[0] for ci in c])\n",
- " if bucket_batch_size > 0: shape = (n, bucket_batch_size, 2, 2**max_qubits, 2**max_qubits)\n",
- " else: shape = (n, 2, 2**max_qubits, 2**max_qubits)\n",
- " \n",
- " # allocating zeros is better memory wise than torch.cat(ci_s) and F.pad(ci, pad, \"constant\", 0)\n",
- " mem = np.prod(shape) * c[0][add_ind].element_size() * 1e-9\n",
- " print(f\"[INFO]: allocate memory for {k} {shape} on {c[0][add_ind].device} approx. {mem:.3f} GB\")\n",
- " ci_s = torch.zeros(shape, device=c[0][add_ind].device) \n",
- " \n",
- " run_i = 0\n",
- " for i,ci in enumerate(c):\n",
- " ci = ci[add_ind] \n",
- " if bucket_batch_size > 0: ci_s[run_i:run_i+ci.shape[0], :, :, :ci.shape[-2], :ci.shape[-1]] = ci \n",
- " else: ci_s[run_i:run_i+ci.shape[0], :, :ci.shape[-2], :ci.shape[-1]] = ci \n",
- " run_i += ci.shape[0]\n",
- "\n",
- " elif v == \"tensor\" and k == \"params\": # hardcoded paramter padding !!\n",
- "\n",
- " max_params = max(ci[add_ind].shape[-2] for ci in c)\n",
- " \n",
- " n = sum(ci[add_ind].shape[0] for ci in c)\n",
- " if bucket_batch_size > 0: shape = (n, bucket_batch_size, max_params, max_gates)\n",
- " else: shape = (n, max_params, max_gates)\n",
- "\n",
- " # allocating zeros is better memory wise than torch.cat(ci_s) and F.pad(ci, pad, \"constant\", 0)\n",
- " mem = np.prod(shape) * c[0][add_ind].element_size() * 1e-9\n",
- " print(f\"[INFO]: allocate memory for {k} {shape} on {c[0][add_ind].device} approx. {mem:.3f} GB\")\n",
- " ci_s = torch.zeros(shape, device=c[0][add_ind].device) \n",
- " \n",
- " run_i = 0\n",
- " for i,ci in enumerate(c):\n",
- " ci = ci[add_ind] \n",
- " if bucket_batch_size > 0: ci_s[run_i:run_i+ci.shape[0], :, :ci.shape[-2], :ci.shape[-1]] = ci \n",
- " else: ci_s[run_i:run_i+ci.shape[0], :ci.shape[-2], :ci.shape[-1]] = ci \n",
- " run_i += ci.shape[0]\n",
- " \n",
- " elif v == \"numpy\": raise NotImplementedError(\"\") \n",
- " else: raise NotImplementedError(\"\") \n",
- " \n",
- " setattr(mixed_Cached_OpenClip_Dataset, str(k), ci_s)\n",
- " add_ind += 1\n",
- " \n",
- " return mixed_Cached_OpenClip_Dataset\n",
- " \n",
- " #------------------------------------\n",
- " \n",
- " # def plot_example(self): print(\"plot_example not implemented for Mixed_Cached_OpenClip_Dataset\")\n",
- " # def plot_distribution(self): print(\"plot_distribution not implemented for Mixed_Cached_OpenClip_Dataset\")\n",
- " \n",
- " @staticmethod\n",
- " def from_config_file(config_path, device: torch.device, save_path: str=None):\n",
- " config = load_config(config_path)\n",
- " config[\"target\"] = class_to_str(Mixed_Cached_OpenClip_Dataset) \n",
- " return Config_Dataset.from_config(config, device, save_path)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "f41f26a8-ac40-4e91-8c0e-1ef07a0fd4f4",
- "metadata": {},
- "source": [
- "# Export -"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "a0474216-8e0c-4ba7-9a37-571ac7d8e82c",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| hide\n",
- "import nbdev; nbdev.nbdev_export()"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "python3",
- "language": "python",
- "name": "python3"
- },
- "widgets": {
- "application/vnd.jupyter.widget-state+json": {
- "state": {},
- "version_major": 2,
- "version_minor": 0
- }
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/src/dataset/qc_dataset.ipynb b/src/dataset/qc_dataset.ipynb
deleted file mode 100644
index 87c7984..0000000
--- a/src/dataset/qc_dataset.ipynb
+++ /dev/null
@@ -1,286 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "id": "a8980c24-d62e-462b-ba89-3195cfdcc374",
- "metadata": {},
- "source": [
- "# Quantum circuit dataset"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "233e6242-46fc-451b-9e58-abf60b1216ef",
- "metadata": {},
- "source": [
- "Dataset for quantum circuits."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "a8832bdd-f61c-44e1-8619-a9cb352ba768",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| default_exp dataset.qc_dataset"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "06272f6f-b4e3-4504-a90a-feebbf6ad821",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "from genQC.imports import *\n",
- "from genQC.dataset.config_dataset import Config_Dataset, Config_Dataset_config\n",
- "from genQC.config_loader import *\n",
- "from genQC.dataset.dataset_helper import *\n",
- "from genQC.platform.qcircuit_dataset_construction import decode_circuit\n",
- "from genQC.platform.simulation.qcircuit_sim import schmidt_rank_vector, instruction_name_to_qiskit_gate\n",
- "import qiskit.quantum_info as qi"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "016fc327-f986-4d69-b5f0-1b39466fb528",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "@dataclass\n",
- "class Qc_Config_Dataset_config(Config_Dataset_config):\n",
- " optimized: bool\n",
- " dataset_to_gpu: bool\n",
- " random_samples: int \n",
- " num_of_qubits: int \n",
- " min_gates: int \n",
- " max_gates: int \n",
- " gate_pool: list[str] "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "36032308-bd0e-4409-9db0-9d89fc258e5a",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "class Qc_Config_Dataset(Config_Dataset):\n",
- " \"\"\"Dataset for quantum circuits, access `gate_pool` directly and all other paras with `.params_config`\"\"\"\n",
- " \n",
- " req_params = [f.name for f in dataclasses.fields(Qc_Config_Dataset_config)]\n",
- " add_balance_fn = None\n",
- " \n",
- " def __init__(self, device: torch.device=torch.device(\"cpu\"), **parameters):\n",
- " super().__init__(device, **parameters) \n",
- " self.gate_pool = parameters[\"gate_pool\"] #[get_obj_from_str(gate) for gate in parameters[\"gate_pool\"]] \n",
- " \n",
- " @property\n",
- " def params_config(self):\n",
- " params_config = super().params_config \n",
- " #params_config[\"gate_pool\"] = [class_to_str(gate) for gate in params_config[\"gate_pool\"]]\n",
- " params_config = Qc_Config_Dataset_config(**params_config)\n",
- " return params_config \n",
- " \n",
- " #----------------------------\n",
- " \n",
- " def x_y_preprocess(self, balance_max=None, shuffle=False, max_samples=None):\n",
- " #params_config = self.params_config\n",
- " #if params_config.dataset_to_gpu: self.to(\"cuda\")\n",
- " \n",
- " z_proc = []\n",
- " for k,v in self.store_dict.items(): \n",
- " if k != \"x\" and k != \"y\":\n",
- " z_proc.append(getattr(self, k))\n",
- " \n",
- " x_proc, y_proc = self.x, self.y\n",
- " \n",
- " #---------------------\n",
- " if shuffle:\n",
- " x_proc, y_proc, *z_proc = shuffle_tensor_dataset(x_proc, y_proc, *z_proc)\n",
- " \n",
- " if exists(max_samples):\n",
- " x_proc = x_proc[:max_samples]\n",
- " y_proc = y_proc[:max_samples]\n",
- " z_proc = (iz[:max_samples] for iz in z_proc) \n",
- " \n",
- " #---------------------\n",
- " t = self.store_dict[\"y\"]\n",
- " if exists(balance_max): \n",
- " if t == \"tensor\" or t == \"numpy\": x_proc, y_proc, *z_proc = balance_tensor_dataset(x_proc, y_proc, *z_proc, make_unique=True, \n",
- " samples=balance_max, add_balance_fn=self.add_balance_fn) \n",
- " else: print(f\"[WARNING]: Unsupported y type: `{t}`. Not balancing dataset!\")\n",
- " else: print(f\"[INFO]: Not balancing dataset! {balance_max=}\")\n",
- " \n",
- " #---------------------\n",
- " if shuffle:\n",
- " x_proc, y_proc, *z_proc = shuffle_tensor_dataset(x_proc, y_proc, *z_proc)\n",
- " \n",
- " return x_proc, y_proc, *z_proc\n",
- " \n",
- " def valid_split(self, x, y, *z, p_valid=0.1):\n",
- " splits = max(int(x.shape[0] * p_valid), 1)\n",
- " x, x_valid = x[splits:].clone(), x[:splits].clone() \n",
- " \n",
- " t = self.store_dict[\"y\"]\n",
- " if t == \"tensor\" : y, y_valid = y[splits:].clone(), y[:splits].clone() \n",
- " elif t == \"numpy\": y, y_valid = y[splits:] , y[:splits] \n",
- " \n",
- " else: raise NotImplementedError(\"Not implemented\")\n",
- " \n",
- " try:\n",
- " z = list(iz[splits:].clone() for iz in z)\n",
- " z_valid = list(iz[:splits].clone() for iz in z) \n",
- " except:\n",
- " z = list(iz[splits:] for iz in z)\n",
- " z_valid = list(iz[:splits] for iz in z) \n",
- " \n",
- " return x, x_valid, y, y_valid, (z, z_valid)\n",
- " \n",
- " def get_dataloaders(self, batch_size, p_valid=0.1, balance_max=None, max_samples=None, y_on_cpu=False):\n",
- " \n",
- " excepts = []\n",
- " if y_on_cpu: excepts.append(\"y\")\n",
- " if self.params_config.dataset_to_gpu: self.to(\"cuda\", excepts=excepts)\n",
- " \n",
- " x_proc, y_proc, *z_proc = self.x_y_preprocess(balance_max=balance_max, max_samples=max_samples) \n",
- " x, x_valid, y, y_valid, (z, z_valid) = self.valid_split(x_proc, y_proc, *z_proc, p_valid=p_valid)\n",
- " \n",
- " ds = TensorDataset(x, y, *z)\n",
- " ds_valid = TensorDataset(x_valid, y_valid, *z_valid)\n",
- " \n",
- " if self.params_config.dataset_to_gpu: \n",
- " train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True)\n",
- " valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True)\n",
- "\n",
- " else: \n",
- " train_loader = DataLoader(dataset=ds , batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=12)\n",
- " valid_loader = DataLoader(dataset=ds_valid, batch_size=batch_size, shuffle=True, pin_memory=True, num_workers=12)\n",
- "\n",
- " self.dataloaders = DataLoaders(train_loader, valid_loader) \n",
- " return self.dataloaders\n",
- "\n",
- " #----------------------------\n",
- " \n",
- " def plot_example(self):\n",
- " params_config = self.params_config\n",
- " enc_tensor = self.x[0]\n",
- " \n",
- " while enc_tensor.dim()>2: enc_tensor=enc_tensor[0]\n",
- "\n",
- " params = None\n",
- " if hasattr(self, \"params\"): params=self.params[0]\n",
- "\n",
- " if isinstance(self.gate_pool[0], str):\n",
- " gate_pool = [instruction_name_to_qiskit_gate(gate) for gate in self.gate_pool]\n",
- " else:\n",
- " gate_pool = self.gate_pool\n",
- " \n",
- " qc = decode_circuit(enc_tensor, gate_pool, params_tensor=params)\n",
- " \n",
- " t = self.store_dict[\"y\"]\n",
- " if t == \"tensor\" : label = self.y[0].cpu().tolist()\n",
- " elif t == \"tensor_list\": \n",
- " print(\"Not implemented\")\n",
- " return\n",
- " else : \n",
- " label = self.y[0]#.tolist()\n",
- " while len(label.shape)>0: label=label[0]\n",
- " \n",
- " print(f\"Label: ``{label}`` SRV is: {schmidt_rank_vector(qi.DensityMatrix(qc))}\")\n",
- " display(qc.draw(\"mpl\", plot_barriers=False))\n",
- "\n",
- " def plot_distribution(self):\n",
- " if hasattr(self, \"dataloaders\"): x, y, *z = self.dataloaders.train.dataset.tensors \n",
- " else: x, y = self.x, self.y\n",
- " \n",
- " t = self.store_dict[\"y\"]\n",
- " if t == \"tensor\" : data={\"svr\":[iy for iy in y.cpu().tolist()]}\n",
- " elif t == \"numpy\": data={\"svr\":[iy for iy in y.tolist()]}\n",
- " else: # list tensor_list \n",
- " print(\"Not implemented\")\n",
- " return\n",
- " \n",
- " print(\"Train dataset (x, y):\", x.shape, y.shape)\n",
- " print(\"Train uniques x :\", torch.unique(x, dim=0).shape) \n",
- " \n",
- " #real data distribution \n",
- " df = pd.DataFrame(data) \n",
- " cnts = df['svr'].value_counts(normalize=True)\n",
- " for n,v in zip(cnts.index, cnts.values): print(f\"{n}: {v*100:.1f}%\") \n",
- " ax = df['svr'].value_counts().plot(kind='bar')"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "e6322ed9-c703-41df-88a3-6b163c051af1",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "{'target': '__main__.Qc_Config_Dataset',\n",
- " 'device': 'cpu',\n",
- " 'comment': '',\n",
- " 'save_path': None,\n",
- " 'save_datetime': '08/26/2024 21:37:39',\n",
- " 'params': Qc_Config_Dataset_config(store_dict={'x': 'tensor', 'y': 'tensor_list'}, optimized=None, dataset_to_gpu=None, random_samples=None, num_of_qubits=None, min_gates=None, max_gates=None, gate_pool=['h', 'cx', 'x'])}"
- ]
- },
- "execution_count": null,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "init = {k:None for k in Qc_Config_Dataset.req_params}\n",
- "init[\"gate_pool\"] = [\"h\", \"cx\", \"x\"]\n",
- "init[\"store_dict\"] = {\"x\":\"tensor\", \"y\":\"tensor_list\"}\n",
- "\n",
- "a = Qc_Config_Dataset(**init)\n",
- "a.get_config()"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "f41f26a8-ac40-4e91-8c0e-1ef07a0fd4f4",
- "metadata": {},
- "source": [
- "# Export -"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "a0474216-8e0c-4ba7-9a37-571ac7d8e82c",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| hide\n",
- "import nbdev; nbdev.nbdev_export()"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "python3",
- "language": "python",
- "name": "python3"
- },
- "widgets": {
- "application/vnd.jupyter.widget-state+json": {
- "state": {},
- "version_major": 2,
- "version_minor": 0
- }
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/src/examples/0_hello_circuit.ipynb b/src/examples/0_hello_circuit.ipynb
deleted file mode 100644
index 19a5d0e..0000000
--- a/src/examples/0_hello_circuit.ipynb
+++ /dev/null
@@ -1,534 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "id": "69a855f1-55dd-482e-94f2-9ad02804be4d",
- "metadata": {},
- "source": [
- "# Generate a circuit"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "e41e2465-49d8-46b8-b046-6ae1becfb268",
- "metadata": {},
- "source": [
- "A minimal example to generate a circuit. We load a pre-trained (SRV, 3 to 8 qubit) model and condition on a given Schmidt-Rank-Vector (SRV)."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "3bde494e-9091-41a4-a601-bbcf9712c564",
- "metadata": {},
- "outputs": [],
- "source": [
- "from genQC.imports import *\n",
- "from genQC.pipeline.diffusion_pipeline import DiffusionPipeline\n",
- "import genQC.inference.infer_srv as infer_srv\n",
- "import genQC.util as util"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "029be4f3-0d9a-4d0a-93d9-2338fda7a983",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[INFO]: Cuda device has a capability of 8.6 (>= 8), allowing tf32 matmul.\n"
- ]
- }
- ],
- "source": [
- "device = util.infer_torch_device() # use cuda if we can\n",
- "util.MemoryCleaner.purge_mem() # clean existing memory alloc"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "4f36be1a-a5c6-4484-a096-4a37c9772e84",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "device(type='cuda')"
- ]
- },
- "execution_count": null,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "device"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "f77a3020-247c-4ac0-aaf1-ee5c371b5f06",
- "metadata": {},
- "source": [
- "## Setup and load"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "742ae430-46f2-4099-ac8f-f422a4ddc1dc",
- "metadata": {},
- "source": [
- "Load the pre-trained model directly from [Hugging Face: Floki00/qc_srv_3to8qubit](https://huggingface.co/Floki00/qc_srv_3to8qubit)."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "e5d60c23-9514-4432-bc82-622c088fced6",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "dc1b8a80999f45f1a474f9ee25f32f4f",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "Fetching 2 files: 0%| | 0/2 [00:00, ?it/s]"
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[INFO]: `genQC.models.unet_qc.QC_Cond_UNet` instantiated from given config on cuda.\n",
- "[INFO]: `genQC.models.frozen_open_clip.CachedFrozenOpenCLIPEmbedder` instantiated from given config on cuda.\n",
- "[INFO]: `genQC.models.frozen_open_clip.CachedFrozenOpenCLIPEmbedder`. No save_path` provided. No state dict loaded.\n"
- ]
- }
- ],
- "source": [
- "pipeline = DiffusionPipeline.from_pretrained(\"Floki00/qc_srv_3to8qubit\", device)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "104f977d-a6c5-4dbf-b272-b1a8fc6f013a",
- "metadata": {},
- "source": [
- "Check on what gates the model was trained"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "62b067ac-d5a4-4424-b7da-571ae95067c6",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "['h', 'cx']"
- ]
- },
- "execution_count": null,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "pipeline.gate_pool"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "431d3e29-f121-4c61-95bc-bfd7960a4870",
- "metadata": {},
- "source": [
- "Set 20 sample steps and use rescaled guidance-formula."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "96702fba-5a10-44e6-bef9-634d9e41a1af",
- "metadata": {},
- "outputs": [],
- "source": [
- "pipeline.guidance_sample_mode = \"rescaled\"\n",
- "pipeline.scheduler.set_timesteps(20) "
- ]
- },
- {
- "cell_type": "markdown",
- "id": "65acff8f-8486-42c9-8e78-b44f31de568b",
- "metadata": {},
- "source": [
- "## Inference / sampling"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "a09bd191-0374-45af-b923-f131c5d36af9",
- "metadata": {},
- "source": [
- "Set our desired condition SRV"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "d1d4b69e-c14a-4dac-9cdf-2b65ecaee158",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "'Generate SRV: [2, 1, 2, 1, 2]'"
- ]
- },
- "execution_count": null,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "srv = [2, 1, 2, 1, 2] # set your target SRV; can be 3 to 8 qubit\n",
- "num_of_qubits = len(srv) \n",
- "\n",
- "prompt = f\"Generate SRV: {srv}\" # model was trained with this phrase\n",
- "prompt"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "0184031c-627c-4b82-9607-e35a22f699f4",
- "metadata": {},
- "source": [
- "Define sample parameters"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "721ba4f1-60f0-4f23-9306-bdf07f8fe659",
- "metadata": {},
- "outputs": [],
- "source": [
- "g = 10 # guidance scale\n",
- "max_gates = 16 # how many time steps the tensor encoding has\n",
- "samples = 64 # how many circuits to generate"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "2842cc0c-770a-451c-ac44-e7265dbd87c2",
- "metadata": {},
- "source": [
- "Sample tokenized circuits"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "f72a4c09-3af9-4221-a987-2dc7fa8b2b33",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "226882c117744a86896c82b71cc87232",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- " 0%| | 0/20 [00:00, ?it/s]"
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[INFO]: (generate_srv_tensors) Generated 64 tensors\n"
- ]
- }
- ],
- "source": [
- "out_tensor = infer_srv.generate_srv_tensors(pipeline, prompt, samples, num_of_qubits, num_of_qubits, max_gates, g, no_bar=False) "
- ]
- },
- {
- "cell_type": "markdown",
- "id": "4d07a692-8c6f-4c31-8fd2-d8cd89edecf0",
- "metadata": {},
- "source": [
- "Check how many distinct tensors we got:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "72c575b4-ebc5-46b7-b588-55f3340df04d",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "64"
- ]
- },
- "execution_count": null,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "out_tensor.unique(dim=0).shape[0]"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "14e5728b-22d5-480f-bde8-5add698611b4",
- "metadata": {},
- "source": [
- "Let's look what is generated. Note, 3 is the padding token (or empty action)."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "9116f267-9f6b-4f80-a0f6-ef38fb694a28",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "tensor([[[ 0, 0, 2, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
- " [ 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
- " [ 0, 0, 0, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
- " [ 0, -2, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
- " [ 1, 2, -2, -2, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]],\n",
- "\n",
- " [[-2, 1, 0, -2, -2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
- " [ 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
- " [ 2, 0, 0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
- " [ 0, 0, 1, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
- " [ 0, 0, 0, 2, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]]])"
- ]
- },
- "execution_count": null,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "out_tensor[:2]"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "d4a3c5f4-3333-49a0-a965-2c392dcc2002",
- "metadata": {},
- "source": [
- "## Convert to qiskit circuit "
- ]
- },
- {
- "cell_type": "markdown",
- "id": "2f71bbfa-8fd0-4a0c-ba9c-1a4f09da527d",
- "metadata": {},
- "source": [
- "To get a qiskit circuit we need to do: \n",
- "\n",
- "- apply cosine similarity to go from embeddings to token matrices (the function `infer_srv.generate_srv_tensors` did this already)\n",
- "- parse token matrix to qiskit and filter out error circuits\n",
- "- calculate SRV and plot circuits"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "79d9c737-6773-47af-8d3a-7e1ec9ec704b",
- "metadata": {},
- "outputs": [],
- "source": [
- "qc_list, error_cnt, srv_list = infer_srv.convert_tensors_to_srvs(out_tensor, pipeline.gate_pool)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "b033c5a9-d77a-467e-8474-537ace2d9b4b",
- "metadata": {},
- "source": [
- "Generated error circuits (token matrices that don't correspond to circuits):"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "6f9dd437-cac3-4fa7-bdfe-0046c6a727e2",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "0"
- ]
- },
- "execution_count": null,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "error_cnt"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "8a93fdc6-9e12-4e07-b25a-ca3f21a5cda4",
- "metadata": {},
- "source": [
- "What SRVs did we get:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "363350c2-9b04-4231-a898-d82dff6382ae",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[[2, 1, 2, 1, 2], [2, 1, 2, 1, 2], [2, 1, 2, 1, 2], [2, 1, 2, 1, 2]]"
- ]
- },
- "execution_count": null,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "srv_list[:4]"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "995435c3-ec71-4b2d-84cc-8aa1ededd546",
- "metadata": {},
- "source": [
- "That is an accuracy of:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "f15f0507-e9c9-4176-8acf-27c2e063694f",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "0.9375"
- ]
- },
- "execution_count": null,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "sum(srv==x for x in srv_list)/len(srv_list)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "782ffa59-6386-49d1-9014-2bf093674e43",
- "metadata": {},
- "source": [
- "Finally plot some of the circuits:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "fc0308c1-1a0b-4be1-90b1-2811a13caee4",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "image/png": "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166tG264ociXdNeuXQvFYDKZFBsbqw0bNpTa7tGjR7V48WJJsiTZl156SZL5rpPLh1H26tVLGzduLHIXxJVGjhypLVu26IEHHtDu3bu1e/duy76C97nA1KlTNW3aNMXFxVkWJizJ4sWLdfToUWVmZkoyL7pXEOu9995ruQtmw4YN6t27t6ZMmaKpU6eW2F5F31MAJSMPji53jOTByiEPkgcBAADgnLgeHF3uGLkerByuB6vJ9aCBCtu3b58xduxYIzIy0vDy8jICAgKMbt26GW+++aaRlZVlOS4nJ8eYNm2aERUVZXh6ehoNGzY0Jk2aVOgYwzCMiIgIY8CAAUXOExcXZ0gyli5dWmwcf/zxhzF06FCjdu3ahre3txEREWEMGzbMWLduXaHjjh49aowaNcoICQkxvL29jejoaGP8+PFGdna25Zj58+cb0dHRhru7uyHJiIuLMwzDMN58801DkrFq1apS35PDhw8bkowFCxYYhmEYp0+fNsaPH280b97cqFGjhhEUFGRcd911xueff15qO4ZhGJ988okxYsQIIyYmxvD19TV8fHyMa665xnjuueeM8+fPFzpWkjF+/Phi25k6dWqhn6XA119/bUgywsPDjby8vDLjqQqxsbFGbGxske0LFiwwJJX4KHh/DcMw0tLSDEnGiBEjyjxfwb+l4h5XxtGhQwcjLCyszDYjIiJKbDMiIqLQsRMnTjRMJpOxe/fuMtuNjY0tsd3LP8uVK1cakox333231PYq8p4WmDJliiHJSE5OLjNe4GpEHiyKPFgx5MGSkQcBAADgzLgeLIrrwYrherBk1eF60GQYZZTrcFUbNmyYjhw5oi1btjg6lGqlV69eysnJ0YoVK+Tl5aXAwMAKt/HNN99o4MCB2rZtm6699lqbxJWWlqZatWpp9uzZGj9+vE3alKTOnTsrIiJCS5cutVmbTz/9tD755BMdOHBA3t7eNmkzKytL6enp+te//qVZs2YpOTlZderUsUnbAFwTebBqkAetRx4EUJ0V3AlMd0XZjhw5oqioKMvzpUuX6o477nBgRCjQtm1bbdu2TZJ5+rCvvvrKwREBqCiuB6sG14PWc+brQdZwQokMw9CGDRssw/ZgWz/99JNCQkJ09913V+r1cXFxGjFihM2+VCXzMM369etr7NixNmvz/Pnz2rZtm1588UWbtSmZf/7nn3/e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rSeffFKDBw8uNGVeVlaWfvvtN/n6+hY6V3Z2tu688069/PLLlm2bNm3S+++/r48//rjQwt29e/fWzTffrKVLl+ruu+9WamqqJkyYoM6dO2vDhg2F1lcqiLNv376qX7++zp07Z9MpBdesWaOLFy/q22+/VZ06dcr9ui5duig2Nlbz58/Xl19+qT59+qh79+4aOHCgGjVqVOxrUlNTdfr0aWVlZWnz5s2aNm2avL29NXDgQMsxI0eO1IwZM/Txxx9rxowZlu1LliyRj4+Pbr/99jJja9eunY4ePVrmcVOmTNHUqVPL/mEBAACcAAUnAAAAAEC1Fx0drXvvvVfvvfeennnmGYWHhxc5Ji0tTZJKnYarYN/58+etjik/P19ffPGFBg0aVKjYVKBg2rJvvvlGnTt3thSbJPPIoYceekiTJk3Srl271KpVK8u+++67r0ixqcAjjzxS6PnSpUsVFBSkvn376vTp05btHTp0kL+/v+Li4nT33XdrzZo1SktL0zPPPFOo2HR5nFUlODhYkrRixQrdf//9xa5HVRyTyaTvvvtOr776qj766CN98skn+uSTTzR+/HgNGzZM8+bNs7Rd4MYbbyz0PDIyUh999JEaNGhg2XbNNdeoXbt2+vTTTy0Fp4yMDH355ZcaOHCgAgMDy4zt448/1oULF8o8Ljo6uhw/KQAAgHOg4AQAAAAAuCpMnjxZixcv1iuvvFLsKKeCYlJB4ak45SlKlVdycrLOnz9fqFhUnKNHj+q6664rsr1FixaW/Ze3ERUVVWw7Hh4ehQonkrR//36lpqaqbt26xb7m1KlTki6tYVRWrFVh+PDhev/99/Xggw/qmWee0Q033KChQ4fqjjvuKLP45O3treeee07PPfecEhMTtXHjRs2ZM0eff/65PD099dFHHxU6/u2331bTpk2Vmpqq//u//9P3338vb2/vIu2OHDlSTz31lH766Sd17dpVX3zxhTIzM8s1nZ5U/LpSAAAAro6CEwAAAADgqhAdHa177rnHMsrpSgUFnO3bt6tt27bFtrF9+3ZJ5lEuzqqk0U3e3t5FCjT5+fmqW7euPv7442JfExISYvP4KsrX11fff/+94uLi9PXXX2vVqlX67LPP1KdPH61evVru7u7laic8PFwjRozQ7bffrpYtW+rzzz/XwoULC63t1LlzZ8tosyFDhqh79+66++67tXfv3kLrUd111116+umntWTJEnXt2lVLlixRzZo1dcstt5QrluTk5HKt4eTv72+TdbAAAADsoXzj0AEAAAAAqAYmT56s3NxczZw5s8i+/v37y93dXYsXLy7x9R9++KE8PDx08803Wx1LSEiIAgMDtXPnzlKPi4iI0N69e4ts37Nnj2V/ZcXExOjMmTPq1q2bbrzxxiKPgjWrYmJiJKnMWKtqej03NzfdcMMNev3117Vr1y7985//1Pr16xUXF1fhtjw9PdW6dWvl5OQUmkbwSu7u7nr55Zd14sQJvfXWW4X21atXT71799bSpUt18uRJrVmzRnfccYe8vLzKFUOnTp0UHh5e5uPVV1+t8M8HAADgKBScAAAAAABXjZiYGN1zzz2aN2+ekpKSCu1r2LCh7r//fq1du1b//ve/i7z23Xff1fr16zVmzJgiU9NVhpubm4YMGaKVK1fqt99+K7LfMAxJ0i233KItW7bo559/tuzLyMjQe++9p8jISKtGWw0bNkx5eXmaPn16kX25ublKSUmRJN10000KCAjQyy+/rKysrGLjlKQaNWooNTW10vEU5+zZs0W2FYxAy87OLvF1+/fv119//VVke0pKin7++WfVrFmzzBFcvXr1UufOnTV79uwiP/fIkSN16tQpjRs3Tjk5OeWeTk8yr+G0Zs2aMh+jRo0qd5sAAACOxpR6AAAAAICrynPPPafFixdr7969atmyZaF9b7zxhvbs2aNHH31Uq1atsoxk+u6777RixQrFxsbqtddes1ksM2bM0OrVqxUbG6uHHnpILVq0UGJiopYuXapNmzYpODhYzzzzjD755BP1799fEyZMUK1atbRo0SIdPnxY//nPf8pcx6g0sbGxGjdunF5++WXFx8frpptukqenp/bv36+lS5dqzpw5uuOOOxQYGKg33nhDDz74oDp16qS7775bNWvW1LZt25SZmalFixZJkjp06KDPPvtMTz75pDp16iR/f38NGjRIkrl4s3HjxkIFqvJ48cUX9f3332vAgAGKiIjQqVOn9M4776hBgwbq3r17ia/btm2b7r77bvXv3189evRQrVq1lJCQoEWLFunEiROaPXt2uabj+/vf/64777xTCxcu1MMPP2zZfvvtt+vRRx/VihUr1LBhQ/Xs2bPcP5O91nB66623lJKSohMnTkiSVq5cqePHj0uSHn/8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788LL7wQ7DCCLsoOl2eYnwdehsvGBTui0BEdAVdNMj8PvGwWwe5tLBYzE+7NT6Cui0uX2WxwaRBnygVLQgxc98XyZw+83LwWtZjZXzdfZH5/4GWYkBq8WKwWmDbC/Dzwskkoi4iInKsXjTMSEREREREREfGvGaPg/MGeZ705XW3vi7TD4ovgvHj/xiYiIiLiT0o4iYiIiIiIiIj4iMUCt88ya+24W8/pR682/261mGTTzdNh2vCAhSgiIiLiFyqpJyIiIiIiIiLiQzYrfOVi2HvUrE9UcApcQMMXZfai7OBymfJxX8qAlKSghisiIiLiE0o4iYiIiIiIiIj4mMUCGUPMz8kK2HcUqurg7V1w03SYMNSseyUiIiLSUyjhJCIiIiIiIiLiR/3iYeYY8/u7OXDBiODGIyIiIuIPWsNJREREREREREREREREvKIZTiIiIiIiIiIhqE9seB5bJFTpNSUiIuJfSjiJiIiIiIiIhKD75gY7ApGeRa8pERER/1JJPREREREREREREREREfGKEk4iIiIiIiIiIiIiIiLiFSWcRERERERERERERERExCtKOImIiIiIiIiIiIiIiIhX7MEOQEREREQC69l1cLIiMOfqFw93zQ7MuUREREREREQkeJRwcmPpG3Cm2v/nSYyBpdf5/zwiIiIiLZ2sgJLyYEchIiIiIiIiIj2JEk5unKkGpysw5xEREREREREREREREQl3WsNJREREREREREREREREvKKEk4iIiIiIiIiIiIiIiHhFCScRERERERERERERERHxihJOIiIiIiIiIiIiIiIi4hV7sAMQERER8aWdO3fys5/9jHXr1uFyuZgzZw5PP/00Y8aMYf78+bzyyivBDlFERETEb373PpRV+efYfWLhvrn+ObaIiK+oTygSPEo4iYiISI+xZs0aFixYwLBhw3jwwQeJiYnh+eefZ968eVRUVJCZmRnsED1yucBiCXYUrb234g4qThVy/X+ubrPtv79i4apv/YVxs74ShMhERETEk7IqOFkR7ChERIIjnPuEIj2BEk4iIiLSI5w4cYLFixczdepUVq9eTUxMDABLlixhxIgRACHXuahtgI37YfMBOFUJ0REwbTh86XzonxDs6EREQofTBVW15n0zyg6xkWBVgXgRERFpIRz7hCI9TdgknPLz87nrrrsoKipi3Lhx7N+/n3fe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58WbHxGRQImLMoMQd+TDplw4XOp534GJZj2k6emmzGegjBsEP14Amw/AloNQ4WFGk8Vi1j+fNcaUK1U1jsDQZWgJCpvVZKQnpUJZlZkmvnIjfP0SGDYAkvw4/bKnsFjMVNLh/eGGC+Bk5RejnmxmWn3/eL2RiohI9yTFwNWTYO4E2F0Inx6Bnflm2/ghMLiPmbXcPyGoYYqIiIiIiIiP2W1mfd4LRkDhKfjoEJw4C3uPmu3Thpv+4Ojk4F177BsH8zPhqomwq8DEVlUHDQ6IjTLJsKyRZj8JLCWcJOj6xEJDX/P74L5KNnVHbJT5ERER8SWbFSanwbD+zQmnm6abz24RERERERHp2YaeZ37KqmDpG+a+a6aETp/QboOpw82PhAYVKxMRERERERERERERERGvKOEkIiIiIiIiIiIiIiIiXlFJPTcSY+CMhwWyfX0eERERERERERERERGRcKeEkxtLrwt2BCIiIiIiIiIiIiIiIuFDJfVERERERERERERERETEK0o4iYiIiIiIiIiIiIiIiFeUcBIRERERERERERERERGvKOEkIiIiIiIiIiIiIiIiXlHCSURERERERERERERERLyihJOIiIiIiIiIiIiIiIh4RQknERERERERERERERER8YoSTiIiIiIiIiIiIiIiIuIVe7ADEBEREREREZG2fvc+lFX559h9YuG+uf45toiIiIj0Tko4iYiIiIiIiISgsio4WRHsKEREREREOkcl9URERERERERERERERMQrSjiJiIiIiIiIiIiIiIiIV1RSz42lb8CZav+fJzEGll7n//OIiIiIiIiIiIiIiIj4kxJObpypBqcrMOcREREREREREREREREJdyqpJyIiIhJEJ87AwePNt4tOg9MZvHhERERERERERLpDM5xEREREAqzBATvzYdMB+PxE623ProPz4uDi0ZA1EuKjgxKiiEhIcTghp7A5Ke9wgk3DJ0VEREREQooSTiIiIiIB9OlheP0TqKjxvM+pSvjHDnh7F1w6Fq7JBKsurIpIL3XiLDy9Bipqoa7B3Pert+CbcyA5MbixiYiIiIhIM126EBEREQmQtXvgfz5sP9nUksMJH+yFP2+Aeod/YxMRac+yZcu47LLL+M53vsPXv/71gJ3X5YI/rTOJ+MZkE5jbf1pntouIiIiISGhQwklEREQkALbkwVvb3W+z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VTQlnJyu5tJ4npypbn+fEQO7HoO3yqqafx80aBB9YgN6+h5BbSgiIiIi4j+B6BN2pz/YU6g/473e0oZhkXBKSkpi48aNgKm5mZKSwtixY/nwww959dVXqa+vZ8qUKUFZc2RoxmyGZsxuup11w9KAxyAiIiKwfPlyIiIiWLVqFWvXrmXGjBm88cYbPPTQQ+Tl5XlcOPb8889n8uTJLFmyhA8++MDncU0bAW9th1ofTUic5f6/ISIiIiIi0quFap9QpDcJi4RTS7m5uaSnp2O1Wpk5cyYzZ84MdkgiIiISAuLj41mxYgUrVqxodf/u3buZOHEiVqvV42Pr6+vJzc31S1zREXBhOmzyweEHJMBoVUwTERERERFpI1T7hCK9iedXWYgaN24c2dnZwQ5DREREwkBZWRmFhYWtSieUl5fz/PPPU1ZWhsvlYteuXfzyl7/kqquu8lscs8aAm7Vru3Ucqw+OIyIiIiIi0huESp9QpLcIuxlOIiIiIp2Vk5MDtF4c1mKx8Ne//pXvf//71NXVMXDgQK6//np+8Ytf+C2OlCS44QL4v23ut5+tgZ+/3vy7O5NS4RKV0xMR6VX8Wdu/p64bICIi0lKo9AlFegslnERERKTHcte5SExMZPXq1QGPZdYYqK6Df+5su83pgvJqz48dNwi+cjG0UwFCRER6oPvmBjsCERGR8BZKfUKR3kCXLURERKTHuueee3C5XGRlZQU7FADmToBbZ0BsZOf2t1rgkrFw12yI1DAhERERERGRLgm1PqFIT6dLFyIiIiIBND0dMtNgRz58mAtHTrbdp08sXDwaskZCYkzgYxQRERERERER6SolnEREREQCLNJuEk/T06G4DE5XQk29uT8uCtL6gU3z0EVEREREREQkjCjhJCIiIhJEg/qYHxERERERERGRcKaxsyIiIiIiIiIiIiIiIuIVzXByIzEGzlQH5jwiIiIiIiIiIiIiIiLhTgknN5ZeF+wIREREREREREREREREwodK6omIiIiIiIiIiIiIiIhXlHASERERERERERERERERryjhJCIiIiIiIiIiIiIiIl5RwklERERERERERERERES8ooSTiIiIiIiIiIiIiIiIeEUJJxEREREREREREREREfGKPdgBiIiIiIiIiIQDhxP2FMG2z+FMNTQ4YeUGuGgkjBsE1l48pNPhhJwC+PgwnK0x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U4PTtPVlY+Gikb177StPLhtnyi9tPgCbDkBNXdsEnt0GWSPNrKjkxODFKiIiIiIiIiIiIr2bEk4iblitMH6I+Sk9a5JOVbUmaRITaWYzpfYLdpShL7WfmXGzaCrsLoKKGqhzQEwEDEyEsSmmrcWzvnEwPxOummgWmz9VATX1EBUBSTGmBGGU3slFREREREREREQkyHSZUqQD/RO0NpO3YqNgenqwowhvWmxeREREREREREREQpnmFoiIiIiIiIiIiIiIiIhXlHASERERERERERERERERr6iknhuJMXCmOjDnERH/0utZRAKtX3zPPl+gBPL/1VPbUEREREREAk99Ge+pDcOXxeVyuYIdhIiIiIiIiIiIiIiIiIQvldQTERERERERERERERERryjhJCIiIiIiIiIiIiIiIl5RwklERERERERERERERES8ooSTiIiIiIiIiIiIiIiIeEUJJxEREREREREREREREfGKEk4iIiIiIiIiIiIiIiLiFSWcRERERERERERERERExCtKOImIiIiIiIiIiIiIiIhXlHASERERERERERERERERryjhJCIiIiIiIiIiIiIiIl5RwklERERERERERERERES8ooSTiIiIiIiIiIiIiIiIeEUJJxEREREREREREREREfGKEk4iIiIiIiIiIiIiIiLiFSWcRERERERERERERERExCtKOImIiIiIiIiIiIiIiIhXlHASERERERERERERERERryjhJCIiIiIiIiIiIiIiIl5RwklERERERERERERERES88v8BE7RhioFQHekAAAAASUVORK5CYII=",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "fig, axs = plt.subplots(2, 4, figsize=(18,5), constrained_layout=True)\n",
- "for qc,is_srv,ax in zip(qc_list, srv_list, axs.flatten()): \n",
- " is_srv = [int(x) for x in is_srv]\n",
- " qc.draw(\"mpl\", plot_barriers=False, ax=ax, style = \"clifford\")\n",
- " ax.set_title(f\"{'Correct' if is_srv==srv else 'NOT correct'}, is SRV = {is_srv}\")\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "0244290d-5c57-4b70-b670-a839876a9ccf",
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "52132fa0-9208-442d-a31d-af65bcfba714",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "genQC Version 0.1.0\n"
- ]
- }
- ],
- "source": [
- "import genQC\n",
- "print(\"genQC Version\", genQC.__version__)"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "python3",
- "language": "python",
- "name": "python3"
- },
- "widgets": {
- "application/vnd.jupyter.widget-state+json": {
- "state": {},
- "version_major": 2,
- "version_minor": 0
- }
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/src/examples/2_unitary_compilation.ipynb b/src/examples/2_unitary_compilation.ipynb
deleted file mode 100644
index e6d40fe..0000000
--- a/src/examples/2_unitary_compilation.ipynb
+++ /dev/null
@@ -1,653 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "id": "69a855f1-55dd-482e-94f2-9ad02804be4d",
- "metadata": {},
- "source": [
- "# Compile unitaries"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "e41e2465-49d8-46b8-b046-6ae1becfb268",
- "metadata": {},
- "source": [
- "In this notebook we want use the unitary compilation model."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "3bde494e-9091-41a4-a601-bbcf9712c564",
- "metadata": {},
- "outputs": [],
- "source": [
- "from genQC.imports import *\n",
- "from genQC.pipeline.diffusion_pipeline import DiffusionPipeline\n",
- "from qiskit import QuantumCircuit\n",
- "from genQC.inference.infer_compilation import generate_comp_tensors, get_gate_and_U_acc\n",
- "from genQC.printing import display_colums\n",
- "from genQC.platform.simulation.qcircuit_sim import instruction_name_to_qiskit_gate\n",
- "import genQC.platform.qcircuit_dataset_construction as data_const\n",
- "import qiskit.quantum_info as qi\n",
- "import genQC.util as util\n",
- "import ast"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "029be4f3-0d9a-4d0a-93d9-2338fda7a983",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[INFO]: Cuda device has a capability of 8.6 (>= 8), allowing tf32 matmul.\n"
- ]
- }
- ],
- "source": [
- "device = util.infer_torch_device() # use cuda if we can\n",
- "util.MemoryCleaner.purge_mem() # clean existing memory alloc"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "3d4ad484-835f-45fd-8772-212ec7ff00c5",
- "metadata": {},
- "outputs": [],
- "source": [
- "def str_cond_to_gate_indices(y): # helper function, used to check if only allowed gates were used by the model!\n",
- " assert y[:15] == \"Compile using: \"\n",
- " c = ast.literal_eval(y[15:]) \n",
- " gate_classes = data_const.gate_pool_to_gate_classes([instruction_name_to_qiskit_gate(gate) for gate in pipeline.gate_pool])\n",
- " gate_clrs = [0] + [gate_classes[ic] for ic in c] # 0 is empty, always allowed!\n",
- " return gate_clrs"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "f77a3020-247c-4ac0-aaf1-ee5c371b5f06",
- "metadata": {},
- "source": [
- "## Setup and load"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "742ae430-46f2-4099-ac8f-f422a4ddc1dc",
- "metadata": {},
- "source": [
- "Load the pre-trained model directly from [Hugging Face: Floki00/qc_unitary_3qubit](https://huggingface.co/Floki00/qc_unitary_3qubit)."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "e5d60c23-9514-4432-bc82-622c088fced6",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "95b3e6a8ad944d45b38703e92799bc84",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "Fetching 2 files: 0%| | 0/2 [00:00, ?it/s]"
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[INFO]: `genQC.models.unet_qc.QC_Compilation_UNet` instantiated from given config on cuda.\n",
- "[INFO]: `genQC.models.frozen_open_clip.CachedFrozenOpenCLIPEmbedder` instantiated from given config on cuda.\n",
- "[INFO]: `genQC.models.frozen_open_clip.CachedFrozenOpenCLIPEmbedder`. No save_path` provided. No state dict loaded.\n"
- ]
- }
- ],
- "source": [
- "pipeline = DiffusionPipeline.from_pretrained(\"Floki00/qc_unitary_3qubit\", device)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "431d3e29-f121-4c61-95bc-bfd7960a4870",
- "metadata": {},
- "source": [
- "Set 20 sample steps and use rescaled guidance-formula."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "96702fba-5a10-44e6-bef9-634d9e41a1af",
- "metadata": {},
- "outputs": [],
- "source": [
- "pipeline.guidance_sample_mode = \"rescaled\"\n",
- "pipeline.scheduler.set_timesteps(20) \n",
- "g = 10"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "2b557151-ba81-423e-8f28-8e35a781b92b",
- "metadata": {},
- "source": [
- "The model was trained with a gate pool of:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "e6a15d3a-b658-429f-99d5-2bcbdcb955cf",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "['h', 'cx', 'z', 'x', 'ccx', 'swap']"
- ]
- },
- "execution_count": null,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "pipeline.gate_pool"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "65acff8f-8486-42c9-8e78-b44f31de568b",
- "metadata": {},
- "source": [
- "## Compile a unitary"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "37444b73-0b79-4fd3-9e91-2dd521f428d3",
- "metadata": {},
- "source": [
- "Compile a given unitary $U$. Note, there has to be a solution with the `pipeline.gate_pool` in order to find the exact solution."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "0a78b750-181c-4060-bbce-d829c190ffbb",
- "metadata": {},
- "outputs": [],
- "source": [
- "def compile_and_plot(U, prompt):\n",
- " U_r, U_i = torch.Tensor(np.real(U)), torch.Tensor(np.imag(U))\n",
- " U_tensor = torch.stack([U_r, U_i], dim=0)\n",
- " \n",
- " out_tensor = generate_comp_tensors(pipeline, prompt, U_tensor, samples, num_of_qubits, num_of_qubits, max_gates, g, unique=True)\n",
- " _, _, _, _, _, comb_corr_qc, _, _, _ = get_gate_and_U_acc(out_tensor, str_cond_to_gate_indices(prompt), U_tensor, pipeline.gate_pool, num_of_qubits, max_gates)\n",
- " comb_corr_qc = sorted(comb_corr_qc, key=lambda x: len(x.data)) #sort to get the shortest solutions\n",
- "\n",
- " fig, axs = plt.subplots(1,4, figsize=(18,5), constrained_layout=True)\n",
- " axs[0].set_title(f\"{prompt}\")\n",
- " for qc,ax in zip(comb_corr_qc, axs.flatten()): \n",
- " qc.draw(\"mpl\", plot_barriers=False, ax=ax)\n",
- " plt.show()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "b49dc061-6ad1-4e64-ab63-3c2a6b7c092e",
- "metadata": {},
- "outputs": [],
- "source": [
- "samples = 512\n",
- "num_of_qubits = 3\n",
- "max_gates = 12"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "7f3744d1-fa19-4403-bd0c-d0dadac805a3",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "\"Compile using: ['h', 'cx', 'z', 'x', 'ccx', 'swap']\""
- ]
- },
- "execution_count": null,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "prompt = \"Compile using: ['h', 'cx', 'z', 'x', 'ccx', 'swap']\" # model was trained with phrases like this, allow full gate set\n",
- "prompt"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "6f38e035-f39c-4abd-bdc9-6386305cd4ca",
- "metadata": {},
- "source": [
- "#### Exercise 1"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "204dbc2d-b69f-45bd-9a48-d10e4d6bde84",
- "metadata": {},
- "source": [
- "Inspired from [(quantumcomputing.stackexchange.com/questions/13821/generate-a-3-qubit-swap-unitary-in-terms-of-elementary-gates/13826)](https://quantumcomputing.stackexchange.com/questions/13821/generate-a-3-qubit-swap-unitary-in-terms-of-elementary-gates/13826). Note, this unitary WAS in the training set."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "5b826437-bcc8-40fd-9c99-f69082fe2efe",
- "metadata": {},
- "outputs": [],
- "source": [
- "U = np.matrix([[1,0,0,0,0,0,0,0],\n",
- " [0,1,0,0,0,0,0,0],\n",
- " [0,0,1,0,0,0,0,0],\n",
- " [0,0,0,0,1,0,0,0],\n",
- " [0,0,0,1,0,0,0,0],\n",
- " [0,0,0,0,0,1,0,0],\n",
- " [0,0,0,0,0,0,1,0],\n",
- " [0,0,0,0,0,0,0,1]], dtype=np.complex128) \n",
- "\n",
- "assert np.allclose(U.H@U, np.identity(2**num_of_qubits)) and np.allclose(U@U.H, np.identity(2**num_of_qubits)) #check if unitary"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "16dd2cd2-2ff5-4e7f-94d2-be51713fa322",
- "metadata": {},
- "source": [
- "Plot correct (exact) compiled circuits:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "e7b35524-f303-4744-b948-1c7dac4fa2e7",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "image/png": "iVBORw0KGgoAAAANSUhEUgAABxMAAAETCAYAAAD9HCj7AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAACRD0lEQVR4nOzddXgU1xoG8Hd34x5IIGiQIEmA4E5wl0JxSrECxWl7C21xa0sVKBRairu7u7tLgiQkuCXEfXfn/pEmJbBAZrO7s7N5f8/T515298x8Sc7OmTnfEYUgCAKIiIiIiIiIiIiIiIiIiN6glDoAIiIiIiIiIiIiIiIiIjJPTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmSkyhUGDSpEmZ/16yZAkUCgXCw8Mli+l93oxXan369IFCoYBCoUC5cuXe+9mM3+2FCxdMFB0RAUB0dHTm91ShUODXX3+VOiQiIiIiIiIiIiIiyiaLSCaGhobi888/R4kSJWBnZwcXFxfUqVMHs2bNQlJSktThkZF5eHhg+fLlmD59epbXixUrpnfis0GDBujTp49eZSdNmoRixYrpVTY7cvJz9enTBw0aNBBV5siRIzlKcCsUCixZskR0uZz8HnMasyHIPf6ceLOeOTo6Yvny5ZgxY4Z0QRERERERERERERGRXqykDiCndu7cic6dO8PW1ha9evVCuXLlkJqaihMnTmDUqFG4efMm5s+fL3WY75SUlAQrK/n8GcwxXkdHR/Ts2VPqMIjoHaytrdGzZ0+Eh4fjyy+/lDocIiIiIiIiIiIiIhLBvLJCIoWFhaFbt27w9vbGoUOHUKBAgcz3hg4dipCQEOzcuVPCCD/Mzs5O6hBEkVu8REREREREREREREREpD9ZL3P6888/Iz4+HgsXLsySSMzg4+ODkSNHZv5brVZj6tSpKFmyJGxtbVGsWDGMGTMGKSkpWcoVK1YMbdq0wZEjR1C1alXY29ujfPnyOHLkCABg06ZNKF++POzs7FClShVcvnw5S/k+ffrAyckJ9+7dQ/PmzeHo6IiC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K1fGL7/8gq+//tpIkRIRERERERERERGRIcl+ZiLJR1JSEuzt7bO8NnPmTCiVSgQGBup93IcPH8LT0xP+/v64ceNGTsPU6dixYyhUqJBZLuV4+PBh1KpVy2SJxIxzjh8/3qSJxNjYWFy9evWt/T7lQu7x50RMTIyo5Z2JiIiIiIiIiIiIyHwwmUgm8/PPP+PixYto2LAhrKyssHv3buzevRsDBw5EkSJF9Drm6NGj0bNnTwCAk5OTIcPNonXr1iZN1okxdOhQk8+WPH/+vEnPB6TvH5qSkmLy8xqK3OPPCScnJ+zfvz/z36VLl5YwGiIiIiIiIiIiIiISg8lEMpnatWtj//79mDp1KuLj41G0aFFMmjQJY8eO1fuYfn5+8PPzM2CURGRoVlZWaNKkidRhEBEREREREREREZEeuGciEREREREREREREREREemklDoAIiIiIiIiIiIiIiIiIjJPTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5WUgdAROYrMjoZF4MicDEoErfCopGUooFCATg7WKN8KXdU8fNAxbJ54eRgLXWoRESUQ1qtgNCHsZnX/ScvE5GSqoGNtRL58tijil9eVPHzQJlirlCpOB6NCADiE9Nw5VYkLgZF4PrdKMQlpkEQAHtbFcoUc0UVPw9U8fOAh7ud1KES0QcIgoBHzxMy28HwJ3FITtHASqWEu4sNKpZNbwfLl3KHjbVK6nBJAoIg4MmLxMw6cu9xbGYdcXO2QUCZPKji54EKpfPA1oZ1hIjkJyIqox8wArfDYzL7AV0crVG+VPo1rmKZPHBkP2Cu9SomJbOO3AqLQWKyOrOvuJxPel9xJV/2FVsqJhOJKIu0NC22HL6PuWuDceT80w9+3kqlQPtG3hjazQ/1q3pBoVCYIEoiIjKU55FJWLDxNv7ecAsPnyV88PP58tih/8dl8HnnsihawMkEERKZF0EQcOziM8xdG4xNB8Kh1ggfLBNYxQtDu/mifSNvJiGIzExcQipW7AjF3LXBuBES9cHPO9pb4dM2PhjS1RflS+cxQYQktYTENKzaFYq564Jx5darD37ewc4KPVqVwJCuvqjk62GCCImI9JeapsHmg+n9gMcuPvvg561UCnRsWgxDuviiXhX2A+YGaWlabDuSXkcOnftwX7FKpcBHDbwxpKsvGtUowDpiQRSCIHz46ZeILJ4gCFi96x6+/u0snkYk6XWMcj7u+Gt8HdSplN/A0RERkaHFJaTi25kX8M/G20hTa0WXVyoV6NGqBGaMqslZV5RrnLryHIOnncK1Ox/uTNbFy8Mev3xVHZ+0LsmHaiKJqdVa/Lz4Gn5ceBXxiWq9jtGkZkHMG1cHPkVdDBwdmQONRouZK25i6t+XEROfptcxAqt44e8JdVC2uJthgyMiyiFBELBiRwhG/X4ezyP16wcMKJMHf42rg5oB+QwcHZkDQRCwbm8Y/vfrWTx+kajXMfxKumHe2NoIrFrAwNGRFJhMJCI8i0jEoKknsfXwgxwfS6EAvujpj2nD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dlaoWDYPOjYphj4flUYeV1sTR0pE2fHgaTzmb7iFXccf4UZIFNLUWp2fK5TPAbUC8uGzDmXQrHahXDe6Pje3g4+fJ2D+htvYefwhrt99hdQ03XWkgKc9albIh37tS6Nl3cJQqTiGn4jM36uYFCzZegcbD4Tjyq1X7+wHzOtmi2r+nujV1gcfNynGfsBcJDo2Bcu2h2D9vjBcvhWJhCTddcTdxQbVynnik1Yl0aV5cdjZWpk4UjImJhOJ3iE3NxCv02i0uBUWg9vhMeg55ggAYO3PjVDOxx3FCjnluo7k17GOEJEliktIxZVbr/A0IhF9xh8DAOyc0wyVyuaFm0vuToTwuk+6CIKA+0/icf1uFLqOPgQAWPFDA5Qp5oqyxV3ZkUwkMympGly/+wphj+PRe9xRAMCm35ugkm/eXD8Tn+1gutQ0DW7cjcK9R3Ho9W8d2fhbY1QsmxcFPA27LzURkamp1VrcCovGnfuxWfoBK5R2R9EC7AcE2A5qNFrcDo/BrbD/+orX/NQQ5Uq5o3gh51xdRywdU8NE9F4qlRL+Pu7w93GHzcT0EUdtGxSVOCoiIjIWZ0cb1KviBQAYOOUkAHD5aqL3UCgUKFbIGcUKOcPGOv1e6eMmxaQNioj0ZmujQlV/T1T198SAyScAAC3qFpY4KjInNtYqVPbzQGU/D/T/t460rFdE4qiIiAzDykqJcqXyoFypPOwHJJ1UKiX8SrrDr+R/fcXtGnpLHBWZAofJEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTkwmEhEREREREREREREREZFOTCYSERERERERERERERERkU5MJhIRERERERERERERERGRTlZSB0DS02i0ePoyCUkpaiiVCjg7WMMzjx0UCoXUoRGRTGi1Ap6+TERishoKhQJODlbIn9ee1xGSBUEQ8CwiCQlJagiCAEd7KxTwdGD9JXqPpGQ1nkcmISVVAxtrFfK62cLFyUbqsIhITxFRyYiJT4VGI8DeToUCHg6wsuLYYyLKvlcxKYiOS4FaLcDONv06Ym3N6wjJQ0xcKl7FpCBNrYWtjRJeHg6wtVFJHRYRyUhcQioiolKQmqaBrY0K+fLYw8HestJvlvXTULYIgoBjF59h88H7uBgUgcu3IpGQpM7ymfx57VHFLy+ql/NEzzY+KFnERaJoicgcCYKA8zcisH5fGC4EReBSUARiE9KyfCavmy2q+Hmgmr8HurcsCX8fd4miJXrb9TuvsHr3PZy/+RKXgiPxKiYly/uuTjao7JsXVf090LVFCVTx85AoUiLzkJyixsYD4Th49gkuBkXiZmgUNBohy2dKebugiq8H6lXOjx6tSsLNxVaiaInoQx48jcfy7SE4e/0lLgZH4MmLxCzv29uqULFsXlTx80C7BkXRuEZBKJUcZENE/3n6MhErdoTg9LUXuBgUgQdPE7K8b2ujRIXSeVDF1wOt6hVBq3qFoVIxuUjmITI6GSt3huLE5ee4GBSBe4/isrxvpVKgXCl3VPXzQLPahfFRw6KwsWZykYj+ExufitW77+Hohae4GBSJO/djsryvVCrgV8INVfw80LhGAXRqWhz2dvJOxykEQRA+/DGyBCmpGizYdBtz1wYjKDRaVNkWdQpjRA8/tKhbONfM1HCrsxwAEH3yU4kjMR/8nWSVG38farUWy3eE4M81wbgYFCGqbP2qXhjWzQ8dmxbLNdcRMi9arYD1+8IwZ3UQTlx+Lqps9XKeGNrNF5+0LpmrOkFy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",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "compile_and_plot(U, prompt)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "8914651c-a30e-4a5b-aaa4-d98debd7147a",
- "metadata": {},
- "source": [
- "#### Exercise 2"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "3d0c618b-2e8d-4037-a1ec-482324112fb8",
- "metadata": {},
- "source": [
- "Inspired from [(quantumcomputing.stackexchange.com/questions/12439/procedures-and-intuition-for-designing-simple-quantum-circuits/12440)](https://quantumcomputing.stackexchange.com/questions/12439/procedures-and-intuition-for-designing-simple-quantum-circuits/12440). Note, this unitary WAS in the training set."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "1b170062-d7aa-4bef-b1d1-a68e85e682ba",
- "metadata": {},
- "outputs": [],
- "source": [
- "U = np.matrix([[1,0,0,0,0,0,0,0],\n",
- " [0,0,0,0,0,0,0,1],\n",
- " [0,1,0,0,0,0,0,0],\n",
- " [0,0,1,0,0,0,0,0],\n",
- " [0,0,0,1,0,0,0,0],\n",
- " [0,0,0,0,1,0,0,0],\n",
- " [0,0,0,0,0,1,0,0],\n",
- " [0,0,0,0,0,0,1,0]], dtype=np.complex128) \n",
- "\n",
- "assert np.allclose(U.H@U, np.identity(2**num_of_qubits)) and np.allclose(U@U.H, np.identity(2**num_of_qubits)) #check if unitary"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "dc81558e-a227-4490-94bc-044ba6dcd502",
- "metadata": {},
- "source": [
- "Plot correct (exact) compiled circuits:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "3886fbf5-3f6f-4a44-89b7-d0a332a69334",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "image/png": "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",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "compile_and_plot(U, prompt)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "46d42b16-2fcf-422a-a206-3eee667f4d4b",
- "metadata": {},
- "source": [
- "#### Exercise 3"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "c9824ae0-f3c6-4755-8a5b-aacb22678ec9",
- "metadata": {},
- "source": [
- "A randomly generated unitary (from a random circuit). This unitary WAS NOT in the training set, it is new to the model!"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "63c51c9b-638a-42c4-8029-9add147d2255",
- "metadata": {},
- "outputs": [],
- "source": [
- "U = np.matrix([[ 0.70710678, 0. , 0. , 0. , 0.70710678, 0. , 0. , 0. ],\n",
- " [ 0. , -0.70710678, 0. , 0. , 0. , -0.70710678, 0. , 0. ],\n",
- " [-0.70710678, 0. , 0. , 0. , 0.70710678, 0. , 0. , 0. ],\n",
- " [ 0. , 0.70710678, 0. , 0. , 0. , -0.70710678, 0. , 0. ],\n",
- " [ 0. , 0. , 0.70710678, 0. , 0. , 0. , 0. , 0.70710678],\n",
- " [ 0. , 0. , 0. , 0.70710678, 0. , 0. , 0.70710678, 0. ],\n",
- " [ 0. , 0. , -0.70710678, 0. , 0. , 0. , 0. , 0.70710678],\n",
- " [ 0. , 0. , 0. ,-0.70710678, 0. , 0. , 0.70710678, 0. ]], dtype=np.complex128)\n",
- "\n",
- "assert np.allclose(U.H@U, np.identity(2**num_of_qubits)) and np.allclose(U@U.H, np.identity(2**num_of_qubits)) #check if unitary"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "5b5e50fd-da8d-47fb-aabb-92044aaba2ce",
- "metadata": {},
- "source": [
- "Plot correct (exact) compiled circuits:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "d6d5023f-b3f4-4cc6-81cb-eead8ffee190",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "image/png": "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aTIBsDWSaX5sTRLqISIiItIHQRAwb1xtLJ/SAJbmMpiZ/HcSVBSBkHAFxE8yRjMTKSzNZVg5tQHmjq3NXhepKF3MBrd2dkG9KgUhERLmlUz0+WMql0kgkQioUcERt3d1QYXSdoYpmoiItCaRCHB1sUHXlqXwZYuSAAD3MvYMEomIchH2TCQiokyTSYATrQBV5kaiyplahIR6iIiIiPRpWLfy6NPeFTtP+GPhxvt47B8OuVwCRbwGpiYSKJUaVChth/EDKqNn69IwN+PX7/QULGCO8+vbw8c3DCt2PMT6A8+gVouQSgXEKdQJIaIA9OngijG9K6KqWwFDl0xERERElG/w2wwREWWJTMI3ESIiIsrfLMxlGNilHAZ2KYdH/uF46BeGbj+cxda5TVCxjD17zWWBu6s9Vv7UEPPG1ca9p2F4+fYjvp58HnsXN4dHDWfYWhvJ8BhERERERPkIzwMTERERERERZVOF0nZwsjcDADSuVRiO//6fssba0gQNqxeCW0lbAEC9KgUZJBIRUbY5mgLBKac21lvbRES5FcNEIiIiIiIiIiIiIsrzjrQydAVERLkTZ5kiIiIiIiIiIiIiIiIiolSxZyIRERERERFRblbAPm+2ZUBFC1rkjrZ47ImIiIhIDxgmEhEREREREeVi8uWLDF1CnvNgX1dDl6AVHnsiIiIi0gcOc0pEREREREREREREREREqWKYSERERERERERERERERESpYphIRERERERERERERERERKlimEhEREREREREREREREREqWKYSERERERERERERERERESpYphIRERERERERERERERERKlimEhEREREREREREREREREqWKYSERERERERERERERERESpkhm6ACIiIiIiyj0+KoEYlaGrSMlCBljLDV0FERERERERUd7DMJHyLVGlAtQaQ5cBSCUQZPxT/JxKA6hEQ1cByARAxj7cREREABKCxA6ngGgjDBMtZcDhlgwUiYiIiIiIiHSNCQblS6JKBVX/EcDHj4YuBbC2hmzjagaKn1BpgNYngYh4Q1cC2JoAJ1oxUCQiIgISeiQaY5AIJNQVo2KYSERERERERKRrPD1O+ZNaYxxBIpBQhzH0kDQiKtE4gkQgoQ5j6CFJRERERERERERERGQI7ApFREREREREREREREQ5QjloFBAWrv+G7e0gX7dS/+1Stri0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gcMtgRiVoStJyUKWUB8RERERERER6RbDRCIiIiIi0pq1nKEdERERERERUX7CYU6JiIiIiIiIiIiIiIiIKFUME4mIiIiIiIiIiIiIiIgoVQwTiYiIiIiIiIiIiIiIiChVDBOJiIiIiIiIiIiIiIiIKFUME4mIiIiIiIiIiIiIiIgoVTJDF0BEREREREREWaf85gcgJEw/jRWwh3z5Iv20RURERERERoFhIhEREREREVFuFhIGREcbugoiIiIiIsqjOMwpEREREREREREREREREaWKPROJiIiIiIiIiMjgNBoRp73f4IFvGN4GxgAA1u57gu6tSqF0MRsDV0dERET6oBw0CggL13/D9naQr1up/3ZzCYaJRERERERERERkMKERCqzf/xSLNz9AYGgcZFIBKrUIAJi16jYmL72BprUL4/t+ldCmYTFIpRxoi4iIKM8KCwfUasO0S2limEhERERERERERAax++Rz9Jl8DhKJgDhFwolDpeq/+2P/XeZ1/R0u3f6AUkWtcfqvtijmbGmIcomIiIjyJV7KRUREREREREREerdy50N8NckL8UpNUpCYFlEE4pUa+L/+iGo99uHZywg9VUlEREREDBOJiIiIiIiIiEiv9p15gW/nekP973CmnxMEoICdKQQh+XKlSoPwj/FoPvQYgkJj9VBpAlEUseZ/T6CIz3jYtcysS0REZGhRMUoc8HqJ6StuYuxv3gCAiYuvYfHmB7h85wNEMfX3aspfOMwpERERERERERHpTZxChQE/XYBak/bJSQdbUwRf6ANHzy0ICVcku0+tFvEhJBYzVt7Cyp8a5nS5AICP0Uos3eqDfWdfYO/iFjA1kaa6niiKGDvPG7tOPEfzukVQqpi1XuojIiLKrLeB0fh1zV2s2/8UCoUacrkEingNAGD/mZc4cv4VFEoNShaxwg/9K2NE9/KQydg/Lb/ikSciIiIiIiLKBqVSg7/3PEaDfocAAA37HcLfex5DqdQYuDIi47Tn1AvEZ/PvI16pwfoDzxAVo9RRVemzsTLB2TVtEfAuGl+OO51qr8NPg0Svte2MIkiMjIrH3DV3UbPnfgBAq+HHsP/sC/YyISLK57Yc9oVbpz34a88TxMapoRGRFCQCSLj973v1i7dR+G6+N2r1OoCHfmGGKpkMjGEiERERERER5RrHjx9HgwYN0KJFCwwbNgyzZs0y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",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "compile_and_plot(U, prompt)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "60d35449-67b2-4ce0-9b5f-ac60670538e5",
- "metadata": {},
- "source": [
- "## Transpile and discover"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "41449006-9c42-4109-bb54-95581f90679a",
- "metadata": {},
- "source": [
- "Set an initial circuit we want to transpile, optimize or use for discovering sub-arrangements:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "f1c82de5-3645-403b-a54e-5185860b0f7c",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "image/png": "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",
- "text/plain": [
- ""
- ]
- },
- "execution_count": null,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "qc = QuantumCircuit(3)\n",
- "qc.h(2)\n",
- "qc.cx(0,1)\n",
- "qc.cx(2,1)\n",
- "qc.h(1)\n",
- "qc.x(1)\n",
- "qc.h(1)\n",
- "qc.x(2)\n",
- "\n",
- "U = qi.Operator(qc).to_matrix() # the unitary of the circuit\n",
- "\n",
- "#-----------------------------------------\n",
- "\n",
- "fig = qc.draw(\"mpl\")\n",
- "fig"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "db8fb061-d950-4727-a1b1-56ea46e48f1b",
- "metadata": {},
- "source": [
- "We set different gate pool targets to see what the model gives us:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "8a35405a-d98d-460b-9b0c-92d167266ad0",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[(\"Compile using: ['h', 'cx', 'z', 'x', 'ccx', 'swap']\", 'all'),\n",
- " (\"Compile using: ['h', 'cx', 'z', 'ccx']\", 'no x, no swap'),\n",
- " (\"Compile using: ['h', 'cx', 'x', 'ccx']\", 'no z, no swap'),\n",
- " (\"Compile using: ['h', 'x', 'ccx']\", 'no cx, no z, no swap'),\n",
- " (\"Compile using: ['h', 'z', 'x', 'ccx']\", 'no cx, no swap')]"
- ]
- },
- "execution_count": null,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "cs_1 = f\"Compile using: {[x for x in pipeline.gate_pool]}\", \"all\"\n",
- "\n",
- "cs_2 = \"Compile using: ['h', 'cx', 'z', 'ccx']\" , \"no x, no swap\" \n",
- "cs_3 = \"Compile using: ['h', 'cx', 'x', 'ccx']\" , \"no z, no swap\" \n",
- "cs_4 = \"Compile using: ['h', 'x', 'ccx']\" , \"no cx, no z, no swap\" \n",
- "cs_5 = \"Compile using: ['h', 'z', 'x', 'ccx']\" , \"no cx, no swap\" \n",
- "\n",
- "cs = [cs_1, cs_2, cs_3, cs_4, cs_5]\n",
- "cs"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "67e3aa4e-e7e0-4f7a-9a5c-14a70dd6b77b",
- "metadata": {},
- "outputs": [],
- "source": [
- "samples = 512\n",
- "num_of_qubits = 3\n",
- "max_gates = 12"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "adfbef8e-d91a-4ed3-b756-0e76c33674fa",
- "metadata": {},
- "source": [
- "Compile with the different gate-sets and plot correct (exact) compiled circuits. Note, some of the circuits might look the same but the gate time-sequences are distinct. Qiskit reorders \"parallel\" gates to make smaller plots."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "6268abb3-1965-4a0e-b5dc-0f3c8db8264a",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "image/png": "iVBORw0KGgoAAAANSUhEUgAABxMAAAF8CAYAAAAEvpV0AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB1hElEQVR4nO3dd3hU1dbH8d9k0gsECBB6D4SiVAHpFhDpKsWrICKiItixYAHxviI2rCA2UMQCFhAQ6VVBaVJDCy1EAgQIKaTnvH9wiUQmkDIzZ2by/TyPz72css/KzOSszF5n720xDMMQAAAAAAAAAAAAAPyLl9kBAAAAAAAAAAAAAHBNFBMBAAAAAAAAAAAA2EQxEQAAAAAAAAAAAIBNFBMBAAAAAAAAAAAA2EQxEQAAAAAAAAAAAIBNFBMBAAAAAAAAAAAA2EQxEQAAAAAAAAAAAIBNFBMBAAAAAAAAAAAA2EQxEQAAAAAAAAAAAIBNFBNNZrFYNH78+Nx/z5gxQxaLRYcPHzYtpiv5d7xmGzp0qCwWiywWixo3bnzFYy++tps2bXJSdAAkKSEhIff31GKx6M033zQ7JAAAAAAAAABAAXlEMTE6OloPPPCAateuLX9/f5UqVUrt2rXTu+++q9TUVLPDg4OFhYVp5syZeu211/Jsr1mzZpELn507d9bQoUOLdO748eNVs2bNIp1bEMX5uYYOHarOnTsX6pxVq1YVq8BtsVg0Y8aMQp9XnNexuDHbg7vHXxz//pwFBQVp5syZmjx5snlBAQAAAAAAAACKxNvsAIpr4cKF6t+/v/z8/DRkyBA1btxYGRkZWrduncaMGaNdu3bp448/NjvMfKWmpsrb233eBleMNygoSHfffbfZYQDIh4+Pj+6++24dPnxYjz/+uNnhAAAAAAAAAAAKwbWqQoV06NAhDRo0SDVq1NCKFStUqVKl3H0PP/ywDhw4oIULF5oY4dX5+/ubHUKhuFu8AAAAAAAAAAAAKDq3nub09ddfV3Jysj777LM8hcSL6tatq0cffTT331lZWXrllVdUp04d+fn5qWbNmho7dqzS09PznFezZk317NlTq1atUsuWLRUQEKAmTZpo1apVkqQff/xRTZo0kb+/v1q0aKGtW7fmOX/o0KEKDg7WwYMH1a1bNwUFBaly5cqaMGGCDMPIc2xB1yBctGiROnTooKCgIIWEhKhHjx7atWvXVc8bP368LBbLZdttrc24adMmdevWTWFhYQoICFCtWrU0bNiwK8Z7sf0DBw5o6NChCg0NVenSpXXvvffq/Pnzec5NTU3VI488orCwMIWEhKh3796KjY21+Rrs2bNHR48everPVxTp6el64oknVL58eQUFBalfv346deqUQ651qa+++krXXXedAgMDVaZMGXXs2FFLliyRJK1YsUJeXl566aWX8pzz9ddfy2KxaOrUqQ6JqXPnznnWsrv0v6JMTXo1qampatCggRo0aJBnCuIzZ86oUqVKuv7665WdnW336170xx9/6NZbb1WZMmUUFBSka665Ru+++26eY/bs2aMBAwaofPnyCggIUP369fX888+bHv/+/ft1++23Kzw8XP7+/qpataoGDRqkc+fOSZJuu+02NW/ePM85vXr1ksVi0c8//5znNbBYLFq0aFFu7E899ZSaNGmi4OBglSpVSt27d9e2bdvytHVx6tXvvvtOY8eOVXh4uIKCgtS7d2/FxMQ45GcGAAAAAAAAAJjPrYuJ8+fPV+3atXX99dcX6Pjhw4frpZdeUvPmzTV58mR16tRJEydO1KBBgy479sCBA/rPf/6jXr16aeLEiTp79qx69eqlWbNm6fHHH9fdd9+tl19+WdHR0RowYIBycnLynJ+dna1bbrlFFStW1Ouvv64WLVpo3LhxGjduXKF/zpkzZ6pHjx4KDg7WpEmT9OKLL2r37t1q37693dZUO3nypLp27arDhw/r2Wef1fvvv6+77rpLGzZsKND5AwYMUFJSkiZOnKgBAwZoxowZevnll/McM3ToUL3//vu69dZbNWnSJAUEBKhHjx4224uMjNSQIUOK/XPZMnr0aG3btk3jxo3TQw89pPnz52vUqFEOudZFL7/8sgYPHiwfHx9NmDBBL7/8sqpVq6YVK1ZIkm644QaNHDlSEydO1JYtWyRJx48f1+jRo3XTTTfpwQcfdEhczz//vGbOnJnnv27dukmSKlSoYPfrBQQE6IsvvtCBAwdyC3TShZHE586d04wZM2S1Wu1+XUlaunSpOnbsqN27d+vRRx/VW2+9pS5dumjBggW5x2zfvl2tW7fWihUrdP/99+vdd99V3759NX/+fFPjz8jIULdu3bRhwwaNHj1aH374oUaMGKGDBw8qISFBktShQwdt27ZNiYmJkiTDMPTb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k8OvJ0nbt2/XhAkTdO+996pZs2YaPny4zVFw9nLx9+hKDx9cHElckAcUnB3/RU2aNNELL7ygNWvWaO3atYqNjdVHH32Uu79Dhw7KyMjQN998o9jY2NyiYceOHXOLiREREapYsWLuOd9//726dOmizz77TIMGDVLXrl110003XXbPuOjf91DDMHTgwIFC3dcAAAAAAAAAAO7DrYuJTz/9tIKCgjR8+HCdOHHisv3R0dF69913JUm33nqrJOmdd97Jc8zbb78tSerRo4fd4/vggw9y/79hGPrggw/k4+OjG2+8scBtdOvWTaVKldKrr75qcy3DU6dOXfH8i0WUi+uoSVJKSoq++OKLPMedPXv2spF5TZs2laQCT3V6Jd26dZOky9bie//9920ev2fPHh09erTY13UFffv2lZeXlyZMmHDZSK9LX/M//vhDb775ph577DE9+eSTGjNmjD744AOtXr3aYbHt3LlTw4cP1913361HH33UYde5VGZmpoYOHarKlSvr3Xff1YwZM3TixAk9/vjjDrtm8+bNVatWLb3zzjuXFckuvgfly5dXx44d9fnnn1/22bv0fTIj/sTERGVlZeXZ1qRJE3l5eeX5/WzdurV8fHw0adIklS1bVo0aNZJ0oci4YcMGrV69Os+oROnCSN1//+7PmTNHsbGxNmP58ssv80wv/f333+v48ePq3r17sX5GAAAAAAAAAIBr8jY7gOKoU6eOvv76aw0cOFCRkZEaMmSIGjdurIyMDP3++++aM2eOhg4dKkm69tprdc899+jjjz9WQkKCOnXqpD///FNffPGF+vbtqy5dutg1Nn9/f/3666+655571Lp1ay1atEgLFy7U2LFjVb58+QK3U6pUKU2dOlWDBw9W8+bNNWjQIJUvX15Hjx7VwoUL1a5duzxFy3/r2rWrqlevrvvuu09jxoyR1WrV559/ntvGRV988YWmTJmifv36qU6dOkpKStInn3yiUqVK5RZii6NFixa6/fbb9c477+j06dNq06aNVq9enTta89/TsUZGRqpTp065U366gs6dO2v16tWFng61bt26ev755/XKK6+oQ4cOuu222+Tn56eNGzeqcuXKmjhxotLS0nTPPfeoXr16+r//+z9J0ssvv6z58+fr3nvv1Y4dOxQUFJTvNS6OCivsunX33nuvJOVOcXmp66+//rJ1Py9atWqVunTponHjxmn8+PGFuuZ///tf/fXXX1q+fLlCQkJ0zTXX6KWXXtILL7ygO+6444qft/Hjx+vll1/WypUrrzoq91JeXl6aOnWqevXqpaZNm+ree+9VpUqVtGfPHu3atSt3ytL33ntP7du3V/PmzTVixAjVqlVLhw8f1sKFC/XXX3+ZFv+KFSs0atQo9e/fXxEREcrKytLMmTNltVp1++235x4XGBioFi1aaMOGDerVq1fu71XHjh2VkpKilJSUy4qJPXv2zB1lef3112vHjh2aNWtWvu992bJl1b59e9177706ceKE3nnnHdWtW1f3339/gX8eAAAAAAAAAID7cOtioiT17t1b27dv1xtvvKF58+Zp6tSp8vPz0zXXXKO33norTwf3p59+qtq1a2vGjBn66aefFB4erueee07jxo2ze1xWq1W//vqrHnroIY0ZM0YhISEaN26cXnrppUK39Z///EeVK1fWa6+9pjfeeEPp6emqUqWKOnTokFsMyo+Pj49++uknjRw5Ui+++KLCw8P12GOPqUyZMnnOvVhc/fbbb3XixAmVLl1a1113nWbNmqVatWoVOmZbvvzyS4WHh+ubb77RTz/9pJtuuknfffed6tevL39/f7tcw5GSk5MVHh5epHMnTJigWrVq6f3339fzzz+vwMBAXXPNNRo8eLAkaezYsTpw4IB+//333NfC19dXX3zxhdq0aaMxY8ZcNqrzUikpKapbt26h4zp16pRSUlI0YsSIy/ZNnz4934LSxXU0K1WqVKjrbdmyRa+++qpGjRqVp4D/7LPPat68ebr//vu1a9cuhYaG5ntdi8VSpPehW7duWrlypV5++WW99dZbysnJUZ06dfLcI6699lpt2LBBL774oqZOnaq0tDTVqFFDAwYMMDX+a6+9Vt26ddP8+fMVGxurwMBAXXvttVq0aJHatGmT59iLoxDbt2+fuy08PFx169bVgQMHLismjh07VikpKfr666/13XffqXnz5lq4cGG+U+yOHTtW27dv18SJE5WUlKQbb7xRU6ZMUWBgYKF+JgAAAAAAAACAe7AYhR1mhasaOnSovv/++9yCC/L3119/qVmzZvrqq6901113Ffr8oUOHasWKFdqyZYu8vb3zLeIUV1JSksqWLat33nlHDz/8sEOuUVS7d+9Wo0aNtGDBAodM12vL008/rW+++UYH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",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "image/png": "iVBORw0KGgoAAAANSUhEUgAABxMAAAETCAYAAAD9HCj7AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAABT+klEQVR4nO3dd3gUVdvH8d/uppOEUBIglID0AFJFBKkiqKggKFghoiJilxcLKiD2Do88YBcUVBRBBQRRAVFRRIq00EIVkBJISCF15/0DyUPMAtlkd2fL93NdXBfZ3TPnZnOYe2bumXMshmEYAgAAAAAAAAAAAIB/sZodAAAAAAAAAAAAAADvRDERAAAAAAAAAAAAgEMUEwEAAAAAAAAAAAA4RDERAAAAAAAAAAAAgEMUEwEAAAAAAAAAAAA4RDERAAAAAAAAAAAAgEMUEwEAAAAAAAAAAAA4RDERAAAAAAAAAAAAgEMUEwEAAAAAAAAAAAA4RDERZWKxWDRu3Liin6dOnSqLxaJdu3aZFtPZ/DtesyUlJclischisah58+Zn/eyp7/aPP/7wUHTwVv369Sv1uAEAAAAAAAAAwBUoJpooJSVFd955p8477zyFhYUpOjpanTp10sSJE3XixAmzw4ObVa1aVR999JFeeOGFYq/XrVu3zIXPbt26KSkpqUxtx40bp7p165apbWmU59+VlJSkbt26OdVm6dKl5SpwWywWTZ06tUxtXcFR/A8++KA++ugjNWnSxLS4AAAAAAAAAACBJcjsAALV/Pnzdd111yk0NFSDBw9W8+bNlZeXp59//lmjRo3Sxo0b9fbbb5sd5hmdOHFCQUG+M3y8Md4KFSro5ptvNjsM+JCuXbtKkt59910dOXLE5GgAAAAAAAAAAIHAu6orAWLnzp26/vrrlZCQoMWLF6tGjRpF7919993avn275s+fb2KE5xYWFmZ2CE7xtXgBAAAAAAAAAAC8AdOcmuCll15SZmam3nvvvWKFxFMaNGig+++/v+jngoICPf3006pfv75CQ0NVt25djR49Wrm5ucXa1a1bV1deeaWWLl2qdu3aKTw8XC1atNDSpUslSbNnz1aLFi0UFhamtm3bas2aNcXaJyUlKTIyUjt27FDv3r1VoUIFxcfHa/z48TIMo9hnS7sG4YIFC9S5c2dVqFBBUVFR6tOnjzZu3HjOduPGjZPFYinxuqO1Gf/44w/17t1bVatWVXh4uOrVq6ehQ4eeNd5T29++fbuSkpIUExOjihUr6tZbb1V2dnaxtidOnNB9992nqlWrKioqSldffbX27dvn8DvYvHmz9uzZc85/X1nk5ubqoYceUmxsrCpUqKBrrrlGhw8fdktfp5s+fbrat2+viIgIVapUSV26dNGiRYskSYsXL5bVatWYMWOKtfn4449lsVg0ZcoUt8TUrVu3orUD//3HnVOT7tu3T7fddpvi4+MVGhqqevXq6a677lJeXl7RZ9LS0vTggw+qbt26Cg0NVa1atTR48OCiJwmHDBmisLAwJScnF9t27969ValSJe3fv99t8QMAAAAAAAAA4CyeTDTB3Llzdd5556ljx46l+vztt9+uadOm6dprr9XIkSO1YsUKPf/880pOTtacOXOKfXb79u268cYbdeedd+rmm2/WK6+8oquuukpvvvmmRo8erREjRkiSnn/+eQ0cOFBbtmyR1fq/mnJhYaEuu+wydejQQS+99JIWLlyosWPHqqCgQOPHj3fq3/nRRx9pyJAh6t27t1588UVlZ2drypQpuvjii7VmzRqXrM936NAh9erVS7GxsXr00UcVExOjXbt2afbs2aVqP3DgQNWrV0/PP/+8Vq9erXfffVdxcXF68cUXiz6TlJSkzz77TLfccos6dOigH3/8UX369HG4vaZNm6pr165FBVxXuvfee1WpUiWNHTtWu3bt0oQJE3TPPfdo5syZLu/rlKeeekrjxo1Tx44dNX78eIWEhGjFihVavHixevXqpR49emjEiBF6/vnn1a9fP7Vp00YHDhzQvffeq549e2r48OFuievxxx/X7bffXuy16dOn69tvv1VcXJxb+ty/f7/at2+vtLQ0DRs2TE2aNNG+ffs0a9YsZWdnKyQkRJmZmercubOSk5M1dOhQtWnTRkeOHNHXX3+tv/76S1WrVtXEiRO1ePFiDRkyRL/++qtsNpveeustLVq0SB999JHi4+PdEj8AAAAAAAAAAGViwKPS09MNSUbfvn1L9fm1a9cakozbb7+92Ov/93//Z0gyFi9eXPRaQkKCIclYvnx50WvffvutIckIDw83du/eXfT6W2+9ZUgylixZUvTakCFDDEnGvffeW/Sa3W43+vTpY4SEhBiHDx8uel2SMXbs2KKfP/jgA0OSsXPnTsMwDCMjI8OIiYkx7rjjjmJx//3330bFihVLvP5vY8eONRwNz3/3M2fOHEOSsXLlyrNu79/xntr+0KFDi33ummuuMapUqVL086pVqwxJxgMPPFDsc0lJSSW2eaqfrl27njUWwzj5XSckJJzzc4bxv39zz549DbvdXvT6gw8+aNhsNiMtLa1U23HWtm3bDKvValxzzTVGYWFhsfdOjyMrK8to0KCB0axZMyMnJ8fo06ePER0dXWy8udsvv/xiBAcHl/h9utLgwYMNq9XqcKyd+j7GjBljSDJmz559xs8Yxv/+Xz7zzDPGjh07jMjISKNfv36ljqVr165Gs2bNyvCvAAAAAAAAAADAOUxz6mHHjx+XJEVFRZXq8998840k6aGHHir2+siRIyWpxNqKiYmJuuiii4p+vvDCCyVJPXr0UJ06dUq8vmPHjhJ93nPPPUV/t1gsuueee5SXl6fvv/++VDFL0nfffae0tDTdcMMNOnLkSNEfm82mCy+8UEuWLCn1ts4mJiZGkjRv3jzl5+c73f7fT8517txZqampRb+nhQsXSlLRE52n3HvvvQ63ZxiGW55KlKRhw4YVm/q1c+fOKiws1O7du93S35dffim73a4xY8YUe3pVUrE4IiIiNHXqVCUnJ6tLly6aP3++Xn/99WLjzZ3+/vtvXXvttWrVqpUmT57slj7sdru+/PJLXXXVVWrXrl2J9099H1988YVatmypa6655oyfkaRevXrpzjvv1Pjx49W/f3+FhYXprbfeckvsAAAAAAAAAACUB8VED4uOjpYkZWRklOrzu3fvltVqVYMGDYq9Xr16dcXExJQoJP27gFOxYkVJUu3atR2+fuzYsWKvW61WnXfeecVea9SokSQVW6fwXLZt2ybpZBEzNja22J9Fixbp0KFDpd7W2XTt2lUDBgzQU089papVq6pv37764IMPSqwneSb//r4qVaok6X/fy6nvv169esU+9+/fhyecK1ZXS0lJkdVqVWJi4jk/26lTJ9111136/fff1bt37xJrVrpLQUGBBg4cqMLCQs2ePVuhoaFu6efw4cM6fvy4mjdvftbPpaSknPMzp7zyyiuqXLmy1q5dq//85z9um54VAAAAAAAAAIDyYM1ED4uOjlZ8fLw2bNjgVLvTn2o6G5vN5tTrhmE4FUdp2e12SSfXTaxevXqJ94OCzj70zvTvLSwsLPG5WbNm6bffftPcuXP17bffaujQoXr11Vf122+/KTIy8qz9ePp7KQ9vjjU3N7foicyUlBRlZ2crIiLC7f2OGjVKv/76q77//nvVqlXL7f250po1a4qK6uvXr9cNN9xgckQAAAAAAAAAAJTEk4kmuPLKK5WSkqJff/31nJ9NSEiQ3W4vetLvlIMHDyotLU0JCQkujc1ut5eY+nTr1q2SpLp165Z6O/Xr15ckxcXFqWfPniX+dOvW7aztTz11l5aWVuz1M03p2aFDBz377LP6448/NGPGDG3cuFGffvppqeM9k1Pf/86dO4u9vn379nJv29vVr19fdrtdmzZtOudnx44dq+TkZL3yyivauXOnHn30UbfH9+mnn2rChAl65ZVX1LVrV7f2FRsbq+jo6HPeBFC/fv1S3SiQlZWlW2+9VYmJiRo2bJheeuklrVy50lXhAgAAAAAAAADgMhQTTfDwww+rQoUKuv3223Xw4MES76ekpGjixImSpCuuuEKSNGHChGKfee211yRJffr0cXl8kyZNKvq7YRiaNGmSgoODdckll5R6G71791Z0dLSee+45h2sZHj58+KztTxUjly1bVvRaVlaWpk2bVuxzx44dK/FkXqtWrSSp1FOdnk3v3r0lqcRafG+88YbDz2/evFl79uwpd7/eoF+/frJarRo/fnzRk6annP6dr1ixQq+88ooeeOABjRw5UqNGjdKkSZP0448/ui22DRs26Pbbb9fNN9+s+++/3239nGK1WtWvXz/NnTtXf/zxR4n3T30fAwYM0J9//qk5c+ac8TOS9Mgjj2jPnj2aNm2aXnvtNdWtW1dDhgxxyZgFAAAAAAAAAMCVmObUBPXr19fHH3+sQYMGqWnTpho8eLCaN2+uvLw8LV++XJ9//rmSkpIkSS1bttSQIUP09ttvKy0tTV27dtXvv/+uadOmqV+/furevbtLYwsLC9PChQs1ZMgQXXjhhVqwYIHmz5+v0aNHKzY2ttTbiY6O1pQpU3TLLbeoTZs2uv766xUbG6s9e/Zo/vz56tSpU7Gi5b/16tVLderU0W233aZRo0bJZrPp/fffL9rGKdOmTdPkyZN1zTXXqH79+srIyNA777yj6OjookJsebRt21YDBgzQhAkTlJqaqg4dOujHH38selrz39OxNm3aVF27di2a8tMbdOvWTT/++KPT06E2aNBAjz/+uJ5++ml17txZ/fv3V2hoqFauXKn4+Hg9//zzysnJ0ZAhQ9SwYUM9++yzkqSnnnpKc+fO1a233qr169erQoUKZ+zj1NOuzqzHKUm33nqrJKlLly6aPn16sfc6duxYYt3PU5YuXaru3btr7NixGjdunFN9Pvfcc1q0aJG6du2qYcOGqWnTpjpw4IA+//xz/fzzz4qJidGoUaM0a9YsXXfddRo6dKjatm2ro0eP6uuvv9abb76pli1bavHixZo8ebLGjh2rNm3aSJI++OADdevWTU8++aReeuklp+ICAAAAAAAAAMCdKCaa5Oqrr9a6dev08ssv66uvvtKUKVMUGhqq888/X6+++qruuOOOos++++67Ou+88zR16lTNmTNH1atX12OPPaaxY8e6PC6bzaaFCxfqrrvu0qhRoxQVFaWxY8dqzJgxTm/rxhtvVHx8vF544QW9/PLLys3NVc2aNdW5c+eiYtCZBAcHa86cORoxYoSefPJJVa9eXQ888IAqVapUrO2p4uqnn36qgwcPqmLFimrfvr1mzJihevXqOR2zIx9++KGqV6+uTz75RHPmzFHPnj01c+ZMNW7cWGFhYS7pw50yMzMdrltZGuPHj1e9evX0xhtv6PHHH1dERITOP/983XLLLZKk0aNHa/v27Vq+fHnRdxESEqJp06apQ4cOGjVqVImnOk+XlZWlBg0aOB3X4cOHlZWVpWHDhpV474MPPjhjMTEzM1OSVKNGDaf7rFmzplasWKEnn3xSM2bM0PHjx1WzZk1dfvnlRetDRkZG6qefftLYsWM1Z84cTZs2TXFxcbrkkktUq1YtZWRkaOjQoWrdurUef/zxom137txZ999/v1599VX1799fHTp0cDo+AAAAAAAAAADcwWI4+7gS/FZSUpJmzZpVVHDBma1du1atW7fW9OnTddNNNzndPikpSYsXL9bq1asVFBSkmJgY1wcpKSMjQ5UrV9aECRN09913u6WPstq0aZOaNWumefPmuWW6XkcefvhhffLJJ9q+fbtCQ0M90qcrZWRkKDc3V3379lV6enqp1mcEAAAAAAAAAKA8WDMROIcTJ06UeG3ChAmyWq3q0qVLmbe7d+9excbG6uKLLy5PeGe1bNky1axZs9iTrt5iyZIluuiiizxWSDzV55NPPumThURJuuWWWxQbG6vly5ebHQoAAAAAAAAAIEDw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",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "image/png": "iVBORw0KGgoAAAANSUhEUgAABxMAAAETCAYAAAD9HCj7AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAABcE0lEQVR4nO3dd3gUVdvH8d/uppJCgNDB0HsLIKJIFQEFBEWKhaIiIhZEH3wsdAtWxBcE7IjYKSogICgWRKWDQAgQCUgRCBBIQvrO+weSh5gFsmF3J7v7/VyXl2R2Zs6d3czeM3PPOcdiGIYhAAAAAAAAAAAAAPgXq9kBAAAAAAAAAAAAACieKCYCAAAAAAAAAAAAcIhiIgAAAAAAAAAAAACHKCYCAAAAAAAAAAAAcIhiIgAAAAAAAAAAAACHKCYCAAAAAAAAAAAAcIhiIgAAAAAAAAAAAACHKCYCAAAAAAAAAAAAcIhiIgAAAAAAAAAAAACHKCbCaRaLRRMmTMj7efbs2bJYLEpMTDQtpov5d7xmGzJkiCwWiywWixo1anTRdc+9t+vXr/dQdCjOHnnkkby/nfDwcLPDAQAAAAAAAAD4AYqJJklISNB9992nGjVqKCQkRJGRkWrTpo1ef/11paenmx0e3Cw6OloffvihXnjhhXzLq1WrVuTCZ4cOHTRkyJAibTthwgRVq1atSNsWxuX8XkOGDFGHDh2c3i4xMVEWi0U//PBDkdq9nJhdwVH8AwcO1Icffqi2bduaFhcAAAAAAAAAwL8EmB2AP1qyZIn69u2r4OBgDRo0SI0aNVJWVpZWr16t0aNHa/v27XrrrbfMDvOC0tPTFRDgPX86xTHesLAw3XnnnWaHAS/TokULtWjRQitXrtTGjRvNDgcAAAAAAAAA4AeKV4XFD+zdu1cDBgxQTEyMvv/+e1WsWDHvtQceeEB79uzRkiVLTIzw0kJCQswOwSneFi8AAAAAAAAAAEBxwTCnHvbSSy8pNTVV7777br5C4jm1atXSyJEj837OycnRM888o5o1ayo4OFjVqlXTU089pczMzHzbVatWTT169NAPP/ygli1bKjQ0VI0bN84bInHBggVq3LixQkJC1KJFC23atCnf9kOGDFF4eLj+/PNPde3aVWFhYapUqZImTZokwzDyrVvYOQiXLl2qtm3bKiwsTBEREerevbu2b99+ye0mTJggi8VSYLmjuRnXr1+vrl27Kjo6WqGhoapevbruvvvui8Z7bv979uzRkCFDFBUVpZIlS+quu+7SmTNn8m2bnp6uhx9+WNHR0YqIiNBNN92kgwcPOnwPdu7cqf3791/y9yuKzMxMPfrooypbtqzCwsJ0880369ixY25p63xz585Vq1atVKJECZUqVUrt2rXTt99+K0n6/vvvZbVaNW7cuHzbfPzxx7JYLJo5c6ZbYho/frysVqu+++67fMuHDRumoKAgbdmyxS3tSlJycrJGjRqlatWqKTg4WFWqVNGgQYOUlJSUt05GRoYmTJigOnXqKCQkRBUrVtQtt9yihIQE0+MHAAAAAAAAAMBZFBM9bNGiRapRo4auueaaQq0/dOhQjRs3Ts2bN9drr72m9u3ba/LkyRowYECBdffs2aPbb79dPXv21OTJk3Xy5En17NlTH330kUaNGqU777xTEydOVEJCgvr16ye73Z5v+9zcXHXr1k3ly5fXSy+9pBYtWmj8+PEaP36807/nhx9+qO7duys8PFwvvviixo4dqx07dujaa6/NVwy8HEePHlWXLl2UmJioJ554QtOmTdMdd9yh3377rVDb9+vXTykpKZo8ebL69eun2bNna+LEifnWGTJkiKZNm6Ybb7xRL774okJDQ9W9e3eH+6tfv74GDRp02b+XIw899JC2bNmi8ePH6/7779eiRYv04IMPuqWtcyZOnKiBAwcqMDBQkyZN0sSJE1W1alV9//33kqROnTppxIgRmjx5ct6Qm4cPH9ZDDz2kzp07a/jw4W6Ja8yYMWrWrJnuuecepaSkSJKWL1+ut99+W+PGjVPTpk3d0m5qaqratm2radOmqUuXLnr99dc1fPhw7dy5UwcOHJB09hjq0aOHJk6cqBYtWujVV1/VyJEjderUKW3bts3U+AEAAAAAAAAAKBIDHnPq1ClDktGrV69Crb9582ZDkjF06NB8y//zn/8Ykozvv/8+b1lMTIwhyVizZk3esuXLlxuSjNDQUGPfvn15y998801DkrFq1aq8ZYMHDzYkGQ899FDeMrvdbnTv3t0ICgoyjh07lrdckjF+/Pi8n99//31DkrF3717DMAwjJSXFiIqKMu699958cf/9999GyZIlCyz/t/HjxxuO/jT/3c7ChQsNSca6desuur9/x3tu/3fffXe+9W6++WajTJkyeT9v2LDBkGQ88sgj+dYbMmRIgX2ea6d9+/YXjcUwzr7XMTExl1zPMP73O3fu3Nmw2+15y0eNGmXYbDYjOTm5UPtx1u7duw2r1WrcfPPNRm5ubr7Xzo8jLS3NqFWrltGwYUMjIyPD6N69uxEZGZnv780d/vjjDyMoKMgYOnSocfLkSaNy5cpGy5YtjezsbLe1OW7cOEOSsWDBggKvnXtP3nvvPUOSMWXKlAuu44r4Bw8ebISFhRXxNwEAAAAAAAAAoPDomehBp0+fliRFREQUav1vvvlGkvToo4/mW/7YY49JUoG5FRs0aKCrr7467+errrpK0tkeZFdccUWB5X/++WeBNs/v7WaxWPTggw8qKytLK1euLFTMkrRixQolJyfrtttuU1JSUt5/NptNV111lVatWlXofV1MVFSUJGnx4sXKzs52evt/95xr27atjh8/nvc5LVu2TJI0YsSIfOs99NBDDvdnGEbesLKuNmzYsHxDv7Zt21a5ubnat2+fW9r78ssvZbfbNW7cOFmt+b8mzo+jRIkSmj17tuLi4tSuXTstWbJEr732Wr6/N3do1KiRJk6cqHfeeUddu3ZVUlKSPvjgAwUEuG8a2Pnz56tp06a6+eabC7x27j2ZP3++oqOjHf6NnP++mRE/AAAAAAAAAABFQTHRgyIjIyUpb2jDS9m3b5+sVqtq1aqVb3mFChUUFRVVoJD07wJOyZIlJUlVq1Z1uPzkyZP5llutVtWoUSPfsjp16kiSU0OT7t69W9LZImbZsmXz/fftt9/q6NGjhd7XxbRv3159+vTRxIkTFR0drV69eun9998vMJ/khfz7/SpVqpSk/70v597/6tWr51vv35+HJ1wqVldLSEiQ1WpVgwYNLrlumzZtdP/992vt2rXq2rVrgTkr3WX06NFq2rSp1q5dq/Hjxxcq1suRkJCgRo0aXXKdunXrFqoo6On4AQAAAAAAAAAoCrrBeFBkZKQqVaqUN3daYZ3fo+libDabU8sNw3AqjsI6Nxfjhx9+qAoVKhR4/VKFlgv9vrm5uQXWmzdvnn777TctWrRIy5cv1913361XX31Vv/32m8LDwy/ajqffl8tRnGPNzMzM65GZkJCgM2fOqESJEm5v988//8wrXP/xxx9ub8/VvD1+AAAAAAAAAIB/oGeih/Xo0UMJCQn69ddfL7luTEyM7HZ7XsHhnCNHjig5OVkxMTEujc1utxcY+nTXrl2SpGrVqhV6PzVr1pQklStXTp07dy7wX4cOHS66/bled8nJyfmWX2hIz9atW+u5557T+vXr9dFHH2n79u369NNPCx3vhZx7//fu3Ztv+Z49ey5738VdzZo1ZbfbtWPHjkuuO378eMXFxemVV17R3r179cQTT7g9PrvdriFDhigyMlJPPfWUPvnkEy1YsMCtbdasWfOSDwLUrFlT8fHxlxx214z4AQAAAAAAAAAoCoqJHvb4448rLCxMQ4cO1ZEjRwq8npCQoNdff12SdOONN0qSpk6dmm+dKVOmSJK6d+/u8vimT5+e92/DMDR9+nQFBgbquuuuK/Q+unbtqsjISD3//PMOiyrHjh276PbnipE//fRT3rK0tDR98MEH+dY7efJkgZ55zZo1k6RCD3V6MV27dpUkzZgxI9/yadOmOVx/586d2r9//2W3Wxz07t1bVqtVkyZNyutpes757/nvv/+uV155RY888ogee+wxjR49WtOnT9ePP/7o1vimTJmiNWvW6K233tIzzzyja665Rvfff7+SkpLc1mafPn20ZcsWLVy4sMBr596TPn36KCkpKd9x9O91zIofAAAAAAAAAICiYJhTD6tZs6Y+/vhj9e/fX/Xr19egQYPUqFEjZWVlac2aNfriiy80ZMgQSVLTpk01ePBgvfXWW0pOTlb79u21du1affDBB+rdu7c6duzo0thCQkK0bNkyDR48WFdddZWWLl2qJUuW6KmnnlLZsmULvZ/IyEjNnDlTAwcOVPPmzTVgwACVLVtW+/fv15IlS9SmTRuHxZZzunTpoiuuuEL33HOPRo8eLZvNpvfeey9vH+d88MEHmjFjhm6++WbVrFlTKSkpevvttxUZGZlXiL0cLVq0UJ8+fTR16lQdP35crVu31o8//pjXW/Pfw7HWr19f7du3zxvyszjo0KGDfvzxR6eHQ61Vq5aefvppPfPMM2rbtq1uueUWBQcHa926dapUqZImT56sjIwMDR48WLVr19Zzzz0nSZo4caIWLVqku+66S3/88YfCwsIu2Ma53q7OzMcpSXFxcRo7dqyGDBminj17SpJmz56tZs2aacSIEfr8888vuG1iYqKqV6+uwYMHa/bs2U61O3r0aM2bN099+/bV3XffrRYtWujEiRP6+uuvNWvWLDVt2lSDBg3SnDlz9Oijj2rt2rVq27at0tLStHLlSo0YMUK9evW6rPgBAAAAAAAAAPA0iokmuOmmm7R161a9/PLL+uqrrzRz5kwFBwerSZMmevXVV3XvvffmrfvOO++oRo0amj17thYuXKgKFSroySef1Pjx410el81m07Jly3T//fdr9OjRioiI0Pjx4zVu3Din93X77berUqVKeuGFF/Tyyy8rMzNTlStXVtu2bXXXXXdddNvAwEAtXLhQI0aM0NixY1WhQgU98sgjKlWqVL5tzxVXP/30Ux05ckQlS5ZUq1at9NFHH6l69epOx+zInDlzVKFCBX3yySdauHChOnfurM8++0x169ZVSEiIS9pwp9TUVIfzVhbGpEmTVL16dU2bNk1PP/20SpQooSZNmmjgwIGSpKeeekp79uzRmjVr8t6LoKAgffDBB2rdurVGjx5doFfn+dLS0lSrVi2nYsrNzdXgwYMVHR2dr8du7dq1NXnyZI0cOVKff/65+vXr53D71NRUSVLFihWdaleSwsPD9fPPP2v8+PFauHChPvjgA5UrV07XXXedqlSpIunsMfTNN9/oueee08cff6z58+erTJkyuvbaa9W4cePLjh8AAAAAAAAAAE+zGM52WYJPGjJkiObNm5dXbMGFbd68WbGxsZo7d67uuOMOp7cfMmSIvv/+e23cuFEBAQGKiopyfZCSUlJSVLp0aU2dOlUPPPCAW9ooqh07dqhhw4ZavHixW4brvZAZM2bo8ccfV0JCgsqXL++xdl0lLS1N6enpeuihh7Ro0SKOVwAAAAAAAACA2zFnInAR6enpBZZNnTpVVqtV7dq1K/J+//rrL5UtW1bXXnvt5YR3UT/99JMqV66cr6drcbFq1SpdffXVHi0knmv34Ycf9spCoiQ9/fTTKlu2rD799FOzQwEAAAAAAAAA+Al6JkISPRMvZOLEidqwYYM6duyogIAALV26VEuXLtWwYcP05ptvFmmfO3bs0KFDhySdHTqzdevWrgwZPmzXrl1584YGBASoQ4cO5gYEAAAAAAAAAPB5FBMhiWLihaxYsUITJ07Ujh07lJqaqiuuuEIDBw7U008/rYAAphwFAAAAAAAAAAC+jWIiAAAAAAAAAAAAAIeYMxEAAAAAAAAAAACAQxQTAQAAAAAAAAAAADhEMREAAAAAAAAAAACAQxQTAQAAAAAAAAAAADhE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",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "image/png": "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OPn3sQ8AAAAAAAAAgL/jycRi7Ny5c5KkMmXK5Gv981Mxjh07Ntvy//znP5J00bsVmzRpovbt22d9btu2rSSpe/fuioiIuGj5/v37L9rnv592M5lMuu+++5Senq4lS5bkK2ZJWrx4seLj43XzzTcrNjY265/FYlHbtm21bNmyfG8rN+Hh4ZKkefPmKSMjo8Dfv+eee7J97ty5s+Li4rLqaeHChZKke++9N9t6999/v8vtGYaRNa2su911113Zpn7t3LmzHA6HDh065JH9SVK1atX09ttva/HixercubM2b96s6dOnq2zZsh7b57fffqtKlSq5LOPzv/+3334rk8nk8onZf5eRL+IHAAAAAAAAAMDXSCYWY+eTGAkJCfla/9ChQzKbzWrQoEG25dWqVVN4ePhFiaR/JwwlqVy5cpKk2rVru1x+5syZbMvNZrMiIyOzLWvYsKEkFWhq0j179kjKTGJWrlw527+ff/5Zp06dyve2ctOlSxcNHDhQEyZMUKVKldS/f3/NmDHjovdJ5uTC8ipfvrykf8rlfPnXq1cv23oX1oc35BWrpwwZMkR9+/bV+vXrNWrUKPXo0cOj+9u3b58aNWokq9Wa6zo1atRQhQoV8tyet+MHAAAAAAAAAMDXch5hh98rW7asatSoke3db/nx76etcmOxWAq03DCMAsWRX+ffxThr1ixVq1btop/nliiScv59HQ7HRet98803Wrt2rebOnatFixZp5MiRmjx5stauXauwsLBc9+PtcikKX8UaFxenjRs3SpK2b98up9Mps7n43NNQ3OMHAAAAAAAAAKCgGAUv5vr166d9+/ZpzZo1ea5bp04dOZ3OrCf9zjt58qTi4+NVp04dt8bmdDovmvp09+7dkqS6devmezv169eXJFWpUkU9e/a86F/Xrl1z/f75p+7i4+OzLc9pSs927drpxRdf1MaNG/XZZ58pOjpas2fPzne8OTlf/gcOHMi2fO/evUXednExZswYJSQk6OWXX9aqVas0depUj+6vfv362rVrV67T1tavX1/Hjh3TX3/9lef2vB0/AAAAAAAAAAC+RjKxmHv00UdVunRp3XnnnTp58uRFP9+3b5/eeOMNSdI111wjSRclQF5//XVJUt++fd0e31tvvZX1/4Zh6K233pLNZivQ9JC9e/dW2bJl9dJLL7lMCp0+fTrX759PRv76669Zy5KSkjRz5sxs6505c+aiJ/NatGghSfme6jQ3vXv3liS988472Za/+eabLtffuXOnYmJiirxff/HNN9/oyy+/1CuvvKLHHntMQ4YM0VNPPZWVYPaEgQMHKjY2NttxeN75uh44cKAMw9CECRNyXMdX8QMAAAAAAAAA4GtMc1rM1a9fX59//rluuukmXXrppRo2bJiaNm2q9PR0rV69Wl9//bWGDx8uSbrssst0++236/3331d8fLy6dOmi9evXa+bMmbr++uvVrVs3t8YWEhKihQsX6vbbb1fbtm21YMEC/fTTT3riiSdUuXLlfG+nbNmyevfdd3Xbbbfp8ssv15AhQ1S5cmXFxMTop59+UseOHV0mi8676qqrFBERoTvuuEOPPPKILBaLpk+fnrWN82bOnKl33nlHAwYMUP369ZWQkKAPPvhAZcuWzUrEFkWrVq00cOBATZ06VXFxcWrXrp1WrFiRlYy6cDrWSy+9VF26dNHy5cuLvG936dq1q1asWFHg6VBPnTql0aNHq1u3brrvvvskZSaaly1bpuHDh2vVqlW5ThdqMpkKVRbDhg3TJ598orFjx2r9+vXq3LmzkpKStGTJEt17773q37+/unXrpttuu03/+9//tGfPHvXp00dOp1MrV67Mireo8QMAAAAAAAAAUFyRTAwA1113nf7880+99tpr+uGHH/Tuu+8qODhYzZs31+TJkzVq1KisdT/88ENFRkbq448/1nfffadq1arp8ccf1/jx490el8Vi0cKFCzV69Gg98sgjKlOmjMaPH69nnnmmwNsaOnSoatSooVdeeUWvvfaa0tLSVLNmTXXu3FkjRozI9bs2m03fffed7r33Xj399NOqVq2aHnroIZUvXz7bd88nV2fPnq2TJ0+qXLlyatOmjT777DPVq1evwDG78sknn6hatWr64osv9N1336lnz5768ssv1ahRI4WEhLhlH56UmJjo8r2VeRk9erTS0tI0Y8aMrKRpxYoV9f7776t///6aNGmSHn300Rz3KUnVq1cv8H4tFovmz5+vF198UZ9//rm+/fZbVaxYUZ06dVKzZs2y1psxY4aaN2+ujz76SI888ojKlSun1q1bq0OHDkWOHwAAAAAAAACA4sxkFPQRIyAfhg8frm+++SYrEYScbd68WS1bttSnn36qW265pcDfHz58uJYuXao//vhD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",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "image/png": "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8MO65ZZbfLLNgwcP6oEHHtDll1+uq666So8//rhXh3I92xP3Ugn+qKgoFS9ePEcvAfg6fgAAAAAAAAAAPIVkop8YMWKEihQporvuukuHDx++4Pc7d+7Uu+++K0nq06ePJGncuHFZ1hkzZowk6corr/R4fO+//37m/xuGoffff19BQUHq3r17jsvo1auXihcvrldffdXtXIZHjx695OfPJiP/+OOPzGWJiYmaPHlylvVOnjx5Qc+8s73nzvYMy49evXpJkj788MMsy9977z2362/ZskV79+7N93b9wcGDB9W/f3916NBBb731ls+2O3ToULlcLn366aeaOHGi7Ha77rzzTq/1wIyKilKnTp302WefXbDvzm7TarWqb9+++umnn7R69eoLyjg3Nl/HDwAAAAAAAACApzDMqZ+oWbOmvvrqKw0YMED169fX7bffroYNGyotLU3Lli3TtGnTNHjwYElSkyZNNGjQIE2cOFHx8fHq3LmzVq1apcmTJ6tv377q2rWrR2MLDQ3V3LlzNWjQILVp00a//PKL5syZo5EjRyoqKirH5RQvXlzjx4/XbbfdpubNm+umm25SVFSU9u7dqzlz5qh9+/ZZkpbnu/zyy1WlShXdeeedeuKJJ2Sz2fTZZ59llnHW5MmT9eGHH+q6665TzZo1lZCQoI8//ljFixfPTMTmR4sWLdSvXz+NGzdOx48fV9u2bbV48eLM3prnD8dav359de7cWYsWLcr3tj2lS5cuWrx4ca6TWQ8++KCOHj2qESNGXNDLs3HjxmrcuLHbz8XFxal69eoaNGiQJk2alKttfv7555ozZ44mTZqkSpUqScpI3N56660aP3687r333ot+dtKkSRoyZIg+//zzzPMnp/73v/+pQ4cOat68uYYNG6bq1asrLi5Oc+bM0bp16yRJr776qn799Vd17txZw4YNU/369XXw4EFNmzZNS5cuVURERL7iBwAAAAAAAADAbCQT/cg111yjDRs26K233tLMmTM1fvx4hYSEqHHjxnrnnXc0dOjQzHU/+eQT1ahRQ5MmTdKMGTNUrlw5Pf300xo9erTH47LZbJo7d67uuecePfHEEypWrJhGjx6t5557LtdlDRw4UBUqVNDrr7+ut956S6mpqapYsaI6duyoIUOGXPKzQUFBmjFjhu699149++yzKleunB5++GGVLFkyy2fP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",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "for c, note in cs: compile_and_plot(U, c)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "acee9cc1-5b36-45d3-9844-2dbb885d6bfe",
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "ef5f0dc2-ad3a-467a-89dd-36d296d458ac",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "genQC Version 0.1.0\n"
- ]
- }
- ],
- "source": [
- "import genQC\n",
- "print(\"genQC Version\", genQC.__version__)"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "python3",
- "language": "python",
- "name": "python3"
- },
- "widgets": {
- "application/vnd.jupyter.widget-state+json": {
- "state": {},
- "version_major": 2,
- "version_minor": 0
- }
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/src/examples/Discrete-continuous circuits with multimodal diffusion/0_compile_testset.ipynb b/src/examples/Discrete-continuous circuits with multimodal diffusion/0_compile_testset.ipynb
new file mode 100644
index 0000000..39ee495
--- /dev/null
+++ b/src/examples/Discrete-continuous circuits with multimodal diffusion/0_compile_testset.ipynb
@@ -0,0 +1,1096 @@
+{
+ "cells": [
+ {
+ "cell_type": "raw",
+ "id": "c4e9a976-c6b5-4ce2-8e92-1dd7cee7c736",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "categories:\n",
+ " - Unitary compilation\n",
+ " - Parameterized gates\n",
+ " - Quantum circuits\n",
+ " - Pretrained model\n",
+ "---"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6c172f48-2011-45c9-ba09-b49edc98b7ec",
+ "metadata": {},
+ "source": [
+ "# Compile unitaries with parametrized circuits\n",
+ "\n",
+ "> A short tutorial showing unitary compilation with parametrized circuits."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "24ecd66b-9552-4e9b-aa68-ce292444d85e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from genQC.imports import *\n",
+ "import genQC.utils.misc_utils as util\n",
+ "\n",
+ "from genQC.dataset.config_dataset import ConfigDataset\n",
+ "from genQC.pipeline.multimodal_diffusion_pipeline import MultimodalDiffusionPipeline_ParametrizedCompilation\n",
+ "from genQC.scheduler.scheduler_dpm import DPMScheduler\n",
+ "\n",
+ "from genQC.platform.tokenizer.circuits_tokenizer import CircuitTokenizer\n",
+ "from genQC.platform.simulation import Simulator, CircuitBackendType\n",
+ "from genQC.inference.sampling import decode_tensors_to_backend, generate_compilation_tensors\n",
+ "from genQC.inference.evaluation_helper import get_unitaries\n",
+ "from genQC.inference.eval_metrics import UnitaryInfidelityNorm\n",
+ "from genQC.dataset.balancing import get_tensor_gate_length"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5ae966b5-0f67-4ffe-9d2b-ed0f23c7c383",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[INFO]: Cuda device has a capability of 8.6 (>= 8), allowing tf32 matmul.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "device(type='cuda')"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "util.MemoryCleaner.purge_mem() # clean existing memory alloc\n",
+ "device = util.infer_torch_device() # use cuda if we can\n",
+ "device"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f903ecdc-f07c-4e0a-8fc9-1d1bea2bfec5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# We set a seed to pytorch, numpy and python. \n",
+ "# Note: This will also set deterministic algorithms, possibly at the cost of reduced performance!\n",
+ "util.set_seed(0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "73b1255b-b440-4bb9-89fc-c864fbcafaa1",
+ "metadata": {},
+ "source": [
+ "## Load model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "39db6391-1b4f-41ec-b0ef-4e3350c5d790",
+ "metadata": {},
+ "source": [
+ "Load the pre-trained model directly from [Hugging Face: Floki00/cirdit_multimodal_compile_3to5qubit](https://huggingface.co/Floki00/cirdit_multimodal_compile_3to5qubit)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6ff4eb91-6929-4d54-b6e5-38f6d7b18fdf",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pipeline = MultimodalDiffusionPipeline_ParametrizedCompilation.from_pretrained(\"Floki00/cirdit_multimodal_compile_3to5qubit\", device)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "03202ede-a4a0-431c-877e-d308604c6901",
+ "metadata": {},
+ "source": [
+ "The model is trained with the gate set:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e4992c91-8f21-48d8-80d2-d156f883133e",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "['h', 'cx', 'ccx', 'swap', 'rx', 'ry', 'rz', 'cp']"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pipeline.gate_pool"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5f48f11f-f215-481c-9f36-60a34b36e97f",
+ "metadata": {},
+ "source": [
+ "which we need in order to define the `vocabulary`, allowing us to decode tokenized circuits."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c97ddef1-173d-412c-a0b3-3a4e90edd299",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'h': 1, 'cx': 2, 'ccx': 3, 'swap': 4, 'rx': 5, 'ry': 6, 'rz': 7, 'cp': 8}"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "vocabulary = {g:i+1 for i, g in enumerate(pipeline.gate_pool)} \n",
+ "tokenizer = CircuitTokenizer(vocabulary)\n",
+ "tokenizer.vocabulary"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b4aab0b8-4f8a-49d4-989b-bc42cd9876e9",
+ "metadata": {},
+ "source": [
+ "### Set inference parameters"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6ab1e4f6-9ae7-4711-825a-92ba96b28d74",
+ "metadata": {},
+ "source": [
+ "Set diffusion model inference parameters."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f7592401-ab32-4beb-8727-42c02d9584b0",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pipeline.scheduler = DPMScheduler.from_scheduler(pipeline.scheduler)\n",
+ "pipeline.scheduler_w = DPMScheduler.from_scheduler(pipeline.scheduler_w)\n",
+ "\n",
+ "timesteps = 40\n",
+ "pipeline.scheduler.set_timesteps(timesteps) \n",
+ "pipeline.scheduler_w.set_timesteps(timesteps) \n",
+ "\n",
+ "pipeline.lambda_h = 1.0\n",
+ "pipeline.lambda_w = 0.35\n",
+ "pipeline.g_h = 0.3\n",
+ "pipeline.g_w = 0.1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "953d070b-492c-44bd-b2fa-7f4b5b9630fa",
+ "metadata": {},
+ "source": [
+ "We assume in this tutorial circuits of 4 qubits."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "51a147e1-543c-49d2-a3b6-4f5c2b8f41d2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "num_of_samples_per_U = 32 # How many circuits we sample per unitary\n",
+ "num_of_qubits = 4\n",
+ "\n",
+ "prompt = \"Compile 4 qubits using: ['h', 'cx', 'ccx', 'swap', 'rx', 'ry', 'rz', 'cp']\"\n",
+ "\n",
+ "# These parameters are specific to our pre-trained model.\n",
+ "system_size = 5\n",
+ "max_gates = 32"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7b2fa84f-94c1-4032-9bdc-b10284c4a68a",
+ "metadata": {},
+ "source": [
+ "For evaluation, we also need a circuit simulator backend."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7920f4e9-0376-448c-9159-2d3095c35aaa",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "simulator = Simulator(CircuitBackendType.QISKIT)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b0060705-6d0d-4d48-95c0-988f700bb3f8",
+ "metadata": {},
+ "source": [
+ "## Load test unitaries"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "827a7eae-3585-45a0-a611-01bc8b4c1c5d",
+ "metadata": {},
+ "source": [
+ "We load a balanced testset directly from [Hugging Face: Floki00/unitary_compilation_testset_3to5qubit](https://huggingface.co/datasets/Floki00/unitary_compilation_testset_3to5qubit)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "118357f2-6340-4667-983c-0b250a08ec4c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "testset = ConfigDataset.from_huggingface(\"Floki00/unitary_compilation_testset_3to5qubit\", device=\"cpu\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ae9737d4-ef9a-4757-b351-ddaa8625aa67",
+ "metadata": {},
+ "source": [
+ "We pick the 4 qubit circuits as test cases for this tutorial."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d61708c5-3e8e-4782-8a02-37abfa2315ec",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "target_xs = testset.xs_4qubits # tokenized circuit\n",
+ "target_ps = testset.ps_4qubits # circuit angle paramters\n",
+ "target_us = testset.us_4qubits.float() # corresponding unitaries, "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dea0df41-ca38-438f-a60a-c158427065ac",
+ "metadata": {},
+ "source": [
+ "For 4 qubits the unitary is a 16x16 matrix. Complex numbers are split into 2 channels (real, imag)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d9e7035c-0a07-4c9f-bbf4-aaa10784cb8c",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "torch.Size([3947, 2, 16, 16])"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "target_us.shape # [batch, 2, 2^n, 2^n]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "01beefbe-5d31-4744-b5cc-0c3897781c8b",
+ "metadata": {},
+ "source": [
+ "A random circuit may look like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0da110b2-4894-4f6b-9a1c-cd50c69fb455",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "rnd_index = torch.randint(target_us.shape[0], (1, ))\n",
+ "\n",
+ "qc_list, _ = decode_tensors_to_backend(simulator, tokenizer, target_xs[rnd_index], target_ps[rnd_index])\n",
+ "qc_list[0].draw(\"mpl\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6975d819-7f39-4942-b185-1d82275b5eef",
+ "metadata": {},
+ "source": [
+ "Next, we further restrict to circuits with a maximum of 16 gates. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9f862cd3-590b-4528-aed8-786558e460c0",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gate_cnts = get_tensor_gate_length(target_xs)\n",
+ "\n",
+ "ind = (gate_cnts <= 16).nonzero().squeeze()\n",
+ "target_xs = target_xs[ind] \n",
+ "target_ps = target_ps[ind] \n",
+ "target_us = target_us[ind] "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6017c08b-11c0-4827-bce2-066bc16a13ac",
+ "metadata": {},
+ "source": [
+ "We plot the distribution of the gate counts for this testset, seeing it is uniformly balanced."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4407e4ba-90c0-49ab-a1b6-cf988eee8b5d",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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/AAAAEsh/AAAAEsh/AAAAEsh/AAAAEsh/AAAAEsh/AAAAEsh/AAAAEvxqPAzffp03XrrrapSpYqCg4PVt29fpaamOvU5d+6cRowYoWrVqqly5crq37+/MjIySqhiAABQ2nhU+Nm4caNGjBihLVu2KCkpSbm5uerWrZvOnj3r6DNmzBh9+OGHeu+997Rx40YdPXpU99xzTwlWDQAASpPyJV3AtVi3bp3T6yVLlig4OFg7duxQx44dlZmZqUWLFmn58uXq3LmzJGnx4sVq3LixtmzZorZt25ZE2QAAoBTxqCs/l8rMzJQkBQUFSZJ27Nih3NxcxcTEOPo0atRItWrV0ubNmwsdJycnR1lZWU4LAAAomzw2/Njtdo0ePVrt27dXs2bNJEnp6eny9vZWYGCgU9+QkBClp6cXOtb06dMVEBDgWCIiIoqydAAAUII8NvyMGDFCe/bs0cqVK10ea/z48crMzHQsR44ccUOFAACgNPKoe34uGjlypNauXatNmzapZs2ajvbQ0FCdP39ep0+fdrr6k5GRodDQ0ELH8/HxkY+PT1GWDAAASgmPuvJjjNHIkSO1Zs0affbZZ4qMjHRa36pVK1WoUEHJycmOttTUVB0+fFjR0dHFXS4AACiFPOrKz4gRI7R8+XK9//77qlKliuM+noCAAPn5+SkgIEBDhw5VQkKCgoKC5O/vr8cff1zR0dE86QUAACR5WPhZsGCBJOn22293al+8eLGGDBkiSfrrX/8qLy8v9e/fXzk5OerevbteffXVYq4UAACUVh4VfowxV+zj6+ur+fPna/78+cVQEQAA8DQedc8PAACAqwg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUgg/AADAUsps+Jk/f77q1KkjX19fRUVFadu2bSVdEgAAKAXKl3QBReEf//iHEhIStHDhQkVFRWnOnDnq3r27UlNTFRwcXNLlFZs64z4qknEPzuhZJOMCAFAcyuSVn5dfflnDhg3TQw89pCZNmmjhwoWqWLGi3nzzzZIuDQAAlLAyd+Xn/Pnz2rFjh8aPH+9o8/LyUkxMjDZv3lzgNjk5OcrJyXG8zszMlCRlZWUVbbFFzJ6TXSTjFnRcimKuwo5/WZzL039WZXWukj4vinMuT/9ZldW5Svq8KO653DWuMebyHU0Z89NPPxlJ5quvvnJqf/rpp02bNm0K3GbSpElGEgsLCwsLC0sZWI4cOXLZrFDmrvxcj/HjxyshIcHx2m6369SpU6pWrZpsNluJ1JSVlaWIiAgdOXJE/v7+JVJDacGx+APH4g8ciz9wLP7AsXBmteNhjNGvv/6q8PDwy/Yrc+GnevXqKleunDIyMpzaMzIyFBoaWuA2Pj4+8vHxcWoLDAwsqhKvib+/vyVO2KvBsfgDx+IPHIs/cCz+wLFwZqXjERAQcMU+Ze6GZ29vb7Vq1UrJycmONrvdruTkZEVHR5dgZQAAoDQoc1d+JCkhIUFxcXFq3bq12rRpozlz5ujs2bN66KGHSro0AABQwspk+Lnvvvt04sQJTZw4Uenp6br55pu1bt06hYSElHRpV83Hx0eTJk3K93acFXEs/sCx+APH4g8ciz9wLJxxPApmM+ZKz4MBAACUHWXunh8AAIDLIfwAAABLIfwAAABLIfwAAABLIfyUoPnz56tOnTry9fVVVFSUtm3bdtn+7733nho1aiRfX181b95cH3/8cTFVWnSmT5+uW2+9VVWqVFFwcLD69u2r1NTUy26zZMkS2Ww2p8XX17eYKi46kydPzrdfjRo1uuw2ZfGckKQ6derkOxY2m00jRowosH9ZOyc2bdqkXr16KTw8XDabTYmJiU7rjTGaOHGiwsLC5Ofnp5iYGO3fv/+K417r35zS4HLHIjc3V2PHjlXz5s1VqVIlhYeHa/DgwTp69Ohlx7ye37XS4ErnxZAhQ/Lt15133nnFcT3xvHAV4aeE/OMf/1BCQoImTZqknTt3qkWLFurevbuOHz9eYP+vvvpK999/v4YOHapdu3apb9++6tu3r/bs2VPMlbvXxo0bNWLECG3ZskVJSUnKzc1Vt27ddPbs2ctu5+/vr2PHjjmWQ4cOFVPFRatp06ZO+/XFF18U2resnhOStH37dqfjkJSUJEm69957C92mLJ0TZ8+eVYsWLTR//vwC18+aNUtz587VwoULtXXrVlWqVEndu3fXuXPnCh3zWv/mlBaXOxbZ2dnauXOnJkyYoJ07d2r16tVKTU1V7969rzjutfyulRZXOi8k6c4773TarxUrVlx2TE89L1zmlm8TxTVr06aNGTFihON1Xl6eCQ8PN9OnTy+w/4ABA0zPnj2d2qKiosyjjz5apHUWt+PHjxtJZuPGjYX2Wbx4sQkICCi+oorJpEmTTIsWLa66v1XOCWOMGTVqlKlbt66x2+0Fri+r54Qxxkgya9ascby22+0mNDTUvPjii46206dPGx8fH7NixYpCx7nWvzml0aXHoiDbtm0zksyhQ4cK7XOtv2ulUUHHIi4uzvTp0+eaxikL58X14MpPCTh//rx27NihmJgYR5uXl5diYmK0efPmArfZvHmzU39J6t69e6H9PVVmZqYkKSgo6LL9zpw5o9q1aysiIkJ9+vTRd999VxzlFbn9+/crPDxcN954o2JjY3X48OFC+1rlnDh//rzeeecdPfzww5f9ouGyek5cKi0tTenp6U4/+4CAAEVFRRX6s7+evzmeKjMzUzab7Yrfz3gtv2ueZMOGDQoODlbDhg01fPhwnTx5stC+VjovLkX4KQE///yz8vLy8n3idEhIiNLT0wvcJj09/Zr6eyK73a7Ro0erffv2atasWaH9GjZsqDfffFPvv/++3nnnHdntdrVr107//e9/i7Fa94uKitKSJUu0bt06LViwQGlpabrtttv066+/FtjfCueEJCUmJur06dMaMmRIoX3K6jlRkIs/32v52V/P3xxPdO7cOY0dO1b333//Zb/E81p/1zzFnXfeqaVLlyo5OVkzZ87Uxo0b1aNHD+Xl5RXY3yrnRUHK5NdbwDONGDFCe/bsueJ779HR0U5fUtuuXTs1btxYr732mqZOnVrUZRaZHj16OP590003KSoqSrVr19a7776roUOHlmBlJWvRokXq0aOHwsPDC+1TVs8JXL3c3FwNGDBAxhgtWLDgsn3L6u/awIEDHf9u3ry5brrpJtWtW1cbNmxQly5dSrCy0ocrPyWgevXqKleunDIyMpzaMzIyFBoaWuA2oaGh19Tf04wcOVJr165VSkqKataseU3bVqhQQS1bttSBAweKqLqSERgYqAYNGhS6X2X9nJCkQ4cOaf369XrkkUeuabuyek5Icvx8r+Vnfz1/czzJxeBz6NAhJSUlXfaqT0Gu9LvmqW688UZVr1690P0q6+fF5RB+SoC3t7datWql5ORkR5vdbldycrLT/73+r+joaKf+kpSUlFRof09hjNHIkSO1Zs0affbZZ4qMjLzmMfLy8vTtt98qLCysCCosOWfOnNGPP/5Y6H6V1XPify1evFjBwcHq2bPnNW1XVs8JSYqMjFRoaKjTzz4rK0tbt24t9Gd/PX9zPMXF4LN//36tX79e1apVu+YxrvS75qn++9//6uTJk4XuV1k+L66opO+4tqqVK1caHx8fs2TJErN3714THx9vAgMDTXp6ujHGmAcffNCMGzfO0f/LL7805cuXNy+99JLZt2+fmTRpkqlQoYL59ttvS2oX3GL48OEmICDAbNiwwRw7dsyxZGdnO/pceiyef/5588knn5gff/zR7NixwwwcOND4+vqa7777riR2wW2efPJJs2HDBpOWlma+/PJLExMTY6pXr26OHz9ujLHOOXFRXl6eqVWrlhk7dmy+dWX9nPj111/Nrl27zK5du4wk8/LLL5tdu3Y5nmCaMWOGCQwMNO+//7755ptvTJ8+fUxkZKT57bffHGN07tzZzJs3z/H6Sn9zSqvLHYvz58+b3r17m5o1a5rdu3c7/Q3JyclxjHHpsbjS71ppdblj8euvv5qnnnrKbN682aSlpZn169ebW265xdSvX9+cO3fOMUZZOS9cRfgpQfPmzTO1atUy3t7epk2bNmbLli2OdZ06dTJxcXFO/d99913ToEED4+3tbZo2bWo++uijYq7Y/SQVuCxevNjR59JjMXr0aMdxCwkJMXfddZfZuXNn8RfvZvfdd58JCwsz3t7e5oYbbjD33XefOXDggGO9Vc6Jiz755BMjyaSmpuZbV9bPiZSUlAJ/Ly7us91uNxMmTDAhISHGx8fHdOnSJd9xql27tpk0aZJT2+X+5pRWlzsWaWlphf4NSUlJcYxx6bG40u9aaXW5Y5GdnW26detmatSoYSpUqGBq165thg0bli/ElJXzwlU2Y4wphgtMAAAApQL3/AAAAEsh/AAAAEsh/AAAAEsh/AAAAEsh/AAAAEsh/AAAAEsh/AAAAEsh/AAAAEsh/AAodjabTUOGDCnpMq5Ldna2nnjiCdWqVUvlypVTnTp1SrokANeI8AOUERs2bJDNZpPNZtMbb7xRYB+bzaa77767mCsrW2bOnKl58+bpvvvu05IlSzRnzpwSqSMxMVGTJ08ukbkBT0f4AcqgyZMn67fffivpMsqkpKQkNW/eXC+++KIefPBB9e3bt0TqSExM1PPPP18icwOejvADlDGtW7fW0aNHS+yKRGmTl5en7Oxst42Xnp6uoKAgt40HoPgRfoAyZsCAAWrVqpVmzpypkydPXrF/YfffLFmyRDabTRs2bHC0TZ48WTabTXv37tXo0aMVFhamihUrqkuXLkpNTZUkrV69Wrfccov8/PxUp04dvf7664XOvX79erVt21YVK1ZUaGioRo0apTNnzuTrl5mZqbFjx6pevXry8fFRjRo1dP/99+s///lPgTWvX79eU6dOVd26deXr66t33333ssfgwoULmjlzppo0aSJfX19Vq1ZN/fr107fffptv7LS0NG3cuNHxFuOV3nrKzs5WQkKCwsLC5Ofnp7Zt2yo5OVlDhgyRzWZz6rtt2zYNGTJEDRo0UMWKFVWlShW1b99ea9ascep3++2366233pIkRx02m01Llixx9Dl27JiGDx+uWrVqydvbW+Hh4YqPj9fx48edxjp16pTGjBnjOFbVqlVTq1at9OKLL152vwBPVr6kCwDgXjabTTNmzFDXrl31wgsv6OWXX3b7HHFxcapcubL+/Oc/68SJE5o9e7a6d++uqVOn6plnntHw4cP18MMPa9GiRXr00UfVpEkTdejQwWmMnTt3atWqVRo2bJgGDx6slJQUzZ07V3v27FFSUpK8vH7/f7PMzEy1a9dOhw8f1sMPP6ymTZvq2LFjevXVVxUVFaWvv/5atWvXdhr7qaeeUm5uroYNGyZ/f381bNjwsvsTGxurd999V127dtXw4cOVnp6u+fPnKzo6Wp9//rlatmypjh076u2339aYMWNUvXp1Pfvss5Kkm2666bJj33vvvfr444/Vt29fxcTEKC0tTf369VNkZGS+vmvWrNH333+vAQMGqHbt2jp58qTeeust3XPPPVq2bJkeeOABSdKzzz4ru92uzz//XG+//bZj+3bt2kmSDh8+rOjoaJ0/f15Dhw5V3bp1deDAAS1YsEApKSn6+uuvFRAQ4Khv06ZN+r//+z/ddNNN+u2337Rv3z5t2LBBTz/99GX3DfBYBkCZkJKSYiSZF1980RhjTNeuXY2Pj485ePCgo48k07NnT6ftJJm4uLh84y1evNhIMikpKY62SZMmGUnm7rvvNna73dH+t7/9zUgyVapUMYcPH3a0Hz9+3Pj4+JiBAwfmm1OSWbNmjVP7E088YSSZFStWOLX5+vqa3bt3O/U9ePCgqVKlilPtF2tu0KCBOXv2bMEH6hKffvqpkWQGDBjgtE+7d+825cqVMx06dHDqX7t2bdOpU6erGvujjz4ykswjjzxSYPulf4LPnDmTb4yzZ8+aBg0amMaNGzu1x8XF5dv+ot69e5saNWqYI0eOOLVv377dlCtXzkyaNMkYY8zp06eNJDN8+PCr2h+grOBtL6CMmjlzps6fP68JEya4fewnnnjC6S2b2267TZLUu3dvRUREONpr1Kihhg0bav/+/fnGaNiwYb6bhceNGydJjrd5jDFatmyZOnbsqBtuuEE///yzY6lUqZLatm2rTz/9NN/Yw4cPV8WKFa9qXy7O9eyzzzrtU4sWLdSrVy998cUXOnHixFWNdakPP/xQkpSQkODUftddd6lx48b5+leqVMnx7+zsbJ08eVLZ2dnq3Lmz9u3bp6ysrCvOmZmZqbVr16p3797y9fV1OmZ16tRRvXr1HMfMz89PPj4+2rp1qw4ePHhd+wh4IsIPUEa1bNlS999/v5YtW6ZvvvnGrWPfeOONTq+rVq0qSQW+lVO1atUC7z0q6D/+YWFhCgwMdNzLc+LECZ08eVKffvqpatSokW9JSkpSRkZGvnEaNGhw1fuSlpYmLy+vAutp2rSpo8/1uDh2vXr18q0r6K2448ePKz4+XiEhIapUqZKqV6+uGjVqaOHChZKk06dPX3HO1NRU2e12LVq0qMBjlpqa6jhm3t7emjNnjvbs2aPIyEg1bdpUjz/+uJKTk69rfwFPwT0/QBk2bdo0rVq1SmPHjtW//vWva9r2woULha4rV67cNbUbY65p7ku3i4mJ0dixY696u6u96lNcLr2xuSDGGHXr1k379u3TqFGj1Lp1awUEBKhcuXJavHixli9fLrvdflXjSNKgQYMUFxdXYB8/Pz/Hv//v//5Pffr00UcffaSNGzdq1apVeuWVV3Tfffdp5cqVV7mHgGch/ABlWGRkpIYPH66//e1vTk9t/a+goCCdOnUqX/ulT1K52759+/K1HTt2TKdPn3ZcWapRo4YCAwOVlZWlmJiYIqnjxhtvlN1u1759+/LdvLx3715JBV/Ruhp16tSR3W7X/v37811Zuvh03EXffPON/v3vf2vixIn5Pr/n73//e76xCwtU9erVk81m0/nz56/6mIWFhemRRx7RI488ory8PD344INasWKFnnzySd16661XNQbgSXjbCyjjnnvuOfn7++uZZ54pcH2DBg20efNmp8/C+eWXX7R48eIirSs1NVWJiYlObTNnzpQkx71AXl5eio2N1bZt27Rq1aoCx7n00e1rdXGu6dOnO12h2rNnjz744AN16NBBNWrUuK6xe/XqJUn661//6tT+8ccf5wt/F6+aXXqVbM+ePfkedZekypUrS1K+4FqtWjXdddddWr16tbZs2ZJvO2OM4x6m7OzsfJ+BVK5cOUcILCgUA2UBV36AMq569ep6+umnC73xeeTIkRo0aJA6d+6sBx98UKdPn9Ybb7yh2rVrKz09vcjqat68uQYNGqRhw4apfv36SklJ0apVq9SpUyfdd999jn4vvPCCvvzySw0YMEADBgxQ27Zt5e3trUOHDunjjz9Wq1atnD7f5lp17dpVAwYM0MqVK/XLL7/o7rvvdjzq7uvrq7lz51732HfddZe6d++uN954Qz///LPjUffXX39dN910k9O9WI0bN1bTpk01a9YsZWdnq2HDhvrhhx/02muvqXnz5tqxY4fT2G3bttUrr7yixx57TD179lSFChUUFRWlyMhILViwQB06dFDHjh01ePBgtWzZUna7Xf/5z3/0/vvva/DgwZo8ebJ++OEHderUSf369VOzZs1UtWpV7du3TwsWLFBkZKTjRnagzCm5B80AuNOlj7r/r7Nnz5qwsLACH3U3xphZs2aZWrVqGW9vb9OoUSOzaNGiyz7qnpaW5rR9WlqakeR4hPp/derUydSuXdupTf//8fqkpCTTpk0b4+vra4KDg83IkSNNVlZWgfVPmTLFNGvWzPj6+prKlSubRo0amUceecRs2bLF0a+gmq9Gbm6umTFjhmnUqJHx9vY2VatWNX369DHffPNNvr7X8qi7Mb8/vj5q1CgTHBxsfH19TZs2bUxycrLp37+/8fPzc+p78OBB86c//clUr17d+Pn5mVtvvdWsXr26wOOel5dnnnzySXPDDTcYLy8vI8ksXrzYsf7EiRPmqaeeMvXr1zc+Pj4mICDANGvWzDzxxBPmu+++M8YY8/PPP5vRo0ebFi1amICAAOPr62vq1q1rRo0aZY4ePXpNxxDwJDZjrvNORADAdWvevLlyc3P1/fffl3QpgOVwzw8AFKGCvmD2o48+0p49e9S1a9cSqAgAV34AoAiNHz9eu3bt0h133KGAgADt3r1bb775pvz9/bV7927VrFmzpEsELIfwAwBF6OOPP9aMGTO0d+9eZWZmKigoSJ07d9bUqVML/PBDAEWP8AMAACyFe34AAIClEH4AAIClEH4AAIClEH4AAIClEH4AAIClEH4AAIClEH4AAIClEH4AAICl/D+MmVs9cUU+QgAAAABJRU5ErkJggg==",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "gate_cnts = get_tensor_gate_length(target_xs)\n",
+ "\n",
+ "d = np.bincount(gate_cnts)\n",
+ "plt.bar(range(d.size), d)\n",
+ "plt.xlabel(\"Number of gates\", fontsize=13)\n",
+ "plt.ylabel(\"Frequency\", fontsize=13)\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7835d6ad-03d1-4a7b-ab89-939b187468a5",
+ "metadata": {},
+ "source": [
+ "## Compile a single unitary"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "142811b8-e9e7-433f-90d2-1f1adf8be00b",
+ "metadata": {},
+ "source": [
+ "First, we want to compile a single unitary for 4 qubits from the testset. We pick one with 8 gates."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "76c28d9d-9420-4197-bbe2-d8a99a879696",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "ind = (gate_cnts == 8).nonzero().squeeze()[:1]\n",
+ "\n",
+ "qc_list, _ = decode_tensors_to_backend(simulator, tokenizer, target_xs[ind], target_ps[ind])\n",
+ "qc_list[0].draw(\"mpl\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6dbf2639-9bcb-402f-89bd-bdfef47a5d29",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "1d212f97803d4180bc93e2ccc8277855",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/40 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[INFO]: (generate_comp_tensors) Generated 32 tensors\n"
+ ]
+ }
+ ],
+ "source": [
+ "U = target_us[ind].squeeze()\n",
+ "\n",
+ "out_tensor, params = generate_compilation_tensors(pipeline, \n",
+ " prompt=prompt, \n",
+ " U=U, \n",
+ " samples=num_of_samples_per_U, \n",
+ " system_size=system_size, \n",
+ " num_of_qubits=num_of_qubits, \n",
+ " max_gates=max_gates,\n",
+ " no_bar=False, # show progress bar\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2fad5657-9483-417d-a49a-af9825a12237",
+ "metadata": {},
+ "source": [
+ "For instance, a circuit tensor alongside parameters the model generated looks like this"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "38513404-0da1-4ca6-b9be-1e1706fc9010",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "tensor([[ 7, 8, 0, -3, 1, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],\n",
+ " [ 0, 8, 0, -3, 0, 7, 4, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],\n",
+ " [ 0, 0, 4, 3, 0, 0, 4, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],\n",
+ " [ 0, 0, 4, 0, 0, 0, 0, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9]], device='cuda:0')\n",
+ "tensor([[ 0.2794, 0.1956, 0.0000, 0.0000, 0.0000, -0.3857, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,\n",
+ " 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]], device='cuda:0')\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(out_tensor[0])\n",
+ "print(params[0])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f67664dd-3644-468d-8ada-ec51216e4d82",
+ "metadata": {},
+ "source": [
+ "### Evaluate and plot circuits"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "bb08c4b1-c2e6-420a-8f76-91dd7bb139ba",
+ "metadata": {},
+ "source": [
+ "We decode these now to circuits and calculate their unitaries"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "400c673d-89d9-455f-b911-e2e7b9e8535b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "generated_qc_list, _ = decode_tensors_to_backend(simulator, tokenizer, out_tensor, params)\n",
+ "generated_us = get_unitaries(simulator, generated_qc_list)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fa287539-98c8-42e6-b4f3-92d2577ca3c2",
+ "metadata": {},
+ "source": [
+ "We then evaluate the unitary infidelity to our target `U`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6f672f64-9f77-4241-abe9-0013e374936d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "U_norms = UnitaryInfidelityNorm.distance(\n",
+ " approx_U=torch.from_numpy(np.stack(generated_us)).to(torch.complex128), \n",
+ " target_U=torch.complex(U[0], U[1]).unsqueeze(0).to(torch.complex128),\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "866aea78-4078-471a-a50a-9287dec78afc",
+ "metadata": {},
+ "source": [
+ "We plot the four best ciruits, w.r.t. the infidelity:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e4d3c2cd-3dee-4459-9054-116bb863453f",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAABe0AAAEoCAYAAADFSNp+AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAXEgAAFxIBZ5/SUgAAnxdJREFUeJzs3XdclXX/x/HXYaMIiqi4FyruXVgOnJmjTBtW5kjTTFNLM7W7su6WNsy0caulppaWq5zkNldmLtzmwIUKKiBb4Pr9wY+TxEY45wDv5+PRI881P9cFfM857+u6vl+TYRgGIiIiIiIiIiIiIiJidXbWLkBERERERERERERERJIptBcRERERERERERERsREK7UVEREREREREREREbIRCexERERERERERERERG6HQXkRERERERERERETERii0FxERERERERERERGxEQrtRURERERERERERERshEJ7EREREREREREREREbodBeRERERERERERERMRGKLQXEREREREREREREbERCu1FRERERERERERERGyEQnsRERERERERERERERuh0F5ERERERERERERExEYotBcRERERERERERERsREK7UVERPKQyWTK0X/VqlUDYOvWrZhMJgYOHGi12k+cOMGUKVNo3749Xl5eODo64u3tTe/evfn999/zbD/nz5/HZDLh7++fZ9ssSPz9/TGZTJw/f97apaRS1H8uIpkpyG374cOHGTlyJH5+flSoUAFnZ2c8PDxo1aoVM2bM4M6dO3myn6LehqhtFxERkbzkYO0CRERECpMBAwakmbZjxw7OnDlD48aNadKkSap5Xl5eFqosa506deLy5cu4ubnh5+eHp6cnx44dY8WKFaxcuZLPPvuMMWPGWLvMfHH+/HmqV69Ou3bt2Lp1q7XLsSnz5s1j0KBBvP3220yePNna5YhYRUFu27dv386XX35J1apVqVevHmXKlCEkJISdO3eyZ88eli1bxm+//YaTk5O1S81zatszprZdRETEtim0FxERyUPz5s1LM23gwIGcOXOGXr162fQXY19fXz788EOeeOIJXFxczNP/97//8eKLLzJu3Di6dOlCvXr1rFhlwff9998THR1NxYoVrV1KKhUrVuT48eMUK1bM2qWI2JyC3LZ369aNbt26UaNGjVTTr127RqdOndi2bRuzZs1i5MiRVqqwcFDbLiIiInlJ3eOIiIgIABs3buS5555LFdgDDBs2jC5dupCYmMjPP/9speoKjypVquDr64ujo6O1S0nF0dERX19fqlSpYu1SRCQP1ahRI01gD1CuXDlef/11ADZv3mzpsgodte0iIiKSlxTai4iI2JibN28yfPhwypcvj7OzMw0aNOC7777LcPmLFy8ycuRIatasiYuLC56envTo0YNdu3blWU2NGzcG4MqVK3m2TYCIiAhGjx5N5cqVcXFxoW7dukybNo2kpKR0l4+OjubDDz+kadOmuLm5mbvymT9/frrLBwUFMXz4cGrXrk2xYsXw9PSkfv36DBs2jJMnTwIwefJkqlevDsC2bdtS9Uudk36o//jjD/r27UvFihVxdnamfPnydOzYkdmzZ6daLqN+j1P6wY6Pj+fdd9/F19cXZ2dnevXqZV4mKiqKKVOm0KJFC9zd3SlevDi+vr6MGDGCU6dOmZebPHkyJpMp3buDAapVq4bJZEo1Lb1+j/39/Rk0aBAA77zzTqpzk9G2RSR9tti2pwTMed01jtr2f6htFxERkdxQ9zgiIiI2JCwsjFatWhEZGUmbNm0IDQ1l+/btDB48mKSkJIYMGZJq+d27d9O9e3du3bpFnTp16N69OyEhIQQEBLB+/XoWLVrEU089dc91nT17FgBvb+973laKuLg4OnTowJkzZ+jQoQPx8fFs2rSJV199lUOHDqUJDq5fv07nzp05fPgw3t7etGvXDsMw2LVrFwMHDmTfvn3MmDHDvPzFixdp1qwZN2/epFatWnTr1o3ExESCgoKYPXs2rVq1ok6dOjRp0oQ+ffqwbNkyypUrR9euXc3baN26dbaOZfr06bz66qskJSXRvHlz2rZtS2hoKIcPH+a1117jhRdeyNZ2kpKS6NWrF9u3b6ddu3Y0atSI0qVLAxAcHEznzp05evQopUqVwt/fH2dnZ86ePcs333xDrVq1qF27drb2k11du3YlISGBnTt3pum328fHx/zvgQMHMn/+fPWNLJIBW2zbb926xaeffgpA9+7d72lbd1PbnpbadhEREckxQ0RERPLVgAEDDMB4++23M1xmy5YtBmAARt++fY3Y2FjzvBUrVhiAUaVKlVTrhIeHG+XLlzfs7e2NhQsXppr3559/GqVKlTLc3NyM69ev31P9f//9t+Hs7GwAxr59++5pW4ZhGOfO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mpCQkKPt2dvbm6+Cpwy8kh9SBm758ccf0230/618+fIAnDp1Ks28LVu2cOfOnWzvO7fnRkTgscce4/jx43nSt2B8fDxr166lWrVqHDhwgIULFzJv3jxz2Hz8+PF021RrqlixIsePH+f777+3Wg2tW7dm3rx56f7XpUsXIPmR5ho1amR7m5s3b2bo0KFUq1aNwMBAduzYwY8//sjmzZu5evUqH3/8cX4djtnNmzd5/vnnSUxMZOnSpWzdupWlS5dy4sQJfHx8+PTTT9PcHVq5cmX27dtHSEgI27dvZ/HixaxZs4agoCCee+45rly5wmuvvZbvtYsUdGrbrd+2A3Tp0oX33nuPgIAAjh49es/b69OnDx9//DFbtmwx991saSdPnmTcuHE4Ozuzfft21q1bx8qVKzl48CClS5fmlVde4e+//061TqVKlZgxYwYhISFs3bqVxYsXs3fvXlavXo2DgwPTpk2z2vGIFHZ5mclMnjwZwzDS/S/lBpQFCxZgGAbz5s275/2BMpmcUCYjeUWhfRExbNgwypYty9SpU5k1a1aax2sSEhIICAhI1RDmB19fX3x9fbl8+XKO1x03bhw3btwwvz537hzvvvsuACNGjDBPv//+++ncuTM7d+5kxIgRREREpNnWoUOHWL9+vfn1ggUL0j32devWYRiG+UopJF+ddXR05MyZMyQmJuboGF5//XVMJhPvv/8+W7ZsSTUvISGBtWvX5mh76bnvvvto3749169fZ+jQoURFRaWaf/78eQIDA82v27ZtC8DChQtTPSJ87tw5Ro0alaN9V6hQAUj+EpGRiRMn4uvrm2p0eRFJ7g/S19c3T7ocuXr1KomJiVStWjXdx3Z9fX1tro9FR0dHfH19qVKlirVLSdfChQsBeO6553K03ssvv4zJZGL58uXUrVs31TwnJyeaNm2aZzVmZM6cOURERPDoo4+mGvysXLlyTJ06FYBPP/001ToeHh40b948ze+Pi4sLH3zwAZB8QUJEMqe23Tba9qlTp/LGG2/QpUsXPD0973l73377LePGjcPf399qgwxOnz6dxMREXnzxRVq1amWeXrt2bd544w0SEhKYPn16qnUWL17MyJEj09TcvXt3nn/+eSA5ZBKRvKdMJjVlMspkJGsK7YuIkiVL8ssvv+Dh4cGwYcOoVq0a3bp149lnn6Vjx46UKVOGrl27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+eCDD4zx48enqeHUqVNGlSpVDMCYOHFijra5adMmw2QyGT4+PsaxY8dSzYuLizP2799/z3Vn5caNG4a7u7sBGMuWLTNPv3r1quHj45PuzzUsLMzYt29fmt+fmJgY47nnnjMAo0+fPvleu0hBl1EbllWbml47ee7cOQMw2rVrl+11UsyePdto1KiR4ezsbJQtW9bo16+fcfny5UzrCAsLM9577z2jWbNmhpubm+Hi4mJUq1bNeOihh4wvv/wyzftVTmv+888/jc6dOxseHh6GyWQyAGPu3LlplouJiTEcHR0NV1dX49atW+keX3ZcuXLFsLe3NxwdHY3Q0NAsl085Nxm9/0RGRhqTJk0yfHx8DCcnJ8PT09Po0aOHsWPHjgy3mdNzmh07duwwunbtapQsWdIoVqyY0aJFC2PevHnpLvv7778bL730ktG4cWPDy8vLcHBwMEqWLGn4+fkZ77//vhEWFpbueu3atcvw5yNSFKltt422/e233zaATP/79znIrG1PmZfZf+fOnUuzXnBwsDF+/Hijfv36RrFixYxixYoZNWvWNB599FFj3rx5xu3bt3N8bL/++qvRpk0bw83NzXBzczPatGljrFq1Kt1lq1atmmXd6f2sUtbLi+93IoWBMhnrZzKGYRivvfaa8d577xkBAQHG0aNHM30fzMqoUaOM999/37h+/Xqq6Xv37jVnJP/73//yoOrMnThxwrC3tzecnZ2NXbt2maefPHnSKF26tOHg4GCcPn061TpPPfWUMWPGDCMiIiLV9NWrVxsODg4GYAQEBOR77ZL3FNoXcUFBQZl++M+N/H6DyIil3yAy8sMPPxiAUa1atRytV69ePcPOzs44fPhwPlWWtSlTphiA8eijj6aZt3z5cgMwevToke3tXbx40QCMUqVK5WGVIiLpS2l/BwwYYO1SREQkj6htF5HCTJlM3ggODr6n0D4zH3zwgQEY/v7+eb7tfxs+fLgBGKNHj04z77PPPjMAY+TIkdne3tChQw3AGDhwYB5WKZaiPu0LoIz6T7u737Lt27fToUMHSpQogbu7O927d+fYsWOplq9WrRpVq1YFkvsCM5lMafoAy6xPsO+++44mTZrg6uqKt7c3AwcO5OrVq5nWfvPmTSZOnEi9evVwdXXFw8ODDh06sHr16mwff3r9p/n7+5u7jnnnnXfMx5LSF9zSpUsx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qPAp069fv26elptzOn/+/HS33atXL9zd3fHw8CA8PJxLly5Rr169NMteunQpVb1ZqVq1Km3atDEPutOhQ4dsrSciIiIiIiJSECmTyVh+ZDJ57c8//zTflPntt9+m6urobinn9PHHHzd3W5MeX19fILn7m/QyGX9/f1q3bk2VKlWA5O6To6Ki0t1mVpnM6tWrGTRoEI6Ojixfvjzdiw1ScCi0lxwrX74858+fJygoiLp166aZHxQUlGZaypXmIUOG0KdPn3yvMSP29va88MILvPXWWyxatAh3d3du3brFkCFDsrx7PD3BwcF07tyZ4OBgRo8ezdtvv52rulIeAUvp2/7fbt68CaS+Gz4359QwjEznN27cmO3bt7N///40of2dO3c4cuQILi4u5kf7siPlTT8kJCTb64iIiIiIiIhIWspk8s+xY8d4+OGHiYyMZNq0aQwaNCjDZStVqsTJkyeZMGECzZs3z3Lb1apVyzSTKVmyJFWqVOHChQscOHCA1q1bp5p/8eJFQkNDqVq1Ku7u7mnW37ZtG0888QSGYfDDDz+k202TFCy2d/lPbF5Kv1w//fRTmnknTpzg4MGDaaZ37twZgBUrVuRbXU5OTkByv+qZGTJkCA4ODsyePfueHsO6desWDz30EGfOnGHQoEFMmzYt50X/v27duuHg4EBgYKA5oL9bSrc4d/fvlh/ntHv37gAsXbo0zbzVq1cTGxtLp06dcHFxydb2EhMTzY961axZM8/qFBERERERESmKlMnkj/Pnz9OlSxdu3LjB5MmTGTNmTKbLWzqTSZnWs2fPNPP279/PI488QlxcHHPmzLHqhRnJOwrtJcdS+iD7/PPPOXTokHl6VFQUL7/8crpXDvv06UO9evVYtGgR//3vf9P042YYBjt37mTnzp25ritl4NeTJ09mulz58uV55JFHOHDgANu2baNRo0Zp+pHPSnR0NN27dycwMJAnn3yS2bNnZ2tgj4kTJ+Lr68vMmTNTTffy8mLQoEFERkYyatQo4uPjzfPmz5/Phg0bcHFxYeDAgebp+XFOhwwZgru7O7/88gvLly83T79+/Trjx48HYOzYsanWWbx4MYGBgWm2dfPmTYYOHcrZs2dp2LBhmivP/fv3x9fXN18/NIiIiIiIiIgUJspkci+jTOb69et06dKFy5cvM3bs2Gz1ojBs2DDKli3L1KlTmTVrlrm7nBQJCQkEBARw5MiRbNc3evRo7O3t+eabb9izZ495+unTp3n//fdxcHBg9OjRqdY5efIkXbt2JSIigunTp6fKjTLTsWNHfH19rTJQrmSPuseRHHvggQcYN24cn3zyCS1btqRDhw54eHiwbds2nJ2d6dmzJ6tWrUq1joODAytXruShhx7irbfeYubMmTRq1IiyZcsSGhrKwYMHuX79OtOmTePBBx/MVV1+fn6ULVuWpUuX4u/vT40aNbCzs+P555/ngQceSLXsiy++aA6lhw4dmuN9vfHGG+zevRt7e3scHBwYPHhwusvNmzcv1evg4GBOnjyZ7uAuH3/8MXv27GHRokX8/vvvtGjRggsXLrBv3z7s7e2ZNWsWlStXNi+fH+fU09OT7777jieffJLHH38cf39/SpcuzcaNGwkLC+PVV1/F398/1Trr16/n6aefpkaNGjRs2JBixYpx+fJl9u/fT2RkJBUrVmTJkiVpLmpcuHCBkydPEh4enq3aRERERERERIo6ZTLJ5syZw5w5c4Dk7nwhOXO5ux/3r776imbNmplfZ5TJDBs2jNOnT1OsWDHzgLH/5uXlxSeffGJ+XbJkSX755Rd69uzJsGHDeO+992jQoAGlSpXi6tWr7N+/n7CwMFasWEGDBg2ydUx16tTh448/5tVXX6VNmzZ07twZJycnfvvtN2JiYvjiiy/w8fFJtU7fvn0JCQmhTJky/PXXX+nW7uvry4QJE1JNO3PmDEFBQZkOfCvWpdBecuXjjz+mTp06zJgxg61bt+Lh4UGXLl2YMmUKkyZNSnedWrVqceDAAWbOnMny5cvZs2cPCQkJeHt707RpUx555BGefPLJXNfk4uLCmjVrmDRpEnv37mX79u0YhkHr1q3TvEG0adMGR0dHHBwcePbZZ3O8r5S+5xMTE/nhhx8yXO7foX1mPDw82L17Nx988AE//fQTq1evxs3NjR49ejBhwoR03zjz45z26dOH7du3895777Fnzx7i4+OpV68eI0eOZMCAAWmWHzJkCMWLFzdflQ8LC8PNzY0GDRrQs2dPRowYgYeHR45qEBEREREREZH0FfVMBpIHZf3jjz9STYuPj081LSIiIlvbSsl4oqOj0x0sFpIHf707tIfkCxWBgYFMmzaNNWvWmLs2Ll++PO3ateOxxx6jU6dO2T4mgFdeeQUfHx8+/vhjfv/9dwBatGjB+PHj6dGjR4a1h4SEZFh7u3bt0oT2YvtMRlYjU4oUQj/++CPPPPMMAwYMyFGwLiIiIiIiIiIiuadMRiRrCu2lyLlz5w4tW7bk0KFD7N27l5YtW1q7JBERERERERGRQk+ZjEj2qHscKTJ+/fVXVq5cyd69ezl69Ci9evXSm4OIiIiIiIiISD5TJiOSM3bWLkDEUvbv38/cuXO5cuUKzzzzDN9++621SxIRERERERERKfSUyYjkjLrHERERERERERERERGxEbrTXkRERERERERERETERii0FxERERERERERERGxEQrtRURERERERERERERshEJ7EREREREREREREREbodBeRERERERERERERMRGKLQXEREREREREREREbERCu1FRERERERERERERGyEQnsRERERERERERERERuh0F5ERERERERERERExEYotBcRERERERERERERsREK7UVEREREREREREREbIRCexERERERERERERERG6HQXkRERERERERERETERii0FxERERERERERERGxEQrtRURERERERERERERshEJ7EREREREREREREREbodBeRERERERERERERMRGOFi7ABEREbGcJMO6+7czWXf/IgBGUpK1S8Bkp3tnbIW120VQ2yi2wdpto9pFERGRfyi0FxERKSKSDOgaADfjrbN/TydY/5DCKbEuIymJhIEvQli49Yoo6YHDvG8UUNkAa7eLoLZRbIPV20a1iyIiIqnoHVFERKQIsWYwZc19i6RizcDeFvYvqVi7bbL2/kXMrNk2qV0UERFJRaG9iIiIiIiIiIiIiIiNUGgvIiIiIiIiIiIiImIjFNqLiIiIiIiIiIiIiNgIhfYiIiIiIiIiIiIiIjbCwdoFiIiIiO2Ju3ae42Ob41q1IUlx0ZTrOQbPds9YuywREatS2ygiIiIilqDQXkRERNLlVqcVPm+uJikummOjGimYEhFBbaOIiIiI5D91jyMiIiKZSoyOICkh3tpliIjYFLWNIiIiIpJfdKe9iIiIpCvy5G5OTmxL9NkDVBn+tbXLERGxCWobRURERCS/KbQXm9FnE4TGWW5/Xs6wrKPl9idZuzP8Fbh5y3I79CyF49fTLLc/KZKOnL7JNz+d4MT5MEqVcKZfTx+6t6mMg4PtP+yW0gVExKFN3Ng8j9L+/Qjbu4q4q2e4c+Myd25eofrYRdYuU/JAfCJsDoYNVyA6AWq7wxPVoVJxa1cmYnsKQ9uYlGSwYfdl5v5yipCbsdSs7M6wJ3xpXs/L2qXZDMOAgzdhZRBcj4WyLtCrKjTxBJPJ2tWJSEGhnEMKEmUytkWhvdiM0DiISrB2FWJVN29BTIwF92e5XUnRc+dOEs+9sY1ftwRxJyGJhEQDgPW7LlHK3ZnNc7rhU8XdylVmj3vjjlxbPpWYC0cpeV9PEqPCuTR3HFVHzrZ2aZIHToTDyN3JwX10YvK0Azfg5/PJAdW4BmCngAqA89FRjAncz8r726T7WoqWgto2Xr4WRYcha7kSEk1kdPKH7237rrJozd+0aebN8mmdKOZatL8mRsTDy3vg7G2ITQQDMAGbgqFGCZjhBx5O1q7SNqhdFMmccg4pUJTJ2JSi/WlMREQKlNDQUEaMGMHRo0dxc3OjTp061K9fn/Pnz3P48GEeffRRXnvtNWuXCcCQyb/z69YgYuISU02PjE4gOjaBB/uv4uSvj1PS3dlKFeZMmR4vc23Fx1R5aRaX5o+nwnMfYOdczNplyT26GgPDdqb9MplgAAb8egGK2cPIelYpT4qIgtS2/1tBaxtj4xJoM3A1F65Gkfj/F5MBEpMMomMT2fbXVZ4Yt4k1Xz5kxSqtK8mAEbvh79twJ+mf6QbJAf6pcHhpNyxoqwuaIiIikn9s/9l8ERERwDAMHn30Ubp06cKRI0dYsGABCxYswDAMihcvzo4dO9i5cye3blnwcb4MXAiOZEnAWWJiE9Odn5QEkdF3+G7FKQtXln3O5arh8+Zq8+uSLXtQbfQ8Ln37CiYHJ25uW0Toxu+sWKHkhUVnIC79X1MgOaD64SxE3rFcTVK0FKS2HQp+27hk/TlCbsWmCuzvFhuXyJa9wQSeKrq3vu0NgfORqQP7uyUYcCEyeTkRERGR/KI77UVEpEDYuHEjSUlJDB48GIBatWrh6upKQkICHTp0AKBNmzYcOHDA/NpaFqz6GyP9PMQsOjaRL5cc59UBDS1TVB6p8uKX1i5B8tCvF/7/rvpM2JuS+7t/pIplarJ1e27doNOuLQDEJibi5VQwnpaxVQWpbc9MQWkbZ/x4zNwlTkbiE5KYvfwkX0xoZaGqbMvS8xCTycVMSJ7/83nwK2uJimyf2kUREZG8p9BeCpzbgVs5/8UgnMpWw961BDVeX4qdozqVFCns9u/fT8uWLc2vT5w4gbe3NwDu7sl9w3t4eBAWFpbtbcbGxnL69Ok8rRPg4LELxGd0i95droZGERgYmOf7z0iSAWDdiwSBgYHqTsBGJBkQlZD170NsosGRoGtUDy8kt5UmGfjew+p+pUqn6bs5N44EHslR3xq1atXCxcUlV/uyZdZu222hXQTLtY0Xg8OzXCYx0eDw8SsWfX+yJedu1gSy7uLo3M0YAgP/zv+CLOUe2kZrtYuFQWFt2yXnlHOIyL8ptJcCyavzYMo/+R+Cf/6A24c34dH8YWuXJCL5zMvLiw0bNmAYBtHR0YwcOZLmzZvj4eFBREQEABEREdSoUSPb2zx9+jSNGjXK+2LLPgplHwZT5m+z0bdD82f/GTGZaL4y64sJ+alJk8Zk+RiCWEzTn6Ky7H87ITaaaR9M5o31/7NQVfnLBMT1fNLaZdC4SWNy8pdw+PBhGja0fric16zetttAuwgWbBt9JoNrpcyXMRLZtvEXGn2/IP/rsUE+b63N1neLwD3baPRO4fkOYgttY07bxcKgsLbtkjvKOUTkbgrtpUBLjAqzdgkiYiF9+/blp59+onbt2tSuXRtvb28aNmxIy5YtWbZsGQ8//DC///47AwcOzPY2a9WqxeHDh/O81nNXYnhy0hHi7mT81dPJ0cSgxxow4vG8339GkgwYfN5iu0vXwYOHitpNdDbt25BYdkW6kkTGPxRnF1dWfjgK96kjLFhZPkoy4M33rV0Fhw4eyvGd9oWRtdt2W2gXwXJt4w8BV/l8ySVi4zK+UOHq4sisGS/SuJZtDv6b3/ZFuTMnJJE4wz7DZZxNiUzqWpcWfSz3Hp7vbKBtzGm7WBgU1rZd7o1yDhEBhfZSQIVu+JZbu5Zi71qCiv2s/8VbRPJf8eLFCQgIML9+6KGHaN68Offddx9z586ldevW9OzZE09Pz2xv08XFJV/ubmrYEPxX3GTrvmDi4tMPRpydHJj8cju8vbJ+BD+vJBnAeYvtLl0NGzYsat/HbdqY27Bve/KAs+lxtoMuFe14sEk9yxaWj4ykJDLv0Ttj1YoVN3cBkd7rnGjQsAEmO7tcVlJ4WLttt4V2ESzXNk6sXofZv/5EbFxcuvMdHeyo7+PJs4+1wmQqmo113SRYsRmuxqR0n5SanQk8Xex57r6qOBSiP+Hcto1qF0XyjnIOEbmb3hHFLDgkmhHv78Kz9QJK+M2nSpfFTFtwhPg7WYzEZAVenQdT97P9OHiUJSHyFmF7V3Ht1885/W43Io/tsHZ5+S7JgF8uwGOboM0aaLcW3vwLLkVZuzLL2hZ6nQ9OHTO/7rp7q/WKEYv766+/aNasGSaTiW+++YYdO3bw+uuvW7sss58/7UBT39K4uaa+Pu7ibI+HmyMbZz1s0cBeJD3VSsAnLcHFPjmgv5urPbTwgkmNrVObFE223rYXdO5uTmz7rjteJZ0p/q/3J7diDtSu6s66rx4qsoE9gIMdzHoAyrokt4N3c7KDMi4w60EKVWAvUphdvxHDmCm7Kd1mISX85lOp049M+e4QsXG5vYSff4p6zlGQKJNJpkwmfxWaO+0DAwN54YUXuH37Nh06dOD69essWbLE2mUVGOcu3cav36/cCIsj8f9vKYmMTuA/M/axdMM5tnzbDSfHjB8RtQaTnR1lur5IyJqZVHj2XWIuHsdIuINbvdbWLi1fGQZM2Ae7r0PMXddT1l+GHdfh61bgW9Jq5YlYTGhoqLVLyFSJ4k7s/L4nATsv8fnCoxw/F8bFq1GMeroek15ogkcJDSwltsGvLKzqBCuCYHlQ8t2l9TxgdH1oVhqKcHYnVmDrbXthUN+nFOfWP8WPa88wfdFRjp4Jo2V9LyYOaUzPdlVwUBqNdzFY3gE2BcPP5+BSNNyMg95VYVQ9cLKtr0UiNsEWM5lLV6O475lfCLkVS0LiPznHO18f4KeAc+z8vgcuzrYVixXVnKMgUSYjllIoPpHFxMTQr18/FixYwJEjRzh27JgGc8mhZyZs4frNWHNgnyI6NpGDJ27w5Y/HrVRZ5ko06kjEoQ0kRoUTGjCLcr3GWrukfLclGPaEpH5zADCA23fgtT81xqOIrbCzM/Fwm8oE/K8ra2Z2AaBfDx8F9mJzSjnD87Xh1frJrwfWguZeCuxFCiu3Yo688LgvP05pD8C377ThsY7VFNjfxckeHq4E37WBCf//1bJZaQX2Iumx1Uxm0FvbuXojxhzYp4iJS+TY2TCmzrXNcSmKYs5RkCiTEUspFJ/Kli9fTvv27alVqxYmkwlfX18aNmzI7du3GTBgAIMGDWL9+vXWLtNmBV25zfGzYRnOj45NZNrCI5YrKAslGvpT/sn/AMlXoX2n7ubS/PHYubpxffUXRJ89YOUK89f8vyE6kyf5IhMg8Jbl6rG2eRfO0WnXFjrt2sL+8CJ04CIiIiIiIjbAFjOZazdi2HskJMPwNDYukS8X287NiUU95yhIlMmkpkwm/9jWc0C5FBgYSOPGjVO9Hjt2LCtWrKBfv3507tyZ5557jq5du2a6ncTEREJCQvK7XJuz91DWjyHfCIvl6tWr+VpHUlJZcnsdqepL/8vF/pK4evV6rvZnTRciMz9PcQkGgZfDKRsfa7mi8kgpIynHvwEDq1RnUu3kARJz2n9akpGU77/X1lCmTBns7XUbmNybW7tXEHf5JAm3Q6k06BPirp4ldMO3GEkJVHzuQ27t+Im44NO4VGlAqVaPWbtcEZF8l1W7aNyJ5cyUx6n8whe4lPexdrkiIhZji5nMvmO3shzgOyrmDhcvXcExH58yUs5R+CiTSU2ZTFp5lckUitDe09OT06dPA7BixQoOHDhA9erVWbJkCffddx9AtgZTCgkJoXz58vlaq01yqQzVx4KDW4aLREdG5Pu5afJjOPbF3PN1H3eLjIwskD/v+t/8jUv5mhnOj44MZ8Tg5wj/c7UFq8oboV0fw93R0WL7i7xdMH8HshIcHIy3t7e1y5ACzs7ZlTsRIdgX8wAg7I+VlHvsNW4HbiHm/CHc6rXmduBmivm0sHKlIiKWkVW7GHlsByXve9TKVYqIWJ5NZjJO5aDmJHAonuEiUZGRVKlcMW/2lwHlHIWPMpm8o0wmc4UitO/fvz/dunVj7dq19OzZk/r162MymahYsSKXLl3C19c3W9spU6YMwcHB+Vyt7TEMg+bPbiY4NP2rgPZ2Jp7tVZcpm/P33Dyxz42YpHzdRSpubm4F8ue97EoxFl02iDfS/9Dj4e5O4MrZOBbAzq/cRo2H2DjL7a9EwfwdyEqZMmWsXYIUAnGXT1Fp4MdcXjAp3flOXpWo8tL/CFn7pYUrExGxjszaRSMpifiQIOJvXsHk4KQ77UWkSLHVTKbVwC2cvxKd7jyTCXp38WHmJuUckjPKZPJwf8pkMlUoQntvb2/2798PwKZNm8yPVjz22GOMGjWKJUuW8Mwzz2S5HXt7+yJ7d+r/3mpDv4lbiYi6k2Ze6ZLOfDjmAbzLZnyFOi/Y2QEWfDOzs7MrkD/vAaVh/Q24Hgv/Gk+H4g4wsq4dlSsUvOMCuGPK2btaO6+ytPMqa369vpV/jta3MxXM3wERS7B3K8XV5VMAuLnjJ0re34trKz7GSEqk5P2Pcm3lpyTG3Ma1SgMrVyoiYhlZtYvFfZoTumkebvVaW7lSERHLstVMZs7ktvR+ZVO6OYenuzOfjHsQb+8Seba/9CjnKHyUyfxDmUz+KhSh/d0OHz5sHqW8RIkSzJ0718oVFQw9/auw4MN2jHh/F7ejEwi/HY97cUfq1ijJD1P8qZDPgb1kn5sjzG8Lb+6HI7cAA6ISwd0RRteDR6tau0IRKQxKt38uzbSKz71v/ne5XmMtWY6IiNVl1S4CeHUcaKFqRERsky1lMh39KvLzpx0Y+s5OwiLjzTmHTxV3Fn3kT7WK+RvYS+GkTEYspdCF9q+88oq1SyiwHvGvSs92Vdiw+zIPvRjAxtldadmgbNYrisV5OsOXrSAkFn6/Ch8chq9agW9Ja1cmIiIiIiIiRZWtZTJdHqjEufVPsnnvFTq9sJ6Abx7Cr3E5a5clBZwyGbGEAtjDkuQnk8lEo9qeAFT2znhgWrENZVygQankf9tlPa6PiIiIiIiISJFiMpmoXzP5i7Purpe8pExG8pNCeylwIo/v4txn/QBIiovm2q+fc/rd7iRG3yb6zH6Cvh5u5QpFRERERERERLIns5zj+pqZXFv5KUZiopWrFBFLUmgvBY5b3Qdwrd4EADvnYpR7ZAzFa9+PnZML0ecP4+xd07oFiojYME+norlvkVRKehTt/Usq1m6brL1/ETNrtk1qF6WIyyjnMJISiTr1B9jZW7dAEbG4QtenvRQ9cdfO41yuOtHnDnLn5hWiTv3BnQ4DcPQoY+3SRERsip0J1j9k/RpErMlkZ4fDvG+sXQYmO907YwtsoV1MqUPEmmyhbVS7KPKPlJyDpEScy9XApVJdos/up3itltYuTUQsRKG9FDgxF44Seex3bpWrTqkH+hC29xdKtx+Ag1tJitdqydUVnyiwFxHJgIIhEQVDkpraRclrkydPxs3NjcuXLzNt2jQAYmJi+P777ylXrhx79uzhvvvuo3fv3gAcP36cX3/9ldOnT/P1118zc+ZMqwzmqbZRxHoyyzkwmbh9eBPej0+ydpkiYkEK7aXAca1SH583fjG/LtdzdKr53o+Ns3RJIiIiIiIiZmPGjOG1114zv161ahXt27cnKCgIFxcXoqKizPPq1q1L3bp1eeutt4iNjaVEiRJcvXoVb29va5QuIlaQWc5R4enJVqhIRKxNl9JFRERERERE8tCsWbNo2rSp+fX58+epUKECnTt3ZvLkyRw9ejTV8itWrKBp06aUKFGCihUrcu7cOUuXLCIiIjZEd9qLiIiIiIiI5KGhQ4fi4PDP1+1q1apx8eJFQkJC2LFjB/b2yYNKfv/99/j6+vLll1/So0cPwsPDuXz5Ms2aNbNW6SIiImIDFNqLiIiIiIiI5JHJkyenmdazZ0/mzZvH8OHDadu2rXl6//79Adi4caN52u3btylXrly+1ykiIiK2S6G92Awv58K9P8kGz1Jw08L7ExERERHJZ66urgwfPjxby1pjEFoRyR/KOaRAUSZjUxTai81Y1tHaFRQNH374IXZ2dpQuXZohQ4YAMGfOHHx8fFizZg3du3fH398fgE8//ZQbN24wduxY1qxZw65du+jTpw/nzp1j6NCheV6b49fT8nybIiIiIiIiItagnEMKEmUytkUD0YoUMY6Ojrz++utcuXIFgCtXruDp6YmLiwsuLi7ExcWlWn7YsGFs3bqV/v37U7VqVfz9/XFyciI8PNwa5YuIiIiIiIiIiBRqCu1FipiEhAS++uor2rdvD8DFixcpV64cfn5+/Pe//2XXrl0Zrnvnzh0cHR3x9vbm8uXLlipZRERERERERESkyFD3OCJFjIODAy+99JL5deXKldmzZw/79+9n3bp1eHp6cuLECRITEzGZTMyaNYtXX32VP//8kxYtWgBw9epVWrVqZa1DEBERERERERERKbQU2osUMePGjUv1ukKFCty8eZPevXvTrFmzVPPq169v/nfp0qXN/46Pj8fDwyN/CxURERERERERESmC1D2OiJgHpM2u/BiEVkRERERERERERBTai4iIiIiIiIiIiIjYDIX2IiIiIiIiIiIiIiI2Qn3ai4iIFCFJhnX3b2ey7v5FRP7N2u0iqG0U22DtvwX9HYiIiPxDob2IiEgRkWRA1wC4GW+d/Xs6wfqH9KVcRGyHtdtFUNsotsHafwv6OxAREUlN3eOIiIgUIdYMpqy5bxGRjFi7bbL2/kVS6DOCiIiI7VBoLyIiIiIiIiIiIiJiIxTai4iIiIiIiIiIiIjYCIX2IiIiIiIiIiIiIiI2QgPRis3oswlC4yy3Py9nWNbRcvsTERERERERkaJDOYeI5JZCe7EZoXEQlWDtKkRERERERERE7p1yDhHJLYX2IiIikkbctfMcH9sc16oNSYqLplzPMXi2e8baZYmIWJXaRpFk+lsQERHJXwrtRUREJF1udVrh8+ZqkuKiOTaqkb6Mi4igtlEkhf4WRERE8o8GohUREZFMJUZHkJQQb+0ysuXOnST6jt/M8o3ns73O3xciaD94LZeuRuVfYSJS6BSktlEkPxWkv4V3vt7P5K/2Z3v5iMh4ur64nt2HruVjVSIiImnpTnspcG4HbuX8F4NwKlsNe9cS1Hh9KXaOTtYuS0Sk0Ik8uZuTE9sSffYAVYZ/be1yssXe3oSbqyNPjd/Mkqkd6N2pWqbLpwT2pUs64+Jsb5kiRaRAK4hto0h+KIh/C16lXBj5wW4AJr/ULNNlIyLj6To8gNMXIihRzNES5UkRppxDRP5Nob0USF6dB1P+yf8Q/PMH3D68CY/mD1u7JBGRNCIi4/luxSmmLzrK5evRAEz+ej8fjGpBneolrVtcNqQ89h5xaBM3Ns+jtH8/wvauIu7qGe7cuMydm1eoPnaRtctMxc7OxKy3WwNkGdzfHdhvnPUwXqVcLFipiBRUBbFt/Ldzl27z+aIjzP/lNACdhq5jTL/6DHuiLp4ezlauzjaExsJP52DlBQj//5vI112CRp7gpbcLoGD+LYzoWw8gy+D+7sB+y5yHaVDL02I1StGlnENE7qbucaRAS4wKs3YJYiFx8YmMmbKHq6HRWS5rGAaTv9rP4VM3LVCZWFpoaChPPfUUDRo0wM/PjwEDBjBlyhQaN25MgwYNrF2e2ZXrUTTsvZw3Zuzj/JVI7iQkAfDL1gs0fWolKzadt26BOeDeuCMJYdeJuXCUkvf1xKvjIBKjwqg6cra1S0tXSnA/oGctnsqgqxwF9iK2paC07XcraG1jik17rtCg93K+XnKC8Mg7AFy/Gct//3eQuo8u5e8LEVau0PpOhEGfzbDwDNyMg0Qjefrv15KnHw+zZnW2p6D9LYzoW4+Zk1rxzjcH0u0qR4G9WJst5hxJSQZvfLGPk+fCsrX8l4uPsWnPlfwtSixKmYzlKbSXNAzDsHYJWQrd8C3HxjQh6uRu3Bt3snY5VpVo+z+uPJGUZHDs7C06DFmX6ZuEYRiM+3Qv0xcdJf5OogUrFEswDINHH32ULl26cOTIERYsWMCCBQto0KABO3bsoFKlStYuEUius9uI37gSEk10bOrfw8REg5jYRPpN3FqggpEyPV7m2oqPSboTz6X546nw3AfYORezdlkZyiy4V2AvYlsKStuenoLWNl67EcOjozcQHZtgvpicIiYukdCwWDoPXUdSUhH5gJmO2AR4aTdEJUB86lNEgpE8fcTu5OXkHwXtbyGj4F6BfeGlnOPeGIbB5evR+A9em2VwP+OHo4z9ZC+x8WooCxNlMpan0F7Mgq7cpv+krdTuuRSAhn2W8+43+4mNs72G1qvzYOp+th8Hj7IkRN4ibO8qrv36Oaff7UbksR3WLi/fJRrw41no/hu8sDN52qdH4Oxt69aVn1xdHPhlemcqlSuW4ZtEypvDdytOsWFWV1rUL2OFSiU/bdy4kaSkJAYPHgxArVq1cHV15YEHHqBEiRJWru4fewNDOB0UQUImV9XuJBhMX3TEglXljHO5avi8udr8umTLHlQbPY9L376CycGJm9sWEbrxOytWmLX0gnsF9iK2p6C07VDw28b//XycxEwC+aQkCA2LJWDnJQtWZVsCrsCdpMyXuZOUvFxRVtD/FiBtcK/AvnC6fC2KwW9tp84jywBo8Nhy/jNjH1HRd6xcWVq2nHPY29vx7TuteeiBSpkG9zN+OMprn/3Jss860L1tFcsWKflKmYzlFZo+7QMDA3nhhRe4ffs2HTp04Pr16yxZssTaZRUYJ8+F8eCA1dwMjyPlAnTorTg+/PYwv2y5wM7ve+DibFu/LiY7O8p0fZGQNTOp8Oy7xFw8jpFwB7d6ra1dWr5KMuCVP+DADYi566LlXzdg8O8w3S+5r83CKOVN4tHRG+gwZB2b5zyMt1fyHTx6cyga9u/fT8uWLc2vT5w4gbe3N6VKlcrV9mJjYzl9+nRelWf2zY8XiMniguedhCR+WHOaoT0sF0gl5zQN72kbVV788p7WDwwMxM50T5vIsZd7e3DzlhdPjtuEq4sd5Us788UrVQm+dJrgoptLpSsoqgRQjaAL5wm8UYivBBdAtWrVwsWlcF5ksmbbnhftIhSctvG75ceJjcv8rrfI6AS+/vEvKpUMy/+CbNBPwdWJSXTLdJmYRFhyIpIa4ecsVFX+s4W/BWt8RmhbHyYNqMo73xxgxg+BJCXBt//xxYi9TGDg5Xzff2Fq220xkzl36TZ+/X4lNCyWpP+/GHcjPI5P5geycnMQfyx6hOI2NsiwLeccKcH94Ld34D94LVu/7ZZqnC4F9oWfMhnLsq0UNpdiYmLo168fS5cuxcfHh06dOtG+fXtrl1Wg9Ju0jRthcWmmx8YlcvxsGNMXHeX15xtbobLMlWjUkSs/vEW5XmMJDZhFpec/tXZJ+W7jFTh4M3Vgn+J2AkzYB2s6g8nCH3gtJb03iXKlXfXmUER4eXmxYcMGDMMgOjqakSNH0rx581xv7/Tp0zRq1CgPK/x/FfpBaf8sF7t563b+7D8jJhPNV2Zx+2A+a9KkMVjj8WSnclDrbSKjnTh95gfaPbjG8jUUAB5+j+EzcTmvvPIq4XtWWLscucvhw4dp2PDeAzVbZNW23QbaRbBg2+j7MThmfTFk1ZqNrPr6sfyvxwb5fvonxX1aZLncgSMnafRQ1ssVGDbwt2C1zwh2LlDrXW5GeELYXvp0H2ixXReWtt1WM5lBb20n5FZsml+ruPgk/r4YwYffHuK9l23v79iWc46MgnsF9kWHMhnLMRkFoWOvLCxatIg///yTzz//HIARI0bQpUsXqlSpwkcffUStWrV47733rFukDTt7KYJmT/1C+O34DJepULYYlzc+na91tFub3EdkbgR9NQwHj7I4lChNiQbtKFajaZbrFHeAbd1ytz9r6rcNToRnPL+EI0y7D5qUtlxN1hATm8Cjozdw6Vo0bZuXY8n6c3pzKAKioqLo3bs3Z8+epXbt2pQqVYqGDRvy+uuvA9C1a1fWr1+f7e3l1532C9cFM/2nS8TFZ/4WW7OiKyumWu6LWpIBg89b94vht9UsfxfdhauxDH7/BC5OJoKuxmFvBx+P8qFTy0L6WNI92BdVgi+vV2NE2fO0KK477W1JYbob89+s2bbbQrsIlmsbB/33OH+dyPxv29HBRP9u3ox+qnL+F2SDZl2vxJ4oD4xMepI1YXB/8XCGlb1owcryly38LVjjM0JkdCLDp57k3JUYIqKS74p6sXcFXupjmbE0CkvbbouZTHBINHUfXWoecDs9XiWdCdneL1/rKKw5R2JiEoPf3kHArksMfqw2n8w/UmQD+1Ph8Mw2+KEd1PawdjWWo0wm/xWKO+0DAwNp3Lhxqtdjx46lRo0aTJkyhTlz5mRrO4mJiYSEhORXmTZr3+HQLO9oCIuI4+rVq/laR1JSWXI7zELVl/6Xi/0lcfXq9Vztz5ouR2Z+nuISDI5eCcf7TqzlirKSbyY2pO2QbcxeepL577agUunEfP89LQjKlCmDvb29tcvIF8WLFycgIMD8+qGHHqJ58+aEhobSt29fDh48SKdOnVi+fDnu7u5Zbs/FxSVf7m56rVItpv/0I5Bx21rc1YFJQ1vQsGHtPN9/RpIM4LzFdpeuhg0bWvQL+d8XInjx1bV4l3Hji9f9aPf8Wh5pX5XxM8+wZGpVeneqZrliCoCQK8B1qFqlGg0rWLsaKSqs2bbbQrsIlmsb//NiCZ6buI3ImIwTJDs7O/4zvA3VKtrWeAKWMuwWHNgFsZn0IuRsb+LFZiVpUKqkxerKb7bwt2DpzwgpfdhfDk1g7n/96fPqJiYNbswH3x6iXNlyTH6pmeWKKeBsMZP569itLJ8+j4lL4OKlKzg65N9wj4U553h/eC0OnbjO+7MP8dHLDWhe26lIfh+/EeUAeHEjNJSrmby/FkbKZNKXV5lMoQjtPT09zXfTrFixggMHDlC9evUcbyckJITy5cvndXm2z6USVB8HDhn33RgdGZ7v56bJj+HYF8v6i1heiYyMLJA/7/pfn8SlQsYhX/TtcF4a9AwRf62zYFVW4v0keLYG+2I8N341nJ0KCRHWrsrqgoOD8fb2tnYZFvHXX3/RrFkzPD092bhxo7XLMfMq5cL4QY34dP4RomPTfnBzcrSjindxnn64hhWqKzr+PehscEjyYElvD2uKp7szT43fzJKpHRTci9gYW23bC4Oe7apQ36cUB0/eIC4+bVcoxVwc6N/Tp8gG9gD1S8J9ZeCP6xCXTm8xznbQ0it5OSm4/j3obMo9bH0frkGFcsUY+cFuAAX32WSTmYxTWaj5BjgUz3CRqMhIqlSumDf7y0ChzjlKdwDvJ8DOkQnTdjFh5KMQf80y+7YhLlUbUP+LQDp0bE9s0BFrl2NZymTSlVeZTKEI7fv370+3bt1Yu3YtPXv2pH79+phy0aF3mTJlCA4OzocKbZthGNzXfwuXrsWkO9/ezsTAxxvw3kv5e26e2OdGjAW7UXRzcyuQP+9frhZj/sUk4o30r9aX9HDnyK/f4Zh/NwtYnWEYvDPrOIsDLvL1xKY888af+LWox41as1g69X7Kehb8R0zvRZkyRedxtNDQUGuXkKF3XmqGvZ2Jj749jJ2dyRzeOzvacX+jMiyf1glXl0LxNmyT/h3Ye5VyMYf2dnYmZr2dPJiXgnsR22PLbXtBZ29vx4ZZXXl6/BY2/RFMQmISCYkGzk7JHxxf6FOHT8fdZ+Uqrctkgikt4L8HYcMVMAHxSZg/W7cvD281KbzjRxUF/w7sG9TyJPDUTfP8EX3rASi4zwFbzWTaDt7K6YtR6c6zM8GT3WoxbZNyjtz4duU5/jv7BJ+80pCXpx7ikQ512FP2E5Z97IdP5cwH8y5szkU58PJR2LxpC9WLF4077ZXJZC6vMplCkRZ4e3uzf/9+ADZt2mR+FOPcuXP85z//4cSJE/j6+tKvX+Z9ldnb2xeZu1P/7bt32/L4q5uJiErd35vJBGU8XXh/9AOUK+2arzXY2QEWfDOzs7MrkD/v/l6w/gZciYbEf/W8UdwBxjawo3KFgndc2ZUyIvmS3y6zcXY3KpUrDvzJD1M7MuzdnfSd9FeqEcxFrMVkMvH28GaMerY+i9ed5Y/D15m/6m9+nNqexzpWs3Z5hVp6gf2/KbgXkaKqRHEnVn/5EH9fiGDGD0f54odjjHm2AWMHNKCMZ/5+3i8oHO3g3WYwoi78dhlCY8HLBbpUhHI6RQVaeoF9ehTc54ytZjLf/def7iN/IyKdfu29Srnw8djWeHtnfCd+XiiMOceMH47y3zknWTatI83refHy1EN89WZb3pixnyde32senLaoiPj/MQdLe3nhXQT6tFcmYzmF7l7cu0dfr169OosWLeKvv/7K8s2hqOvcqhLLpnXEp7I7Hm6OAJQo5kiH+8rz5w+P5ntgL9nn6gDz2kDbcskhvbtj8n9lXeCNRtCzEI/7kvLmkN6I5K7OySOYVypXjA5D1nE1NNqKlYr8o5S7M8OfqsvYAcnvTT6VLfd4bFEUfyeRh15cn2lgnyIluB/QsxZPjd/MoZM3LFipiIh1+VRxZ0jvOgA8272mAvt0lHOF53zglQbJ/1dgX/D1f2NbloF9ihF96zFzUive+eYAC1f/baEKCz5bymRaN/Nmzcwu+Fb3+CfnKO5Im2bl2L2wJ5XyObAvjGb8cJTXPvszzaCz9vZ2fPtOax56oBL+g9dy8lyY9YqUfKNMxrIKxZ32d3vllVesXUKB1cmvIqfXPMGO/VdpM3ANO77vTqPapa1dlqTDwwk+vg/C4+FCZHKQX7NE4X5MN7M3hxSuLslvEo+O3kCHIet0dVfkHkSfP8ztgxtIiLxFxX7vEXftPNfXzMCIj6XCs/8lbM8K4kMugGFQ4dl3rV2umZOjPd+8+SBNfUtnGtinSAnuuzxQkUa1M//yLiJF263dK4i7fJKE26FUGvQJsZdOcOn7CVQa9Aku5X0IWfcNSfHRuDfpgmvVBtYuVyTfRJ7cw+XvJ1D73Y2Y7B2IOrWX8H1rMNk74P3kf7i+ajpxV89QrEZTvDo9b+1yU3lvZHOALAP7FCP61qNi2WI89ECl/CyrULG1TKZ1M2+O//I4uw9d44HnVrN9bjea+HpZu6wCKaPAPkVKcD/47R34D15b5O64L+yUyVheobvTXu6dT5Xku0DL6m4bm+fhBA09wce9cAf2ABGRd9hz+HqGbw4pUt4kqld0Y/9x3TUrklvFqjXC5OBEYnTyQEImB0eSoiMwkhKxL+aOV+fBOJbyxrNDfytXmlbnVhWzFdinsLMz8eRDNXLV96qIFB12zq7ciQjBzjX5s7JLJV9K3t8LgMToCMIPrAeTHSYHJytWKZL/3Or4UaKBv/n17SPbKPPwcJLiY0iICKXcI2NwcPei1AOPW6/IDDSo5ZntwD5Frw7VNA5RIVD9/wfYVoCYOwkJSazadiHDwD7F3Xfcb/vrqgUrlPymTMby9M4jBU7k8V2ErPuKkvc/Smzw37jVfZAS9dsCcGneeIrXuo9SD9reB0S5Nx4lnNgxv0e2QjVXFwdWz+yiAE7kHpXt8TLXV30BQPy1c3g9NIy4a2eJOX+YYjWbERv8N2UeHm7lKkVELCPu8ikqDfyYywsmpZlnJCVi7+JG2e4vc3nBRCoNnGqFCkWsw7Pds9zYupC44L8x2dljJCaSFBuFfTF1CSiSXZnlHNFn9hPy22yqDv/aavU5ONgR8E3XbH3Htre3Y+5/2+j7eCGjTMbydKe9FDhudR/AtXoT4kMuUP7xiUSf/tM8r8zDL1mxMslvOWnw9eYgcm/C9wdwdflU4kMvcmPz99i7eXJz6wIij+3AqVx1Yi+dwLVyPWuXKSJiMfZupbi6fAoAN3f8RPyNK0Qc/I0bm+bh4FYKpzJVuPbLp7jVa2PlSkXyV+ylE0Sd3EPI+m+4ueMnjMTkQT7d6rfFoYQnEYc24N6ki5WrFClYMso5jIQ7RJ8/jLN3TStXqO/jot8BS9Od9lLwqSEQEclzHs0ewqPZQ6mmVX5huvnfDm6lcKnka+myRESspnT759JMqzH2B/O/Kz73gSXLEbEal0q+1Jq8PtU078fGmf/t0ayrpUsSKXz+P+eIPneQOzevEHXqD+50GICjR8bdkohI4aI77aXAiblwlMhjv+PoVZmrSz+imE8Lbu1aBsCtnT8Tvm8NSXEapVpEREREREREbF9GOUfxWi0p/8Qk3Oq1UWAvUsToTnspcFyr1MfnjV/Snefd+zULVyMiIiIiIiIiknuZ5RyQ+mkWESkadKe9iIiIiIiIiIiIiIiNUGgvIiIiIiIiIiIiImIjFNqLiIgUIZ5ORXPfIiIZsXbbZO39i6TQZwQRERHboT7txWZ4ORfu/YmIWJudCdY/ZP0apPCZPHkybm5uXL58mWnTpgEQExPD999/T7ly5dizZw/33XcfvXv3BuDq1atMmDCBfv360alTJwYOHEiTJk148skn2blzJw888AAVK1a05iFJEWEL7WJKHSLWZAt/C/o7kMJIOYeI5JZCe7EZyzpauwIRkcJPX4glv4wZM4bXXvtnQPhVq1bRvn17goKCcHFxISoqyjzP29ubgQMHkpCQAICXlxe3b9/GZDLRs2dPZs6cybhxGnBNLEPtokgy/S2I5D3lHPJvH374IXZ2dpQuXZohQ4YAMGfOHHx8fFizZg3du3fH398fgOnTp3PixAneeecdNmzYwP79+3n11VdZs2YNQ4cOteJRiCWoexwRERERuWezZs2iadOm5tfnz5+nQoUKdO7cmcmTJ3P06NEM1/3kk094+eWXmT9/Pi4uLkRERFiiZBERERERi3J0dOT111/nypUrAFy5cgVPT09cXFxwcXEhLi7OvOzo0aNp164dt27domXLlly7dg0HBwecnJwIDw+31iGIhehOexERERG5Z0OHDsXB4Z+PltWqVePixYuEhISwY8cO7O3tAfj+++/p3bs3S5cuJSkpifvuu4+vvvqK69ev07dvX2JjY/Hw8LDWYYiIiIiI5JuEhAS++uor2rdvD8DFixcpV64cfn5++Pn58fbbb/PQQw+Z5wUHB9O3b18g+cnWoKAgvL29uXz5sj4zF3IK7UVERETknkyePDnNtJ49ezJv3jyGDx9O27ZtzdP79+8PwMyZM83TJkyYYP73zz//bP5iIiIiIiJSmDg4OPDSSy+ZX1euXJk9e/awf/9+1q1bh6enJydOnCAxMZHXXnuNTp06cfHiRRYvXszff//NpEmTOHbsGK1atbLiUYglKLQX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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "plot_k_best = 4\n",
+ "\n",
+ "idx = np.argsort(U_norms)\n",
+ "fig, axs = plt.subplots(1, plot_k_best, figsize=(10, 2), constrained_layout=True, dpi=150)\n",
+ "\n",
+ "for i, (idx_i, ax) in enumerate(zip(idx[:plot_k_best], axs.flatten())): \n",
+ " ax.clear()\n",
+ " generated_qc_list[idx_i].draw(\"mpl\", plot_barriers=False, ax=ax)\n",
+ " ax.set_title(f\"The {i+1}. best circuit: \\n infidelity {U_norms[idx_i]:0.1e}.\", fontsize=10)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "83e74d47-2ea6-4489-8db8-1c12f911cee4",
+ "metadata": {},
+ "source": [
+ "## Compile testset unitaries"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d5ba0233-747b-4010-905b-724cdf98aa16",
+ "metadata": {},
+ "source": [
+ "To get an overall performance estimation, we compile multiple unitaries, record the best infidelities and plot the distribution."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b113a7bc-e33d-47be-9e0d-08e5676ce3fd",
+ "metadata": {},
+ "source": [
+ "### Generate tensors"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0365b747-1bf9-445d-a2f9-e6926df11732",
+ "metadata": {},
+ "source": [
+ "To keep the tutorial short in computation time, we only take a few unitaries here, but this can be adjusted by the user to use the full testset."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "26bb8354-af39-4059-a468-b9bc744dab09",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "Us = target_us[:16]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "eb331ca2-b61a-4f3b-88e4-4b37a4a11886",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "41ecaa3e39064fdabb2618c82cc2c817",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/16 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "best_infidelities = []\n",
+ "\n",
+ "for U in tqdm(Us):\n",
+ " out_tensor, params = generate_compilation_tensors(pipeline, \n",
+ " prompt=prompt, \n",
+ " U=U, \n",
+ " samples=num_of_samples_per_U, \n",
+ " system_size=system_size, \n",
+ " num_of_qubits=num_of_qubits, \n",
+ " max_gates=max_gates\n",
+ " )\n",
+ "\n",
+ " generated_qc_list, _ = decode_tensors_to_backend(simulator, tokenizer, out_tensor, params)\n",
+ " generated_us = get_unitaries(simulator, generated_qc_list)\n",
+ "\n",
+ " U_norms = UnitaryInfidelityNorm.distance(\n",
+ " approx_U=torch.from_numpy(np.stack(generated_us)).to(torch.complex128), \n",
+ " target_U=torch.complex(U[0], U[1]).unsqueeze(0).to(torch.complex128),\n",
+ " )\n",
+ "\n",
+ " best_infidelities.append(U_norms.min())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3696b804-cc4f-418a-976e-9180bc70334e",
+ "metadata": {},
+ "source": [
+ "### Plot infidelities"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "519e4e8d-ca3a-45a7-a25d-89cc6a1ce7e7",
+ "metadata": {},
+ "source": [
+ "For the compiled unitaries, we get the following distribution of the best infidelities."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5b27912e-28db-44c5-b2bf-aa57f8491c41",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAABCsAAAHTCAYAAAAOMpKCAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAXEgAAFxIBZ5/SUgAAbRVJREFUeJzt3Wd4FGX/9vFz0xOSkEDoJUDoKF2kg4iA0lFQEaVJEUXUP3JbEBDUW1RUEBUUBCwgxYoiClIE6QiiNCVA6J1AgBBIcj0veHbuLNmElN1kE76f48ghTv3N7rWzM+fOXGMzxhgBAAAAAAB4CK/cLgAAAAAAACAlwgoAAAAAAOBRCCsAAAAAAIBHIawAAAAAAAAehbACAAAAAAB4FMIKAAAAAADgUQgrAAAAAACARyGsAAAAAAAAHoWwAgAAAAAAeBTCCgAAAAAA4FEIKwAAAAAAgEchrAAAAAAAAB6FsAIAAAAAAHgUwgoAAAAAAOBRCCuADNi/f79sNptsNpv279/vlnWsWrVK7du3V5EiReTt7S2bzaYuXbpIkmbOnCmbzaZy5cpladnZnT8t6b0urnjN3FW3p0pKStLbb7+tOnXqqECBAtbr9+233+Z2aXleTnyGXaVly5ay2WwaM2ZMbpfikbL7+uSltuBJxowZI5vNppYtW+Z2KZlmf79XrFiR26UgHbnZxtgv5L5y5crJZrNp5syZuV0KPIhPbheAvGPMmDF6+eWXnY4LDAxUqVKl1LhxYw0aNEiNGzfO4erSZj+g7dOnj8ee9K5bt06tWrVSYmKibDabChcuLG9vb4WHh+d2aW6xf/9+68uIE7L/eeqppzR58mRJkp+fn4oVKyZJCggIyND8sbGxWrlypTZv3qw//vhDmzdv1rFjxyRJM2bMUJ8+fTJcy549e/Thhx/q559/1sGDB5WYmKhixYqpZs2aatOmjYYMGZK5jfNgtMf8491331VsbKy6dOmi2rVr50oNV69e1cyZM7Vp0yZt3bpVR44c0alTp2Sz2VSiRAndfvvt6tevn1q3bu10/qSkJK1YsUI//fST1qxZo927d+v8+fMKDg5WtWrV1KFDBz322GP59vshJ8XGxurdd9+VdG3/GxYWlqv1AHAuLxzLwz0IK5Al9pMoSUpOTtaZM2e0Z88e7dmzR59++qlGjx7tMQf99oClZcuWWd7B+fr6qkqVKta/Xe3dd99VYmKimjRpou+//16FChVyGF+wYEFVqVJFpUqVcvm63SW912z//v3W+5JeO8mL251VcXFxmjp1qiTpjTfe0PDhw2Wz2TK1jG+//VZ9+/bNdi3vvvuunnvuOSUkJEiSgoKC5OXlpX379mnfvn1asWJFngsrXNEec0rZsmVVpUoVRURE5HYpHim91+fdd99VTEyMypUrl2thxblz5zRw4EDr/202m8LCwnT+/Hnt3btXe/fu1Zw5c9S7d29NmzZNPj6Oh2KDBw/WtGnTrP/38vJSaGioYmNjtXbtWq1du1aTJk3St99+q4YNG+bYdkVERKhKlSoqW7Zsjq3TVeyf/aCgIIfhsbGx1me/T58+hBVALoqKilJAQIAKFiyYapwrjuWRNxFWIEvsv9baJSUlad26dRo2bJg2b96sl19+WW3atPGoKyyyo1SpUtq1a5fblv/XX39Jkh544IFUQYUkde3aVV27dnXb+t3BFa9ZXtzurNq1a5euXr0qSXrssccyHVTYFS9eXHXq1FHdunVVr149devWLVPzv/322/q///s/+fj46Pnnn9eAAQNUvnx5SdLZs2e1YcMGLVmyJEu15SZ3f4Zd6dNPP83tEjyap78+/v7+Gjp0qJo2barbb79dJUuWlK+vr5KTk7Vjxw69+uqr+vLLLzVr1ixVrVpVzz33nMP8V69eVdGiRfXII4+oW7duql+/vnx9fXXhwgXNnTtX//nPf3T8+HG1b99eu3btUpEiRXJku5544gk98cQTObIuV8srn33gZvbrr7/mdgnwQIQVcAlvb281adJE3377rcqUKSNJ+u677/JNWOFuly5dkiQFBwfnciXILfY2IGW9HTz88MOZutXjen/99Zd14vTll1/q3nvvdRgfHh6utm3bqm3btlleB5DfhYSEaNKkSamGe3l56ZZbbtHs2bN14MABrVmzRtOnT08VVjz22GP68MMPFRgY6DA8ODhY/fv3V/Xq1dW4cWOdOXNGU6dO1ciRI926PQAA5BY62IRLlS5dWoULF5YkXbhwIc3p4uLi9Prrr6tRo0YqVKiQ/P39VaZMGT3wwANau3ZtmvOdPXtWo0aNUt26dRUaGio/Pz8VL15cNWvW1ODBgx1S2T59+jj8On3HHXdYnSdlttPG9DpeWrFihTVOunavf79+/VSmTBn5+/urdOnS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nzMjJiZGR44ckSR17Ngx0/PfyG233ZZmCGE/OShevHiana56osDAQN15552SpHbt2mnUqFFav369rly5kq3l2r+Hevfurf/7v//TypUrHZ4KlZ4ffvhB999/vypUqKACBQo4dDg4b948Senvg1Lu21MqVqyY9e/bbrvthtPExsY6naZMmTJOwxBJCg0NVb169SRl/HswO8cqV65csQLHrBwnZYaPj4+aNWvmdJyXl5fV8WjK7c7u8UxKWf1ucbW09qvS/46H3fUdnZycrNmzZ6tTp04qW7asAgMDHT4f9h9asvMdfddddykqKkoJCQmpHqW9YMECxcbGKjg4WD179sz+BoGwAq4REBCg2rVra9q0aeratasSEhLUp08fhxNA+wGS5HhlhrM/u5Rf3M8++6x69Oihq1ev6uOPP9bdd9+tsLAw3XrrrXr22WczddDnLiEhIU6H+/j87ynBV69eddn67Af/kqyTYGfSSojt70lycnK670fK5DijB1Pu5Iltyf5epPc+SP97L1K+d54gZdvt2bOnQ8BmV6ZMGevL99dff821q2zS+pxJ//usufJzZvfPP/+oevXqGjFihH777TedPHlSvr6+KlKkiIoVK+ZwEJ/eL45FixbN0vrt7f7y5cvptnn7fjdlm/f29taXX36p8uXLKyYmRs8995zq1Kmj0NBQ3XXXXfrwww/d8tmuUaOGtb32qyvsQYT95MD+X/v4vXv3Wgfj7g4rcrIt+fv7q2HDhlq8eLE6deqkf//9Vw8//HCaT5BJae3atWrfvr3i4+PVpEkTfffddw7fKxmV8oqfyMjITM9/I+m1bfu63bFed5s2bZpq1aqlkydPaty4cWrYsKFCQkLUtGlTvfnmm1k68XrjjTd0xx136MKFC3r77bfVsmVLhYaGqn79+ho9erTTJ4glJyerZ8+e6tixo+bNm6d9+/bpypUrCg8Pt/ZBAQEBktLfB2XkWCU7xzM3+h60j8/o92B2jlXOnDmjxMTEG9aV3i/pGWV/AlhanG13do9nUsrqd4ur5dZ39KVLl9S6dWs99NBDWrhwoQ4ePKjk5GQVKlTI+nz4+vpKyt53tM1m08CBAyVJH3/8scO4jz76SNK14yhPuMolPyCsgMsNGDBAknTu3DktWrTIGp7yxCY+Pj7V1RnO/lI+OszX11dz587V1q1bNWrUKLVq1UpB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pV69eumzzz5T06ZNFRQUpKNHjyomJibXLmVzh4EDB+r3339Xx44dVahQISUkJFhXD2zYsOGGSXKTJk30zz//6K233lLz5s0VFham2NhYeXt7q1q1aurVq5e++OIL61ddV3j//fc1ZswY3XrrrZKkAwcOKCYmxuFqgBvJ6bZ0IwULFtSvv/6q6dOnq2XLlgoJCdGFCxdUvHhx3XvvvVq+fLl1+benqlq1qrZv365Ro0ZZvxImJSWpSpUqevLJJ/X333+rQ4cOWVp2cHCwVq9erWeeeUbly5eXj4+PfH19de+992rt2rXWrTS5ISPtsUOHDvrtt9/Uvn17hYWFKTExUREREerbt682b96sO++8M0dqHTx4sHbv3q3hw4erVq1a8vf3t57rXr9+fQ0dOlRLlixx+AWuU6dO+vTTT9W3b1/VqlVLBQsW1Llz5xQSEqIGDRpo3Lhx2r59u6pWrZpqffZ+Vho2bJjlmlP2P1G/fn2ntzSkvNoiq48sbd68uX788Ue1bt1aYWFhOn78uGJiYnK047rnnntOb775pjp06KCKFSvKZrPp3LlzCg0NVb169TRs2DBt2bJF7777rtPbBlL+wnv27Nl0H2V48uTJLNVYoUIFbdmyRR988IFatmyp8PBwxcXFKSwsTI0aNdK4ceP09NNPZ/k1SM8jjzyiXbt26amnnlL16tXl4+Oj+Ph4RUZGqkuXLvrss89SPZHInfuOF154QRMnTlT9+vXl6+urQ4cOKSYmxuqI77bbbtO8efP02GOPqV69etb3jP2R7SNGjNDOnTvVrFmzDK+zVKlS+v777/X000+rYcOGKlGihC5cuCAfHx9Vr15djz/+uP7++2/ryR52YWFhWrNmjZ566imVK1dO3t7e8vHxUcuWLTVnzhyX3crgCuPHj9eXX36ppk2byhgjPz8/1a5dWxMnTtTixYuzdHVhdo5V6tevr23btunRRx9VqVKllJiYqIIFC6p37976448/Ul3tkFWPP/64fv75Z7Vr105eXl7y8vJS1apVNXbsWK1duzbNK1+yejzjajNnztTEiRNVu3ZtBQYGKjk5WU2aNNG8efM0adIkt647PRk5lo+MjNSmTZvUv39/6xwkICBAHTp00M8//6znn3/e5XWFhoZat2PTsaZ72Izx8F6oAABArjh06JDKlCkjb29v7dixQ5UrV87tkgB4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N25s7Y9feOEFN24JbladO3c2klL9ZUV+PbcjrMhD7CdnjRo1MnFxcdbwCRMmGEmmRYsWGV7WQw89ZCSZbt26matXr1rDhw4daiSZ3r17u7By4H9c1Y5fe+01M2LECBMTE+Mw/J9//jFly5Y1kszzzz/vytIBY4xr98UpTZs2zUgyAwcOJKyAW7myDe/cudMEBASYIkWKmN9//91hXFJSktm4caOrygYcuKodnzhxwvj7+xtfX1+zYsUKh3ErV640/v7+xmazZSuIBpx5/fXXzUsvvWS+//57c/ToUesHt6zIr+d2hBV5REJCgilYsKCRZP74449U42vWrGkkmU2bNt1wWUeOHDFeXl7Gz8/PHDt2zGHc5cuXTZEiRYy3t7c5fvy4y+oHjHFtO07P7NmzjSRTrly5bC0HuJ672vCxY8dMeHi4ueuuu8zy5csJK+A2rm7Dd999t5FkfvzxR1eXCqTJle144cKFRpJp27at0/GdOnUykszcuXOzXTeQnqyGFfn53I4+K/KI33//XefOnVNUVJTq1KmTavx9990nSVq4cOENl7V48WIlJyerWbNmKlasmMM4f39/dezYUUlJSVq0aJFrigf+P1e24/TUqlVLknTkyJFsLQe4nrva8LBhwxQfH68PPvjAJXUCaXFlGz548KB+/vlnVahQQffcc4/LawXS4sp27O/vn6F1Fi5cOHNFAjkkP5/bEVbkEX/++ackqW7duk7H24dv27YtR5cFZEZOtb29e/dKkooXL56t5QDXc0cbXrRokebOnasXXnhBFStWzH6RQDpc2YZXrFih5ORkNW7cWImJiZo3b56GDRumJ554QlOmTNHZs2ddVziQgivbcYMGDRQWFqZly5Zp5cqVDuN+++03/fzzz6pUqZKaNWuWzaoB98jP53Y+uV0AMubAgQOSpNKlSzsdbx8eExOTo8sCMiOn2t7EiRMlSZ07d87WcoDruboNX7x4UUOGDFGVKlX0n//8xzVFAulwZRvesWOHJCk4OFjNmjXTunXrHMa/+OKLWrBgge64447slAyk4sp2XLBgQU2fPl09e/bUHXfcocaNG6t06dI6dOiQ1qxZoyZNmujTTz+Vn5+f6zYAcKH8fG7HlRV5xIULFyRJQUFBTscXKFBAkhQXF5ejywIyIyfa3pQpU7R06VKFhYXpueeey/JyAGdc3YZHjhyp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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "plt.figure(figsize=(7, 3), constrained_layout=True, dpi=150)\n",
+ "plt.title(f\"Best infidelities of {len(best_infidelities)} unitaries, with {num_of_samples_per_U} circuits sampled per unitary.\")\n",
+ "plt.xlabel(UnitaryInfidelityNorm.name(), fontsize=13)\n",
+ "plt.ylabel(\"Frequency\", fontsize=13)\n",
+ "plt.hist(best_infidelities, bins=60)\n",
+ "plt.xlim([-0.05, 1.05])\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d6c5de55-7f37-4b51-9308-e1908b675326",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8c9909e3-a035-46f7-8979-44be322a5fe0",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "genQC Version 0.2.0\n"
+ ]
+ }
+ ],
+ "source": [
+ "import genQC\n",
+ "print(\"genQC Version\", genQC.__version__)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "745e9097-b8fb-4621-af9e-e115630c5a1d",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "python3",
+ "language": "python",
+ "name": "python3"
+ },
+ "widgets": {
+ "application/vnd.jupyter.widget-state+json": {
+ "state": {
+ "10aa5cc8502446c0ba1d3917ecd6790c": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "13d1f24a857c4b97b07ec36fe2b2f3e8": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "16846c1990e24e3b9b246ca483e670aa": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "173f24fff9024479b51f8642c2a20890": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_d3fb9ba04cbe4258b54914829554616d",
+ "style": "IPY_MODEL_4e66f780d7154ec4b7984ea9d58a94d8",
+ "value": " 40/40 [00:15<00:00, 2.67it/s]"
+ }
+ },
+ "1d212f97803d4180bc93e2ccc8277855": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_5098ae4c3dbd4e2daadd9a7cd3358c29",
+ "IPY_MODEL_dc6cbcb4369a41e69129099a53150b72",
+ "IPY_MODEL_173f24fff9024479b51f8642c2a20890"
+ ],
+ "layout": "IPY_MODEL_840bce2699264e83a13ef0f0e5a46f2e"
+ }
+ },
+ "26b2f763e7fe47fb92bac12432a45adc": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_5bc6d07104954da5835822c034ed472f",
+ "style": "IPY_MODEL_d08e198700c640f8bafd47ce5af3182d",
+ "value": " 16/16 [04:08<00:00, 15.70s/it]"
+ }
+ },
+ "363950db340a4e0796907fa34e41d56d": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "41ecaa3e39064fdabb2618c82cc2c817": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_bafdd6a005834225a60dc2d1e061aa64",
+ "IPY_MODEL_d580fa8c95e949a0a71e6341a5e0ec2e",
+ "IPY_MODEL_26b2f763e7fe47fb92bac12432a45adc"
+ ],
+ "layout": "IPY_MODEL_13d1f24a857c4b97b07ec36fe2b2f3e8"
+ }
+ },
+ "48e5d282b12349388b6fdec4929adfdb": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "4e66f780d7154ec4b7984ea9d58a94d8": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "5098ae4c3dbd4e2daadd9a7cd3358c29": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_aa556319d474425f9a0f1f28b45bbe1a",
+ "style": "IPY_MODEL_16846c1990e24e3b9b246ca483e670aa",
+ "value": "100%"
+ }
+ },
+ "5bc6d07104954da5835822c034ed472f": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "616b4af59c894e58afe303d3dbbaf8c1": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "6dbaefdf5c8e48c28cd867610e6ca21a": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_721b89bfef704fe7a381047f9c8f4c8c",
+ "style": "IPY_MODEL_10aa5cc8502446c0ba1d3917ecd6790c",
+ "value": "Fetching 4 files: 100%"
+ }
+ },
+ "721b89bfef704fe7a381047f9c8f4c8c": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "803c865f87fd4693bd655a8f82195f8f": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "836761d0b78f459c98dfbf1e90db4711": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "840bce2699264e83a13ef0f0e5a46f2e": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "8a2c3bff466c4dd683f7a4162a39e5d8": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "8e6a61b9182c484fa1ea066833ffdb70": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "93e3597245eb4104885fde076800959a": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_6dbaefdf5c8e48c28cd867610e6ca21a",
+ "IPY_MODEL_d109cd9c508546b79fd97126c9172a9f",
+ "IPY_MODEL_b2f7f31612984c84a20353334cefd4b1"
+ ],
+ "layout": "IPY_MODEL_bcbe8bfcb5264f388d7d942c24d7ebd3"
+ }
+ },
+ "9fbfde92ec4d4e1c9c6a824cbfaabbbf": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "aa556319d474425f9a0f1f28b45bbe1a": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "b2f7f31612984c84a20353334cefd4b1": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_48e5d282b12349388b6fdec4929adfdb",
+ "style": "IPY_MODEL_b31576e33c174944ac4c727ffecf7f28",
+ "value": " 4/4 [00:00<00:00, 800.17it/s]"
+ }
+ },
+ "b31576e33c174944ac4c727ffecf7f28": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "bafdd6a005834225a60dc2d1e061aa64": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_ddf711e546d3441fb242aff8a2e86981",
+ "style": "IPY_MODEL_616b4af59c894e58afe303d3dbbaf8c1",
+ "value": "100%"
+ }
+ },
+ "bcbe8bfcb5264f388d7d942c24d7ebd3": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "d08e198700c640f8bafd47ce5af3182d": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "d109cd9c508546b79fd97126c9172a9f": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_8e6a61b9182c484fa1ea066833ffdb70",
+ "max": 4,
+ "style": "IPY_MODEL_9fbfde92ec4d4e1c9c6a824cbfaabbbf",
+ "value": 4
+ }
+ },
+ "d3fb9ba04cbe4258b54914829554616d": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "d580fa8c95e949a0a71e6341a5e0ec2e": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_363950db340a4e0796907fa34e41d56d",
+ "max": 16,
+ "style": "IPY_MODEL_836761d0b78f459c98dfbf1e90db4711",
+ "value": 16
+ }
+ },
+ "dc6cbcb4369a41e69129099a53150b72": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_8a2c3bff466c4dd683f7a4162a39e5d8",
+ "max": 40,
+ "style": "IPY_MODEL_803c865f87fd4693bd655a8f82195f8f",
+ "value": 40
+ }
+ },
+ "ddf711e546d3441fb242aff8a2e86981": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ }
+ },
+ "version_major": 2,
+ "version_minor": 0
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/src/examples/Discrete-continuous circuits with multimodal diffusion/1_qft_and_gpe.ipynb b/src/examples/Discrete-continuous circuits with multimodal diffusion/1_qft_and_gpe.ipynb
new file mode 100644
index 0000000..745eb38
--- /dev/null
+++ b/src/examples/Discrete-continuous circuits with multimodal diffusion/1_qft_and_gpe.ipynb
@@ -0,0 +1,1195 @@
+{
+ "cells": [
+ {
+ "cell_type": "raw",
+ "id": "c4e9a976-c6b5-4ce2-8e92-1dd7cee7c736",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "categories:\n",
+ " - Unitary compilation\n",
+ " - Parameterized gates\n",
+ " - Quantum circuits\n",
+ " - Pretrained model\n",
+ "---"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6c172f48-2011-45c9-ba09-b49edc98b7ec",
+ "metadata": {},
+ "source": [
+ "# Quantum Fourier transform and gate-pair tokenization\n",
+ "\n",
+ "> A short tutorial showing the compilation of the Quantum Fourier transform (QFT) and extracting tokens via Gate-Pair tokenization (GPE)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "24ecd66b-9552-4e9b-aa68-ce292444d85e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from genQC.imports import *\n",
+ "import genQC.utils.misc_utils as util\n",
+ "\n",
+ "from genQC.dataset.config_dataset import ConfigDataset\n",
+ "from genQC.pipeline.multimodal_diffusion_pipeline import MultimodalDiffusionPipeline_ParametrizedCompilation\n",
+ "from genQC.scheduler.scheduler_dpm import DPMScheduler\n",
+ "\n",
+ "from genQC.platform.tokenizer.circuits_tokenizer import CircuitTokenizer\n",
+ "from genQC.platform.simulation import Simulator, CircuitBackendType\n",
+ "from genQC.inference.sampling import decode_tensors_to_backend, generate_compilation_tensors\n",
+ "from genQC.inference.evaluation_helper import get_unitaries\n",
+ "from genQC.inference.eval_metrics import UnitaryInfidelityNorm\n",
+ "from genQC.benchmark.bench_compilation import SpecialUnitaries\n",
+ "import genQC.platform.tokenizer.tensor_tokenizer as gpe"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5ae966b5-0f67-4ffe-9d2b-ed0f23c7c383",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[INFO]: Cuda device has a capability of 8.6 (>= 8), allowing tf32 matmul.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "device(type='cuda')"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "util.MemoryCleaner.purge_mem() # clean existing memory alloc\n",
+ "device = util.infer_torch_device() # use cuda if we can\n",
+ "device"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f903ecdc-f07c-4e0a-8fc9-1d1bea2bfec5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# We set a seed to pytorch, numpy and python. \n",
+ "# Note: This will also set deterministic algorithms, possibly at the cost of reduced performance!\n",
+ "util.set_seed(0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "73b1255b-b440-4bb9-89fc-c864fbcafaa1",
+ "metadata": {},
+ "source": [
+ "## Load model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5baf1d7a-5cf5-4d06-919b-75bc0aa2c6f0",
+ "metadata": {},
+ "source": [
+ "Load the pre-trained model directly from [Hugging Face: Floki00/cirdit_multimodal_compile_3to5qubit](https://huggingface.co/Floki00/cirdit_multimodal_compile_3to5qubit)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6ff4eb91-6929-4d54-b6e5-38f6d7b18fdf",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pipeline = MultimodalDiffusionPipeline_ParametrizedCompilation.from_pretrained(\"Floki00/cirdit_multimodal_compile_3to5qubit\", device)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1d836ce9-e753-4407-be75-72c5da485328",
+ "metadata": {},
+ "source": [
+ "The model is trained with the gate set:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "13830bb8-7be4-4fed-9983-09f13d1bf8f4",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "['h', 'cx', 'ccx', 'swap', 'rx', 'ry', 'rz', 'cp']"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pipeline.gate_pool"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2047cc5b-deeb-4bf7-8706-152e1aceddfc",
+ "metadata": {},
+ "source": [
+ "which we need in order to define the `vocabulary`, allowing us to decode tokenized circuits."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "36c309d1-bd8b-40b4-b0e7-2d887b6963cb",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'h': 1, 'cx': 2, 'ccx': 3, 'swap': 4, 'rx': 5, 'ry': 6, 'rz': 7, 'cp': 8}"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "vocabulary = {g:i+1 for i, g in enumerate(pipeline.gate_pool)} \n",
+ "tokenizer = CircuitTokenizer(vocabulary)\n",
+ "tokenizer.vocabulary"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "67f82d86-963b-4d43-8105-6656d42269b7",
+ "metadata": {},
+ "source": [
+ "### Set inference parameters"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d58cd4d1-c1ec-4e27-a286-7d302b467ca0",
+ "metadata": {},
+ "source": [
+ "Set diffusion model inference parameters."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "eda49101-93cb-48c6-a761-ca8aae4ad1ba",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pipeline.scheduler = DPMScheduler.from_scheduler(pipeline.scheduler)\n",
+ "pipeline.scheduler_w = DPMScheduler.from_scheduler(pipeline.scheduler_w)\n",
+ "\n",
+ "timesteps = 40\n",
+ "pipeline.scheduler.set_timesteps(timesteps) \n",
+ "pipeline.scheduler_w.set_timesteps(timesteps) \n",
+ "\n",
+ "pipeline.lambda_h = 1.5\n",
+ "pipeline.lambda_w = 0.45\n",
+ "pipeline.g_h = 0.4\n",
+ "pipeline.g_w = 0.2\n",
+ "\n",
+ "# These parameters are specific to our pre-trained model.\n",
+ "system_size = 5\n",
+ "max_gates = 32"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3d32dbf9-7279-4edf-bdc7-ed3e33609a7f",
+ "metadata": {},
+ "source": [
+ "For evaluation, we also need a circuit simulator backend."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e996c1f7-76e9-460d-ab39-c5b43114ade9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "simulator = Simulator(CircuitBackendType.QISKIT)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7835d6ad-03d1-4a7b-ab89-939b187468a5",
+ "metadata": {},
+ "source": [
+ "## Compile the QFT unitary"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b09547e6-2cb4-4e3f-8ec0-2894bac018cd",
+ "metadata": {},
+ "source": [
+ "We now compile the 4-qubit QFT."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "cea55486-b01e-4ee6-a94f-9720740e8176",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "samples = 512\n",
+ "num_of_qubits = 4\n",
+ "prompt = f\"Compile {num_of_qubits} qubits using: ['h', 'cx', 'ccx', 'swap', 'rx', 'ry', 'rz', 'cp']\"\n",
+ "\n",
+ "U = SpecialUnitaries.QFT(num_of_qubits).to(torch.complex64)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6dbf2639-9bcb-402f-89bd-bdfef47a5d29",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "947bf1d7d4d04ffea3abf8530908ad62",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/40 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "fa240a3d6d114b6da392ac2fbe059980",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/40 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[INFO]: (generate_comp_tensors) Generated 512 tensors\n"
+ ]
+ }
+ ],
+ "source": [
+ "out_tensor, params = generate_compilation_tensors(pipeline, \n",
+ " prompt=prompt, \n",
+ " U=U, \n",
+ " samples=samples, \n",
+ " system_size=system_size, \n",
+ " num_of_qubits=num_of_qubits, \n",
+ " max_gates=max_gates,\n",
+ " no_bar=False, # show progress bar\n",
+ " auto_batch_size=256, # limit batch size for less GPU memory usage\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "188ca3b3-aad8-4553-afa6-948b90089643",
+ "metadata": {},
+ "source": [
+ "For instance, a circuit tensor alongside parameters the model generated looks like this"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "38513404-0da1-4ca6-b9be-1e1706fc9010",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "tensor([[ 0, 0, 8, 0, -2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],\n",
+ " [ 0, 0, 0, 4, 2, 3, 1, 8, 0, 8, 8, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],\n",
+ " [ 0, 8, 8, 4, 0, -3, 0, 0, 0, 0, 8, 1, 8, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],\n",
+ " [ 1, 8, 0, 0, 0, -3, 0, 8, 4, 8, 0, 0, 8, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9]], device='cuda:0')\n",
+ "tensor([[ 0.0000, -0.7021, -0.9835, 0.0000, 0.0000, 0.0000, 0.0000, -0.9720, 0.0000, 0.6553, 0.2625, 0.0000, -0.7555, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,\n",
+ " 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]], device='cuda:0')\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(out_tensor[0])\n",
+ "print(params[0])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b42cc007-fcfe-4baa-8565-06e603552e8a",
+ "metadata": {},
+ "source": [
+ "### Evaluate and plot circuits"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a75b21e3-444d-423a-a02f-7ff48f9e8ddd",
+ "metadata": {},
+ "source": [
+ "We decode these now to circuits and calculate their unitaries."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "400c673d-89d9-455f-b911-e2e7b9e8535b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "generated_qc_list, _ = decode_tensors_to_backend(simulator, tokenizer, out_tensor, params)\n",
+ "generated_us = get_unitaries(simulator, generated_qc_list)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fcf6e586-c40c-4d66-9a94-f18e6f82ffc0",
+ "metadata": {},
+ "source": [
+ "We then evaluate the unitary infidelity to our target `U`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2593218b-7edb-4499-abd0-8ac4524d034a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "U_norms = UnitaryInfidelityNorm.distance(\n",
+ " approx_U=torch.from_numpy(np.stack(generated_us)).to(torch.complex128), \n",
+ " target_U=U.unsqueeze(0).to(torch.complex128),\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3e68cd4c-3870-4748-bff7-93c3e4fff3d8",
+ "metadata": {},
+ "source": [
+ "We get the following distribution of the infidelities."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8625b627-f981-417d-82c7-934d4dc0697f",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "plt.figure(figsize=(7, 3), constrained_layout=True)\n",
+ "plt.xlabel(UnitaryInfidelityNorm.name(), fontsize=13)\n",
+ "plt.ylabel(\"Frequency\", fontsize=13)\n",
+ "plt.hist(U_norms, bins=60)\n",
+ "plt.xlim([-0.05, 1.05])\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ddec7c92-bc95-45e6-940a-22439e2324d4",
+ "metadata": {},
+ "source": [
+ "We plot the four best ciruits, w.r.t. the infidelity:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "37788247-4e1d-4d39-8877-90deb67c5cd8",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAABe0AAACyCAYAAADfwuTvAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAXEgAAFxIBZ5/SUgAAeb5JREFUeJzt3Xd4VNXWx/HvpNMSakgoCRAQ5FIlCEhJ6JHeVAQxIM0CooAKeBWwUQSRi+0amoKivhRRQFDphK6AIL2FGnpvafv9Y25GhiSQhCQzCb/P8+SBnLrOmcmZPevss7bFGGMQERERERERERERERGHc3F0ACIiIiIiIiIiIiIiYqWkvYiIiIiIiIiIiIiIk1DSXkRERERERERERETESShpLyIiIiIiIiIiIiLiJJS0FxERERERERERERFxEkrai4iIiIiIiIiIiIg4CSXtRURERERERERERESchJL2IiIiIiIiIiIiIiJOQkl7EREREREREREREREnoaS9iIiIiIiIiIiIiIiTUNJeRERERERERERERMRJKGkvIiIiIiIiIiIiIuIklLQXEREREREREREREXESStqLiIiIiIiIiIiIiDgJJe0dyGKxpOmnVKlSAKxYsQKLxUL37t0dFvu1a9eYMWMG/fv3p1atWnh6emKxWBgxYkSG7ufw4cNYLBZCQ0MzdLvZRWhoKBaLhcOHDzs6FDsP+usiIiIiIiIiIiKSWdwcHcCDLDw8PMm0NWvWcODAAapWrUq1atXs5hUuXDiLIru3ffv28eyzzzo6DIc4fPgwpUuXJiQkhBUrVjg6HKcyffp0evTowfDhwzP8Bo6IiIiIiIiIiMiDQEl7B5o+fXqSad27d+fAgQO0a9fOqZOe+fLlo2fPntSsWZOaNWuycOFC3n77bUeHleN8/fXXXL9+neLFizs6FDvFixdn165d5M6d29GhiIiIiIiIiIiI5ChK2ku6BAUFMXnyZNvvv/76qwOjybkCAgIcHUKy3N3dqVChgqPDEBERERERERERyXFU0z6bO3/+PC+88AL+/v54enpSqVIlpk6dmuLyR48epV+/fgQFBeHl5UXBggVp1aoVa9euzcKo0+fy5csMGDCAkiVL4uXlxcMPP8yECRNISEhIdvnr168zatQoqlevTt68ecmbNy+1a9fmq6++Snb5qKgoXnjhBR566CFy585NwYIF+de//kXfvn3Zs2cPACNGjKB06dIArFy50m7MgbSMMbBhwwY6d+5M8eLF8fT0xN/fn8aNGxMREWG3XEo17RPHOIiJieGdd96hQoUKeHp60q5dO9sy165dY8yYMQQHB+Pt7U2ePHmoUKECL730Env37rUtN2LECCwWS7JPfgCUKlUKi8ViNy25mvahoaH06NEDgJEjR9qdm5S2LSIiIiIiIiIiIvaUtM/GLl68SJ06dfjpp5+oX78+devWZffu3fTs2dOuF3yidevWUbVqVT799FPc3d1p2bIllSpVYsmSJTRo0IDvv//eAUeROrdu3aJRo0Z8/fXXPProozRt2pSoqCgGDhzIc889l2T506dPU6dOHYYNG0Z0dDQhISE0aNCA3bt30717d/r372+3/NGjR3nkkUf44osvAGjRogUhISF4enoSERHBunXrAKhWrRodO3YEoGjRooSHh9t+6tWrl6pjmThxIo899hjff/89/v7+dOjQgUqVKrFjxw5ee+21VJ+ThIQE2rVrx9ixYwkKCqJt27b4+/sDcPLkSWrVqsWQIUM4ePAgoaGhtGjRgjx58vDFF1+waNGiVO8ntcLCwqhbty4AVatWtTs3ZcuWtS3XvXv3TBm0WERERCSz3N4ZITU/pUqVAmDFihVp7tyR0f766y/69etH7dq1KVasGJ6envj4+FCnTh0mTZpEbGxshuwnuU4dD5KUOts42oP+uoiIiGRXKo+Tjc2fP5/OnTszffp0PD09Afjxxx9p37497777Lr169bIte/nyZTp27Mjly5eZOXMmXbt2tc3bvHkzzZo1o1evXjRq1IgiRYpk+bHcy/r166lSpQr79u2zDch74MABGjRowFdffUW7du3sepn36NGDv/76iwEDBjBmzBjb+Tl16hStWrXik08+oWXLloSFhQEwefJkzp8/T79+/Zg0aZLdvo8cOWL7MtOuXTuqVavGnDlzqFChQpp7kK9atYpXX32VvHnzMm/ePBo3bmybFxcXl6YyQ0ePHsXT05M9e/YkqXnfrVs3/v77b5588kmmTJlC3rx5bfMOHz7M5cuX0xR3agwZMgQ/Pz8iIyOdfkwGERERkbQIDw9PMm3NmjUcOHCAqlWrUq1aNbt5ie1VZ7Bq1So+/fRTAgMDqVixIkWKFOHMmTNERkayfv165syZw6+//oqHh4ejQ81whw8fpnTp0oSEhLBixQpHh+NUpk+fTo8ePRg+fLja7SIiIk5IPe2zMW9vbz755BNbQhqsSeVKlSpx5MgRu14eU6dO5eTJk7zyyit2CXuA4OBg3nrrLa5evcrMmTOzKvw0GzdunN0XoKCgIN566y0APvnkE9v0rVu3smjRImrWrMlHH31kd36KFi3Kl19+CcDnn39um37mzBkAmjRpkmS/AQEBBAUFZcgxjB49GmMMb775pl3CHsDNzY0WLVqkaXujRo1KkrDfuHEjS5cuxdfXl8mTJ9sl7MFa7qZKlSrpO4AM4O/vT/ny5Z3qy6xIRsrOvTF3797NmDFjaNiwIYULF8bd3R0/Pz86dOjA6tWrM2w/D3qvP/XGFMl+pk+fnuQn8SnLdu3aJZk3btw4B0f8jxYtWnDgwAEOHz7M77//zqxZs/j99985fPgwlSpVYuXKlbb2saTf119/za5du5K0zR2tePHi7Nq1i6+//trRoYg4nezcbk/Ou+++a4s1o3I7D3r7UO12cST1tM/GatSoQaFChZJMf+ihh9ixYwcnT560fagk9uDu0KFDstuqX78+YE34OqOCBQvStGnTJNOffvppXnjhBdauXUtCQgIuLi62Y23Xrh0uLknvSyXWuL/9WGvUqAHAsGHDcHV1pUmTJnh5eWXoMcTFxdl6+PTp0+e+t2exWGjdunWS6b///jtgPTf58uW77/1ktFGjRjFq1ChHhyGSabJzb8wmTZpw/Phx2xggBQsWZOfOncybN48ff/yRjz76iFdeecXRYWYK9cZMmXpjimRvZcqUSXZ60aJFeeONN+jWrRvLli2jX79+WRxZzhIQEODoEJLl7u5OhQoVHB2GiFPKzu32O+3Zs4f3338fi8WCMcbR4WQqtdtTpnZ7zqKe9tlYiRIlkp2emKi9deuWbVriXcG6desme8e4Zs2aAJw9ezZzg06nwMDAZKf7+PiQP39+bty4wYULF4B/jvXNN99M8Q751atX7Y61e/fuPPnkk+zcuZPWrVtToEABGjRowAcffEB0dHSGHMO5c+e4ceMGBQsWpECBAve9PV9fX7unCBIdPXoUIMOeDhCRtMnOvTErVKjA119/zZkzZ/jtt9/4/vvv2b59O1988QXGGAYPHszOnTsdHWa2p96YIg+m8+fP88ILL+Dv74+npyeVKlVi6tSpKS5/9OhR+vXrR1BQEF5eXhQsWJBWrVqxdu3aDIvJ3d0dIMNL41y+fJkBAwZQsmRJvLy8ePjhh5kwYQIJCQnJLn/9+nVGjRpl61yTePP4q6++Snb5qKgoXnjhBR566CFy585NwYIF+de//kXfvn3Zs2cPACNGjKB06dIArFy50u67QFp6x27YsIHOnTtTvHhxPD098ff3p3HjxkRERNgtl1JvzMTeuTExMbzzzjtUqFABT09Pu9Ke165dY8yYMQQHB+Pt7U2ePHmoUKECL730Env37rUtN2LECCwWS4olOkuVKoXFYrGbllxvzNDQUHr06AHAyJEj7c5NWst/imRn2bndfjtjDH369CF//vy0adPG0eHkKGq3iyOpp302llwv8pQkNpA7depEnjx5UlwuJ/TCSDzWevXqpTpx7erqyvfff8+QIUOYP38+y5YtY8OGDaxevZrRo0ezePFiHnvsscwMO80y+kmAlKT05UpEcp7EJ3Xu1LdvX+bOncuvv/7K//3f/zF8+PAsjixnUW9MkQfPxYsXqVOnDlevXqV+/fqcPXuWVatW0bNnTxISEuzGogJYt24dLVu25MKFC5QvX56WLVty5swZlixZwuLFi/nmm2946qmn7iumCxcuMH78eABatmx5X9u63a1bt2jUqBEHDhygUaNGxMTEsHTpUgYOHMi2bduSJIVPnz5N06ZN+euvv/Dz8yMkJARjDGvXrqV79+5s3rzZbsypo0eP8sgjj3D+/HnKlStHixYtiI+PJyoqioiICOrUqUP58uWpVq0aHTt2ZM6cORQtWtQ2lhVgS8rdy8SJExk4cCAJCQnUqFGDBg0acPbsWf766y9ee+01evfunartJCQk0K5dO1atWkVISAhVqlSxPTF98uRJmjZtyt9//02BAgUIDQ3F09OTgwcP8sUXX1CuXDkeeuihVO0ntcLCwoiLiyMyMjJJb+KyZcva/t+9e3e++uor9dgUcXKTJ09m1apVzJw5k99++83R4eQoareLQxlxKuHh4QYww4cPT3GZ5cuXG8CEh4ffdRvLly+3TWvcuLEBzObNmzM24P8ZNWrUPeNOj0OHDhnAFCpUKNn5ly5dMoDJlSuXiYuLM8YY8+677xrAjBs37r72fenSJfPqq68awNSsWTNJTCEhIWnaXlxcnMmVK5cBzIULF1K1TkhIiAHMoUOH7KYDJjAwMNl13n//fQOYAQMGpGofictPmjQp2Zjd3d3NnZeKlM7BtGnTMuV9IJLdpfXafu7cOfP8888bPz8/4+HhYf71r3+ZKVOmpLjukSNHzEsvvWTKlCljPD09TYECBUzLli1NZGRkhh3Da6+9ZgDTp0+f+97W7deQS5cumZdfftmUKFHCeHp6mgoVKpiPPvrIxMfHJ7vutWvXzAcffGCqVatm8uTJY/LkyWNq1aplpk+fnuzyhw8fNs8//7wpV66cyZUrlylQoICpWLGi6dOnj9m9e7cxxpjhw4cbINmflD5rk7N+/Xrz1FNPmWLFihkPDw/j5+dnGjVqZL788ku75e51bb9165YZOXKkKV++vPHw8DBt27a1LXP16lUzevRoU6NGDZMvXz6TO3duU758efPiiy+aPXv22JZLPKZp06YlG2tgYGCqru2JsSb3k9K2RR4Uabm2A6Zz587m5s2btnnz5s0zgAkICLBb59KlS8bf39+4urqamTNn2s3btGmTKVCggMmbN685ffp0muLdu3evCQ8PN926dTPNmjUzefPmNYB5/vnnU7zmpkXiNQQwVapUMWfOnLHN279/vylWrJgBzLx58+zWa9Giha3tevv5iY6ONsHBwQYwv/zyi23622+/bQDTr1+/JDFERUWZ/fv3J4kpre12Y4xZuXKlsVgsJl++fOb333+3mxcbG2sWLlxoN+1u13bAlC1b1hw7dizJfhK/pz355JPmypUrdvMOHTpktm3bZvs9o67txqSu3Z6a97hITpPd2u0nT540+fPnN40bN7aLf8aMGena3p3Uble7XRxH5XEeEIn14OfNm+fgSNLn3LlzLF26NMn07777DoA6derg6uoKZNyxent7M2rUKCwWCzt27LBNT3x8OC4uLk3bc3V1tT2WmpmDfSUOpjtr1iyuXr16z+X9/f0B7B69TbR8+XJiY2NTve/0nhsR+Udib8yffvqJ+vXrU7duXXbv3k3Pnj2ZPHlykuXXrVtH1apV+fTTT3F3d6dly5ZUqlSJJUuW0KBBA77//vsMievgwYMA+Pn5Zcj24J/emF9//TWPPvooTZs2JSoqioEDB/Lcc88lWf706dPUqVOHYcOGER0dTUhICA0aNGD37t10796d/v372y2f2Bvziy++AKyDMYaEhODp6UlERATr1q0DsPXGBGuN5/DwcNtPWnpjPvbYY3z//ff4+/vToUMHKlWqxI4dO3jttddSfU4Se2OOHTuWoKAg2rZta7tOnzx5klq1ajFkyBAOHjxIaGgoLVq0IE+ePHzxxRcsWrQo1ftJrbCwMOrWrQtA1apV7c7Nnb0xLRaLemKKpMDb25tPPvnErrRhu3btqFSpEkeOHLErqTJ16lROnjzJK6+8QteuXe22ExwczFtvvcXVq1fTPMjgqVOn+Oqrr5gxYwa//vorV69e5eWXX2bMmDFpeoI3NcaNG2dX+zkoKIi33noLgE8++cQ2fevWrSxatIiaNWvy0Ucf2Z2fokWL2trMn3/+uW36mTNngH/avLcLCAjIsBKRo0ePxhjDm2++SePGje3mubm50aJFizRtb9SoUUnKK2zcuJGlS5fi6+vL5MmTyZs3r938UqVKUaVKlfQdQAbw9/enfPnyTl3HW8SRnKHd/vLLL3Pjxg2762RmULs9KbXbJdM5+q6B2MusnvYXLlwwvr6+xt3d3fz3v/9Ncic0NjbWLF682Gzfvj1dcae2p3358uVN+fLlk+1lkpzbe+xUq1bNnD171jbv4MGDpnjx4gYwc+bMsVuvadOmBjAvvviiuXTpUpLtbt261a7Hztdff53ssSf2gHrooYds027dumXc3d2Nn5+frXd/aq1YscJYLBbj7e1tli1bZjcvrT12Uuppb4wxDRs2NIB5+umnzdWrV+3mHTp0yPz111+23/fv328AU6BAAbv9HDx40Dz88MO283/nNkimx07ie7NTp04pxjZkyBBTvnz5ZHv2i+RU2a035p32799vPD09M+yJLfXGVG9MkZwgLdf2hg0bJju/Q4cOBjBr1661TXv88ccNkGKvy02bNtk+K9IjLi7OHDx40IwfP954e3ubcuXKJbkepUfiNaRgwYLJzr948aLtCdnE7yJjxowxgHn//fdT3G7evHmNn5+f7ffJkycbwFSsWNH8/PPP5saNG/eMKa3X9tjYWNsTsufPn0/VOne7tlssFrvPrURpfUI2q6/tIg+i7NRu//nnnw1gRo4cmST+jO5pr3b7IbvpardLVlBP+wdE/vz5mT9/Pj4+PvTt25dSpUrRokULunbtSuPGjSlSpAhhYWHs378/1dts3749tWvXpnbt2nz22WeAtZZa4rT27dsnWWfPnj3s2bMnTb23AWrXro2Liwtly5alY8eOtGnThkqVKnH8+HGeeeYZOnToYLf8zJkzqV69Op999hmBgYE0bNiQrl270qpVKwICAqhWrRqLFy+2LT9nzhwqV65M2bJlad++PV26dKFOnTp06NABFxcX3nvvPduyHh4ehIWFER0dTdWqVXn22Wfp1asX06ZNu+dxhISEMHbsWK5cuUKjRo2oWbMmXbp0oVmzZhQvXpwuXbqk6bykZMaMGZQvX55Zs2YREBBA27ZtefLJJ6lRowZBQUF2Ty0EBQXx7LPPcuHCBapVq0abNm1o0qQJlStXplKlSikOApyc2rVr4+vry+zZswkNDeW5556jV69edgOmnTx5kj179jjtoMcijuYMvTFvFxcXR/fu3bl16xZPPfUUNWrUSPe2kqPemEmpN6ZIzlOiRIlkp+fLlw+w9mBMlHidr1u3rt0AoYk/NWvWBEh3W8rV1ZXSpUszcOBApk2bxr59+5L0eLwfKbUdfXx8yJ8/Pzdu3ODChQvAP8f65ptvJnusFouFq1ev2h1r9+7defLJJ9m5cyetW7emQIECNGjQgA8++IDo6OgMOYZz585x48YNChYsSIECBe57e76+vnafW4mOHj0KkGGfRyKStRzZbr969SovvvgiDz30EG+88UaGHM+9qN2elNrtkpk0EO0DpHbt2mzfvp0JEyawcOFCVq5cCVj/YENCQmjfvn2yF8iUbNmyhaioKLtpx48f5/jx40DKDfb08PT0ZPHixQwbNowff/yRs2fPUrp0aXr37s0rr7ySZHlfX1/Wrl1LREQE3333HVu2bGHt2rUULVqUMmXK8PLLL9O5c2fb8gMHDqREiRJERkayevVqrl27RrFixXjqqacYNGgQwcHBdtufPHkygwcP5rfffuPbb78lPj6euLg4evTocc9jGTx4MLVq1WLChAlERkaybds2ChcuTOXKlXn66afv+1yBdSTxTZs28fHHHzN79mx+++03XF1dKVGiBC+++CKtWrWyWz4iIoJixYrxzTffsGTJEkqWLMnQoUMZMmRImj4cvby8WLhwIcOGDWPjxo2sWrUKYwz16tVzuoF8RZxVjRo1bIPT3e6hhx5ix44dnDx5klKlSgHw66+/AiS5cZmofv36gLXhmF4vv/wya9asoUyZMrYbtBmlYMGCtpJmt3v66ad54YUXWLt2LQkJCbi4uNiOtV27dsmWcahevTp58+a1O9bEGwzDhg3D1dWV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fuDAgZQoUYLIyEhWr17NtWvXKFasGE899RSDBg0iODjYbvuTJ09m8ODB/Pbbb3z77bfEx8cTFxdHjx497nksgwcPplatWkyYMIHIyEi2bdtG4cKFqVy5Mk8//fR9nyv4pzfmxx9/zOzZs/ntt99wdXWlRIkSKfbGLFasGN98842tp+vQoUMZMmRIqgcFg396Yw4bNoyNGzeyatUqjDHUq1fP6QYEExEREXFWGdluz2pqt6eN2u2SESzGGOPoICRtpk+fTo8ePRg+fDgjRoy47+2dOXOGQYMG8dtvv3H69GkSEhKYNm0a3bt3x2KxEBgYaPeIzr10796dr776iuXLlycZyTytktv/4cOHKV26NCEhIaxYseK+tp9ecXFxvPfee2zatIldu3Zx5swZYmNjKVmyJE2bNuWNN95IU03JU6dOsWDBAhYsWMCmTZs4ffo0uXPnpmrVqjz33HM8++yzWCyWTDwiez///DPjxo1jy5YtADzyyCO89tprtGzZMsmyUVFR/PTTTyxcuJCtW7dy7tw5fHx8CA4O5sUXX6RNmzZZFreIiIiIiIiIiEh2p5r2Qs+ePZkxYwa+vr48/fTThIeHU7ZsWUeHlWbTp0/HYrFkyI2Me7l58yYjR45k1apV+Pv7ExYWRvPmzYmJieHzzz+nSpUqbN68OdXbGzRoEL169WLBggWULFmSDh06ULlyZdasWUP37t158skn01yrLb0+/vhj2rRpw9q1a6lbty6NGjVi48aNtGrVKtmaZ127duXll19mxYoVVKhQgY4dO1KmTBmWLFlC27ZtGThwYJbELSIiIiIiIiIikhOoPE421L59e2rXrk3hwoXve1sxMTEsWrSIUqVKsWXLFlxc7O/j7Nq1C3d39/veT0YqXrw4u3btInfu3A6LwcvLizVr1lCrVi3c3P75M4qPj+ff//43o0eP5vnnn0914r5QoUK8//779O7dmyJFitimb9q0iSZNmjB79mymTJlCnz59MvxYbrdnzx4GDx6Mp6cny5cvp06dOoB1FPPHHnuMV199lbCwMLubOiVKlGDSpEmEh4eTL18+2/SFCxfSrl07JkyYQFhYWKoHtBQREREREREREXmQqad9NuTj40OFChUyJGkfHR1NfHw8gYGBSRL2YB1ZPC31s7KCu7s7FSpUICAgwGExuLm5UbduXbuEPYCrqyvvvvsu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FufPn092nb1791KtWjVGjx7NjRs3aN68OcHBwWzYsIHWrVszbty4dB9zWFgYdevWBawNzsRjSazz2bZtW7sSCnfKjBIKDRs2THUJhUSff/45a9euZfz48RQsWPCey69bt46qVavy6aef4u7uTsuWLalUqRJLliyhQYMGfP/996ne98WLFzly5AhAsgMjlixZksKFCxMVFcXly5dTtU2VUBDJOMmVmHrooYcAOHny5H1vf/Xq1QB07tw5ybyKFSsmWx7n119/BbAb3PR2iR14MrMNFxgYSFhYGDt37rRrc2/fvp0NGzYQHBycrsFe9+/fjzGGM2fOsHjxYtzd3alRo0ayJSRTktjmjouLY86cOTz++ON4e3tTqVIlfv75Z0qUKEFUVJRd7WNnOKcp+e677xg7dixeXl58++23Di13IZJTqN2e/drtd5Y+mzZtGv369WPAgAF8/fXX3LhxI9n1Mvqcpqb02c2bN9m7d2+qtqd2ezbnwF7+kk6OLo9jjDGDBw82gHF3dzfNmzc3Tz75pClatKgJCAgwrVu3TjaOvXv3mtKlSxvA+Pr6miZNmpguXbqYZs2aGV9f32QfgU1LzDdu3LBtJyQkxPTo0cP07NnTREZGJon/119/tT1S+sknnyR77HczfPhwA5jixYubli1bmi5duphGjRqZggULGsD4+PiYVatWJVkv8TW687VLfHw1d+7cJjw8PNmfQYMGJdneunXrTOHChQ1gSpYsaR5//HFbLPnz5zeA3SPXqZFYKsHNzc08/vjjpm3btiZXrlwGMP/5z3+SLF+tWjUDmCJFiqQY+6hRo5KsFxgYmGGP7InkBCqhkPNKKBhjzLFjx4y3t7dp2LChbdrdyuNcunTJ+Pv7G1dXVzNz5ky7eZs2bTIFChQwefPmNadPn07V/rdt22YAU6BAgRSXSbyOp+ZR4AsXLpgiRYoYwMyZMydVMYg8yO51bV+6dGmy62VUu93T09MA5tq1a8muk9gGvf3a/vjjjycp+ZLcT9myZdMdc2pKKPz0009JSnH179/fAOa///1viuulxcWLF03JkiVNrly5zJEjR1K1Tvv27Q1gKlasmOz8119/3QCma9eutmnpOafJtakT2wEZVR5n6dKlxtPT07i6uqb5O4PIg0zt9pzXblfpM3FGGohW0uXDDz+kfPnyTJo0iRUrVuDj40OzZs0YM2YMw4YNS3adcuXKsWXLFj755BPmzp3L+vXriYuLw8/Pj+rVq9OmTRuefPLJdMfk5eXFwoULGTZsGBs3bmTVqlUYY6hXrx6PPfaY3bL169fH3d0dNzc3unbtmuZ9dejQgStXrrB69Wo2bdrE+fPnyZUrF2XLlqVv3770798ff3//VG8v8THb69evp9jTJzAwMMld2tq1a7N9+3YmTJjAwoULbWV5/P39CQkJoX379jRp0iRNx/bqq69StmxZPvzwQ1vvrODgYF5//XVatWqVYuxnzpxJMfaQkBCGDBmSpjhExF5KJRTmzJmTJSUUKlWqlORJqjsf979d4uP+HTp0ICIigokTJ953jMnp06cPo0ePJiIiIklZsfstoXDgwAHb71WqVGHmzJlpKqEA0K9fP27evMnnn3+equWnTp3KyZMnGTRoUJLPp+DgYN566y0GDhzIzJkzefXVV++5vcQSCnfrOZmWEgrPP/88Z86coXbt2rRv3/6ey4vI3d054KAzSOxJHhYWRtGiRVNcLiMGYLybFi1aULJkSX744QcmTpyIh4cHM2fOJG/evDz99NMZsg8fHx9at27NZ599xm+//cZzzz13z3USS1mkVK4icfrp06dt09JzTpNrV5cqVYp27drh7e2Nj48Ply5d4tixY1SsWDHJsseOHbOL906bNm2ibdu2xMTEMGXKFLs2hojcH7Xbk+fM7fbbS5/5+fmxaNEi6taty5EjRxg4cCC//fYbrVu3ZufOnbZ2dUafU5U+kzspaZ8Nde/ePdkSJNOnT2f69OkprnfnRRusDb/kpt9tnUS9evWiV69eaYrDx8eHN998kzfffDPF7d5r/3eLOTg42Pb46d3MmzeP2NhYunTpQv78+VMVy+2qVKnC+PHj07xeSudmxYoVad5WIj8/P8aMGZPqOsmp0bp162Rr1yfn8OHD6dpHetcTeVA5QwmFO8cscYbH/RNLKCxatIi1a9fabtJmRAkFgLNnz/LHH3/w5ptvUqNGDSIiIggPD0/VNubOncuPP/7I22+/Tfny5VO1jjOc05SMGTOG77//noIFC/LNN98kWwtbRJyLv78/hw8fJioqiocffjjJ/KioqCTTSpQoAVjb+o4cp8jV1ZXevXvz9ttv88033+Dt7c2FCxfo1asX+fLly7D9JCbKz5w5k6rlEz9TEhM8d0osS5E3b17b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/Te9pk9wYEQi2c+JS94cO1eYy9fy0qnbKxTKnbo6nAEBAbz99tuZHN19CgzAbcKoNK8jIpIt6ZonIiLZlT7DRESynMUYYxwdRHR0NCtXruSpp55K9ToJO3cTP2R4uvbnOnokLhUrpGvd1PjpCEzZC9dioURemFgLfJxzzMhMtfksjP4Lzt8CXy/rI7FBTjD4nUh2cXsN+5jYBFupnMQa9yIiIs7qbrWeU+I6/gPVeRaRbCM91znQtU5ERFLHKZL26eHMSXuAG3HW0jiFvawDrT2oYuKtSfvCXnosUSQtDh27wuip2+xK4ty4GUfvkWv4ZGgd8nt7OjhCERGRlJnYOMzhqDStYykViMXd4Q8Ci4ikSnquc6BrnYiIpI6S9iIiTiohweDiYrnnNBERERERERERyTnU91lExEkll5xXwl5EREREREREJGdT0l5ERERERERERERExEkoaS8iIiIiIiIiIiIi4iSybU17ExuL2XcgXetaygVhcXfP4IhERERERERERERERO5Ptk3ai4iIiIiIiIiIiIjkNCqPIyIiIiIiIiIiIiLiJJS0FxERERERERERERFxEkrai4iIiIiIiIiIiIg4CSXtRURERERERERERESchJL2IiIiIiIiIiIiIiJOQkl7EREREREREREREREnoaS9iIiIiIiIiIiIiIiTUNJeRERERERERERERMRJKGkvIiIiIiIiIiIiIuIklLQXEREREREREREREXESStqLiIiIiIiIiIiIiDgJJe1FRERERERERERERJyEkvYiIiIiIiIiIiIiIk5CSXsRERERERERERERESehpL2IiIiIiIiIiIiIiJNQ0l5ERERERERERERExEkoaS8iIiIiIiIiIiIi4iSUtBcRERERERERERERcRJK2ouIiIiIiIiIiIiIOIn/B+d+JIRDnnQcAAAAAElFTkSuQmCC",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "plot_k_best = 4\n",
+ "\n",
+ "idx = np.argsort(U_norms)\n",
+ "fig, axs = plt.subplots(1, plot_k_best, figsize=(10, 2), constrained_layout=True, dpi=150)\n",
+ "\n",
+ "for i, (idx_i, ax) in enumerate(zip(idx[:plot_k_best], axs.flatten())): \n",
+ " ax.clear()\n",
+ " generated_qc_list[idx_i].draw(\"mpl\", plot_barriers=False, ax=ax)\n",
+ " ax.set_title(f\"The {i+1}. best circuit: \\n infidelity {U_norms[idx_i]:0.1e}.\", fontsize=10)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "02831e92-8b4b-4534-b411-e22a432ab1a8",
+ "metadata": {},
+ "source": [
+ "## Gate-Pair tokenization"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "78fbf7dc-03e4-44a4-b9f2-7b0d6b3f07d0",
+ "metadata": {},
+ "source": [
+ "Now we want to extract reusable substructures (gadgets) from generated circuits. We use all generated tensors in `out_tensor`, regardless if their circuits have good or bad infidelity."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f1bb2aed-1070-427a-a085-7332fdbfbdbd",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gate_pair_tokenizer = gpe.GatePairTokenizer(unique_class_values=pipeline.embedder.unique_class_values, \n",
+ " zero_token=0, \n",
+ " padding_token=9, \n",
+ " device=\"cpu\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a4df80e9-2bf3-4216-b143-36999e2d598e",
+ "metadata": {},
+ "source": [
+ "Next, we run our proposed Gate-Pair Encoding (GPE) scheme:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "49fe6f42-d84d-4196-8e7d-5127b11e0685",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "962235847487477e88ee001eaa67b2ee",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/100 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "New depth reached 1\n",
+ "New depth reached 2\n",
+ "New depth reached 3\n",
+ "break: max_iters reached\n"
+ ]
+ }
+ ],
+ "source": [
+ "_ = gate_pair_tokenizer.learn(out_tensor.cpu(), max_depth=5, max_iters=100)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "87fa40be-403b-494f-ab65-4a686350fc0c",
+ "metadata": {},
+ "source": [
+ "Now we plot the extracted tokens."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "601fa89b-154f-4e12-8f73-5fb3a1d74463",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "max_depth = 4\n",
+ "topk = 5"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b734265c-2189-406b-9886-adcbc2be6584",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "3ad8e473e03047969891e6c1204a1290",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/100 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "unpacked_vocab_configs_depths, unpacked_vocab_configs_cnts_depths = \\\n",
+ " gpe.get_topk_depth_unpacked(gate_pair_tokenizer, num_of_qubits, use_raw=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7e234672-0b27-4449-ae92-28d5aafd299b",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAACbIAAAR9CAYAAACJNiOfAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAewgAAHsIBbtB1PgABAABJREFUeJzs3XeYXVXdNuBn0iaZ9JAGCSn0XhIICQIJSAkICK8BERSQKKErn6DCqxRFRUBFQAUFKUpvCoIxCEGK9CpVCCSBEEoqCSH9fH/MOwNDenJmzpT7vq5czqy99lq/MzFnHfZ+Zu2yQqFQCAAAAAAAAAAAAJRIs1IXAAAAAAAAAAAAQNMmyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAABN0osvvpivfvWr6dWrV8rLy7POOuvksMMOy4svvljq0lhJt912W7785S9nvfXWS0VFRTbeeON85zvfyYwZM5baf9asWfnud7+b/v37p7y8PL169cqIESMyZ86c6j7Dhg1LWVnZUv+0bNmyjl4ZQMNnnW34Xn311Zx88snZcccd07p165SVlWX8+PFL7Tt79ux8+9vfTu/evVNeXp5NN900v/vd75bo98ADD2T//ffPuuuum9atW6dnz54ZPnx4Hn744Vp+NQAANARlhUKhUOoiAAAAAKAu3XbbbfnKV76SLl26ZOTIkenfv3/Gjx+fK664IlOnTs0NN9yQAw88sNRlsgJdu3bNOuuskwMOOCB9+vTJf/7zn1x66aVZb7318vTTT6dNmzbVfWfOnJmhQ4fm7bffztFHH50NNtggH3zwQR588MH86U9/SufOnZMk99xzT957770a83z00Uc55phjss8+++Suu+6q09cI0BBZZxuHq666KiNHjsxmm22WFi1a5Nlnn82bb76Zfv361ei3aNGi7LLLLnnyySdz/PHHZ8MNN8w//vGP/PWvf81PfvKTnH766dV9L7/88vztb3/L9ttvn549e2b69On585//nP/85z+56667Mnz48Dp+lQAA1CeCbAAAAAA0KePGjctWW22VPn365IEHHki3bt2qj02ZMiU777xz3nrrrTz//PNZb731SljpkubMmZOKiool2hcuXJjFixenVatWJaiqdO6///4MGzasRts111yTI444In/4wx/yjW98o7r9uOOOy/XXX5+nn346/fv3X6V5/vznP+drX/tarr322hx66KHFKB2g0bLONh7Tpk1Ly5Yt0759+1xwwQU59dRTlxpku/nmm3PwwQfniiuuyFFHHVXdPmLEiNx1112ZMGFCunfvvsx55syZk/XWWy/bbLNNRo8eXVsvBwCABsCjRQEAAABoUs4///zMmTMnv//972vcXE8qd/i67LLL8tFHH+W8886rcWzSpEkZOXJk1llnnZSXl6d///459thjM3/+/Oo+M2bMyMknn5x+/fqlvLw8vXv3zuGHH54pU6YkqdzZZGmP5br//vtTVlaW+++/v7pt2LBh2WKLLfLUU09ll112SUVFRU4//fSMHz8+ZWVlueCCC3LhhRdm/fXXT3l5eV566aUkySuvvJIRI0akS5cuad26dbbbbrvccccdNearquPhhx/O//t//y/dunVL27Ztc+CBB+aDDz5Y4mf297//PUOHDk379u3ToUOHbL/99rnuuutq9HnssccyfPjwdOzYMRUVFRk6dOgSjwmbNWtWvv3tb1f/fLp375499tgjTz/9dHWfOXPm5JVXXqn+mS3PZ0NsSap3+Hn55Zer22bMmJErr7wyRx99dPr375/58+dn3rx5Kxy/ynXXXZe2bdvmi1/84kqfA9BUWWcbzzrbpUuXtG/ffoX9HnzwwSTJIYccUqP9kEMOydy5c/PXv/51uedXVFSkW7duy3w0OAAATUeLUhcAAAAAAHXpzjvvTL9+/bLzzjsv9fguu+ySfv361XiE5DvvvJNBgwZlxowZOfroo7PJJptk0qRJueWWWzJnzpy0atUqs2fPzs4775yXX345Rx11VAYMGJApU6bkjjvuyNtvv52uXbuucq1Tp07N3nvvnUMOOSRf/epX06NHj+pjV155ZebOnZujjz465eXl6dKlS1588cV87nOfS69evfL9738/bdu2zU033ZQDDjggt9566xKPcTvxxBPTuXPnnHnmmRk/fnwuvPDCnHDCCbnxxhur+1x11VU56qijsvnmm+e0005Lp06d8swzz2T06NHVu5Pdd9992XvvvTNw4MCceeaZadasWa688srstttuefDBBzNo0KAkyTHHHJNbbrklJ5xwQjbbbLNMnTo1Dz30UF5++eUMGDAgSfL4449n1113zZlnnpmzzjprlX9m7777bpLU+Hk/9NBDmTt3bjbYYIOMGDEif/nLX7J48eIMGTIkv/nNb7LNNtssc7wPPvgg99xzT7785S+nbdu2q1wPQFNjnf1EY1xnl2bevHlp3rz5EjvWVe1u99RTT+Wb3/xmjWMffvhh5s+fnylTpuSaa67JCy+8UOMRpAAANE2CbAAAAAA0GTNnzsw777yzwp21ttpqq9xxxx2ZNWtW2rdvn9NOOy3vvvtuHnvssWy33XbV/X70ox+lUCgkqdyB5oUXXshtt91W40b2D37wg+o+q+rdd9/NpZdemlGjRlW3Ve0y8/bbb+f111+vsdvN7rvvnj59+uSJJ55IeXl5kspHau6000753ve+t8QN9rXWWitjxoxJWVlZkmTx4sW56KKLMnPmzHTs2DEzZ87MSSedlEGDBuX+++9P69atq8+tek2FQiHHHHNMdt111/z973+vHmvUqFHZfPPN84Mf/CBjxoxJktx111355je/mV/84hfV43z3u99drZ/Nsvz85z9P8+bNM2LEiOq21157LUly2mmnZf31188111yTmTNn5uyzz85uu+2WF198MWuvvfZSx7vxxhuzcOHCHHbYYUWtE6Axss42/nV2aTbeeOMsWrQojz76aHbaaafq9qqd2iZNmrTEOQcffHD+8Y9/JElatWqVUaNG5Yc//GGt1woAQP3m0aIAAAAANBmzZs1KkhU+Jqvq+IcffpjFixfnL3/5S/bbb78aN9erVN1QvvXWW7P11lsvcRP7031WVXl5eb7+9a8v9diXvvSlGjfXp02blvvuuy8HH3xwZs2alSlTpmTKlCmZOnVq9tprr7z22mtL3Eg++uija9S28847Z9GiRZkwYUKS5J577smsWbPy/e9/v8bN9U+/pmeffTavvfZaDj300EydOrV63o8++iif//zn88ADD2Tx4sVJkk6dOuWxxx7LO++8s8zXPGzYsBQKhdXaJea6667LFVdcke985zvZcMMNq9tnz55dXfO9996bQw89NMcee2z+8pe/ZPr06fnNb36z3DG7deuWPfbYY5XrAWhqrLONe51dlkMPPTQdO3bMUUcdlXvuuSfjx4/P73//+/z2t79Nknz88cdLnHPuuedmzJgxueKKKzJ48ODMnz8/CxcuLFpNAAA0THZkAwAAAKDJqLpxXnWjfVk+fSP+gw8+yIcffpgttthiueeMGzcuX/rSl4pT6P/p1avXEo/pqtK/f/8a37/++uspFAr54Q9/uMwdTd5///306tWr+vs+ffrUON65c+ckyfTp05NUvqYky33tVbudHXHEEcvsM3PmzHTu3DnnnXdejjjiiKy77roZOHBg9tlnnxx++OFZb731lnnuynrwwQczcuTI7LXXXvnJT35S41ibNm2SJPvtt1/atWtX3T548OD0798///73v5c65htvvJFHHnkkJ5xwQlq0cCkVYEWss413nV2enj175o477sjXvva17LnnnkmSDh065OKLL84RRxxRY+2t8unHen/1q1/NgAEDcuSRR+aWW26p1VoBAKjfXH0BAAAAoMno2LFj1l577Tz//PPL7ff888+nV69e6dChw1J3EVldy9oxZtGiRUttrwpgrcyxqt1YTjnllOy1115LPWeDDTao8X3z5s2X2m9VHtFWNe/5559f46b0p1XdwD744IOz88475/bbb8+YMWNy/vnn5+c//3luu+227L333is952c999xz2X///bPFFlvklltuWSJ0ts466yRJevToscS53bt3rw4UfNZ1112XJB4rCrCSrLONc51dGbvsskveeOON/Oc//8lHH32UrbfeunpnuI022mi557Zq1Sr7779/zj333Hz88cfL/XsBAKBxE2QDAAAAoEnZd99984c//CEPPfRQdtpppyWOP/jggxk/fnxGjRqVJOnWrVs6dOiQF154Ybnjrr/++ivsU7UTy4wZM2q0Vz1ibE1U7bbSsmXL7L777ms8XlL5mpLkhRdeWOLm/Gf7dOjQYaXmXXvttXPcccfluOOOy/vvv58BAwbkJz/5yWrfYB83blyGDx+e7t275+67717qri8DBw5MkiUe+ZYk77zzTjbZZJOljn3ddddl/fXXz+DBg1erNoCmyDq78hrCOrsqmjdvXiNs989//jNJVqrujz/+OIVCIbNmzRJkAwBowpqVugAAAAAAqEunnnpq2rRpk1GjRmXq1Kk1jk2bNi3HHHNMKioqcuqppyZJmjVrlgMOOCB33nlnnnzyySXGq9pV5Utf+lKee+653H777cvsU3Uz+oEHHqg+tmjRovz+979f49fVvXv3DBs2LJdddlkmT568xPEPPvhglcfcc8890759+/zsZz/L3Llzaxyrek0DBw7M+uuvnwsuuCCzZ89e5ryLFi3KzJkzl6h5nXXWybx586rb5syZk1deeSVTpkxZYX3vvvtu9txzzzRr1iz/+Mc/0q1bt6X223jjjbP11lvnr3/9a41xx4wZk7feeit77LHHEuc888wzefnll3PooYeusA4APmGdXXn1fZ1dEx988EF+/vOfZ6uttqoRZHv//feX6DtjxozceuutWXfdddO9e/darQsAgPrNjmwAAAAANCkbbrhhrr766hx22GHZcsstM3LkyPTv3z/jx4/PFVdckSlTpuT666+vvhmeJD/96U8zZsyYDB06NEcffXQ23XTTTJ48OTfffHMeeuihdOrUKaeeempuueWWHHTQQTnqqKMycODATJs2LXfccUcuvfTSbL311tl8880zePDgnHbaaZk2bVq6dOmSG264IQsXLizKa/vNb36TnXbaKVtuuWW++c1vZr311st7772XRx55JG+//Xaee+65VRqvQ4cO+dWvfpVvfOMb2X777XPooYemc+fOee655zJnzpxcffXVadasWS6//PLsvffe2XzzzfP1r389vXr1yqRJkzJ27Nh06NAhd955Z2bNmpXevXtnxIgR2XrrrdOuXbv885//zBNPPJFf/OIX1XM+/vjj2XXXXXPmmWfmrLPOWm59w4cPzxtvvJHvfve7eeihh/LQQw9VH+vRo0eNgNqvfvWr7LHHHtlpp50yatSozJw5M7/85S+z0UYb5dhjj11i7GuvvTaJx4oCrCrr7Mqr7+vszJkzc/HFFydJHn744STJJZdckk6dOqVTp0454YQTqvsOHTo0Q4YMyQYbbJB33303v//97zN79uz87W9/S7Nmn+yrsffee6d3797ZYYcd0r1790ycODFXXnll3nnnndx4442r9PMDAKDxEWQDAAAAoMk56KCDsskmm+RnP/tZ9U31tdZaK7vuumtOP/30bLHFFjX69+rVK4899lh++MMf5tprr82HH36YXr16Ze+9905FRUWSpF27dnnwwQdz5pln5vbbb8/VV1+d7t275/Of/3x69+5dPda1116bUaNG5dxzz02nTp0ycuTI7LrrrkvdFWxVbbbZZnnyySdz9tln56qrrsrUqVPTvXv3bLvttjnjjDNWa8yRI0eme/fuOffcc/PjH/84LVu2zCabbJKTTz65us+wYcPyyCOP5Mc//nEuueSSzJ49Oz179swOO+xQ/ei4ioqKHHfccRkzZkxuu+22LF68OBtssEF++9vfLjVItjKqAgPnnXfeEseGDh1a42e66667ZvTo0fnhD3+Y008/PRUVFTnggANy3nnnLfE40sWLF+eGG27IgAEDsvHGG69WbQBNmXV25dXndXb69On54Q9/WKOtKhTXt2/fGkG2gQMH5uabb86kSZPSoUOH7LHHHvnxj39c/UjWKkcddVRuuOGG/OpXv8qMGTPSuXPnDB48ONddd1123nnn1aoTAIDGo6xQtTcxAAAAAAAAAAAAlECzFXcBWL4XX3wxX/3qV9OrV6+Ul5dnnXXWyWGHHZYXX3yx1KWxCqp+07x169bp1q1bRo4cmSlTpizRr6ysbKl/zj333Br9Xn311Zx88snZcccd07p165SVlWX8+PF19GoAGg/rbOOzxx57pKysrMZvrifJVVddtcx1tqysrPrxZp924403ZsiQIWnbtm06deqUHXfcMffdd19dvRQAAAAAoARcN274VuVear9+/ZZ6zfiYY45Zou8999yTnXbaKRUVFencuXNGjBjhHi0NikeLAmvktttuy1e+8pV06dIlI0eOTP/+/TN+/PhcccUVueWWW3LDDTfkwAMPLHWZrMDvfve7HHfccfn85z+fX/7yl3n77bfz61//Ok8++WQee+yxtG7dukb/PfbYI4cffniNtm233bbG94888kguuuiibLbZZtl0003z7LPP1vbLAGh0rLONz2233ZZHHnlkqcd22WWX/OlPf1qi/Ve/+lWee+65fP7zn6/RftZZZ+VHP/pRRowYkSOPPDILFizICy+8kEmTJtVK7QAAAABA6blu3Dis6r3UbbbZJt/5zndqtG200UY1vv/b3/6WL37xixkwYEDOPffcfPjhh/n1r3+dnXbaKc8880y6detW7JcBRefRosBqGzduXLbaaqv06dMnDzzwQI2Fb8qUKdl5553z1ltv5fnnn896661XwkqXNGfOnFRUVCzRvnDhwixevDitWrUqQVWlMX/+/PTo0SNbbbVV7r///pSVlSWp/KCz33775aKLLsqJJ55Y3b+srCzHH398LrnkkuWOO23atLRs2TLt27fPBRdckFNPPTVvvvlm+vXrV5svB6DRsM42PnPnzs2mm26ao446KmecccZKracff/xxevTokcGDB2fMmDHV7Y8++mh23HHH/OIXv8jJJ59c26UDAAAAAPWA68aNx6rcS+3Xr1+22GKL/O1vf1vumJtvvnnmz5+fF198sfrn+dxzz2XAgAH59re/nV/84he18VKgqDxaFFht559/fubMmZPf//73S6S3u3btmssuuywfffRRzjvvvBrHJk2alJEjR2adddZJeXl5+vfvn2OPPTbz58+v7jNjxoycfPLJ6devX8rLy9O7d+8cfvjh1Y+6rHr01me3Qa0KYt1///3VbcOGDcsWW2yRp556KrvssksqKipy+umnZ/z48SkrK8sFF1yQCy+8MOuvv37Ky8vz0ksvJUleeeWVjBgxIl26dEnr1q2z3Xbb5Y477qgxX1UdDz/8cP7f//t/6datW9q2bZsDDzwwH3zwwRI/s7///e8ZOnRo2rdvnw4dOmT77bfPddddV6PPY489luHDh6djx46pqKjI0KFD8/DDD9foM2vWrHz729+u/vl07949e+yxR55++unqPnPmzMkrr7yy1MeDftoLL7yQGTNm5Mtf/nJ1iC1J9t1337Rr1y433HDDUs/7+OOPM3fu3GWO26VLl7Rv3365cwOwbNbZxrHOftp5552XxYsX55RTTlnpc+68887MmjUrhx12WI32Cy+8MD179sy3vvWtFAqFzJ49e6XHBAAAAAAaJteNG89149W5lzp//vx89NFHSz02bdq0vPTSSznwwANrhAK33nrrbLrppsu85wv1jUeLAqvtzjvvTL9+/bLzzjsv9fguu+ySfv365a677qpue+eddzJo0KDMmDEjRx99dDbZZJNMmjQpt9xyS+bMmZNWrVpl9uzZ2XnnnfPyyy/nqKOOyoABAzJlypTccccdefvtt9O1a9dVrnXq1KnZe++9c8ghh+SrX/1qevToUX3syiuvzNy5c3P00UenvLw8Xbp0yYsvvpjPfe5z6dWrV77//e+nbdu2uemmm3LAAQfk1ltvXWI73hNPPDGdO3fOmWeemfHjx+fCCy/MCSeckBtvvLG6z1VXXZWjjjoqm2++eU477bR06tQpzzzzTEaPHp1DDz00SXLfffdl7733zsCBA3PmmWemWbNmufLKK7PbbrvlwQcfzKBBg5IkxxxzTG655ZaccMIJ2WyzzTJ16tQ89NBDefnllzNgwIAkyeOPP55dd901Z555Zs4666xl/mzmzZuXJGnTps0Sx9q0aZNnnnkmixcvTrNmn2Sfr7rqqvz2t79NoVDIpptumh/84AfVrwGA4rDOfqIhr7NVJk6cmHPPPTd//OMfl7rmLsu1116bNm3a5H/+539qtN97773Zcccdc9FFF+Wcc87J1KlT07Nnz/zv//5vTjjhhJUeHwAAAABoOFw3/kRjuG68Ku67775UVFRk0aJF6du3b04++eR861vfqj6+vHu+FRUVefHFF/Puu++mZ8+eRa0Liq4AsBpmzJhRSFL44he/uNx++++/fyFJ4cMPPywUCoXC4YcfXmjWrFnhiSeeWKLv4sWLC4VCoXDGGWcUkhRuu+22Zfa58sorC0kKb775Zo3jY8eOLSQpjB07trpt6NChhSSFSy+9tEbfN998s5Ck0KFDh8L7779f49jnP//5wpZbblmYO3dujbl33HHHwoYbbljdVlXH7rvvXl1boVAonHzyyYXmzZsXZsyYUSgUKn9e7du3L+ywww6Fjz/+eKmvafHixYUNN9ywsNdee9UYa86cOYX+/fsX9thjj+q2jh07Fo4//vglfj5L+1mceeaZy+33wQcfFMrKygojR46s0f7KK68UkhSSFKZMmVLdvuOOOxYuvPDCwl//+tfC7373u8IWW2xRSFL47W9/u8w5zj///KX+fQGwdNbZQo06GvI6W2XEiBGFHXfcsfr7JCucY+rUqYVWrVoVDj744Brt06ZNKyQprLXWWoV27doVzj///MKNN95YGD58+FL/LgAAAACAhs9140KNOhrDdeMqK7qXut9++xV+/vOfF/7yl78UrrjiisLOO+9cSFL47ne/W91n0aJFhU6dOhU+//nP1zh3ypQphbZt2xaSFJ588slVqgtKwaNFgdUya9asJFnhdqdVxz/88MMsXrw4f/nLX7Lffvtlu+22W6Jv1WMtb7311my99dZLpOo/3WdVlZeX5+tf//pSj33pS1+qsfXutGnTct999+Xggw/OrFmzMmXKlEyZMiVTp07NXnvtlddeey2TJk2qMcbRRx9do7add945ixYtyoQJE5Ik99xzT2bNmpXvf//7ad269VJf07PPPpvXXnsthx56aKZOnVo970cffZTPf/7zeeCBB7J48eIkSadOnfLYY4/lnXfeWeZrHjZsWAqFwgrT/l27ds3BBx+cq6++Or/4xS/yxhtv5MEHH8yXv/zltGzZMknlY0SrPPzww/nWt76V/fffP8ccc0yeeuqpbLHFFjn99NNr9ANg9VlnG886myRjx47NrbfemgsvvHCFfT/tlltuyfz585d4rGjVY0SnTp2ayy+/PKecckoOPvjg3HXXXdlss81yzjnnrNI8AAAAAED957px47puvCruuOOOfPe7380Xv/jFHHXUUfnXv/6VvfbaK7/85S/z9ttvJ0maNWuWUaNG5d57781pp52W1157LU899VQOPvjg6kfIupdLQyDIBqyWqg9AVR+YluXTH6g++OCDfPjhh9liiy2We864ceNW2GdV9erVq8azwD+tf//+Nb5//fXXUygU8sMf/jDdunWr8efMM89Mkrz//vs1zunTp0+N7zt37pwkmT59epLK15Rkua/rtddeS5IcccQRS8x7+eWXZ968eZk5c2aS5LzzzssLL7yQddddN4MGDcpZZ52VN954Y6V+Fktz2WWXZZ999skpp5yS9ddfP7vssku23HLL7LfffkmSdu3aLfPcVq1a5YQTTsiMGTPy1FNPrXYNAHzCOtt41tmFCxfmpJNOyte+9rVsv/32q3Tutddemy5dumTvvfeu0V61NXzLli0zYsSI6vZmzZrly1/+ct5+++1MnDhxteoFAAAAAOon140bz3XjNVVWVpaTTz45CxcuzP3331/d/qMf/SgjR47Meeedl4022ijbbbddWrRokZEjRyZZ/j1fqC9alLoAoGHq2LFj1l577Tz//PPL7ff888+nV69e6dChQ1ET3stK/i9atGip7Ut7FviyjlWl6k855ZTstddeSz1ngw02qPF98+bNl9qvUCgsc97Pqpr3/PPPzzbbbLPUPlUfLg4++ODsvPPOuf322zNmzJicf/75+fnPf57bbrttiZvdK6Njx47561//mokTJ2b8+PHp27dv+vbtmx133DHdunVLp06dlnv+uuuum6TytyUAWHPW2cazzl5zzTV59dVXc9lll2X8+PE1js2aNSvjx49P9+7dU1FRUePYxIkT8+CDD+boo4+u3iG1SpcuXdK6det06tRpiZ9N9+7dk1RerPnshRwAAAAAoOFy3bjxXDcuhqXdn23VqlUuv/zy/OQnP8l///vf9OjRIxtttFEOPfTQNGvWbImfIdRHgmzAatt3333zhz/8IQ899FB22mmnJY4/+OCDGT9+fEaNGpUk6datWzp06JAXXnhhueOuv/76K+xTlaifMWNGjfaqrWLXxHrrrZekcpeT3XfffY3HSypfU5K88MILy/yAUNWnQ4cOKzXv2muvneOOOy7HHXdc3n///QwYMCA/+clP1uiDUp8+fapvelftsPalL31phedV/bbBp7cABmDNWGdXXn1eZydOnJgFCxbkc5/73BLHrrnmmlxzzTW5/fbbc8ABB9Q4dv3116dQKCzxWNGkcue1bbbZJk888UTmz59f47caq7a1tyYDAAAAQOPjuvHKq8/XjYthefdne/TokR49eiSpDBref//92WGHHezIRoPg0aLAajv11FPTpk2bjBo1KlOnTq1xbNq0aTnmmGNSUVGRU089NUnlTdcDDjggd955Z5588sklxqtKx3/pS1/Kc889l9tvv32Zfao+VDzwwAPVxxYtWpTf//73a/y6unfvnmHDhuWyyy7L5MmTlzj+wQcfrPKYe+65Z9q3b5+f/exnmTt3bo1jVa9p4MCBWX/99XPBBRdk9uzZy5x30aJF1VvYfrrmddZZJ/PmzatumzNnTl555ZVMmTJlletNktNOOy0LFy7MySefvEQNnzZr1qxceOGF6dq1awYOHLhacwGwJOvsyqvP6+whhxyS22+/fYk/SbLPPvvk9ttvzw477LDEedddd1369Omz1ItRSfLlL385ixYtytVXX13dNnfu3Fx77bXZbLPNss466yy3LgAAAACg4XHdeOXV5+vGq2LatGlL7Hq3YMGCnHvuuWnVqlV23XXX5Z5/wQUXZPLkyfnOd75TtJqgNtmRDVhtG264Ya6++uocdthh2XLLLTNy5Mj0798/48ePzxVXXJEpU6bk+uuvr/5QkyQ//elPM2bMmAwdOjRHH310Nt1000yePDk333xzHnrooXTq1Cmnnnpqbrnllhx00EE56qijMnDgwEybNi133HFHLr300my99dbZfPPNM3jw4Jx22mmZNm1aunTpkhtuuCELFy4symv7zW9+k5122ilbbrllvvnNb2a99dbLe++9l0ceeSRvv/12nnvuuVUar0OHDvnVr36Vb3zjG9l+++1z6KGHpnPnznnuuecyZ86cXH311WnWrFkuv/zy7L333tl8883z9a9/Pb169cqkSZMyduzYdOjQIXfeeWdmzZqV3r17Z8SIEdl6663Trl27/POf/8wTTzyRX/ziF9VzPv7449l1111z5pln5qyzzlpufeeee25eeOGF7LDDDmnRokX+8pe/ZMyYMTnnnHOy/fbb1/i5/OUvf8l+++2XPn36ZPLkyfnjH/+YiRMn5k9/+lONHWFmzpyZiy++OEny8MMPJ0kuueSSdOrUKZ06dcoJJ5ywSj9DgKbGOrvy6vM6u8kmm2STTTZZ6rH+/fsvsRNbUvkbgs8//3y+//3vL3O7/lGjRuXyyy/P8ccfn//+97/p06dP/vSnP2XChAm58847V+nnBwAAAAA0DK4br7z6fN04Wfl7qXfccUfOOeecjBgxIv3798+0adNy3XXX5YUXXshPf/rT9OzZs3rMP//5z7n11luzyy67VNd400035Rvf+MZKPYUL6oUCwBp6/vnnC1/5ylcKa6+9dqFly5aFnj17Fr7yla8U/vOf/yy1/4QJEwqHH354oVu3boXy8vLCeuutVzj++OML8+bNq+4zderUwgknnFDo1atXoVWrVoXevXsXjjjiiMKUKVOq+4wbN66w++67F8rLyws9evQonH766YV77rmnkKQwduzY6n5Dhw4tbL755kvU8eabbxaSFM4///yl1jlu3LjC4YcfXujZs2ehZcuWhV69ehX23Xffwi233FLd58orrywkKTzxxBM1zh07duwSdRQKhcIdd9xR2HHHHQtt2rQpdOjQoTBo0KDC9ddfX6PPM888U/if//mfwlprrVUoLy8v9O3bt3DwwQcX7r333kKhUCjMmzevcOqppxa23nrrQvv27Qtt27YtbL311oXf/va3S63hzDPPXOrr+7S//e1vhUGDBhXat29fqKioKAwePLhw0003LdFvzJgxhT322KP6Z9KpU6fCnnvuWV3bp1X9fJf2p2/fviusCYBK1tmGv84uTZLC8ccfv9Rj3//+9wtJCs8///xyx3jvvfcKRxxxRKFLly6F8vLywg477FAYPXr0atUDAAAAADQcrhs3/OvGK3sv9cknnyzst99+1X8v7dq1K+y0005LvZf72GOPFXbZZZdC586dC61bty5svfXWhUsvvbSwePHiFdYD9UVZofB/eyYCAAAAAAAAAABACTQrdQEAAAAAAAAAAAA0bYJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJsAAAAAAAAAAAAlJQgGwAAAAAAAAAAACUlyAYAAAAAAAAAAEBJCbIBAAAAAAAAAABQUoJs9dhZZ52VsrKylJWV5ayzzip1OQAAAAAAAAAAALWiRW0OPmzYsPzrX/9a6rHy8vJ07NgxHTp0SI8ePbLttttm4MCB2W233dKnT5/aLItV9Oabb+aJJ57Ik08+mSeeeCJPPfVUZs2aVX28UCiUsDoAAAAAAAAAAKChq9Ug2/LMmzcv77//ft5///28/vrrefjhh5MkzZo1y/Dhw3PSSSdlr732KlV5RXfVVVfl61//epLkiCOOyFVXXVXaglbC5MmTs+WWW2bq1KmlLgUAAAAAAAAAAGjE6izItv3222fQoEHV3y9evDgzZ87MjBkz8uKLL2bChAnV7XfffXfuvvvuHHnkkbnooovSvn37uiqTT5k3b54QGwAAAAAAwEpatGhxmjUrS1lZWa3Os3Dh4rRo0axW5wAAgLpWZ0G2ffbZJ2edddYyj7/77rv505/+lIsuuihvv/12kspdzF588cX861//Sps2beqoUj6rXbt2GTBgQAYNGpTtt98+c+fOzRFHHFHqsgAAAAAAAOqNRYsW54gfPJDOHcpz0fcH11qYbfacBfnC8WPypd375aTDNq+VOQAAoBRK9mjRz+rZs2dOPfXUHHvssTnqqKNy8803J0meeOKJHHnkkbnxxhtLXGHT06NHj7zwwgvZdNNN06zZJ7/Vc//995euKAAAAAAAgHqmKsR27V3jqttqI8xWFWJ74Kl388BT7yaJMBsAAI1GvdtzuF27drnxxhvzhS98obrtpptuygMPPFDCqpqmNm3aZPPNN68RYgMAAAAAAKCmP9z6ao0Q2yXXv5STzn00hUKhaHN8OsRW5Vs/fzTPvDylaHMAAEAp1cuEUllZWa655pq0b9++uu0nP/nJSp378ssv5/TTT8+gQYPSo0ePtGrVKt26dcsOO+yQM844I++8884Kxxg2bFjKyspSVlZWvfvYW2+9lR/84AfZeuut06VLl7Rt2zabbLJJTj755Lz++uvLHOvII49MWVlZvv71r1e3XX311dXjf/rPsGHDVljbwoULc80112T33XdPr169Ul5enrXXXjsHHHBA/va3v63wfAAAAAAAAIrrG/+zcQ4Zvl6NtmKG2ZYWYkuSC74zKNtu2nWNxwcAgPqg3jxa9LO6dOmSI488MhdffHGS5J577sm0adPSpUuXpfafN29evvWtb+Xyyy/PokWLahybMmVKpkyZkscffzwXXHBBzjvvvJxwwgkrXcsdd9yRww8/PDNnzqzR/uqrr+bVV1/NZZddlgsvvDBHH330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LS8vL7m5ualr167au3dvjj7x8fF66qmnrO9P9erV1bNnT/3000/WPklJSTpx4oT1PcvP/Pnz5e/vrylTpsgwDCUkJOTZb/fu3apWrZo1xCZJdnZ2Gjx4sC5fvqzvv//+tscCAAAAAACA7RBkAwAAAAAAQKW3YcMGBQUFqXPnznm2d+nSRUFBQdq4caN128WLF9WuXTutWrVKDz/8sN5//3098sgj+v7775WUlCRJSkhIUOfOnbVgwQLdd999eu+99zRhwgSdOHFCFy5cKFKtUVFR6tOnj1q0aKH58+erW7du1rYlS5ZowYIFGjdunObNmydfX18dO3ZMHTp00PHjx/X8889r3rx5qlKlivr3769169blGn/y5Mk6cuSIZs6cqccff1wbNmzQpEmTcvRZunSp/vznPys6OlrTp0/XW2+9pRYtWujbb7+19tm+fbu6dOmiuLg4zZw5U2+88YZiYmJ07733av/+/dZ+EyZM0D//+U8NGDBACxcu1DPPPCNXV1cdP37c2mf//v2666679MEHH9z2/dm2bZvatm2r999/X9WqVZOHh4dq1qyZa9+UlBS5urrm2j97drv//e9/tz0WAAAAAAAAbMfB7AIAAAAAAAAAM8XGxurixYvq169fvv2aNWum9evXKz4+Xh4eHpo+fbouX76sffv2qU2bNtZ+s2fPlmEYkrJmegsNDdXatWv14IMPWvu89NJL1j6FdfnyZX344YcaP368dVv2bG4XLlzQ6dOnc8wq16NHDwUGBurAgQNydnaWJE2cOFGdOnXSc889l6MuSfLz89OWLVtksVgkZS23+f777ys2NlZeXl6KjY3Vk08+qXbt2mnnzp1ycXGx7pv9mgzD0IQJE9StWzdt3rzZOtb48ePVuHFjvfTSS9qyZYskaePGjRo7dqzmzZtnHefZZ58t0ntz/fp1RUZGau/evdq+fbtmzpypwMBALVmyRJMnT5ajo6P1ffvTn/6k7777TufOnVOdOnWsY+zevVtS1mx7AAAAAAAAKD3MyAYAAAAAAIBKLT4+XpLk4eGRb7/s9ri4OGVmZurrr7/WAw88kCPEli07uPXVV1+pefPmucJiN/cpLGdnZ40aNSrPtgEDBuQIsUVHR2v79u0aPHiw4uPjFRkZqcjISEVFRalXr146depUrsDWuHHjctTWuXNnZWRk6Ny5c5KkrVu3Kj4+Xs8//3yOENvNr+nw4cM6deqUhg4dqqioKOtxExMT1b17d+3atUuZmZmSJG9vb+3bt08XL1685WsOCQmRYRiaNWtWvu9N9jKiUVFR+vjjj/XMM89o8ODB2rhxoxo1aqTXXnvN2nfMmDGyt7fX4MGD9d///ldnzpzRm2++aZ2lLjk5Od9jAQAAAAAAwLYIsgEAAAAAAKBSyw6oZQfabuXmwNu1a9cUFxenJk2a5LvPmTNnbtunsAICAuTk5JRnW3BwcI7np0+flmEYmjFjhqpVq5bjMXPmTEnS1atXc+wTGBiY47mPj4+krNnOpKzXJCnf13Xq1ClJ0siRI3Md9+OPP1ZKSopiY2MlSXPmzFFoaKjuuOMOtWvXTrNmzdJvv/1WoPfij7KXCnV0dNTAgQOt2+3s7PTwww/rwoULOn/+vKSsGfZWrlypM2fO6J577lH9+vX1/vvva/78+ZIkd3f3ItUAAAAAAACAomFpUQAAAAAAAFRqXl5eqlmzpo4ePZpvv6NHjyogIECenp42na3rVjOzZWRk5Lk9O6xVkLbsWc+eeeYZ9erVK8996tevn+O5vb19nv0KsxRq9nHnzp2rFi1a5NknOyg2ePBgde7cWevWrdOWLVs0d+5cvf3221q7dq369OlT4GNKkq+vr1xcXOTt7Z3rdVSvXl1SViAvO6w3cOBA/eUvf9GRI0eUkZGhVq1aaefOnZKkhg0bFurYAAAAAAAAKB6CbAAAAAAAAKj0+vbtq48++kh79uxRp06dcrXv3r1bYWFhGj9+vCSpWrVq8vT0VGhoaL7j1qtX77Z9smc8i4mJybE9eynP4qhbt66krBnKevToUezxpKzXJEmhoaG5QnB/7OPp6Vmg49asWVMTJ07UxIkTdfXqVbVq1Uqvv/56oYNsdnZ2atGihQ4cOKDU1NQcM9dlL11689KrkuTk5KS2bdtan3/33XeSZLP3CwAAAAAAAAXD0qIAAAAAAACo9KZNmyZXV1eNHz9eUVFROdqio6M1YcIEubm5adq0aZKyAlP9+/fXhg0bdPDgwVzjZc9eNmDAAB05ckTr1q27ZZ/s0NeuXbusbRkZGVq8eHGxX1f16tUVEhKiRYsW6dKlS7nar127Vugx77vvPnl4eOjNN9/UjRs3crRlv6bWrVurXr16euedd5SQkHDL42ZkZFiXGL255lq1aiklJcW6LSkpSSdOnFBkZORt63v44YeVkZGhZcuWWbfduHFDK1asUKNGjVSrVq1b7nvq1Cl9+OGH6tu3LzOyAQAAAAAAlDJmZAMAAAAAAECl16BBAy1btkzDhg1T06ZNNXr0aAUHByssLEyffPKJIiMj9fnnn1tDZ5L0xhtvaMuWLeratavGjRunu+66S5cuXdLq1au1Z88eeXt7a9q0aVqzZo0GDRqkxx57TK1bt1Z0dLTWr1+vDz/8UM2bN1fjxo3VoUMHTZ8+XdHR0fL19dWqVauUnp5uk9f2j3/8Q506dVLTpk01duxY1a1bV1euXNEPP/ygCxcu6MiRI4Uaz9PTU++++67GjBmjtm3baujQofLx8dGRI0eUlJSkZcuWyc7OTh9//LH69Omjxo0ba9SoUQoICFBERIR27NghT09PbdiwQfHx8apdu7YGDhyo5s2by93dXd99950OHDigefPmWY+5f/9+devWTTNnztSsWbPyrW/8+PH6+OOP9cQTT+jkyZMKDAzUZ599pnPnzmnDhg05+jZq1EiDBg1SYGCgzp49q3/+85/y9fXVhx9+WKj3BAAAAAAAAMVHkA0AAAAAAACQNGjQIN1555168803reE1Pz8/devWTS+88IKaNGmSo39AQID27dunGTNmaMWKFYqLi1NAQID69OkjNzc3SZK7u7t2796tmTNnat26dVq2bJmqV6+u7t27q3bt2taxVqxYofHjx+utt96St7e3Ro8erW7duqlnz57Ffl2NGjXSwYMH9corr2jp0qWKiopS9erV1bJlS7388stFGnP06NGqXr263nrrLb366qtydHTUnXfeqalTp1r7hISE6IcfftCrr76qDz74QAkJCfL391f79u2tS7S6ublp4sSJ2rJli9auXavMzEzVr19fCxcu1OOPP16k2lxdXbV9+3Y9++yz+vTTT5WYmKgWLVpo48aN6tWrV46+zZs315IlS3TlyhVVrVpVgwcP1iuvvKLq1asX6dgAAAAAAAAoOouRPd8/AAAAAAAAAAAAAAAAAAAmsDO7AAAAAAAAAAAAAAAAAABA5UaQDQAAAAAAAAAAAAAAAABgKoJsAAAAAAAAAAAAAAAAAABTEWQDAAAAAAAAAAAAAAAAAJiKIBsAAAAAAAAAAAAAAAAAwFQE2QAU27FjxzR8+HAFBATI2dlZtWrV0rBhw3Ts2DGzS0MB/frrr5o6dao6duwoFxcXWSwWhYWF5eq3c+dOWSyWWz5ef/11a99t27bpscceU8OGDeXm5qa6detqzJgxunTpUim+MgAAAAAAAAAAAAAAUB5YDMMwzC4CQPm1du1aDRkyRL6+vho9erSCg4MVFhamTz75RFFRUVq1apUefPBBs8vEbSxdulSjR49Wo0aN5ODgoMOHD+vs2bMKCgrK0e/KlSvaunVrrv0/++wzbdmyRfv371fbtm0lSW3atFF0dLQGDRqkBg0a6LffftMHH3wgNzc3HT58WP7+/qXx0gAAAAAAAAAAAAAAQDlAkA1AkZ05c0bNmjVTYGCgdu3apWrVqlnbIiMj1blzZ4WHh+vo0aOqW7euiZXmlpSUJDc3t1zb09PTlZmZKScnJxOqMk90dLQcHR3l4eGhd955R9OmTcszyHYrDRo0kMVi0cmTJ63bdu3apU6dOsnOzi7Htq5du+rFF1/Ua6+9ZuuXAQAAAAAAAAAAAAAAyimWFgVQZHPnzlVSUpIWL16cI8QmSVWrVtWiRYuUmJioOXPm5GiLiIjQ6NGjVatWLTk7Oys4OFiPP/64UlNTrX1iYmI0depUBQUFydnZWbVr19aIESMUGRkpKWsGsbyWv8xe+nLnzp3WbSEhIWrSpIn+97//qUuXLnJzc9MLL7ygsLAwWSwWvfPOO5o/f77q1asnZ2dn/fLLL5KkEydOaODAgfL19ZWLi4vatGmj9evX5zhedh179+7V3/72N1WrVk1VqlTRgw8+qGvXruV6zzZv3qyuXbvKw8NDnp6eatu2rVauXJmjz759+9S7d295eXnJzc1NXbt21d69e3P0iY+P11NPPWV9f6pXr66ePXvqp59+svZJSkrSiRMnrO9Zfnx9feXh4XHbfnnZv3+/Tp8+rWHDhuXY3qVLlxwhtuxtvr6+On78eJGOBQAAAAAAAAAAAAAAKiYHswsAUH5t2LBBQUFB6ty5c57tXbp0UVBQkDZu3GjddvHiRbVr104xMTEaN26c7rzzTkVERGjNmjVKSkqSk5OTEhIS1LlzZx0/flyPPfaYWrVqpcjISK1fv14XLlxQ1apVC11rVFSU+vTpo7/+9a8aPny4atSoYW1bsmSJbty4oXHjxsnZ2Vm+vr46duyY7rnnHgUEBOj5559XlSpV9OWXX6p///766quvci2XOnnyZPn4+GjmzJkKCwvT/PnzNWnSJH3xxRfWPkuXLtVjjz2mxo0ba/r06fL29tahQ4f07bffaujQoZKk7du3q0+fPmrdurVmzpwpOzs7LVmyRPfee692796tdu3aSZImTJigNWvWaNKkSWrUqJGioqK0Z88eHT9+XK1atZKUFTDr1q2bZs6cqVmzZhX6PSuoFStWSFKuIFteEhISlJCQUKR/QwAAAAAAAAAAAAAAUHERZANQJLGxsbp48aL69euXb79mzZpp/fr1io+Pl4eHh6ZPn67Lly9r3759atOmjbXf7Nmzlb3S8dy5cxUaGqq1a9fmCIy99NJLKupqyJcvX9aHH36o8ePHW7dlz+Z24cIFnT59Osescj169FBgYKAOHDggZ2dnSdLEiRPVqVMnPffcc7mCbH5+ftqyZYssFoskKTMzU++//75iY2Pl5eWl2NhYPfnkk2rXrp127twpFxcX677Zr8kwDE2YMEHdunXT5s2brWONHz9ejRs31ksvvaQtW7ZIkjZu3KixY8dq3rx51nGeffbZIr03xZGRkaEvvvhC7dq1U/369W/bf/78+UpNTdXDDz9cCtUBAAAAAAAAAAAAAIDygqVFARRJfHy8JN12Ocrs9ri4OGVmZurrr7/WAw88kCPEli07uPXVV1+pefPmucJiN/cpLGdnZ40aNSrPtgEDBuQIsUVHR2v79u0aPHiw4uPjFRkZqcjISEVFRalXr146deqUIiIicowxbty4HLV17txZGRkZOnfunCRp69atio+P1/PPP58jxHbzazp8+LBOnTqloUOHKioqynrcxMREde/eXbt27VJmZqYkydvbW/v27dPFixdv+ZpDQkJkGEaJzsa2bds2XblypUCzse3atUuvvPKKBg8erHvvvbfEagIAAAAAAAAAAAAAAOUPM7IBKJLsgFp2oO1Wbg68Xbt2TXFxcWrSpEm++5w5c0YDBgywTaH/LyAgQE5OTnm2BQcH53h++vRpGYahGTNmaMaMGXnuc/XqVQUEBFifBwYG5mj38fGRJF2/fl1S1muSlO9rP3XqlCRp5MiRt+wTGxsrHx8fzZkzRyNHjtQdd9yh1q1b6/7779eIESNUt27dW+5bElasWCF7e/vbzrB24sQJPfjgg2rSpIk+/vjjUqoOAAAAAAAAAAAAAACUFwTZABSJl5eXatasqaNHj+bb7+jRowoICJCnp6eSk5NtdvxbzcyWkZGR53ZXV9dbjvXHtuxZz5555hn16tUrz33+uIymvb19nv0KsxRq9nHnzp2rFi1a5NnH3d1dkjR48GB17txZ69at05YtWzR37ly9/fbbWrt2rfr06VPgYxZHcnKy1q1bpx49eqhGjRq37BceHq777rtPXl5e2rRp021n8QMAAAAAAAAAAAAAAJUPQTYARda3b1999NFH2rNnjzp16pSrfffu3QoLC9P48eMlSdWqVZOnp6dCQ0PzHbdevXq37ZM941lMTEyO7dlLeRZH9qxmjo6O6tGjR7HHk7JekySFhobmCsH9sY+np2eBjluzZk1NnDhREydO1NWrV9WqVSu9/vrrpRZkW79+veLj4/NdVjQqKkr33XefUlJStG3bNtWsWbNUagMAAAAAAAAAAAAAAOWLndkFACi/pk2bJldXV40fP15RUVE52qKjozVhwgS5ublp2rRpkiQ7Ozv1799fGzZs0MGDB3ONlz172YABA3TkyBGtW7fuln2yQ1+7du2ytmVkZGjx4sXFfl3Vq1dXSEiIFi1apEuXLuVqv3btWqHHvO++++Th4aE333xTN27cyNGW/Zpat26tevXq6Z133lFCQsItj5uRkaHY2NhcNdeqVUspKSnWbUlJSTpx4oQiIyMLXW9BrFy5Um5ubnrwwQfzbE9MTNT999+viIgIbdq0SQ0aNCiROgAAAAAAAAAAAAAAQPnHjGwAiqxBgwZatmyZhg0bpqZNm2r06NEKDg5WWFiYPvnkE0VGRurzzz+3hs4k6Y033tCWLVvUtWtXjRs3TnfddZcuXbqk1atXa8+ePfL29ta0adO0Zs0aDRo0SI899phat26t6OhorV+/Xh9++KGaN2+uxo0bq0OHDpo+fbqio6Pl6+urVatWKT093Sav7R//+Ic6deqkpk2bauzYsapbt66uXLmiH374QRcuXNCRI0cKNZ6np6feffddjRkzRm3bttXQoUPl4+OjI0eOKCkpScuWLZOdnZ0+/vhj9enTR40bN9aoUaMUEBCgiIgI7dixQ56entqwYYPi4+NVu3ZtDRw4UM2bN5e7u7u+++47HThwQPPmzbMec//+/erWrZtmzpypWbNm5VtfbGysFixYIEnau3evJOmDDz6Qt7e3vL29NWnSpBz9o6OjtXnzZg0YMMC63OkfDRs2TPv3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AAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAMAAAAAAAAAAKCoBNkAAAAAAAAAAAAoKkE2AAAAAAAAAAAAikqQDQAAAAAAAAAAgKISZAPqxQsvvJCjjz46vXv3TllZWXr16pWjjjoqL7zwQrFLo4buuOOOfOlLX0r//v3ToUOHbLnlljnrrLMye/bsNR736quvpl27dikpKckzzzzTMMUCAAAAAI2eeeOm79///nfOPPPM7LrrrlXzwJMmTVpt+7vvvjvbb7992rVrl8033zwXXHBBli1b1nAFA7Rwxt7m4eabb64aT3v06JGRI0dm+vTpxS4L6kVJoVAoFLsIoHm54447csQRR6S8vDwjR45Mv379MmnSpFx77bWZMWNGbr755hxyyCHFLpO16N69e3r16pWDDz44m2++ecaPH59f/epX6d+/f5599tm0b99+lccdeOCBefjhh7NgwYI8/fTT2XHHHRu4cgAAAACgsTFv3Dxcf/31GTlyZLbZZpu0bt06//rXv/L666+nb9++K7W9//7785nPfCbDhw/PEUcckfHjx+eqq67KSSedlF/+8pcNXzxAC2PsbR5++ctf5tRTT82ee+6Zz3/+83nrrbfy85//PAMHDsyTTz6Zdu3aFbtEqFOCbECdevXVV/Oxj30sm2++eR599NH06NGjat/06dMzbNiwvPnmmxk3blz69+9fxEpXtnDhwnTo0GGl7cuWLUtFRUXatm1bhKqKZ/To0Rk+fHi1bTfeeGOOPfbY/OY3v8mJJ5640jF//etfc+CBB+acc87JRRddJMgGAAAAAJg3bkZmzpyZNm3apFOnTrn00ktz9tlnrzbItu2226ZNmzZ55pln0rp16yTJ+eefnx/96Ed58cUXs9VWWzVw9QAth7G3eViyZEk22mijfOxjH8vo0aNTUlKSJPnzn/+cz33uc/nFL36R008/vchVQt1yaVGgTo0aNSoLFy7Mr3/962ofiJLKFb6uueaaLFiwIJdcckm1fVOmTMnIkSPTq1evlJWVpV+/fvnqV7+aJUuWVLWZPXt2zjzzzPTt2zdlZWXZdNNNc8wxx1Qtm3r99devchnzFYP66NGjq7YNHz48gwcPzj//+c/svvvu6dChQ84777xMmjQpJSUlufTSS3P55ZdnwIABKSsry4svvpgkefnll3PYYYelvLw87dq1y4477pi777672uOtqGPs2LH5f//v/6VHjx7ZYIMNcsghh+S9995b6TW7//77s8cee6RTp07p3Llzhg4dmptuuqlamyeffDL77bdfunTpkg4dOmSPPfbI2LFjq7WZN29ezjjjjKrXp2fPntl7773z7LPPVrVZuHBhXn755RotNfvREFuSqm9lvPTSSyvtW7p0ab7xjW/kG9/4RgYMGLDW/gEAAACAlsG8cfOZNy4vL0+nTp3W2u7FF1/Miy++mJNOOqkqxJYkp556agqFQm677ba19gHAujP2No+xd8KECZk9e3a+9KUvVYXYkuSzn/1sOnbsmJtvvnmNx0NT1HrtTQBq7p577knfvn0zbNiwVe7ffffd07dv39x7771V295+++3stNNOmT17dk466aRstdVWmTJlSm677bYsXLgwbdu2zfz58zNs2LC89NJLOeGEE7L99ttn+vTpufvuu/PWW2+le/futa51xowZ2X///XP44Yfn6KOPzkYbbVS177rrrsuiRYty0kknpaysLOXl5XnhhRfyqU99Kr179863vvWtbLDBBvnjH/+Ygw8+OLfffvtKS++efvrp6datWy644IJMmjQpl19+eU477bTccsstVW2uv/76nHDCCdl2223z7W9/O127ds1zzz2Xv/zlLznyyCOTJA8//HD233//7LDDDrngggtSWlqa6667Lp/+9Kfz2GOPZaeddkqSnHLKKbntttty2mmnZZtttsmMGTMyZsyYvPTSS9l+++2TJE899VRGjBiRCy64IBdeeGGtX7OpU6cmySpf78svvzyzZs3K+eefnzvuuKPWfQMAAAAAzZN54w80x3njVXnuueeSZKUrdvTq1Subbrpp1X4A6oex9wNNeexdvHhxkqR9+/Yr7Wvfvn2ee+65VFRUpLTUGlY0IwWAOjJ79uxCksJBBx20xnYHHnhgIUlh7ty5hUKhUDjmmGMKpaWlhaeffnqlthUVFYVCoVD47ne/W0hSuOOOO1bb5rrrriskKbz++uvV9j/yyCOFJIVHHnmkatsee+xRSFL41a9+Va3t66+/XkhS6Ny5c+Hdd9+ttm/PPfcsDBkypLBo0aJqj73rrrsWBg0aVLVtRR177bVXVW2FQqFw5plnFlq1alWYPXt2oVCofL06depU2HnnnQvvv//+Kp9TRUVFYdCgQYV99923Wl8LFy4s9OvXr7D33ntXbevSpUvha1/72kqvz6peiwsuuGCN7VZn5MiRhVatWhX+85//VNv+zjvvFDp16lS45pprCoXCB6/Bqv6bAgAAAAAth3njQrU6mtO88ahRo1b52n543xtvvLHSvqFDhxZ22WWXWj0WADVn7C1Uq6Mpj73vvfdeoaSkpDBy5Mhq219++eVCkkKSwvTp09fYBzQ1YplAnZk3b16SrHVZ8RX7586dm4qKitx111353Oc+t9I3s5JULZF6++23Z7vttlspQf/hNrVVVlaW448/fpX7Dj300GrL7M6cOTMPP/xwvvjFL2bevHmZPn16pk+fnhkzZmTffffNK6+8kilTplTr46STTqpW27Bhw7J8+fJMnjw5SfK3v/0t8+bNy7e+9a20a9dulc/pX//6V1555ZUceeSRmTFjRtXjLliwIHvuuWceffTRVFRUJEm6du2aJ598Mm+//fZqn/Pw4cNTKBTW6Vt1N910U6699tqcddZZGTRoULV95557bvr3758TTzyx1v0CAAAAAM2XeePmPW+8Ou+//36Sytfzo9q1a1e1H4C6Z+xtPmNv9+7d88UvfjE33HBDLrvssrz22mt57LHH8qUvfSlt2rRJEmMqzY5LiwJ1ZsWHnRUfjlbnwx+e3nvvvcydOzeDBw9e4zGvvvpqDj300Lop9L969+6dtm3brnJfv379qt2fOHFiCoVCvvOd7+Q73/nOKo95991307t376r7m2++ebX93bp1S5LMmjUrSeVzSrLG5/7KK68kSY499tjVtpkzZ066deuWSy65JMcee2w222yz7LDDDjnggANyzDHHpH///qs9tqYee+yxjBw5Mvvuu29++MMfVtv3xBNP5He/+10eeughy9YCAAAAANWYN26+88ZrsuISaCsuifZhixYtWuUl0gCoG8be5jX2XnPNNXn//ffzzW9+M9/85jeTJEcffXQGDBiQO+64Ix07dlynfqGxEmQD6kyXLl2yySabZNy4cWtsN27cuPTu3TudO3eu04T46lL+y5cvX+X2Nf2h/NF9KxL03/zmN7Pvvvuu8piBAwdWu9+qVatVtisUCqt93I9a8bijRo3Kxz/+8VW2WfHh5Itf/GKGDRuWO++8Mw888EBGjRqVn/zkJ7njjjuy//771/gxP+r555/PgQcemMGDB+e2225L69bVh45zzjknw4YNS79+/TJp0qQkyfTp05Mk77zzTt54442VPiACAAAAAC2DeePmOW+8NptsskmSyjnizTbbrNq+d955JzvttFO9PTZAS2fsbV5jb5cuXfKnP/0pb7zxRiZNmpQ+ffqkT58+2XXXXdOjR4907dq11n1CYybIBtSpz372s/nNb36TMWPGZLfddltp/2OPPZZJkybl5JNPTpL06NEjnTt3zoQJE9bY74ABA9baZkV6fvbs2dW2r1gWdn2sSMi3adMme+2113r3l1Q+pySZMGHCSh+oPtqmc+fONXrcTTbZJKeeempOPfXUvPvuu9l+++3zwx/+cJ0nJF599dXst99+6dmzZ+67775VJvrfeOONTJ48eaVvRCTJgQcemC5duqz03wQAAAAAaDnMG9dcU5g3rokVJ/mfeeaZaqG1t99+O2+99VZOOumkentsAIy9tdFUxt7NN9+8avGQ2bNn55///Gedr44HjYFrwAF16uyzz0779u1z8sknZ8aMGdX2zZw5M6eccko6dOiQs88+O0lSWlqagw8+OPfcc0+eeeaZlfpbkYQ/9NBD8/zzz+fOO+9cbZsVHyAeffTRqn3Lly/Pr3/96/V+Xj179szw4cNzzTXX5J133llp/3vvvVfrPvfZZ5906tQpF198cRYtWlRt34rntMMOO2TAgAG59NJLM3/+/NU+7vLlyzNnzpyVau7Vq1e1pdsXLlyYl19+uWrFtDWZOnVq9tlnn5SWluavf/1rtevPf9ivf/3r3HnnndVup59+epLk0ksvze9///u1PhYAAAAA0HyZN665xj5vXFPbbrttttpqq/z617+utgLPL3/5y5SUlOSwww6rs8cCYGXG3pprimPvt7/97SxbtixnnnnmOh0PjZkV2YA6NWjQoNxwww056qijMmTIkIwcObLqkpPXXnttpk+fnj/84Q9VH2CS5Ec/+lEeeOCB7LHHHjnppJOy9dZb55133smtt96aMWPGpGvXrjn77LNz22235Qtf+EJOOOGE7LDDDpk5c2buvvvu/OpXv8p2222XbbfdNrvssku+/e1vZ+bMmSkvL8/NN9+cZcuW1clzu+qqq7LbbrtlyJAh+cpXvpL+/ftn2rRpefzxx/PWW2/l+eefr1V/nTt3zs9+9rOceOKJGTp0aI488sh069Ytzz//fBYuXJgbbrghpaWl+e1vf5v9998/2267bY4//vj07t07U6ZMySOPPJLOnTvnnnvuybx587LpppvmsMMOy3bbbZeOHTvmwQcfzNNPP53LLrus6jGfeuqpjBgxIhdccEEuvPDCNda333775bXXXss555yTMWPGZMyYMVX7Ntpoo+y9995JKj/cfdSKb1jsscce2XHHHWv1ugAAAAAAzYt545pr7PPGc+bMyRVXXJEkGTt2bJLkyiuvTNeuXdO1a9ecdtppVW1HjRqVAw88MPvss08OP/zwTJgwIVdeeWVOPPHEbL311rV6XQCoHWNvzTX2sffHP/5xJkyYkJ133jmtW7fOXXfdlQceeCAXXXRRhg4dui4vITRuBYB6MG7cuMIRRxxR2GSTTQpt2rQpbLzxxoUjjjiiMH78+FW2nzx5cuGYY44p9OjRo1BWVlbo379/4Wtf+1ph8eLFVW1mzJhROO200wq9e/cutG3btrDpppsWjj322ML06dOr2rz66quFvfbaq1BWVlbYaKONCuedd17hb3/7WyFJ4ZFHHqlqt8ceexS23Xbblep4/fXXC0kKo0aNWmWdr776auGYY44pbLzxxoU2bdoUevfuXfjsZz9buO2226raXHfddYUkhaeffrrasY888shKdRQKhcLdd99d2HXXXQvt27cvdO7cubDTTjsV/vCHP1Rr89xzzxU+//nPFzbccMNCWVlZoU+fPoUvfvGLhYceeqhQKBQKixcvLpx99tmF7bbbrtCpU6fCBhtsUNhuu+0KV1999SpruOCCC1b5/D4syWpve+yxxxqPXd1rAAAAAAC0XOaNm/688YrXYlW3Pn36rNT+zjvvLHz84x8vlJWVFTbddNPC+eefX1iyZMlaHweAumHsbfpj75///OfCTjvtVOjUqVOhQ4cOhV122aXwxz/+ca3HQVNVUij8dy1EAAAAAAAAAAAAKILSYhcAAAAAAAAAAABAyybIBgAAAAAAAAAAQFEJsgEAAAAAAAAAAFBUgmwAAAAAAAAAAAAUlSAbAAAAAAAAAAAARSXIBgAAAAAAAAAAQFEJsgEAAAAAAAAAAFBUgmwAANDEvPDCCzn66KPTu3fvlJWVpVevXjnqqKPywgsvFLs0auiOO+7Il770pfTv3z8dOnTIlltumbPOOiuzZ8+u1m7GjBkZNWpUdt999/To0SNdu3bNLrvskltuuaU4hQO0AMbZpu/f//53zjzzzOy6665p165dSkpKMmnSpJXajR49OiUlJau9/fCHP2z44gEAAABasJJCoVAodhEAAEDN3HHHHTniiCNSXl6ekSNHpl+/fpk0aVKuvfbazJgxIzfffHMOOeSQYpfJWnTv3j29evXKwQcfnM033zzjx4/Pr371q/Tv3z/PPvts2rdvnyT585//nM9//vM54IADMmLEiLRu3Tq33357HnnkkXz3u9/N9773vSI/E4DmxTjbPFx//fUZOXJkttlmm7Ru3Tr/+te/8vrrr6dv377V2k2bNi1/+9vfVjr+d7/7XR544IE89dRTGTp0aANVDQAAAIAgGwAANBGvvvpqPvaxj2XzzTfPo48+mh49elTtmz59eoYNG5Y333wz48aNS//+/YtY6coWLlyYDh06rLR92bJlqaioSNu2bYtQVfGMHj06w4cPr7btxhtvzLHHHpvf/OY3OfHEE5Mkr7/+ekpLS9OnT5+qdoVCIXvttVfGjh2bGTNmZIMNNmjI0gGaLeNs8zFz5sy0adMmnTp1yqWXXpqzzz57lUG21Rk0aFBKSkryn//8p34LBQAAAKAalxYFAIAmYtSoUVm4cGF+/etfVzu5nlSu8HXNNddkwYIFueSSS6rtmzJlSkaOHJlevXqlrKws/fr1y1e/+tUsWbKkqs3s2bNz5plnpm/fvikrK8umm26aY445JtOnT09SubLJqi7LteKSXKNHj67aNnz48AwePDj//Oc/s/vuu6dDhw4577zzMmnSpJSUlOTSSy/N5ZdfngEDBqSsrCwvvvhikuTll1/OYYcdlvLy8rRr1y477rhj7r777mqPt6KOsWPH5v/9v/+XHj16ZIMNNsghhxyS9957b6XX7P77788ee+yRTp06pXPnzhk6dGhuuummam2efPLJ7LfffunSpUs6dOiQPfbYI2PHjq3WZt68eTnjjDOqXp+ePXtm7733zrPPPlvVZuHChXn55ZerXrM1+WiILUnVCj8vvfRS1bZ+/fpVC7ElSUlJSQ4++OAsXrw4r7322lofC4CaMc42n3G2vLw8nTp1Wmu7VXnqqacyceLEHHXUUet0PAAAAADrrnWxCwAAAGrmnnvuSd++fTNs2LBV7t99993Tt2/f3HvvvVXb3n777ey0006ZPXt2TjrppGy11VaZMmVKbmf1kBAAAE/tSURBVLvttixcuDBt27bN/PnzM2zYsLz00ks54YQTsv3222f69Om5++6789Zbb6V79+61rnXGjBnZf//9c/jhh+foo4/ORhttVLXvuuuuy6JFi3LSSSelrKws5eXleeGFF/KpT30qvXv3zre+9a1ssMEG+eMf/5iDDz44t99++0qXcTv99NPTrVu3XHDBBZk0aVIuv/zynHbaabnllluq2lx//fU54YQTsu222+bb3/52unbtmueeey5/+ctfcuSRRyZJHn744ey///7ZYYcdcsEFF6S0tDTXXXddPv3pT+exxx7LTjvtlCQ55ZRTctttt+W0007LNttskxkzZmTMmDF56aWXsv322yepPPE9YsSIXHDBBbnwwgtr/ZpNnTo1SWr0etemLQA1Y5z9QHMcZ2vq97//fZIIsgEAAAAUgSAbAAA0AXPmzMnbb7+dgw46aI3tPvaxj+Xuu+/OvHnz0qlTp3z729/O1KlT8+STT2bHHXesavf9738/hUIhSeUKNBMmTMgdd9xR7UT2+eefX9WmtqZOnZpf/epXOfnkk6u2rVhl5q233srEiROrrXaz1157ZfPNN8/TTz+dsrKyJMmpp56a3XbbLeeee+5KJ9g33HDDPPDAAykpKUmSVFRU5Be/+EXmzJmTLl26ZM6cOfn617+enXbaKaNHj067du2qjl3xnAqFQk455ZSMGDEi999/f1VfJ598crbddtucf/75eeCBB5Ik9957b77yla/ksssuq+rnnHPOWafXZnV+8pOfpFWrVjnssMPW2G7mzJn57W9/m2HDhmWTTTap0xoAWirjbPMfZ2ti+fLlueWWW7LTTjtl4MCBDf74AAAAAC2dS4sCAEATMG/evCRZ62WyVuyfO3duKioqctddd+Vzn/tctZPrK6w4oXz77bdnu+22W+kk9ofb1FZZWVmOP/74Ve479NBDq51cnzlzZh5++OF88YtfzLx58zJ9+vRMnz49M2bMyL777ptXXnklU6ZMqdbHSSedVK22YcOGZfny5Zk8eXKS5G9/+1vmzZuXb33rW9VOrn/4Of3rX//KK6+8kiOPPDIzZsyoetwFCxZkzz33zKOPPpqKiookSdeuXfPkk0/m7bffXu1zHj58eAqFwjqtEnPTTTfl2muvzVlnnZVBgwattl1FRUWOOuqozJ49O1dccUWtHweAVTPONu9xtqYeeuihTJs2zWpsAAAAAEViRTYAAGgCVpw4X3GifXU+fCL+vffey9y5czN48OA1HvPqq6/m0EMPrZtC/6t3795p27btKvf169ev2v2JEyemUCjkO9/5Tr7zne+s8ph33303vXv3rrq/+eabV9vfrVu3JMmsWbOSVD6nJGt87q+88kqS5Nhjj11tmzlz5qRbt2655JJLcuyxx2azzTbLDjvskAMOOCDHHHNM+vfvv9pja+qxxx7LyJEjs+++++aHP/zhGtuefvrp+ctf/pIbb7wx22233Xo/NgCVjLPNd5ytjd///vdp1apVvvSlLzXo4wIAAABQSZANAACagC5dumSTTTbJuHHj1thu3Lhx6d27dzp37pz333+/zh5/dSvGLF++fJXb27dvv9q+PrpvxWos3/zmN7Pvvvuu8piPXt6rVatWq2xXm0u0rXjcUaNG5eMf//gq23Ts2DFJ8sUvfjHDhg3LnXfemQceeCCjRo3KT37yk9xxxx3Zf//9a/yYH/X888/nwAMPzODBg3PbbbeldevV/4n2ve99L1dffXV+/OMf58tf/vI6PyYAKzPONs9xtjbef//93Hnnndlrr72y0UYbNchjAgAAAFCdIBsAADQRn/3sZ/Ob3/wmY8aMyW677bbS/sceeyyTJk3KySefnCTp0aNHOnfunAkTJqyx3wEDBqy1zYqVWGbPnl1t+4pLjK2PFauttGnTJnvttdd695dUPqckmTBhwkon5z/apnPnzjV63E022SSnnnpqTj311Lz77rvZfvvt88Mf/nCdT7C/+uqr2W+//dKzZ8/cd999VSfzV+Wqq67KhRdemDPOOCPnnnvuOj0eAGtmnK25pjDO1tbdd9+defPmuawoAAAAQBGVFrsAAACgZs4+++y0b98+J598cmbMmFFt38yZM3PKKaekQ4cOOfvss5MkpaWlOfjgg3PPPffkmWeeWam/FauqHHrooXn++edz5513rrbNipPRjz76aNW+5cuX59e//vV6P6+ePXtm+PDhueaaa/LOO++stP+9996rdZ/77LNPOnXqlIsvvjiLFi2qtm/Fc9phhx0yYMCAXHrppZk/f/5qH3f58uWZM2fOSjX36tUrixcvrtq2cOHCvPzyy5k+ffpa65s6dWr22WeflJaW5q9//Wt69Oix2ra33HJLvv71r+eoo47KT3/607X2DcC6Mc7WXGMfZ9fFTTfdlA4dOuSQQw6pl/4BAAAAWDsrsgEAQBMxaNCg3HDDDTnqqKMyZMiQjBw5Mv369cukSZNy7bXXZvr06fnDH/5QdTI8SX70ox/lgQceyB577JGTTjopW2+9dd55553ceuutGTNmTLp27Zqzzz47t912W77whS/khBNOyA477JCZM2fm7rvvzq9+9atst9122XbbbbPLLrvk29/+dmbOnJny8vLcfPPNWbZsWZ08t6uuuiq77bZbhgwZkq985Svp379/pk2blscffzxvvfVWnn/++Vr117lz5/zsZz/LiSeemKFDh+bII49Mt27d8vzzz2fhwoW54YYbUlpamt/+9rfZf//9s+222+b4449P7969M2XKlDzyyCPp3Llz7rnnnsybNy+bbrppDjvssGy33Xbp2LFjHnzwwTz99NO57LLLqh7zqaeeyogRI3LBBRfkwgsvXGN9++23X1577bWcc845GTNmTMaMGVO1b6ONNsree+9d1ecxxxyTDTfcMHvuuWd+//vfV+tn1113rVppB4D1Y5ytucY+zs6ZMydXXHFFkmTs2LFJkiuvvDJdu3ZN165dc9ppp1VrP3PmzNx///059NBD17hCKgAAAAD1S5ANAACakC984QvZaqutcvHFF1edVN9www0zYsSInHfeeRk8eHC19r17986TTz6Z73znO/n973+fuXPnpnfv3tl///3ToUOHJEnHjh3z2GOP5YILLsidd96ZG264IT179syee+6ZTTfdtKqv3//+9zn55JPz4x//OF27ds3IkSMzYsSIqtDV+thmm23yzDPP5Hvf+16uv/76zJgxIz179swnPvGJfPe7312nPkeOHJmePXvmxz/+cX7wgx+kTZs22WqrrXLmmWdWtRk+fHgef/zx/OAHP8iVV16Z+fPnZ+ONN87OO+9cdem4Dh065NRTT80DDzyQO+64IxUVFRk4cGCuvvrqfPWrX12n2lYEBi655JKV9u2xxx5Vr+mLL76YJUuW5L333ssJJ5ywUtvrrrtOkA2gDhlna64xj7OzZs3Kd77znWrbVoTi+vTps1KQ7dZbb83SpUtz5JFHrtPjAQAAAFA3Sgor1vsHAAAAAAAAAACAIigtdgEAAAAAAAAAAAC0bIJsAAAAAAAAAAAAFJUgGwAAAAAAAAAAAEUlyAYAAAAAAAAAAEBRCbIBAAAAAAAAAABQVIJsAAAAAAAAAAAAFJUgGwAA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Ds19992XJHnuueey33771aiWmhoyZMha22y44QfX3Jg7d26dPn6S/PznP19p24IFC3LVVVflu9/9biZOnJjPfOYz+d///d8ce+yxdf741L+SkuT4QZWD/C2vJw+/U/nN748a0Cn5Qr/Kpbx9i4n6smP35ObhH1xaZ1VLxndtmxzSJzmsr8vbAtSX5YXke/+qXBGnpoviXP5C5SU7erSrz8oAaGoKheT7z1WuulbTMeWXLyf79E423aBeSysa4ywAtBxd2laeZL/3rco5z1dWcRqntCQZvnHyxX6V86OszLwxAKtinIXmo9EH2T58mc7y8uoR2rfffrvq5xkzZuSqq66qdf+rulzph22++eY16ufD7d57771a17E2a1sVLqkM862wdOkqPrnXgw022CDnnHNOBg0alM9//vOpqKjIKaeckmHDhqV///4NUgN17+MbVt6mL0qufim5+795z316VQbYPl5eGXqD+rZJh+Qb2yYnb5Vc+WJy838XP9prk2REr2TExklbYUqAevX4u8nU92t3TEWSOycnJ21ZLyUB0ET9c0YyeUHtj7tjcvL1beq+nsbAOAsALUvbVpUBq4M3T8bPSm55Lfnrf091HbhZcspWSU/Bq7UybwzAqhhnoXlo1JcWXbBgQd56662q+x+9tOaHQ27ratmyZWvc36FDhxr1s8EGH3w1+KOXQK0LJY08NXTIIYdkzz33TFJ5edWrr766yBVRF7q3S4Z96Lrge/dKPrGhEBsNr12rZIcPFp3Mvr0rbyYjAOrfnZPX7Y+G2yfVdSUANHV3rcOYUkjlWLSq1cKbA+MsALRMJSXJx8qTvXp9sG3YRk6u15Z5YwBWxTgLTVujXpHtmWeeyfLly6vu77LLLtX2fzg89rGPfSzPP/98ndewcOHCGrVbcYnTJOnUqVOd19EU7L333nnooYeSJGPHji1yNQBAU9WmTZtUVFSs07G7lXfPg58cXqf1XPSfFzNq4svVts1by5ch1sWIESMyZub0Ou93fW31i/Fpv1ntl8GZsThp3a5DCksX10NVQH0pLS1tsBW+KY71GWfX1xaXPJ4NBu1U6+PmLU3ad+2e5fPXvKp+U2SchZbFOAsAAABr1qiDbLfeemvVz6Wlpdltt92q7d9oow+Wipo6dWq91PDGG2/UqN2bb75Z9XP37i3zgsrdunWr+nnGjBlFrAQAoO6cv8U2OWfQ1tW2DX74/iJV0/BKWrVZe6M1HOsEOwArrO+Y0hwZZwEAAADgA402yDZjxozccMMNVff322+/dOnSpVqbj3/84ykrK8vixYvz7rvvZuLEiRk4cGCd1vHEE0/ka1/72hrbLF++PE8//XTV/e23336lNo390qB14Z133qn6uby8vIiVAABN2fqsUFAx4cUsP+97dVhNw3nkkUdSOrj2K7LUtxPHJM/PrLy0W220LU2WzJ/jkuQAjUwxVwL6+hPJE+8mtV0PrlVJMn/6O2mzLtfgbOSMswAAAADwgUY5BVgoFHLsscdm/vz5VdvOP//8ldq1b98+n/70p6vuX3311XVey9133525c+eusc1f/vKXvPvuu0mSdu3a5ZOf/ORKbdq1a1f1c3NdPv7Pf/5z1c9bb731GloCANBU7Ne79ifXS5Ps2ztOrgNQzT69ah9iK03y6U3SLENsiXEWAAAAAD6s0U0Dzp8/P4cffnjuvffeqm1f/vKXVxkOS5Jzzz236ucrrrgiDz74YI0fqyaXI507d27OO++81e5fsGBBzjnnnKr7hx122EorxyXJhhtuWPXzlClTalxjsSxYsCCLFi2qcftf/vKXeeaZZ6ruH3roofVRFgAADWz/zZJ2rWp3TEWSL/Srl3IAaML26p10rOW1AZr7mGKcBQAAAIAPNJog29SpU3PppZdmm222yR//+Meq7bvuumt+85vfrPa4PfbYI8cee2ySZNmyZfnMZz6Tiy++uNpqbh+2aNGi3HXXXTnooINy4IEHrrWutm3b5qqrrsq3vvWtLFmypNq+KVOm5DOf+UxefPHFJJUrxF1wwQWr7Gfw4MFVPz/55JN544031vrYxfTKK69k4MCBGTVqVN58883Vtps6dWrOPPPMapdfHTZsWD772c+u9pjjjjsuJSUlKSkpSd++feuybACAOvXgriNyzqCVV5qd8On9c8xmLeMM8gatkxO3qN0xwzdOtulaL+UA0IS1a5WcslXN25ck2aVH8onyeiup6IyzAAAAAPCBWn4Pdt3dd999mT59etX9ioqKzJ07N7Nnz86LL76Y119/faVjvvKVr+RnP/tZysrK1tj3Nddck3feeScPPPBAlixZkvPOOy8XXXRRdt5552y++eYpKyvL7Nmz8+qrr2bChAlZvHhxkmSHHXZYa90XXXRR/ud//ic/+clPcu2112b48OHp1q1bJk+enNGjR1cLt11++eUZOHDgKvvZeOONs+uuu+Yf//hHFi1alO222y777bdfNtlkk5SWVuYJBwwYkK9+9atrramhTJkyJeecc07OOeec9O3bN4MHD0737t1TVlaWuXPn5uWXX864ceOyfPnyqmO23HLL3HLLLUWsGgCAunbswGT6ouTm1ytDBWu6BNoOGyYXbd9QlQHQ1HypX/LeouSGiWsfUwZ3S34ytPlfQtM4CwAAAACVGizI9vTTT+fpp59ea7tWrVpl//33zxlnnJE999yzRn2XlZXlvvvuy/e+971cdtllWbhwYRYuXJhHHnlktce0adMmu+yyy1r7Hjp0aG699dYcc8wxmT59em677baV2rRr1y4//elPc9JJJ62xr5///Of59Kc/nXnz5mX27Nm5+eabq+3fY489Gk2QrU2bNiktLU1FRUWSZNKkSZk0adJq25eWlmbkyJH5yU9+km7dujVQlQAANISSkuSswcnAzsn1ryRvLVz5RHuXNpWXORu5RdKm0az7DEBjU1KSnL5N0r9T8r+vJJPnrzymdGydHNo3+cqWtb/sZlNknAUAAACASg0WZPuotm3bpnPnzunSpUs23njjfOITn8gOO+yQvfbaK5tuummt+2vVqlW+//3v5/TTT8+NN96YBx98MC+++GKmT5+epUuXpnPnzunTp0+GDBmSESNG5IADDkiPHj1q1PdBBx2UcePG5Ve/+lXuvffevPHGG1myZEk222yz7LfffjnttNMyaNCgtfaz4447Zty4cbniiivyyCOP5LXXXsv8+fOrrWjWWGy77baZOnVq/va3v+Uf//hHxo0bl9deey0zZszI0qVL06lTp2y44YYZMmRIPvWpT+WII45I7969i102AAD1pKQkObhPctDmydPTk8ffTX73auW+i7ZPPr1J0rYFhA0AqBuf2Sw5YNPk2RnJ2HeTGydWbr/wE8nevZKyFjamGGcBAAAAoJ6DbKNHj67P7lepR48eOeuss3LWWWfVab99+vTJxRdfnIsvvni9+unbt28uu+yyGrW98MILc+GFF9a47+HDh6dQWNMFKGqnR48eOfLII3PkkUfWWZ9Jcv311+f666+v0z4BAGgYJSXJTj0qbytOsO9X+++hAEBKSpIdulfeVgTZPrtZcWsqNuMsAAAAAC2ZixEAAAAA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Oscdd1z69OmTzTbbLL/73e9SUlJStf+mm25K165dc8ABB2TUqFG5++6707dv31x33XWZPXt2fvzjH+fSSy8t4jMAKA7vn9REsX9PfB4DAAAAVsWlRQEAAGiUjjvuuPz9739PeXl5xowZU23f7bffnv333z/vvvtuHn300Tz22GMZPHhw7rnnnnTv3j0zZ87M/Pnzi1Q5QHF5/6Qm/J4AAAAAjY0gGwAAAI3a4MGDM2XKlKr7M2fOTFlZWUpKSvLMM89kxIgRSZK99torjz/+eJJk5513zsMPP1yUegEaC++f1ITfEwAAAKCxcGlRAAAAGrXHH388X/va16ruT5w4MX369EmSzJ49O507d06SdOnSJbNnz06S9OnTJ+PGjcuBBx7Y4PUCNBbeP6kJvycA0LiVbFieQrGLqAclG5YXuwSAJs34QHMlyAYAAECjdP3112f06NH55Cc/mR133HGVbbp06ZKpU6cmSebOnZuuXbs2YIUAjZP3T2rC7wkANA2tL/lBsUsAoBEyPtBcubQoAAAAjdJxxx2X0aNH5+KLL662feDAgZk8eXKSZOjQofn73/+eJHnwwQezyy67JEkmT56crbbaqmELBmgkvH9SE35PAAAAgMZGkA2AFmXSpEnp0qVLhg8fnk984hO54oorVtnurLPOytNPP50keeGFF7L77rtnt912y7nnnlvV5sgjj8yUKVMapG4A4APl5eVZtGhRCoVCevbsmV133TW77bZbxo0bl8997nNJkieeeCIjRowocqUAjYv3T2rC7wkALZF5YwCAxkGQDYAWZ8iQIRk9enQeffTRXHTRRXnnnXeq7Z82bVomTJiQoUOHJklOOeWUXHHFFRkzZkxeeeWVPPTQQ0mSU089NaNGjWrw+gGgJRg+fHguvPDC1e4/7LDDcv/99ydJzj333IwZMyb/93//lzZt2mT69OkpLy9Pp06dGqhagMbD+yc14fcEAFZm3hgAoPgE2QBosTp16pR+/frlzTffrLb9jjvuyD777JMkWbJkSaZNm5btttsuSXLooYdWTUh86lOfyl//+tcUCoWGLRwAyJFHHpkDDjhglfu6d++eyy67rIErAmgavH9SE35PAGjJzBsDABSPIBsALdaUKVPy2muvZcCAAdW2T5gwIVtssUWSZMaMGSkvL6/aV15enhkzZiRJSkpK0rlz50ybNq3higYAAAAAoN6YNwYAKJ7WxS4AABra+PHjM3z48BQKhVx99dXZcMMNq+0vKSmp+rm8vDyzZs2quj9r1qyV2gMAAAAA0LSZNwYAKD5BNgBanCFDhmT06NFr3P+f//wnSVJWVpaePXvmhRdeyLbbbpu77rorJ510UpKkUChk7ty52WijjRqibAAAAAAA6ol5YwCA4nNpUQD4iM9//vN54IEHqu5fffXVOeWUU7Lbbrtl8803z5577pkkGTt2bPbee+9q38QDAAAAAKD5MW8MAFD/rMgGQIvSt2/fjBkzZo1tevTokcGDB+fpp5/O0KFDM2TIkDz22GMrtbv66qtzySWX1FepAAAAAAA0APPGAACNgyAbAKzCZZddttY2N910UwNUAgAAAABAY2DeGACgfrm0KAAAAAAAAAAAAEUlyAYAAAAAAAAAAEBRCbIBAAAAAAAAAABQVIJsAAAAAAAAAAAAFJUgGwAAAAAAAAAAAEUlyAYAAAAAAAAAAEBRtS52AQAAANDYFOYvSGHS5GKXsU5K+vZJSccNil0G0EJ5/wQAAABgXQmyAQAAwEcUJk3O8vO+V+wy1kmrH12QksHbFLsMoIXy/gkAAADAunJpUQAAAAAAAAAAAIpKkA0AAAAAAAAAAICiEmQDAAAAAAAAAACgqATZAAAAAAAAAAAAKCpBNgAAAAAAAAAAAIpKkA0AAAAAAAAAAICiEmQDAAAAAAAAAACgqFoXuwAAoNKyc76TwoyZxS6jzpVsWJ7Wl/yg2GUAAAAAAAAA0IgJsgFAI1GYMTN5b3qxy6hzhWIXAAAAAAAAAECj59KiAAAAAAAAAAAAFJUgGwAAAAAAAAAAAEUlyAYAAAAAAAAAAEBRCbIBAAAAAAAAAABQVIJsAAAA1Ll330/+NuWD+1MXFq8WgKZk1uLkwbc/uP/G/OLVQuNlnAUAAACao9bFLgAAAIDm418zkpteS0a/k1R8aPvnHkyGbZQc0T8Z2qNo5QE0Wi/Nrnz//NuUZFnhg+2ffzjZuUdyeL9k2MZFK49GwjgLAAAANGeCbAAAANSJP7yW/HRCUpLqJ9eTpJBkzLvJo9OSU7dKTtiiCAUCNFL3vpl8719JCiu/fybJ0+8lT76XHNU/OWPbpKSkgQukUTDOAgAAAM2dS4sCAACw3u5+I7lsQuWJ9FWFMJKk4r8rDF39cnLzaw1VWeOw1z8eySWvvLTS9sEP358b33y9CBUBjcXod5ILn6t8j1zt++d///39a8k1/26oyhoH75+VjLMAAABASyDIBgAAwHpZuCwZNb52x/z8xWT2kvqpB6CpWFaR/Ghc7Y659j/J2wvrpx4aJ+MsAAAA0FIIsgEAALBe/vJW8v7y2h2ztCL58xv1Uw9AU/Ho1GTm4spVtmrjjkn1UQ2NlXEWAAAAaCkE2QAAAFgvd72RlNTymJIkd0yuj2oAmo4/vVH7yblCkju9f7YoxlkAAACgpWhd7AIAAODD2rRpk4qKimKXAdTCkBumpXXn7rU6ppBk0syFadWqU/0UtZ52K++eBz85vE77vOg/L2bUxJerbZu3bFmdPkaSjBgxImNmTl+nY0tLS7N06dI6rojGxDjbuGx9xQtpt+lWtT5uztKkdfsNUliyqB6qWj8t9f2zPjXHcbalMs4CAADAmgmyAQAAsH5KartOzH+VtqrbOhq587fYJucM2rratsEP31+kaoBGoWTdL5ZQUlJa60uSNlUt/v3TOAsAAAC0EIJsAAA0KlYogKbni48kr89LrQMVvTqX5dnly+ulpvVVMeHFLD/ve8UuY5088sgjKR28TbHLoJEyzjYup4xN/jmj9u+f7VslSxbMW+d8U33y/ln3muM4CwAAAM3BrLmL061zWaPvsylZ9699AgAAQJLPblb7k+srjgNoyQ5Yh/fPkiSf2WzdF+mi6THOAgAAQOPzlzFvpe9+t+S+x96ssz4nvjE3Hzv0zvzgmufqrM+mRpANAACA9XLgZknrWgYqSpMc0qdeygFoMvbplWxQy+slFJIc1rc+qqGxMs4CAABA47Jk6fJ89aKxmTt/aQ4548E6CbNNfGNuRoy8L29NW5DvX/Nc/v367PUvtAkSZAMAAGC9dC1LTtyidsccNSDZqH391NMYPbjriJwzaOuVtk/49P45ZrN+RagIaAzatU5OX/mtYY0+t1kysHP91NMYef80zgIAAEBj07ZNq9z/y32zcff2WbK0Yr3DbB8OsbVuXZJbL/10tuzXte4KbkIE2QBo1AqFZMKs5NZJH2y7+uXkh88nL88pWlkAwEeM3CI5/L95gtX9obliMZmDNk9O36YhqgJo/A7rl5yyZeXPa3v/HLFxct52DVEVjY1xFgCqM28MABTbVv265pFrD1jvMNuqQmwHf7pv3RfcRNTy4gUA0HBGv5Nc+5/kpY9MPLw+v/J25+RkSLfKb6Z/aqPi1AgAVCopSc4anGzbLblxYvLK3P9uT+Vl8JKkX6fkyP6VJ9hLanmJNIDm7MQtK1dZu2FiMn5W5bYPv3/26pAc0T/5Qr+klffPFsk4CwAfMG8MADQWK8JsI0bel6nT388hZzyYOy/fKwcM26xGxwuxrUyQDYBG6fpXkitfWnu78bOSM56snNA/vH/919VUff/fE/KzV/+TWQd8vlb7AKA2SkqS/TdN9uudvDA7eWZ6smBZskHrZLvy5OPlTqwDrM7wTSpv/5mTPP5uMn9Z0qF1snWXZKceSan3zxbPOAsA5o0BgMZnXcNsQmyrJsgGQKNzy+s1m4xYoZDk0glJx9bJZzevt7IAgBoqKUkGd6u8AVA7W3SpvMHqGGcBaKnMGwMAjVVtw2xCbKtXWuwCAODD3l6YXDZ+3Y696PlkxqK6rQcAAAAAgOIybwwANHYrwmwbd2+fJUsrcsgZD+a+x95cqZ0Q25oJsgHQqNwxKalYx2OXFZI/vVGX1QDw/9u7f9g47zKA489rn8slpuCmDooMxU5ypKFyBqgQEqhIKYXBDG0aNgRCsaBDBSRDFbHAAgxRhCIEgkp0YkFqS6ZYAlUgAQtDRYUjUlQTklDSWA0hDTG2fHGOIdhAE/+LX9/znv35SLfc+X565OHe03Nf3QEAAABkszcGADrBnWK2X798aeHxC29cF7EtQ8gGQGXMzkWcXONC4cVzEXOtUsbZkG7cvHnb7ab/FwAAAABQUfbGAEAneXvMdvjY7xYeO/TN34rYllHLHgA2m5mZmZiYmMgeAyrpzHRvvDW7c01nTM5EnHr5z7GrPl3SVO2zq9mMnnU8f2ruRmw99cIdH+vtXr+3BM1mM/50+nRp5zUajajX66WdBwAAAJDN3hgWt9n3xqyMvTFLcZ0FMjx79AMx+u0zcflqc+G+yb9PR627iONfaUTjPdfjdImfoa6ndl5nhWzQZhMTE7Fv377sMaCS+j722dh99Pk1n/P5Lz8d137/8xImaq/XPvmZGNzau27nb+nqjl9+fP9t9//4/Nn46d/W77v1L168WOrr3vj4eAwPD5d2HgAAAEA2e2NY3GbfG7My9sYsxXUWSPOOHRG7vh5R+89nwK25uHH2h3H40CupY61WO6+zQjYAKqMoirIOKuecDaarKOLhvm233X9q8mLCNAAAAAAAy7M3BgA6Vuvmrdv/3TeXM0uHELJBmzUajRgfH88eAyrpj9O9ceyNtZ/zk2e/35FfET9w7HsRV9/KHqN0AwMDpb7uNRqN0s4CAAAAqAJ7Y1jcZt8bszL2xizFdRbIcOHSTBz61pmYvDIbXUXElnpXTE1H9DQOx4kje+ITH+rLHnHF2nmdFbJBm9XrdV9tDItozEX86BcR15rL/+1ittcjRj68O2pd5c3VLs2enuwR1kVPT4/XPQAAAIAl2BvD4jb73hhYO9dZoN0mLlyLp46MxeSV2ajVinj++KOxd2df7B8di0uXp+PIidfi5InHYuSRB7JHrRxv1wCojHp3xOPvX9sZBwfDMgIAAAAAYIOwNwYAOsnEhWuxf3QsXp+cWojYnnh0KPbu7ItfPTcSO/q3xGzzZhw4/FKM/eav2eNWjrdsAFTKwaGI4i6eV0REdxHxxGDJAwEAAAAAkMreGADoBItFbPPEbMsTsgFQKe/rjfjaQ6t/Xisiju6L6K+XPtKG8I0Hh+MfI0+u+jEAAAAAgGz2xgBA1S0Xsc0Tsy1NyAZA5Xxud8SX9qzuOV99KOLJoXUZBwAAAACAZPbGAEBVrTRimydmW5yQDYDKKYqIp/ZGfOfhiN33Lv23e94VcfwjEV9otGc2AAAAAADaz94YAKii1UZs88Rsd1bLHgAAFvPp90Z8aiDilSsRPzsXcX4q4l83IrbWInbdG3FwMGL4vlsLDAAAAAAANj57YwCgKu42Yps3H7PtHx2LS5en48Dhl+Lkicdi5JEH1m/oiitarVYrewgAIKI5+nTEm5ezxyjf9v7oee4H2VMAwKq0rk9F69z57DHuSjE0GMU7e7PHADYpr58AAADAZrDWiO1/vfqXqwsx2z09XZs6ZhOyAUBFCNkAAAAAAAAAqm22ORcffPzFOPv6P9ccsc17e8z2hxcOxIM7+0qZt5N0ZQ8AAAAAAAAAAADQCe7p6Y7vPvPR2FqvlRKxRfz3Z0Z39G+JZ764L/YMvXvtg3Yg38gGABXhG9kAAAAAAAAAOsObV6Zj+7YtpZ/Zf189iqIo9dxOUcseAAAAAAAAAAAAoJOUHbGt15mdxE+LAgAAAAAAAAAAkErIBgAAAAAAAAAAQCohGwAAAAAAAAAAAKmEbAAAAAAAAAAAAKQSsgEAAAAAAAAAAJBKyAYAAAAAAAAAAEAqIRsAAAAAAAAAAACphGwAAAAAAAAAAACkErIBAAAAAAAAAACQSsgGAAAAAAAAAABAKiEbAAAAAAAAAAAAqYRsAAAAAAAAAAAApBKyAQAAAAAAAAAAkKqWPQAAcEtx/7ZoZQ+xDor7t2WPAAAAAAAAAEDFFa1WayN+Zg4AAAAAAAAAAECH8NOiAAAAAAAAAAAApBKyAQAAAAAAAAAAkErIBgAAAAAAAAAAQCohGwAAAAAAAAAAAKmEbAAAAAAAAAAAAKQSsgEAAAAAAAAAAJBKyAYAAAAAAAAAAEAqIRsAAAAAAAAAAACphGwAAAAAAAAAAACkErIBAAAAAAAAAACQSsgGAAAAAAAAAABAKiEbAAAAAAAAAAAAqYRsAAAAAAAAAAAApBKyAQAAAAAAAAAAkErIBgAAAAAAAAAAQCohGwAAAAAAAAAAAKmEbAAAAAAAAAAAAKQSsgEAAAAAAAAAAJBKyAYAAAAAAAAAAEAqIRsAAAAAAAAAAACphGwAAAAAAAAAAACkErIBAAAAAAAAAACQSsgGAAAAAAAAAABAKiEbAAAAAAAAAAAAqYRsAAAAAAAAAAAApBKyAQAAAAAAAAAAkErIBgAAAAAAAAAAQCohGwAAAAAAAAAAAKmEbAAAAAAAAAAAAKQSsgEAAAAAAAAAAJBKyAYAAAAAAAAAAEAqIRsAAAAAAAAAAACphGwAAAAAAAAAAACkErIBAAAAAAAAAACQSsgGAAAAAAAAAABAKiEbAAAAAAAAAAAAqYRsAAAAAAAAAAAApBKyAQAAAAAAAAAAkErIBgAAAAAAAAAAQCohGwAAAAAAAAAAAKn+De1Fa21MDcy4AAAAAElFTkSuQmCC",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "max_depth = min(max_depth, max(unpacked_vocab_configs_depths.keys()))\n",
+ "fig, axs = plt.subplots(max_depth, topk, figsize=(12, 6), dpi=200)\n",
+ "\n",
+ "for ax in axs.flatten():\n",
+ " ax.clear() \n",
+ " ax.set_axis_off()\n",
+ "\n",
+ "for (depth, unpacked_vocab_configs), (unpacked_vocab_configs_cnts), axs_sel in \\\n",
+ " zip(unpacked_vocab_configs_depths.items(), unpacked_vocab_configs_cnts_depths.values(), axs):\n",
+ " \n",
+ " if depth > max_depth:\n",
+ " break\n",
+ " \n",
+ " for i, (ax, unpacked_vocab_config, unpacked_vocab_config_cnt) in \\\n",
+ " enumerate(zip(axs_sel, unpacked_vocab_configs, unpacked_vocab_configs_cnts)):\n",
+ " \n",
+ " zero_ps = torch.zeros((1, unpacked_vocab_config.shape[-1])) - 1\n",
+ " instr = tokenizer.decode(unpacked_vocab_config, zero_ps)\n",
+ " qc = simulator.genqc_to_backend(instr, place_barriers=False)\n",
+ "\n",
+ " #------\n",
+ "\n",
+ " ax.clear() \n",
+ " qc.draw(\"mpl\", \n",
+ " plot_barriers=False, \n",
+ " ax=ax, \n",
+ " idle_wires=False)\n",
+ "\n",
+ " for text in ax.texts:\n",
+ " if 'q' in text.get_text():\n",
+ " text.set_visible(False)\n",
+ " text.remove()\n",
+ "\n",
+ " ax.patch.set_facecolor('none')\n",
+ " ax.patches[0].set_color(\"none\")\n",
+ " \n",
+ " ax.set_title(f\"Occurrences: {unpacked_vocab_config_cnt.item()}\", fontsize=6)\n",
+ " if i==0:\n",
+ " plt.figtext(-0.03, 1-(depth-0.7)/max_depth, f\"Depth {depth}:\", horizontalalignment='left', verticalalignment='top', fontsize=12)\n",
+ "\n",
+ "plt.tight_layout()\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "146b3c78-3975-4d48-8fe4-7d257c0dcedd",
+ "metadata": {},
+ "source": [
+ "As we only extract discrete tokens, the parameters of the continuous gates are set to 0 for plotting."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "22d4e269-2a56-4026-81f3-8e22889adaf9",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8c9909e3-a035-46f7-8979-44be322a5fe0",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "genQC Version 0.2.0\n"
+ ]
+ }
+ ],
+ "source": [
+ "import genQC\n",
+ "print(\"genQC Version\", genQC.__version__)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "87d2050b-6b59-44d0-8a41-f393bc656bcf",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "python3",
+ "language": "python",
+ "name": "python3"
+ },
+ "widgets": {
+ "application/vnd.jupyter.widget-state+json": {
+ "state": {
+ "021f52d503b44f5e93f326f4e1c681e9": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_b7b3c47c7bb14d2ea1ee06e8afdba7f6",
+ "style": "IPY_MODEL_12d3e5aabc814faa8604361b1c80490a",
+ "value": "100%"
+ }
+ },
+ "0ba4139e005649238a5b4040b628100f": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_2dc7af76b38b435e8115388ff771bdcb",
+ "max": 40,
+ "style": "IPY_MODEL_804a105cc9d647bbab287df9ff67502f",
+ "value": 40
+ }
+ },
+ "0de51d1b7df643d181aabefb36044dd0": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "122ff94a05ef4363937c00a7ddcb041b": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "12d3e5aabc814faa8604361b1c80490a": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "189915e75b474731878f4dd2d82eea7d": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "18f39bb92f814ba192c66550cf35f972": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_122ff94a05ef4363937c00a7ddcb041b",
+ "max": 40,
+ "style": "IPY_MODEL_0de51d1b7df643d181aabefb36044dd0",
+ "value": 40
+ }
+ },
+ "2dc7af76b38b435e8115388ff771bdcb": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "318e739a6c804569a9a6fe91a95791f9": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_c9419d0d5f8e441e815aaccc5a7e69e1",
+ "style": "IPY_MODEL_4da4dd4163ae4fffa6ba18402643beee",
+ "value": "Fetching 4 files: 100%"
+ }
+ },
+ "341b725d6ea741f4afb8cede9a4ad7a0": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "3ad8e473e03047969891e6c1204a1290": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_021f52d503b44f5e93f326f4e1c681e9",
+ "IPY_MODEL_51fcfd77dc8c4475ba826f165c8f1f46",
+ "IPY_MODEL_eb040d59b1a24ae287c1cc61832eaf82"
+ ],
+ "layout": "IPY_MODEL_824ffe4f7f30494b8139208af9f5d021"
+ }
+ },
+ "4690a080654a4e1ea3b9edd4d3c073b1": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "49813f4fc64b44a799ec78f0921ce5d0": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "4b5ae46e8b1445eda653dfcc62686dbe": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "4d2312b354ca4f30807c08d024c6e9f0": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "4da4dd4163ae4fffa6ba18402643beee": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "51fcfd77dc8c4475ba826f165c8f1f46": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_b391734f3fc7449a801ebc1b0dfec054",
+ "style": "IPY_MODEL_7e807267d2c74ffc91806e201348b15b",
+ "value": 100
+ }
+ },
+ "6b156939bb904c9dafe187a6c1770ea3": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "6d6e280e276840b3babdf728594bc1c1": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_edff37b931ba467f8c3af55668d610dc",
+ "style": "IPY_MODEL_4d2312b354ca4f30807c08d024c6e9f0",
+ "value": "100%"
+ }
+ },
+ "7e807267d2c74ffc91806e201348b15b": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "7e928f8f42024d3dae49e80ddd82b3b2": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_c649ae350b714328a6b377c44e47f509",
+ "style": "IPY_MODEL_eeaf1b6b54674163b2a61d665af4df79",
+ "value": " 40/40 [01:52<00:00, 2.84s/it]"
+ }
+ },
+ "804a105cc9d647bbab287df9ff67502f": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "82066e5a51a344d49ba6c6f44b06325b": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_8837e58cb680404e8479e38a03e27688",
+ "style": "IPY_MODEL_e9132d33e6fe420cbe944ee26871e305",
+ "value": 100
+ }
+ },
+ "824ffe4f7f30494b8139208af9f5d021": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "8837e58cb680404e8479e38a03e27688": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "8fcdeb78bf434af0b590a4b334a69901": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "947bf1d7d4d04ffea3abf8530908ad62": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_9d2698b2d5244c8ab28111bfebf13091",
+ "IPY_MODEL_18f39bb92f814ba192c66550cf35f972",
+ "IPY_MODEL_7e928f8f42024d3dae49e80ddd82b3b2"
+ ],
+ "layout": "IPY_MODEL_c157678062c94873bd42f3fcc219c967"
+ }
+ },
+ "962235847487477e88ee001eaa67b2ee": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_6d6e280e276840b3babdf728594bc1c1",
+ "IPY_MODEL_82066e5a51a344d49ba6c6f44b06325b",
+ "IPY_MODEL_b47cb0c8ab634af4b7c9571985cccf3f"
+ ],
+ "layout": "IPY_MODEL_49813f4fc64b44a799ec78f0921ce5d0"
+ }
+ },
+ "9a1869704d7a48e3b26e80f244377358": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "9d2698b2d5244c8ab28111bfebf13091": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_4b5ae46e8b1445eda653dfcc62686dbe",
+ "style": "IPY_MODEL_a7327c7ff92e4b0e87ef3ec38a95779d",
+ "value": "100%"
+ }
+ },
+ "a67d491da7384d2e9c7d96de8779849f": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "a7327c7ff92e4b0e87ef3ec38a95779d": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "b391734f3fc7449a801ebc1b0dfec054": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "b47cb0c8ab634af4b7c9571985cccf3f": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_341b725d6ea741f4afb8cede9a4ad7a0",
+ "style": "IPY_MODEL_dced64245df74f84840f0e2e922f1d5c",
+ "value": " 100/100 [00:40<00:00, 2.51it/s]"
+ }
+ },
+ "b7b3c47c7bb14d2ea1ee06e8afdba7f6": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "bda858eb4a444b6f9c2e4d12918619d8": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_dbbafaa32f044dc79cde20dca640d182",
+ "style": "IPY_MODEL_d8cd8f60d9a44adabb401eee87dc5b95",
+ "value": "100%"
+ }
+ },
+ "c157678062c94873bd42f3fcc219c967": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "c649ae350b714328a6b377c44e47f509": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "c9419d0d5f8e441e815aaccc5a7e69e1": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "cae28b86bb0e49368d6dac67ccc30360": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "d3e70533f1024409ab6f0a3662f0f515": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "d8cd8f60d9a44adabb401eee87dc5b95": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "dbbafaa32f044dc79cde20dca640d182": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "dbef30da12f646c2b35e308367007bd2": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_e3497cfed0494def925f01b715b31d10",
+ "style": "IPY_MODEL_4690a080654a4e1ea3b9edd4d3c073b1",
+ "value": " 4/4 [00:00<00:00, 798.72it/s]"
+ }
+ },
+ "dced64245df74f84840f0e2e922f1d5c": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "dfc99b2ee2b74d00b6097a20baade825": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_318e739a6c804569a9a6fe91a95791f9",
+ "IPY_MODEL_ff3ef79425414a96859a7dae83ee809a",
+ "IPY_MODEL_dbef30da12f646c2b35e308367007bd2"
+ ],
+ "layout": "IPY_MODEL_6b156939bb904c9dafe187a6c1770ea3"
+ }
+ },
+ "dfe56ee8b5b24cf2aaf2d149d0911663": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "e3497cfed0494def925f01b715b31d10": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "e9132d33e6fe420cbe944ee26871e305": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "eb040d59b1a24ae287c1cc61832eaf82": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_a67d491da7384d2e9c7d96de8779849f",
+ "style": "IPY_MODEL_cae28b86bb0e49368d6dac67ccc30360",
+ "value": " 100/100 [00:00<00:00, 1785.68it/s]"
+ }
+ },
+ "eb4ee8e9c8454fac8dc424778e9f73a2": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_dfe56ee8b5b24cf2aaf2d149d0911663",
+ "style": "IPY_MODEL_189915e75b474731878f4dd2d82eea7d",
+ "value": " 40/40 [01:53<00:00, 2.82s/it]"
+ }
+ },
+ "edff37b931ba467f8c3af55668d610dc": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "eeaf1b6b54674163b2a61d665af4df79": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "fa240a3d6d114b6da392ac2fbe059980": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_bda858eb4a444b6f9c2e4d12918619d8",
+ "IPY_MODEL_0ba4139e005649238a5b4040b628100f",
+ "IPY_MODEL_eb4ee8e9c8454fac8dc424778e9f73a2"
+ ],
+ "layout": "IPY_MODEL_8fcdeb78bf434af0b590a4b334a69901"
+ }
+ },
+ "ff3ef79425414a96859a7dae83ee809a": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_d3e70533f1024409ab6f0a3662f0f515",
+ "max": 4,
+ "style": "IPY_MODEL_9a1869704d7a48e3b26e80f244377358",
+ "value": 4
+ }
+ }
+ },
+ "version_major": 2,
+ "version_minor": 0
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/src/examples/Quantum circuit synthesis with diffusion models/0_hello_circuit.ipynb b/src/examples/Quantum circuit synthesis with diffusion models/0_hello_circuit.ipynb
new file mode 100644
index 0000000..4d0d61f
--- /dev/null
+++ b/src/examples/Quantum circuit synthesis with diffusion models/0_hello_circuit.ipynb
@@ -0,0 +1,752 @@
+{
+ "cells": [
+ {
+ "cell_type": "raw",
+ "id": "a1eaa873-0e5d-4dd3-9e44-440a70df611f",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "categories:\n",
+ " - Entanglement generation\n",
+ " - Quantum circuits\n",
+ " - Pretrained model\n",
+ "---"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "69a855f1-55dd-482e-94f2-9ad02804be4d",
+ "metadata": {},
+ "source": [
+ "# Generate a circuit\n",
+ "\n",
+ "> A minimal example to generate a circuit. We load a pre-trained (SRV, 3 to 8 qubit) model and condition on a given Schmidt-Rank-Vector (SRV)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3bde494e-9091-41a4-a601-bbcf9712c564",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from genQC.imports import *\n",
+ "import genQC.utils.misc_utils as util\n",
+ "\n",
+ "from genQC.pipeline.diffusion_pipeline import DiffusionPipeline\n",
+ "from genQC.platform.tokenizer.circuits_tokenizer import CircuitTokenizer\n",
+ "from genQC.platform.simulation import Simulator, CircuitBackendType\n",
+ "\n",
+ "from genQC.inference.sampling import generate_tensors, decode_tensors_to_backend\n",
+ "from genQC.inference.evaluation_helper import get_srvs"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "029be4f3-0d9a-4d0a-93d9-2338fda7a983",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[INFO]: Cuda device has a capability of 8.6 (>= 8), allowing tf32 matmul.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "device(type='cuda')"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "util.MemoryCleaner.purge_mem() # clean existing memory alloc\n",
+ "device = util.infer_torch_device() # use cuda if we can\n",
+ "device"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "785dc2ca-1fe8-4f0d-94bc-964b8f1733ac",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# We set a seed to pytorch, numpy and python. \n",
+ "# Note: This will also set deterministic algorithms, possibly at the cost of reduced performance!\n",
+ "util.set_seed(0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f77a3020-247c-4ac0-aaf1-ee5c371b5f06",
+ "metadata": {},
+ "source": [
+ "## Setup and load"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "742ae430-46f2-4099-ac8f-f422a4ddc1dc",
+ "metadata": {},
+ "source": [
+ "Load the pre-trained model directly from [Hugging Face: Floki00/qc_srv_3to8qubit](https://huggingface.co/Floki00/qc_srv_3to8qubit)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e5d60c23-9514-4432-bc82-622c088fced6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pipeline = DiffusionPipeline.from_pretrained(\"Floki00/qc_srv_3to8qubit\", device)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "104f977d-a6c5-4dbf-b272-b1a8fc6f013a",
+ "metadata": {},
+ "source": [
+ "Check on what gates the model was trained"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "62b067ac-d5a4-4424-b7da-571ae95067c6",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "['h', 'cx']"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pipeline.gate_pool"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "431d3e29-f121-4c61-95bc-bfd7960a4870",
+ "metadata": {},
+ "source": [
+ "Set 20 sample steps and use rescaled guidance-formula."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "96702fba-5a10-44e6-bef9-634d9e41a1af",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pipeline.guidance_sample_mode = \"rescaled\"\n",
+ "pipeline.scheduler.set_timesteps(20) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "65acff8f-8486-42c9-8e78-b44f31de568b",
+ "metadata": {},
+ "source": [
+ "## Inference / sampling"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a09bd191-0374-45af-b923-f131c5d36af9",
+ "metadata": {},
+ "source": [
+ "Set our desired condition SRV"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d1d4b69e-c14a-4dac-9cdf-2b65ecaee158",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'Generate SRV: [2, 1, 2, 1, 2]'"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "srv = [2, 1, 2, 1, 2] # set your target SRV; can be 3 to 8 qubit\n",
+ "num_of_qubits = len(srv) \n",
+ "\n",
+ "prompt = f\"Generate SRV: {srv}\" # model was trained with this phrase\n",
+ "prompt"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0184031c-627c-4b82-9607-e35a22f699f4",
+ "metadata": {},
+ "source": [
+ "Define sample parameters"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "721ba4f1-60f0-4f23-9306-bdf07f8fe659",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "g = 10 # guidance scale\n",
+ "max_gates = 16 # how many time steps the tensor encoding has\n",
+ "samples = 64 # how many circuits to generate"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2842cc0c-770a-451c-ac44-e7265dbd87c2",
+ "metadata": {},
+ "source": [
+ "Sample tokenized circuits"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "51a84305-bcf1-43b5-a3b9-752a2defaf81",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "3728850a9cc2452db5d4236acf614a80",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/20 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[INFO]: (generate_comp_tensors) Generated 64 tensors\n"
+ ]
+ }
+ ],
+ "source": [
+ "out_tensor = generate_tensors(pipeline, prompt, samples, num_of_qubits, num_of_qubits, max_gates, g, no_bar=False)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4d07a692-8c6f-4c31-8fd2-d8cd89edecf0",
+ "metadata": {},
+ "source": [
+ "Check how many distinct tensors we got:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "72c575b4-ebc5-46b7-b588-55f3340df04d",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "64"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "out_tensor.unique(dim=0).shape[0]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "14e5728b-22d5-480f-bde8-5add698611b4",
+ "metadata": {},
+ "source": [
+ "Let's look what is generated. Note, 3 is the padding token (or empty action)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9116f267-9f6b-4f80-a0f6-ef38fb694a28",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "tensor([[[ 1, 0, 2, -2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
+ " [ 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
+ " [ 0, 1, -2, 0, -2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
+ " [ 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
+ " [ 0, 0, 0, 2, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]],\n",
+ "\n",
+ " [[ 0, 0, 0, 2, 1, -2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
+ " [ 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
+ " [ 2, 1, -2, -2, 0, 2, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
+ " [ 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n",
+ " [-2, 0, 2, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3]]], device='cuda:0')"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "out_tensor[:2]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d4a3c5f4-3333-49a0-a965-2c392dcc2002",
+ "metadata": {},
+ "source": [
+ "## Convert to qiskit circuit "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2f71bbfa-8fd0-4a0c-ba9c-1a4f09da527d",
+ "metadata": {},
+ "source": [
+ "To get a qiskit circuit we need to do: \n",
+ "\n",
+ "- apply cosine similarity to go from embeddings to token matrices (the function `generate_tensors` did this already)\n",
+ "- parse token matrix to qiskit and filter out error circuits\n",
+ "- calculate SRV and plot circuits"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "177968da-cbc2-455e-bd5d-d0357016c5cd",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "vocabulary = {g:i+1 for i, g in enumerate(pipeline.gate_pool)} \n",
+ "tokenizer = CircuitTokenizer(vocabulary)\n",
+ "simulator = Simulator(CircuitBackendType.QISKIT)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "420e3dea-8ccd-46eb-a21c-84137345566b",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "qiskit.circuit.quantumcircuit.QuantumCircuit"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "qc_list, error_cnt = decode_tensors_to_backend(simulator, tokenizer, out_tensor)\n",
+ "qc_list[0].__class__"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b033c5a9-d77a-467e-8474-537ace2d9b4b",
+ "metadata": {},
+ "source": [
+ "Generated error circuits (token matrices that don't correspond to circuits):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6f9dd437-cac3-4fa7-bdfe-0046c6a727e2",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "error_cnt"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8a93fdc6-9e12-4e07-b25a-ca3f21a5cda4",
+ "metadata": {},
+ "source": [
+ "What SRVs did we get:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "79d9c737-6773-47af-8d3a-7e1ec9ec704b",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[[2, 1, 2, 1, 2], [2, 1, 2, 1, 2], [2, 1, 2, 1, 2], [2, 1, 2, 1, 2]]"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "srv_list = get_srvs(simulator, qc_list)\n",
+ "srv_list[:4]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "995435c3-ec71-4b2d-84cc-8aa1ededd546",
+ "metadata": {},
+ "source": [
+ "That is an accuracy of:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f15f0507-e9c9-4176-8acf-27c2e063694f",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0.96875"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "sum(srv==x for x in srv_list)/len(srv_list)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "782ffa59-6386-49d1-9014-2bf093674e43",
+ "metadata": {},
+ "source": [
+ "Finally plot some of the circuits:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "fc0308c1-1a0b-4be1-90b1-2811a13caee4",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAABl0AAAH/CAYAAADdZxvRAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAA0tRJREFUeJzs3Xl4FGW69/Ffd/aQjSUkbAYCBDCg7KsQUBEQHBhGAQ/KqLi84zoOLsdRBxjnqKijjtsRl0HEYdxG9OCCaAQEZXNDQGQnJiGBECAkhKxd7x9lAiHdWSu95fu5rla6ulJ1pzpdd/Vz1/M8NsMwDAEAAAAAAAAAAKBR7J4OAAAAAAAAAAAAwB9QdAEAAAAAAAAAALAARRcAAAAAAAAAAAALUHQBAAAAAAAAAACwAEUXAAAAAAAAAAAAC1B0AQAAAAAAAAAAsABFFwAAAAAAAAAAAAtQdAEAAAAAAAAAALAARRcAAAAAAAAAAAALUHSxyIEDB2Sz2fTaa695OhSfMHr0aNlsNtlsNk2aNMnT4eBXTz/9dOX7YrPZdOTIEU+HBMBHkAfrhzzonciDABqKPFg/5EHvRB4E0FDkwfohD3onK/Ngg4oue/fu1U033aTExESFhoYqKipKI0aM0D/+8Q+dOnWqwcF4yk8//aR58+bpwIEDng6l0rp16zRhwgR16NBBoaGhOuecc3TZZZdp6dKlVdY78w/BZrMpKipKKSkp+uijjyrXuf3222Wz2bRnzx6X+7v//vtls9n0448/NtnvdLaePXtqyZIluuuuuyqX5ebm6vHHH9eoUaMUGxurmJgYDR06VG+99Vaj9rVp0ybdfPPNGjBggIKCgmSz2RobvlJTU3XdddcpKSlJ4eHhSkxM1PXXX6+srKxGbfd//ud/9Jvf/EZxcXGy2WyaN29eo7ZXn2M6fvx4LVmyRL/97W8btU/A35EHmx55kDxIHgS8F3mw6ZEHyYPkQcB7kQebHnmQPOjzedCopw8//NAICwszYmJijNtvv9146aWXjOeee86YMWOGERQUZNxwww313aTHvfPOO4YkY9WqVQ3ehsPhME6dOmWUlZU1Op63337bsNlsRr9+/YwFCxYYL730knHfffcZI0aMMEaPHl1lXUnG2LFjjSVLlhivv/668dBDDxnt27c3bDabsWLFCsMwDGPDhg2GJGP+/Pku99mlSxejT58+jY69rlJSUoyUlJRqy5cvX24EBQUZkydPNp5++mnjueeeM8aMGWNIMv7yl780eH9z5841goKCjAEDBhhJSUlGA/70qxkwYIDRpUsX45577jFefvll47777jMiIyONuLg4Iysrq8HblWTEx8cb48aNMyQZc+fObVScDTmmc+fONSQZOTk5jdo34I/Ig86RB+uHPOgaeRDwbuRB58iD9UMedI08CHg38qBz5MH6IQ+65i95sF5HeN++fUZERITRs2dP4+DBg9Ve3717t/H00083OJgKDofDKCwsdPraqVOnjPLy8kbv40xWnFysdO655xrJyclGcXFxtdcOHTpU5bkk45Zbbqmy7KeffjIkGRMmTKhc1q1bN6Nnz55O9/f1118bkoxHH33UgujrxtXJZd++fcaBAweqLHM4HMaFF15ohISEGAUFBQ3aX3Z2duXf1C233GLJyWXNmjXV/hbXrFljSDLuv//+Bm93//79hmEYRk5OjiUnl4YcUy6yAefIg+5BHiQPGgZ5EPBG5EH3IA+SBw2DPAh4I/Kge5AHyYOG4ft5sF7Diz322GMqKCjQq6++qnbt2lV7vVu3brrjjjsqn5eVlemhhx5S165dFRISos6dO+vPf/6ziouLq/xc586dNWnSJH366acaOHCgwsLCtHDhQq1evVo2m01vvvmmHnjgAXXo0EHh4eE6ceKEJGnjxo0aP368oqOjFR4erpSUFH311VfV4srMzNTs2bPVvn17hYSEqEuXLvrDH/6gkpISvfbaa7riiiskSWPGjKnsjrZ69er6HBqnYxdmZ2fr2muvVceOHRUSEqJ27dpp8uTJtXbX27t3rwYNGqTg4OBqr7Vt27bWWHr16qU2bdpo7969lctmzpypn3/+Wd9991219ZcuXSqbzaYrr7yy1m03tS5duighIaHKMpvNpilTpqi4uFj79u1r0Hbj4uIUFhZmRYiVRo0aJbvdXm1Zq1attGPHjgZvt3Pnzo2MrKqmOqZAc0QedI08aA3yIHkQ8GbkQdfIg9YgD5IHAW9GHnSNPGgN8qD/5MHA+qy8fPlyJSYmavjw4XVa//rrr9fixYt1+eWXa86cOdq4caMeeeQR7dixQ8uWLauy7s6dO3XllVfqpptu0g033KAePXpUvvbQQw8pODhYd911l4qLixUcHKwvvvhCEyZM0IABAzR37lzZ7XYtWrRIF154odauXavBgwdLkg4ePKjBgwfr+PHjuvHGG9WzZ09lZmbq3XffVWFhoUaNGqXbb79dzzzzjP785z+rV69eklT5/8b43e9+p+3bt+u2225T586ddfjwYX322Wf65ZdfavwDSkhIUGpqqjIyMtSxY8d67zcvL0/Hjh1T165dK5fNnDlT8+fP19KlS9W/f//K5eXl5Xr77bc1cuRInXPOOTVut7CwUIWFhbXuPyAgQC1btqx33DXJzs6WJLVp08bS7VqtoKBABQUFXh+n5DvHFPAm5MH6IQ9ax1fO2eRBwL+RB+uHPGgdXzlnkwcB/0YerB/yoHV85ZxNHjxDXbvE5OXlGZKMyZMn12n9H374wZBkXH/99VWW33XXXYYk44svvqhclpCQYEiqHGuvwqpVqwxJRmJiYpVudQ6Hw+jevbsxbtw4w+FwVC4vLCw0unTpYowdO7Zy2axZswy73W5s3ry5WowVP2tFN7r9+/cbkoxFixYZhmEYx44dMyQZjz/+eL239eqrrxqSjODgYGPMmDHGgw8+aKxdu9Zp90FJxuzZs42cnBzj8OHDxjfffGOMHz/e6b4HDRpkdOzYscp2VqxYYUgyFi5cWGtcFV2ranskJCTUui1X3eicyc3NNdq2bWuMHDmyTuvXxqpudM489NBDhiQjNTW10duyqhudM7UdU7qTA9WRB2tGHiQPGgZ5EPBn5MGakQfJg4ZBHgT8GXmwZuRB8qBhkAfPVOeeLhVd1yIjI+u0/scffyxJ+tOf/lRl+Zw5c/TEE0/oo48+0pgxYyqXd+nSRePGjXO6rd///vdVukD98MMP2r17tx544AHl5uZWWfeiiy7SkiVL5HA4JEnvv/++LrvsMg0cOLDadm02W51+l4YICwtTcHCwVq9erdmzZ9erwnndddepQ4cOevLJJ7Vq1SqtWrVKDz30kBITE7VkyZJqFfVXX31Vr776auXzoKAg3XPPPdWO/VVXXaU77rhDX375pUaPHi3J7EIXHBxc2ZWwJrNmzdIFF1xQ63pWdldzOByaOXOmjh8/rmeffday7TaFL7/8UvPnz9e0adN04YUXejocl3zpmALehDxYP+RBa/jSOZs8CPg38mD9kAet4UvnbPIg4N/Ig/VDHrSGL52zyYNV1bnoEhUVJUnKz8+v0/ppaWmy2+3q1q1bleXx8fGKiYlRWlpaleVdunRxua2zX9u9e7ck86TjSl5enkpKSnTixAn17t27TjFbKSQkRAsWLNCcOXMUFxenoUOHatKkSZo1a5bi4+Nr/flx48Zp3LhxKiws1Lfffqu33npLL774oiZNmqSff/65yhiGkydP1q233qqSkhJt3rxZDz/8sAoLC6uNqzdjxgz96U9/0tKlSzV69GgVFRVp2bJlmjBhQp1OfomJiUpMTKz/wWiE2267TStWrNDrr7+u888/3637ro+ff/5Zv/3tb9W7d2+98sorng6nRr5yTAFvQx6sH/KgNXzlnE0eBPwfebB+yIPW8JVzNnkQ8H/kwfohD1rDV87Z5MHq6lV0ad++vbZt21avHdS1alpTFfDs1yqqtY8//rj69u3r9GciIiJ09OjRugXZRP74xz/qsssu0/vvv69PP/1UDz74oB555BF98cUX6tevX522ER4erpEjR2rkyJFq06aN5s+fr08++aTKibVjx466+OKLJUmXXnqp2rRpo1tvvVVjxozR1KlTK9dr27atxo4dq//85z96/vnntXz5cuXn52vmzJl1iqViXL7aBAQEKDY2tk7brMn8+fP1wgsv6NFHH9XVV1/d6O01lfT0dF1yySWKjo7Wxx9/XOe7HjzBV44p4I3Ig/VHHmwcXzlnkweB5oE8WH/kwcbxlXM2eRBoHsiD9UcebBxfOWeTB52z177KaZMmTdLevXu1fv36WtdNSEiQw+GorL5WOHTokI4fP66EhIT6RXqGiomQoqKidPHFFzt9BAUFKTY2VlFRUbWeEJuyO13Xrl01Z84crVy5Utu2bVNJSYn+/ve/N2hbFV0Bs7KyalzvpptuUteuXfXAAw/IMIwqr82cOVNHjx7VJ598oqVLlyoqKkqXXXZZnfb/xBNPqF27drU+Bg0a1KDf70zPP/+85s2bpz/+8Y+69957G729ppKbm6tLLrlExcXF+vTTT9WuXTtPh+SSrxxTwJuRBxsWK3mw/nzlnE0eBJoX8mDDYiUP1p+vnLPJg0DzQh5sWKzkwfrzlXM2edC1ehVd7rnnHrVo0ULXX3+9Dh06VO31vXv36h//+Icks7IoSU8//XSVdZ588klJ0sSJExsSryRpwIAB6tq1q5544gmnFcacnBxJkt1u15QpU7R8+XJ988031dar+OC1aNFCknT8+PEGx3S2wsJCFRUV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HrSGr5yzyYOA/yMP1g950Bq+cs4mDwL+jzxYP+RBa/jKOZs8WF29ii7t27fXtm3b6rWDulZNa6oCnv1aRbX28ccfV9++fZ3+TEREhI4ePVq3IJvIH//4R1122WV6//339emnn+rBBx/UI488oi+++EL9+vWr0zbCw8M1cuRIjRw5Um3atNH8+fP1ySefVDmxduzYURdffLEk6dJLL1WbNm106623asyYMZo6dWrlem3bttXYsWP1n//8R88//7yWL1+u/Px8zZw5s06xVIzLV5uAgADFxsbWaZs1mT9/vl544QU9+uijuvrqqxu9vaaSnp6uSy65RNHR0fr444/rfNeDJ/jKMQW8EXmw/siDjeMr52zyINA8kAfrjzzYOL5yziYPAs0DebD+yION4yvnbPKgc/baVzlt0qRJ2rt3r9avX1/rugkJCXI4HJXV1wqHDh3S8ePHlZCQUL9Iz1AxEVJUVJQuvvhip4+goCDFxsYqKiqq1hNiU3an69q1q+bMmaOVK1dq27ZtKikp0d///vcGbauiK2BWVlaN6910003q2rWrHnjgARmGUeW1mTNn6ujRo/rkk0+0dOlSRUVF6bLLLqvT/p944gm1a9eu1segQYMa9Pud6fnnn9e8efP0xz/+Uffee2+jt9dUcnNzdckll6i4uFiffvqp2rVr5+mQXPKVYwp4M/Jgw2IlD9afr5yzyYNA80IebFis5MH685VzNnkQaF7Igw2LlTxYf75yziYPulavoss999yjFi1a6Prrr9ehQ4eqvb5371794x//kGRWFiXp6aefrrLOk08+KUmaOHFiQ+KVJA0YMEBdu3bVE0884bTCmJOTI0my2+2aMmWKli9frm+++abaehUfvBYtWkiSjh8/3uCYzlZYWKiioqIqy7p27arIyEgVFxfX+LOpqalOl1eMB9mjR48afz4wMFBz5szRjh079MEHH1R5bcqUKQoPD9cLL7ygTz75RFOnTlVoaGhtv44kc+zCzz77rNbHv/71rzptz5W33npLt99+u2bOnFn59+KNTp48qUsvvVSZmZn6+OOP1b17d0+H5JKvHFPA25EH64482HC+cs4mDwLND3mw7siDDecr52zyIND8kAfrjjzYcL5yziYP1qzOw4tJ5odj6dKlmj59unr16qVZs2apd+/eKikp0ddff6133nlH11xzjSTp/PPP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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig, axs = plt.subplots(2, 4, figsize=(18,5), constrained_layout=True)\n",
+ "for qc,is_srv,ax in zip(qc_list, srv_list, axs.flatten()): \n",
+ " is_srv = [int(x) for x in is_srv]\n",
+ " qc.draw(\"mpl\", plot_barriers=False, ax=ax, style = \"clifford\")\n",
+ " ax.set_title(f\"{'Correct' if is_srv==srv else 'NOT correct'}, is SRV = {is_srv}\")\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0244290d-5c57-4b70-b670-a839876a9ccf",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "52132fa0-9208-442d-a31d-af65bcfba714",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "genQC Version 0.2.0\n"
+ ]
+ }
+ ],
+ "source": [
+ "import genQC\n",
+ "print(\"genQC Version\", genQC.__version__)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d6aed55c-e9ec-4c5e-a691-37695556bde4",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "python3",
+ "language": "python",
+ "name": "python3"
+ },
+ "widgets": {
+ "application/vnd.jupyter.widget-state+json": {
+ "state": {
+ "01fd2d6f0f8049c09946379c771a467c": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "0f3daaab2a3a4d229f890bd491a05e82": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_5ead7394a77a46c89a0dc48418f4e39e",
+ "max": 2,
+ "style": "IPY_MODEL_eff7700fa64041f3abb2862855b60290",
+ "value": 2
+ }
+ },
+ "2786583fbd6a457cb54fe2c9fb700ad0": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "3728850a9cc2452db5d4236acf614a80": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_ef23d7660a5e417db0d9eeba1031e9ad",
+ "IPY_MODEL_aedc7c86f4ee4d98a9e56c0688ecde25",
+ "IPY_MODEL_7a6f3a282b084773908db5ee1ba437cc"
+ ],
+ "layout": "IPY_MODEL_2786583fbd6a457cb54fe2c9fb700ad0"
+ }
+ },
+ "5b1ceaae9fc44634ac4b685d7bd73a0a": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "5ead7394a77a46c89a0dc48418f4e39e": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "5f2c514cac1c47eca2ab21e293678346": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "785851c8014c4432af6e1fbd0d6aa0f5": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "7a6f3a282b084773908db5ee1ba437cc": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_b9786c3808464a3f8b1c03d6bea7a50e",
+ "style": "IPY_MODEL_785851c8014c4432af6e1fbd0d6aa0f5",
+ "value": " 20/20 [00:00<00:00, 55.44it/s]"
+ }
+ },
+ "86be99d6a44d4001be89fc20c52ad5c3": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "9c0135bb50e44bb5b81cbe7f3d4b9cdf": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_cf8d8da83cdf4941b075544e68cf8dd0",
+ "style": "IPY_MODEL_c2f8914870514273ada4c3f0c9f4f9b9",
+ "value": "Fetching 2 files: 100%"
+ }
+ },
+ "a1ff221e8278428d9d3bbe4bc9ac86f8": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_9c0135bb50e44bb5b81cbe7f3d4b9cdf",
+ "IPY_MODEL_0f3daaab2a3a4d229f890bd491a05e82",
+ "IPY_MODEL_a4091280768a4ee29d4ba6c0ec4f0e9e"
+ ],
+ "layout": "IPY_MODEL_a478f88390034ed6b78b289f74a466c2"
+ }
+ },
+ "a4091280768a4ee29d4ba6c0ec4f0e9e": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_5f2c514cac1c47eca2ab21e293678346",
+ "style": "IPY_MODEL_b2cb6f61f8cb449e946c418c2a71dd82",
+ "value": " 2/2 [00:00<00:00, 400.09it/s]"
+ }
+ },
+ "a478f88390034ed6b78b289f74a466c2": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "aedc7c86f4ee4d98a9e56c0688ecde25": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_86be99d6a44d4001be89fc20c52ad5c3",
+ "max": 20,
+ "style": "IPY_MODEL_5b1ceaae9fc44634ac4b685d7bd73a0a",
+ "value": 20
+ }
+ },
+ "b2cb6f61f8cb449e946c418c2a71dd82": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "b9786c3808464a3f8b1c03d6bea7a50e": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "c2f8914870514273ada4c3f0c9f4f9b9": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "cf8d8da83cdf4941b075544e68cf8dd0": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "ef23d7660a5e417db0d9eeba1031e9ad": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_01fd2d6f0f8049c09946379c771a467c",
+ "style": "IPY_MODEL_ff8e452e252b4f26922e88b85ba10ddf",
+ "value": "100%"
+ }
+ },
+ "eff7700fa64041f3abb2862855b60290": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "ff8e452e252b4f26922e88b85ba10ddf": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ }
+ },
+ "version_major": 2,
+ "version_minor": 0
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/src/examples/1_editing_and_masking.ipynb b/src/examples/Quantum circuit synthesis with diffusion models/1_editing_and_masking.ipynb
similarity index 99%
rename from src/examples/1_editing_and_masking.ipynb
rename to src/examples/Quantum circuit synthesis with diffusion models/1_editing_and_masking.ipynb
index 40bd51a..199cfd7 100644
--- a/src/examples/1_editing_and_masking.ipynb
+++ b/src/examples/Quantum circuit synthesis with diffusion models/1_editing_and_masking.ipynb
@@ -1,5 +1,18 @@
{
"cells": [
+ {
+ "cell_type": "raw",
+ "id": "0c597a85-7e30-4b05-9a05-39cd65c7b44b",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "categories:\n",
+ " - Entanglement generation\n",
+ " - Quantum circuits\n",
+ " - Pretrained model\n",
+ "---"
+ ]
+ },
{
"cell_type": "markdown",
"id": "69a855f1-55dd-482e-94f2-9ad02804be4d",
@@ -16,6 +29,18 @@
"In this notebook we show editing and masking of circuits."
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "438ed9c8-1c47-40a6-ad6f-0f126e7784bc",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# NOTE: this notebook is designed for an old version of genQC! Please use ´pip install genQC==0.1.0 -q´\n",
+ "import genQC\n",
+ "assert genQC.__version__ in [\"0.1\", \"0.1.0\", \"0.1.1\"]"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -28,8 +53,8 @@
"from genQC.inference.infer_srv import convert_tensors_to_srvs, schmidt_rank_vector\n",
"import genQC.platform.qcircuit_dataset_construction as data_const\n",
"from genQC.platform.simulation.qcircuit_sim import instruction_name_to_qiskit_gate\n",
- "import genQC.util as util\n",
- "from qiskit.quantum_info import DensityMatrix"
+ "# import genQC.util as util\n",
+ "# from qiskit.quantum_info import DensityMatrix"
]
},
{
@@ -743,6 +768,14 @@
"import genQC\n",
"print(\"genQC Version\", genQC.__version__)"
]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e812d846-4722-428f-826e-1cc22c500f18",
+ "metadata": {},
+ "outputs": [],
+ "source": []
}
],
"metadata": {
diff --git a/src/examples/Quantum circuit synthesis with diffusion models/2_unitary_compilation.ipynb b/src/examples/Quantum circuit synthesis with diffusion models/2_unitary_compilation.ipynb
new file mode 100644
index 0000000..c08d618
--- /dev/null
+++ b/src/examples/Quantum circuit synthesis with diffusion models/2_unitary_compilation.ipynb
@@ -0,0 +1,1719 @@
+{
+ "cells": [
+ {
+ "cell_type": "raw",
+ "id": "b2972412-2867-4931-a132-46ed417c4ea8",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "categories:\n",
+ " - Unitary compilation\n",
+ " - Quantum circuits\n",
+ " - Pretrained model\n",
+ "---"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "69a855f1-55dd-482e-94f2-9ad02804be4d",
+ "metadata": {},
+ "source": [
+ "# Compile unitaries\n",
+ "\n",
+ "> A short tutorial showing how to use the unitary compilation model."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3bde494e-9091-41a4-a601-bbcf9712c564",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from genQC.imports import *\n",
+ "import genQC.utils.misc_utils as util\n",
+ "\n",
+ "from genQC.pipeline.diffusion_pipeline import DiffusionPipeline\n",
+ "from genQC.platform.tokenizer.circuits_tokenizer import CircuitTokenizer\n",
+ "from genQC.platform.simulation import Simulator, CircuitBackendType\n",
+ "from genQC.inference.sampling import decode_tensors_to_backend, generate_compilation_tensors\n",
+ "from genQC.inference.evaluation_helper import get_unitaries\n",
+ "from genQC.inference.eval_metrics import UnitaryInfidelityNorm\n",
+ "\n",
+ "from qiskit import QuantumCircuit\n",
+ "import qiskit.quantum_info as qi"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "029be4f3-0d9a-4d0a-93d9-2338fda7a983",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[INFO]: Cuda device has a capability of 8.6 (>= 8), allowing tf32 matmul.\n"
+ ]
+ }
+ ],
+ "source": [
+ "device = util.infer_torch_device() # use cuda if we can\n",
+ "util.MemoryCleaner.purge_mem() # clean existing memory alloc"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e29645b3-226d-49b2-9a22-bb6c18e05f1b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# We set a seed to pytorch, numpy and python. \n",
+ "# Note: This will also set deterministic algorithms, possibly at the cost of reduced performance!\n",
+ "util.set_seed(0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f77a3020-247c-4ac0-aaf1-ee5c371b5f06",
+ "metadata": {},
+ "source": [
+ "## Setup and load"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "742ae430-46f2-4099-ac8f-f422a4ddc1dc",
+ "metadata": {},
+ "source": [
+ "Load the pre-trained model directly from [Hugging Face: Floki00/qc_unitary_3qubit](https://huggingface.co/Floki00/qc_unitary_3qubit)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e5d60c23-9514-4432-bc82-622c088fced6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pipeline = DiffusionPipeline.from_pretrained(\"Floki00/qc_unitary_3qubit\", device, )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "431d3e29-f121-4c61-95bc-bfd7960a4870",
+ "metadata": {},
+ "source": [
+ "Set 20 sample steps and use rescaled guidance-formula."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "96702fba-5a10-44e6-bef9-634d9e41a1af",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pipeline.guidance_sample_mode = \"rescaled\"\n",
+ "pipeline.scheduler.set_timesteps(20) \n",
+ "g = 10"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2b557151-ba81-423e-8f28-8e35a781b92b",
+ "metadata": {},
+ "source": [
+ "The model was trained with a gate pool of:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e6a15d3a-b658-429f-99d5-2bcbdcb955cf",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "['h', 'cx', 'z', 'x', 'ccx', 'swap']"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pipeline.gate_pool"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "114a1229-9e8d-47d8-874c-189a197e4ebd",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "vocabulary = {g:i+1 for i, g in enumerate(pipeline.gate_pool)} \n",
+ "tokenizer = CircuitTokenizer(vocabulary)\n",
+ "simulator = Simulator(CircuitBackendType.QISKIT)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "65acff8f-8486-42c9-8e78-b44f31de568b",
+ "metadata": {},
+ "source": [
+ "## Compile a unitary"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "37444b73-0b79-4fd3-9e91-2dd521f428d3",
+ "metadata": {},
+ "source": [
+ "Compile a given unitary $U$. Note, there has to be a solution with the `pipeline.gate_pool` in order to find the exact solution."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0a78b750-181c-4060-bbce-d829c190ffbb",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def compile_and_plot(U, prompt):\n",
+ " U_r, U_i = torch.Tensor(np.real(U)), torch.Tensor(np.imag(U))\n",
+ " U_tensor = torch.stack([U_r, U_i], dim=0)\n",
+ " \n",
+ " out_tensor = generate_compilation_tensors(pipeline, \n",
+ " prompt=prompt, \n",
+ " U=U_tensor, \n",
+ " samples=samples, \n",
+ " system_size=num_of_qubits, \n",
+ " num_of_qubits=num_of_qubits, \n",
+ " max_gates=max_gates, \n",
+ " g=g, \n",
+ " no_bar=False, \n",
+ " tensor_prod_pad=False, \n",
+ " enable_params=False)\n",
+ "\n",
+ " out_tensor = out_tensor.unique(dim=0)\n",
+ " \n",
+ " qc_list, error_cnt = decode_tensors_to_backend(simulator, tokenizer, out_tensor)\n",
+ "\n",
+ " approx_Us = get_unitaries(simulator, qc_list)\n",
+ " approx_Us = torch.from_numpy(np.stack(approx_Us)).to(torch.complex128)\n",
+ " target_Us = torch.complex(U_r, U_i).unsqueeze(0).to(torch.complex128)\n",
+ " \n",
+ " U_norms = UnitaryInfidelityNorm.distance(target_Us, approx_Us)\n",
+ "\n",
+ " corr = ( U_norms.abs() < 1.0e-3 )\n",
+ " corr_qc = [qc for qc, c in zip(qc_list, corr) if c]\n",
+ " corr_qc = sorted(corr_qc, key=lambda x: len(x.data)) # sort to get the shortest solutions\n",
+ "\n",
+ " fig, axs = plt.subplots(1,4, figsize=(12, 4), constrained_layout=True, dpi=150)\n",
+ " axs[0].set_title(f\"{prompt}\")\n",
+ " for qc,ax in zip(corr_qc, axs.flatten()): \n",
+ " qc.draw(\"mpl\", plot_barriers=False, ax=ax)\n",
+ " plt.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b49dc061-6ad1-4e64-ab63-3c2a6b7c092e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "samples = 512\n",
+ "num_of_qubits = 3\n",
+ "max_gates = 12"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7f3744d1-fa19-4403-bd0c-d0dadac805a3",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\"Compile using: ['h', 'cx', 'z', 'x', 'ccx', 'swap']\""
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "prompt = \"Compile using: ['h', 'cx', 'z', 'x', 'ccx', 'swap']\" # model was trained with phrases like this, allow full gate set\n",
+ "prompt"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6f38e035-f39c-4abd-bdc9-6386305cd4ca",
+ "metadata": {},
+ "source": [
+ "#### Exercise 1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "204dbc2d-b69f-45bd-9a48-d10e4d6bde84",
+ "metadata": {},
+ "source": [
+ "Inspired from [(quantumcomputing.stackexchange.com/questions/13821/generate-a-3-qubit-swap-unitary-in-terms-of-elementary-gates/13826)](https://quantumcomputing.stackexchange.com/questions/13821/generate-a-3-qubit-swap-unitary-in-terms-of-elementary-gates/13826). Note, this unitary WAS in the training set."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5b826437-bcc8-40fd-9c99-f69082fe2efe",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "U = np.matrix([[1,0,0,0,0,0,0,0],\n",
+ " [0,1,0,0,0,0,0,0],\n",
+ " [0,0,1,0,0,0,0,0],\n",
+ " [0,0,0,0,1,0,0,0],\n",
+ " [0,0,0,1,0,0,0,0],\n",
+ " [0,0,0,0,0,1,0,0],\n",
+ " [0,0,0,0,0,0,1,0],\n",
+ " [0,0,0,0,0,0,0,1]], dtype=np.complex128) \n",
+ "\n",
+ "assert np.allclose(U.H@U, np.identity(2**num_of_qubits)) and np.allclose(U@U.H, np.identity(2**num_of_qubits)) #check if unitary"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "16dd2cd2-2ff5-4e7f-94d2-be51713fa322",
+ "metadata": {},
+ "source": [
+ "Plot correct (exact) compiled circuits:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e7b35524-f303-4744-b948-1c7dac4fa2e7",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "6749b88ab48941ecb6c367d988f75fa4",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/20 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[INFO]: (generate_comp_tensors) Generated 512 tensors\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "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SZIiIyOVn5+v9evXa/369frkk0/0ww8/qF27dm4d93RGjx6tUaNGlfw9MjJShmFo586d2rlzp+bPn68OHTq41Lx89dVX9dhjj0lSyb/R5s2b9cwzz+iXX37R/PnzZbVaT1nu4Ycf1uuvv17y9/j4eG3atEmPPfaYfvjhhzL3nV27dqlhw4aS5JF/R2cUFhbqiiuu0KxZsxQcHKzIyEhlZGRoxYoVWrFihbZu3apnn33WtPr+KysrS0OHDtWXX35Z8rOYmBjZbLaS/XTJkiVat26dJKlx48Z65513dMMNN+itt95Sz5491bdvX7t13nXXXdq2bZuqV6+uSZMmmd6ojIuLU40aNRw+npGRoaysLC9W5NjYsWO1ZMkSbdy4Udddd50WL16s4OB/D/cbNmzQ8OHDJUlPPvmkunbtalapdnbu3KnLL7+8pMFX3PRMT08v2fePHTumN99802654kb+8ePHJUnBwcGKi4vTgQMHtHv3bs2fP1+1atVS//79Jfnm9pk/f74uu+wy5ebmSpJCQkIUFRWlffv2ad++fVq5cqVCQ0NLjlvdunWTxWLRkSNHtH79erVu3bpkXfn5+Vq6dGnJ33/++We1bNnSbryff/5ZktS9e3e7n48bN67k2GO1WhUbGytJ2rNnj/bs2aOpU6fqzTff1H333Vfm7/PYY4/plVdeKfk3zsnJ0Zo1a7RmzRp9//33mjVrlsLCwlzYUgAAAAAAAADgfhW+QnXhwoUljb6K3Btz7969Jc3U5ORkLV26VCdOnNCJEye0ePFiNWvWTMePH1e/fv20f/9+h+u55ZZb1L59e23cuFFpaWnKyMjQW2+9JavVqiVLlmj06NEaPHiwLrvsMu3atUupqalKT0/Xk08+KUmaNm2aFixY4HD97777rn777Te99957ysjI0PHjx7Vnzx5dddVVkqRnn31W3377rcvb4b8mTJigxx57TJGRkXrhhRd08OBBZWZmKisrS6tWrVL37t118OBBXX755Tpx4oTbxj2d3bt3l3ypPnz4cO3fv1+ZmZnKyMhQamqqlixZorvuuksxMTFOr/uPP/7Q448/rscff1x///23jh8/rtTUVD399NOSiva5Tz/99JTlpk6dWtJMve6667Rv3z4dP35cGRkZ+uCDD/Tbb79p/PjxFfitveedd97RokWLNHHiRKWnpystLU179+7VZZddJkl6/vnntXXrVpOr/NdNN92kL7/8UkFBQXrssce0d+9epaenKzU1Vf/8848+//xznXfeeXbLDBkyREOGDJFUdPXnwYMHSx6bNGmSPvvsM1ksFn366adlNjK9Zdy4cTp06FCpf3777beSxtIll1xicqVSRESEpk6dqoiICP366692Jz5kZ2fr2muvVU5Ojjp37lzyujJbenq6evXqpQ0bNighIUEffPCBjh8/rmPHjikzM1Pbt2/X66+/rvr169stt3btWvXr10/Hjx9XixYtNHv2bGVlZenIkSPKzs7WqlWr9NBDD9kdi3xx+wwbNky5ubnq2bOn1q9fr7y8PB0/flzZ2dnasGGDnn32Wbsr/6tWrapWrVpJ+rc5WmzlypXKysoq2Wf/+3heXp6WLVsmSbrwwgvtHqtdu7ZGjRqlVatWKSsrS8eOHVN2drZ27NhRcrX58OHDtXbtWoe/y7p16/TKK6/onnvu0eHDh3Xs2DEdP35czz33nCwWi+bOnasnnnjCtQ0FAAAAAAAAAJ5gVNDIkSMNSYYkY//+/S6v58477zQkGQkJCcbBgwdPeXzv3r1GbGysIcm4++677R7buXNnSQ0tWrQwcnJyTll+yJAhJc+5+OKLDZvNdspzzj//fEOSccstt5zyWLdu3UqWnzBhwimPFxYWGl27di2pwdHyo0aNKvdj6enpRnx8vCHJmDNnzinLGYZh5OfnG+3btzckGW+88Uapz3HkxhtvNCQZN954o8PnfPLJJ4Yko379+nY/nzZtmiHJaNq0qVNjOlqfYRjGqFGjSrZxadvJMAxjwIABhiSjR48edj+32WxG48aNy/z3LR5bktGtW7dTHj95P3I0flmK6y9t3eV18n72888/n/J4Tk6OUbt2bUOS8fzzz7s8jjstWLCgpOZ3333XqWUzMjJK/t0uuugio7Cw0Ni6dasRHR1tSDIefPBBD1XtPmlpaUbLli0NSUarVq2M9PR0s0sqMX78eEOSERQUVLI/3XHHHYYkIz4+3ti9e7fJFf6rOEvCwsKMNWvWlHu5Ll26GJKMJk2aGKmpqU6N6Svb5/DhwyWvsQMHDpR7uQceeMCQZFx22WV2P3/22WcNScYTTzxhhISEGPHx8UZhYWHJ47/88oshyQgPDy81T8ty9913O8zRk4/xQ4YMKXX54v0gODjY5fcU5ck2AAAAAAAAAHBGha9QPXr0aMn/JyYmurQOwzA0ffp0SdKdd96pmjVrnvKcpKQk3XnnnZJU5pS8Dz74YKnTBPbq1avk/x9//PFSpw8tfs6ff/7pcP1169bVTTfddMrPg4KCNHLkSEnSxo0btX79eofrKK+vvvpKqampateunV39JwsODtagQYMkFd3P1Fvi4+MlFU1zevK9ad0hLCxMDz/8cKmP9evXT9Kp/0br1q3Ttm3bJEkjRowo9d/3xhtvVL169RyO26BBAxmGIcMwTJ3uV5I6d+58ypVhUtG2Kc9+6k0ff/yxJKlly5YaNmyYU8tGR0dr6tSpCg0N1U8//aTnnntOgwYN0okTJ9SuXTuNGTPGEyW7TUFBga655hpt2LBBNWrU0Pfff+/SVdmecuedd2rAgAGy2Wy6/vrr9cEHH+j999+XJH344Ydlvh68rXg/uvXWW8s9ffnWrVtLpq598cUXS70vd1l8ZfvExMQoKKgork++kvt0io8hixcvtrsv6cKFCyVJl156qc455xylpqZqzZo1pzx+3nnnOT3tbvHU3SdPKVwaR1f+PvLII4qIiFBBQYG++uorp8YGAAAAAAAAAE+pcEPVHXbu3Kljx45Jknr06OHweRdffLGkoibuzp07S31Ox44dS/35yVOGnn322WU+p/g+fKW54IILHN7L8fzzzy+5D9+qVascrqO8iqdcTElJUc2aNR3+GT16tKSiaXi9pWPHjqpataoOHjyoc845R2+//bY2b94swzAqvO4WLVooOjq61Mdq164tSSX7S7HiZkBISIg6depU6rIWi0XdunWrcH3ecM455zh8zNE2MMvy5cslFTVnXNG+fXu9+OKLkqRnnnlGq1atUlRUVEmjtTK79957NXfuXEVEROjbb7+tNA24k3300UeqV6+eDhw4oDvuuENSUdOyeJryymD37t06cOCAJJVMa10exfue1WpVnz59XBrbF7ZPRESELrroIklS79699fTTT2vlypXKy8src7lu3brJarUqLS1Nq1evliTl5OTo119/VXR0tDp27FjSdD152t/i/y/tpA5J2rFjhx5++GG1b99e8fHxslqtslgsslgsJVNe79u3z2FddevWVePGjUt9LDY2Vu3bt5fknhwFAAAAAAAAAHeocEO1SpUqJf/vaoPn77//Lvn/OnXqOHxeUlJSqcuczNHVYcWN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7ijbJXFxcRo2bJg+/PDDki+U9+7dq0GDBpVc2fP888+7Zaz4+Hi9+eabkqSpU6eqb9++WrlypWw2m6SiJmpKSopef/11tWjRQt9//71T67/ssssUHR2t/Px8XXPNNfrrr78kFV3lOWvWLPXo0UNRUVGlLvvyyy+rT58+mjx5st1UzLm5uZo+fbpeffVVSVLfvn2d/bVdYrFY9Oyzz0qS5s6dqxtvvFEHDhyQVDSl8YQJE3THHXeUXJVbml27dslischiseiZZ57xRtleNXHixJLfb9GiRW5b74UXXqhrr71WknTPPffoiSeesNsnjhw5oo8++qikYVZs27ZtuueeeyRJDz74oHr16iVJCg0N1ZQpUxQVFaXVq1drxIgRTtXjqX/HgQMH6o8//lB8fLx++OEHVa1atcLr9OQ+9+CDD2rTpk2Kj4/X559/LqvVKqnoRI3if4vbbrutzCvUSzN06NCSmt3p4YcfVpMmTZSbm6uLLrpIH374oV2DdPv27Ro9erRee+01u+XGjRun8PBwbd26VZ07d9acOXOUn58vqSh3fv/9d915552nXDnpS9tn+fLlat26td544w2lpKSUZIBhGFq+fLmGDRsmSUpKSlLr1q1PWb64OVo840DxFanFOnbsqOjoaK1evVoFBQWKiYlR+/btT1lPz549FRQUpGPHjmnw4MElJ0/l5eVp+vTp6tmzp2JiYk77+8TFxenTTz/V/fffryNHjkgqujL1xRdf1OjRoyVJd999t2rXrl2u7QMAAAAAAAAAnua2ywf79++vn3/+WY0bN9aRI0f0+OOPq0mTJgoLC1OVKlUUGhqqs846S88995zS0tI0aNAgu2ZdUlKSvvnmG8XFxWnjxo3q3LmzoqOjFR0drS5duiglJUXx8fH65ptvSqaGNMNdd92lDh066Pbbb1dsbKwSExNVr149TZ8+XZI0cuRIXXHFFW4b78Ybb9T48eMVGhqqH3/8Ueeee64iIyNVtWpVhYeHKzk5WQ8//LA2b97s9Bf4cXFxevPNN2WxWLRixQqdeeaZio2NVXR0tPr376969eqVfLn9XzabTXPmzNENN9ygunXrKjIyUlWqVFFERIQGDhyotLQ0NW/eXGPHjnXHZiiX6667Tg888IAkafLkyUpKSlJiYqJiY2N166236rzzztOdd94pSQoPD/daXYFgwoQJGjBggGw2m8aMGaO6desqLi5O8fHxqlatmm677baSaTyloqb9oEGDdOLECbVr104vvfSS3fqaNWumt956S5I0duxYzZs3z6u/T2lmz54tqahBf8EFF6hmzZoO/5ht5syZJVf0f/jhh6pXr57d42+99ZbOPPNMHT9+XIMHD1ZhYaEZZdqJiYnRnDlzlJycrOPHj+v2229XQkKCqlSpoqioKDVu3FijRo065T7bbdu21axZsxQXF6cNGzaoT58+ioqKUtWqVRUREaGOHTvq/fff14kTJ0qW8cXts379eg0fPlzJyckKDw9X1apVFRoaqs6dO2v9+vWKjY3VF198UdIYPtl/G6j//XtwcLC6dOlS8vfzzz+/5B7bJ2vSpEnJFMkzZ85UUlKS4uPjFR0drYEDByo6OrrkdVuWtm3b6tFHH9Vbb72l6tWrKzExUQkJCXryySdlGIZ69OihMWPGlGu7AAAAAAAAAIA3uHU+1s6dO2vz5s2aMmWKBg8erMaNGys8PFwZGRlKTEwsmVYxJSVFX3zxhUJCQuyW79atm1JSUvTQQw+pefPmstlsMgxDzZs318MPP6yUlBSdf/757izZaaGhofrpp5/04osvqlmzZsrNzVVcXJwuuugi/fDDDw6nMayIO++8U3/99ZcefvhhtWnTRmFhYUpNTVV0dLQ6dOige++9V/Pnz3dpKuRbbrlFP/zwg7p3767Y2FgVFBSoadOmGjNmjH755ReHV6jefvvt+uCDDzRo0CC1bNlSkZGRSk9PV0JCgs4//3y9+eabWrNmjdebS2+88YZmzpypCy64QDExMcrNzVXz5s316quvau7cucrMzJRUdPVvoCm+miw6OlotWrRw67ojIyP11Vdf6fvvv9cVV1yh2rVrKycnR8HBwWrdurXuu+++kqlUpaIr0letWqWoqChNmTKl1Klzb775Zl1zzTUyDEM33HCD/v7773LVcvKU4+eee27Ff7n/yMnJ0eHDh8v8U16eqHXv3r269dZbJRW9vq+66qpTnhMZGakpU6YoLCxMS5Ysceqq+uKazznnHLfUe7JGjRpp7dq1evfdd3XBBRcoISFBGRkZio+P13nnnafnnnuu1Hub9uzZU1u3btWTTz6pdu3aKSIiQpmZmapTp4569eql999/v6SJ6Ivb5+yzz9b06dM1bNgwtW/fXlWrVlV6errCw8NLmpNl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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "compile_and_plot(U, prompt)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8914651c-a30e-4a5b-aaa4-d98debd7147a",
+ "metadata": {},
+ "source": [
+ "#### Exercise 2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3d0c618b-2e8d-4037-a1ec-482324112fb8",
+ "metadata": {},
+ "source": [
+ "Inspired from [(quantumcomputing.stackexchange.com/questions/12439/procedures-and-intuition-for-designing-simple-quantum-circuits/12440)](https://quantumcomputing.stackexchange.com/questions/12439/procedures-and-intuition-for-designing-simple-quantum-circuits/12440). Note, this unitary WAS in the training set."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1b170062-d7aa-4bef-b1d1-a68e85e682ba",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "U = np.matrix([[1,0,0,0,0,0,0,0],\n",
+ " [0,0,0,0,0,0,0,1],\n",
+ " [0,1,0,0,0,0,0,0],\n",
+ " [0,0,1,0,0,0,0,0],\n",
+ " [0,0,0,1,0,0,0,0],\n",
+ " [0,0,0,0,1,0,0,0],\n",
+ " [0,0,0,0,0,1,0,0],\n",
+ " [0,0,0,0,0,0,1,0]], dtype=np.complex128) \n",
+ "\n",
+ "assert np.allclose(U.H@U, np.identity(2**num_of_qubits)) and np.allclose(U@U.H, np.identity(2**num_of_qubits)) #check if unitary"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dc81558e-a227-4490-94bc-044ba6dcd502",
+ "metadata": {},
+ "source": [
+ "Plot correct (exact) compiled circuits:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3886fbf5-3f6f-4a44-89b7-d0a332a69334",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "0c02e2bbc3454fc7ba2621a251caa75b",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/20 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[INFO]: (generate_comp_tensors) Generated 512 tensors\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAB1QAAAEkCAYAAACYBQM3AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAXEgAAFxIBZ5/SUgAAXj1JREFUeJzt3Xd4FPXaxvF7s+k9oSNdioQuiCIICAgoKlgRsHD02PWoiA1RFNGDXTwKKqAUFcSCWEFApSkoVaogvSsQkpCQuvP+kTcLA9mQ3exmNrvfz3VxKdmd+T382J17hmeKzTAMQwAAAAAAAAAAAACA04RYXQAAAAAAAAAAAAAA+CsaqgAAAAAAAAAAAADgAg1VAAAAAAAAAAAAAHCBhioAAAAAAAAAAAAAuEBDFQAAAAAAAAAAAABcoKEKAAAAAAAAAAAAAC7QUAUAAAAAAAAAAAAAF2ioAgAAAAAAAAAAAIALNFQBAAAAAAAAAAAAwAUaqgAAAAAAAAAAAADgAg1VAAAAAAAAAAAAAHCBhioAAAAAAAAAAAAAuEBDFQAAAAAAAAAAAABcoKEKAAAAAAAAAAAAAC7QUC0nXbt2lc1m0zPPPOPWa4Fm0qRJstlsqlevntWleN0zzzwjm81m+tWvXz+31hFMnwUA8JYHH3zwtO3v4MGDrS4LAAAAAAAAQIDwekO1oKBAM2bM0M0336zGjRsrMTFR4eHhqlq1qjp16qQnnnhC69at8/awgN8ICwtTtWrVVK1aNSUlJZ32elHj1Rf/2F/UkJ00aZLX112vXj3ZbDb9/PPPXl+3u3w5h4MHD/ZqU7si1VqkqCG1Y8cOr653x44dznVXZMyPNUr6LsXHxzu3u5GRkeVfHAAAAAAAAICA5tWG6tKlS5WSkqL+/ftr6tSp2rJli7KyshQXF6fDhw9ryZIlGj16tFq0aKFrrrlGubm53hzer9WpU0dNmjRR5cqVrS7FUgkJCWrSpInOPvtsq0vxmQsvvFAHDhzQgQMH9MEHH1hdDgAEvJEjRzq3u/3797e6HAAAAAAAAAABJtRbK/r666913XXXKScnR5UqVdLQoUN1zTXXqFGjRpIKr1xdtWqVPv/8c40dO1ZffPGFsrKyFB4e7q0S/NqUKVOsLsEvXHXVVbrqqqusLgMAAAAAAAAAAAAoFa80VLds2aIbb7xROTk5SklJ0Zw5c1SrVi3Te+x2u9q1a6d27drpkUce0a233uqNoQEAAAAAAAAAAADAZ7xyy9/hw4crPT1dkZGRmjlz5mnN1FMlJyfryy+/VEJCwmmvHThwQI888oiaNWummJgYxcTEqFmzZnr00Ud18ODBYtd38nPnduzYoZ07d+r2229XnTp1FBkZqbPPPlvDhw9XZmamc5l169bpxhtvVO3atRUZGalGjRpp1KhRysvLK3aMomdTPvPMM8rNzdXo0aPVsmVLxcTEKCkpSZdccom+//57l3/mk5f3xLp163THHXeoUaNGio6OVmxsrFq2bKknn3xShw4d8midRc9fLOnZjpMmTZLNZlO9evWKfX3OnDm6+uqrVatWLYWHhys+Pl4NGjRQz5499corr+jIkSOlXl/R8/G6du0qSZo/f7769OmjKlWqKDIyUk2bNtWzzz6r7OzsEv9cs2bNUrdu3ZSYmKjY2Fi1atVKL730kvLy8k4bw58ZhqHx48fr/PPPV3x8vOLi4tShQwd9+OGHVpfm0g8//KAbbrhBdevWVVRUlJKTk9WyZUvdf//9+vXXX53v++uvvxQfHy+bzaYHHnig2HVlZGSoUaNGstls6tWrlwzDKK8/RrGKvi+l+WW1u+++WzabTYmJiS6f8zlu3DjZbDaFhoZq4cKF5VtgCXJzczVhwgT17t1b1apVU0REhGrUqKEOHTpo5MiR2r59e7HLHT58WCNHjtT555+v5ORkRUZGql69eurZs6fGjRuntLQ053sr6vxs2rRJd9xxhxo3bqzo6GhFRkaqdu3auuCCCzRs2DBt2rTJ+d7PP/9cNptNVapUKfa706tXL+fntbjnmv/3v/+VzWbTRRddZPp5amqqJk6cqOuvv14tWrRwznXdunU1cOBALV261GX9p25/Z8yYoS5duig5OVkxMTFq27at3nrrLRUUFHg4QwAAAAAAAADgG2W+QvXgwYP67LPPJEmDBg1S48aNS73sqY2HBQsWqF+/fjp69KgkKSYmRpK0YcMGbdiwQRMmTNBXX32lTp06uVznypUrddttt+no0aOKj49Xfn6+tm3bpueff14LFy7U/Pnz9cMPP+j6669XVlaWEhISlJubq7/++ktPPfWU1q1bp+nTp7tcf25urnr06KFFixYpNDRUsbGxOnr0qObNm6d58+ZpxIgRHjdNXXnppZf0xBNPyOFwSJKio6OVl5entWvXau3atfrggw/07bffqk2bNl4d90xGjhypESNGOH8fHR0twzC0fft2bd++XXPnzlW7du08al6+/PLLeuyxxyTJ+Xe0adMmPfPMM1qwYIHmzp0ru91+2nJDhw7Vq6++6vx9YmKiNmzYoMcee0zffvttiZ+dHTt2qH79+pLkk79HdxQUFOiqq67SrFmzFBoaqujoaGVkZGjp0qVaunSptmzZomeffday+k6VlZWlwYMH69NPP3X+LC4uTg6Hw/k5XbRokVavXi1Jatiwod5++23dfPPNevPNN9WzZ0/16dPHtM577rlHf/31l6pWraopU6ZY3qhMSEhQtWrVXL6ekZGhrKyscqzItddee02LFi3S+vXrNXDgQC1cuFChoSc29+vWrdOQIUMkSU8++aQ6d+5sVakm27dv15VXXuls8BU1PdPT052f/SNHjuiNN94wLVfUyE9NTZUkhYaGKiEhQfv27dPOnTs1d+5c1ahRQ/369ZNUMedn7ty5uuKKK5STkyNJCgsLU0xMjPbs2aM9e/Zo2bJlCg8Pd263unTpIpvNpkOHDmnt2rVq2bKlc115eXlavHix8/c//vijmjdvbhrvxx9/lCR169bN9PMxY8Y4tz12u13x8fGSpF27dmnXrl2aPn263njjDf3nP/8p8c/z2GOP6aWXXnL+HWdnZ2vlypVauXKlvvnmG82aNUsREREezBQAAAAAAAAAeF+Zr1D96aefnI2+sjwbc/fu3c5makpKihYvXqxjx47p2LFjWrhwoZo0aaLU1FT17dtXe/fudbme2267TW3bttX69euVlpamjIwMvfnmm7Lb7Vq0aJFGjhypQYMG6YorrtCOHTt09OhRpaen68knn5QkffLJJ5o3b57L9Y8dO1a//fab3nnnHWVkZCg1NVW7du3StddeK0l69tln9dVXX3k8D6eaOHGiHnvsMUVHR+v555/X/v37lZmZqaysLC1fvlzdunXT/v37deWVV+rYsWNeG/dMdu7c6fxH9SFDhmjv3r3KzMxURkaGjh49qkWLFumee+5RXFyc2+tes2aNHn/8cT3++OP6+++/lZqaqqNHj+rpp5+WVPiZmzx58mnLTZ8+3dlMHThwoPbs2aPU1FRlZGTovffe02+//aZx48aV4U9dft5++239/PPPmjRpktLT05WWlqbdu3friiuukCSNGjVKW7ZssbjKE/71r3/p008/VUhIiB577DHt3r1b6enpOnr0qP755x999NFH6tChg2mZm266STfddJOkwqs/9+/f73xtypQp+vDDD2Wz2TR58uQSG5nlZcyYMTpw4ECxv3777TdnY+myyy6zuFIpKipK06dPV1RUlH799VfTiQ/Hjx/XDTfcoOzsbHXs2NH5vbJaenq6evXqpXXr1ikpKUnvvfeeUlNTdeTIEWVmZmrr1q169dVXVbduXdNyq1atUt++fZWamqpmzZrpu+++U1ZWlg4dOqTjx49r+fLlevjhh03booo4P3fffbdycnLUs2dPrV27Vrm5uUpNTdXx48e1bt06Pfvss6Yr/ytXrqwWLVpIOtEcLbJs2TJlZWU5P7Onvp6bm6slS5ZIki6++GLTazVr1tSIESO0fPlyZWVl6ciRIzp+/Li2bdvmvNp8yJAhWrVqlcs/y+rVq/XSSy/pvvvu08GDB3XkyBGlpqbqueeek81m05w5c/TEE094NlEAAAAAAAAA4AtGGQ0fPtyQZEgy9u7d6/F67rrrLkOSkZSUZOzfv/+013fv3m3Ex8cbkox7773X9Nr27dudNTRr1szIzs4+bfmbbrrJ+Z5LLrnEcDgcp73noosuMiQZt91222mvdenSxbn8xIkTT3u9oKDA6Ny5s7MGV8uPGDGi1K+lp6cbiYmJhiRj9uzZpy1nGIaRl5dntG3b1pBkvP7668W+x5VbbrnFkGTccsstLt/zwQcfGJKMunXrmn7+ySefGJKMxo0buzWmq/UZhmGMGDHCOcfFzZNhGMbVV19tSDJ69Ohh+rnD4TAaNmxY4t9v0diSjC5dupz2+smfI1fjl6So/uLWXVonf85+/PHH017Pzs42atasaUgyRo0a5fE43jRv3jxnzWPHjnVr2YyMDOffW/fu3Y2CggJjy5YtRmxsrCHJeOihh3xUtfekpaUZzZs3NyQZLVq0MNLT060uyWncuHGGJCMkJMT5ebrzzjsNSUZiYqKxc+dOiys8oShLIiIijJUrV5Z6uU6dOhmSjEaNGhlHjx51a8yKMj8HDx50fsf27dtX6uUefPBBQ5JxxRVXmH7+7LPPGpKMJ554wggLCzMSExONgoIC5+sLFiwwJBmRkZHF5mlJ7r33Xpc5evI2/qabbip2+aLPQWhoqMf7FKXJNgAAAAAAAABwR5mvUD18+LDz/5OTkz1ah2EYmjFjhiTprrvuUvXq1U97T61atXTXXXdJUom35H3ooYeKvU1gr169nP//+OOPF3v70KL3/PHHHy7XX7t2bf3rX/867echISEaPny4JGn9+vVau3aty3WU1ueff66jR4+qTZs2pvpPFhoaqgEDBkgqfJ5peUlMTJRUeJvTk59N6w0REREaOnRosa/17dtX0ul/R6tXr9Zff/0lSRo2bFixf7+33HKL6tSp43LcevXqyTAMGYZh6e1+Jaljx46nXRkmFc5NaT6n5en999+XJDVv3lx33323W8vGxsZq+vTpCg8P1/z58/Xcc89pwIABOnbsmNq0aaPRo0f7omSvyc/P1/XXX69169apWrVq+uabbzy6KttX7rrrLl199dVyOBy68cYb9d577+ndd9+VJI0fP77E70N5K/oc/fvf/y717cu3bNnivHXtCy+8UOxzuUtSUeYnLi5OISGFcX3yldxnUrQNWbhwoem5pD/99JMk6fLLL9f555+vo0ePauXKlae93qFDB7dvu1t06+6TbylcHFdX/j7yyCOKiopSfn6+Pv/8c7fGBgAAAAAAAABfKXND1Ru2b9+uI0eOSJJ69Ojh8n2XXHKJpMIm7vbt24t9T/v27Yv9+cm3DD3vvPNKfE/Rc/iK07VrV5fPcrzoooucz+Fbvny5y3WUVtEtFzdu3Kjq1au7/DVy5EhJhbfhLS/t27dX5cqVtX//fp1//vl66623tGnTJhmGUeZ1N2vWTLGxscW+VrNmTUlyfl6KFDUDwsLCdOGFFxa7rM1mU5cuXcpcX3k4//zzXb7mag6s8ssvv0gqbM54om3btnrhhRckSc8884yWL1+umJgYZ6PVn91///2aM2eOoqKi9NVXX/lNA+5kEyZMUJ06dbRv3z7deeedkgqblkW3KfcHO3fu1L59+yTJeVvr0ij67Nntdl166aUejV0R5icqKkrdu3eXJPXu3VtPP/20li1bptzc3BKX69Kli+x2u9LS0rRixQpJUnZ2tn799VfFxsaqffv2zqbrybf9Lfr/4k7qkKRt27Zp6NChatu2rRITE2W322Wz2WSz2Zy3vN6zZ4/LumrXrq2GDRsW+1p8fLzatm0ryTs5CgAAAAAAAADeUOaGaqVKlZz/72mD5++//3b+/1lnneXyfbVq1Sp2mZO5ujqsqNFZmvfk5eW5rKGk+iIjI53z4ao+dxQ1GLKzs3Xw4EGXv9LT0yVJWVlZZR6ztBITEzVt2jRVqVJF69ev1/3336+mTZsqKSlJV155pT788MMS57EkJV3hV/R3lJ+fb/r5P//8I6nw81hSE66kvz9/Upo58HR+ve3AgQOSdNqzLd0xZMgQtWvXzvn7V155RY0bNy5zbb702muv6Z133nE+59XVyRxWS0pK0ttvv+38fYMGDTRmzBgLKzpd0WdIcu9zVLRc5cqVFRMT49HYFWF+pMLGb6tWrfTPP//oueee0wUXXKC4uDh16tRJL7/8crH5m5CQ4Lzat6hJ+ssvvygnJ8d5AlC3bt1Mrx8/flxLly6VVHxDdebMmUpJSdGrr76qlStXKi0tTbGxsapataqqVaumpKQkSSrxzgVn2g4Xve6NHAUAAAAAAAAAbyhzQ7VZs2bO/1+1alVZV4eTFN2isX///s5b0Zb0a8eOHeVaX48ePbR9+3ZNmTJFt9xyixo1aqS0tDR9/fXXuummm9SmTRvt3bu3XGtydfUwfMcbc/77779rzZo1zt8vXLiwzOv0pVmzZumRRx6RJD333HO67rrrLK6oZOPHj3f+/969e523x/YXnn6GvPV99/f5kaQ6depo5cqVmj17tv7zn/+obdu2cjgcWrJkiR599FE1bNjQdJVpkVMbpkX/Lfp5hw4dFBkZqcWLFysvL09LlixRbm6uoqOjT7tS/vDhwxo8eLBycnLUrVs3/fzzz8rKylJaWpoOHjyoAwcO6NNPP/XlNAAAAAAAAACAJcrcUL344oudz3abOXOmR+uoWrWq8/9Luk3gya+dvEx5KqlBmJOT43ymrDfqK3qWrK9u5Vt0pWN2drbL96SlpZW4jpiYGN10002aNGmSNm/erD179ujFF19UZGSk88rV8lClShVJ0qFDh0q8DWZ5N3iDQVk/p+np6RowYIDy8vLUokUL2Ww2TZs2TZMmTfJild6zcuVKDRo0SA6HQzfddJOefPJJq0sq0VtvvaWvvvpKdrtdKSkpysnJ0Q033FCuV7SfycnPzXbnc1S03KFDhzx+lnNFmJ8iISEh6tWrl8aMGaPly5fryJEj+uijj1SnTh2lpqZq4MCBp23/iq4yLWqUntpQjYiI0IUXXqjMzEwtW7bM+XqnTp0UFhZmWtd3332n9PR0JSUl6euvv1aXLl0UFRVles/JVxu7cqbtcNHrVuU8AAAAAAAAAJyqzA3VatWq6ZprrpEkffzxx9q8eXOply163mb9+vWVnJwsSZo/f77L98+bN09S4W1d69ev72nJZbJgwQKXzwldtGiR81a0J9++1FMdO3aUJK1YsUL79+8v8/pOVXRrxt27d7t8z7Jly9xa51lnnaVHH31UDz/8sCRp7ty5nhfohnPPPVdS4W1wi56reCrDMPz+yseKqOiZtV9//bVHy999993atm2bqlWrpnnz5umBBx6QVPh80i1btnitTm/Ys2ePrrjiCmVmZqpTp06aMGGC1SWVaO3atc4raZ9++ml99913SkxM1MaNG/XQQw9ZXN0JderUcd7m1Z3PUdFnr6CgQN9//73b41aU+XElLi5OAwcO1MSJEyVJBw8e1Nq1a03vKWqMZmVlad68efr999+VnJys1q1bO99z8lWsP/30k6Tib/dblBVNmjRRdHR0sTUV5XRJdu/era1btxb7WkZGhvN5r97IUQAAAAAAAADwhjI3VCVp1KhRio2N1fHjx3X11Vef8eqT1NRUXXPNNc6rH202m/r37y9Jevfdd4u9wmXfvn169913JUkDBgzwRtke2bVrlyZPnnzazx0Oh1544QVJUkpKilq0aFHmsa677jolJiYqLy9PQ4YMcdnILRr/6NGjbq2/VatWkgpvt1pcU3Xjxo364osvil02JyenxHUXXbVUdPWyr7Vu3VoNGzaUJI0ePbrYufrwww99drVvMLvtttskSevXr9e4cePcWnby5Mn6+OOPnc8hrVq1ql588UW1adNGx44d04ABA0q84rg8HTt2TJdffrn27dunBg0aaObMmSU+r9dqx48f1w033KDs7Gx16tRJTz75pOrWrav33ntPkvTee+/p888/t7jKE4o+RxMmTCj17eMbNmyozp07S5KGDRvmfJ50aVSk+TnTd+Dkq0RP3ebGxsbqvPPOkySNHDlS+fn56tKli+l9Rc3Tr776SsuXLzf97GQJCQmSpM2bNxd7Z4PVq1fr448/Ls0fSc8991yxP3/11Vd1/PhxhYaGOk/WAgAAAAAAAACreaXb1bhxY02dOlXh4eFav369WrdurRdffNH0HLqCggKtWrVKTz/9tBo0aHBao27YsGFKTEzUkSNH1KNHD9NVhkuWLFGPHj109OhRJScn6/HHH/dG2R5JSEjQ3XffrfHjxzv/QXn37t0aMGCA88qeUaNGeWWsxMREvfHGG5Kk6dOnq0+fPlq2bJkcDoekwibqxo0b9eqrr6pZs2b65ptv3Fr/FVdcodjYWOXl5en666/Xn3/+KanwKs9Zs2apR48eiomJKXbZF198UZdeeqmmTp1quhVzTk6OZsyYoZdfflmS1KdPH3f/2B6x2Wx69tlnJUlz5szRLbfcon379kkqvKXxxIkTdeeddzqvyi3Ojh07ZLPZZLPZ9Mwzz5RH2eVq0qRJzj/fzz//7LX1XnzxxbrhhhskSffdd5+eeOIJ02fi0KFDmjBhgrNhVuSvv/7SfffdJ0l66KGH1KtXL0lSeHi4pk2bppiYGK1YsULDhg1zqx5f/T32799fa9asUWJior799ltVrly5zOv05WfuoYce0oYNG5SYmKiPPvpIdrtdUuGJGkV/F7fffnuJV6gXZ/Dgwc6avWno0KFq1KiRcnJy1L17d40fP97UIN26datGjhypV155xbTcmDFjFBkZqS1btqhjx46aPXu28vLyJBXmzu+//6677rrrtCsnK9L8/PLLL2rZsqVef/11bdy40ZkBhmHol19+0d133y1JqlWrllq2bHna8kXN0aI7DhRdkVqkffv2io2N1YoVK5Sfn6+4uDi1bdv2tPX07NlTISEhOnLkiAYNGuQ8eSo3N1czZsxQz549FRcXd8Y/T0JCgiZPnqwHHnhAhw4dklR4ZeoLL7ygkSNHSpLuvfde1axZs1TzAwAAAAAAAAC+5rXLB/v166cff/xRDRs21KFDh/T444+rUaNGioiIUKVKlRQeHq5zzz1Xzz33nNLS0jRgwABTs65WrVr68ssvlZCQoPXr16tjx46KjY1VbGysOnXqpI0bNyoxMVFffvml89aQVrjnnnvUrl073XHHHYqPj1dycrLq1KmjGTNmSJKGDx+uq666ymvj3XLLLRo3bpzCw8P1/fff64ILLlB0dLQqV66syMhIpaSkaOjQodq0aZPb/4CfkJCgN954QzabTUuXLtU555yj+Ph4xcbGql+/fqpTp47zH7dP5XA4NHv2bN18882qXbu2oqOjValSJUVFRal///5KS0tT06ZN9dprr3ljGkpl4MCBevDBByVJU6dOVa1atZScnKz4+Hj9+9//VocOHXTXXXdJkiIjI8utrmAwceJEXX311XI4HBo9erRq166thIQEJSYmqkqVKrr99tudt/GUCpv2AwYM0LFjx9SmTRv997//Na2vSZMmevPNNyVJr732mn744Ydy/fMU57vvvpNU2KDv2rWrqlev7vKX1b744gvnFf3jx49XnTp1TK+/+eabOuecc5SamqpBgwapoKDAijJN4uLiNHv2bKWkpCg1NVV33HGHkpKSVKlSJcXExKhhw4YaMWLEac/Zbt26tWbNmqWEhAStW7dOl156qWJiYlS5cmVFRUWpffv2evfdd3Xs2DHnMhVxftauXashQ4YoJSVFkZGRqly5ssLDw9WxY0etXbtW8fHx+vjjj52N4ZOd2kA99fehoaHq1KmT8/cXXXSR8xnbJ2vUqJHzFslffPGFatWqpcTERMXGxqp///6KjY11fm9L0rp1az366KN68803VbVqVSUnJyspKUlPPvmkDMNQjx49NHr06FLNCwAAAAAAAACUB6/ej7Vjx47atGmTpk2bpkGDBqlhw4aKjIxURkaGkpOTnbdV3Lhxoz7++GOFhYWZlu/SpYs2btyohx9+WE2bNpXD4ZBhGGratKmGDh2qjRs36qKLLvJmyW4LDw/X/Pnz9cILL6hJkybKyclRQkKCunfvrm+//dblbQzL4q677tKff/6poUOHqlWrVoqIiNDRo0cVGxurdu3a6f7779fcuXM9uhXybbfdpm+//VbdunVTfHy88vPz1bhxY40ePVoLFixweYXqHXfcoffee08DBgxQ8+bNFR0drfT0dCUlJemiiy7SG2+8oZUrV5Z7c+n111/XF198oa5duyouLk45OTlq2rSpXn75Zc2ZM0eZmZmSCq/+DTZFV5PFxsaqWbNmXl13dHS0Pv/8c33zzTe66qqrVLNmTWVnZys0NFQtW7bUf/7zH+etVKXCK9KXL1+umJgYTZs2rdhb59566626/vrrZRiGbr75Zv3999+lquXkW45fcMEFZf/DnSI7O1sHDx4s8Vdp+aLW3bt369///rekwu/3tddee9p7oqOjNW3aNEVERGjRokVuXVVfVPP555/vlXpP1qBBA61atUpjx45V165dlZSUpIyMDCUmJqpDhw567rnnin22ac+ePbVlyxY9+eSTatOmjaKiopSZmamzzjpLvXr10rvvvutsIlbE+TnvvPM0Y8YM3X333Wrbtq0qV66s9PR0RUZGOpuTJeXjhRdeqIiICElS9erVlZKSctp7Tm6yFne73yKjR4/WlClT1L59e0VFRSkvL08NGzbUsGHDtGrVqlJfVfriiy9q+vTp6tSpkwzDUHh4uFq3bq0xY8Zo9uzZnPQCAAAAAAAAwK/YjJIezAmnrl27asGCBRoxYkRA3g42WHTs2FG//PKLRo4cqaeeesqr637mmWf07LPPqkuXLl69pa639OjRQ/Pnz9fw4cN90vj3F6NGjdJTTz2lTp06adGiRVaXU6KKVKtUeGvXxMREHT9+XPPmzVP37t2tLsmvMD8lK69t5ODBgzV58mTdcsstmjRpks/GAQAAAAAAABA8vHqFKuDPFixY4Hw2b+/evS2upnzl5OTol19+UXJysoYOHWp1OT71448/SpJeeOEFiys5s4pUqyQtXbpUx48fV7du3WgWFoP5AQAAAAAAAIDAREMVAeXee+/VpEmTdODAARVdfH306FG9++676tu3r6TCW1ued955PqthwYIFstlsstls6tevn8/GcUdRo+fRRx9VQkKC1eX4TE5Ojn799Vf17t3b8tuDn0lFqrXITz/9JKniNIDLG/NjnQcffNC53Z08ebLV5QAAAAAAAAAIMKFWFwB405IlSzR27FhJUkREhKKjo3X06FFnczUlJUVTpkzxydixsbGqVq2a6WdJSUk+GctdXbp0UTDc3TsiIkLHjx+3uoxSqUi1FhkxYoRGjBhhdRl+i/mxTnx8/Gnb30A+eQQAAAAAAABA+aKhioAycuRIffnll1q2bJkOHjyotLQ0JSUlqVmzZrr66qt1xx13KDo62idjDx06NOBvpwsA/mjkyJEaOXKk1WUAAAAAAAAACFA2IxguWwMAAAAAAAAAAAAAD/AMVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXAi1ugAA8Ge5eQXKzilQSIhNURF22e3BfR5Kbl6BjmcXyG5nPiQpJ7fw88F8AAhU5KAZOWhGDgIIdOSgGTloRg4CCHTkoBk5aBaMOUhDFQBOsnlHmr6Yv0PL1x/Sig2HtGPfMedrdrtNKQ0S1Talss5vUUXX9ayvSomRFlbrexu2pmrm/J1asbFwPnbtz3S+Fmq3qXmjJLVtemI+EuMjLKzW99b8eVhf/bzL+fnY+3eW87Ww0BC1aJSktimV1aFVVV17ST3FxYRbWC0AuI8cNCMHzchBAIGOHDQjB83IQQCBjhw0IwfNyEHJZhiGYXURAGAlwzD01c+79Pb0DZr7675SLxcZbtcNlzbQ/QNSdG5KZR9WWL4cDkOfz9uhsZ9s1M+/7y/1ctGRdg3q01D3D0hRi8bJPqywfOXnOzRjznaNnbFRS1YdLPVysdGhuvmKRrp/YIrOqZ/ouwIBoIzIQTNy0IwcBBDoyEEzctCMHAQQ6MhBM3LQjBw0o6EKIKjt3Jehfz+zWPOWln6HoTj/GZiiF/7TTjHRYV6qzBp/7UrXv55aqMVuBOSpQkJsGnpLcz17z7mKjKjYN0LYsDVVg59aqN/XHfJ4HaF2m4b9u7WevKOVwsPsXqwOAMqOHDQjB83IQQCBjhw0IwfNyEEAgY4cNCMHzcjB09FQBRC0Js3arPv/+6uOZeV7ZX1n147Tx6MvVvsWVbyyvvI27pONeviVZTqeU+CV9Z1TP0HTX7pYrZpU8sr6ypNhGHptyjoNe3O5cvMcXllny8bJ+uTliwPqrCwAFRs5aEYOnkAOAggG5KAZOXgCOQggGJCDZuTgCeSgazRUAQSl0RPX6Ikxy72+3pioUM0ac4m6X1DT6+v2FcMw9NRbK/T8+DVeX3d8TJi+H9dLF7au5vV1+4phGHropWUa89F6r6+7UkKE5rzbW20D6FYoAComcvAEctCMHAQQDMjBE8hBM3IQQDAgB08gB83IwZLRUAUQdN6Yuk4PvbzMZ+uPirBr7nuXqmObihGWo95bpafeWumz9cfHhOmniZdVmOcpPPb6b3rpg7U+W39yfIQWTe6jlLOTfDYGAJSEHDQjB83IQQCBjhw0IwfNyEEAgY4cNCMHzcjBktFQBRBUlqw6qIsGfyN3tnw7ZveXJNXr/Umpl6leKUrrv7xGyQkR7pZYrn74ZY963TXHrWU8mY96NWO19ourFevnz1L4Yt4OXTNkvlvLeDIf59RP0KoZ/Sr8sxQAVDzkoBk5aEYOAgh05KAZOWhGDgIIdOSgGTloRg6eWYjVBQBAeck6nq9/PbXQrZ0GSUqIDVNCrHuBd+DwcT344lL3BipnaRm5+vczi91ezpP52LHvmB597Te3xypPh1KzdfeoJW4v58l8bNqephFjfXf2GwAUhxw0IwfNyEEAgY4cNCMHzchBAIGOHDQjB83IwdKhoQogaIx8d5W27Eovt/GmfvOXZi/eU27juWvYm8u1+0BmuY03bsYmLVpxoNzGc9fDryzT30eyy228Vyav08oNh8ptPAAgB83IQTNyEECgIwfNyEEzchBAoCMHzchBM3KwdGioImjt/ydLz727SvV6faLwc99X+Lkf6OzLZmj0xDX658hxq8srd3sOZOrpt1eo9iXTFNbmfUW0/UCNL/9Ur01ZqyNpOVaXV2bHsvI0dvrGch/35Ul/lPuYpXH4aLYmzvyz3Md9ZbLv7sFfFnsOZOqjb7eW65gOh6HXpq4r1zGBk5GDZuSgb5CDZuTgCeQgrEYOmpGDvkEOmpGDJ5CDsBo5aEYO+gY5aEYOnlBRc5CGKoKOYRj674Q1qn3JdD0zdqV27j+mvHxDefkObduToSffXK6zekzT/z5eb3Wp5cIwDA3/33LV7TVdz49foz0Hs5RfYCg3z6G/dqdr6Ku/qUa3jzXxi/IPGW/66NutysjKK/dxf/xtvzZuO1ru457JB19uUU6uo9zH/Wbhbu3af6zcxz2T8Z//qQJH+T9S/NMftuvvw8F3oAJrkYNm5KBvkYNm5KAZOQgrkINm5KBvkYNm5KAZOQgrkINm5KBvkYNm5KBZRcxBGqqQJGVmZuqBBx5Q1apVFRcXp8GDB2vSpEkKCwtTdnb5XepdHp56a4WGvblcBQ5DxW0nHIaUl2/oP6OX6sX315R/geXIMAw9+OJSPT9+jRxG4Zkh5tcLf+XmOfTvZxbr7ekbLKq07MZ/bt2Ojz/udFk1Hw6HoQ++3GzJ2K4YhqEJFv0d5eY5NPWbvywZG2bk4Ank4Mmvk4PeQA6eQA6akYP+gxw8gRw8+XVy0BvIwRPIQTNy0H+QgyeQgye/Tg56Azl4AjloVhFzkIYqlJ+fr8suu0zfffedXn/9dX322Wfavn27hg0bpiZNmigyMtLqEr1m3tK9en586XcGHn9juZau+duHFVlr1k879ebHpd8ZuP+/v2rt5iM+rMg3MrPytGrTYcvGX7L6oGVjF+efI8e1eWeaZeMvXuVf87Fj7zHt+yfLsvH97fMRjMhB18hBM3LQM/62nSMHzchBkIOukYNm5KBn/G07Rw6akYMgB10jB83IQc/423aOHDQjB90TanUBsN6YMWO0evVq/fnnn6pevbok6ZxzzlG9evXUrVs3i6vzrjEfrZfdblNBQekuYQ+12/S/aRt0QauqPq7MGq9PXS97iK3Ul/TbQ2wa+8lGjXuqo48r867Vfx457Syz8rTmzyPKz3coNNQ/zmFZscG6najC8Q/JMAzZbDZL6yiywuIHoFs9PsjBkpCDZuSgZ8jBU8cnB/1pfJCDJSEHzchBz5CDp45PDvrT+CAHS0IOmpGDniEHTx2fHPSn8d3lH59iWMYwDL322mu6/fbbnTsNklS3bl2FhoaqVatWkqSNGzfqvPPOU+PGjdWtWzft37/fqpI9tmv/MX27cHepdxokKb/A0IwftgXkw9g3bT+qhSsOuHV/9PwCQ5O+2qL0Y7k+rMz7rDwLS5KO5xTozx3Wnfl0KqvnIzU916+eF2D1fOzan6nDRwPrFkIVCTlYMnLQjBz0DDloRg6akYPWIgdLRg6akYOeIQfNyEEzctBa5GDJyEEzctAz5KAZOWhW0XLQZhiGdacnwHIbNmxQs2bNtGjRInXq1Mn58/3796tmzZr64YcfdMkll6hLly4aMmSI+vbtqzFjxmjlypWaPHmyR2PWrVtXaWnlvxHNi0pRVpXbPFo25uBYheZs9XJF1sqNbqPjlW/0aNnY/a/LnrfHyxX5TnZ8T+Uk9ir2tR2z+yshNqzE5RPiwiVJaRkl7zClHctTvd6fFPtazMG3FZqzrRTV+t7xxCuUG9+12NfKaz5i978me97eMxdbDo4nXaPcuAuLfa3c5mPff2XP9/4ZWQkJCdq5c6fX1xtIyMHSIQfNyMHikYNm5KAZOeifyMHSIQfNyMHikYNm5KAZOeifyMHSIQfNyMHikYNm5KBZoOQgV6gGub17C7+4Vauab90wd+5cSVLr1q118OBBbdmyRX379pUk3XbbbZo5c2b5FuoFhq3kL7+vlvVbZZmPkAo2HzZ/2NTZrS7gJNbPh2Hzn/nwj1r8oYbgRA76flm/RQ6WM3/azlk/H/6RPYX8oxZ/qCE4kYO+X9ZvkYPlzJ+2c9bPh39kTyH/qMUfaghO5KDvl/Vb5GA586ftnPXz4R/ZU8g/avGHGkqHZ6gGuUqVKkmStm7dqsaNG0uSMjMzNWrUKNWoUUNVqlTRihUrVLt2becysbGxioyM1OHDh53Lu8Oqs+Pm/rpXPe+c7dGy8+bMUodW1bxckbW+mLdD1wyZ79GySxfPV/NGyV6uyHdGvbdKT721stjXXJ0Zc7LUxYVnrCV1+tDjGubM/kYd2/jHZ+ix13/TSx+sLfa18pqPJYt+Uqsm7m8/fOHe53/R2E82Fvtaec3HmtW/q0GteI+Xh+fIwdIhB83IQfeRg2bkoBk5aB1ysHTIQTNy0H3koBk5aEYOWoccLB1y0IwcdB85aEYOmlWkHKShGuSaN2+uunXr6uGHH1Z+fr7y8/P14osvKiMjQ23atLG6PK/q2Lqa4mPClJ6Z59ZyVZIi1S6lio+qsk639jUUGW5Xdm5BqZex2aQ61WOVcnaSDyvzvro1Yq0uwS9qKOIPtdTxgxqKWD0foXabalaJtrSGYEYOnhk5eAI5WLFrKOIPtZCDJ5CD1iIHz4wcPIEcrNg1FPGHWsjBE8hBa5GDZ0YOnkAOVuwaivhDLeTgCRUtB62/vhmWCg8P12effaaoqCj1799fI0eO1PDhw5WYmKjWrVtLkmrVqqXdu3c7lzl27Jiys7M9OgvLStFRofr3NU1kD7GVepmQEOme/k0VFhZ4X5XE+AjddEVD2e2lnw9Jun9gikLcmEN/0K6ZtTt+VZMjdVY1/wkGq+fj7NpxSoqPsLSGk7VNqWzp+M0aJikygvObrEIOlowcPB056D5y0IwcNCMHrUUOlowcPB056D5y0IwcNCMHrUUOlowcPB056D5y0IwcNKtoORh4W0O4rV27dlqxYoWysrK0atUqdevWTZs3b1arVq0kSdWqVVPDhg01a9YsSdLEiRPVr18/Cyv23P0DUhQdFaqQUnzy7SE2JcZF6M7rzvF9YRYZcnNzhYeGyFaK/QC73aZqlaL0r36NfV+YlzWuG6+YKOs2zG1TKstWmkkuJy0bJynUzR1Gb7I6qE91boq1B0H+Nh/BiBwsHjloRg56jhw087ftPjkIcrB45KAZOeg5ctDM37b75CDIweKRg2bkoOfIQTN/2+6Tg+6hoYrT/PHHH3I4HM4zsSRp3Lhxeu6559SoUSN9+eWXGj16tHUFlkG9s+L0zf8uUUS4vcQzsux2m2KiQjV7XC/VqECXnLvrnPqJmvlGD4WHhZxxPhJiwvXDO72VnOA/Z9CUlt0eol4X1rJs/Es7WTd2cSIjQnVx+xqWje9v85EUH6ELWlp3dtqlHf1rPkAOSuTgqcjBsvG37T45aEYO4lTkIDl4KnKwbPxtu08OmpGDOBU5SA6eihwsG3/b7pODZuSge2io4jSrV69WdHS0GjVq5PxZs2bNtHz5cm3ZskU//fSTatasaWGFZdO5XQ39OvUKdT2vuiSZbu1gD7HJZpMuueAsLfv4Sp3XPPCeEXCqXh1raeEHl6tDq6qSip+PyzrV1u/Tr1SLxhXngeunuqd/U0vGjYkK1c1XNDrzG8uZVfORFB+u/r0aWDJ2Sayaj5pVotX34rqWjA3XyEFysAg5WHbkoBk5aEYO+idykBwsQg6WHTloRg6akYP+iRwkB4uQg2VHDpqRg2YVMQdpqOI0d911lzIzMxVSmvs/VFCtmlTSvPGX6c+vrtXjt7ZUWGiIwkJD9OTtrfXXt9fp+3G9dE79RKvLLDftW1TRosmXa90XV+uRwS2c8zHi7jbaOae/vvrfJWpQK97qMsuk2/k11KReQrmPe+PlDZUQF17u457J5Z3rqFa1mHIf99Z+jRUV6X/3xb+uZ31VTiz/swzvuLZJQD6LpKIjB8lBctB7yEEzctCMHPRP5CA5SA56DzloRg6akYP+iRwkB8lB7yEHzchBs4qYgxWrWsDLGtdL0Kj72yk6KlTRUaF69t5zK3xAlkWzhkn67wPnOefjqTvbqHb1WKvL8gqbzabn729brmPGRofq8VtbluuYpRUaGqLn7ju3XMdMig/Xw7e0KNcxSysyIlQj7i7f+ahWKVL3D2xWrmMCpyIHzchB7yIHzchBM3IQ/oAcNCMHvYscNCMHzchB+ANy0Iwc9C5y0IwcNKuoOUhDFUDQuOaS+urfu77by6Udy1PasTy3l3t5SHvVOyvO7eXKyy1XNlKfzrXdXs7T+fjfEx38+tkb9/Rvqi7tqru9nKfz8e7TnSrkszcAVFzkoBk5aEYOAgh05KAZOWhGDgIIdOSgGTloRg6Wjs0wDMPqIgCrJXacKkk6uuQmiyvxD4E8H4dSs9Xi6i904PBxn45zSYeamvNOb9lsrh9q7w/2HsxUy2tm6kh6jk/Hubp7PX32Wje/n49te9LV+tovlZHl/o6AO266vKGmvNDFp2MA7gjk7b4nAnk+yEEzctCMHESwCuTtvicCeT7IQTNy0IwcRLAK5O2+JwJ5PshBM3LQjBw8M65QBRBUKidFavY7vZTow/v3tzmnkj59pbvfh6QknVUtRt+P66XYKN/dv//C1lU15fnOFWI+GtSK19dvXaLIcLvPxujWvobeG9HRZ+sHgJKQg2bkoBk5CCDQkYNm5KAZOQgg0JGDZuSgGTl4ZjRUAQSdVk0q6ccJl6pKUqTX131+iyqaN/5Sv3zguivtW1TR3Pcu9cnOVJd21fX92F6KiQ7z+rp9pUu7GvpubE+f7Ez17lhLX/+vpyIj/O8B9ACCBzloRg6akYMAAh05aEYOmpGDAAIdOWhGDpqRgyWjoQogKLVpWlkrPumrXhee5ZX12WzSQzc1048TLquQ93+/oFVVrfikr0f3yi+OPcSmYf9upTnv9FZ8bMXZiSpycfua+n16X13QsopX1hcWGqKR956rr968RNE+POsNAEqLHDQjB83IQQCBjhw0IwfNyEEAgY4cNCMHzchB12ioAghatavH6vtxvTThmU5Kivc83M6pn6CFH/TRa49cUKFDoUGteP044TL974kOio/x/Myplo2TtfSjK/T8f9opwoe3iPC1c+onavHky/XykPaKKcPfa7tmlbV8el89dWcbhYURuwD8BzloRg6akYMAAh05aEYOmpGDAAIdOWhGDpqRg8WzGYZhWF0EYLVAfti4J4JxPrKO5+uTOdv09vSNWrHh0BnfHxJi0xVdauue/k3V44KzFBLi//fBd8exrDx9/N1WvT19o/7YfOSM77fbbbqqW13d07+pup5Xo0I8F8Ad6cdyNfWbv/T29I3auO3oGd8fFhqiay+pp3v6N1XHNtUCbj4QeIJxu1+SYJwPctCMHDQjBxHognG7X5JgnA9y0IwcNCMHEeiCcbtfkmCcD3LQjBw0IwdPoKEKKDiDsiTBPh879mZoxYZDWrHhsDbvTNOsn3ZKkq7rWV8tGiWrbUoltU2prEqJ3n/WgL8xDEPb92ZoxYbDWrHhkLbsTNdXPxfOx/W9GqhV42S1Tamsc1MqKSm+4t3Sw12GYeivXenOz8fWPen6+uddkqQbLm2gVo0rqW1KJZ3btHKFel4EEOzb/VMF+3yQgyeQg2bkIAJVsG/3TxXs80EOnkAOmpGDCFTBvt0/VbDPBzl4AjloRg7SUAUkEZSnYj7MmA8z5sOM+UAg4HNsxnyYMR9mzIcZ84FAwOfYjPkwYz7MmA8z5gOBgM+xGfNhxnyYMR9mwTYfFf+mxQAAAAAAAAAAAADgIzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUATgZhqHU9Bw5HIYcDkMZmblWlwQAQLkhBwEAwYwcBAAEM3IQwJmEWl0AAGut23JEn/6wQ8s3/KPl6w/p7yPZztfiO0xVnRoxaptSWe2bV9HAy85WnRqxFlYLAIB3kYMAgGBGDgIAghk5CMAdNFSBIORwGPps7na9PX2jFq44UOJ7d+3P1K79mZo5f6ee/N8KXd65tu4fmKIeF5xVTtUCAOBd5CAAIJiRgwCAYEYOAvAUDVUgyGzbk67bRizWz7/vd3tZh8PQVz/v0lc/79INvRvorWEdVCkx0gdVAgDgG+QgACCYkYMAgGBGDgIoC56hCgSRD77crJbXzPRop+FU02dvU0q/z/XDL3u8UBkAAL5HDgIAghk5CAAIZuQggLKioQoEidET1+jWpxcp83i+19b595Fs9bn3B82Ys81r6wQAwBfIQQBAMCMHAQDBjBwE4A00VIEgMObDdXpizHKfrDu/wNDAx37W1z/v8sn6AQAoK3IQABDMyEEAQDAjBwF4Cw1VIMAtXfO3hrzym1vL7JjdXztm9y/1+wschm584mftPnDM3fIAAPApchAAEMzIQQBAMCMHAXgTDVUggGXn5OtfTy+Uw2G4tVxCbJgSYsPcWiY9M093PLtEhuHeWAAA+Ao5CAAIZuQgACCYkYMAvI2GKiDJMIyADLwXJqzRpu1p5Tbe7CV79NG3W8ttvPISqJ8PeAefDwSCQP0ck4PwhUD9vniK+UAgCNTPMTnoHYH6+fAU8wEEnkD9XpOD8IVA/b54Ktjmg4Yqgta2Pel69LXfVOmiD5V+LE/px/JUtctHevLN5dq1v+LfouF4dr7emrah3Md9dcragNiIrv8rVfe98IviO0xxfj5qXzJdL4xfrYOHj1tdHiz2+7p/9K/hCxV93iTn56PR5Z/qjanrdDQ9x+rygFIhB30jUHIQZqs3HdYdzy5W7PmTnd+X+r0/0csf/KHDR7OtLq/ckYMIBOSgbwRKDnI8aEYOAoGHHPSNQMlBmJGDZsF8PGgz+IYjyDgchob/b4VGv79GITabCk657UNIiE0yDI28t62G3d5KNpvNokrLZtKszfrXU4s8WjZ18Y2SpKROH3q0/C9TL1eHVtU8WtZqeXkO3fPCEk34fLPsdpsKCk75fNgKPyNjn7xQt197jkVVWiux41RJ0tElN1lcSfnLOp6vgY//pFk/7VKo3ab8kz4fRZuKyHC7PhrdVVd1r2dNkcAZkINnFsw5WJxg3u7n5Bbo1qcX6uPvtrnc7oeFhuj9kRdpUJ+GFlVZfshBBAJy8MyCOQc5HjQjB88smPeTUDGRg2cWzDlYnGDezpGDZhwPcoUq/l9mZqYeeOABVa1aVXFxcRo8eLAmTZqksLAwZWcH1lkWD7+yTP+duEaGodN2GqTCHQuHIQ1/a4VGjF1pQYXeMeGLzUE5dlk4/v8h8hP/v/5TD54lyWFI+QWG7hi5ROM+2VjeJcJCuXkFuuzeOfp6wS5JMu00SJJhFP7Kzi3QNUPm6/O5260oEx4iB08gByv22PCe/HyHrhkyX9O/31b4exfb/bx8h258YoGmfr3FijLLDTkY2MjBE8jBij12WXA8aEYOIpiQgyeQgxV7bHgPOWjG8WAhGqpQfn6+LrvsMn333Xd6/fXX9dlnn2n79u0aNmyYmjRposjISKtL9JrvFu3WGx+uL/X7n3t3tRYu3+/DinwjN69Av6/7x7Lxf1l90LKxy+L9mZs144ftKu11+/e98Iv+3H7UpzXBf7z4/h9atOKAHI6S31f0+Rn0xM/650jw3Q6sIiIHXSMHPVNRcxBmb03boO8W7lYx/85kUrTdv/XpRQFxezRXyMHARQ66Rg56pqLmIMeDZuQgggU56Bo56JmKmoMwIwfNOB4sREMVGjNmjFavXq1FixZp0KBB6tWrl6ZMmaL9+/erdevWVpfnVWM+XC+7vfS3qgi12/Q/C+63X1br/0pVbt4Ztm4+9OeONGVk5lo2vicMw9DrH65TiBt3MrHZbBo3Y5PvioLfyMtz6H8fbzjjTlQRwyhc5v0vOSuxIiAHXSMHPVMRcxBmDodR+I9NbuwXOAzpvc8Cc7+AHAxs5KBr5KBnKmIOcjxoRg4imJCDrpGDnqmIOQgzctCM48ETaKgGOcMw9Nprr+n2229X9erVnT+vW7euQkND1apVK0nSnXfeqbPOOqvC3jdfKnzY+g+/7i32tj2u5BcYmjl/pw4cyvJhZd63+s8jlo5vGNLaLamW1uCuX9f8rQ1bj5Y6GKTCW6NMnPmnjmfn+64w+IWvft6pf1Ldu82Pw5DemrZRPKrcv5GDJSMHPVMRcxBm85bu1c79x0p9lZJUeNA9bsYm5Vn4jze+Qg4GLnKwZOSgZypiDnI8aEYOIliQgyUjBz1TEXMQZuSgGceDJ4RaXQCstXHjRu3bt0/9+vUz/Xz//v3Kz893nok1aNAgjRw50rRz4am6desqLS2tzOtxV15UM6nKrW4vV+Aw1LDZRQrN2eqDqnwjJ66zlNS32Nd2zO6vhNiwEpdPiAuXdOIh7K6kHctTvd6fFPvaJb37KSy74jxTJie2g5R8rdvLHcvKV9VaTWXPt+5WIuUtrdYoSVJiYqK1hZSj7IReUnx3yWZ3a7k9BzOVmFxdNiPHR5WVLCEhQTt37rRk7IqCHDwzctC1QMrBkgTjdj8nrquU2EeyuXf+6ZG0HFWqXk8hBem+Kcwi5GDgIgfPjBx0LZBykONBM3Kw9Px5P4kcPDNy8MzIQdcCKQdL4s/bOV8hB804HjyBhmqQ27t3rySpatWqpp/PnTtXkpw7Dp07dy7XunzBsHn+cTdsJQet/3Fv4+YTbm5gLWcLlQyH20HpXBYBrXD74dkZVYYtzLIdB5wZOVjaZclBt1W0HIRJWbf7gYYcDFzkYGmXrWjfaz/IoIqWgxwPmpCDCBbkYGmXrWjfaz/IoIqWgzAhB804Hjwh8Pb64JZKlSpJkrZu3arGjRtLkjIzMzVq1CjVqFFDVapU8fqYVp0dN3/pPvW443uPlv3xh690fsuqZ36jn3jzo/V64MWlxb7m6sypkxWdgZXU6UOPa/j80+nq3amWx8uXt6lfb9HNTy70aNkNa39X7eqxXq7IfyV2nCpJOrruqLWFlKOX3v9DT7z5+xkfvH4qm03658BORYSzI+2vyMHSIQfdV9FysCTBuN0f98lG3fP8Lx4tu2vbBiXFR3i5ImuRg4GLHCwdctB9FS0HOR40IwdLLxj3kwIJOVg65KD7KloOliQYt3PkoBnHgyfwDNUg17x5c9WtW1cPP/ywvv76a82cOVPdu3dXRkZGwD14vWObqkr6/1s2uKNGlSi1Tansg4p8p/5ZcVaXoAa1rK/BHZd2qq2wUPc2iSEhUutzkgPu4Bmn63txHbd3Gux2my67qHZA7TQEInLwzMhBz1S0HITZFV3qKCTEvWdk2UNsuujcagF38CyRg4GMHDwzctAzFS0HOR40IwcRLMjBMyMHPVPRchBm5KAZx4Mn0FANcuHh4frss88UFRWl/v37a+TIkRo+fLgSExMDbschMiJUd1x3juxubAxDbNK9/VMU6uaBldXaplSydPy4mDA1rBNvaQ3uqpwUqRt611eovfSfD4dD+s/AZj6sCv6iSf1EdWtfw63tR0GBoftuSPFhVfAGcrBk5KBnKmIOwqxW9Rhd0aW27G7sFxQ4DN0foPsF5GDgIgdLRg56piLmIMeDZuQgggU5WDJy0DMVMQdhRg6acTx4QsXaGsIn2rVrpxUrVigrK0urVq1St27dtHnzZrVq1crq0rzu/gEpSogNK9WX3263qUpylO64tkk5VOZdNavGqHrlKMvGP7dpJbfP4vEHj93aSmGhIbKVonS73aZGdePVv1cD3xcGvzDirjalfq/dbtMFLauo54Vn+bAieAs5WDxy0HMVNQdhNvyO1gqx2Uq9X9CyUZL6XVzX94VZhBwMXORg8chBz1XUHOR40IwcRLAgB4tHDnquouYgzMhBM44HC9FQxWn++OMPORwO05lYgwcPVq1ahfd9r1Wrlm666SaLqiubs6rF6PtxvRUTFVriGSZ2u02JseGa+25vVUm2LoDL4vLOtS0bu89F1o1dFs0aJmnmGz0UHhZS4s6l3W5TzSrR+uGd3oqO4lHUwaJzuxqa/HxnhYTYStwxtofY1LR+or7+X092oCsocpAcLKuKmoMwa9esima8crHs9jNv9+ufFafZ7/RWWFjgHl6Rg8GDHCQHy6qi5iDHg2bkIIIVOUgOllVFzUGYkYNmHA8WCty/YXhs9erVio6OVqNGjZw/mzRpkvbs2SPDMLRnzx5NnTrVwgrLpn2LKvrt4yt1Refastlk+mKH2Aq/9Nd0r6ffp/VVi8bJFlZaNndf39SScSPCQ3TrVY0tGdsbenWspcWTL1e39jUkFX4ewkJDnGcqh4eF6KY+DbV8Wl/V84NnMqB8DerTUHPf7a3zW1SRpNMOQKIjQ3V3/6ZaMuVyVU6KtKJEeAE5SA6WRUXPQZj161ZPP0/so05tqkkq2i8o3DewSYqMsOu2qxtr2UdXqkaVaGuLLQfkYHAgB8nBsqjoOcjxoFlxOVgkGHMQwYEcJAfLoqLnIMzIQbPijgeLjo+l4DgetBmGYVhdBGCVPQcy9dF3f+npt1dKkp6/v51uvPxsVa8cGBvADjd+paV//OP2cqmLb5QkJXX60O1lb7mykSaN6uz2cv7or13pmvb9Vh04dFyhdpsa1onXoD4NlZwQeA8Xd0dix8IDh6NLKuYZmd6ydvMRfTp3u158/w9J0ltPdNCAy85WbHSYxZUBpUcOFo8cNGO7X2jjtqOaMWebDh4+rvAwu5rUS9DAy85WQly41aVZghxEICAHi0cOFuJ40KwoB1+YsEaS9MajFwR1DhZhPwkVGTlYPHLQjO1cIXLQrOh48J8j2YqMsCulQWJQHA/SUAUUuMGwYPl+db31O7eX83THITrSrrVfXK0GtXjweiAL1O+Lp5gPBIJA/RyTg94RqJ8PeAefDwSCQP0ck4PwhUD9vniK+UAgCNTPMTnoHYH6+fAU8xHcuOUvEMC6tKuh+wakuL1c2rE8pR3Lc3u5Fx9qH3A7DQCAioscBAAEM3IQABDMyEEA3hZqdQEAfOu/D7TTnCV7tGVXeqmXqdf7E7fH6da+hu7pb83zCQAAcIUcBAAEM3IQABDMyEEA3sQVqkCAi40O05x3euusqr57/kHrc5L1+WvdTQ+yBwDAH5CDAIBgRg4CAIIZOQjAm2ioAkGgfq04LZzUR/XPivX6ui9oWUXzx1+mxPgIr68bAABvIAcBAMGMHAQABDNyEIC30FAFgkSDWvFaPr2fBvU52yvrCwmx6ZHBLfTjhMuUnMBOAwDAv5GDAIBgRg4CAIIZOQjAG2ioAkEkOSFCH/63q74c00O1q8d4vJ4WjZK0ZPLlemlIe0VF8ihmAEDFQA4CAIIZOQgACGbkIICy4hsPBKG+F9dVn4tq69tFuzX2k4364Ze9Z1wm1G7T1T3q6Z7+TdW5bXXZbDwXAABQMZGDAIBgRg4CAIIZOQjAUzRUgSAVGhqivhfXVd+L6yo1PUcrNxzW8g3/aOO2NGVl5yskRIqNClOLRklqm1JZrc+ppNjoMKvLBgDAK8hBAEAwIwcBAMGMHATgCRqqAJQUH6HuF9RU9wtqWl0KAADljhwEAAQzchAAEMzIQQClxTNUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoIag6HoT0HMlXgMFTgMLT3YKYMw7C6LAAAygU5CAAIZuQgACCYkYMA4J5QqwsAytvKDYc07fttWr7hkFZuOKT0zDzna7Uuma7EuHCd27SSzmteRYMuO1stGidbWC0AAN5FDgIAghk5CAAIZuQgAHiOhiqCgsNh6OPvtuqtaRu0bO0/Jb73aEaufvxtv378bb9efP8PdWpTTfcPTNF1PevLZrOVU8UAAHgPOQgACGbkIAAgmJGDAOAdNFQR8P7ala5bn16oRSsPerT84lUHtXjVQY3//E9NeKaT6taM83KFAAD4DjkIAAhm5CAAIJiRgwDgPTxDFQFtwud/quU1X3i803CyeUv3qfnVX+jjb7d6oTIAAHyPHAQABDNyEAAQzMhBAPAuGqoIWKMnrtHtzy7W8ZwCr63zWFa+Bj3xs96evsFr6wQAwBfIQQBAMCMHAQDBjBwEAO+joYqA9L+P1+uJMct9tv77XvhVk2Zt9tn6AQAoC3IQABDMyEEAQDAjBwHAN2ioIuAsX/+PHnppmVvL7JjdXztm93drmTtHLtGGraluLQMAgK+RgwCAYEYOAgCCGTkIAL5DQxUBJSe3QIOHL1SBw3BruYTYMCXEhrm1TG6eQ4OfWqj8fIdbywEA4CvkIAAgmJGDAIBgRg4CgG/RUEVAeemDP7R+69FyG+/3dYf01jSeG4DgkZ/vkMNhyDAKfwW7vDzmA/6FHIS35ec75Pj/bZzDzX+YQeAjB+FvyEHAtzgeNCMH4W/IQXgbx4Nm5CBoqCJg5OQWWBLiYz5ar4ICzsZC4DIMQ4tXHtCAR39S1HmTlJGZp/RjearW9WONeHul9h7MtLrEcmUYhn76bZ+uHTJfkSfNR83u0zTqvVU6cCjL6hIRpMhBeIthGPpt7T8aPHyBottPVsaxwu1clc4fatiY5dq5L8PqEmEhchD+ihwEfIPjQTNyEP6KHIS3cDxoRg7iZDaDVjoCxLTvtmrg4z97tGzq4hslSUmdPvRo+W/eukR9OtfxaFnAn2Vm5WnAYz/p6wW7FWq3Kb/AHBkhITbZJI0dfqHuuPYca4osR2kZubpmyDzNX7a/+PmwSXa7Te+P7KwbL29oUZUIVuQgvCE7J1+Dhy/UJ3O2u9zuyzD0ytDz9eCNzWSz2SyqFFYgB+HPyEHA+zgeNCMH4c/IQXgDx4Nm5CBOxRWqkCRlZmbqgQceUNWqVRUXF6fBgwdr0qRJCgsLU3Z2ttXllcr7X262buyZWywbG/CV3LwCXXH/XH27aI8knbbTIEkOh6ECh6E7Ry7ROzM2lneJ5ep4dr563jlbP/9+QJKL+TCkvHxDNw1boKlfs12oSMjBMo5NDgaEggKHrn34R336w3ZJrrf7DkMa8vIyvTJpbXmXCAuRg4GNHCzj2OQgAhDHg2bkYGAjB8s4NjkYEDgeNCMHURwaqlB+fr4uu+wyfffdd3r99df12Wefafv27Ro2bJiaNGmiyMhIq0s8I4fD0LK1/1g2/q9/HLRsbMBXXp+6Tj8v31/qZyTc+/wv2rYn3cdVWef58au1fP0/KijlfNw2YhG3e6ogyMGyIwcDwzufbtK3C3ertI/GefT137VuyxHfFgW/QQ4GLnKw7MhBBCKOB83IwcBFDpYdORgYOB40IwdRHBqq0JgxY7R69WotWrRIgwYNUq9evTRlyhTt379frVu3trq8UtmyM00ZmXmWjb//n+Pa/w87yggcBQUO/e/jDXLnpvA2m03vfrrJd0VZKCe3QONmbCr1TqUkFTgMTfjiT98VBa8hB8uOHKz4DMPQmI/Wy507NtntNo2bEZjbfZiRg4GNHCw7chCBhuNBM3IwsJGDZUcOVnwcD5qRg3CFhmqQMwxDr732mm6//XZVr17d+fO6desqNDRUrVq10uHDh3XppZeqSZMmatGihW699Vbl5ORYWPXp1my2/myY1ZsOW10C4DVzftmrvX+7tzNc4DD03md/KjevwEdVWefLH3fqSJp72z2HQ3p7+kbxqHL/Rg56DzlYsS1acUBbdqa7dcBYUGDog1mblZll3T/eoHyQg4GLHPQechCBhONBM3IwcJGD3kMOVmwcD5qRg3DFZpDsQW3Dhg1q1qyZFi1apE6dOjl/vn//ftWsWVM//PCD2rZtq3Xr1qlz585yOBwaNGiQ2rVrp4cfftijMevWrau0tDRv/REkSbkxF+h4peuKfW3H7P5KiA0rcfmEuHBJUlpGbonvSzuWp3q9Pyn2tahDHyo8a1UpqgX8X05cV2Un9pFs7p93E7d3pEIKvPsdt1p2Qi/lxHeXbHa3l43f/aRshjXPXElISNDOnTstGbuiIAcLkYPIie2g7ORrPVo2dt+Lsuf/7eWK4E/IwcBFDhYiBwEzjgfNyMHARQ4WIgfB8aAZORgYfJGDXKEa5Pbu3StJqlq1qunnc+fOlSS1bt1aycnJ6ty5syQpJCRE7dq1065du8q30DMw3Lkfgc/wdULgMGx2SZ6db2PYQr1bjF8IFfMRmMhBbyIHKzRbqNw6Hdm0rPv/uIiKhhwMVOSgN5GDCBwcD56KHAxU5KA3kYMVGseDJuQgXOFvN8hVqlRJkrR161Y1btxYkpSZmalRo0apRo0aqlKliun92dnZmjRpkl5++WWPx/TF2XEffLlZtz69qNjXXJ05dbLUxTdKkpI6fehxDVMmTdC1Pet7vDzgT979dJPuem6JR8vu3rZBifERXq7IWq9MWqvHXv/NrWfmSFJIiE2HDu5UeFjg7VwGCnKwEDmIj779Szc+scCjZTetW66zqsV4uSL4E3IwcJGDhchBwIzjQTNyMHCRg4XIQXA8aEYOwhVOHQlyzZs3V926dfXwww/r66+/1syZM9W9e3dlZGSc9uB1h8OhW265RRdffLF69+5tTcEuNKgVZ3UJOru29TUA3nJl1zqyh7h3hqM9xKbu59cIyJ2Gq3vUdfu8tFC7TX271uHg2c+Rg95DDlZsl11UWxFh7h0ahITY1L55lYA7eMbpyMHARQ56DzmIQMLxoBk5GLjIQe8hBys2jgfNyEG4QkM1yIWHh+uzzz5TVFSU+vfvr5EjR2r48OFKTEw8bcfh3nvvVUhIiN544w1Lai1Jm3Mqycq7W0SEh6hZwyTrCgC8rEaVaF3To57s9tJ/sQochu4bkOLDqqzToFa8el1Yy62dqfwCQ/feEJjzEUjIQe8gByu+pPgIDbq8oVvbfYfD0P0D2c4FA3IwcJGD3kEOItBwPGhGDgYuctA7yMGKj+NBM3IQrtBQhdq1a6cVK1YoKytLq1atUrdu3bR582a1atXK+Z5HH31Uu3fv1pQpUxQS4n8fm/jYcDWum2DZ+C0bJ3PWIQLOk3e0Vpg9pFQ75XZ74Vlpl3eu4/vCLPLM3W0UEmIr3XyE2NStfQ11O7+G7wtDmZGDZUcOBobH/tVSURF2lebfCu12m1o2StJ13NYraJCDgYscLDtyEIGI40EzcjBwkYNlRw4GBo4HzchBFMf/EgCW++OPP+RwOJxnYq1fv14vv/yytm7dqvPOO0+tW7fWI488Ym2Rxeh7sXUbrCu7srFE4GnZOFlfjumhiDB7iWdk2UNsatYgUd++3VOhoYEbK+e3rKpPXr5YdrutxDOTQ0JsOrdpJX3xeg/ZrDxFFB4jB91HDgaGxvUS9O1bPRUVGXrG7X6DWnH6flwvRYTzDyfBghwMHuSg+8hBBCKOB83IweBBDrqPHAwMHA+akYMojs0wDHcfA4AA98477+jhhx9WRkaGX5515cq2Pelq2OdTefKJLsvD10PtNu2ee4OqV452f2CgAli18ZCeHrtS3y7cLZsKz7oyJOXnG4qPCdPt1zTRiLvbKC4m3OpSy8XSNX/rmXEr9cOve/9/PkJkyFB+vqGk+HDddV1TDb+jtaKjQq0uFR4iB91DDgae9X+lasTYlZr5407JMEzb/ZioUN16VWM9e8+5SuLZMEGJHAx85KB7yEEEOo4HzcjBwEcOuoccDDwcD5qRgzgZDVUElMvumaPvF+9xe7my7Dhc36u+Pnm5m9vLARXNzn0Z+mzuDv195LjCQkPUpF6Crr2kvqIig/NAcevudH0xb4f+Sc1WRLhdKQ0SdXWPegF9dh78HzkIb9p7MFMzftiuA4eyFBYaorNrx+v6nvUVEx1mdWnwA+Qg/BE5CPgOx4Nm5CD8ETkIb+J40IwchERDFQFmzZ+H1e6GWcovcO9j7emOQ1SEXWs+u0qNLHxOAQAARchBAEAwIwcBAMGMHAQA36o49y0ASqFVk0p66s42bi+XdixPacfy3F7uhf+0Y6cBAOA3yEEAQDAjBwEAwYwcBADf4gpVBJy8PIc63fKNflv3j0/H6XpeDc0ff6lCQlw/lBoAgPJGDgIAghk5CAAIZuQgAPgOV6gi4ISFheibty7ROfV9d4ZU63OSNfP17uw0AAD8DjkIAAhm5CAAIJiRgwDgOzRUEZCqJEdpwft91PqcZK+v+4KWVTR//GVKjI/w+roBAPAGchAAEMzIQQBAMCMHAcA3aKgiYFWtFKUlk6/Qgzc2k80LJ0yFhNj0xG2t9PP7fZScwE4DAMC/kYMAgGBGDgIAghk5CADexzNUERQWrzyge57/RWu3pHq0fNuUyho3/EKd17yKlysDAMD3yEEAQDAjBwEAwYwcBADvoKGKoGEYhhavPKixn2zUF/N3KDfPUeL7IyPsur5nfd3Tv6nat6gimzdO5wIAwCLkIAAgmJGDAIBgRg4CQNnRUEVQyskt0NotR7Riw2Ft2JqqzOP5stmkmKgwNTs7Ue2aVVazhkkKD7NbXSoAAF5HDgIAghk5CAAIZuQgAHiGhioAAAAAAAAAAAAAuBBidQEAAAAAAAAAAAAA4K9oqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALhAQxUAAAAAAAAAAAAAXKChCgAAAAAAAAAAAAAu0FAFAAAAAAAAAAAAABdoqAIAAAAAAAAAAACACzRUAQAAAAAAAAAAAMAFGqoAAAAAAAAAAAAA4AINVQAAAAAAAAAAAABwgYYqAAAAAAAAAAAAALjwf+qaNLHhVWRuAAAAAElFTkSuQmCC",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "compile_and_plot(U, prompt)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "46d42b16-2fcf-422a-a206-3eee667f4d4b",
+ "metadata": {},
+ "source": [
+ "#### Exercise 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c9824ae0-f3c6-4755-8a5b-aacb22678ec9",
+ "metadata": {},
+ "source": [
+ "A randomly generated unitary (from a random circuit). This unitary WAS NOT in the training set, it is new to the model!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "63c51c9b-638a-42c4-8029-9add147d2255",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "U = np.matrix([[ 0.70710678, 0. , 0. , 0. , 0.70710678, 0. , 0. , 0. ],\n",
+ " [ 0. , -0.70710678, 0. , 0. , 0. , -0.70710678, 0. , 0. ],\n",
+ " [-0.70710678, 0. , 0. , 0. , 0.70710678, 0. , 0. , 0. ],\n",
+ " [ 0. , 0.70710678, 0. , 0. , 0. , -0.70710678, 0. , 0. ],\n",
+ " [ 0. , 0. , 0.70710678, 0. , 0. , 0. , 0. , 0.70710678],\n",
+ " [ 0. , 0. , 0. , 0.70710678, 0. , 0. , 0.70710678, 0. ],\n",
+ " [ 0. , 0. , -0.70710678, 0. , 0. , 0. , 0. , 0.70710678],\n",
+ " [ 0. , 0. , 0. ,-0.70710678, 0. , 0. , 0.70710678, 0. ]], dtype=np.complex128)\n",
+ "\n",
+ "assert np.allclose(U.H@U, np.identity(2**num_of_qubits)) and np.allclose(U@U.H, np.identity(2**num_of_qubits)) #check if unitary"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5b5e50fd-da8d-47fb-aabb-92044aaba2ce",
+ "metadata": {},
+ "source": [
+ "Plot correct (exact) compiled circuits:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d6d5023f-b3f4-4cc6-81cb-eead8ffee190",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "10fa6f9af7dd44d1967ad187cd73ef34",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/20 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[INFO]: (generate_comp_tensors) Generated 512 tensors\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAB1QAAADtCAYAAAAIlUf/AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAXEgAAFxIBZ5/SUgAAkW1JREFUeJzs3XdYU9cfBvA3IYQ9xQmiIuJA3HvvvW21WrfVWu1Qa9WqdVDbWltr9efeq85q1br33ltw78GQvcm8vz8oUYRAAiED38/z9Kkkd3y5OTch973nHJEgCAKIiIiIiIiIiIiIiIiIiCgTsakLICIiIiIiIiIiIiIiIiIyVwxUiYiIiIiIiIiIiIiIiIi0YKBKRERERERERERERERERKQFA1UiIiIiIiIiIiIiIiIiIi0YqBIRERERERERERERERERacFAlYiIiIiIiIiIiIiIiIhICwaqRERERERERERERERERERaMFAlIiIiIiIiIiIiIiIiItKCgSoRERERERERERERERERkRYMVImIiIiIiIiIiIiIiIiItGCgSkRERERERERERERERESkBQNVIiIiIiIiIiIiIiIiIiItGKgSEREREREREREREREREWnBQJWIiIiIiIiIiIiIiIiISAsGqkbSrFkziEQiTJ8+Xa/nCpo1a9ZAJBKhdOnSpi7F4KZPnw6RSJThv27duum1jQ+pLRARGcro0aMzvf8OGjTI1GURERERERERERFRAWHwQFWlUmHr1q0YMGAA/Pz84OrqCqlUiiJFiqBRo0b4/vvvERQUZOjdEpkNa2trFC1aFEWLFoWbm1um59OD1/y42J8eyK5Zs8bg2y5dujREIhFOnDhh8G3rKz+P4aBBgwwaaltSrenSA6lnz54ZdLvPnj3TbNuS8fiYRnbnkrOzs+Z919bW1vjFERERERERERERUYFm0ED1woULqFSpEnr37o3169fj4cOHSE5OhpOTE6KionD27FnMmjULAQEB6NmzJ+RyuSF3b9a8vb1Rvnx5eHh4mLoUk3JxcUH58uVRtmxZU5eSbxo0aICwsDCEhYVh9erVpi6HiKjACwwM1Lzv9u7d29TlEBERERERERERUQEjMdSG/v33X3z88ceQyWQoVKgQxo0bh549e6JcuXIA0nquXr9+Hdu3b8eiRYuwY8cOJCcnQyqVGqoEs7Zu3TpTl2AW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GjZsiHPnziEwMBA//PCDQbc9ffp0zJgxA02bNjXokLqG0qpVKxw9ehRTpkzJl+DfXMycORM//PADGjVqhNOnT5u6nGxZUq1A2tCurq6uSElJwZEjR9CyZUtTl2RWeHyyZ6z3yEGDBmHt2rUYOHAg1qxZk2/7ISIiIiIiIiIiog+HQXuoEpmzkydPaubmbdeunYmrMS6ZTIZz587B3d0d48aNM3U5+erYsWMAgJ9//tnEleTMkmoFgAsXLiAlJQUtWrRgWJgFHh8iIiIiIiIiIiKigomBKhUoo0aNwpo1axAWFob0ztexsbFYunQpunbtCiBtaMvatWvnWw0nT56ESCSCSCRCt27d8m0/+kgPesaPHw8XFxdTl5NvZDIZzp8/j3bt2pl8ePCcWFKt6Y4fPw7AcgJgY+PxMZ3Ro0dr3nfXrl1r6nKIiIiIiIiIiIiogJGYugAiQzp79iwWLVoEALCxsYG9vT1iY2M14WqlSpWwbt26fNm3o6MjihYtmuExNze3fNmXvpo2bYoPYXRvGxsbpKSkmLoMnVhSremmTZuGadOmmboMs8XjYzrOzs6Z3n8L8s0jREREREREREREZFwMVKlACQwMxM6dO3Hx4kWEh4cjLi4Obm5u8Pf3R48ePTB8+HDY29vny77HjRtX4IfTJSIyR4GBgQgMDDR1GURERERERERERFRAiYQPodsaEREREREREREREREREVEucA5VIiIiIiIiIiIiIiIiIiItGKgSEREREREREREREREREWnBQJWIiIiIiIiIiIiIiIiISAsGqkREREREREREREREREREWjBQJSIiIiIiIiIiIiIiIiLSgoEqEREREREREREREREREZEWDFSJiIiIiIiIiIiIiIiIiLRgoEpEREREREREREREREREpAUDVSIiIiIiIiIiIiIiIiIiLRioEhERERERERERERERERFpwUCViIiIiIiIiIiIiIiIiEgLBqpERERERERERERERERERFowUCUiIiIiIiIiIiIiIiIi0oKBKhERERERERERERERERGRFgxUiYiIiIiIiIiIiIiIiIi0YKBKRERERERERERERERERKQFA1UiIiIiIiIiIiIiIiIiIi0YqBIRERERERERERERERERacFAlYiIiIiIiIiIiIiIiIhICwaqRERERERERERERERERERaMFAlIiIiIiIi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iCyY9YI5pi6hQAn6p6epSyhQ2D6JiIiIiIiIqCBgoEpEREREREREZK7cXIGYWNPs9wPlYQNEykyz37yub4l1p2/DUmsnIiKiDwMDVSIiIiIiIiIiM2W9apGpS/jg7G1j6gpyx1LrBiy7diIiIvowiE1dABFRbklEgIvU1FWkcZGm1UNvmcvrY6jXpqD9PkRERERERERERESkG/ZQJSKLJREDB9sASsHUlaQFXBLeopKBubw+hnptCtrvQ0RERERERERERES6YaBKRBZNIuYbmTkraK9PQft9iIiIiIiIiIiIiChn7ONCRERERERERERERERERKQFA1UiIiIiIiIiIiIiIiIiIi0YqBIRERERERERERERERERacFAlYiIiIiIiIiIiIiIiIhICwaqRERERERERERERERERERaSExdABERERERERERERERvVW37268Ck/K1314FXXAxY1d9FpH8e0kICo6nyr6TyF3WM/5OX/3QUSkJwaqRERERERERERERERm5FV4EkIikk1dRmZR0UB0jKmrICIyOg75S0RERERERERERERERESkBQNVIiIiIiIiIiIiIiIiIiItGKgSEREREREREREREREREWnBQJWIiIiIiIiIiIiIiIiISAsGqkREREREREREREREVCCt//chTlwOzXG5pdvu4dLtCCNURESWiIEqEREREREREREREVE2BEHAk1cJAACFUm3iakgfTg7W6Db6SLah6oJNdzBt0TU42EmMWFlGbGP6S0iSI+hRjKnLsBjm3MbCo1IAAPGJchNXoh0DVSIiIiIiIiIiIiIiLU5eCYVf57/RZPBeAEDZDlvx07IbEATBxJWRLrq1KI01PzbWGqou2HQHM5fdwNHl7eHv62aCCoHjl0Lg1+lvNP2vjfl23IZfVt5kG9MiVabEwMknUbbDNrT/4gAAoPGgPXj2OsHElZmvoxdfo1ynbRna2OzVt0zexl6EJqJ+v92o9ckuAECl7tvRb+IJpKQqTVpXVkx3uwWRhRCUSkBlBndrWIkhkvCUJSKigiclVQm5Qg0XJ2mOy8bGy2BrYwVbG34mahMZkwpXJykkkpzvnQyPSkERd1uIRCIjVEYEhEUmo5iHvcGXJSIiooJp1apVWL58OTw9PREWFoYTJ05AYuTrY9fuRKLn2KOIipVpHnsTnYrZa25BrlBhxqiaRq2H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+3c9nO9f950kXWj06BlNc5J+u3+KdeMbGzPj85cmbzxVhlD0RkuWLMn48eMrHQMAoNcqFAqZePjQTD+jqdMydYepp4xIoZDst3ddGdIBAACVplDtQxobGzNnzpy8/PLLKRQKOf/889PS0pKmpqaO9zz++ON59dVXM2rUqC6ft7m5uceedt64uTWT//HptGztvJSrKiQTxw7L1beU96nrwtatyZzvlPWanVm48OGKrFBta2vPNxe8nLsWvpbPnTEk6ze15tYffzgnTb8j/3L+sFTby6YivnrzC7lv6e/T2tb5+4r96vKju+dlUL0/+e9Xzc1GcwMA7M4Ff9v4jt7/yZN796pbAACg+/h0vQ+ZPHlyTjzxxDQ2Nmb48OGZMmVK1qxZk379tu81snbt2lx66aW5884739F5BwwY0KMrmy44rT03/3BlWraWboVqaqrztc9PzPjDh/ZYjl1p39KSbWW9YufGjh2XQm15945pa2vPJV9flLsWvrbTnqkTxz2TC2c/mn322Se3XDEx1dVVZc1F8vVBI/PgOXenta30706xWMiUjw3Pxz56TBmTAQAAAABA36EB6UOqqqpy6623Zv369XniiSey1157daxK2rJlS6ZOnZrrrrsuQ4bsfnxROV3xuSMypKEuNTW7vh3raqvzqVNG5JjDelfurjiof/1O436T5NkTTq1QmnduR5l6/R1P71SmJsm005ty4+xjc9s9q3L+rEfSurtlknS7MYc05OIpf5m62l2P8C5WF9IwqDbXXDauzMkAAAAAAKDvUKj2Yc8880xHoTp//vwsX748l112WSZNmpTFixdXON2b9tlrjyz73idy/JH7p1hdSP89qtOvpir1dcXU1Vbni+eNzn985dgUCsbKllNnZeoOStXKm/tP4zProsNSX1dMfV0x/WqqUrdHdYrVhRw9Zkh+8b3Tsv++/SsdEwAAAAAA3rMK7e3tnW9eyXvW5MmTM23atJx11lmVjtJlz7+4Pvf8/Dd5Y9PWfGBIfU47/kOp71/+PUN3aN/Skm1nnVux6/+p4h23lWXkb1fK1Le66Qfbx/+ee+pI438rZNPmbfnvn63Jmpc3pK62mJMnfjAjD9yz0rEAAAAAAOA9T6EKnXi/Fqpzbv5VZl63rEtl6g47StVZnxmTr15yRA8nBAAAAAAAKI9ipQMAvc/0M5vywaH1+eTJI7r8PdNOb8qeA/rluCOG9mAyAAAAAACA8rJCFTrxfl2hCgAAAAAAwHY2OgQAAAAAAAAoQaEKAAAAAAAAUIJCFQAAAAAAAKAEhSoAAAAAAABACQpVAAAAAAAAgBIUqgAAAAAAAAAlKFQBAAAAAAAASlCoAgAAAAAAAJSgUIXOVFclAwdWOsV2AwduzwMAAAAAAEDZFNrb29srHQJ6s/Zt25LWtkrHSKqrUigWK50CAAAAAADgfUWhCgAAAAAAAFCC+aEAAAAAAAAAJShUAQAAAAAAAEpQqAIAAAAAAACUoFAFAAAAAAAAKEGhCgAAAAAAAFCCQhUAAAAAAACgBIUqAAAAAAAAQAkKVQAAAAAAAIASFKoAAAAAAAAAJShUAQAAAAAAAEpQqAIAAAAAAACUoFAFAAAAAAAAKEGhCgAAAAAAAFCCQhUAAAAAAACgBIUqAAAAAAAAQAkKVQAAAAAAAIASFKoAAAAAAAAAJShUAQAAAAAAAEpQqAIAAAAAAACUoFAFAAAAAAAAKEGhCgAAAAAAAFCCQhUAAAAAAACgBIUqAAAAAAAAQAkKVQAAAAAAAIASFKoAAAAAAAAAJShUAQAAAAAAAEr4f4sJsx95ybv3AAAAAElFTkSuQmCC",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "compile_and_plot(U, prompt)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "60d35449-67b2-4ce0-9b5f-ac60670538e5",
+ "metadata": {},
+ "source": [
+ "## Transpile and discover"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "41449006-9c42-4109-bb54-95581f90679a",
+ "metadata": {},
+ "source": [
+ "Set an initial circuit we want to transpile, optimize or use for discovering sub-arrangements:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f1c82de5-3645-403b-a54e-5185860b0f7c",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "qc = QuantumCircuit(3)\n",
+ "qc.h(2)\n",
+ "qc.cx(0,1)\n",
+ "qc.cx(2,1)\n",
+ "qc.h(1)\n",
+ "qc.x(1)\n",
+ "qc.h(1)\n",
+ "qc.x(2)\n",
+ "\n",
+ "U = qi.Operator(qc).to_matrix() # the unitary of the circuit\n",
+ "\n",
+ "#-----------------------------------------\n",
+ "\n",
+ "fig = qc.draw(\"mpl\")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "db8fb061-d950-4727-a1b1-56ea46e48f1b",
+ "metadata": {},
+ "source": [
+ "We set different gate pool targets to see what the model gives us:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8a35405a-d98d-460b-9b0c-92d167266ad0",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[(\"Compile using: ['h', 'cx', 'z', 'x', 'ccx', 'swap']\", 'all'),\n",
+ " (\"Compile using: ['h', 'cx', 'z', 'ccx']\", 'no x, no swap'),\n",
+ " (\"Compile using: ['h', 'cx', 'x', 'ccx']\", 'no z, no swap'),\n",
+ " (\"Compile using: ['h', 'x', 'ccx']\", 'no cx, no z, no swap'),\n",
+ " (\"Compile using: ['h', 'z', 'x', 'ccx']\", 'no cx, no swap')]"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cs_1 = f\"Compile using: {[x for x in pipeline.gate_pool]}\", \"all\"\n",
+ "\n",
+ "cs_2 = \"Compile using: ['h', 'cx', 'z', 'ccx']\" , \"no x, no swap\" \n",
+ "cs_3 = \"Compile using: ['h', 'cx', 'x', 'ccx']\" , \"no z, no swap\" \n",
+ "cs_4 = \"Compile using: ['h', 'x', 'ccx']\" , \"no cx, no z, no swap\" \n",
+ "cs_5 = \"Compile using: ['h', 'z', 'x', 'ccx']\" , \"no cx, no swap\" \n",
+ "\n",
+ "cs = [cs_1, cs_2, cs_3, cs_4, cs_5]\n",
+ "cs"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "67e3aa4e-e7e0-4f7a-9a5c-14a70dd6b77b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "samples = 512\n",
+ "num_of_qubits = 3\n",
+ "max_gates = 12"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "adfbef8e-d91a-4ed3-b756-0e76c33674fa",
+ "metadata": {},
+ "source": [
+ "Compile with the different gate-sets and plot correct (exact) compiled circuits. Note, some of the circuits might look the same but the gate time-sequences are distinct. Qiskit reorders \"parallel\" gates to make smaller plots."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6268abb3-1965-4a0e-b5dc-0f3c8db8264a",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "ae50feaf2ed44d22a7ac3e578af52ad5",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/20 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[INFO]: (generate_comp_tensors) Generated 512 tensors\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "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nj17tGfPHs2cOVNLly5V06ZNbXrcfzNu3Di99dZblu99fHxkNpt1/PhxHT9+XCtXrlSLFi0KVLx877339Oqrr0qS5TU6cOCAxowZo/Xr12vlypVydXW9Yb/hw4frgw8+sHwfFBSk/fv369VXX9XSpUtveu2cOHFC1apVkyS7vI75kZmZqXvvvVeLFy+Wm5ubfHx8lJCQoC1btmjLli06fPiwxo4da1h8/5SUlKSBAwdq/vz5lmX+/v7KysqyXKcbN27Url27JEk1a9bUJ598ogEDBmjq1Km688471aNHjxxtDhkyREeOHFHZsmX19ddfG16oDAwMVGhoqNX1CQkJSkpKcmBE1k2ePFkbN27Uvn379PDDD2vDhg1yc/v77X7v3r16+eWXJUmvv/66brvtNqNCzeH48eO65557LAW+7KJnfHy85dq/cuWKPvrooxz7ZRfyY2JiJElubm4KDAzU2bNndfLkSa1cuVLlypVT7969JRXN87Ny5Ur17NlTqampkiR3d3f5+vrq9OnTOn36tLZu3SoPDw/L+1aHDh1kMpl06dIl7dmzR40aNbK0lZ6erk2bNlm+X7NmjRo0aJDjeGvWrJEkde7cOcfyKVOmWN57XF1dFRAQIEk6deqUTp06pblz5+qjjz7S888/f9Of59VXX9W7775reY1TUlK0Y8cO7dixQz///LMWL14sT0/PApwpAAAAAAAAALC9QvdQXbt2raXQV5i5MaOioizF1PDwcG3atElXr17V1atXtWHDBtWpU0cxMTHq1auXzpw5Y7WdJ554Qs2bN9e+ffsUFxenhIQETZ06Va6urtq4caPGjRunfv36qWfPnjpx4oRiY2MVHx+v119/XZL0/fffa9WqVVbb//TTT/XHH3/os88+U0JCgmJiYnTq1Ck98MADkqSxY8dqyZIlBT4P/zRjxgy9+uqr8vHx0X/+8x9FR0crMTFRSUlJ2rZtmzp37qzo6Gjdc889unr1qs2O+29Onjxpuan+8ssv68yZM0pMTFRCQoJiY2O1ceNGDRkyRP7+/vlu+6+//tJrr72m1157TRcuXFBMTIxiY2P15ptvSrp2zX311Vc37Dd37lxLMfXhhx/W6dOnFRMTo4SEBH3++ef6448/NG3atEL81I7zySefaN26dZo1a5bi4+MVFxenqKgo9ezZU5I0YcIEHT582OAo//bY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AADgnwpdUA0NDdX9998vSfruu+906NChPO+bPd9mtWrVVKpUKUnS6tWrrW6/atUqSdeGda1WrVpBQy6U9evXW50ndOPGjZahaK8fvrSg2rZtK0navn27oqOjC93eP2UPzRgVFWV1m61bt+arzQoVKuiVV17RsGHDJEkrV64seID50KxZM0nXhsHNnlfxn8xms9P3fCyKsues/emnnwq0/zPPPKNjx44pNDRUq1at0gsvvCDp2vykhw8ftlmctnD69Gn17NlTiYmJateunb788kujQ7qpPXv2WHrSvvnmm/rll18UFBSkyMhIvfTSSwZH97fKlStbhnnNz3WUfe1lZmbq119/zfdxi8r5scbf318PP/ywZsyYIUk6f/689uzZk2Ob7MJoUlKSVq1apT///FOlSpVSkyZNLNtc34t17dq1knIf7jc7V9SpU0c+Pj65xpSdp28mKipKR48ezXVdQkKCZb5XW+RRAAAAAAAAALCFQhdUJWnChAny8/NTcnKy7rvvvn/tfRITE6P777/f0vvRZDIpIiJCkjR9+vRce7icPXtW06dPlyT17dvXFmEXyKlTp/TVV1/dsDwrK0sTJ06UJIWHh6thw4aFPtaDDz6ooKAgpaen6+WXX7ZayM0+fmxsbL7ab9y4saRrw63mVlSNjIzUggULct03NTX1pm1n91rK7r1sb02aNFHNmjUlSe+8806u5+qbb76xW2/fkuyJJ56QJO3bt0/Tpk3L175fffWVvvvuO8s8pGXLltWkSZPUtGlTXb16VX379r1pj2NHunr1qu6++26dPXtW1atX18KFC286X6/RkpOT9dBDDyklJUXt2rXT66+/ripVqujzzz+XJH3++ef68ccfDY7yb9nX0Zdffpnn4eNr1qyp2267TZI0atQoy3zSeVGUzs+//Q5c30v0n++5fn5+atmypSRp3LhxysjIUIcOHXJsl108XbJkibZt25Zj2fUCAwMlSYcOHcp1ZINdu3bpu+++y8uPpPHjx+e6/IMPPlBycrLc3NwsD2sBAAAAAAAAgNFsUu2qXbu2Zs+eLQ8PD+3bt09NmjTRpEmTcsxDl5mZqZ07d+rNN99U9erVbyjUjRo1SkFBQbpy5Yq6dOmSo5fhb7/9pi5duig2NlalSpXSa6+9ZouwCyQwMFDPPPOMvvjiC8sN5aioKPXt29fSs2fChAk2OVZQUJA++ugjSdLcuXPVo0cPbd26VVlZWZKuFVEjIyP1wQcfqH79+vr555/z1X7Pnj3l5+en9PR09enTRwcPHpR0rZfn4sWL1aVLF/n6+ua676RJk9S9e3fNnj07x1DMqampmjdvnt577z1JUo8ePfL7YxeIyWTS2LFjJUnLly/Xo48+qrNnz0q6NqTxjBkz9PTTT1t65ebmxIkTMplMMplMGjNmjCPCdqhZs2ZZfr5169bZrN1OnTrpoYcekiQ999xzGjlyZI5r4tKlS/ryyy8tBbNsR44c0XPPPSdJeumll9S1a1dJkoeHh+bMmSNfX19t375do0aNylc89nodIyIi9NdffykoKEhLly5VSEhIodu05zX30ksvaf/+/QoKCtK3334rV1dXSdce1Mh+LZ566qmb9lDPzcCBAy0x29Lw4cNVq1Ytpaam6vbbb9cXX3yRo0B69OhRjRs3Tu+//36O/aZMmSIvLy8dPnxYbdu21bJly5Seni7pWt75888/NXjw4Bt6Thal8/P777+rUaNG+vDDDxUZGWnJAWazWb///rueeeYZSVLFihXVqFGjG/bPLo5mjziQ3SM1W6tWreTn56ft27crIyND/v7+at68+Q3t3HnnnXJxcdGVK1fUr18/y8NTaWlpmjdvnu688075+/v/688TGBior776Si+88IIuXbok6VrP1IkTJ2rcuHGSpGeffVbly5fP0/kBAAAAAAAAAHuzWffB3r17a82aNapZs6YuXbqk1157TbVq1ZKnp6dKly4tDw8PNWvWTOPHj1dcXJz69u2bo1hXsWJFLVq0SIGBgdq3b5/atm0rPz8/+fn5qV27doqMjFRQUJAWLVpkGRrSCEOGDFGLFi00aNAgBQQEqFSpUqpcubLmzZsnSRo9erTuvfdemx3v0Ucf1bRp0+Th4aFff/1VrVu3lo+Pj0JCQuTl5aXw8HANHz5cBw4cyPcN/MDAQH300UcymUzasmWL6tatq4CAAPn5+al3796qXLmy5eb2P2VlZWnZsmUaMGCAKlWqJB8fH5UuXVre3t6KiIhQXFyc6tWrp8mTJ9viNOTJww8/rBdffFGSNHv2bFWsWFGlSpVSQECAnnzySbVp00aDBw+WJHl5eTksrpJgxowZuu+++5SVlaV33nlHlSpVUmBgoIKCglSmTBk99dRTlmE8pWtF+759++rq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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "7f3c6b2a064f41f5b322224677a87ee7",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/20 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[INFO]: (generate_comp_tensors) Generated 512 tensors\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAABxkAAAE5CAYAAABMCZ5iAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAXEgAAFxIBZ5/SUgAAXJtJREFUeJzt3Xd8VFX+//H3pJGeUBMgEEASulQLClIEFVFAUAOrCMqqSFlUxMoqArKgu6CoKKBLFL8iiBRdVLqIXap0CBIIgYSWkEb6/P7gR3QkCbnJzNxJ5vV8PPJQ5t5zziczd3LuuZ97zrVYrVarAAAAAAAAAAAAAKCMPMwOAAAAAAAAAAAAAEDlQpIRAAAAAAAAAAAAgCEkGQEAAAAAAAAAAAAYQpIRAAAAAAAAAAAAgCEkGQEAAAAAAAAAAAAYQpIRAAAAAAAAAAAAgCEkGQEAAAAAAAAAAAAYQpIRAAAAAAAAAAAAgCEkGQEAAAAAAAAAAAAYQpIRAAAAAAAAAAAAgCEkGQEAAAAAAAAAAAAYQpIRAAAAAAAAAAAAgCEkGQEAAAAAAAAAAAAYQpIRAAAAAAAAAAAAgCEkGV1M9+7dZbFYNGnSJEPbqprY2FhZLBY1atTI7FDsbtKkSbJYLDY/AwYMMFSHOx0LwJU8/vjjl32nhg8fbnZYAAAAAAAAAFClmZZkLCgo0JIlS/TAAw8oOjpaoaGh8vHxUZ06ddSlSxc999xz2r17t1nhAQ7n7e2tsLAwhYWFqXr16pdtv5SMdESy5FKSMjY21u51N2rUSBaLRd98843d6zbKke/h8OHD7ZrorUyxmqG09yc4OLjou+Tr6+v84AAAAAAAAADADZmSZPzpp5/UsmVLxcTEaOHChTp06JCysrIUFBSks2fP6vvvv9f06dPVpk0bDRo0SLm5uWaEaYqGDRuqWbNmqlWrltmhmCokJETNmjXTVVddZXYoDnPDDTcoKSlJSUlJWrBggdnhAJXW5MmTi75LMTExZocDAAAAAAAAAG7By9kNfvHFF7rnnnuUk5OjmjVr6qmnntKgQYMUFRUl6eIMx+3bt+uzzz7TnDlztGzZMmVlZcnHx8fZoZriww8/NDsEl3DXXXfprrvuMjsMAAAAAAAAAAAAFMOpScZDhw7p/vvvV05Ojlq2bKnVq1crIiLCZh9PT0916tRJnTp10oQJE/TQQw85M0QAAAAAAAAAAAAAV+DU5VInTpyotLQ0+fr6avny5ZclGP+qRo0aWrFihUJCQi7blpSUpAkTJqhVq1YKCAhQQECAWrVqpaefflrJycnF1hcfHy+LxSKLxaL4+HgdPXpUDz/8sBo2bChfX19dddVVmjhxojIzM4vK7N69W/fff78aNGggX19fRUVFaerUqcrLyyu2jUvPups0aZJyc3M1ffp0XX311QoICFD16tXVu3dvffXVVyX+zn8uXx67d+/WI488oqioKPn7+yswMFBXX321XnjhBZ05c6ZcdV56nltpz4qLjY2VxWJRo0aNit2+evVqDRw4UBEREfLx8VFwcLCaNGmiW265Rf/+97917ty5Mtd36dls3bt3lyStX79effv2Ve3ateXr66sWLVro5ZdfVnZ2dqm/18qVK9WzZ0+FhoYqMDBQbdu21auvvqq8vLzL2nBlVqtV8+fP13XXXafg4GAFBQWpc+fO+uijj8wOrURr1qzR4MGDFRkZKT8/P9WoUUNXX321xo4dqx9//LFov7i4OAUHB8tisWjcuHHF1pWenq6oqChZLBbdeuutslqtzvo1inXp+1KWH1eyb98+jR49Wi1btlRQUJACAwPVrFkzDR48WJ999pkKCwuLLVeVP0sAAAAAAAAAQMmcNpMxOTlZS5culSTdd999io6OLnPZv16M37RpkwYMGKDU1FRJUkBAgCRp79692rt3r9577z19/vnn6tKlS4l1btu2TSNGjFBqaqqCg4OVn5+v33//Xa+88oq+/fZbrV+/XmvWrNG9996rrKwshYSEKDc3V3FxcfrnP/+p3bt365NPPimx/tzcXPXq1UubN2+Wl5eXAgMDlZqaqnXr1mndunV66aWXyp1ILMmrr76q5557rigZ4O/vr7y8PO3atUu7du3SggULtGrVKrVv396u7V7J5MmT9dJLLxX929/fX1arVUeOHNGRI0e0du1aderUqVwJvddee03PPPOMJBV9Rvv379ekSZO0adMmrV27Vp6enpeVe+qpp/Sf//yn6N+hoaHau3evnnnmGa1atarUYyc+Pl6NGzeWJId8jkYUFBTorrvu0sqVK+Xl5SV/f3+lp6frp59+0k8//aRDhw7p5ZdfNi2+v8rKytLw4cP16aefFr0WFBSkwsLCouN08+bN2rFjhySpadOmevvtt/XAAw9o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n1doaKjOnTunXr162cxG+/7779WrVy+lpqaqRo0aevbZZ532u/1VSEiIHnvsMc2fP79omdGEhAQNGTJEGzdulHQx6WoPoaGhev311yVJn3zyifr27auff/5ZhYWFki4mFvft26f//Oc/atWqlf73v/8Zqv/OO+9UYGCg8vLydO+99+rAgQOSLs4GXLlypXr16qWAgIBiy86YMUN9+vTRwoULbZaxzcnJ0ZIlS/Taa69Jkvr27Wv01y4Xi8Wil19+WZK0evVqDRs2TCdOnJB0cTnY999/X48++mjR7M3ixMfHy2KxyGKxaNKkSc4I26liY2OLfr9vvvnGbvX26NFDgwcPliSNGTNGzz33nM0xcebMGb333ntFCaxL4uLiNGbMGEnSE088oVtvvVWS5OPjo0WLFikgIEBbt27V888/bygeR32OMTEx2rlzp0JDQ7Vq1SrVqlWrwnU6KtaQkBC9+uqrkqTFixfrrrvu0o4dO4q2Z2VladWqVerfv7/S0tKKXne1zxIAAAAAAAAA4Hxezm5wwIAB2rBhg4YPH664uDg9++yzevbZZ+Xj46PAwEClpqYWJccsFouGDBlik8CKiIjQihUr1L9/f+3Zs0c33nhj0fbMzExJF5NuK1asUP369Z396xUZNWqUNm/erEceeUSjR49WYGCgUlJSirZPnDhRd911l93aGzZsmC5cuKBx48bpq6++0ldffaVq1aopMDBQaWlpysvLK9rXYrEYqjskJESvv/66Hn74Yf30009q3ry5goKClJOTo9zcXF1//fW6//77i5IHf1ZYWKivv/5aX3/9taSLMxf9/PyUkpJSNGuqRYsWmjlzZgV+e2P+9re/6ddff9Xrr7+uhQsX6qOPPlJoaKgyMjKUl5ennj176rrrrtO//vUv+fr6Oi0ud/D+++8rNzdXy5Yt0/Tp0zV9+nQFBwfLYrEUzVi+NHNWupjIHjJkiDIyMtS+fXv961//sqmvWbNmmj17tkaMGKGZM2fqlltu0S233OLU3+mvvvzyS0kXk9bdu3cvdd/iZmM726OPPqpz585p4sSJWrlypVauXFn0Pf3z3+NL/73EHT5LAAAAAAAAAEDJnDqT8ZIbb7xR+/fv16JFi3TfffepadOm8vX1VXp6umrUqKEuXbrohRde0L59+/Txxx/L29vbpny3bt20b98+jR8/Xi1atFBh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CElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgCElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgCElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgCElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgCElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgCElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgCElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgCElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgCElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgCElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgCElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgCElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgCElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgCElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgCElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgCElGAAAAAAAAAAAAAIaQZAQAAAAAAAAAAABgyP8DbWefjimqeTEAAAAASUVORK5CYII=",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "28dcd971b8b444e5bbd889d9011d0bc2",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/20 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[INFO]: (generate_comp_tensors) Generated 512 tensors\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "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s2fPavDgwerbt6/efvttHTx4UP7+/iooKNDPP/+sF154QXfffXfx9s2bN9eLL74oSXr++ef18ccflzjm2LFjtXfvXtWpU0evvvpqid8XnjZ79mwlJCQoNTVVt956q/Ly8pye37Ztm+6//35J0qOPPqpu3bpZUWYJ+/fvV4cOHTRq1Ch99tln+vXXXxUaGqq0tDRt2LBBkydP1pw5c0rs9/nnnys+Pl6TJ0/W999/r/T0dIWFheno0aNauXKlxo4dq9WrVxdvX1PPDwAAAAAAAADAmceajKtXr1ZBQYEkVelee4cOHdKAAQOUkpKiNm3a6Ouvv1ZGRoYyMjL01VdfqWXLljpz5oz69++vI0eOlHmcO+64Qx06dND27duVmpqq9PR0Pf/88/L19dW6des0bdo0DRs2TNdff72SkpKUkpKitLQ0Pfroo5Kkt99+W6tWrSrz+HPnztX333+vl156Senp6Tpz5owOHjyom266SZI0depUffjhh5U+D3/0yiuv6MEHH1RISIj++c9/6tixY8rMzNTZs2e1ceNG9erVS8eOHdMNN9ygjIwMl417PgcOHNDUqVMlSffff7+OHDmizMxMpaenKyUlRevWrdPYsWMVHh5u+tg//vijHnroIT300EM6efKkzpw5o5SUFD3++OOSCl9zixcvLrHfW2+9pWeeeUaSdOutt+rw4cM6c+aM0tPTtWDBAn3//feaN29eFb5rz3nxxRe1Zs0aLVq0SGlpaUpNTdWhQ4d0/fXXS5KmT5+uPXv2WFzlb/7617/qnXfekY+Pjx588EEdOnRIaWlpSklJ0a+//qo33nhDnTt3dtpn+PDhxU39kSNH6tixY8XPvfrqq3r99dflcDi0ePFi1a1b16PfT2mCg4P11ltvKTg4WN9++61To/fcuXO65ZZblJWVpS5duhS/Vq2Wlpamvn37atu2bYqOjtaCBQt05swZnT59WpmZmdq3b5+eeeYZxcXFOe23ZcsW9e/fX2fOnFFCQoI++eQTnT17VsnJyTp37pw2btyo8ePHO83vmnh+AAAAAAAAAAClMDxk0qRJhiRDknHkyJFKH2fMmDGGJCM6Oto4duxYiecPHTpkREREGJKMe+65x+m5/fv3F9eQkJBgZGVlldh/+PDhxdtcddVVRkFBQYltrrzySkOScccdd5R4rnv37sX7v/LKKyWez8/P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OHCgy8YbMWKE5s2bp4CAAH366ae6/PLLFRISotjYWAUFBalNmzaaMGGCdu7cabpBERkZqeeee04Oh0MbNmxQq1atFBERobCwMA0YMECNGzfWtGnTSt23oKBAK1as0F/+8hc1atRIISEhqlWrloKDgzVkyBClpqaqdevWmj17titOQ4Xceuut+sc//iFJeu2119SwYUPFxMQoIiJCd955pzp37qwxY8ZIkoKCgjxWlx288sorGjRokAoKCvTkk0+qUaNGioyMVFRUlGrXrq1Ro0Zp06ZNxdvn5uZq6NChysjIUPv27fWvf/3L6XgtW7bU888/L0maPXu2Pv/8c49+P6V57733iq+mXrhwoRo3buz0/PPPP69WrVrpzJkzGjZsmPLz860o00l4eLhWrFihNm3a6MyZMxo9erSio6NVq1YthYaGqnnz5po8eXKJe+G2a9dOH3zwgSIjI7Vt2zZdc801Cg0NVWxsrIKDg3XppZdq/vz5ysjIKN6nJp4fAAAAAAAAAEBJHm8ySoX3ENy5c6eWLFmiYcOGqXnz5goKClJ6erpiYmKKl89LTEzUm2++KX9/f6f9u3fvrsTERI0fP16tW7dWQUGBDMNQ69atNWHCBCUmJurKK6+04lsrFhAQoC+++EIzZsxQy5YtlZ2drcjISPXu3Vsff/yxnnjiCZePOWbMGO3atUsTJkxQ27ZtFRgYqJSUFIWFhaljx4669957tXLlykotI3vHHXfo448/Vq9evRQREaG8vDy1aNFCTz75pNauXVvmlYyjR4/WggULNHToUF144YUKCQlRWlqaoqOjdeWVV+q5557T5s2bVa9evap++6Y8++yzeu+999SjRw+Fh4crOztbrVu31qxZs/TZZ58pMzNTUuFVonZTtIxxWFiYEhISXHrskJAQvfvuu/rf//6ngQMH6oILLlBWVpb8/Px08cUX6+9//3vxkplS4ZXLGzduVGhoqJYsWaKAgIASx7z99ts1ePBgGYahv/zlLzp58mSFavn9cs2XX3551b85FV6tfOedd0oqnDM33XRTiW1CQkK0ZMkSBQYGat26daauaC6q+bLLLnNJvb/XrFkzbdmyRXPnzlWPHj0UHR2t9PR0RUVFqXPnznriiSdKvVfi1VdfrT179ujRRx9V+/btFRwcrMzMTDVo0EB9+/bV/Pnz1atXL0nuPz8AAAAA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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "6265ec7334ff4d5d99bba8f022ec088e",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/20 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[INFO]: (generate_comp_tensors) Generated 512 tensors\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAABxkAAADtCAYAAABj0cvvAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAXEgAAFxIBZ5/SUgAAUvVJREFUeJzt3Xd4VGXax/HfTDJJCAkkobeIAUIJIEgXEaQoigooFrCw6IrY3rWgYFn7uooUXftiAVHAhmADRARE6QIRAtIhSJWQQHpmMvP+kSUQUsgkM3NOZr6f6/KSZM6cc+d5Ts49d+5TLC6XyyUAAAAAAAAAAAAAKCer0QEAAAAAAAAAAAAAqFpoMgIAAAAAAAAAAABwC01GAAAAAAAAAAAAAG6hyQgAAAAAAAAAAADALTQZAQAAAAAAAAAAALiFJiMAAAAAAAAAAAAAt9BkBAAAAAAAAAAAAOAWmowAAAAAAAAAAAAA3EKTEQAAAAAAAAAAAIBbaDICAAAAAAAAAAAAcAtNRgAAAAAAAAAAAABuockIAAAAAAAAAAAAwC00GQEAAAAAAAAAAAC4hSYjAAAAAAAAAAAAALfQZIT69Okji8WiZ555xq3X/M20adNksVjUtGlTo0PxuGeeeUYWi6XIf0OGDHFrHYG0L6By5s6dW2x/88ffKwAAAAAAAAAIZAHVZMzPz9dnn32m2267TfHx8YqKilJISIjq1q2riy++WI899pg2b95sdJiA19hsNtWrV0/16tVTdHR0sddPNSP/9re/eXzbp5qU06ZN8/i6mzZtKovFoqVLl3psnUuXLvVac+xUQ7tPnz4eX7evlDU+YWFhhftZjRo1fB8cAAAAAAAAAMDrgo0OwFdWrVqlkSNHavv27YXfs9lsioyMVEpKin799Vf9+uuveumll3Tttddq1qxZCgkJMTBi34mNjVXLli1Vu3Zto0MxVM2aNdWyZUs1atTI6FC85qKLLvJoIw4oycCBA3X48GFJBQ3VUaNGGRwRAAAAAAAAAMDTAqLJ+M033+j6669Xbm6uatWqpbFjx+q6665TixYtJBVc4bhhwwZ9+eWXeuuttzRnzhxlZWUFTJPxo48+MjoEUxg6dKiGDh1qdBgAAAAAAAAAAACm5/dNxh07duiWW25Rbm6u2rRpo4ULF6px48ZFlgkKClLnzp3VuXNnPfLII7r99tsNihYAAAAAAAAAAAAwP79/JuOTTz6pkydPKiwsTF999VWxBuPZYmJiNHfuXNWsWbPYa4cPH9YjjzyihIQEVa9eXdWrV1dCQoIeffRRHTlypMT17d27VxaLRRaLRXv37tW+fft05513KjY2VmFhYWrWrJmefPJJZWZmFr5n8+bNuuWWW9SkSROFhYWpRYsWeuGFF2S320vcxqln3T3zzDPKy8vTSy+9pPbt26t69eqKjo7WgAEDNH/+/FJ/5jPfXxGbN2/W6NGj1aJFC4WHhysiIkLt27fXE088oWPHjlVonX/729/O+WzAU8+1K+2ZeQsXLtS1116rxo0bKyQkRDVq1FBcXJwuu+wyTZw4UcePHy/3+k49q/DUM/QWL16sQYMGqU6dOgoLC1Pr1q317LPPKicnp8yfa968eerbt6+ioqIUERGhCy64QBMmTJDdbi+2DTNzuVyaOnWqunXrpho1aigyMlI9evTQxx9/bHRohXJzc9WxY0dZLBZ16dKl1N+fG2+8URaLRQ0bNqzw/uoNKSkpeu6559StWzfFxMQoLCxMTZs21WWXXaa3335bJ06cKPF9W7du1b333qs2bdooMjJSERERatmypW666SZ9+eWXcjqdkqr++AAAAAAAAAAAjOXXTcYjR47oiy++kCTdfPPNio+PL/d7LRZLka+XLVum1q1ba+LEidqyZUth43DLli165ZVX1Lp1a/3yyy9lrnP9+vXq0KGD3nvvPZ04cUIOh0O7d+/Wv/71L11xxRWy2+367rvv1K1bN33yySdKT09XXl6edu7cqX/+85+69dZby1x/Xl6e+vfvr8cee0xbt25VSEiI0tLS9OOPP+rKK6+scBOxLBMmTNAFF1ygqVOnaufOnbJYLLLb7dq0aZNefPFFtW/fXhs2bPD4ds/lueee08CBA/XVV1/pwIEDstlscrlc2rNnjxYtWqRHHnlEv//+e4XW/corrxQ2bh0Oh/Ly8vTHH3/omWee0ZVXXqn8/PwS3zd27FgNGTJES5Ys0YkTJ2Sz2bRlyxaNGzdO/fv3L7XJIxVtVntjHt2Rn5+voUOHavTo0Vq/fr0sFosyMjK0atUq3XrrrXr66acNje+U0NBQzZo1S+Hh4Vq3bp0ef/zxYsu89957+uyzz2S1WjVjxgzTPJf0hx9+UIsWLfT0009rzZo1Sk9PV0REhA4ePKhFixbpnnvu0ZIlS4q97+WXX1bbtm311ltvaevWrXI4HAoNDdXOnTv16aefatiwYTp58qSkqj0+AAAAAAAAAADj+XWTccmSJYVX7VTmWXv79+/XkCFDlJaWpjZt2uiXX35RRkaGMjIy9PPPP6tly5ZKTU3V4MGDdeDAgVLXc8cdd6hTp05KSkrSiRMnlJ6erv/85z8KCgrS8uXL9dxzz+nmm2/W1Vdfrb179yotLU0nT57UE088IUn69NNP9eOPP5a6/rfeektr1qzRO++8o/T0dKWmpio5OVnDhg2TJD377LP6+uuvKzwOZ3v//fc1btw4hYeH61//+pcOHTqkzMxMZWVlad26derbt68OHTqka665RhkZGR7b7rns27dPzz77rCTpoYce0oEDB5SZman09HSlpaVp+fLluueeexQZGen2uhMTEzV+/HiNHz9eR48eVWpqqtLS0vTUU09JKtjnpk+fXux9s2fP1qRJkyRJI0aM0J9//qnU1FSlp6frv//9r9asWaO33367Ej+177z55ptaunSppk2bppMnT+rEiRPav3+/rr76aknSCy+8oB07dhgcZYFWrVrp9ddflyRNmjRJP/zwQ+Frf/zxh/7xj39Ikh599FH169fPkBjPtmHDBg0ePFipqalKSEjQ999/r6ysLB07dkzZ2dlat26dHn744WL779tvv63x48fL6XTqmmuu0YYNG5Sdna2UlBSlp6frhx9+0I033iir9fRhvyqODwAAAAAAAADAJFx+7Mknn3RJcklyHThwoMLrGTNmjEuSKzo62nXo0KFir+/fv99Vo0YNlyTXvffeW+S1PXv2FMaQkJDgysnJKfb+W2+9tXCZAQMGuJxOZ7FlevXq5ZLkuuOOO4q91rt378L3v//++8Vez8/Pd11yySWFMZT2/qeffrrcr508edIVFRXlkuRasGBBsfe5XC6X3W53derUySXJNWXKlBKXKc3IkSNdklwjR44sdZkPP/zQJcl13nnnFfn+p59+6pLkio+Pd2ubpa3P5XK5nn766cIxLmmcXC6X69prr3VJcvXv37/I951Op6t58+Zlzu+pbUty9e7du9jrZ+5HpW2/LKfiL2nd5XXmfvbTTz8Vez0nJ8fVsGFDlyTXCy+8UOHteMNNN93kkuSqV6+e68iRI66cnBzXBRdc4JLk6tq1qysvL8/oEAtdfPHF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Va9evcT3vvzyy7riiis0Y8aMIrexzc3N1WeffaZXXnlFkjRo0CB3f+wKsVgsevbZZyVJCxcu1MiRI3Xw4EFJBbeDff/993XXXXcVXr1Zkr1798pischiseiZZ57xRdg+NW3atMKfb+nSpR5bb25uroYPH66srCx169ZNzz33XOFrkyZNUtu2bXXy5EmNGDGi8Erf8mratKksFov69OnjsXgl6bXXXlNYWJh27Nihnj17asGCBbLb7ZIKjlVr167VmDFjCq+elgp+9ydMmCBJ+vTTTzV06FBt3Lix8PWsrCx99913Gjx4cJHGpTfHBwAAAAAAAADg3/y+yShJQ4YM0U8//aTmzZvr2LFjGj9+vFq0aKHQ0FDVqlVLISEhuvDCC/X888/rxIkTGj58eJEGVuPGjTV37lzVrFlTSUlJ6tmzpyIiIhQREaGLL75YW7duVVRUlObOnatGjRoZ9nPec8896ty5s0aPHq0aNWooJiZGsbGx+uyzzyRJTz75pIYOHeqx7Y0cOVJvv/22QkJCNH/+fHXv3l3h4eGqXbu2wsLC1KZNG40dO1Z//PGHLBaLW+uuWbOmXn31VVksFq1atUqtWrVSjRo1FBERoSFDhig2NrZIQ+RMTqdTCxYs0G233aYmTZooPDxctWrVUrVq1XTjjTfqxIkTat26tSZPnuyJYSiXESNGFF4tNmPGDDVu3FgxMTGqUaOG/v73v6tHjx4aM2aMJCksLMxncfm7Rx55RImJiYqMjNTMmTMLn/UpFYzz7NmzVa1aNa1atUpPP/20gZGe1qFDB82bN081a9bU5s2bdcUVV6h69eqqXbu2qlWrpq5du+rdd99VRkZGkffdddddevHFF2W1WjVv3jx17NixcN+PjIzUVVddpa+//rrwRACpao4PAAAAAAAAAMAcAqLJKBU8Q/CPP/7QrFmzdPPNN6t58+YKCwtTenq6YmJidPHFF+uJJ57Q1q1bNXPmTNlstiLv7927t7Zu3aqHH35YrVu3ltPplMvlUuvWrTV27Fht3bpVvXr1MuinKxASEqLFixfrxRdfVMuWLZWbm6uaNWuqX79++u677/T88897fJtjxozRtm3bNHbsWF1wwQUKDQ1VWlqaIiIi1LlzZ91///1atGhRhW4je8cdd+i7775T3759C28FGR8fr5deeknLli0r9UrG0aNH67///a+G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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "0298541f82a240129de94458bb7b67ff",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/20 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[INFO]: (generate_comp_tensors) Generated 512 tensors\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAABxkAAAEkCAYAAADzQY8nAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAXEgAAFxIBZ5/SUgAAg+FJREFUeJzs3Xd4FOXexvF7N5U0Qu+9BBKQLtJ7EURQQRBFUEQR9EUFFRUF7GI/KqAeFYUjyEFpIlUBQQTpSO+9BgjpyW523j9yiEaSkA27O0n2+7muXJDdKffOzM5M5jfPMxbDMAwBAAAAAAAAAAAAQC5ZzQ4AAAAAAAAAAAAAoGChyAgAAAAAAAAAAADAKRQZAQAAAAAAAAAAADiFIiMAAAAAAAAAAAAAp1BkBAAAAAAAAAAAAOAUiowAAAAAAAAAAAAAnEKREQAAAAAAAAAAAIBTKDICAAAAAAAAAAAAcApFRgAAAAAAAAAAAABOocgIAAAAAAAAAAAAwCkUGQEAAAAAAAAAAAA4hSIjAAAAAAAAAAAAAKdQZAQAAAAAAAAAAADgFIqMAAAAAAAAAAAAAJxCkbEAad++vSwWiyZMmODUe4XNtGnTZLFYVLVqVbOjuNyECRNksVgy/fTp08epaXjTtgDvMW/evGu+G4VxHwAAAAAAAAAABUW+LDKmpaVp9uzZuv/++1W7dm2Fh4fL399fpUuXVuvWrfXcc89p586dZscE3MbPz09lypRRmTJlVKxYsWvev1qMHDJkiMvnfbVIOW3aNJdPu2rVqrJYLFq1apVLpleQskrSqlWr3FYcu1p8b9++vcun7Sk5LZ/AwMCM70RYWJjnwwEAAAAAAAAAMvE1O8A/rV+/XoMHD9b+/fszXvPz81NoaKguXryo3377Tb/99pvefPNN3XnnnZo5c6b8/f1NTOw5lStXVkREhEqWLGl2FFMVLVpUERERqlChgtlR3KZly5YuLW4BBV337t119uxZSekF1QceeMDkRAAAAAAAAADg3fJVkXHhwoXq16+fUlJSVKJECY0ZM0Z33XWXatWqJSm9hePWrVv1/fffa/Lkyfrhhx+UmJjoNUXGb775xuwI+cIdd9yhO+64w+wYAAAAAAAAAAAAXivfFBkPHDig++67TykpKYqMjNTSpUtVsWLFTMP4+PioadOmatq0qZ5++mk9+OCDJqUFAAAAAAAAAAAAvFe+eSbjuHHjFBsbq8DAQM2dO/eaAuM/FS9eXPPmzVPRokWvee/s2bN6+umnFRUVpeDgYAUHBysqKkrPPPOMzp07l+X0jh49KovFIovFoqNHj+rYsWMaNmyYKleurMDAQNWoUUPjxo1TQkJCxjg7d+7Ufffdp0qVKikwMFC1atXSq6++KpvNluU8rj4/bsKECUpNTdWbb76pm266ScHBwSpWrJi6dOmixYsXZ/uZ/z5+XuzcuVMPP/ywatWqpaCgIIWEhOimm27SCy+8oOjo6DxNc8iQIdd9NuDVZ8Vl9xy6pUuX6s4771TFihXl7++vsLAwVa9eXV27dtU777yjS5cu5Xp6V59VePW5dD///LN69uypUqVKKTAwUHXr1tXEiROVnJyc4+eaP3++OnbsqPDwcIWEhKhBgwaaNGmSbDbbNfPIzwzD0Oeff67mzZsrLCxMoaGhatGihWbMmGF2tEyuPv/wej9mL/OUlBQ1atRIFotFzZo1y/a73r9/f1ksFpUvXz7P3y13uHjxol5++WU1b95cxYsXV2BgoKpWraquXbtqypQpunLlSpbj7dmzRyNHjlRkZKRCQ0MVEhKiiIgIDRgwQN9//70cDoekgr98AAAAAAAAAAC5ly+KjOfOndOcOXMkSffee69q166d63EtFkum31evXq26devqnXfe0e7duzOKE7t379bbb7+tunXrau3atTlOc8uWLWrYsKH+/e9/68qVK7Lb7Tp8+LBee+013XrrrbLZbFq0aJGaN2+u//znP4qLi1NqaqoOHjyoF198UYMGDcpx+qmpqercubOee+457dmzR/7+/oqJidGKFSvUo0ePPBcRczJp0iQ1aNBAn3/+uQ4ePCiLxSKbzaY///xTr7/+um666SZt3brV5fO9npdfflndu3fX3LlzderUKfn5+ckwDB05ckTLly/X008/rR07duRp2m+//XZG4dZutys1NVV79+7VhAkT1KNHD6WlpWU53pgxY9SnTx+tXLlSV65ckZ+fn3bv3q1nn31WnTt3zrZwImUuVrtjPTojLS1Nd9xxhx5++GFt2bJFFotF8fHxWr9+vQYNGqTx48ebmu/vSpUqpTJlymT74+ubPxpdBwQEaObMmQoKCtKmTZv0/PPPXzPMv//9b82ePVtWq1XTp0/PN89QXbZsmWrVqqXx48frjz/+UFxcnEJCQnT69GktX75cI0aM0MqVK68Z76233lK9evU0efJk7dmzR3a7XQEBATp48KC+++479e3bV7GxsZIK9vIBAAAAAAAAADgnXxQZV65cmdES5kaetXfixAn16dNHMTExioyM1Nq1axUfH6/4+Hj9+uuvioiI0OXLl9W7d2+dOnUq2+kMHTpUTZo00a5du3TlyhXFxcXpX//6l3x8fLRmzRq9/PLLuvfee9WrVy8dPXpUMTExio2N1QsvvCBJ+u6777RixYpspz958mT98ccfmjp1quLi4nT58mUdP35cffv2lSRNnDhRCxYsyPNy+KcvvvhCzz77rIKCgvTaa6/pzJkzSkhIUGJiojZt2qSOHTvqzJkzuv322xUfH++y+V7PsWPHNHHiREnSU089pVOnTikhIUFxcXGKiYnRmjVrNGLECIWGhjo97e3bt2vs2LEaO3aszp8/r8uXLysmJkYvvfSSpPRt7uuvv75mvFmzZundd9+VJA0cOFAnT57U5cuXFRcXp88++0x//PGHpkyZcgOf2nM++eQTrVq1StOmTVNsbKyuXLmiEydOqFevXpKkV199VQcOHDA5ZbqNGzfq7NmzWf589dVXMgxDktSzZ0+Tk0p16tTRRx99JEl69913tWzZsoz39u7dq1GjRkmSnnnmGXXq1MmUjP+0detW9e7dW5cvX1ZUVJR++uknJSYmKjo6WklJSdq0aZNGjx59zXdtypQpGjt2rBwOh26//XZt3bpVSUlJunjxouLi4rRs2TL1799fVutfh5KCuHwAAAAAAAAAAHlg5APjxo0zJBmSjFOnTuV5OsOHDzckGcWKFTPOnDlzzfsnTpwwwsLCDEnGyJEjM7135MiRjAxRUVFGcnLyNeMPGjQoY5guXboYDofjmmHatGljSDKGDh16zXvt2rXLGP+LL7645v20tDSjbdu2GRmyG3/8+PG5fi82NtYIDw83JBlLliy5ZjzDMAybzWY0adLEkGS8//77WQ6TncGDBxuSjMGDB2c7zFdffWVIMqpUqZLp9e+++86QZNSuXdupeWY3PcMwjPHjx2cs46yWk2EYxp133mlIMjp37pzpdYfDYdSsWTPH9Xt13pKMdu3aXfP+37ej7Oafk6v5s5p2bv19O/vll1+ueT85OdkoX768Icl49dVX8zwfT9i+fbsRGhpqSDKGDBlidpxM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8ODBjGHS0tK0detWvfTSS6pevfo1xavnn39e4eHhunTpkjp37pypNdpvv/2mzp07KyYmRsWLF9fYsWM99tn+qWjRonr00Uf1+eefZ3QzeuLECd1zzz1auXKlpPSiqyuEh4frgw8+kCTNmjVLPXv21IYNG+RwOCSlFxb37Nmjd999V1FRUfrxxx+dmn6vXr0UEhIim82mu+++W/v27ZOU3hpw/vz56ty5s4KDg7Mc96233tKtt96q6dOnZ+rGNiUlRbNnz9bbb78tSerZs6ezHztPLBaLJk6cKElaunSpBg8erNOnT0tK7w72iy++0COPPJLRejMrR48elcVikcVi0YQJEzwR26OmTZuW8flWrVrlkmlGR0erZ8+eio2NVePGjTVjxgyXFJbdkVVK3z7vueceJSYmqnnz5nr55Zcz3nv33XdVr149xcbGauDAgRmtknOratWqslgsat++vcvyStKHH36owMBAHThwQK1atdKSJUtks9kkpe9XN27cqOHDh2e09JbS91OTJk2SJH333Xe64447tG3btoz3ExMTtWjRIvXu3TtT4dKdywcAAAAAAAAAkH/4mh3g7/r06aNffvlFQ4YM0cGDBzV27FiNHTtW/v7+CgkJUUxMTEZxzGKx6J577slUwKpYsaLmzZun3r17a9euXWrVqlXG+1efRRYeHq558+apQoUKnv+A/zNixAitWbNGDz/8sEaOHKmQkBBdvnw54/1x48bpjjvucNn8Bg8erKSkJI0aNUqLFy/W4sWLFRAQoJCQEMXGxmYUG6T05eqMokWL6oMPPtCwYcO0fv161alTR6GhoUpJSVFqaqpuueUW3XfffVk+h83hcGjJkiVasmSJpPSWi0WKFNHly5czWk3VrVs34xl9njBw4EBt3LhRH3zwgaZPn64ZM2YoPDxc8fHxstls6tixo5o3b6433nhDgYGBHstVmO3cuTOjdejBgwdVo0aNbIdt2bJlti1jPeXpp5/W9u3bFRoaqm+//TbjuaSSFBgYqFmzZqlZs2Zav369xo8fr9dee83EtOkaNmyo+fPn6+6779bOnTt16623ys/PT2FhYZn2Ad27d8803iOPPKJLly5p3Lhxmj9/vubPn5/xPf37/vjqv1LBXD4AAAAAAAAAAOflm5aMV7Vq1Up79+7VzJkzde+996pmzZoKDAxUXFycihcvrtat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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "for c, note in cs: compile_and_plot(U, c)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "acee9cc1-5b36-45d3-9844-2dbb885d6bfe",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ef5f0dc2-ad3a-467a-89dd-36d296d458ac",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "genQC Version 0.2.0\n"
+ ]
+ }
+ ],
+ "source": [
+ "import genQC\n",
+ "print(\"genQC Version\", genQC.__version__)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "87bc40b7-b02b-415e-a679-9e02a5a09b0d",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "python3",
+ "language": "python",
+ "name": "python3"
+ },
+ "widgets": {
+ "application/vnd.jupyter.widget-state+json": {
+ "state": {
+ "0298541f82a240129de94458bb7b67ff": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_930ba28ac47a4bebbb4199ed948aa46e",
+ "IPY_MODEL_7bd54b72babc41f4b52d9ce220d9783a",
+ "IPY_MODEL_74e912f110f54b8aabb277c2e081bc0a"
+ ],
+ "layout": "IPY_MODEL_8aa9e67a45104804b6d81b5a4e49cbeb"
+ }
+ },
+ "0833094c16cc4cd19e9d443a52969b96": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_fa95db801d064294aa915bfbb4027f0a",
+ "style": "IPY_MODEL_d6583f6818654dc18afdec2988394f4c",
+ "value": " 20/20 [00:01<00:00, 20.01it/s]"
+ }
+ },
+ "098fcc9c1ce54e909728d59de93a23c5": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "0c02e2bbc3454fc7ba2621a251caa75b": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_593cf9c04ac044458f12412a98638089",
+ "IPY_MODEL_87d790fe47334753a509d39f80094803",
+ "IPY_MODEL_2ae50452f7644708aeefa0f5cfc2c3e4"
+ ],
+ "layout": "IPY_MODEL_3758caa7322c4ba3be6597629bff6a03"
+ }
+ },
+ "0cc3571558b343f89546b8bed66939e7": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "0da36d09da6e4de5b79deb2c032aae92": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "0e8677125e6c4781a376e79fd24d4d5b": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "101b609da5af4273be556966dfa19584": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "101cb6f917ec44bd8be5578f98a8f83d": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_eeaf80f66c6343028e7cd16a934594dc",
+ "max": 20,
+ "style": "IPY_MODEL_cca75f888b44402ab03bbe0ae81d7f34",
+ "value": 20
+ }
+ },
+ "10fa6f9af7dd44d1967ad187cd73ef34": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_a43177f8d01f4df8b02b9ee9dbcd9e7c",
+ "IPY_MODEL_9009d0fea9994869976a3182b421716c",
+ "IPY_MODEL_9f364abd328642a5aa25444788aa3232"
+ ],
+ "layout": "IPY_MODEL_ede127b1c05749f497fa9e8fb7dd7c3b"
+ }
+ },
+ "1c1b7331ec164ee799ce823c6ee36fe1": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "2029352e52364079b9787288eee4f9e0": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "2880b76772ff4e5d9b164084fcdc7fb1": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_2029352e52364079b9787288eee4f9e0",
+ "max": 20,
+ "style": "IPY_MODEL_40fbc3cac71c4f2fa1c17214f84617bd",
+ "value": 20
+ }
+ },
+ "28dcd971b8b444e5bbd889d9011d0bc2": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_d5da81c8e7994dcaa5b0a14020a94347",
+ "IPY_MODEL_101cb6f917ec44bd8be5578f98a8f83d",
+ "IPY_MODEL_e27ae8ec281941829bd41501d40bc571"
+ ],
+ "layout": "IPY_MODEL_aada31cfad3e421cb9dd0aa2c0ad375d"
+ }
+ },
+ "294fc42ccb3f449f9b9830d1a34dd30a": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "2ae50452f7644708aeefa0f5cfc2c3e4": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_e395dc9e66a040f0af05816f1f9ce04a",
+ "style": "IPY_MODEL_3fbf3156e96542e48b2df76562e4c971",
+ "value": " 20/20 [00:00<00:00, 20.57it/s]"
+ }
+ },
+ "2df62b119beb419b8c26577216d83be0": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "320705878bc04784a4154c5231654d98": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "346ea51f47d445ed92010a8e2f94d398": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "372c9c4dbf7549e087975d892ca0328e": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_e0e807e874db44688403db6e5c1881d9",
+ "style": "IPY_MODEL_a78b0378abfe430d90972e593af02ecb",
+ "value": " 20/20 [00:00<00:00, 19.86it/s]"
+ }
+ },
+ "37539ab59279428a991d3100f3013e10": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "3758caa7322c4ba3be6597629bff6a03": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "37c0fa9b5c124e6da733577779235e9b": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "3b1cd3f6a59d40e8a22389a13b2b4137": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "3c62ad0751bb42bb9abe7e86e51d9eb7": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_59a48eccc59f4179b29832b45b143b76",
+ "style": "IPY_MODEL_6dfbb417585945398f1f31fd640bf82d",
+ "value": "100%"
+ }
+ },
+ "3d288fade7994efb9dfbf8f605fa4e76": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_ed3d58d5aaec4b80867ef7335ec4a8ca",
+ "style": "IPY_MODEL_c3627eb30a46446fbad163e4d2019490",
+ "value": "100%"
+ }
+ },
+ "3e6160d9a9bb417992337401c1b06c24": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "3ea2b1fab3894d7ebd3a3802f7924889": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "3ec8d841fd054480bd46e9e55fef9226": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "3ee80340baae4a9981beefe75cc9beb5": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "3fbf3156e96542e48b2df76562e4c971": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "40fbc3cac71c4f2fa1c17214f84617bd": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "444d218d5d9e45b6ad5f5875c68f0ab5": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "4f5cae7061e442fb9b6fbb9839836d97": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "549cf32909e04601a27f9e4352df7730": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "54bf2bb5fccf49099a13ef9f24c5e62d": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "57141bac1651424cb0fd3a0ec434795f": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_ac25c36362ab428895d475d60bcf7b7a",
+ "style": "IPY_MODEL_ca20f304f28e439c8f4bd2b757d71277",
+ "value": "100%"
+ }
+ },
+ "593cf9c04ac044458f12412a98638089": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_1c1b7331ec164ee799ce823c6ee36fe1",
+ "style": "IPY_MODEL_bcfbae6e2de64373b5a8a67c1bea86ec",
+ "value": "100%"
+ }
+ },
+ "59a48eccc59f4179b29832b45b143b76": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "59e8e753c3c84c088c2b83247c4dfc4e": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "5e789e4091a945f0b900cf68ab373316": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "6265ec7334ff4d5d99bba8f022ec088e": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_3c62ad0751bb42bb9abe7e86e51d9eb7",
+ "IPY_MODEL_b0f97e6c275e4268b1308677ab3f9c7f",
+ "IPY_MODEL_eb969490152e41269a36ea75995056ac"
+ ],
+ "layout": "IPY_MODEL_444d218d5d9e45b6ad5f5875c68f0ab5"
+ }
+ },
+ "6749b88ab48941ecb6c367d988f75fa4": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_57141bac1651424cb0fd3a0ec434795f",
+ "IPY_MODEL_9348de1ac9d24267aad67d3c9d5df705",
+ "IPY_MODEL_0833094c16cc4cd19e9d443a52969b96"
+ ],
+ "layout": "IPY_MODEL_e2caee050c794c9abb75f4af955eed67"
+ }
+ },
+ "67e43b661028495aa9870a92902b3bed": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "68a90c9ed89145f89b0894b47d2ffbb8": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "6dfbb417585945398f1f31fd640bf82d": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "731ddd98742c429dbdf5c91a96fe15e2": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_3ee80340baae4a9981beefe75cc9beb5",
+ "max": 2,
+ "style": "IPY_MODEL_320705878bc04784a4154c5231654d98",
+ "value": 2
+ }
+ },
+ "74e912f110f54b8aabb277c2e081bc0a": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_3ea2b1fab3894d7ebd3a3802f7924889",
+ "style": "IPY_MODEL_3e6160d9a9bb417992337401c1b06c24",
+ "value": " 20/20 [00:00<00:00, 20.63it/s]"
+ }
+ },
+ "7803241950aa47dba5b4747c4f21d233": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_67e43b661028495aa9870a92902b3bed",
+ "style": "IPY_MODEL_af8f4ea420d049d598f3d8776496eec0",
+ "value": "Fetching 2 files: 100%"
+ }
+ },
+ "7bd54b72babc41f4b52d9ce220d9783a": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_e7870033150746d6963e19fb87c48f34",
+ "max": 20,
+ "style": "IPY_MODEL_dbbdc4d236274044b3d3b8b1d9441370",
+ "value": 20
+ }
+ },
+ "7f3c6b2a064f41f5b322224677a87ee7": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_3d288fade7994efb9dfbf8f605fa4e76",
+ "IPY_MODEL_2880b76772ff4e5d9b164084fcdc7fb1",
+ "IPY_MODEL_372c9c4dbf7549e087975d892ca0328e"
+ ],
+ "layout": "IPY_MODEL_82f72fad30054961ac1a5b3079b8e7b3"
+ }
+ },
+ "82f72fad30054961ac1a5b3079b8e7b3": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "87d790fe47334753a509d39f80094803": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_4f5cae7061e442fb9b6fbb9839836d97",
+ "max": 20,
+ "style": "IPY_MODEL_37539ab59279428a991d3100f3013e10",
+ "value": 20
+ }
+ },
+ "8aa9e67a45104804b6d81b5a4e49cbeb": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "9009d0fea9994869976a3182b421716c": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_54bf2bb5fccf49099a13ef9f24c5e62d",
+ "max": 20,
+ "style": "IPY_MODEL_3ec8d841fd054480bd46e9e55fef9226",
+ "value": 20
+ }
+ },
+ "930ba28ac47a4bebbb4199ed948aa46e": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_101b609da5af4273be556966dfa19584",
+ "style": "IPY_MODEL_549cf32909e04601a27f9e4352df7730",
+ "value": "100%"
+ }
+ },
+ "9348de1ac9d24267aad67d3c9d5df705": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_d66354d629094fb99fc24a31b7d87d3e",
+ "max": 20,
+ "style": "IPY_MODEL_ca038a8a22c24a37b6569fbe03be141b",
+ "value": 20
+ }
+ },
+ "9f364abd328642a5aa25444788aa3232": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_c92031f02c384325b845329c4500d3ed",
+ "style": "IPY_MODEL_294fc42ccb3f449f9b9830d1a34dd30a",
+ "value": " 20/20 [00:00<00:00, 20.59it/s]"
+ }
+ },
+ "a3bdd67c32774890a32b0da136583ed4": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_d8e9ad75f24d46c483e15c22b327ead0",
+ "style": "IPY_MODEL_de6f11dc1be84740821d0cd2e4b45548",
+ "value": " 2/2 [00:00<00:00, 285.55it/s]"
+ }
+ },
+ "a43177f8d01f4df8b02b9ee9dbcd9e7c": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_098fcc9c1ce54e909728d59de93a23c5",
+ "style": "IPY_MODEL_a6016c4347614ed7b47a85bab923e7f2",
+ "value": "100%"
+ }
+ },
+ "a6016c4347614ed7b47a85bab923e7f2": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "a78b0378abfe430d90972e593af02ecb": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "aada31cfad3e421cb9dd0aa2c0ad375d": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "ac25c36362ab428895d475d60bcf7b7a": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "ae50feaf2ed44d22a7ac3e578af52ad5": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_e14ae7f0142245b58a67c217d6f5656a",
+ "IPY_MODEL_d004d9c8e0f147d5b067683f299c5ba5",
+ "IPY_MODEL_eed4126ec40d41c2ba6eb8a3a7cdcabb"
+ ],
+ "layout": "IPY_MODEL_0cc3571558b343f89546b8bed66939e7"
+ }
+ },
+ "af8f4ea420d049d598f3d8776496eec0": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "b0f97e6c275e4268b1308677ab3f9c7f": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_ddba76dc375c40f7a04ea247fbb9b169",
+ "max": 20,
+ "style": "IPY_MODEL_37c0fa9b5c124e6da733577779235e9b",
+ "value": 20
+ }
+ },
+ "b3299358bf3a4fcabeefa50eb0c69ed9": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "bcfbae6e2de64373b5a8a67c1bea86ec": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "c3627eb30a46446fbad163e4d2019490": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "c599e663ed1540cf9bd7ef82f6b627fd": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "c92031f02c384325b845329c4500d3ed": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "ca038a8a22c24a37b6569fbe03be141b": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "ca20f304f28e439c8f4bd2b757d71277": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "cca75f888b44402ab03bbe0ae81d7f34": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "d004d9c8e0f147d5b067683f299c5ba5": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "bar_style": "success",
+ "layout": "IPY_MODEL_b3299358bf3a4fcabeefa50eb0c69ed9",
+ "max": 20,
+ "style": "IPY_MODEL_346ea51f47d445ed92010a8e2f94d398",
+ "value": 20
+ }
+ },
+ "d2a146d969be4226a7c2cf3ab8b960a1": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "d5da81c8e7994dcaa5b0a14020a94347": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_c599e663ed1540cf9bd7ef82f6b627fd",
+ "style": "IPY_MODEL_d76a1ce507424e278f0b4e853965b23d",
+ "value": "100%"
+ }
+ },
+ "d6583f6818654dc18afdec2988394f4c": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "d66354d629094fb99fc24a31b7d87d3e": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "d76a1ce507424e278f0b4e853965b23d": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "d8a1aeb16dc145e2bc2b877572d1937e": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "d8e9ad75f24d46c483e15c22b327ead0": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "dbbdc4d236274044b3d3b8b1d9441370": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "description_width": ""
+ }
+ },
+ "ddba76dc375c40f7a04ea247fbb9b169": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "de6f11dc1be84740821d0cd2e4b45548": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLStyleModel",
+ "state": {
+ "description_width": "",
+ "font_size": null,
+ "text_color": null
+ }
+ },
+ "e0e807e874db44688403db6e5c1881d9": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "e14ae7f0142245b58a67c217d6f5656a": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_0da36d09da6e4de5b79deb2c032aae92",
+ "style": "IPY_MODEL_0e8677125e6c4781a376e79fd24d4d5b",
+ "value": "100%"
+ }
+ },
+ "e27ae8ec281941829bd41501d40bc571": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_d2a146d969be4226a7c2cf3ab8b960a1",
+ "style": "IPY_MODEL_5e789e4091a945f0b900cf68ab373316",
+ "value": " 20/20 [00:00<00:00, 20.57it/s]"
+ }
+ },
+ "e2caee050c794c9abb75f4af955eed67": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "e395dc9e66a040f0af05816f1f9ce04a": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "e7870033150746d6963e19fb87c48f34": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "eb969490152e41269a36ea75995056ac": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_d8a1aeb16dc145e2bc2b877572d1937e",
+ "style": "IPY_MODEL_68a90c9ed89145f89b0894b47d2ffbb8",
+ "value": " 20/20 [00:00<00:00, 20.64it/s]"
+ }
+ },
+ "ed3d58d5aaec4b80867ef7335ec4a8ca": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "ede127b1c05749f497fa9e8fb7dd7c3b": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "eeaf80f66c6343028e7cd16a934594dc": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ },
+ "eed4126ec40d41c2ba6eb8a3a7cdcabb": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "layout": "IPY_MODEL_59e8e753c3c84c088c2b83247c4dfc4e",
+ "style": "IPY_MODEL_2df62b119beb419b8c26577216d83be0",
+ "value": " 20/20 [00:00<00:00, 19.86it/s]"
+ }
+ },
+ "f848c31412ce47d29d781c352a05bf26": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "2.0.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "children": [
+ "IPY_MODEL_7803241950aa47dba5b4747c4f21d233",
+ "IPY_MODEL_731ddd98742c429dbdf5c91a96fe15e2",
+ "IPY_MODEL_a3bdd67c32774890a32b0da136583ed4"
+ ],
+ "layout": "IPY_MODEL_3b1cd3f6a59d40e8a22389a13b2b4137"
+ }
+ },
+ "fa95db801d064294aa915bfbb4027f0a": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "2.0.0",
+ "model_name": "LayoutModel",
+ "state": {}
+ }
+ },
+ "version_major": 2,
+ "version_minor": 0
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/src/examples/3_dataset_and_fineTune.ipynb b/src/examples/Quantum circuit synthesis with diffusion models/3_dataset_and_fineTune.ipynb
similarity index 99%
rename from src/examples/3_dataset_and_fineTune.ipynb
rename to src/examples/Quantum circuit synthesis with diffusion models/3_dataset_and_fineTune.ipynb
index 459c84d..6b7e26a 100644
--- a/src/examples/3_dataset_and_fineTune.ipynb
+++ b/src/examples/Quantum circuit synthesis with diffusion models/3_dataset_and_fineTune.ipynb
@@ -1,5 +1,18 @@
{
"cells": [
+ {
+ "cell_type": "raw",
+ "id": "39698881-e18c-4ae1-b791-f7b5d9e6489a",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "categories:\n",
+ " - Entanglement generation\n",
+ " - Quantum circuits\n",
+ " - Training\n",
+ "---"
+ ]
+ },
{
"cell_type": "markdown",
"id": "69a855f1-55dd-482e-94f2-9ad02804be4d",
@@ -16,6 +29,18 @@
"In this notebook we create a (demo) 9-qubit dataset and fine-tune the model with it. Note, we use direct fine-tuning similar as you would train the model from scratch (with a higher learn-rate and larger dataset)."
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e37a2b43-48f2-437b-ac2b-486aa788a8c5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# NOTE: this notebook is designed for an old version of genQC! Please use ´pip install genQC==0.1.0 -q´\n",
+ "import genQC\n",
+ "assert genQC.__version__ in [\"0.1\", \"0.1.0\", \"0.1.1\"]"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -1613,6 +1638,14 @@
"import genQC\n",
"print(\"genQC Version\", genQC.__version__)"
]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e3a34a4a-5d62-4504-8010-2444ef49fa75",
+ "metadata": {},
+ "outputs": [],
+ "source": []
}
],
"metadata": {
diff --git a/src/examples/tutorials.qmd b/src/examples/tutorials.qmd
new file mode 100644
index 0000000..184b170
--- /dev/null
+++ b/src/examples/tutorials.qmd
@@ -0,0 +1,24 @@
+---
+html:
+ code-copy: false
+listing:
+ - id: tutorials-links
+ image-placeholder: "../webpage/assets/logo.png"
+ sort: false
+ type: grid
+ categories: true
+ sort-ui: [title]
+ filter-ui: [title]
+ contents: "*{.qmd,.ipynb}"
+---
+
+# Tutorials Overview
+
+#### Welcome to the `genQC` Tutorials.
+
+Here you can familiarize yourself with basics concepts and modules. Tutorials show how to reproduce some figures and results of the corresponding [research papers](../webpage/research.qmd).
+
+
+
+::: {#tutorials-links}
+:::
\ No newline at end of file
diff --git a/src/get_started.ipynb b/src/get_started.ipynb
new file mode 100644
index 0000000..82c4be5
--- /dev/null
+++ b/src/get_started.ipynb
@@ -0,0 +1,456 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# genQC · Generative Quantum Circuits\n",
+ "\n",
+ "> Generating quantum circuits with diffusion models"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
We develop a multi-modal generative diffusion model to compile quantum operations in parameterized quantum circuits.
+:::
+::: {.abstract}
+
Abstract
+
Efficiently compiling quantum operations remains a major bottleneck in scaling quantum computing. Today’s state-of-the-art methods achieve low compilation error by combining search algorithms with gradient-based parameter optimization, but they incur long runtimes and require multiple calls to quantum hardware or expensive classical simulations, making their scaling prohibitive. Recently, machine-learning models have emerged as an alternative, though they are currently restricted to discrete gate sets. Here, we introduce a multimodal denoising diffusion model that simultaneously generates a circuit’s structure and its continuous parameters for compiling a target unitary. It leverages two independent diffusion processes, one for discrete gate selection and one for parameter prediction. We benchmark the model over different experiments, analyzing the method’s accuracy across varying qubit counts, circuit depths, and proportions of parameterized gates. Finally, by exploiting its rapid circuit generation, we create large datasets of circuits for particular operations and use these to extract valuable heuristics that can help us discover new insights into quantum circuit synthesis.
We use a generative diffusion model to synthesize quantum circuits for entanglement generation and unitary compilation.
+:::
+::: {.abstract}
+
Abstract
+
Quantum computing has recently emerged as a transformative technology. Yet, its promised advantages rely on efficiently translating quantum operations into viable physical realizations. In this work, we use generative machine learning models, specifically denoising diffusion models (DMs), to facilitate this transformation. Leveraging text-conditioning, we steer the model to produce desired quantum operations within gate-based quantum circuits. Notably, DMs allow to sidestep during training the exponential overhead inherent in the classical simulation of quantum dynamics — a consistent bottleneck in preceding ML techniques. We demonstrate the model’s capabilities across two tasks: entanglement generation and unitary compilation. The model excels at generating new circuits and supports typical DM extensions such as masking and editing to, for instance, align the circuit generation to the constraints of the targeted quantum device. Given their flexibility and generalization abilities, we envision DMs as pivotal in quantum circuit synthesis, enhancing both practical applications but also insights into theoretical quantum computation.