From 04920cf8d84e35eb6271849c6c71d588347cb805 Mon Sep 17 00:00:00 2001 From: Yi Hong Teoh Date: Mon, 13 May 2024 10:30:17 -0400 Subject: [PATCH] Added documentation --- .github/workflows/python-publish.yml | 30 + README.md | 42 +- docs/architecture.md | 45 + docs/data.md | 17 + docs/index.md | 25 + docs/installation.md | 11 + docs/reference/data.md | 1 + docs/reference/models/graph.md | 1 + docs/reference/models/index.md | 3 + docs/reference/models/transformer.md | 1 + docs/reference/observables.md | 1 + docs/reference/training.md | 1 + docs/reference/utils.md | 3 + docs/resource/Generated_training_data.md | 3127 +++++++++++++++++ docs/resource/architectureV1.jpg | Bin 0 -> 98742 bytes docs/resource/architectureV1.tex | 181 + docs/resource/architectureV2.tex | 144 + docs/resource/architectureV3.tex | 171 + docs/usage.md | 18 + mkdocs.yaml | 124 + setup.cfg | 7 +- src/rydberggpt/data/dataclasses.py | 8 +- src/rydberggpt/data/graph_structures.py | 4 +- src/rydberggpt/data/rydberg_dataset.py | 4 +- src/rydberggpt/data/utils_graph.py | 12 +- .../models/graph_embedding/layers.py | 2 +- .../models/graph_embedding/models.py | 2 +- .../models/rydberg_decoder_wavefunction.py | 10 +- .../models/rydberg_encoder_decoder.py | 4 +- src/rydberggpt/models/transformer/layers.py | 4 +- src/rydberggpt/models/transformer/models.py | 12 +- src/rydberggpt/models/transformer/modules.py | 6 +- src/rydberggpt/models/transformer/utils.py | 4 +- src/rydberggpt/observables/rydberg_energy.py | 6 +- src/rydberggpt/tests/test_dataloader.py | 4 +- src/rydberggpt/training/loss.py | 4 +- src/rydberggpt/training/trainer.py | 6 +- src/rydberggpt/training/utils.py | 2 +- src/rydberggpt/utils.py | 15 +- src/rydberggpt/utils_ckpt.py | 18 +- 40 files changed, 3999 insertions(+), 81 deletions(-) create mode 100644 .github/workflows/python-publish.yml create mode 100644 docs/architecture.md create mode 100644 docs/data.md create mode 100644 docs/index.md create mode 100644 docs/installation.md create mode 100644 docs/reference/data.md create mode 100644 docs/reference/models/graph.md create mode 100644 docs/reference/models/index.md create mode 100644 docs/reference/models/transformer.md create mode 100644 docs/reference/observables.md create mode 100644 docs/reference/training.md create mode 100644 docs/reference/utils.md create mode 100644 docs/resource/Generated_training_data.md create mode 100644 docs/resource/architectureV1.jpg create mode 100644 docs/resource/architectureV1.tex create mode 100644 docs/resource/architectureV2.tex create mode 100644 docs/resource/architectureV3.tex create mode 100644 docs/usage.md create mode 100644 mkdocs.yaml diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 00000000..01c04ba6 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,30 @@ +name: ci +on: + push: + branches: + - master + - main +permissions: + contents: write +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Configure Git Credentials + run: | + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v4 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - run: python -m pip install -e . --no-deps + - run: pip install mkdocstrings pymdown-extensions mkdocs-material mkdocstrings-python + - run: mkdocs gh-deploy --force diff --git a/README.md b/README.md index b7d370d1..835cc94b 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,9 @@ The`config.yaml` is used to define the hyperparameters for : - Others ### Training -To train RydbergGPT locally, execute the `train.py` with : +To train RydbergGPT locally, execute the `main.py` with : ```bash -python train.py --config_name=config_small.yaml -``` -For the cluster: -```bash -.scripts/train.sh +python main.py --config_name=config_small.yaml ``` ## Installation @@ -41,18 +37,28 @@ Clone the repository using the following command : ```bash git clone https://github.com/PIQuIL/RydbergGPT ``` -Install with pip : + +Create a conda environment ```bash -cd RydbergGPT -pip install . +conda create --name rydberg_env python=3.11 ``` -if you prefer to use a containerized version, see `container/README.md`. - +and finally install via pip in developer mode: +```bash +cd RydbergGPT +pip install -e . +``` ## Documentation +Documentation is implemented with [MkDocs](https://www.mkdocs.org/), to deploy the documentation run the following commands: +```bash +# Install package with mkdocs dependencies +pip install .[docs] -Currently unavaiable +# Deploy the documentation server locally +mkdocs serve +``` +The documentation should then be available at http://localhost:8000. ## Architecture @@ -73,7 +79,7 @@ Here, $V_{ij}$ = blockade interaction strength between atoms $i$ and $j$, $R_b$ Vanilla transformer architecture taken from [Attention is All You Need](https://arxiv.org/pdf/1706.03762.pdf). -![Architecture](https://github.com/PIQuIL/RydbergGPT/blob/main/resources/architecture%20diagram.jpg) +![Architecture](https://github.com/PIQuIL/RydbergGPT/blob/main/docs/resource/architectureV1.jpg) ```math \begin{align} @@ -100,8 +106,8 @@ R_b &= [1.05, 1.15, 1.3] \\ ``` There are a total of `8 x 10 x 3 x 9 = 2160` configurations (see [table](https://github.com/PIQuIL/RydbergGPT/blob/main/resources/Generated_training_data.md)). -## Acknowledgements - + + + diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 00000000..b3168a09 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,45 @@ +# Architecture + +## Rydberg System +$$ +\hat{H}_{\mathrm{Rydberg}} = +\sum_{i < j} \frac{C_6}{\lVert \mathbf{r}_i - \mathbf{r}_j \rVert} \hat{n}_i \hat{n}_j - \delta \sum_{i} \hat{n}_i - \frac{\Omega}{2} \sum_{i} \hat{\sigma}_i^{(x)}, +$$ + +$$ +C_6 = \Omega \left( \frac{R_b}{a} \right)^6, \quad V_{ij} = \frac{a^6}{\lVert \mathbf{r}_i - \mathbf{r}_j \rVert^6} +$$ + +- $V_{ij}$ = blockade interaction between atoms $i$ and $j$ +- $a$ = Lattice spacing +- $R_b$ = Rydberg blockade radius +- $\mathbf{r}_i$ = the position of atom $i$ +- $\hat{n}_i$ = number operator at ion $i$ +- $\delta$ = detuning at atom $i$ +- $\Omega$ = Rabi frequency at atom $i$ + +## Transformer + +Vanilla transformer architecture taken from [Attention is All You Need](https://arxiv.org/pdf/1706.03762.pdf). + +![Architecture](resource/architectureV1.jpg) + +$$ +H_i = \mathrm{GraphNN}(\mathrm{edges} = V_{ij} \ ; \mathrm{nodes}=\Omega_i, \Delta_i, R_b, \beta) += \text{Hamiltonian parameters encoded in a sequence by a graph neural network,} +$$ + +$$ +\sigma_i = \text{one-hot encoding of measured spin of qubit $i$} +$$ + +$$ +P_i = P(\sigma_i | \sigma_{< i}) = \text{conditional probability distribution of spin $i$} +$$ + +$$ +i = \text{sequence index (either $T$ or $S$ axis shown in the architecture diagram).} +$$ + +The transformer encoder represents the Rydberg Hamiltonian with a sequence.
+The transformer decoder represents the corresponding ground state wavefunction. \ No newline at end of file diff --git a/docs/data.md b/docs/data.md new file mode 100644 index 00000000..541416fe --- /dev/null +++ b/docs/data.md @@ -0,0 +1,17 @@ +# Data + +Consider setting $\Omega = 1$ and varying the other Hamiltonian parameters independently : + +$$ +L = [5, 6, 11, 12, 15, 16, 19, 20] +$$ +$$ +\delta / \Omega = [-0.36, -0.13, 0.93, 1.05, 1.17, 1.29, 1.52, 1.76, 2.94, 3.17] +$$ +$$ +R_b / a = [1.05, 1.15, 1.3] +$$ +$$ +\beta \Omega = [0.5, 1, 2, 4, 8, 16, 32, 48, 64] +$$ +There are a total of `8 x 10 x 3 x 9 = 2160` configurations. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..05390444 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,25 @@ +# RydbergGPT +A large language model (LLM) for Rydberg atom array physics. + +## Acknowledgements + +We sincerely thank the authors of the following very helpful codebases we used when building this repository : + +- Transformer tutorials: + - [Annotated Transformer](https://github.com/harvardnlp/annotated-transformer/) + - [Illustrated Transformer](https://jalammar.github.io/illustrated-transformer/) +- Transformer quantum state: + - [Predicting Properties of Quantum Systems with Conditional Generative Models](https://github.com/PennyLaneAI/generative-quantum-states) + - [Transformer Quantum State](https://github.com/yuanhangzhang98/transformer_quantum_state) + + +## References + +```bib +@inproceedings{46201, +title = {Attention is All You Need}, +author = {Ashish Vaswani and Noam Shazeer and Niki Parmar and Jakob Uszkoreit and Llion Jones and Aidan N. Gomez and Lukasz Kaiser and Illia Polosukhin}, +year = {2017}, +URL = {https://arxiv.org/pdf/1706.03762.pdf} +} +``` diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 00000000..df76d1bf --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,11 @@ +# Installation + +Clone the repository using the following command : +```bash +git clone https://github.com/PIQuIL/RydbergGPT +``` +Install with pip : +```bash +cd RydbergGPT +pip install . +``` \ No newline at end of file diff --git a/docs/reference/data.md b/docs/reference/data.md new file mode 100644 index 00000000..e349ff1e --- /dev/null +++ b/docs/reference/data.md @@ -0,0 +1 @@ +::: rydberggpt.data diff --git a/docs/reference/models/graph.md b/docs/reference/models/graph.md new file mode 100644 index 00000000..dd009471 --- /dev/null +++ b/docs/reference/models/graph.md @@ -0,0 +1 @@ +::: rydberggpt.models.graph_embedding \ No newline at end of file diff --git a/docs/reference/models/index.md b/docs/reference/models/index.md new file mode 100644 index 00000000..f0b1dff3 --- /dev/null +++ b/docs/reference/models/index.md @@ -0,0 +1,3 @@ +::: rydberggpt.models.rydberg_decoder_wavefunction + +::: rydberggpt.models.rydberg_encoder_decoder \ No newline at end of file diff --git a/docs/reference/models/transformer.md b/docs/reference/models/transformer.md new file mode 100644 index 00000000..528c8fa1 --- /dev/null +++ b/docs/reference/models/transformer.md @@ -0,0 +1 @@ +::: rydberggpt.models.transformer \ No newline at end of file diff --git a/docs/reference/observables.md b/docs/reference/observables.md new file mode 100644 index 00000000..05d426bd --- /dev/null +++ b/docs/reference/observables.md @@ -0,0 +1 @@ +::: rydberggpt.observables diff --git a/docs/reference/training.md b/docs/reference/training.md new file mode 100644 index 00000000..6cbb8d44 --- /dev/null +++ b/docs/reference/training.md @@ -0,0 +1 @@ +::: rydberggpt.training \ No newline at end of file diff --git a/docs/reference/utils.md b/docs/reference/utils.md new file mode 100644 index 00000000..c09b4d78 --- /dev/null +++ b/docs/reference/utils.md @@ -0,0 +1,3 @@ +::: rydberggpt.utils + +::: rydberggpt.utils_ckpt \ No newline at end of file diff --git a/docs/resource/Generated_training_data.md b/docs/resource/Generated_training_data.md new file mode 100644 index 00000000..ef7a24ae --- /dev/null +++ b/docs/resource/Generated_training_data.md @@ -0,0 +1,3127 @@ +## size = 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
delta
-1.545-0.5453.9554.4554.9555.4556.4557.45512.45513.455
beta=0.5Rb1.05
1.15
1.3
beta=1Rb1.05
1.15
1.3
beta=2Rb1.05
1.15
1.3
beta=4Rb1.05
1.15
1.3
beta=8Rb1.05
1.15
1.3
beta=16Rb1.05
1.15
1.3
beta=32Rb1.05
1.15
1.3
beta=48Rb1.05
1.15
1.3
beta=64Rb1.05
1.15
1.3
+ +## size = 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
delta
-1.545-0.5453.9554.4554.9555.4556.4557.45512.45513.455
beta=0.5Rb1.05
1.15
1.3
beta=1Rb1.05
1.15
1.3
beta=2Rb1.05
1.15
1.3
beta=4Rb1.05
1.15
1.3
beta=8Rb1.05
1.15
1.3
beta=16Rb1.05
1.15
1.3
beta=32Rb1.05
1.15
1.3
beta=48Rb1.05
1.15
1.3
beta=64Rb1.05
1.15
1.3
+ +## size = 11 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
delta
-1.545-0.5453.9554.4554.9555.4556.4557.45512.45513.455
beta=0.5Rb1.05
1.15
1.3
beta=1Rb1.05
1.15
1.3
beta=2Rb1.05
1.15
1.3
beta=4Rb1.05
1.15
1.3
beta=8Rb1.05
1.15
1.3
beta=16Rb1.05
1.15
1.3
beta=32Rb1.05
1.15
1.3
beta=48Rb1.05
1.15
1.3
beta=64Rb1.05
1.15
1.3
+ +## size = 12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
delta
-1.545-0.5453.9554.4554.9555.4556.4557.45512.45513.455
beta=0.5Rb1.05
1.15
1.3
beta=1Rb1.05
1.15
1.3
beta=2Rb1.05
1.15
1.3
beta=4Rb1.05
1.15
1.3
beta=8Rb1.05
1.15
1.3
beta=16Rb1.05
1.15
1.3
beta=32Rb1.05
1.15
1.3
beta=48Rb1.05
1.15
1.3
beta=64Rb1.05
1.15
1.3
+ +## size = 15 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
delta
-1.545-0.5453.9554.4554.9555.4556.4557.45512.45513.455
beta=0.5Rb1.05
1.15
1.3
beta=1Rb1.05
1.15
1.3
beta=2Rb1.05
1.15
1.3
beta=4Rb1.05
1.15
1.3
beta=8Rb1.05
1.15
1.3
beta=16Rb1.05
1.15
1.3
beta=32Rb1.05
1.15
1.3
beta=48Rb1.05
1.15
1.3
beta=64Rb1.05
1.15
1.3
+ +## size = 16 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
delta
-1.545-0.5453.9554.4554.9555.4556.4557.45512.45513.455
beta=0.5Rb1.05
1.15
1.3
beta=1Rb1.05
1.15
1.3
beta=2Rb1.05
1.15
1.3
beta=4Rb1.05
1.15
1.3
beta=8Rb1.05
1.15
1.3
beta=16Rb1.05
1.15
1.3
beta=32Rb1.05
1.15
1.3
beta=48Rb1.05
1.15
1.3
beta=64Rb1.05
1.15
1.3
+ +## size = 19 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
delta
-1.545-0.5453.9554.4554.9555.4556.4557.45512.45513.455
beta=0.5Rb1.05
1.15
1.3
beta=1Rb1.05
1.15
1.3
beta=2Rb1.05
1.15
1.3
beta=4Rb1.05
1.15
1.3
beta=8Rb1.05
1.15
1.3
beta=16Rb1.05
1.15
1.3
beta=32Rb1.05
1.15
1.3
beta=48Rb1.05
1.15
1.3
beta=64Rb1.05
1.15
1.3
+ +## size = 20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
delta
-1.545-0.5453.9554.4554.9555.4556.4557.45512.45513.455
beta=0.5Rb1.05
1.15
1.3
beta=1Rb1.05
1.15
1.3
beta=2Rb1.05
1.15
1.3
beta=4Rb1.05
1.15
1.3
beta=8Rb1.05
1.15
1.3
beta=16Rb1.05
1.15
1.3
beta=32Rb1.05
1.15
1.3
beta=48Rb1.05
1.15
1.3
beta=64Rb1.05
1.15
1.3
diff --git a/docs/resource/architectureV1.jpg b/docs/resource/architectureV1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b92afd8fa840d640a0ae33617c9ba1b4e662e152 GIT binary patch literal 98742 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�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 literal 0 HcmV?d00001 diff --git a/docs/resource/architectureV1.tex b/docs/resource/architectureV1.tex new file mode 100644 index 00000000..ec8ca553 --- /dev/null +++ b/docs/resource/architectureV1.tex @@ -0,0 +1,181 @@ +\documentclass{standalone} +\usepackage{amsmath,amssymb,pgfmath,pgffor,tikz,xcolor,xifthen} +\usetikzlibrary{calc,decorations.pathmorphing,decorations.pathreplacing,patterns,backgrounds} + + +\begin{document} + +\def\x{20} +\def\xx{27} +\def\y{9} +\def\d{4} +\def\a{1} +\def\b{4} +\def\c{1} +\def\e{0.75} +\def\f{3} +\def\g{2} + +\begin{tikzpicture}[ + scale=1., + show background rectangle, + background rectangle/.style={fill=white}, + color=black,] + + \coordinate (encoderbox) at (0,0); + \coordinate (encoderbox_south) at ($(encoderbox) + (\x/2,-\y)$); + \coordinate (encoderbox_west) at ($(encoderbox) + (0,-\y/2)$); + \coordinate (encoderbox_east) at ($(encoderbox) + (\x,-\y/2)$); + + \draw [-,fill=teal!15] (encoderbox) --++ (0,-\y) --++ (\x,0) --++ (0,\y) --++ (-\x,0); + \node [anchor=south] (encoder) at ($(encoderbox) + (\x/2,0) + (0,0.5)$) {\huge Transformer Encoder}; + + \coordinate (encodermha) at ($(encoderbox_west) + (0.5*\x-1.5*\d-0.5,0) $); + \coordinate (encoderlayernorm1) at ($(encodermha) + (\d,0)$); + \coordinate (encoderfeedforward) at ($(encoderlayernorm1) + (\d,0)$); + \coordinate (encoderlayernorm2) at ($(encoderfeedforward) + (\d,0)$); + \coordinate (encoderblockmultiplier) at ($(encodermha) + (-\a,\b) + (3*\d+1+2*\a,0)$); + + \node [anchor=west] at (encoderblockmultiplier) {\Large $\times N_{\mathrm{block}}$}; + + \draw [dashed, fill=yellow!10] ($(encodermha) + (-\a,\b)$) --++ (3*\d+1+2*\a,0) --++ (0,-2*\b) --++ (-3*\d-1-2*\a,0) --++ (0,2*\b); + + \node [draw, rectangle, fill=red!10, minimum width=1.cm,anchor=west] at (encodermha) {\rotatebox{270}{\Large Multi Head Attention}}; + + \node [draw, rectangle, fill=blue!10, minimum width=1.cm,anchor=west] at (encoderlayernorm1) {\rotatebox{270}{\textwidth 1pt \Large Layer Normalization}}; + + \node [draw, rectangle, fill=green!10, minimum width=1.cm,anchor=west] at (encoderfeedforward) {\rotatebox{270}{\textwidth 1pt \Large Feedforward Layer}}; + + \node [draw, rectangle, fill=blue!10, minimum width=1.cm,anchor=west] at (encoderlayernorm2) {\rotatebox{270}{\textwidth 1pt \Large Layer Normalization}}; + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + \coordinate (decoderbox) at ($(encoderbox) + (0,-\y) + (0,-\f)$); + \coordinate (decoderbox_north) at ($(decoderbox) + (\xx/2,0)$); + \coordinate (decoderbox_west) at ($(decoderbox) + (0,-\y/2)$); + \coordinate (decoderbox_east) at ($(decoderbox) + (\xx,-\y/2)$); + + + \draw [-,fill=teal!15] (decoderbox) --++ (0,-\y) --++ (\xx,0) --++ (0,\y) --++ (-\xx,0); + \node [anchor=north] (decoder) at ($(decoderbox) + (\xx/2,-\y) + (0,-0.5)$) {\huge Transformer Decoder}; + + + \coordinate (decodermha) at ($(decoderbox_west) + (0.5*\xx-2.5*\d-0.5,0) $); + \coordinate (decoderlayernorm1) at ($(decodermha) + (\d,0)$); + \coordinate (encoderdecodermha) at ($(decoderlayernorm1) + (\d,0)$); + \coordinate (decoderlayernorm2) at ($(encoderdecodermha) + (\d,0)$); + \coordinate (decoderfeedforward) at ($(decoderlayernorm2) + (\d,0)$); + \coordinate (decoderlayernorm3) at ($(decoderfeedforward) + (\d,0)$); + \coordinate (decoderblockmultiplier) at ($(decodermha) + (-\a,\b) + (5*\d+1+2*\a,0)$); + + \draw [dashed, fill=yellow!10] ($(decodermha) + (-\a,\b)$) --++ (5*\d+1+2*\a,0) --++ (0,-2*\b) --++ (-5*\d-1-2*\a,0) --++ (0,2*\b); + + \node [anchor=west] at (decoderblockmultiplier) {\Large $\times N_{\mathrm{block}}$}; + + \node [draw, rectangle, fill=red!10, minimum width=1.cm,anchor=west] at (decodermha) {\rotatebox{270}{\Large Multi Head Attention (Causal)}}; + + \node [draw, rectangle, fill=red!10, minimum width=1.cm,anchor=west] at (encoderdecodermha) {\rotatebox{270}{\Large Multi Head Attention}}; + + \node [draw, rectangle, fill=green!10, minimum width=1.cm,anchor=west] at (decoderfeedforward) {\rotatebox{270}{\textwidth 1pt \Large Feedforward Layer}}; + + \node [draw, rectangle, fill=blue!10, minimum width=1.cm,anchor=west] at (decoderlayernorm1) {\rotatebox{270}{\textwidth 1pt \Large Layer Normalization}}; + + \node [draw, rectangle, fill=blue!10, minimum width=1.cm,anchor=west] at (decoderlayernorm2) {\rotatebox{270}{\textwidth 1pt \Large Layer Normalization}}; + + \node [draw, rectangle, fill=blue!10, minimum width=1.cm,anchor=west] at (decoderlayernorm3) {\rotatebox{270}{\textwidth 1pt \Large Layer Normalization}}; + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + \coordinate (encoderinput) at ($(encoderbox_west) + (-4,0)$); + \coordinate (encoderoutput) at ($(encoderbox_south) + (0,-1)$); + \coordinate (decoderinput) at ($(decoderbox_west) + (-4,0)$); + \coordinate (decoderoutput) at ($(decoderbox_east) + (2,0)$); + + + \draw [->,line width=2pt] (encoderbox_west) to (encodermha); + \draw [<-,line width=2pt] ($(encodermha) + (0,\c)$) --++ (-\c,0) --++ (0,-\c); + \draw [<-,line width=2pt] ($(encodermha) + (0,-\c)$) --++ (-\c,0) --++ (0,\c); + \draw [->,line width=2pt] ($(encodermha) + (1,0)$) to (encoderlayernorm1); + \draw [->,line width=2pt] ($(encoderlayernorm1) + (1,0)$) to (encoderfeedforward); + \draw [->,line width=2pt] ($(encoderfeedforward) + (1,0)$) to (encoderlayernorm2); + \draw [->,line width=2pt] ($(encoderlayernorm2) + (1,0)$) --++ (\a,0) --++ (0,-\b) --++ (-3*\d-1-2*\a,0) --++ (-\e*0.5*\x+\e*1.5*\d+\e*0.5+\e*\a,0) --++ (0, \b); + \draw [->,line width=2pt] ($(encoderlayernorm2) + (1,0)$) --++ (\a,0) --++ (0.5*\x-1.5*\d-0.5-\a,0); + \draw [->,line width=2pt] (encoderbox_east) --++ (1,0) --++ (0,-0.5*\y-1) -- (encoderoutput); + + + \draw [-,line width=2pt] ($(encoderbox_west) + (\e*0.5*\x-\e*1.5*\d-\e*0.5-\e*\a,0)$) --++ (0,\b) -| ($(encodermha) + (0.5*\d-0.5+1,0)$) node [at end, circle, fill=white, draw] {\Large \textbf +}; + \draw [-, line width=2pt] ($(encoderlayernorm1) + (0.5*\d-0.5+1,0)$) --++ (0,\b) -| ($(encoderfeedforward) + (0.5*\d-0.5+1,0)$) node [at end, circle, fill=white, draw] {\Large \textbf +}; + + + + \coordinate (encoderdecoderconnection) at ($(encoderbox_south) + (0,-\f)$); + + \draw [->,line width=2pt] (encoderoutput) to (encoderdecoderconnection); + \node [anchor=west] at ($.5*(encoderoutput) + .5*(encoderdecoderconnection)$) {\Large $X$ (shape: $[B,T,E]$)}; + + \draw [->,line width=2pt] (decoderbox_west) to (decodermha); + \draw [<-,line width=2pt] ($(decodermha) + (0,\c)$) --++ (-\c,0) --++ (0,-\c); + \draw [<-,line width=2pt] ($(decodermha) + (0,-\c)$) --++ (-\c,0) --++ (0,\c); + \draw [->,line width=2pt] ($(decodermha) + (1,0)$) to (decoderlayernorm1); + \draw [->,line width=2pt] ($(decoderlayernorm1) + (1,0) + (0,-\c)$) to ($(encoderdecodermha)+ (0,-\c)$); + \draw [->,line width=2pt] (encoderdecoderconnection) |- (encoderdecodermha); + \draw [<-,line width=2pt] ($(encoderdecodermha) + (0,\c)$) -| (encoderdecoderconnection); + \draw [->,line width=2pt] ($(encoderdecodermha) + (1,0)$) to (decoderlayernorm2); + \draw [->,line width=2pt] ($(decoderlayernorm2) + (1,0)$) to (decoderfeedforward); + \draw [->,line width=2pt] ($(decoderfeedforward) + (1,0)$) to (decoderlayernorm3); + \draw [->,line width=2pt] ($(decoderlayernorm3) + (1,0)$) to (decoderbox_east); + \draw [->,line width=2pt] ($(decoderlayernorm3) + (1,0)$) --++ (\a,0) --++ (0,-\b) --++ (-5*\d-1-2*\a,0) --++ (-\e*0.5*\xx+\e*2.5*\d+\e*0.5+\e*\a,0) --++ (0, \b); + + \draw [-,line width=2pt] ($(decoderbox_west) + (\e*0.5*\xx-\e*2.5*\d-\e*0.5-\e*\a,0)$) --++ (0,\b) -| ($(decodermha) + (0.5*\d-0.5+1,0)$) node [at end, circle, fill=white, draw] {\Large \textbf +}; + \draw [-, line width=2pt] ($(decoderlayernorm1) + (0.5*\d-0.5+1,-1)$) --++ (0,-\b+2) -| ($(encoderdecodermha) + (0.5*\d-0.5+1,0)$) node [at end, circle, fill=white, draw] {\Large \textbf +}; + \draw [-, line width=2pt] ($(decoderlayernorm2) + (0.5*\d-0.5+1,0)$) --++ (0,\b) -| ($(decoderfeedforward) + (0.5*\d-0.5+1,0)$) node [at end, circle, fill=white, draw] {\Large \textbf +}; + + \draw [->,line width=2pt] (decoderbox_east) to (decoderoutput); + + \node [anchor=north] at ($.5*(decoderbox_east)+.5*(decoderoutput)$) {\rotatebox{270}{\Large $Y$ (shape: $[B,S,E]$)}}; + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + \coordinate (dense) at (decoderoutput); + \coordinate (softmax) at ($(dense) + (\g,0)$); + \coordinate (prob) at ($(softmax) + (\g,0)$); + + \node [draw, rectangle, fill=orange!10, minimum width=1.cm,anchor=west] at (dense) {\rotatebox{270}{\Large Dense Layer}}; + \node [draw, rectangle, fill=purple!10, minimum width=1.cm,anchor=west] at (softmax) {\rotatebox{270}{\Large Softmax}}; + \node [anchor=west] at (prob) {\Large $P$ (shape: $[B,S,2]$)}; + + \draw [->,line width=2pt] ($(dense) + (1,0)$) to (softmax); + \draw [->,line width=2pt] ($(softmax) + (1,0)$) to (prob); + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + \coordinate (encoderemb) at ($(encoderinput) + (1,0)$); + \coordinate (decoderemb) at ($(decoderinput) + (1,0)$); + \coordinate (encoderposemb) at ($(encoderemb) + (2,0)$); + \coordinate (decoderposemb) at ($(decoderemb) + (2,0)$); + + + \draw [->,line width=2pt] (encoderinput) to (encoderbox_west); + \node [anchor=east] at (encoderinput) {\Large $H$ (shape: $[B,T,4]$)}; + \draw [->,line width=2pt] (decoderinput) to (decoderbox_west); + \node [anchor=east] at (decoderinput) {\Large $\sigma$ (shape: $[B,S,2]$)}; + + \node [draw, rectangle, fill=gray!10, minimum width=1.cm,anchor=west] at (encoderemb) {\rotatebox{270}{\Large Embedding Layer}}; + \node [draw, rectangle, fill=gray!10, minimum width=1.cm,anchor=west] at (decoderemb) {\rotatebox{270}{\Large Embedding Layer}}; + + + \node [circle, fill=white, draw, minimum size=0.8cm,line width=1.5] (encoderposembnode) at (encoderposemb) {}; + \draw[looseness=2,line width=1.5] (encoderposembnode.west) to[out=70,in =110] (encoderposembnode.center) to [out=-70,in=-110](encoderposembnode.east); + + \node [circle, fill=white, draw, minimum size=0.8cm,line width=1.5] (decoderposembnode) at (decoderposemb) {}; + \draw[looseness=2,line width=1.5] (decoderposembnode.west) to[out=70,in =110] (decoderposembnode.center) to [out=-70,in=-110](decoderposembnode.east); + + +\end{tikzpicture} + +\end{document} diff --git a/docs/resource/architectureV2.tex b/docs/resource/architectureV2.tex new file mode 100644 index 00000000..02c59dc9 --- /dev/null +++ b/docs/resource/architectureV2.tex @@ -0,0 +1,144 @@ +\documentclass{standalone} +\usepackage{amsmath,amssymb,pgfmath,pgffor,tikz,xcolor,xifthen} +\usetikzlibrary{calc,decorations.pathmorphing,decorations.pathreplacing,patterns,backgrounds} + + +\begin{document} + +% \def\x{20} +% \def\xx{27} +% \def\y{9} +% \def\d{4} +% \def\a{1} +% \def\b{4} +% \def\c{1} +% \def\e{0.75} +% \def\f{3} +% \def\g{2} + +\begin{tikzpicture} + +\def\L{4} +\def\Lm{3} +\foreach \i in {1,...,\L} { +\foreach \j in {1,...,\L} { + \node [circle,fill=magenta!15,draw,minimum size=0.65cm] (spin-\i-\j) at ($(\i,\j) + (0,0)$) {}; +} +} + +\foreach \i in {1,...,\Lm} { +\foreach \j in {1,...,\L} { + \pgfmathsetmacro\ip{int(\i+1)} + \pgfmathsetmacro\jp{int(\j+1)} + \draw [-, opacity=0.5, line width = 2] (spin-\i-\j) -- (spin-\ip-\j); +} +} + +\foreach \i in {1,...,\L} { +\foreach \j in {1,...,\Lm} { + \pgfmathsetmacro\ip{int(\i+1)} + \pgfmathsetmacro\jp{int(\j+1)} + \draw [-, opacity=0.5, line width = 2] (spin-\i-\j) -- (spin-\i-\jp); +} +} + +\foreach \i in {1,...,\Lm} { +\foreach \j in {1,...,\Lm} { + \pgfmathsetmacro\ip{int(\i+1)} + \pgfmathsetmacro\jp{int(\j+1)} + \draw [-, opacity=0.05, line width = 2] (spin-\i-\j) -- (spin-\ip-\jp); +} +} + +\foreach \i in {1,...,\Lm} { +\foreach \j in {2,...,\L} { + \pgfmathsetmacro\ip{int(\i+1)} + \pgfmathsetmacro\jm{int(\j-1)} + \draw [-, opacity=0.05, line width=2] (spin-\i-\j) -- (spin-\ip-\jm); +} +} + +\node [align=center] at (\L/2+0.5, 0 ) {\Large $\mathbf{x} = \{H(R_b, \Delta, \Omega), \beta \}$}; + +\coordinate (input1) at (\L/2+0.5,\L+0.5) ; + +\node [align=center,anchor=south, rectangle, fill=teal!10, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (enc_graphnn) at ($(input1.north) + (0,1)$) {GraphNN}; + +\node [align=center,anchor=south, rectangle, fill=orange!10, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (enc_posemb1) at ($(enc_graphnn.north) + (0,1)$) {PosEmb}; + +\node [align=center,anchor=south, rectangle, fill=green!10, draw, rounded corners=0.5cm, minimum height=5cm, minimum width=5cm] (tfenc) at ($(enc_posemb1.north) + (0,1)$) {}; +\node [anchor=east] at (tfenc.west) {\Large \rotatebox{90}{Transformer Encoder}}; + +\node [align=center,anchor=south, rectangle, fill=yellow!10, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (enc_mha_self) at ($(tfenc.south) + (0,1)$) {MHA (Self)}; +\node [align=center,anchor=south, rectangle, fill=cyan!10, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (enc_ffnn) at ($(enc_mha_self.north) + (0,1)$) {FFNN}; + +\draw [->,line width=2] (input1) -- (enc_graphnn); +\draw [->,line width=2] (enc_graphnn) -- (enc_posemb1); +\draw [->,line width=2] (enc_posemb1) -- (tfenc); +\draw [->,line width=2] (enc_mha_self) -- (enc_ffnn); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\foreach \i in {1,...,\L} { +\foreach \j in {1,...,\L} { + \pgfmathsetmacro\k{int(\i+\L*(\j-1))} + \pgfmathsetmacro\m{mod(\i+\j,2)} + \ifthenelse{\equal{\m}{0.0}}{ + \node [circle,fill=red!15,draw,minimum size=0.65cm] (spin2-\i-\j) at ($(\i,\j) + (6,0)$) {$\uparrow$}; + } + { + \node [circle,fill=blue!15,draw,minimum size=0.75cm] (spin2-\i-\j) at ($(\i,\j) + (6,0)$) {$\downarrow$}; + } +} +} + +\foreach \i in {1,...,\Lm} { +\foreach \j in {1,...,\L} { + \pgfmathsetmacro\ip{int(\i+1)} + \pgfmathsetmacro\jp{int(\j+1)} + \pgfmathsetmacro\k{mod(\j,2)} + \ifthenelse{\equal{\k}{1.0}}{ + \draw [->, opacity=0.5, line width = 2] (spin2-\i-\j) -- (spin2-\ip-\j); + } + { + \draw [<-, opacity=0.5, line width = 2] (spin2-\i-\j) -- (spin2-\ip-\j); + } +} +} + +\foreach \j in {1,...,\Lm} { + \pgfmathsetmacro\jp{int(\j+1)} + \pgfmathsetmacro\ip{int(1 + mod(\j,2)*\Lm)} + \draw [->, opacity=0.5, line width = 2] (spin2-\ip-\j) -- (spin2-\ip-\jp); +} + +\node [align=center] at ($(\L/2+0.5, 0) + (6,0)$) {\Large $\boldsymbol{\sigma} = \{ \sigma_1,\sigma_1,...,\sigma_N \}$}; + +\coordinate (input2) at ($(input1) + (6,0)$); + +\node [align=center,anchor=south, rectangle, fill=teal!10, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (dec_emb) at ($(input2.north) + (0,1)$) {Embedding}; + +\node [align=center,anchor=south, rectangle, fill=orange!10, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (dec_posemb) at ($(dec_emb.north) + (0,1)$) {PosEmb}; + +\node [align=center,anchor=south, rectangle, fill=green!10, draw, rounded corners=0.5cm, minimum height=7cm, minimum width=5cm] (tfdec) at ($(dec_posemb.north) + (0,1)$) {}; +\node [anchor=west] at (tfdec.east) {\Large \rotatebox{270}{Transformer Decoder}}; + +\node [align=center,anchor=south, rectangle, fill=yellow!10, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (dec_mha_selfcausal) at ($(tfdec.south) + (0,1)$) {MHA (Self, Causal)}; +\node [align=center,anchor=south, rectangle, fill=yellow!10, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (dec_mha_cross) at ($(dec_mha_selfcausal.north) + (0,1)$) {MHA (Cross)}; +\node [align=center,anchor=south, rectangle, fill=cyan!10, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (dec_ffnn) at ($(dec_mha_cross.north) + (0,1)$) {FFNN}; + + +\node (output2) at($(tfdec.north) + (0,1)$) {\Large $p_\theta(\boldsymbol{\sigma};\mathbf{x}) = \{ p_\theta(\sigma_1;\mathbf{x}), p_\theta(\sigma_2|\sigma_1;\mathbf{x}),...,p_\theta(\sigma_N|\sigma_{,line width=2] (input2) -- (dec_emb); +\draw [->,line width=2] (dec_emb) -- (dec_posemb); +\draw [->,line width=2] (dec_posemb) -- (tfdec); +\draw [->,line width=2] (tfdec) -- (output2); +\draw [->,line width=2] (dec_mha_selfcausal) -- (dec_mha_cross); +\draw [->,line width=2] (dec_mha_cross) -- (dec_ffnn); +\draw [->,line width=2] (tfenc.east |- dec_mha_cross.west) -- (dec_mha_cross.west); + + +\end{tikzpicture} + +\end{document} diff --git a/docs/resource/architectureV3.tex b/docs/resource/architectureV3.tex new file mode 100644 index 00000000..ed2b615b --- /dev/null +++ b/docs/resource/architectureV3.tex @@ -0,0 +1,171 @@ +\documentclass{standalone} +\usepackage{amsmath,amssymb,pgfmath,pgffor,tikz,xifthen} +\usepackage[prefix=M]{xcolor-material} +\usetikzlibrary{calc,decorations.pathmorphing,decorations.pathreplacing,patterns,backgrounds} + +\begin{document} + +\begin{tikzpicture} + +\node [anchor=north, rectangle, fill=MYellow50, draw, dashed, line width=2, rounded corners=0.5cm, minimum height=6cm, minimum width=15cm] (tfdec) at (6,5) {}; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\def\L{4} +\def\Lm{3} + +\foreach \i in {1,...,\L} { +\foreach \j in {1,...,\L} { + \node [circle,fill=MPurple200,draw,minimum size=0.65cm] (spin-\i-\j) at ($(\i,\j) + (0,0)$) {}; +} +} + +\foreach \i in {1,...,\Lm} {, +\foreach \j in {1,...,\L} { + \pgfmathsetmacro\ip{int(\i+1)} + \pgfmathsetmacro\jp{int(\j+1)} + \draw [-, opacity=0.5, line width = 2] (spin-\i-\j) -- (spin-\ip-\j); +} +} + +\foreach \i in {1,...,\L} { +\foreach \j in {1,...,\Lm} { + \pgfmathsetmacro\ip{int(\i+1)} + \pgfmathsetmacro\jp{int(\j+1)} + \draw [-, opacity=0.5, line width = 2] (spin-\i-\j) -- (spin-\i-\jp); +} +} + +\foreach \i in {1,...,\Lm} { +\foreach \j in {1,...,\Lm} { + \pgfmathsetmacro\ip{int(\i+1)} + \pgfmathsetmacro\jp{int(\j+1)} + \draw [-, opacity=0.05, line width = 2] (spin-\i-\j) -- (spin-\ip-\jp); +} +} + +\foreach \i in {1,...,\Lm} { +\foreach \j in {2,...,\L} { + \pgfmathsetmacro\ip{int(\i+1)} + \pgfmathsetmacro\jm{int(\j-1)} + \draw [-, opacity=0.05, line width=2] (spin-\i-\j) -- (spin-\ip-\jm); +} +} + +% \draw [-stealth, line width=2, color=MGreen600] (1,2) --++ (0,1) node [midway, left] {\Large $a$}; +\draw [dashed, color=MIndigo800, line width=2] (2,2) circle (1.15cm); +\draw [-stealth, line width=2, color=MIndigo800, rotate around={345:(2,2)}] (2,2) --++ (0,1.15) node [midway, right] {\LARGE $R_b$}; + +\node [anchor=north] at (\L/2+0.5, 0.5 ) { +\LARGE +$ +\mathbf{x} = \{ \Omega, \delta/\Omega, R_b/a, \mathbf{V}, \beta / \Omega \} +$ +}; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\foreach \i in {1,...,\L} { +\foreach \j in {1,...,\L} { + \pgfmathsetmacro\k{int(\i+\L*(\j-1))} + \pgfmathsetmacro\m{mod(\i+\j,2)} + \ifthenelse{\equal{\m}{0.0}}{ + \node [circle,fill=MRed200,draw,minimum size=0.65cm] (spin2-\i-\j) at ($(\i,\j) + (7,0)$) {$\uparrow$}; + } + { + \node [circle,fill=MBlue200,draw,minimum size=0.65cm] (spin2-\i-\j) at ($(\i,\j) + (7,0)$) {$\downarrow$}; + } +} +} + +\foreach \i in {1,...,\Lm} { +\foreach \j in {1,...,\L} { + \pgfmathsetmacro\ip{int(\i+1)} + \pgfmathsetmacro\jp{int(\j+1)} + \pgfmathsetmacro\k{mod(\j,2)} + \ifthenelse{\equal{\k}{1.0}}{ + \draw [-stealth, line width = 2] (spin2-\i-\j) -- (spin2-\ip-\j); + } + { + \draw [stealth-, line width = 2] (spin2-\i-\j) -- (spin2-\ip-\j); + } +} +} + +\foreach \j in {1,...,\Lm} { + \pgfmathsetmacro\jp{int(\j+1)} + \pgfmathsetmacro\ip{int(1 + mod(\j,2)*\Lm)} + \draw [-stealth, line width = 2] (spin2-\ip-\j) -- (spin2-\ip-\jp); +} + +\node [anchor=north] at ($(\L/2+0.5, 0.5) + (7,0)$) {\LARGE $\boldsymbol{\sigma} = \{ \sigma_1,\sigma_1,...,\sigma_N \}$}; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\coordinate (input1) at (\L/2+0.5,\L+0.5) ; + +\node [align=center,anchor=south, rectangle, fill=MTeal100, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (enc_graphnn) at ($(input1.north) + (0,1)$) {GraphNN}; + +\node [align=center,anchor=south, rectangle, fill=MOrange100, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (enc_posemb1) at ($(enc_graphnn.north) + (0,1)$) {PosEmb}; + +\node [align=center,anchor=south, rectangle, fill=MLightGreen300, draw, rounded corners=0.5cm, minimum height=5cm, minimum width=5cm] (tfenc) at ($(enc_posemb1.north) + (0,1)$) {}; +\node [anchor=east] at (tfenc.west) {\Large \rotatebox{90}{Transformer Encoder}}; + +\node [align=center,anchor=south, rectangle, fill=MYellow100, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (enc_mha_self) at ($(tfenc.south) + (0,1)$) {MHA (Self)}; +\node [align=center,anchor=south, rectangle, fill=MCyan100, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (enc_ffnn) at ($(enc_mha_self.north) + (0,1)$) {FFNN}; + +\draw [-stealth,line width=2] (input1) -- (enc_graphnn); +\draw [-stealth,line width=2] (enc_graphnn) -- (enc_posemb1); +\draw [-stealth,line width=2] (enc_posemb1) -- (tfenc); +\draw [-stealth,line width=2] (enc_mha_self) -- (enc_ffnn); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +\coordinate (input2) at ($(input1) + (7,0)$); + +\node [align=center,anchor=south, rectangle, fill=MTeal100, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (dec_emb) at ($(input2.north) + (0,1)$) {Embedding}; + +\node [align=center,anchor=south, rectangle, fill=MOrange100, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (dec_posemb) at ($(dec_emb.north) + (0,1)$) {PosEmb}; + +\node [align=center,anchor=south, rectangle, fill=MLightGreen300, draw, rounded corners=0.5cm, minimum height=7cm, minimum width=5cm] (tfdec) at ($(dec_posemb.north) + (0,1)$) {}; +\node [anchor=west] at (tfdec.east) {\Large \rotatebox{270}{Transformer Decoder}}; + +\node [align=center,anchor=south, rectangle, fill=MYellow100, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (dec_mha_selfcausal) at ($(tfdec.south) + (0,1)$) {MHA (Self, Causal)}; +\node [align=center,anchor=south, rectangle, fill=MYellow100, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (dec_mha_cross) at ($(dec_mha_selfcausal.north) + (0,1)$) {MHA (Cross)}; +\node [align=center,anchor=south, rectangle, fill=MCyan100, draw, rounded corners=0.5cm, minimum height=1cm, minimum width=4cm] (dec_ffnn) at ($(dec_mha_cross.north) + (0,1)$) {FFNN}; + + +\node (output2) at($(tfdec.north) + (0,1)$) {\Large $p_\theta(\boldsymbol{\sigma};\mathbf{x}) = \{ p_\theta(\sigma_1;\mathbf{x}), p_\theta(\sigma_2|\sigma_1;\mathbf{x}),...,p_\theta(\sigma_N|\sigma_{ Batch: batch (List[Batch]): A list of Batch objects to be collated. Returns: - Batch: A single Batch object containing the collated data. + (Batch): A single Batch object containing the collated data. """ graph_batch = PyGBatch.from_data_list([b.graph for b in batch]) diff --git a/src/rydberggpt/data/graph_structures.py b/src/rydberggpt/data/graph_structures.py index 92d99cca..a26ce145 100644 --- a/src/rydberggpt/data/graph_structures.py +++ b/src/rydberggpt/data/graph_structures.py @@ -12,7 +12,7 @@ def get_graph(config: BaseGraph) -> nx.Graph: config (BaseGraph): The graph configuration, an instance of a subclass of the BaseGraph dataclass. Returns: - nx.Graph: The generated graph based on the configuration. + (nx.Graph): The generated graph based on the configuration. Raises: NotImplementedError: If the graph name provided in the configuration is not implemented. @@ -35,7 +35,7 @@ def generate_grid_graph(n_rows: int, n_cols: int) -> nx.Graph: n_cols (int): The number of columns in the grid. Returns: - nx.Graph: The generated grid graph with node positions and edge weights. + (nx.Graph): The generated grid graph with node positions and edge weights. """ # Create an empty graph diff --git a/src/rydberggpt/data/rydberg_dataset.py b/src/rydberggpt/data/rydberg_dataset.py index 7ecb31c5..9a0a7eb2 100644 --- a/src/rydberggpt/data/rydberg_dataset.py +++ b/src/rydberggpt/data/rydberg_dataset.py @@ -21,7 +21,7 @@ def get_rydberg_dataloader( num_workers: int = 0, data_path: str = "dataset", buffer_size: int = 4, -) -> Tuple[DataLoader, DataLoader]: +) -> DataLoader: # Tutorial how to use datapipes with Dataloader # https://pytorch.org/data/main/dp_tutorial.html datapipe = build_datapipes( @@ -55,7 +55,7 @@ def build_datapipes(root_dir: str, batch_size: int, buffer_size: int): buffer_size (int): The buffer size to use when buffering data into batches. Returns: - IterDataPipe: The final data pipe containing batches of processed data. + (IterDataPipe): The final data pipe containing batches of processed data. """ file_lister = FileLister([root_dir], recursive=True) config_dp, dataset_dp, graph_dp = file_lister.demux( diff --git a/src/rydberggpt/data/utils_graph.py b/src/rydberggpt/data/utils_graph.py index f1fce423..1a6624f9 100644 --- a/src/rydberggpt/data/utils_graph.py +++ b/src/rydberggpt/data/utils_graph.py @@ -16,7 +16,7 @@ def pyg_graph_data(config, graph_data): config_data (Dict): The configuration data for the graph. Returns: - PyG Data: The graph as a PyG Data object. + (Data): The graph as a PyG Data object. """ node_features = torch.tensor( @@ -41,7 +41,7 @@ def networkx_to_pyg_data(graph: nx.Graph, node_features: torch.Tensor) -> Data: graph: NetworkX graph object. Returns: - A PyTorch Geometric Data object representing the input graph. + (Data): A PyTorch Geometric Data object representing the input graph. """ x = node_features.repeat(len(graph.nodes()), 1) @@ -68,7 +68,7 @@ def batch_pyg_data(data_list: List[Data]) -> Data: data_list: List of PyTorch Geometric Data objects. Returns: - A single batched Data object containing all input Data objects. + (Data): A single batched Data object containing all input Data objects. """ batched_data = PyGBatch.from_data_list(data_list) return batched_data @@ -82,7 +82,7 @@ def graph_to_dict(graph: nx.Graph) -> Dict: graph: NetworkX graph object. Returns: - A dictionary representing the NetworkX graph. + (Dict): A dictionary representing the NetworkX graph. """ graph_dict = nx.node_link_data(graph) return graph_dict @@ -108,7 +108,7 @@ def read_graph_from_json(file_path: str) -> Dict: file_path: Path to the JSON file to read. Returns: - A dictionary representing a NetworkX graph. + (Dict): A dictionary representing a NetworkX graph. """ with open(file_path, "r") as f: graph_dict = json.load(f) @@ -123,7 +123,7 @@ def dict_to_graph(graph_dict: Dict) -> nx.Graph: graph_dict: Dictionary representing a NetworkX graph. Returns: - NetworkX graph object. + (nx.Graph): NetworkX graph object. """ graph = nx.node_link_graph(graph_dict) return graph diff --git a/src/rydberggpt/models/graph_embedding/layers.py b/src/rydberggpt/models/graph_embedding/layers.py index 4df91818..7b527ab0 100644 --- a/src/rydberggpt/models/graph_embedding/layers.py +++ b/src/rydberggpt/models/graph_embedding/layers.py @@ -32,7 +32,7 @@ def forward( edge_attr (OptTensor): Edge feature matrix. Returns: - torch.Tensor: The output tensor after passing through the GraphLayer. + (torch.Tensor): The output tensor after passing through the GraphLayer. """ x = self.graph_layer(x, edge_index, edge_attr) x = F.relu(self.norm(x)) diff --git a/src/rydberggpt/models/graph_embedding/models.py b/src/rydberggpt/models/graph_embedding/models.py index 641fed40..282f0d22 100644 --- a/src/rydberggpt/models/graph_embedding/models.py +++ b/src/rydberggpt/models/graph_embedding/models.py @@ -58,7 +58,7 @@ def forward(self, data: Data) -> Tensor: data (Data): The input graph data. Returns: - Tensor: The output tensor with reshaped dimensions. + (Tensor): The output tensor with reshaped dimensions. """ # [..., num_features], [2, ...] [...] x, edge_index, edge_attr = data.x, data.edge_index, data.edge_attr diff --git a/src/rydberggpt/models/rydberg_decoder_wavefunction.py b/src/rydberggpt/models/rydberg_decoder_wavefunction.py index 115f9886..8fb8ec7d 100644 --- a/src/rydberggpt/models/rydberg_decoder_wavefunction.py +++ b/src/rydberggpt/models/rydberg_decoder_wavefunction.py @@ -66,7 +66,7 @@ def from_rydberg_encoder_decoder(cls, cond: Batch, model: RydbergEncoderDecoder) model (RydbergEncoderDecoder): The model used to generate a RydbergDecoderWavefunction. Returns: - RydbergDecoderWavefunction: The wavefunction taken from a trained RydergEncoderDecoder model for the groundstate of the Hamiltonian/graph specified by cond. + (RydbergDecoderWavefunction): The wavefunction taken from a trained RydergEncoderDecoder model for the groundstate of the Hamiltonian/graph specified by cond. """ return cls( @@ -89,7 +89,7 @@ def get_log_probs(self, x: torch.Tensor): x (torch.Tensor): The input tensor. Returns: - torch.Tensor: The log probabilities. + (torch.Tensor): The log probabilities. """ assert ( @@ -127,7 +127,7 @@ def get_samples( verbose (bool, optional): A flag indicating whether to print sampling progress. Defaults to True, Returns: - torch.Tensor: A tensor containing the generated samples. The shape of the tensor is (batch_size, num_atoms, 2) for one-hot encoding format, and (batch_size, num_atoms) for label format. The samples are padded according to the number of nodes in each graph within `cond`. + (torch.Tensor): A tensor containing the generated samples. The shape of the tensor is (batch_size, num_atoms, 2) for one-hot encoding format, and (batch_size, num_atoms) for label format. The samples are padded according to the number of nodes in each graph within `cond`. """ if verbose: print("") @@ -179,7 +179,7 @@ def get_x_magnetization( samples (torch.Tensor): Samples drawn from model based on cond. Returns: - torch.Tensor: A tensor containing the estimated x magnetization of each sample. + (torch.Tensor): A tensor containing the estimated x magnetization of each sample. """ # Create all possible states achievable by a single spin flip @@ -213,7 +213,7 @@ def get_rydberg_energy( undo_sample_path_args (tuple): Additional arguments for undo_sample_path. Returns: - torch.Tensor: A tensor containing the estimated energy of each sample alongside its decomposition into terms. + (torch.Tensor): A tensor containing the estimated energy of each sample alongside its decomposition into terms. """ samples = samples diff --git a/src/rydberggpt/models/rydberg_encoder_decoder.py b/src/rydberggpt/models/rydberg_encoder_decoder.py index 6ca193c1..dc047d3f 100644 --- a/src/rydberggpt/models/rydberg_encoder_decoder.py +++ b/src/rydberggpt/models/rydberg_encoder_decoder.py @@ -97,7 +97,7 @@ def get_log_probs(self, x: torch.Tensor, cond: Batch): cond (Batch): The conditional graph structure. Returns: - torch.Tensor: The log probabilities. + (torch.Tensor): The log probabilities. """ if not hasattr(cond, "num_graphs"): @@ -140,7 +140,7 @@ def get_samples( in one-hot encoding format. If False, the samples are returned in label format. Defaults to True. Returns: - torch.Tensor: A tensor containing the generated samples. The shape of the tensor is (batch_size, num_atoms, 2) for one-hot encoding format, and (batch_size, num_atoms) for label format. The samples are padded according to the number of nodes in each graph within `cond`. + (torch.Tensor): A tensor containing the generated samples. The shape of the tensor is (batch_size, num_atoms, 2) for one-hot encoding format, and (batch_size, num_atoms) for label format. The samples are padded according to the number of nodes in each graph within `cond`. """ if not hasattr(cond, "num_graphs"): diff --git a/src/rydberggpt/models/transformer/layers.py b/src/rydberggpt/models/transformer/layers.py index 7f3e6f17..a1ae98a2 100644 --- a/src/rydberggpt/models/transformer/layers.py +++ b/src/rydberggpt/models/transformer/layers.py @@ -47,7 +47,7 @@ def forward( batch_mask (torch.Tensor): The mask tensor for batches. Returns: - torch.Tensor: The output tensor. + (torch.Tensor): The output tensor. """ causal_attn_mask = torch.meshgrid( @@ -104,7 +104,7 @@ def forward(self, x: torch.Tensor, batch_mask: torch.Tensor) -> torch.Tensor: batch_mask (torch.Tensor): The mask tensor for batches. Returns: - torch.Tensor: The output tensor. + (torch.Tensor): The output tensor. """ batch_key_mask = batch_mask diff --git a/src/rydberggpt/models/transformer/models.py b/src/rydberggpt/models/transformer/models.py index c0c8ef41..35488f0d 100644 --- a/src/rydberggpt/models/transformer/models.py +++ b/src/rydberggpt/models/transformer/models.py @@ -44,7 +44,7 @@ def forward(self, tgt: torch.Tensor, src: torch.Tensor) -> torch.Tensor: src (torch.Tensor): The source tensor of shape (batch_size, src_seq_length, d_model_src). Returns: - torch.Tensor: The output tensor after passing through the encoder-decoder architecture, + (torch.Tensor): The output tensor after passing through the encoder-decoder architecture, with shape (batch_size, tgt_seq_length, d_model). """ @@ -60,7 +60,7 @@ def encode(self, src: torch.Tensor) -> torch.Tensor: src (torch.Tensor): The source tensor of shape (batch_size, src_seq_length, d_model_src). Returns: - torch.Tensor: The encoded tensor of shape (batch_size, src_seq_length, d_model_tgt). + (torch.Tensor): The encoded tensor of shape (batch_size, src_seq_length, d_model_tgt). """ x, batch_mask = self.src_embed(src) @@ -78,7 +78,7 @@ def decode( memory (torch.Tensor): The memory tensor of shape (batch_size, src_seq_length, d_model). Returns: - torch.Tensor: The decoded tensor of shape (batch_size, tgt_seq_length, d_model). + (torch.Tensor): The decoded tensor of shape (batch_size, tgt_seq_length, d_model). """ return self.decoder(self.tgt_embed(tgt), memory, batch_mask=batch_mask) @@ -109,7 +109,7 @@ def forward(self, x: torch.Tensor, batch_mask: torch.Tensor) -> torch.Tensor: batch_mask (torch.Tensor): The mask tensor for batches. Returns: - torch.Tensor: The output tensor after passing through all layers of the encoder, + (torch.Tensor): The output tensor after passing through all layers of the encoder, with the same shape as the input tensor (batch_size, seq_length, d_model). """ for layer in self.layers: @@ -146,7 +146,7 @@ def forward( batch_mask (torch.Tensor): The mask tensor for batches. Returns: - torch.Tensor: The output tensor after passing through all layers of the decoder of shape (batch_size, seq_length, d_model). + (torch.Tensor): The output tensor after passing through all layers of the decoder of shape (batch_size, seq_length, d_model). """ for layer in self.layers: x = layer(x, memory, batch_mask=batch_mask) @@ -177,7 +177,7 @@ def forward(self, x: torch.Tensor) -> torch.Tensor: x (torch.Tensor): The input tensor of shape (batch_size, seq_length, d_model). Returns: - torch.Tensor: The output tensor of shape (batch_size, seq_length, vocab_size), + (torch.Tensor): The output tensor of shape (batch_size, seq_length, vocab_size), with log-softmax applied along the last dimension. """ diff --git a/src/rydberggpt/models/transformer/modules.py b/src/rydberggpt/models/transformer/modules.py index cfc34dd8..75c987cc 100644 --- a/src/rydberggpt/models/transformer/modules.py +++ b/src/rydberggpt/models/transformer/modules.py @@ -30,7 +30,7 @@ def forward(self, x: torch.Tensor, sublayer: nn.Module) -> torch.Tensor: sublayer (nn.Module): The sublayer module. Returns: - torch.Tensor: The output tensor. + (torch.Tensor): The output tensor. """ # NOTE For GPT2 the authors moved Layer normalization (Ba et al., 2016) # to the input of each sub-block. @@ -62,7 +62,7 @@ def forward(self, x: torch.Tensor) -> torch.Tensor: x (torch.Tensor): The input tensor. Returns: - torch.Tensor: The output tensor. + (torch.Tensor): The output tensor. """ return self.w_2(self.dropout(F.relu(self.w_1(x)))) @@ -89,7 +89,7 @@ def forward(self, x: torch.Tensor) -> torch.Tensor: x (torch.Tensor): The input tensor. Returns: - torch.Tensor: The output tensor. + (torch.Tensor): The output tensor. """ x = self.lut(x) * math.sqrt(self.d_model) return x diff --git a/src/rydberggpt/models/transformer/utils.py b/src/rydberggpt/models/transformer/utils.py index 88941d64..e5a00231 100644 --- a/src/rydberggpt/models/transformer/utils.py +++ b/src/rydberggpt/models/transformer/utils.py @@ -17,7 +17,7 @@ def snake_flip(x: torch.Tensor) -> torch.Tensor: x (torch.Tensor): The tensor to apply the snake flip to, dimensions should be [..., Ly, Lx]. Returns: - torch.Tensor: The "snake" flipped tensor, dimensions will be [..., Ly, Lx]. + (torch.Tensor): The "snake" flipped tensor, dimensions will be [..., Ly, Lx]. """ if not isinstance(x, torch.Tensor): @@ -40,6 +40,6 @@ def flattened_snake_flip(x: torch.Tensor, Lx: int, Ly: int) -> torch.Tensor: x (torch.Tensor): The tensor to apply the snake flip to, dimensions should be [..., Ly * Lx]. Returns: - torch.Tensor: The "snake" flipped tensor, dimensions will be [..., Ly * Lx]. + (torch.Tensor): The "snake" flipped tensor, dimensions will be [..., Ly * Lx]. """ return snake_flip(x.reshape(*x.shape[:-1], Ly, Lx)).reshape(*x.shape[:-1], -1) diff --git a/src/rydberggpt/observables/rydberg_energy.py b/src/rydberggpt/observables/rydberg_energy.py index cd0c7804..051fc4af 100644 --- a/src/rydberggpt/observables/rydberg_energy.py +++ b/src/rydberggpt/observables/rydberg_energy.py @@ -26,7 +26,7 @@ def get_staggered_magnetization( undo_sample_path_args (tuple): Additional arguments for undo_sample_path. Returns: - torch.Tensor: A tensor containing the estimated staggered magnetization of each sample. + (torch.Tensor): A tensor containing the estimated staggered magnetization of each sample. """ if undo_sample_path is not None: @@ -64,7 +64,7 @@ def get_x_magnetization( device (str, optional): The device on which to allocate the tensors. Defaults to "cpu". Returns: - torch.Tensor: A tensor containing the estimated x magnetization of each sample. + (torch.Tensor): A tensor containing the estimated x magnetization of each sample. """ model = model.to(device) @@ -109,7 +109,7 @@ def get_rydberg_energy( undo_sample_path_args (tuple): Additional arguments for undo_sample_path. Returns: - torch.Tensor: A tensor containing the estimated energy of each sample alongside its decomposition into terms. + (torch.Tensor): A tensor containing the estimated energy of each sample alongside its decomposition into terms. """ model = model.to(device) diff --git a/src/rydberggpt/tests/test_dataloader.py b/src/rydberggpt/tests/test_dataloader.py index e9b0ccec..45289902 100644 --- a/src/rydberggpt/tests/test_dataloader.py +++ b/src/rydberggpt/tests/test_dataloader.py @@ -13,7 +13,7 @@ def test_dataloader(): batch_size = 128 buffer_size = 2 - num_workers = 1 + num_workers = 0 base_dir = "src/rydberggpt/tests/dataset_test/" dataloader = get_rydberg_dataloader( @@ -49,4 +49,4 @@ def test_dataloader(): print(counter) -# test_dataloader() +test_dataloader() diff --git a/src/rydberggpt/training/loss.py b/src/rydberggpt/training/loss.py index 67da1043..52b35973 100644 --- a/src/rydberggpt/training/loss.py +++ b/src/rydberggpt/training/loss.py @@ -13,7 +13,7 @@ class NLLLoss(pl.LightningModule): The loss is calculated by taking the negative log of the probabilities predicted by the model for the true class labels. Methods: - forward(cond_log_probs: Tensor, tgt: Tensor) -> Tensor: + forward: Computes the NLL loss based on the conditional log probabilities and the target values. Examples: @@ -33,7 +33,7 @@ def forward(self, cond_log_probs: Tensor, tgt: Tensor) -> Tensor: tgt (Tensor): The target values. Returns: - Tensor: The computed NLL loss. + (Tensor): The computed NLL loss. """ num_atoms = tgt.shape[-2] - (tgt == 0.0).all(-1).sum(-1) log_probs = (cond_log_probs * tgt).sum(dim=(-2, -1)) diff --git a/src/rydberggpt/training/trainer.py b/src/rydberggpt/training/trainer.py index 3e8324f7..7c6e8343 100644 --- a/src/rydberggpt/training/trainer.py +++ b/src/rydberggpt/training/trainer.py @@ -49,7 +49,7 @@ def forward(self, m_onehot: torch.Tensor, cond: torch.Tensor) -> torch.Tensor: cond (torch.Tensor): Conditioning tensor. # TODO prompt Returns: - torch.Tensor: Conditional log probabilities tensor. + (torch.Tensor): Conditional log probabilities tensor. """ out = self.model.forward(m_onehot, cond) cond_log_probs = self.model.generator(out) @@ -64,7 +64,7 @@ def training_step(self, batch: torch.Tensor, batch_idx: int) -> torch.Tensor: batch_idx (int): The index of the current batch. Returns: - torch.Tensor: The training loss for the current batch. + (torch.Tensor): The training loss for the current batch. """ m_shifted_onehot = shift_inputs(batch.m_onehot) @@ -78,7 +78,7 @@ def configure_optimizers(self) -> Dict[str, Union[optim.Optimizer, Dict]]: Configures the optimizer and learning rate scheduler for the RydbergGPTTrainer. Returns: - Dict[str, Union[optim.Optimizer, Dict]]: A dictionary containing the optimizer and lr_scheduler configurations. + (Dict[str, Union[optim.Optimizer, Dict]]): A dictionary containing the optimizer and lr_scheduler configurations. """ optimizer_class = getattr(optim, self.config.optimizer) optimizer = optimizer_class( diff --git a/src/rydberggpt/training/utils.py b/src/rydberggpt/training/utils.py index d66bf355..86c54084 100644 --- a/src/rydberggpt/training/utils.py +++ b/src/rydberggpt/training/utils.py @@ -14,7 +14,7 @@ def set_example_input_array(train_loader: DataLoader) -> Tuple[Any, Any]: train_loader (DataLoader): The DataLoader instance for the training data. Returns: - Tuple[Any, Any]: A tuple containing m_onehot and graph from the example batch. + (Tuple[Any, Any]): A tuple containing m_onehot and graph from the example batch. """ logging.info("Setting example input array...") example_batch = next(iter(train_loader)) diff --git a/src/rydberggpt/utils.py b/src/rydberggpt/utils.py index 9bb4b400..5b9fa51d 100644 --- a/src/rydberggpt/utils.py +++ b/src/rydberggpt/utils.py @@ -23,9 +23,11 @@ def time_and_log(fn: Callable[..., Any]) -> Callable[..., Any]: Callable[..., Any]: The wrapped function. Usage: + ```py @time_and_log def my_function(arg1, arg2): # function logic here + ``` """ def wrapped(*args: Any, **kwargs: Any) -> Any: @@ -51,7 +53,7 @@ def shift_inputs(tensor: torch.Tensor) -> torch.Tensor: tensor (torch.Tensor): The input tensor of shape [B, S, D]. Returns: - torch.Tensor: The resulting tensor after the shift and pad operation. + (torch.Tensor): The resulting tensor after the shift and pad operation. """ B, _, D = tensor.size() zero_padding = torch.zeros((B, 1, D), device=tensor.device, dtype=tensor.dtype) @@ -66,9 +68,6 @@ def save_to_yaml(data: Dict[str, Any], filename: str) -> None: Args: data (Dict[str, Any]): The dictionary to be saved. filename (str): The path to the file where the dictionary will be saved. - - Returns: - None """ with open(filename, "w") as file: yaml.dump(data, file) @@ -85,7 +84,7 @@ def to_one_hot( num_classes: Number of classes in the one-hot representation. Returns: - data: The one-hot representation of the input data. + data (torch.Tensor): The one-hot representation of the input data. """ if isinstance(data, (list, tuple)): @@ -107,7 +106,7 @@ def create_dataclass_from_dict(name: str, data: Dict[str, Any]) -> Type: data (Dict[str, Any]): A dictionary containing the dataclass fields and their values. Returns: - Type: A new dataclass with the specified name and fields. + (Type): A new dataclass with the specified name and fields. """ fields = [(key, type(value)) for key, value in data.items()] return make_dataclass(name, fields) @@ -161,7 +160,7 @@ def create_config_from_yaml(yaml_content: Dict) -> dataclass: yaml_content (Dict): A dictionary containing the YAML content. Returns: - dataclass: A dataclass object representing the config. + (dataclass): A dataclass object representing the config. """ flattened_config = flatten_yaml(yaml_content) Config = create_dataclass_from_dict("Config", flattened_config) @@ -177,7 +176,7 @@ def load_config_file(checkpoint_path: str, config_file: str = "hparams.yaml") -> config_file (str, optional): The name of the configuration file, defaults to "hparams.yaml". Returns: - str: The path to the configuration file. + (str): The path to the configuration file. Raises: FileNotFoundError: If the configuration file is not found in the specified directory. diff --git a/src/rydberggpt/utils_ckpt.py b/src/rydberggpt/utils_ckpt.py index aae718ae..8e4abc4e 100644 --- a/src/rydberggpt/utils_ckpt.py +++ b/src/rydberggpt/utils_ckpt.py @@ -1,6 +1,6 @@ import os import re -from typing import Type, Union +from typing import Optional import pytorch_lightning as pl from torch import nn @@ -16,10 +16,10 @@ def get_ckpt_path(from_ckpt: int, log_dir: str = "logs/lightning_logs") -> str: Args: from_ckpt (int): The version number of the checkpoint. log_dir (str, optional): The root directory where checkpoints are stored. - Defaults to "logs/lightning_logs". + Defaults to "logs/lightning_logs". Returns: - str: The path to the specified checkpoint version directory. + (str): The path to the specified checkpoint version directory. Raises: FileNotFoundError: If no checkpoint is found in the specified directory. @@ -40,7 +40,7 @@ def find_latest_ckpt(log_dir: str): log_dir (str): The path to the log directory containing the checkpoint files. Returns: - str: The path to the latest checkpoint file. + (str): The path to the latest checkpoint file. """ log_dir = os.path.join(log_dir, "checkpoints") ckpt_files = [file for file in os.listdir(log_dir) if file.endswith(".ckpt")] @@ -53,7 +53,7 @@ def find_latest_ckpt(log_dir: str): return os.path.join(log_dir, latest_ckpt) -def find_best_ckpt(log_dir: str) -> Union[str, None]: +def find_best_ckpt(log_dir: str) -> Optional[str]: """ Find the best checkpoint file (with the lowest training loss) in the specified log directory. @@ -61,7 +61,7 @@ def find_best_ckpt(log_dir: str) -> Union[str, None]: log_dir (str): The path to the log directory containing the checkpoint files. Returns: - str: The path to the checkpoint file with the lowest training loss. + (str): The path to the checkpoint file with the lowest training loss. """ log_dir = os.path.join(log_dir, "checkpoints") ckpt_files = [file for file in os.listdir(log_dir) if file.endswith(".ckpt")] @@ -89,7 +89,7 @@ def get_model_from_ckpt( log_path: str, model: nn.Module, ckpt: str = "best", - trainer: Type[pl.LightningModule] = RydbergGPTTrainer, + trainer: pl.LightningModule = RydbergGPTTrainer, ) -> nn.Module: """ Load a model from a specified checkpoint file in the log directory. @@ -98,10 +98,10 @@ def get_model_from_ckpt( log_path (str): The path to the log directory containing the checkpoint files. model (nn.Module): The model class to load. ckpt (str, optional): The checkpoint to load. Must be either "best" or "latest". Defaults to "best". - trainer (Type[pl.LightningModule], optional): The trainer class to use for loading the model. Defaults to RydbergGPTTrainer. + trainer (pl.LightningModule, optional): The trainer class to use for loading the model. Defaults to RydbergGPTTrainer. Returns: - nn.Module: The loaded model. + (nn.Module): The loaded model. Raises: ValueError: If the value of ckpt is not "best" or "latest".