├── .coveragerc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── documentation-issue.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── deploy_dev.yml │ ├── deploy_latest.yml │ ├── doc.yml │ ├── link-checker.yml │ ├── lint.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── .vscode └── settings.json ├── CITATION.bib ├── CITATION.cff ├── LICENSE ├── README.md ├── codecov.yml ├── docs ├── assets │ ├── analog-logo-dark.svg │ ├── analog-logo.svg │ ├── favicon.ico │ ├── index │ │ ├── geometry-visualization.png │ │ ├── hardware-program-visualization.png │ │ ├── program-visualization.png │ │ └── report-visualization.png │ ├── logo-dark.svg │ ├── logo.svg │ ├── quick_start │ │ ├── geometry-visualization.png │ │ ├── ipython-hints.gif │ │ ├── jupyter-hints.gif │ │ ├── pulse-sequence-visualization.png │ │ ├── pycharm-hints.gif │ │ ├── report-visualization.png │ │ └── vscode-hints.gif │ └── readme-gifs │ │ ├── graph-select.gif │ │ ├── hover-bitstrings.gif │ │ ├── locations-hover.gif │ │ ├── smart-docs.gif │ │ └── visualize-bitstrings.gif ├── contributing │ ├── asking-a-question.md │ ├── code-of-conduct.md │ ├── community-slack.md │ ├── design-philosophy-and-architecture.md │ ├── documentation-issues.md │ ├── feature-requests.md │ ├── index.md │ ├── providing-feedback.md │ └── reporting-a-bug.md ├── home │ ├── background.md │ ├── emulation.md │ ├── geometry.md │ ├── gotchas.md │ ├── migration.md │ ├── quick_start.md │ ├── submission.md │ ├── visualization.md │ └── waveforms.md ├── index.md ├── javascripts │ └── mathjax.js ├── overrides │ └── main.html ├── reference │ ├── hardware-capabilities.md │ ├── overview.md │ └── standard.md ├── scripts │ └── gen_ref_nav.py └── stylesheets │ └── extra.css ├── justfile ├── mkdocs.yml ├── pyproject.toml ├── src └── bloqade │ └── analog │ ├── __init__.py │ ├── atom_arrangement.py │ ├── builder │ ├── __init__.py │ ├── args.py │ ├── assign.py │ ├── backend │ │ ├── __init__.py │ │ ├── bloqade.py │ │ ├── braket.py │ │ └── quera.py │ ├── base.py │ ├── coupling.py │ ├── drive.py │ ├── field.py │ ├── parallelize.py │ ├── parse │ │ ├── __init__.py │ │ ├── builder.py │ │ ├── stream.py │ │ └── trait.py │ ├── pragmas.py │ ├── route.py │ ├── sequence_builder.py │ ├── spatial.py │ ├── start.py │ ├── typing.py │ └── waveform.py │ ├── compiler │ ├── __init__.py │ ├── analysis │ │ ├── __init__.py │ │ ├── common │ │ │ ├── __init__.py │ │ │ ├── assignment_scan.py │ │ │ ├── check_slices.py │ │ │ ├── is_constant.py │ │ │ ├── is_hyperfine.py │ │ │ ├── scan_channels.py │ │ │ └── scan_variables.py │ │ ├── hardware │ │ │ ├── __init__.py │ │ │ ├── channels.py │ │ │ ├── lattice.py │ │ │ ├── piecewise_constant.py │ │ │ └── piecewise_linear.py │ │ └── python │ │ │ ├── __init__.py │ │ │ └── waveform.py │ ├── codegen │ │ ├── __init__.py │ │ ├── common │ │ │ ├── __init__.py │ │ │ └── json.py │ │ ├── hardware │ │ │ ├── __init__.py │ │ │ ├── lattice.py │ │ │ ├── lattice_site_coefficients.py │ │ │ ├── piecewise_constant.py │ │ │ └── piecewise_linear.py │ │ ├── julia │ │ │ ├── __init__.py │ │ │ └── types.py │ │ └── python │ │ │ ├── __init__.py │ │ │ ├── emulator_ir.py │ │ │ └── waveform.py │ ├── passes │ │ ├── __init__.py │ │ ├── emulator.py │ │ └── hardware │ │ │ ├── __init__.py │ │ │ ├── components.py │ │ │ ├── define.py │ │ │ └── units.py │ └── rewrite │ │ ├── __init__.py │ │ ├── common │ │ ├── __init__.py │ │ ├── add_padding.py │ │ ├── assign_to_literal.py │ │ ├── assign_variables.py │ │ ├── canonicalize.py │ │ └── flatten.py │ │ └── python │ │ ├── __init__.py │ │ └── waveform.py │ ├── constants.py │ ├── emulate │ ├── __init__.py │ ├── codegen │ │ ├── __init__.py │ │ └── hamiltonian.py │ ├── ir │ │ ├── __init__.py │ │ ├── atom_type.py │ │ ├── emulator.py │ │ ├── space.py │ │ └── state_vector.py │ └── sparse_operator.py │ ├── factory.py │ ├── ir │ ├── __init__.py │ ├── analog_circuit.py │ ├── control │ │ ├── __init__.py │ │ ├── field.py │ │ ├── pulse.py │ │ ├── sequence.py │ │ ├── traits │ │ │ ├── __init__.py │ │ │ ├── append.py │ │ │ ├── canonicalize.py │ │ │ ├── hash.py │ │ │ └── slice.py │ │ └── waveform.py │ ├── location │ │ ├── __init__.py │ │ ├── bravais.py │ │ └── location.py │ ├── routine │ │ ├── __init__.py │ │ ├── base.py │ │ ├── bloqade.py │ │ ├── braket.py │ │ ├── params.py │ │ └── quera.py │ ├── scalar.py │ ├── tree_print.py │ └── visitor.py │ ├── migrate.py │ ├── serialize.py │ ├── submission │ ├── __init__.py │ ├── base.py │ ├── braket.py │ ├── capabilities.py │ ├── config │ │ ├── aquila_api_config.json │ │ ├── capabilities.json │ │ ├── experimental_capabilities.json │ │ └── mock_api_config.json │ ├── ir │ │ ├── __init__.py │ │ ├── braket.py │ │ ├── capabilities.py │ │ ├── parallel.py │ │ ├── task_results.py │ │ └── task_specification.py │ ├── load_config.py │ ├── mock.py │ └── quera.py │ ├── task │ ├── __init__.py │ ├── base.py │ ├── batch.py │ ├── bloqade.py │ ├── braket.py │ ├── braket_simulator.py │ ├── exclusive.py │ └── quera.py │ └── visualization │ ├── __init__.py │ ├── atom_arrangement_visualize.py │ ├── display.py │ ├── ir_visualize.py │ ├── report_visualize.py │ └── task_visualize.py ├── tests ├── __init__.py ├── _test_compiler_passes.py ├── _test_issue.py ├── cassettes │ └── test_quera_internal_api │ │ ├── test_quera_retrieve.yaml │ │ └── test_quera_submit.yaml ├── data │ ├── config │ │ └── submit_quera_api.json │ └── expected_pprint_output │ │ ├── list_of_locations_pprint_output.txt │ │ ├── rectangular_pprint_defect_count_output.txt │ │ ├── rectangular_pprint_defect_density_output.txt │ │ ├── rectangular_pprint_output.txt │ │ ├── square_pprint_defect_count_output.txt │ │ ├── square_pprint_defect_density_output.txt │ │ ├── square_pprint_output.txt │ │ └── square_pprint_var_output.txt ├── test_2d_state_compile.py ├── test_adiabatic_compile.py ├── test_args.py ├── test_assign.py ├── test_assign_to_literal.py ├── test_batch.py ├── test_braket_emulator.py ├── test_braket_ir.py ├── test_braket_submission_backend.py ├── test_bravais.py ├── test_builder.py ├── test_builder_job.py ├── test_builder_old.py ├── test_builder_visual.py ├── test_canonicalize.py ├── test_constants.py ├── test_costum_submission.py ├── test_emulator_codegen.py ├── test_emulator_interface.py ├── test_examples.py ├── test_expectation_value.py ├── test_factory.py ├── test_field.py ├── test_flatten_sequence.py ├── test_floquet_compile.py ├── test_hamiltonian_codegen.py ├── test_hardware_codegen.py ├── test_hardware_codegen_lattice.py ├── test_hardware_codegen_v2.py ├── test_ir_visual.py ├── test_is_constant.py ├── test_lattice.py ├── test_lattice_gauge_theory_compile.py ├── test_lattice_pprint.py ├── test_migrate.py ├── test_mis_compile.py ├── test_misc.py ├── test_mock_submit.py ├── test_multi_rabi_compile.py ├── test_noneq_dynamics_compile.py ├── test_parallelize.py ├── test_printer.py ├── test_program_visitor.py ├── test_pulse.py ├── test_python_codegen.py ├── test_python_emulator.py ├── test_quera_hardware_analysis.py ├── test_quera_submission_backend.py ├── test_ramsey_compile.py ├── test_report.py ├── test_report_visual.py ├── test_run_callback.py ├── test_scalar.py ├── test_scan_channels.py ├── test_scar_compile.py ├── test_schema_codegen.py ├── test_sequence.py ├── test_serialize_decorator.py ├── test_single_rabi_compile.py ├── test_space.py ├── test_sparse_operator.py ├── test_submission_base.py ├── test_task.py ├── test_task2.py ├── test_types.py ├── test_variable_scan.py ├── test_version.py ├── test_waveform.py └── test_z2_compile.py └── uv.lock /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = */tests/* 3 | 4 | [report] 5 | omit = */tests/* 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve stability of Bloqade 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | 13 | **Minimal broken example** 14 | 15 | 16 | **Expected behavior** 17 | 18 | 19 | **Screenshots** 20 | 21 | 22 | **Desktop (please complete the following information):** 23 | - OS: [e.g. iOS] 24 | - Bloqade Version: 25 | - Python Version: 26 | 27 | **Additional context** 28 | 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation Issue 3 | about: 'An issue pertaining to the documentation. ' 4 | title: '' 5 | labels: documentation 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the issue** 11 | 12 | 13 | **Link to the page and section containing the issue, if applicable** 14 | 15 | 16 | **Suggested content or correction, if applicable** 17 | 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for Bloqade 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | 13 | **Describe the solution you'd like** 14 | 15 | 16 | **Describe alternatives you've considered** 17 | 18 | 19 | **Additional context** 20 | 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | schedule: 9 | - cron: '00 01 * * *' 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | python-version: ["3.10", "3.11", "3.12"] 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Install uv 24 | uses: astral-sh/setup-uv@v6 25 | with: 26 | # Install a specific version of uv. 27 | version: "0.5.1" 28 | enable-cache: true 29 | cache-dependency-glob: "uv.lock" 30 | - name: Set up Python ${{ matrix.python-version }} 31 | run: uv python install ${{ matrix.python-version }} 32 | - name: Install the project 33 | run: uv sync --all-extras --dev 34 | - name: Run tests 35 | # For example, using `pytest` 36 | run: uv run just coverage 37 | - name: Upload Coverage to Codecov 38 | uses: codecov/codecov-action@v5 39 | with: 40 | files: coverage.xml # optional 41 | fail_ci_if_error: true # optional (default = false) 42 | verbose: true # optional (default = false) 43 | token: ${{ secrets.CODECOV_TOKEN }} # required 44 | - name: Archive code coverage results 45 | if: matrix.python-version == '3.12' 46 | uses: actions/upload-artifact@v4 47 | with: 48 | name: code-coverage-report 49 | path: coverage.xml 50 | retention-days: 2 51 | post: 52 | runs-on: ubuntu-latest 53 | needs: build 54 | if: github.event.pull_request 55 | steps: 56 | - uses: actions/checkout@v4 57 | - name: download covearge 58 | uses: actions/download-artifact@v4 59 | with: 60 | name: code-coverage-report 61 | - name: check coverage 62 | run: | 63 | if [ -f coverage.xml ]; then 64 | echo "Coverage file exists" 65 | else 66 | echo "Coverage file does not exist" 67 | exit 1 68 | fi 69 | - name: post covearge 70 | uses: orgoro/coverage@v3.2 71 | with: 72 | coverageFile: coverage.xml 73 | token: ${{ secrets.GITHUB_TOKEN }} 74 | -------------------------------------------------------------------------------- /.github/workflows/deploy_dev.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Devopment Branch Docs 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | documentation: 13 | name: Deploy dev documentation 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | - name: Install uv 20 | uses: astral-sh/setup-uv@v6 21 | with: 22 | # Install a specific version of uv. 23 | version: "0.5.1" 24 | enable-cache: true 25 | cache-dependency-glob: "uv.lock" 26 | - name: Install Documentation dependencies 27 | run: uv sync --group doc 28 | - name: Set up build cache 29 | uses: actions/cache@v4 30 | id: cache 31 | with: 32 | key: mkdocs-material-${{ github.ref }} 33 | path: .cache 34 | restore-keys: | 35 | mkdocs-material- 36 | # derived from: 37 | # https://github.com/RemoteCloud/public-documentation/blob/dev/.github/workflows/build_docs.yml 38 | - name: Configure Git user 39 | run: | 40 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 41 | git config --local user.name "github-actions[bot]" 42 | - name: Deploy documentation 43 | env: 44 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 45 | GOOGLE_ANALYTICS_KEY: ${{ secrets.GOOGLE_ANALYTICS_KEY }} 46 | run: | 47 | git fetch origin gh-pages --depth=1 48 | uv run mike deploy -p dev 49 | -------------------------------------------------------------------------------- /.github/workflows/deploy_latest.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Release Docs 2 | on: 3 | push: 4 | tags: 5 | - "**" 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 9 | cancel-in-progress: true 10 | 11 | # See: https://github.com/pdm-project/pdm/issues/1879 12 | env: 13 | PDM_DEPS: 'urllib3<2' 14 | 15 | jobs: 16 | documentation: 17 | name: Deploy release documentation 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Wait for dev documentation to deploy 21 | uses: lewagon/wait-on-check-action@v1.3.4 22 | with: 23 | ref: main 24 | check-name: 'Deploy dev documentation' 25 | repo-token: ${{ secrets.GITHUB_TOKEN }} 26 | wait-interval: 10 27 | - uses: actions/checkout@v4 28 | with: 29 | fetch-depth: 0 30 | - name: Install uv 31 | uses: astral-sh/setup-uv@v6 32 | with: 33 | # Install a specific version of uv. 34 | version: "0.5.1" 35 | enable-cache: true 36 | cache-dependency-glob: "uv.lock" 37 | - name: Install Documentation dependencies 38 | run: uv sync --group doc 39 | - name: Set up build cache 40 | uses: actions/cache@v4 41 | id: cache 42 | with: 43 | key: mkdocs-material-${{ github.ref }} 44 | path: .cache 45 | restore-keys: | 46 | mkdocs-material- 47 | # derived from: 48 | # https://github.com/RemoteCloud/public-documentation/blob/dev/.github/workflows/build_docs.yml 49 | - name: Configure Git user 50 | run: | 51 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 52 | git config --local user.name "github-actions[bot]" 53 | - name: Set release notes tag 54 | run: | 55 | export TAG_PATH=${{ github.ref }} 56 | echo ${TAG_PATH} 57 | echo "TAG_VERSION=${TAG_PATH##*/}" >> $GITHUB_ENV 58 | echo ${TAG_VERSION} 59 | - name: Deploy documentation 60 | env: 61 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 62 | GOOGLE_ANALYTICS_KEY: ${{ secrets.GOOGLE_ANALYTICS_KEY }} 63 | run: | 64 | git fetch origin gh-pages --depth=1 65 | uv run mike deploy --update-alias --push ${TAG_VERSION} latest 66 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Documentation (preview) 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | - reopened 7 | - synchronize 8 | - closed 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | documentation: 15 | name: Deploy preview documentation 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Install uv 20 | uses: astral-sh/setup-uv@v6 21 | with: 22 | # Install a specific version of uv. 23 | version: "0.5.1" 24 | enable-cache: true 25 | cache-dependency-glob: "uv.lock" 26 | - name: Install Documentation dependencies 27 | run: uv sync --group doc 28 | - name: Set up build cache 29 | uses: actions/cache@v4 30 | id: cache 31 | with: 32 | key: mkdocs-material-${{ github.ref }} 33 | path: .cache 34 | restore-keys: | 35 | mkdocs-material- 36 | - name: Depoly documentation 37 | env: 38 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 39 | run: | 40 | uv run mkdocs build 41 | - name: Deploy preview 42 | uses: rossjrw/pr-preview-action@v1 43 | with: 44 | source-dir: ./site 45 | -------------------------------------------------------------------------------- /.github/workflows/link-checker.yml: -------------------------------------------------------------------------------- 1 | on: 2 | schedule: 3 | - cron: '0 0 * * 0' 4 | 5 | name: Check markdown links 6 | jobs: 7 | my-broken-link-checker: 8 | name: Check broken links 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check 12 | uses: ruzickap/action-my-broken-link-checker@v2 13 | with: 14 | url: https://bloqade.quera.com 15 | cmd_params: "--buffer-size=8192 --exclude='https://github.com/QuEraComputing/bloqade-analog/edit/' --exclude='https://fonts.gstatic.com'" 16 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | ruff: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: chartboost/ruff-action@v1 17 | black: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: psf/black@stable 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v**" 7 | 8 | jobs: 9 | build: 10 | name: Build distribution 📦 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Install uv 16 | uses: astral-sh/setup-uv@v6 17 | with: 18 | # Install a specific version of uv. 19 | version: "0.5.5" 20 | enable-cache: true 21 | cache-dependency-glob: "uv.lock" 22 | - name: Install the project 23 | run: uv sync --all-extras --dev 24 | - name: Build distribution 📦 25 | run: uv build 26 | - name: Store the distribution packages 27 | uses: actions/upload-artifact@v4 28 | with: 29 | name: python-package-distributions 30 | path: dist/ 31 | 32 | publish-to-pypi: 33 | name: >- 34 | Publish Python 🐍 distribution 📦 to PyPI 35 | if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes 36 | needs: 37 | - build 38 | runs-on: ubuntu-latest 39 | environment: 40 | name: pypi 41 | url: https://pypi.org/p/bloqade-analog # Replace with your PyPI project name 42 | permissions: 43 | id-token: write # IMPORTANT: mandatory for trusted publishing 44 | 45 | steps: 46 | - name: Download all the dists 47 | uses: actions/download-artifact@v4 48 | with: 49 | name: python-package-distributions 50 | path: dist/ 51 | - name: Publish distribution 📦 to PyPI 52 | uses: pypa/gh-action-pypi-publish@release/v1 53 | 54 | github-release: 55 | name: >- 56 | Sign the Python 🐍 distribution 📦 with Sigstore 57 | and upload them to GitHub Release 58 | needs: 59 | - publish-to-pypi 60 | runs-on: ubuntu-latest 61 | 62 | permissions: 63 | contents: write # IMPORTANT: mandatory for making GitHub Releases 64 | id-token: write # IMPORTANT: mandatory for sigstore 65 | 66 | steps: 67 | - name: Download all the dists 68 | uses: actions/download-artifact@v4 69 | with: 70 | name: python-package-distributions 71 | path: dist/ 72 | - name: Sign the dists with Sigstore 73 | uses: sigstore/gh-action-sigstore-python@v3.0.0 74 | with: 75 | inputs: >- 76 | ./dist/*.tar.gz 77 | ./dist/*.whl 78 | - name: Create GitHub Release 79 | env: 80 | GITHUB_TOKEN: ${{ github.token }} 81 | run: >- 82 | gh release create 83 | '${{ github.ref_name }}' 84 | --repo '${{ github.repository }}' 85 | --notes "" 86 | - name: Upload artifact signatures to GitHub Release 87 | env: 88 | GITHUB_TOKEN: ${{ github.token }} 89 | # Upload to GitHub Release using the `gh` CLI. 90 | # `dist/` contains the built packages, and the 91 | # sigstore-produced signatures and certificates. 92 | run: >- 93 | gh release upload 94 | '${{ github.ref_name }}' dist/** 95 | --repo '${{ github.repository }}' 96 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/.gitmodules -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 6 | hooks: 7 | - id: check-yaml 8 | args: ['--unsafe'] 9 | - id: end-of-file-fixer 10 | - id: trailing-whitespace 11 | - repo: https://github.com/pycqa/isort 12 | rev: 5.13.2 13 | hooks: 14 | - id: isort 15 | name: isort (python) 16 | - repo: https://github.com/psf/black 17 | rev: 25.1.0 18 | hooks: 19 | - id: black 20 | - repo: https://github.com/charliermarsh/ruff-pre-commit 21 | # Ruff version. 22 | rev: "v0.9.2" 23 | hooks: 24 | - id: ruff 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.testing.pytestArgs": [ 3 | "tests" 4 | ], 5 | "python.testing.unittestEnabled": false, 6 | "python.testing.pytestEnabled": true, 7 | "python.formatting.provider": "black", 8 | "python.analysis.extraPaths": [ 9 | "./tests" 10 | ], 11 | "terminal.integrated.env.osx": { 12 | "PYTHONPATH": "${workspaceFolder}/src", 13 | }, 14 | "terminal.integrated.env.linux": { 15 | "PYTHONPATH": "${workspaceFolder}/src", 16 | }, 17 | "terminal.integrated.env.windows": { 18 | "PYTHONPATH": "${workspaceFolder}/src", 19 | }, 20 | "python.linting.enabled": false, 21 | "yaml.schemas": { 22 | "https://squidfunk.github.io/mkdocs-material/schema.json": "mkdocs.yml" 23 | }, 24 | "yaml.customTags": [ 25 | "!ENV scalar", 26 | "!ENV sequence", 27 | "tag:yaml.org,2002:python/name:materialx.emoji.to_svg", 28 | "tag:yaml.org,2002:python/name:materialx.emoji.twemoji", 29 | "tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /CITATION.bib: -------------------------------------------------------------------------------- 1 | @software{bloqade_2024_11114110, 2 | author = {Weinberg, Phillip and 3 | Wu, Kai-Hsin and 4 | John Long and 5 | Luo, Xiu-zhe (Roger)}, 6 | title = {QuEraComputing/bloqade-python: v0.15.11}, 7 | month = may, 8 | year = 2024, 9 | publisher = {Zenodo}, 10 | version = {v0.15.11}, 11 | doi = {10.5281/zenodo.11114110}, 12 | url = {https://doi.org/10.5281/zenodo.11114110} 13 | } 14 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | abstract: "Bloqade is an SDK designed to make writing and analyzing the results of analog quantum programs on QuEra's neutral atom quantum computers as seamless and flexible as possible. Currently, QuEra's hardware is on Amazon Braket, the primary method of accessing QuEra's quantum hardware. Over the alpha phase, we plan to expand the emulator capabilities to include a performance Python emulator but also a direct integration with Julia via Bloqade.jl." 2 | authors: 3 | - affiliation: QuEra Computing Inc. 4 | family-names: Weinberg 5 | given-names: Phillip 6 | - affiliation: QuEra Computing Inc. 7 | family-names: Wu 8 | given-names: Kai-Hsin 9 | - affiliation: QuEra Computing Inc. 10 | family-names: Long 11 | given-names: John 12 | - affiliation: QuEra Computing Inc. 13 | family-names: Luo 14 | given-names: Xiu-zhe (Roger) 15 | cff-version: 1.2.0 16 | date-released: '2024-05-04' 17 | doi: 10.5281/zenodo.11114110 18 | license: 19 | - cc-by-4.0 20 | repository-code: https://github.com/QuEraComputing/bloqade-analog/tree/v0.15.11 21 | title: 'QuEraComputing/bloqade-analog: v0.15.11' 22 | type: software 23 | version: v0.15.11 24 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: auto 6 | threshold: 4% 7 | patch: 8 | default: 9 | target: auto 10 | threshold: 4% 11 | -------------------------------------------------------------------------------- /docs/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/favicon.ico -------------------------------------------------------------------------------- /docs/assets/index/geometry-visualization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/index/geometry-visualization.png -------------------------------------------------------------------------------- /docs/assets/index/hardware-program-visualization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/index/hardware-program-visualization.png -------------------------------------------------------------------------------- /docs/assets/index/program-visualization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/index/program-visualization.png -------------------------------------------------------------------------------- /docs/assets/index/report-visualization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/index/report-visualization.png -------------------------------------------------------------------------------- /docs/assets/quick_start/geometry-visualization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/quick_start/geometry-visualization.png -------------------------------------------------------------------------------- /docs/assets/quick_start/ipython-hints.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/quick_start/ipython-hints.gif -------------------------------------------------------------------------------- /docs/assets/quick_start/jupyter-hints.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/quick_start/jupyter-hints.gif -------------------------------------------------------------------------------- /docs/assets/quick_start/pulse-sequence-visualization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/quick_start/pulse-sequence-visualization.png -------------------------------------------------------------------------------- /docs/assets/quick_start/pycharm-hints.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/quick_start/pycharm-hints.gif -------------------------------------------------------------------------------- /docs/assets/quick_start/report-visualization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/quick_start/report-visualization.png -------------------------------------------------------------------------------- /docs/assets/quick_start/vscode-hints.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/quick_start/vscode-hints.gif -------------------------------------------------------------------------------- /docs/assets/readme-gifs/graph-select.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/readme-gifs/graph-select.gif -------------------------------------------------------------------------------- /docs/assets/readme-gifs/hover-bitstrings.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/readme-gifs/hover-bitstrings.gif -------------------------------------------------------------------------------- /docs/assets/readme-gifs/locations-hover.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/readme-gifs/locations-hover.gif -------------------------------------------------------------------------------- /docs/assets/readme-gifs/smart-docs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/readme-gifs/smart-docs.gif -------------------------------------------------------------------------------- /docs/assets/readme-gifs/visualize-bitstrings.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/docs/assets/readme-gifs/visualize-bitstrings.gif -------------------------------------------------------------------------------- /docs/contributing/asking-a-question.md: -------------------------------------------------------------------------------- 1 | # Ask a Question 2 | 3 | If you're interested in contributing to Bloqade, or just want to discuss the project, join the discussion on GitHub Discussions at https://github.com/QuEraComputing/bloqade-analog/discussions 4 | -------------------------------------------------------------------------------- /docs/contributing/code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Design Philosophy and Architecture 2 | 3 | Given the heterogeneous nature of the hardware we target, 4 | We have decided to use a compiler-based approach to our software 5 | stack, allowing us to target different hardware backends 6 | with the same high-level language. Below is a diagram of the 7 | software stack in Bloqade. 8 | 9 | ```mermaid 10 | graph TD 11 | Builder["Builder Representation"] 12 | PythonAST["Bloqade AST Python"] 13 | JuliaAST["Bloqade AST Julia"] 14 | 15 | EmulatorPy["Emulator IR Python"] 16 | EmulatorJL["Emulator IR Julia"] 17 | 18 | QuEra["QuEra IR"] 19 | Braket["Braket IR"] 20 | JuliaEmulator["Bloqade.jl"] 21 | PythonEmulator["Python Emulator"] 22 | 23 | Aquila["Aquila"] 24 | 25 | Builder -->|parse| PythonAST 26 | PythonAST -->|lower| EmulatorPy 27 | PythonAST -->|lower| QuEra 28 | PythonAST -->|lower| Braket 29 | PythonAST -->|transpile| JuliaAST 30 | 31 | QuEra -->|execute| Aquila 32 | Braket -->|execute| Aquila 33 | 34 | JuliaAST -->|lower| EmulatorJL 35 | EmulatorPy -->|execute| PythonEmulator 36 | EmulatorJL -->|execute| JuliaEmulator 37 | 38 | ``` 39 | 40 | ## High-Level Builder Representation 41 | 42 | When programming Bloqade using the Python API, the user constructs 43 | a representation of an analog quantum circuit. This representation 44 | is a *flattened* version of the actual analog circuit. *Flattened* 45 | means that the user input is a linear sequence of operations where 46 | the context of neighboring nodes in the sequence of instructions 47 | can determine the program tree structure. The Bloqade AST describes 48 | the actual analog circuit. 49 | 50 | ## Bloqade AST 51 | 52 | The Bloqade AST is a representation of a quantum analog circuit for 53 | neutral atom computing. It is a directed acyclic graph (DAG) with nodes 54 | for different hierarchical levels of the circuit. The base node is the 55 | `AnalogCircuit` which contains the geometry of the atoms stored as a 56 | `AtomArragment` or `ParallelRegister` objects. The other part of the 57 | circuit is the `Sequence`, which contains the waveforms that describe 58 | the drives for the Ryberg/Hyperfine transitions of 59 | each Rydberg atom. Each transition is represented by a `Pulse` including 60 | a `Field` for the drive's detuning, Rabi amplitude, and Rabi phase 61 | . A `Field` relates the spatial and temporal dependence 62 | of a drive. The spatial modulates the temporal dependence of the 63 | waveform. A DAG also describes the `Waveform` object. Finally, we 64 | have basic `Scalar` expressions as well for describing the syntax 65 | of real-valued continuous numbers. 66 | 67 | ## Bloqade Compilers and Transpilers 68 | 69 | Given a user program expressed as the Bloqade AST, we can target various 70 | backends by transforming from the Bloqade AST to other kinds of IR. 71 | For example, when submitting a task to QuEra's hardware, we transform the 72 | Bloqade AST to the IR that describes a valid program for the hardware. 73 | 74 | This process is referred to as `lowering`, which in a general sense is a 75 | transformation that takes you from one IR to another where the target IR 76 | is specialized or has a smaller syntactical structure. `Transpiling` 77 | corresponds to a transformation that takes you from 78 | one language to equivalent expressions in another. For example, we 79 | can transpile from the Bloqade AST in Python to the Bloqade AST in Julia. 80 | The generic term for both of these types of transformation in Bloqade is 81 | Code Generation. You will find various code generation implementations 82 | in various `codegen` modules. 83 | -------------------------------------------------------------------------------- /docs/contributing/community-slack.md: -------------------------------------------------------------------------------- 1 | # Community Slack 2 | 3 | You can join QuEra's Slack workspace with this [link](https://join.slack.com/t/querapublic/shared_invite/zt-1d5jjy2kl-_BxvXJQ4_xs6ZoUclQOTJg). Join the `#bloqade` channel to discuss anything related to Bloqade. 4 | -------------------------------------------------------------------------------- /docs/contributing/design-philosophy-and-architecture.md: -------------------------------------------------------------------------------- 1 | # Design Philosophy and Architecture 2 | 3 | Given the heterogeneous nature of the hardware we target, 4 | We have decided to use a compiler-based approach to our software 5 | stack, allowing us to target different hardware backends 6 | with the same high-level language. Below is a diagram of the 7 | software stack in Bloqade. 8 | 9 | ```mermaid 10 | graph TD 11 | Builder["Builder Representation"] 12 | PythonAST["Bloqade AST Python"] 13 | JuliaAST["Bloqade AST Julia"] 14 | 15 | EmulatorPy["Emulator IR Python"] 16 | EmulatorJL["Emulator IR Julia"] 17 | 18 | QuEra["QuEra IR"] 19 | Braket["Braket IR"] 20 | JuliaEmulator["Bloqade.jl"] 21 | PythonEmulator["Python Emulator"] 22 | 23 | Aquila["Aquila"] 24 | 25 | Builder -->|parse| PythonAST 26 | PythonAST -->|lower| EmulatorPy 27 | PythonAST -->|lower| QuEra 28 | PythonAST -->|lower| Braket 29 | PythonAST -->|transpile| JuliaAST 30 | 31 | QuEra -->|execute| Aquila 32 | Braket -->|execute| Aquila 33 | 34 | JuliaAST -->|lower| EmulatorJL 35 | EmulatorPy -->|execute| PythonEmulator 36 | EmulatorJL -->|execute| JuliaEmulator 37 | 38 | ``` 39 | 40 | ## High-Level Builder Representation 41 | 42 | When programming Bloqade using the Python API, the user constructs 43 | a representation of an analog quantum circuit. This representation 44 | is a *flattened* version of the actual analog circuit. *Flattened* 45 | means that the user input is a linear sequence of operations where 46 | the context of neighboring nodes in the sequence of instructions 47 | can determine the program tree structure. The Bloqade AST describes 48 | the actual analog circuit. 49 | 50 | ## Bloqade AST 51 | 52 | The Bloqade AST is a representation of a quantum analog circuit for 53 | neutral atom computing. It is a directed acyclic graph (DAG) with nodes 54 | for different hierarchical levels of the circuit. The base node is the 55 | `AnalogCircuit` which contains the geometry of the atoms stored as a 56 | `AtomArragment` or `ParallelRegister` objects. The other part of the 57 | circuit is the `Sequence`, which contains the waveforms that describe 58 | the drives for the Ryberg/Hyperfine transitions of 59 | each Rydberg atom. Each transition is represented by a `Pulse` including 60 | a `Field` for the drive's detuning, Rabi amplitude, and Rabi phase 61 | . A `Field` relates the spatial and temporal dependence 62 | of a drive. The spatial modulates the temporal dependence of the 63 | waveform. A DAG also describes the `Waveform` object. Finally, we 64 | have basic `Scalar` expressions as well for describing the syntax 65 | of real-valued continuous numbers. 66 | 67 | ## Bloqade Compilers and Transpilers 68 | 69 | Given a user program expressed as the Bloqade AST, we can target various 70 | backends by transforming from the Bloqade AST to other kinds of IR. 71 | For example, when submitting a task to QuEra's hardware, we transform the 72 | Bloqade AST to the IR that describes a valid program for the hardware. 73 | 74 | This process is referred to as `lowering`, which in a general sense is a 75 | transformation that takes you from one IR to another where the target IR 76 | is specialized or has a smaller syntactical structure. `Transpiling` 77 | corresponds to a transformation that takes you from 78 | one language to equivalent expressions in another. For example, we 79 | can transpile from the Bloqade AST in Python to the Bloqade AST in Julia. 80 | The generic term for both of these types of transformation in Bloqade is 81 | Code Generation. You will find various code generation implementations 82 | in various `codegen` modules. 83 | -------------------------------------------------------------------------------- /docs/contributing/documentation-issues.md: -------------------------------------------------------------------------------- 1 | # Reporting a Documentation Issue 2 | 3 | We are always looking to improve our documentation. If you find 4 | a typo or think something is unclear, please open an issue on 5 | our GitHub page: https://github.com/QuEraComputing/bloqade-analog/issues 6 | 7 | For typos or other minor problems, create an issue that contains 8 | a link to the specific page that includes the problem, along with 9 | a description of the problem and possibly a solution. 10 | 11 | For a request for new documentation content, please open up an 12 | issue and describe what you think is missing from the documentation. 13 | -------------------------------------------------------------------------------- /docs/contributing/feature-requests.md: -------------------------------------------------------------------------------- 1 | # Requesting new Features 2 | 3 | Given that we are currently at the beginning of the development of 4 | the Bloqade python interface, we are open to suggestions about what 5 | features would be helpful to include in future package iterations. 6 | If you have a request for a new feature, please open an issue on our 7 | GitHub page: https://github.com/QuEraComputing/bloqade-analog/issues 8 | 9 | We ask that the feature requests be as specific as possible. Please 10 | include the following information in your feature request: 11 | 12 | 1. A short, descriptive title. 13 | 14 | 2. A detailed description of the feature, including your attempt 15 | to solve the problem with the current version of Bloqade. 16 | 17 | 3. A minimal code example that demonstrates the need for the feature. 18 | 19 | 4. The version of Bloqade you are using. 20 | 21 | 5. The version of Python you are using. 22 | 23 | 6. The version of your operating system. 24 | -------------------------------------------------------------------------------- /docs/contributing/index.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing to the project! We welcome all contributions. There are many different ways to contribute to Bloqade, and we are always looking for more help. We accept contributions in the form of bug reports, feature requests, documentation improvements, and code contributions. For more information about how to contribute, please read the following sections. 4 | 5 | 6 | ## Table of Contents 7 | 8 | - [Reporting a Bug ](reporting-a-bug.md) 9 | - [Reporting Documentation Issues](documentation-issues.md) 10 | - [Feature Requests](feature-requests.md) 11 | - [Developing Bloqade](developing-bloqade.md) 12 | - [Design Philosophy and Architecture](design-philosophy-and-architecture.md) 13 | - [Community Slack](community-slack.md) 14 | - [Ask a Question](asking-a-question.md) 15 | - [Providing Feedback](providing-feedback.md) 16 | -------------------------------------------------------------------------------- /docs/contributing/providing-feedback.md: -------------------------------------------------------------------------------- 1 | # Providing Feedback 2 | 3 | While Github Issues are a great way for us to better understand any issues your having with Bloqade as well as provide us with feature requests, we're always looking for ways to collect more general feedback about what the user experience with Bloqade is like. 4 | 5 | To do that we have [this form](https://share.hsforms.com/1FJjYan2VQC6NfrQ5IPAcewdrwvd) where you can provide your thoughts after using/experimenting/tinkering/hacking with Bloqade. 6 | 7 | Your feedback will help guide the future of Bloqade's design so be honest and know that you're contributing to the future of Quantum Computing with Neutral Atoms! 8 | -------------------------------------------------------------------------------- /docs/contributing/reporting-a-bug.md: -------------------------------------------------------------------------------- 1 | # Reporting a Bug 2 | 3 | Bloqade is currently in the **alpha** phase of development, meaning bugs most likely exist in the 4 | current implementation. We are continuously striving to 5 | improve the stability of Bloqade. As such, we encourage 6 | our users to report all bugs they find. To do this, we 7 | ask you to submit an issue to our GitHub page: 8 | https://github.com/QuEraComputing/bloqade-analog/issues 9 | 10 | Please include the following information in your bug report: 11 | 12 | 1. A short, descriptive title. 13 | 14 | 2. A detailed description of the bug, including the 15 | expected behavior and what happened. 16 | 17 | 3. A minimal code example that reproduces the bug. 18 | 19 | 4. The version of Bloqade you are using. 20 | 21 | 5. The version of Python you are using. 22 | 23 | 6. The version of your operating system. 24 | -------------------------------------------------------------------------------- /docs/home/emulation.md: -------------------------------------------------------------------------------- 1 | # Emulation 2 | 3 | This page is a work in progress! 4 | -------------------------------------------------------------------------------- /docs/home/geometry.md: -------------------------------------------------------------------------------- 1 | # Geometry 2 | 3 | This page is a work in progress! 4 | -------------------------------------------------------------------------------- /docs/home/migration.md: -------------------------------------------------------------------------------- 1 | # Migrating to Bloqade Analog 2 | 3 | ## Introduction 4 | 5 | In order to make room for more features inside the Bloqade ecosystem, we have created a new package to take the place of the old `bloqade` package. The new package is called `bloqade-analog`. The old package `bloqade` will house a namespace package for other features such as our new Bloqade Digital package with support for circuit-based quantum computers! 6 | 7 | ## Installation 8 | 9 | You can install the package with `pip` in your Python environment of choice via: 10 | 11 | ```sh 12 | pip install bloqade-analog 13 | ``` 14 | 15 | ## Migration 16 | 17 | The new package is a drop-in replacement for the old one. You can simply replace `import bloqade` with `import bloqade.analog` or `from bloqade.analog import ...` in your code. Everything else should work as before. 18 | 19 | ## Example 20 | 21 | lets say your header of your python script looks like this: 22 | 23 | ```python 24 | from bloqade import var 25 | from bloqade.atom_arrangement import Square 26 | ... 27 | ``` 28 | You can simply replace it with: 29 | 30 | ```python 31 | from bloqade.analog import var 32 | from bloqade.analog.atom_arrangement import Square 33 | ... 34 | ``` 35 | 36 | ## Migrating old bloqade JSON files 37 | 38 | If you have old bloqade JSON files, you will not be able to directly deserialize them anymore because of the package restructuring. However, we have provided some tools to migrate those JSON files to be compatible with `bloqade-analog`. You can do this by running the following command in the command line for a one or more files: 39 | 40 | ```sh 41 | python -m bloqade.analog.migrate ... 42 | ``` 43 | With default arguments this will create a new file with the same name as the old file, but with `-analog` appended to the end of the filename. For example, if you have a file called `my_bloqade.json`, the new file will be called `my_bloqade-analog.json`. You can then use `load` to deserialize this file with the `bloqade-analog` package. There are other options for converting the file, such as setting the indent level for the output file or overwriting the old file. You can see all the options by running: 44 | 45 | ```sh 46 | python -m bloqade.analog.migrate --help 47 | ``` 48 | 49 | Another option is to use the migration tool in a python script: 50 | 51 | ```python 52 | from bloqade.analog.migrate import migrate 53 | 54 | # set the indent level for the output file 55 | indent: int = ... 56 | # set to True if you want to overwrite the old file, otherwise the new file will be created with -analog appended to the end of the filename 57 | overwrite: bool = ... 58 | f 59 | or filename in ["file1.json", "file2.json", ...]: 60 | migrate(filename, indent=indent, overwrite=overwrite) 61 | ``` 62 | This will migrate all the files in the list to the new format. 63 | 64 | 65 | ## Having trouble, comments, or concerns? 66 | 67 | Please open an issue on our [GitHub](https://github.com/QuEraComputing/bloqade-analog/issues) 68 | -------------------------------------------------------------------------------- /docs/home/submission.md: -------------------------------------------------------------------------------- 1 | # Submission 2 | 3 | This page is a work in progress! 4 | -------------------------------------------------------------------------------- /docs/home/visualization.md: -------------------------------------------------------------------------------- 1 | # Visualization 2 | 3 | This page is a work in progress! 4 | -------------------------------------------------------------------------------- /docs/home/waveforms.md: -------------------------------------------------------------------------------- 1 | # Waveforms 2 | 3 | This page is a work in progress! 4 | -------------------------------------------------------------------------------- /docs/javascripts/mathjax.js: -------------------------------------------------------------------------------- 1 | document$.subscribe(({ body }) => { 2 | renderMathInElement(body, { 3 | delimiters: [ 4 | { left: "$$", right: "$$", display: true }, 5 | { left: "$", right: "$", display: false }, 6 | { left: "\\(", right: "\\)", display: false }, 7 | { left: "\\[", right: "\\]", display: true } 8 | ], 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /docs/overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | {% if page.nb_url %} 5 | 6 | {% include ".icons/material/download.svg" %} 7 | 8 | {% endif %} 9 | 10 | {{ super() }} 11 | {% endblock content %} 12 | -------------------------------------------------------------------------------- /docs/reference/overview.md: -------------------------------------------------------------------------------- 1 | # Builder Overview 2 | 3 | You may have noticed from the [Getting Started](../home/getting_started.md) and [Tutorials](https://queracomputing.github.io/bloqade-analog-examples/latest/) 4 | that Bloqade uses this interesting, dot-intensive syntax. 5 | 6 | ```python 7 | from bloqade import start 8 | 9 | prog = start.add_position((0,0)).rydberg.rabi.amplitude.uniform.constant(1,1) 10 | ``` 11 | *Exhibit A: Lots of Dots* 12 | 13 | In fact, it might look remniscent of what you see in some gate-based Quantum Computing SDKs: 14 | 15 | ```python 16 | # this is strictly pseudocode 17 | circuit = init_qubits(n_qubits) 18 | # note the dots! 19 | circuit.x(0).z(1).cnot(0, 1)... 20 | ``` 21 | 22 | What's the deal with that? 23 | 24 | ## Syntax Motivations 25 | 26 | We call this syntax the *builder* or *builder syntax* and as its name implies, it is designed to let you build programs for Analog Hamiltonian Simulation hardware as easily and as straightforward as possible. 27 | 28 | The linear structure implies a natural hierarchy in how you think about targeting the various degrees of freedom (detuning, atom positions, Rabi amplitude, etc.) your program will have. In the beginning you have unrestricted access to all these degrees of freedom but in order to do something useful you need to: 29 | 30 | 1. Narrow down and explicitly identify **what** you want to control 31 | 2. Provide the instructions on **how** you want to control what your focused on 32 | 33 | *Context* is a strong component of the builder syntax, as you are both actively restricted from doing certain things that can introduce ambiguity based on where you are in your program and repeating the same action in different parts of the program yields different results. 34 | 35 | ## Visual Guides 36 | 37 | While we hope the Smart Documentation (the ability to instantly see all your next possible steps and their capabilities in your favorite IDE/IPython) is sufficient to get you where you need to go, we undestand it's particularly beneficial to get a high-level overview of things before diving in. 38 | 39 | The [Standard Representation](standard.md) is a nice flow chart that gives a high-level overview of the different steps and components in the builder syntax. 40 | -------------------------------------------------------------------------------- /docs/reference/standard.md: -------------------------------------------------------------------------------- 1 | 2 | # Build Workflow 3 | ```mermaid 4 | 5 | flowchart TD 6 | ProgramStart(["start"]) 7 | 8 | Geometry("Geometry or Lattice") 9 | 10 | Coupling["Coupling 11 | ----------- 12 | rydberg 13 | hyperfine"] 14 | 15 | Detuning["detuning"] 16 | Rabi["rabi"] 17 | 18 | Amplitude["amplitude"] 19 | Phase["phase"] 20 | 21 | SpaceModulation("SpatialModulation 22 | ---------------------- 23 | uniform 24 | scale 25 | location 26 | ") 27 | Waveform{"Waveform 28 | ------------ 29 | piecewise_linear 30 | piecewise_constant 31 | constant 32 | linear 33 | poly 34 | fn 35 | "} 36 | 37 | Options(["Options 38 | --------- 39 | assign 40 | batch_assign 41 | args 42 | parallelize 43 | "]) 44 | 45 | Services(["Services 46 | ---------- 47 | bloqade 48 | quera 49 | braket"]) 50 | 51 | QuEraBackends(["Backends 52 | ------------ 53 | mock 54 | cloud_mock 55 | aquila 56 | device"]) 57 | 58 | BraketBackends(["Backends 59 | ------------ 60 | aquila 61 | local_emulator"]) 62 | 63 | BloqadeBackends(["Backends 64 | ------------ 65 | python 66 | julia"]) 67 | 68 | Execution(" 69 | Execution hardware only 70 | ------------------------------- 71 | run_async() 72 | 73 | Hardware and simulation 74 | ------------------------------- 75 | run() 76 | __call__") 77 | 78 | ProgramStart -->|add_position| Geometry; 79 | Geometry --> Coupling; 80 | ProgramStart --> Coupling; 81 | 82 | Coupling --> Detuning; 83 | Coupling --> Rabi; 84 | 85 | Rabi --> Amplitude; 86 | Rabi --> Phase; 87 | 88 | Detuning --> SpaceModulation; 89 | Amplitude --> SpaceModulation; 90 | Phase --> SpaceModulation; 91 | 92 | SpaceModulation --> Waveform; 93 | 94 | Waveform --> Coupling; 95 | Waveform --> Services; 96 | Waveform --> Options; 97 | Options --> Services; 98 | 99 | Services -->|quera| QuEraBackends; 100 | Services -->|braket| BraketBackends; 101 | Services -->|bloqade| BloqadeBackends; 102 | QuEraBackends --> Execution; 103 | BraketBackends --> Execution; 104 | BloqadeBackends --> Execution; 105 | 106 | click ProgramStart "../bloqade/#bloqade.start"; 107 | click Geometry "../bloqade/atom_arrangement/"; 108 | click Coupling "../bloqade/builder/drive/"; 109 | click Detuning "../bloqade/builder/field/#bloqade.builder.field.Detuning"; 110 | click Rabi "../bloqade/builder/field/#bloqade.builder.field.Rabi"; 111 | click Amplitude "../bloqade/builder/field/#bloqade.builder.field.Amplitude"; 112 | click Phase "../bloqade/builder/field/#bloqade.builder.field.Phase"; 113 | click SpaceModulation "../bloqade/builder/spatial/"; 114 | click Waveform "../bloqade/builder/waveform/"; 115 | click Options "../bloqade/builder/pragmas/"; 116 | click Services "../bloqade/builder/backend/"; 117 | click QuEraBackends "../bloqade/builder/backend/quera/#bloqade.builder.backend.quera.QuEraDeviceRoute"; 118 | click BraketBackends "../bloqade/builder/backend/braket/#bloqade.builder.backend.braket.BraketDeviceRoute"; 119 | click BloqadeBackends "../bloqade/builder/backend/bloqade/#bloqade.builder.backend.bloqade.BloqadeBackend"; 120 | click Execution "../bloqade/ir/routine/braket/#bloqade.ir.routine.braket.BraketRoutine"; 121 | 122 | ``` 123 | -------------------------------------------------------------------------------- /docs/scripts/gen_ref_nav.py: -------------------------------------------------------------------------------- 1 | """Generate the code reference pages and navigation.""" 2 | 3 | from pathlib import Path 4 | 5 | import mkdocs_gen_files 6 | 7 | SRC_PATH = "src" 8 | 9 | skip_keywords = [ 10 | "julia", ## [KHW] skip for now since we didn't have julia codegen rdy 11 | "builder/base", ## hiding from user 12 | "builder/terminate", ## hiding from user 13 | "ir/tree_print", ## hiding from user 14 | "ir/visitor", ## hiding from user 15 | "codegen/", ## hiding from user 16 | "builder/factory", ## hiding from user 17 | "builder_old", ## deprecated from user 18 | "task_old", ## deprecated from user 19 | "visualization", ## hiding from user 20 | "submission/capabilities", ## hiding from user 21 | "submission/quera_api_client", 22 | ] 23 | 24 | nav = mkdocs_gen_files.Nav() 25 | for path in sorted(Path(SRC_PATH).rglob("*.py")): 26 | module_path = path.relative_to(SRC_PATH).with_suffix("") 27 | doc_path = path.relative_to(SRC_PATH).with_suffix(".md") 28 | full_doc_path = Path("reference", doc_path) 29 | 30 | iskip = False 31 | 32 | for kwrd in skip_keywords: 33 | if kwrd in str(doc_path): 34 | iskip = True 35 | break 36 | if iskip: 37 | print("[Ignore]", str(doc_path)) 38 | continue 39 | 40 | print("[>]", str(doc_path)) 41 | 42 | parts = tuple(module_path.parts) 43 | 44 | if parts[-1] == "__init__": 45 | parts = parts[:-1] 46 | doc_path = doc_path.with_name("index.md") 47 | full_doc_path = full_doc_path.with_name("index.md") 48 | elif parts[-1].startswith("_"): 49 | continue 50 | 51 | nav[parts] = doc_path.as_posix() 52 | with mkdocs_gen_files.open(full_doc_path, "w") as fd: 53 | ident = ".".join(parts) 54 | fd.write(f"::: {ident}") 55 | 56 | mkdocs_gen_files.set_edit_path(full_doc_path, ".." / path) 57 | 58 | with mkdocs_gen_files.open("reference/SUMMARY.txt", "w") as nav_file: 59 | nav_file.writelines(nav.build_literate_nav()) 60 | -------------------------------------------------------------------------------- /docs/stylesheets/extra.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --md-primary-fg-color: #6437FF; 3 | --md-accent-fg-color: #6437FF; 4 | } 5 | 6 | #logo_light_mode { 7 | display: var(--md-footer-logo-light-mode); 8 | } 9 | 10 | #logo_dark_mode { 11 | display: var(--md-footer-logo-dark-mode); 12 | } 13 | 14 | [data-md-color-scheme="slate"] { 15 | --md-footer-logo-dark-mode: block; 16 | --md-footer-logo-light-mode: none; 17 | } 18 | 19 | [data-md-color-scheme="default"] { 20 | --md-footer-logo-dark-mode: none; 21 | --md-footer-logo-light-mode: block; 22 | } 23 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | coverage-run: 2 | coverage run -m pytest tests 3 | 4 | coverage-xml: 5 | coverage xml 6 | 7 | coverage-html: 8 | coverage html 9 | 10 | coverage-report: 11 | coverage report 12 | 13 | coverage-open: 14 | open htmlcov/index.html 15 | 16 | coverage: coverage-run coverage-xml coverage-report 17 | 18 | doc: 19 | mkdocs serve 20 | 21 | doc-build: 22 | mkdocs build 23 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "bloqade-analog" 3 | version = "0.16.5" 4 | description = "Analog neutral atom software development kit" 5 | authors = [ 6 | { name = "Roger-luo", email = "rluo@quera.com" }, 7 | { name = "kaihsin", email="khwu@quera.com" }, 8 | { name = "weinbe58", email="pweinberg@quera.com"}, 9 | { name = "johnzl-777", email="jlong@quera.com"}, 10 | ] 11 | classifiers = [ 12 | "Development Status :: 3 - Alpha", 13 | "License :: OSI Approved :: Apache Software License", 14 | "Operating System :: OS Independent", 15 | "Programming Language :: Python", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Programming Language :: Python :: 3.12" 20 | ] 21 | dependencies = [ 22 | "juliacall>=0.9.14", 23 | "numpy>=1.26.0", 24 | "pydantic>=2.0", 25 | "scipy>=1.9.3", 26 | "pandas>=2.1.0", 27 | "bokeh>=3.2.2", 28 | "tabulate>=0.9.0", 29 | "requests-sigv4>=0.1.6", 30 | "amazon-braket-sdk>=1.78.0", 31 | "plotext>=5.2.8", 32 | "beartype>=0.15.0", 33 | "simplejson>=3.19.1", 34 | "plum-dispatch>=2.2.2", 35 | "numba>=0.58.0", 36 | ] 37 | requires-python = ">=3.9,<3.13" 38 | readme = "README.md" 39 | license = {text = "Apache License 2.0"} 40 | 41 | [build-system] 42 | requires = ["hatchling"] 43 | build-backend = "hatchling.build" 44 | 45 | [tool.hatch.metadata] 46 | allow-direct-references = true 47 | 48 | [tool.hatch.build.targets.wheel] 49 | packages = ["src/bloqade"] 50 | 51 | [tool.black] 52 | line-length = 88 53 | 54 | [tool.isort] 55 | profile = "black" 56 | combine_as_imports = true 57 | multi_line_output = 3 58 | length_sort = true 59 | src_paths = ["src/bloqade"] 60 | 61 | [tool.ruff] 62 | exclude = [ 63 | ".bzr", 64 | ".direnv", 65 | ".eggs", 66 | ".git", 67 | ".hg", 68 | ".mypy_cache", 69 | ".nox", 70 | ".pants.d", 71 | ".pytype", 72 | ".ruff_cache", 73 | ".svn", 74 | ".tox", 75 | ".venv", 76 | "__pypackages__", 77 | "_build", 78 | "buck-out", 79 | "build", 80 | "dist", 81 | "node_modules", 82 | "venv", 83 | ] 84 | dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" 85 | # Assume Python 3.9. 86 | target-version = "py39" 87 | 88 | [tool.jupytext] 89 | formats = "ipynb,py:percent" 90 | hide_notebook_metadata = false 91 | 92 | [dependency-groups] 93 | dev = [ 94 | "black>=24.10.0", 95 | "coverage>=7.6.10", 96 | "ipykernel>=6.29.5", 97 | "ipython>=8.18.1", 98 | "jupyter>=1.1.1", 99 | "jupytext>=1.16.6", 100 | "mypy>=1.14.1", 101 | "pre-commit>=4.0.1", 102 | "pytest-recording>=0.13.2", 103 | "pytest>=8.3.4", 104 | "ruff>=0.8.5", 105 | "vcrpy>=7.0.0", 106 | "pyinstrument>=5.0.0", 107 | "scikit-optimize>=0.10.2", 108 | "matplotlib>=3.9.4", 109 | "icecream>=2.1.3", 110 | "tqdm>=4.67.1", 111 | "rust-just>=1.39.0", 112 | ] 113 | doc = [ 114 | "mkdocs>=1.5.3", 115 | "mkdocs-material>=9.1.9", 116 | "mkdocstrings[python]>=0.21.2", 117 | "mkdocs-minify-plugin>=0.6.4", 118 | "mkdocs-jupyter>=0.24.1", 119 | "mkdocs-gen-files>=0.5.0", 120 | "mkdocs-literate-nav>=0.6.0", 121 | "mike>=1.1.2", 122 | ] 123 | -------------------------------------------------------------------------------- /src/bloqade/analog/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | __import__("pkg_resources").declare_namespace(__name__) 3 | except ImportError: 4 | __path__ = __import__("pkgutil").extend_path(__path__, __name__) 5 | 6 | import importlib.metadata 7 | 8 | import bloqade.analog.ir as _ir 9 | from bloqade.analog.ir import ( 10 | Literal, 11 | Variable, 12 | var, 13 | cast, 14 | start, 15 | to_waveform as waveform, 16 | ) 17 | from bloqade.analog.factory import ( 18 | linear, 19 | constant, 20 | rydberg_h, 21 | get_capabilities, 22 | piecewise_linear, 23 | piecewise_constant, 24 | ) 25 | from bloqade.analog.constants import RB_C6 26 | from bloqade.analog.serialize import load, save, dumps, loads 27 | 28 | __version__ = importlib.metadata.version("bloqade-analog") 29 | 30 | 31 | def tree_depth(depth: int = None): 32 | """Setting globally maximum depth for tree printing 33 | 34 | If `depth=None`, return current depth. 35 | If `depth` is provided, setting current depth to `depth` 36 | 37 | Args: 38 | depth (int, optional): the user specified depth. Defaults to None. 39 | 40 | Returns: 41 | int: current updated depth 42 | """ 43 | if depth is not None: 44 | _ir.tree_print.MAX_TREE_DEPTH = depth 45 | return _ir.tree_print.MAX_TREE_DEPTH 46 | 47 | 48 | __all__ = [ 49 | "RB_C6", 50 | "start", 51 | "var", 52 | "cast", 53 | "Variable", 54 | "Literal", 55 | "piecewise_linear", 56 | "piecewise_constant", 57 | "linear", 58 | "constant", 59 | "tree_depth", 60 | "load", 61 | "save", 62 | "loads", 63 | "dumps", 64 | "rydberg_h", 65 | "waveform", 66 | "get_capabilities", 67 | ] 68 | -------------------------------------------------------------------------------- /src/bloqade/analog/atom_arrangement.py: -------------------------------------------------------------------------------- 1 | from bloqade.analog.ir.location import ( 2 | Lieb, 3 | Chain, 4 | Kagome, 5 | Square, 6 | Honeycomb, 7 | Triangular, 8 | Rectangular, 9 | AtomArrangement, 10 | ListOfLocations, 11 | ) 12 | 13 | __all__ = [ 14 | "AtomArrangement", 15 | "Chain", 16 | "Square", 17 | "Rectangular", 18 | "Honeycomb", 19 | "Triangular", 20 | "Lieb", 21 | "Kagome", 22 | "ListOfLocations", 23 | ] 24 | -------------------------------------------------------------------------------- /src/bloqade/analog/builder/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/builder/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/builder/args.py: -------------------------------------------------------------------------------- 1 | from beartype import beartype 2 | from beartype.typing import List, Union, Optional 3 | 4 | from bloqade.analog.ir.scalar import Variable 5 | from bloqade.analog.builder.base import Builder 6 | from bloqade.analog.builder.backend import BackendRoute 7 | from bloqade.analog.builder.pragmas import Parallelizable 8 | 9 | 10 | class Args(Parallelizable, BackendRoute, Builder): 11 | @beartype 12 | def __init__( 13 | self, order: List[Union[str, Variable]], parent: Optional[Builder] = None 14 | ) -> None: 15 | super().__init__(parent) 16 | self._order = tuple([o.name if isinstance(o, Variable) else o for o in order]) 17 | -------------------------------------------------------------------------------- /src/bloqade/analog/builder/backend/__init__.py: -------------------------------------------------------------------------------- 1 | from bloqade.analog.builder.backend.quera import QuEraService 2 | from bloqade.analog.builder.backend.braket import BraketService 3 | from bloqade.analog.builder.backend.bloqade import BloqadeService 4 | 5 | 6 | class BackendRoute(QuEraService, BraketService, BloqadeService): 7 | """ 8 | Specify the backend to run your program on via a string 9 | (versus more formal builder syntax) of specifying the vendor/product first 10 | (Bloqade/Braket) and narrowing it down 11 | (e.g: ...device("quera.aquila") versus ...quera.aquila()) 12 | - You can pass the following arguments: 13 | - `"braket.aquila"` 14 | - `"braket.local_emulator"` 15 | - `"bloqade.python"` 16 | - `"bloqade.julia"` 17 | 18 | """ 19 | 20 | def device(self, name: str, *args, **kwargs): 21 | if name == "quera.aquila": 22 | dev = self.quera.aquila 23 | elif name == "braket.aquila": 24 | dev = self.braket.aquila 25 | elif name == "braket.local_emulator": 26 | dev = self.braket.local_emulator 27 | elif name == "bloqade.python": 28 | dev = self.bloqade.python 29 | elif name == "bloqade.julia": 30 | dev = self.bloqade.julia 31 | else: 32 | raise ValueError(f"Unknown device: {name}") 33 | return dev(*args, **kwargs) 34 | -------------------------------------------------------------------------------- /src/bloqade/analog/builder/backend/bloqade.py: -------------------------------------------------------------------------------- 1 | from bloqade.analog.builder.base import Builder 2 | 3 | 4 | class BloqadeService(Builder): 5 | @property 6 | def bloqade(self): 7 | """ 8 | Specify the Bloqade backend. 9 | 10 | - Possible Next Steps: 11 | - `...bloqade.python()`: target submission to the Bloqade python backend 12 | - `...bloqade.julia()`: (CURRENTLY NOT IMPLEMENTED!)target 13 | submission to the Bloqade.jl backend 14 | """ 15 | return BloqadeDeviceRoute(self) 16 | 17 | 18 | class BloqadeDeviceRoute(Builder): 19 | def python(self): 20 | """ 21 | Specify the Bloqade Python backend. 22 | 23 | - Possible Next Steps: 24 | - `...python().run(shots)`: 25 | to submit to the python emulator and await results 26 | """ 27 | return self.parse().bloqade.python() 28 | 29 | def julia(self, solver: str, nthreads: int = 1): 30 | raise NotImplementedError 31 | -------------------------------------------------------------------------------- /src/bloqade/analog/builder/backend/braket.py: -------------------------------------------------------------------------------- 1 | from beartype.typing import TYPE_CHECKING 2 | 3 | from bloqade.analog.builder.base import Builder 4 | 5 | if TYPE_CHECKING: 6 | from bloqade.analog.ir.routine.braket import ( 7 | BraketHardwareRoutine, 8 | BraketLocalEmulatorRoutine, 9 | ) 10 | 11 | 12 | class BraketService(Builder): 13 | @property 14 | def braket(self): 15 | """ 16 | Specify the Braket backend. This allows you to access the AWS Braket local 17 | emulator OR go submit things to QuEra hardware on AWS Braket service. 18 | 19 | - Possible Next Steps are: 20 | - `...braket.aquila()`: target submission to the QuEra Aquila QPU 21 | - `...braket.local_emulator()`: target submission to the Braket 22 | local emulator 23 | """ 24 | return BraketDeviceRoute(self) 25 | 26 | 27 | class BraketDeviceRoute(Builder): 28 | def device(self, device_arn) -> "BraketHardwareRoutine": 29 | """ 30 | Specify QPU based on the device ARN on Braket to submit your program to. 31 | 32 | The number of shots you specify in the subsequent `.run` method will either: 33 | - dictate the number of times your program is run 34 | - dictate the number of times per parameter your program is run if 35 | you have a variable with batch assignments/intend to conduct 36 | a parameter sweep 37 | 38 | 39 | - Possible next steps are: 40 | - `...device(arn).run(shots)`: To submit to hardware and WAIT for 41 | results (blocking) 42 | - `...device(arn).run_async(shots)`: To submit to hardware and immediately 43 | allow for other operations to occur 44 | """ 45 | return self.parse().braket.device(device_arn) 46 | 47 | def aquila(self) -> "BraketHardwareRoutine": 48 | """ 49 | Specify QuEra's Aquila QPU on Braket to submit your program to. 50 | 51 | The number of shots you specify in the subsequent `.run` method will either: 52 | - dictate the number of times your program is run 53 | - dictate the number of times per parameter your program is run if 54 | you have a variable with batch assignments/intend to conduct 55 | a parameter sweep 56 | 57 | 58 | - Possible next steps are: 59 | - `...aquila().run(shots)`: To submit to hardware and WAIT for 60 | results (blocking) 61 | - `...aquila().run_async(shots)`: To submit to hardware and immediately 62 | allow for other operations to occur 63 | """ 64 | return self.parse().braket.aquila() 65 | 66 | def local_emulator(self) -> "BraketLocalEmulatorRoutine": 67 | """ 68 | Specify the Braket local emulator to submit your program to. 69 | 70 | - The number of shots you specify in the subsequent `.run` method will either: 71 | - dictate the number of times your program is run 72 | - dictate the number of times per parameter your program is run if 73 | you have a variable with batch assignments/intend to 74 | conduct a parameter sweep 75 | - Possible next steps are: 76 | - `...local_emulator().run(shots)`: to submit to the emulator 77 | and await results 78 | 79 | """ 80 | return self.parse().braket.local_emulator() 81 | -------------------------------------------------------------------------------- /src/bloqade/analog/builder/base.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union, Optional 2 | from numbers import Real 3 | 4 | from bloqade.analog.builder.parse.trait import Show, Parse 5 | 6 | ParamType = Union[Real, List[Real]] 7 | 8 | 9 | class Builder(Parse, Show): 10 | def __init__( 11 | self, 12 | parent: Optional["Builder"] = None, 13 | ) -> None: 14 | self.__parent__ = parent 15 | 16 | def __str__(self): 17 | return str(self.parse()) 18 | -------------------------------------------------------------------------------- /src/bloqade/analog/builder/drive.py: -------------------------------------------------------------------------------- 1 | from bloqade.analog.builder.coupling import Rydberg, Hyperfine 2 | 3 | 4 | class Drive: 5 | @property 6 | def rydberg(self) -> Rydberg: 7 | """ 8 | Address the Rydberg level coupling in your program. 9 | 10 | - Next possible steps to build your program are specifying the 11 | [`Rabi`][bloqade.builder.field.Rabi] field or 12 | [`Detuning`][bloqade.builder.field.Detuning] field. 13 | - `...rydberg.rabi`: for Rabi field 14 | - `...rydberg.detuning`: for Detuning field 15 | - In the absence of a field you the value is set to zero by default. 16 | """ 17 | return Rydberg(self) 18 | 19 | @property 20 | def hyperfine(self) -> Hyperfine: 21 | """ 22 | Address the Hyperfine level coupling in your program. 23 | 24 | - Next possible steps to build your program are specifying the 25 | [`Rabi`][bloqade.builder.field.Rabi] field or 26 | [`Detuning`][bloqade.builder.field.Detuning] field. 27 | - `...hyperfine.rabi`: for Rabi field 28 | - `...hyperfine.detuning`: for Detuning field 29 | - In the absence of a field you the value is set to zero by default. 30 | 31 | """ 32 | return Hyperfine(self) 33 | -------------------------------------------------------------------------------- /src/bloqade/analog/builder/parallelize.py: -------------------------------------------------------------------------------- 1 | from beartype import beartype 2 | from beartype.typing import Optional 3 | 4 | from bloqade.analog.ir import cast 5 | from bloqade.analog.builder.base import Builder 6 | from bloqade.analog.builder.typing import LiteralType 7 | from bloqade.analog.builder.backend import BackendRoute 8 | 9 | 10 | class Parallelize(BackendRoute, Builder): 11 | @beartype 12 | def __init__( 13 | self, cluster_spacing: LiteralType, parent: Optional[Builder] = None 14 | ) -> None: 15 | super().__init__(parent) 16 | self._cluster_spacing = cast(cluster_spacing) 17 | -------------------------------------------------------------------------------- /src/bloqade/analog/builder/parse/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/builder/parse/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/builder/route.py: -------------------------------------------------------------------------------- 1 | from bloqade.analog.builder.drive import Drive 2 | from bloqade.analog.builder.field import Rabi, Field 3 | from bloqade.analog.builder.backend import BackendRoute 4 | from bloqade.analog.builder.pragmas import ( 5 | AddArgs, 6 | Assignable, 7 | Parallelizable, 8 | BatchAssignable, 9 | ) 10 | from bloqade.analog.builder.coupling import LevelCoupling 11 | 12 | 13 | class PulseRoute(Drive, LevelCoupling, Field, Rabi): 14 | pass 15 | 16 | 17 | class PragmaRoute(Assignable, BatchAssignable, Parallelizable, AddArgs, BackendRoute): 18 | pass 19 | 20 | 21 | class WaveformRoute(PulseRoute, PragmaRoute): 22 | pass 23 | -------------------------------------------------------------------------------- /src/bloqade/analog/builder/sequence_builder.py: -------------------------------------------------------------------------------- 1 | from bloqade.analog.builder.base import Builder 2 | from bloqade.analog.builder.route import PragmaRoute 3 | from bloqade.analog.ir.control.sequence import SequenceExpr 4 | 5 | 6 | class SequenceBuilder(PragmaRoute, Builder): 7 | def __init__(self, sequence: SequenceExpr, parent: Builder): 8 | super().__init__(parent) 9 | self._sequence = sequence 10 | -------------------------------------------------------------------------------- /src/bloqade/analog/builder/spatial.py: -------------------------------------------------------------------------------- 1 | from beartype import beartype 2 | from beartype.typing import TYPE_CHECKING, List, Union, Optional 3 | 4 | from bloqade.analog.builder.base import Builder 5 | from bloqade.analog.builder.typing import ScalarType, LiteralType 6 | from bloqade.analog.builder.waveform import WaveformAttachable 7 | 8 | if TYPE_CHECKING: 9 | from bloqade.analog.ir.control.field import ( 10 | RunTimeVector, 11 | ScaledLocations, 12 | UniformModulation, 13 | AssignedRunTimeVector, 14 | ) 15 | 16 | 17 | class SpatialModulation(WaveformAttachable): 18 | pass 19 | 20 | 21 | class Uniform(SpatialModulation): 22 | """ 23 | The node specify a uniform spacial modulation. Which is ready to apply waveform 24 | (See [`Waveform`][bloqade.builder.waveform] for available waveform options) 25 | 26 | Examples: 27 | 28 | - To hit this node from the start node: 29 | 30 | >>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)]) 31 | >>> loc = reg.rydberg.detuning.uniform 32 | 33 | - Apply Linear waveform: 34 | 35 | >>> wv = bloqade.ir.Linear(start=0,stop=1,duration=0.5) 36 | >>> reg = bloqade.start.add_position([(0,0),(1,1),(2,2),(3,3)]) 37 | >>> loc = reg.rydberg.detuning.uniform.apply(wv) 38 | 39 | """ 40 | 41 | def __bloqade_ir__(self) -> "UniformModulation": 42 | from bloqade.analog.ir import Uniform 43 | 44 | return Uniform 45 | 46 | 47 | class Location(SpatialModulation): 48 | @beartype 49 | def __init__( 50 | self, 51 | labels: List[int], 52 | scales: List[ScalarType], 53 | parent: Optional[Builder] = None, 54 | ) -> None: 55 | from bloqade.analog.ir.scalar import cast 56 | from bloqade.analog.ir.control.field import Location 57 | 58 | super().__init__(parent) 59 | self._scaled_locations = { 60 | Location(label): cast(scale) for label, scale in zip(labels, scales) 61 | } 62 | 63 | def __bloqade_ir__(self) -> "ScaledLocations": 64 | from bloqade.analog.ir import ScaledLocations 65 | 66 | return ScaledLocations(self._scaled_locations) 67 | 68 | 69 | class Scale(SpatialModulation): 70 | @beartype 71 | def __init__( 72 | self, 73 | name_or_list: Union[str, List[LiteralType]], 74 | parent: Optional[Builder] = None, 75 | ) -> None: 76 | super().__init__(parent) 77 | self._name_or_list = name_or_list 78 | 79 | def __bloqade_ir__(self) -> Union["RunTimeVector", "AssignedRunTimeVector"]: 80 | from bloqade.analog.ir import RunTimeVector, AssignedRunTimeVector 81 | 82 | if isinstance(self._name_or_list, str): 83 | return RunTimeVector(self._name_or_list) 84 | else: 85 | return AssignedRunTimeVector(None, self._name_or_list) 86 | -------------------------------------------------------------------------------- /src/bloqade/analog/builder/start.py: -------------------------------------------------------------------------------- 1 | from beartype import beartype 2 | 3 | from bloqade.analog.builder.base import Builder 4 | from bloqade.analog.builder.drive import Drive 5 | from bloqade.analog.ir.control.sequence import SequenceExpr 6 | from bloqade.analog.builder.sequence_builder import SequenceBuilder 7 | 8 | 9 | class ProgramStart(Drive, Builder): 10 | """ 11 | ProgramStart is the base class for a starting/entry node for building a program. 12 | """ 13 | 14 | @beartype 15 | def apply(self, sequence: SequenceExpr) -> SequenceBuilder: 16 | """ 17 | Apply a pre-built sequence to a program. 18 | 19 | This allows you to build a program independent of any geometry 20 | and then `apply` the program to said geometry. Or, if you have a 21 | program you would like to try on multiple geometries you can 22 | trivially do so with this. 23 | 24 | Example Usage: 25 | ``` 26 | >>> from numpy import pi 27 | >>> seq = start.rydberg.rabi.amplitude.constant(2.0 * pi, 4.5) 28 | # choose a geometry of interest to apply the program on 29 | >>> from bloqade.analog.atom_arrangement import Chain, Kagome 30 | >>> complete_program = Chain(10).apply(seq) 31 | # you can .apply to as many geometries as you like 32 | >>> another_complete_program = Kagome(3).apply(seq) 33 | ``` 34 | 35 | - From here you can now do: 36 | - `...assign(assignments).bloqade`: select the bloqade 37 | local emulator backend 38 | - `...assign(assignments).braket`: select braket 39 | local emulator or QuEra hardware 40 | - `...assign(assignments).device(specifier_string)`: select 41 | backend by specifying a string 42 | - Assign multiple values to a single variable for a parameter sweep: 43 | - `...assign(assignments).batch_assign(assignments)`: 44 | - Parallelize the program register, duplicating the geometry and waveform 45 | sequence to take advantage of all available 46 | space/qubits on the QPU: 47 | - `...assign(assignments).parallelize(cluster_spacing)` 48 | - Defer value assignment of certain variables to runtime: 49 | - `...assign(assignments).args([previously_defined_vars])` 50 | 51 | """ 52 | return SequenceBuilder(sequence, self) 53 | -------------------------------------------------------------------------------- /src/bloqade/analog/builder/typing.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from numbers import Real 3 | 4 | from beartype.typing import List, Union 5 | 6 | from bloqade.analog.ir.scalar import Scalar 7 | 8 | ScalarType = Union[Real, Decimal, str, Scalar] 9 | LiteralType = Union[Real, Decimal] 10 | ParamType = Union[LiteralType, List[LiteralType]] 11 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/compiler/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/analysis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/compiler/analysis/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/analysis/common/__init__.py: -------------------------------------------------------------------------------- 1 | from .is_constant import IsConstant 2 | from .check_slices import CheckSlices 3 | from .is_hyperfine import IsHyperfineSequence 4 | from .scan_channels import ScanChannels 5 | from .scan_variables import ScanVariables 6 | from .assignment_scan import AssignmentScan 7 | 8 | __all__ = [ 9 | "AssignmentScan", 10 | "CheckSlices", 11 | "IsConstant", 12 | "IsHyperfineSequence", 13 | "ScanChannels", 14 | "ScanVariables", 15 | ] 16 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/analysis/common/assignment_scan.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | from decimal import Decimal 3 | 4 | import bloqade.analog.ir.control.waveform as waveform 5 | from bloqade.analog.ir.visitor import BloqadeIRVisitor 6 | from bloqade.analog.builder.typing import LiteralType 7 | 8 | 9 | class AssignmentScan(BloqadeIRVisitor): 10 | def __init__(self, assignments: Dict[str, LiteralType] = {}): 11 | self.assignments = dict(assignments) 12 | 13 | def visit_waveform_Record(self, node: waveform.Record): 14 | self.visit(node.waveform) 15 | duration = node.waveform.duration(**self.assignments) 16 | var = node.var 17 | 18 | if node.side is waveform.Side.Right: 19 | value = node.waveform.eval_decimal(duration, **self.assignments) 20 | else: 21 | value = node.waveform.eval_decimal(Decimal(0), **self.assignments) 22 | 23 | self.assignments[var.name] = value 24 | 25 | def scan(self, node) -> Dict[str, LiteralType]: 26 | self.visit(node) 27 | return self.assignments 28 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/analysis/common/check_slices.py: -------------------------------------------------------------------------------- 1 | from beartype.typing import Union 2 | 3 | from bloqade.analog.ir.control import pulse, sequence, waveform 4 | from bloqade.analog.ir.visitor import BloqadeIRVisitor 5 | 6 | 7 | class CheckSlices(BloqadeIRVisitor): 8 | def check_slice(self, node: Union[waveform.Slice, pulse.Slice, sequence.Slice]): 9 | start_time = node.start() 10 | stop_time = node.stop() 11 | 12 | duration = node.waveform.duration() 13 | 14 | if start_time < 0: 15 | raise ValueError(f"Start time {start_time} is negative") 16 | 17 | if stop_time < 0: 18 | raise ValueError(f"Stop time {stop_time} is negative") 19 | 20 | if start_time > stop_time: 21 | raise ValueError( 22 | f"Start time {start_time} is greater than stop time {stop_time}" 23 | ) 24 | 25 | if stop_time > duration: 26 | raise ValueError( 27 | f"Stop time {stop_time} is greater than waveform duration {duration}" 28 | ) 29 | 30 | def visit_waveform_Slice(self, node: waveform.Slice): 31 | self.visit(node.waveform) 32 | self.check_slice(node) 33 | 34 | def visit_pulse_Slice(self, node: pulse.Slice): 35 | self.visit(node.pulse) 36 | self.check_slice(node) 37 | 38 | def visit_sequence_Slice(self, node: sequence.Slice): 39 | self.visit(node.sequence) 40 | self.check_slice(node) 41 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/analysis/common/is_hyperfine.py: -------------------------------------------------------------------------------- 1 | from beartype.typing import Any 2 | 3 | import bloqade.analog.ir.control.sequence as sequence 4 | from bloqade.analog.ir.visitor import BloqadeIRVisitor 5 | 6 | 7 | class IsHyperfineSequence(BloqadeIRVisitor): 8 | def __init__(self): 9 | self.is_hyperfine = False 10 | 11 | def generic_visit(self, node: Any) -> Any: 12 | # skip visiting children if we already know there are hyperfine pulses 13 | if self.is_hyperfine: 14 | return 15 | 16 | super().generic_visit(node) 17 | 18 | def visit_sequence_Sequence(self, node: sequence.Sequence) -> Any: 19 | self.is_hyperfine = self.is_hyperfine or sequence.hyperfine in node.pulses 20 | 21 | def emit(self, node) -> bool: 22 | self.visit(node) 23 | return self.is_hyperfine 24 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/analysis/common/scan_channels.py: -------------------------------------------------------------------------------- 1 | import bloqade.analog.ir.control.field as field 2 | import bloqade.analog.ir.control.pulse as pulse 3 | import bloqade.analog.ir.control.sequence as sequence 4 | from bloqade.analog.ir.visitor import BloqadeIRVisitor 5 | 6 | 7 | class ScanChannels(BloqadeIRVisitor): 8 | def __init__(self): 9 | self.channels = None 10 | 11 | def visit_sequence_Sequence(self, node: sequence.Sequence): 12 | for lc, p in node.pulses.items(): 13 | saved = dict() if self.channels is None else dict(self.channels) 14 | self.channels = saved.get(lc, {}) 15 | self.visit(p) 16 | saved[lc] = self.channels 17 | self.channels = dict(saved) 18 | 19 | def visit_pulse_Pulse(self, node: pulse.Pulse): 20 | for fn, f in node.fields.items(): 21 | saved = dict() if self.channels is None else dict(self.channels) 22 | self.channels = saved.get(fn, set()) 23 | self.visit(f) 24 | saved[fn] = self.channels 25 | self.channels = dict(saved) 26 | 27 | def visit_field_Field(self, node: field.Field): 28 | self.channels = set() if self.channels is None else self.channels 29 | self.channels = set(node.drives.keys()) | self.channels 30 | 31 | def scan(self, node): 32 | self.visit(node) 33 | return self.channels 34 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/analysis/common/scan_variables.py: -------------------------------------------------------------------------------- 1 | from beartype.typing import FrozenSet 2 | from pydantic.v1.dataclasses import dataclass 3 | 4 | import bloqade.analog.ir.scalar as scalar 5 | import bloqade.analog.ir.control.field as field 6 | from bloqade.analog.ir.visitor import BloqadeIRVisitor 7 | 8 | 9 | @dataclass(frozen=True) 10 | class ScanVariableResults: 11 | scalar_vars: FrozenSet[str] 12 | vector_vars: FrozenSet[str] 13 | assigned_scalar_vars: FrozenSet[str] 14 | assigned_vector_vars: FrozenSet[str] 15 | 16 | @property 17 | def is_assigned(self) -> bool: 18 | return len(self.scalar_vars) == 0 or len(self.vector_vars) == 0 19 | 20 | 21 | class ScanVariables(BloqadeIRVisitor): 22 | def __init__(self): 23 | self.scalar_vars = set() 24 | self.assign_scalar_vars = set() 25 | self.vector_vars = set() 26 | self.assigned_vector_vars = set() 27 | 28 | def visit_scalar_Variable(self, node: scalar.Variable): 29 | self.scalar_vars.add(node.name) 30 | 31 | def visit_scalar_AssignedVariable(self, node: scalar.AssignedVariable): 32 | self.assign_scalar_vars.add(node.name) 33 | 34 | def visit_field_RunTimeVector(self, node: field.RunTimeVector): 35 | self.vector_vars.add(node.name) 36 | 37 | def visit_field_AssignedRunTimeVector(self, node: field.AssignedRunTimeVector): 38 | if node.name is not None: # skip literal vectors 39 | self.assigned_vector_vars.add(node.name) 40 | 41 | def scan(self, node) -> ScanVariableResults: 42 | self.visit(node) 43 | return ScanVariableResults( 44 | self.scalar_vars, 45 | self.vector_vars, 46 | self.assign_scalar_vars, 47 | self.assigned_vector_vars, 48 | ) 49 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/analysis/hardware/__init__.py: -------------------------------------------------------------------------------- 1 | from .lattice import BasicLatticeValidation 2 | from .channels import ValidateChannels 3 | from .piecewise_linear import ValidatePiecewiseLinearChannel 4 | from .piecewise_constant import ValidatePiecewiseConstantChannel 5 | 6 | __all__ = [ 7 | "BasicLatticeValidation", 8 | "ValidateChannels", 9 | "ValidatePiecewiseLinearChannel", 10 | "ValidatePiecewiseConstantChannel", 11 | ] 12 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/analysis/hardware/lattice.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from bloqade.analog.ir import location 4 | from bloqade.analog.ir.visitor import BloqadeIRVisitor 5 | from bloqade.analog.submission.ir.capabilities import QuEraCapabilities 6 | 7 | 8 | class BasicLatticeValidation(BloqadeIRVisitor): 9 | """This visitor checks that the AtomArrangement is within the bounds of 10 | the lattice and that the number of sites is within the maximum number of sites. 11 | 12 | """ 13 | 14 | def __init__(self, capabilities: QuEraCapabilities): 15 | self.capabilities = capabilities 16 | 17 | def generic_visit(self, node): 18 | # dispatch all AtomArrangement nodes to visit_register 19 | # otherwise dispatch to super 20 | if isinstance(node, location.AtomArrangement): 21 | self.visit_register(node) 22 | 23 | super().generic_visit(node) 24 | 25 | def visit_register(self, node: location.AtomArrangement): 26 | # default visitor for AtomArrangement 27 | 28 | height_max = self.capabilities.capabilities.lattice.area.height / Decimal( 29 | "1e-6" 30 | ) 31 | width_max = self.capabilities.capabilities.lattice.area.width / Decimal("1e-6") 32 | number_sites_max = ( 33 | self.capabilities.capabilities.lattice.geometry.number_sites_max 34 | ) 35 | 36 | sites = [] 37 | x_min = Decimal("inf") 38 | x_max = Decimal("-inf") 39 | y_min = Decimal("inf") 40 | y_max = Decimal("-inf") 41 | 42 | for location_info in node.enumerate(): 43 | site = tuple(ele() for ele in location_info.position) 44 | sites.append(site) 45 | 46 | x_min = min(x_min, site[0]) 47 | x_max = max(x_max, site[0]) 48 | y_min = min(y_min, site[1]) 49 | y_max = max(y_max, site[1]) 50 | 51 | if len(sites) > number_sites_max: 52 | raise ValueError( 53 | "Too many sites in AtomArrangement, found " 54 | f"{len(sites)} but maximum is {number_sites_max}" 55 | ) 56 | 57 | if x_max - x_min > width_max: 58 | raise ValueError( 59 | "AtomArrangement too wide, found " 60 | f"{x_max - x_min} but maximum is {width_max}" 61 | ) 62 | 63 | if y_max - y_min > height_max: 64 | raise ValueError( 65 | "AtomArrangement too tall, found " 66 | f"{y_max - y_min} but maximum is {height_max}" 67 | ) 68 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/analysis/hardware/piecewise_constant.py: -------------------------------------------------------------------------------- 1 | from bloqade.analog.ir.control import field, pulse, sequence, waveform 2 | from bloqade.analog.ir.visitor import BloqadeIRVisitor 3 | 4 | 5 | class ValidatePiecewiseConstantChannel(BloqadeIRVisitor): 6 | def __init__( 7 | self, 8 | level_coupling: sequence.LevelCoupling, 9 | field_name: pulse.FieldName, 10 | spatial_modulations: field.SpatialModulation, 11 | ): 12 | self.level_coupling = level_coupling 13 | self.field_name = field_name 14 | self.spatial_modulations = spatial_modulations 15 | 16 | def emit_shape_error(self, node: waveform.Waveform): 17 | return ( 18 | "failed to compile waveform to piecewise constant. " 19 | f"found non-constant waveform: {node.print_node()} " 20 | f"for the {self.level_coupling} {self.field_name} with spatial " 21 | f"modulation:\n{self.spatial_modulations}" 22 | ) 23 | 24 | def visit_waveform_Linear(self, node: waveform.Linear) -> None: 25 | if node.start() != node.stop(): 26 | raise ValueError(self.emit_shape_error(node)) 27 | 28 | def visit_waveform_Poly(self, node: waveform.Poly) -> None: 29 | if len(node.coeffs) > 1: 30 | raise ValueError(self.emit_shape_error(node)) 31 | 32 | def visit_waveform_Sample(self, node: waveform.Sample) -> None: 33 | if node.interpolation != waveform.Interpolation.Constant: 34 | raise ValueError(self.emit_shape_error(node)) 35 | 36 | def visit_waveform_Smooth(self, node: waveform.Smooth) -> None: 37 | raise ValueError(self.emit_shape_error(node)) 38 | 39 | def visit_waveform_PythonFn(self, node: waveform.PythonFn) -> None: 40 | raise ValueError(self.emit_shape_error(node)) 41 | 42 | def visit_sequence_Sequence(self, node: sequence.Sequence) -> None: 43 | self.visit(node.pulses[self.level_coupling]) 44 | 45 | def visit_pulse_Pulse(self, node: pulse.Pulse) -> None: 46 | self.visit(node.fields[self.field_name]) 47 | 48 | def visit_field_Field(self, node: field.Field) -> None: 49 | self.visit(node.drives[self.spatial_modulations]) 50 | 51 | # node type doesn't matter here 52 | def scan(self, node) -> None: 53 | self.visit(node) 54 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/analysis/python/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/compiler/analysis/python/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/analysis/python/waveform.py: -------------------------------------------------------------------------------- 1 | from beartype import beartype 2 | from beartype.typing import Set, Dict, FrozenSet 3 | from pydantic.v1.dataclasses import dataclass 4 | 5 | import bloqade.analog.ir.control.waveform as waveform 6 | from bloqade.analog.ir.visitor import BloqadeIRVisitor 7 | 8 | 9 | @dataclass(frozen=True) 10 | class WaveformScanResult: 11 | bindings: Dict[waveform.Waveform, str] 12 | imports: Dict[str, FrozenSet[str]] 13 | 14 | 15 | class WaveformScan(BloqadeIRVisitor): 16 | def __init__( 17 | self, 18 | bindings: Dict[waveform.Waveform, str] = {}, 19 | imports: Dict[str, Set[str]] = {}, 20 | ): 21 | self.bindings = dict(bindings) 22 | self.imports = dict(imports) 23 | self.i = 0 24 | 25 | def add_binding(self, node: waveform.Waveform): 26 | if node in self.bindings: 27 | return 28 | 29 | symbol = f"__bloqade_var{self.i}" 30 | 31 | while symbol in self.imports.values(): 32 | self.i += 1 33 | symbol = f"__bloqade_var{self.i}" 34 | 35 | self.i += 1 36 | 37 | self.bindings[node] = symbol 38 | 39 | def generic_visit(self, node: waveform.Waveform): 40 | if isinstance(node, waveform.Waveform): 41 | self.add_binding(node) 42 | super().generic_visit(node) 43 | 44 | @beartype 45 | def scan(self, node: waveform.Waveform) -> WaveformScanResult: 46 | self.visit(node) 47 | imports = { 48 | module: frozenset(imports) for module, imports in self.imports.items() 49 | } 50 | return WaveformScanResult(self.bindings, imports) 51 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/codegen/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/compiler/codegen/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/codegen/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/compiler/codegen/common/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/codegen/hardware/__init__.py: -------------------------------------------------------------------------------- 1 | from .lattice import GenerateLattice 2 | from .piecewise_linear import PiecewiseLinear, GeneratePiecewiseLinearChannel 3 | from .piecewise_constant import PiecewiseConstant, GeneratePiecewiseConstantChannel 4 | from .lattice_site_coefficients import GenerateLatticeSiteCoefficients 5 | 6 | __all__ = [ 7 | "GenerateLattice", 8 | "GenerateLatticeSiteCoefficients", 9 | "GeneratePiecewiseConstantChannel", 10 | "GeneratePiecewiseLinearChannel", 11 | "PiecewiseConstant", 12 | "PiecewiseLinear", 13 | ] 14 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/codegen/hardware/lattice_site_coefficients.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from beartype import beartype 4 | from beartype.typing import Optional 5 | 6 | from bloqade.analog.ir import scalar, analog_circuit 7 | from bloqade.analog.ir.control import field, pulse 8 | from bloqade.analog.ir.visitor import BloqadeIRVisitor 9 | from bloqade.analog.submission.ir.parallel import ParallelDecoder 10 | 11 | 12 | class GenerateLatticeSiteCoefficients(BloqadeIRVisitor): 13 | def __init__(self, parallel_decoder: Optional[ParallelDecoder] = None): 14 | self.n_sites = None 15 | self.parallel_decoder = parallel_decoder 16 | self.lattice_site_coefficients = None 17 | 18 | def post_spatial_modulation_visit(self): 19 | if self.parallel_decoder is None: 20 | # if we are not parallelizing, we don't need to do anything 21 | return 22 | 23 | # create a copy of the cluster site coefficients 24 | lattice_site_coefficients = list(self.lattice_site_coefficients) 25 | # insert the cluster site coefficients into the parallelized 26 | # lattice site coefficients 27 | self.lattice_site_coefficients = [] 28 | # sort by global location index so that we can insert the 29 | # cluster site coefficients in the correct order 30 | sorted_decoder_mapping = sorted( 31 | self.parallel_decoder.mapping, key=lambda x: x.global_location_index 32 | ) 33 | for cluster_site_info in sorted_decoder_mapping: 34 | self.lattice_site_coefficients.append( 35 | lattice_site_coefficients[cluster_site_info.cluster_location_index] 36 | ) 37 | 38 | # We don't need to visit UniformModulation because local detuning 39 | # UniformModulation is merged into global detuning 40 | 41 | def visit_field_ScaledLocations(self, node: field.ScaledLocations): 42 | self.lattice_site_coefficients = [] 43 | 44 | for i in range(self.n_sites): 45 | value = node.value.get(field.Location(i), scalar.Literal(0)) 46 | self.lattice_site_coefficients.append(value()) 47 | 48 | def visit_field_AssignedRunTimeVector(self, node: field.AssignedRunTimeVector): 49 | self.lattice_site_coefficients = list(map(Decimal, map(str, node.value))) 50 | 51 | def visit_field_Field(self, node: field.Field): 52 | (local_detuning_spatial_modulation,) = node.drives.keys() - {field.Uniform} 53 | self.visit(local_detuning_spatial_modulation) 54 | self.post_spatial_modulation_visit() 55 | 56 | def visit_pulse_Pulse(self, node: pulse.Pulse): 57 | self.visit(node.fields[pulse.detuning]) 58 | 59 | def visit_analog_circuit_AnalogCircuit(self, node: analog_circuit.AnalogCircuit): 60 | self.n_sites = node.register.n_sites 61 | self.visit(node.sequence) 62 | 63 | # needs to be an instance of analog_circuit.AnalogCircuit 64 | # because we need to know the number of sites 65 | @beartype 66 | def emit(self, node: analog_circuit.AnalogCircuit): 67 | self.visit(node) 68 | return self.lattice_site_coefficients 69 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/codegen/julia/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/compiler/codegen/julia/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/codegen/julia/types.py: -------------------------------------------------------------------------------- 1 | # from juliacall import Main, TypeValue # type: ignore 2 | # from typing import List, Dict, Any 3 | # from dataclasses import dataclass 4 | # from .to_julia import ToJulia 5 | 6 | # PythonCall = Main.seval("PythonCall") 7 | 8 | 9 | # @dataclass(frozen=True) 10 | # class JLTypeVar: 11 | # name: str 12 | 13 | 14 | # class JLType: 15 | # """Superclass for all Julia types.""" 16 | 17 | # def __init__(self, expr, *typeparams) -> None: 18 | # self.expr = expr 19 | # self.obj = getattr(Main, expr) 20 | # params = [] 21 | # for param in typeparams: 22 | # if isinstance(param, JLType): 23 | # params.append(param.obj) 24 | # elif isinstance(param, TypeValue): 25 | # params.append(param) 26 | # else: 27 | # raise Exception(f"Unknown type parameter: {param}") 28 | # self.typeparams = params 29 | 30 | # def __call__(self, x) -> Any: 31 | # if self.typeparams: 32 | # jl_type = self.obj[self.typeparams] 33 | # else: 34 | # jl_type = self.obj 35 | # return PythonCall.pyconvert(jl_type, x) 36 | 37 | # def __getitem__(self, *typeparams: Any) -> Any: 38 | # return JLType(self.expr, *typeparams) 39 | 40 | # def __repr__(self) -> str: 41 | # repr = self.expr 42 | # if self.typeparams: 43 | # repr += "[" + ",".join(map(str, self.typeparams)) + "]" 44 | # return repr 45 | 46 | 47 | # class JLVectorType(JLType): 48 | # def __init__(self, expr, *typeparams) -> None: 49 | # super().__init__(expr, *typeparams) 50 | 51 | # def __call__(self, x: List[ToJulia]) -> Any: 52 | # type = self.obj[self.typeparams] 53 | # ret = type(Main.undef, len(x)) 54 | # for i, v in enumerate(x): 55 | # ret[i] = v.julia() 56 | # return ret 57 | 58 | # def __getitem__(self, typeparams: Any) -> Any: 59 | # assert len(typeparams) == 1 60 | # assert self.typeparams == [] 61 | # return JLVectorType(*typeparams) 62 | 63 | 64 | # class JLDictType(JLType): 65 | # def __init__(self, expr, *typeparams) -> None: 66 | # super().__init__(expr, *typeparams) 67 | 68 | # def __call__(self, x: Dict[ToJulia, ToJulia]) -> Any: 69 | # type = self.obj[self.typeparams] 70 | # ret = type() 71 | # for k, v in x.items(): 72 | # ret[k.julia()] = v.julia() 73 | # return ret 74 | 75 | # def __getitem__(self, *typeparams: Any) -> Any: 76 | # assert len(self.typeparams) + len(typeparams) == 2 77 | # return JLDictType(self.expr, *typeparams) 78 | 79 | 80 | # Int32 = JLType("Int32") 81 | # Int64 = JLType("Int64") 82 | # Float32 = JLType("Float32") 83 | # Float64 = JLType("Float64") 84 | # Complex = JLType("Complex") 85 | # ComplexF32 = JLType("ComplexF32") 86 | # ComplexF64 = JLType("ComplexF64") 87 | # String = JLType("String") 88 | # Bool = JLType("Bool") 89 | # Symbol = JLType("Symbol") 90 | # Vector = JLVectorType("Vector") 91 | # Dict = JLDictType("Dict") 92 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/codegen/python/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/compiler/codegen/python/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/passes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/compiler/passes/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/passes/emulator.py: -------------------------------------------------------------------------------- 1 | from bloqade.analog.compiler.rewrite.common import ( 2 | AddPadding, 3 | Canonicalizer, 4 | FlattenCircuit, 5 | AssignBloqadeIR, 6 | AssignToLiteral, 7 | ) 8 | from bloqade.analog.compiler.analysis.common import ( 9 | ScanChannels, 10 | ScanVariables, 11 | AssignmentScan, 12 | ) 13 | from bloqade.analog.compiler.codegen.python.emulator_ir import EmulatorProgramCodeGen 14 | 15 | 16 | def flatten(circuit): 17 | level_couplings = ScanChannels().scan(circuit) 18 | circuit = AddPadding(level_couplings=level_couplings).visit(circuit) 19 | return FlattenCircuit(level_couplings=level_couplings).visit(circuit) 20 | 21 | 22 | def assign(assignments, circuit): 23 | completed_assignments = AssignmentScan(assignments).scan(circuit) 24 | circuit = AssignBloqadeIR(completed_assignments).emit(circuit) 25 | assignment_analysis = ScanVariables().scan(circuit) 26 | 27 | if not assignment_analysis.is_assigned: 28 | missing_vars = assignment_analysis.scalar_vars.union( 29 | assignment_analysis.vector_vars 30 | ) 31 | raise ValueError( 32 | "Missing assignments for variables:\n" 33 | + ("\n".join(f"{var}" for var in missing_vars)) 34 | + "\n" 35 | ) 36 | 37 | return completed_assignments, Canonicalizer().visit( 38 | AssignToLiteral().visit(circuit) 39 | ) 40 | 41 | 42 | def generate_emulator_ir(circuit, blockade_radius, waveform_runtime, use_hyperfine): 43 | return EmulatorProgramCodeGen( 44 | blockade_radius=blockade_radius, 45 | waveform_runtime=waveform_runtime, 46 | use_hyperfine=use_hyperfine, 47 | ).emit(circuit) 48 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/passes/hardware/__init__.py: -------------------------------------------------------------------------------- 1 | from .define import ( 2 | assign_circuit, 3 | analyze_channels, 4 | generate_ahs_code, 5 | generate_quera_ir, 6 | generate_braket_ir, 7 | validate_waveforms, 8 | canonicalize_circuit, 9 | ) 10 | 11 | __all__ = [ 12 | "analyze_channels", 13 | "canonicalize_circuit", 14 | "assign_circuit", 15 | "validate_waveforms", 16 | "generate_ahs_code", 17 | "generate_quera_ir", 18 | "generate_braket_ir", 19 | ] 20 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/passes/hardware/components.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from beartype.typing import List, Optional 4 | from pydantic.v1.dataclasses import dataclass 5 | 6 | from bloqade.analog.compiler.codegen.hardware.lattice import AHSLatticeData 7 | from bloqade.analog.compiler.codegen.hardware.piecewise_linear import PiecewiseLinear 8 | from bloqade.analog.compiler.codegen.hardware.piecewise_constant import ( 9 | PiecewiseConstant, 10 | ) 11 | 12 | 13 | @dataclass 14 | class AHSComponents: 15 | lattice_data: AHSLatticeData 16 | global_detuning: PiecewiseLinear 17 | global_amplitude: PiecewiseLinear 18 | global_phase: PiecewiseConstant 19 | local_detuning: Optional[PiecewiseLinear] 20 | lattice_site_coefficients: Optional[List[Decimal]] 21 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/passes/hardware/units.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from beartype.typing import Tuple 4 | 5 | 6 | def convert_time_units(time: Decimal) -> Decimal: 7 | return time * Decimal("1e-6") 8 | 9 | 10 | def convert_energy_units(energy: Decimal) -> Decimal: 11 | return energy * Decimal("1e6") 12 | 13 | 14 | def convert_coordinate_units( 15 | length: Tuple[Decimal, Decimal], 16 | ) -> Tuple[Decimal, Decimal]: 17 | return (length[0] * Decimal("1e-6"), length[1] * Decimal("1e-6")) 18 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/rewrite/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/compiler/rewrite/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/rewrite/common/__init__.py: -------------------------------------------------------------------------------- 1 | from .flatten import FlattenCircuit 2 | from .add_padding import AddPadding 3 | from .canonicalize import Canonicalizer 4 | from .assign_variables import AssignBloqadeIR 5 | from .assign_to_literal import AssignToLiteral 6 | 7 | __all__ = [ 8 | "AddPadding", 9 | "AssignToLiteral", 10 | "AssignBloqadeIR", 11 | "Canonicalizer", 12 | "FlattenCircuit", 13 | ] 14 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/rewrite/common/assign_to_literal.py: -------------------------------------------------------------------------------- 1 | import bloqade.analog.ir.scalar as scalar 2 | from bloqade.analog.ir.control import waveform 3 | from bloqade.analog.ir.visitor import BloqadeIRTransformer 4 | 5 | 6 | class AssignToLiteral(BloqadeIRTransformer): 7 | """Transform all assigned variables to literals.""" 8 | 9 | def visit_scalar_AssignedVariable(self, node: scalar.AssignedVariable): 10 | return scalar.Literal(node.value) 11 | 12 | def visit_waveform_PythonFn(self, node: waveform.PythonFn): 13 | return node # skip these nodes 14 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/rewrite/common/assign_variables.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | 3 | import bloqade.analog.ir.scalar as scalar 4 | import bloqade.analog.ir.control.field as field 5 | import bloqade.analog.ir.control.waveform as waveform 6 | from bloqade.analog.ir.visitor import BloqadeIRTransformer 7 | from bloqade.analog.builder.typing import LiteralType 8 | 9 | 10 | class AssignBloqadeIR(BloqadeIRTransformer): 11 | def __init__(self, mapping: Dict[str, LiteralType]): 12 | self.mapping = dict(mapping) 13 | 14 | def visit_scalar_Variable(self, node: scalar.Variable): 15 | if node.name in self.mapping: 16 | return scalar.AssignedVariable(node.name, self.mapping[node.name]) 17 | 18 | return node 19 | 20 | def visit_scalar_AssignedVariable(self, node: scalar.AssignedVariable): 21 | if node.name in self.mapping: 22 | raise ValueError(f"Variable {node.name} already assigned to {node.value}.") 23 | 24 | return node 25 | 26 | def visit_field_RunTimeVector(self, node: field.RunTimeVector): 27 | if node.name in self.mapping: 28 | return field.AssignedRunTimeVector(node.name, self.mapping[node.name]) 29 | 30 | return node 31 | 32 | def visit_waveform_Record(self, node: waveform.Record): 33 | if node.var.name in self.mapping: 34 | return self.visit(node.waveform) 35 | 36 | return waveform.Record(self.visit(node.waveform), node.var) 37 | 38 | def visit_field_AssignedRunTimeVector(self, node: field.AssignedRunTimeVector): 39 | if node.name in self.mapping: 40 | raise ValueError(f"Variable {node.name} already assigned to {node.value}.") 41 | 42 | return node 43 | 44 | def emit(self, node) -> Any: 45 | return self.visit(node) 46 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/rewrite/common/flatten.py: -------------------------------------------------------------------------------- 1 | import bloqade.analog.ir.control.field as field 2 | import bloqade.analog.ir.control.pulse as pulse 3 | import bloqade.analog.ir.control.sequence as sequence 4 | from bloqade.analog.ir.visitor import BloqadeIRTransformer 5 | 6 | 7 | class FlattenCircuit(BloqadeIRTransformer): 8 | # every visitor for sequence returns a Sequence 9 | 10 | def __init__( 11 | self, 12 | level_couplings=None, 13 | field_names=None, 14 | spatial_modulations=None, 15 | ): 16 | self.level_couplings = level_couplings 17 | self.field_names = field_names 18 | self.spatial_modulations = spatial_modulations 19 | self.duration = None 20 | 21 | ####################### 22 | # Visitor definitions # 23 | ####################### 24 | 25 | def visit_sequence_Append(self, node: sequence.Append) -> sequence.Sequence: 26 | seqs = list(map(self.visit, node.sequences)) 27 | 28 | pulses = {} 29 | for lc in self.level_couplings: 30 | for s in seqs: 31 | p = s.pulses[lc] 32 | pulses[lc] = pulses[lc].append(p) if lc in pulses else p 33 | 34 | return self.visit(sequence.Sequence(pulses)) 35 | 36 | def visit_sequence_Slice(self, node: sequence.Slice) -> sequence.Sequence: 37 | seq = self.visit(node.sequence) 38 | interval = node.interval 39 | 40 | pulses = {} 41 | 42 | for lc in self.level_couplings: 43 | p = seq.pulses[lc] 44 | pulses[lc] = p[interval.start : interval.stop] 45 | 46 | return self.visit(sequence.Sequence(pulses)) 47 | 48 | def visit_sequence_NamedSequence( 49 | self, node: sequence.NamedSequence 50 | ) -> sequence.Sequence: 51 | return self.visit(node.sequence) 52 | 53 | def visit_pulse_Slice(self, node: pulse.Slice) -> pulse.Pulse: 54 | p = self.visit(node.pulse) 55 | interval = node.interval 56 | 57 | fields = {} 58 | 59 | for fn, f in p.fields.items(): 60 | drives = {} 61 | for sm, wf in f.drives.items(): 62 | drives[sm] = wf[interval.start : interval.stop] 63 | 64 | fields[fn] = field.Field(drives) 65 | 66 | return pulse.Pulse(fields) 67 | 68 | def visit_pulse_Append(self, node: pulse.Append) -> pulse.Pulse: 69 | pulses = list(map(self.visit, node.pulses)) 70 | fields = dict(pulses[0].fields) 71 | 72 | for p in pulses[1:]: 73 | for fn, f in p.fields.items(): 74 | for sm, wf in f.drives.items(): 75 | curr_wf = fields[fn].drives[sm] 76 | fields[fn].drives[sm] = curr_wf.append(wf) 77 | 78 | return pulse.Pulse(fields) 79 | 80 | def visit_pulse_NamedPulse(self, node: pulse.NamedPulse) -> pulse.Pulse: 81 | return self.visit(node) 82 | -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/rewrite/python/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/compiler/rewrite/python/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/compiler/rewrite/python/waveform.py: -------------------------------------------------------------------------------- 1 | from functools import reduce 2 | 3 | import numpy as np 4 | 5 | import bloqade.analog.ir.control.waveform as waveform 6 | from bloqade.analog.ir.visitor import BloqadeIRTransformer 7 | 8 | 9 | def concat(lhs: waveform.Waveform, rhs: waveform.Waveform) -> waveform.Waveform: 10 | return lhs.append(rhs) 11 | 12 | 13 | class NormalizeWaveformPython(BloqadeIRTransformer): 14 | def visit_waveform_Sample(self, node: waveform.Sample): 15 | times, values = node.samples() 16 | 17 | durations = np.diff(times) 18 | 19 | if node.interpolation is waveform.Interpolation.Constant: 20 | segments = [waveform.Constant(*args) for args in zip(values, durations)] 21 | elif node.interpolation is waveform.Interpolation.Linear: 22 | segments = [ 23 | waveform.Linear(*args) 24 | for args in zip(values[:-1], values[1:], durations) 25 | ] 26 | 27 | return reduce(concat, segments) 28 | -------------------------------------------------------------------------------- /src/bloqade/analog/constants.py: -------------------------------------------------------------------------------- 1 | from math import pi 2 | 3 | RB_C6 = 2 * pi * 862690 4 | """The C6 constant for the Rydberg Interaction of two Rubidium atoms in units of: 5 | rad μm^6/μs""" 6 | -------------------------------------------------------------------------------- /src/bloqade/analog/emulate/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/emulate/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/emulate/codegen/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/emulate/codegen/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/emulate/ir/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/emulate/ir/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/ir/__init__.py: -------------------------------------------------------------------------------- 1 | from .scalar import Scalar, Literal, Interval, Variable, var, cast 2 | from .location import ( 3 | Lieb, 4 | Chain, 5 | Kagome, 6 | Square, 7 | Honeycomb, 8 | Triangular, 9 | Rectangular, 10 | BoundedBravais, 11 | AtomArrangement, 12 | ListOfLocations, 13 | ParallelRegister, 14 | start, 15 | ) 16 | from .control.field import ( 17 | Field, 18 | Uniform, 19 | Location, 20 | RunTimeVector, 21 | ScaledLocations, 22 | SpatialModulation, 23 | AssignedRunTimeVector, 24 | ) 25 | from .control.pulse import Pulse, FieldName, NamedPulse, rabi, detuning 26 | from .analog_circuit import AnalogCircuit 27 | from .control.sequence import Sequence, LevelCoupling, rydberg, hyperfine 28 | from .control.waveform import ( 29 | Poly, 30 | Side, 31 | Linear, 32 | Record, 33 | Sample, 34 | Constant, 35 | PythonFn, 36 | Waveform, 37 | Alignment, 38 | CosineKernel, 39 | Interpolation, 40 | SigmoidKernel, 41 | TricubeKernel, 42 | UniformKernel, 43 | BiweightKernel, 44 | GaussianKernel, 45 | LogisticKernel, 46 | TriangleKernel, 47 | AlignedWaveform, 48 | ParabolicKernel, 49 | TriweightKernel, 50 | to_waveform, 51 | ) 52 | 53 | __all__ = [ 54 | "var", 55 | "cast", 56 | "Scalar", 57 | "Interval", 58 | "Variable", 59 | "Literal", 60 | "Linear", 61 | "Constant", 62 | "Poly", 63 | "Record", 64 | "AlignedWaveform", 65 | "Alignment", 66 | "Side", 67 | "Waveform", 68 | "Sample", 69 | "Interpolation", 70 | "PythonFn", 71 | "to_waveform", 72 | "GaussianKernel", 73 | "LogisticKernel", 74 | "SigmoidKernel", 75 | "TriangleKernel", 76 | "UniformKernel", 77 | "ParabolicKernel", 78 | "BiweightKernel", 79 | "TriweightKernel", 80 | "TricubeKernel", 81 | "CosineKernel", 82 | "Field", 83 | "Location", 84 | "ScaledLocations", 85 | "Uniform", 86 | "RunTimeVector", 87 | "AssignedRunTimeVector", 88 | "SpatialModulation", 89 | "Pulse", 90 | "NamedPulse", 91 | "FieldName", 92 | "rabi", 93 | "detuning", 94 | "LevelCoupling", 95 | "rydberg", 96 | "hyperfine", 97 | "Sequence", 98 | "AnalogCircuit", 99 | ### location ir ### 100 | "start", 101 | "AtomArrangement", 102 | "Chain", 103 | "Square", 104 | "Rectangular", 105 | "Honeycomb", 106 | "Triangular", 107 | "Lieb", 108 | "Kagome", 109 | "BoundedBravais", 110 | "ListOfLocations", 111 | "ParallelRegister", 112 | ] 113 | -------------------------------------------------------------------------------- /src/bloqade/analog/ir/control/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/ir/control/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/ir/control/traits/__init__.py: -------------------------------------------------------------------------------- 1 | from .hash import HashTrait 2 | from .slice import SliceTrait 3 | from .append import AppendTrait 4 | from .canonicalize import CanonicalizeTrait 5 | 6 | __all__ = [ 7 | "HashTrait", 8 | "AppendTrait", 9 | "SliceTrait", 10 | "CanonicalizeTrait", 11 | ] 12 | -------------------------------------------------------------------------------- /src/bloqade/analog/ir/control/traits/append.py: -------------------------------------------------------------------------------- 1 | from functools import cached_property 2 | 3 | from bloqade.analog.ir.scalar import cast 4 | 5 | 6 | class AppendTrait: 7 | @property 8 | def _sub_exprs(self): 9 | raise NotImplementedError("sub_exprs property is not implemented") 10 | 11 | @cached_property 12 | def duration(self): 13 | duration = cast(0) 14 | for p in self._sub_exprs: 15 | duration = duration + p.duration 16 | 17 | return duration 18 | -------------------------------------------------------------------------------- /src/bloqade/analog/ir/control/traits/canonicalize.py: -------------------------------------------------------------------------------- 1 | class CanonicalizeTrait: 2 | @staticmethod 3 | def canonicalize(expr): 4 | from bloqade.analog.compiler.rewrite.common.canonicalize import Canonicalizer 5 | 6 | return Canonicalizer().visit(expr) 7 | -------------------------------------------------------------------------------- /src/bloqade/analog/ir/control/traits/hash.py: -------------------------------------------------------------------------------- 1 | from functools import cached_property 2 | from dataclasses import fields 3 | 4 | 5 | class HashTrait: 6 | @cached_property 7 | def _hash_value(self): 8 | value = hash(self.__class__) 9 | for field in fields(self): 10 | field_value = getattr(self, field.name) 11 | if isinstance(field_value, list): 12 | value ^= hash(tuple(field_value)) 13 | elif isinstance(field_value, dict): 14 | value ^= hash(frozenset(field_value.items())) 15 | else: 16 | value ^= hash(field_value) 17 | 18 | return value 19 | 20 | def __hash__(self): 21 | return self._hash_value 22 | -------------------------------------------------------------------------------- /src/bloqade/analog/ir/control/traits/slice.py: -------------------------------------------------------------------------------- 1 | from functools import cached_property 2 | 3 | from bloqade.analog.ir.scalar import Scalar, cast 4 | 5 | 6 | class SliceTrait: 7 | @property 8 | def _sub_expr(self): 9 | raise NotImplementedError("sub_expr property is not implemented") 10 | 11 | @cached_property 12 | def start(self) -> Scalar: 13 | """Start time of the sliced object 14 | 15 | Returns: 16 | Scalar: The starting time of the sliced object as a 17 | Scalar Expression 18 | """ 19 | if self.interval.start is None: 20 | return cast(0) 21 | else: 22 | return self.interval.start 23 | 24 | @cached_property 25 | def stop(self) -> Scalar: 26 | """Stop time of the sliced object 27 | 28 | Returns: 29 | Scalar: The stopping time of the sliced object as a 30 | Scalar Expression 31 | """ 32 | if self.interval.stop is None: 33 | return self._sub_expr.duration 34 | else: 35 | return self.interval.stop 36 | 37 | @cached_property 38 | def duration(self) -> Scalar: 39 | return self._sub_expr.duration[self.interval.start : self.interval.stop] 40 | -------------------------------------------------------------------------------- /src/bloqade/analog/ir/location/__init__.py: -------------------------------------------------------------------------------- 1 | from .bravais import ( 2 | Lieb, 3 | Chain, 4 | Kagome, 5 | Square, 6 | Honeycomb, 7 | Triangular, 8 | Rectangular, 9 | BoundedBravais, 10 | ) 11 | from .location import LocationInfo, AtomArrangement, ListOfLocations, ParallelRegister 12 | 13 | start = ListOfLocations() 14 | """ 15 | A Program starting point, alias of empty 16 | [`ListOfLocations`][bloqade.ir.location.list.ListOfLocations]. 17 | 18 | - Next possible steps to build your program are: 19 | - Specify which level coupling to address with: 20 | - `start.rydberg`: for [`Rydberg`][bloqade.builder.coupling.Rydberg] 21 | Level coupling 22 | - `start.hyperfine`: for [`Hyperfine`][bloqade.builder.coupling.Hyperfine] 23 | Level coupling 24 | - LOCKOUT: You cannot add atoms to your geometry after specifying level coupling. 25 | - continue/start building your geometry with: 26 | - `start.add_position()`: to add atom(s) to current register. It will accept: 27 | - A single coordinate, represented as a tuple (e.g. `(5,6)`) with a value that 28 | can either be: 29 | - integers: `(5,6)` 30 | - floats: `(5.1, 2.5)` 31 | - strings (for later variable assignment): `("x", "y")` 32 | - [`Scalar`][bloqade.ir.scalar.Scalar] objects: `(2*cast("x"), 5+cast("y"))` 33 | - A list of coordinates, represented as a list of types mentioned previously. 34 | - A numpy array with shape (n, 2) where n is the total number of atoms 35 | """ 36 | 37 | 38 | __all__ = [ 39 | "start", 40 | "AtomArrangement", 41 | "Chain", 42 | "Square", 43 | "Rectangular", 44 | "Honeycomb", 45 | "Triangular", 46 | "Lieb", 47 | "Kagome", 48 | "BoundedBravais", 49 | "ListOfLocations", 50 | "ParallelRegister", 51 | "LocationInfo", 52 | ] 53 | -------------------------------------------------------------------------------- /src/bloqade/analog/ir/routine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/ir/routine/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/ir/routine/base.py: -------------------------------------------------------------------------------- 1 | # from bloqade.analog.ir.routine.params import Params 2 | from typing import TYPE_CHECKING, Union, Optional 3 | 4 | from pydantic.v1 import ConfigDict 5 | from pydantic.v1.dataclasses import dataclass 6 | 7 | from bloqade.analog.ir import Sequence, AtomArrangement, ParallelRegister 8 | from bloqade.analog.builder.base import Builder 9 | from bloqade.analog.ir.analog_circuit import AnalogCircuit 10 | from bloqade.analog.ir.routine.params import Params 11 | from bloqade.analog.builder.parse.trait import Show, Parse 12 | 13 | if TYPE_CHECKING: 14 | from bloqade.analog.ir.routine.quera import QuEraServiceOptions 15 | from bloqade.analog.ir.routine.braket import BraketServiceOptions 16 | from bloqade.analog.ir.routine.bloqade import BloqadeServiceOptions 17 | 18 | 19 | class RoutineParse(Parse): 20 | def parse_register(self: "RoutineBase") -> Union[AtomArrangement, ParallelRegister]: 21 | return self.circuit.register 22 | 23 | def parse_sequence(self: "RoutineBase") -> Sequence: 24 | return self.circuit.sequence 25 | 26 | def parse_circuit(self: "RoutineBase") -> AnalogCircuit: 27 | return self.circuit 28 | 29 | def parse(self: "RoutineBase") -> "Routine": 30 | if self.source is None: 31 | raise ValueError("Cannot parse a routine without a source Builder.") 32 | return self 33 | 34 | 35 | class RoutineShow(Show): 36 | def show(self: "RoutineBase", *args, batch_index: int = 0): 37 | """Show an interactive plot of the routine. 38 | 39 | batch_index: int 40 | which parameter set out of the batch to use. Default is 0. 41 | If there are no batch parameters, use 0. 42 | 43 | *args: Any 44 | Specify the parameters that are defined in the `.args([...])` build step. 45 | 46 | """ 47 | if self.source is None: 48 | raise ValueError("Cannot show a routine without a source Builder.") 49 | 50 | return self.source.show(*args, batch_id=batch_index) 51 | 52 | 53 | __pydantic_dataclass_config__ = ConfigDict(arbitrary_types_allowed=True) 54 | 55 | 56 | @dataclass(frozen=True, config=__pydantic_dataclass_config__) 57 | class RoutineBase(RoutineParse, RoutineShow): 58 | source: Optional[Builder] 59 | circuit: AnalogCircuit 60 | params: Params 61 | 62 | def __str__(self): 63 | out = self.__class__.__name__ + "\n" 64 | out = out + str(self.circuit) 65 | out = out + "\n---------------------\n" 66 | out = out + str(self.params) 67 | return out 68 | 69 | 70 | @dataclass(frozen=True, config=__pydantic_dataclass_config__) 71 | class Routine(RoutineBase): 72 | """Result of parsing a completed Builder string.""" 73 | 74 | @property 75 | def braket(self) -> "BraketServiceOptions": 76 | from .braket import BraketServiceOptions 77 | 78 | return BraketServiceOptions(self.source, self.circuit, self.params) 79 | 80 | @property 81 | def quera(self) -> "QuEraServiceOptions": 82 | from .quera import QuEraServiceOptions 83 | 84 | return QuEraServiceOptions(self.source, self.circuit, self.params) 85 | 86 | @property 87 | def bloqade(self) -> "BloqadeServiceOptions": 88 | from .bloqade import BloqadeServiceOptions 89 | 90 | return BloqadeServiceOptions(self.source, self.circuit, self.params) 91 | -------------------------------------------------------------------------------- /src/bloqade/analog/ir/routine/params.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Tuple, Union 2 | from decimal import Decimal 3 | from functools import cached_property 4 | 5 | from pydantic.v1.dataclasses import dataclass 6 | 7 | ParamType = Union[Decimal, List[Decimal]] 8 | 9 | 10 | @dataclass(frozen=True) 11 | class ScalarArg: 12 | name: str 13 | 14 | 15 | @dataclass(frozen=True) 16 | class VectorArg: 17 | name: str 18 | 19 | 20 | @dataclass(frozen=True) 21 | class Params: 22 | n_sites: int 23 | static_params: Dict[str, ParamType] 24 | batch_params: List[Dict[str, ParamType]] 25 | args_list: Tuple[Union[ScalarArg, VectorArg], ...] 26 | 27 | @cached_property 28 | def num_args(self) -> int: 29 | num_args = 0 30 | for arg in self.args_list: 31 | if isinstance(arg, VectorArg): 32 | # expect n_sites args for vector arguments 33 | num_args += self.n_sites 34 | else: 35 | num_args += 1 36 | 37 | return num_args 38 | 39 | @cached_property 40 | def arg_names(self) -> Tuple[str]: 41 | return tuple([arg.name for arg in self.args_list]) 42 | 43 | def parse_args(self, *flattened_args) -> Dict[str, Decimal]: 44 | if len(flattened_args) != self.num_args: 45 | raise ValueError( 46 | f"Expected {self.num_args} arguments, got {len(flattened_args)}." 47 | ) 48 | 49 | args = [] 50 | i = 0 51 | for arg in self.args_list: 52 | # if arg is a vector, then we need to unpack the next n_sites args 53 | if isinstance(arg, VectorArg): 54 | vec = list(map(Decimal, map(str, flattened_args[i : i + self.n_sites]))) 55 | args.append(vec) 56 | i += self.n_sites 57 | else: 58 | args.append(Decimal(str(flattened_args[i]))) 59 | i += 1 60 | 61 | return dict(zip(self.arg_names, args)) 62 | 63 | def batch_assignments(self, *args) -> List[Dict[str, ParamType]]: 64 | flattened_args = self.parse_args(*args) 65 | if len(self.batch_params) == 0: 66 | # handle case where there are no batch params 67 | return [flattened_args] 68 | else: 69 | return [{**flattened_args, **batch} for batch in self.batch_params] 70 | 71 | def __str__(self): 72 | out = "" 73 | out += "> Static params:\n" 74 | for var, litrl in self.static_params.items(): 75 | out += f" :: {var} \n => {litrl}\n" 76 | 77 | out += "\n> Batch params:\n" 78 | for lid, pair in enumerate(self.batch_params): 79 | out += f"- batch {lid}:\n" 80 | for var, litrl in pair.items(): 81 | out += f" :: {var}\n => {litrl}\n" 82 | 83 | out += "\n> Arguments:\n" 84 | out += " " + repr(self.args_list) 85 | 86 | return out 87 | -------------------------------------------------------------------------------- /src/bloqade/analog/migrate.py: -------------------------------------------------------------------------------- 1 | from typing import IO, Any, Dict, List 2 | from dataclasses import field, dataclass 3 | 4 | import simplejson as json 5 | 6 | 7 | @dataclass 8 | class JSONWalker: 9 | has_done_something: bool = field(init=False, default=False) 10 | 11 | def walk_dict(self, obj: Dict[str, Any]) -> Dict[str, Any]: 12 | new_obj = {} 13 | for key, value in obj.items(): 14 | 15 | if key.startswith("bloqade.analog."): 16 | new_obj[key] = self.walk(value) 17 | elif key.startswith("bloqade."): 18 | new_obj[key.replace("bloqade.", "bloqade.analog.")] = self.walk(value) 19 | self.has_done_something = True 20 | else: 21 | new_obj[key] = self.walk(value) 22 | 23 | return new_obj 24 | 25 | def walk(self, obj: Dict[str, Any] | List[Any]) -> Dict[str, Any] | List[Any]: 26 | if isinstance(obj, dict): 27 | return self.walk_dict(obj) 28 | elif isinstance(obj, list): 29 | return list(map(self.walk, obj)) 30 | else: 31 | return obj 32 | 33 | def convert(self, obj: Dict[str, Any] | List[Any]): 34 | self.has_done_something = False 35 | new_obj = self.walk(obj) 36 | return new_obj, self.has_done_something 37 | 38 | 39 | def _migrate(input_oi: IO[str], output_oi: IO[str], indent: int | None = None): 40 | obj = json.load(input_oi) 41 | new_obj, has_done_something = JSONWalker().convert(obj) 42 | 43 | if has_done_something: 44 | json.dump(new_obj, output_oi, indent=indent) 45 | 46 | 47 | def migrate( 48 | filename: str, 49 | indent: int | None = None, 50 | overwrite: bool = False, 51 | ): 52 | new_filename = filename if overwrite else filename.replace(".json", "-analog.json") 53 | with open(filename, "r") as in_io, open(new_filename, "w") as out_io: 54 | _migrate(in_io, out_io, indent) 55 | 56 | 57 | def _entry(): 58 | import argparse 59 | 60 | import tqdm 61 | 62 | parser = argparse.ArgumentParser() 63 | 64 | parser.add_argument("filenames", type=str, nargs="*") 65 | parser.add_argument("--indent", type=int, default=None) 66 | parser.add_argument( 67 | "--overwrite", 68 | action="store_true", 69 | help="Overwrite the original file", 70 | default=False, 71 | ) 72 | 73 | args = parser.parse_args() 74 | for filename in tqdm.tqdm(args.filenames): 75 | migrate(filename, args.indent, args.overwrite) 76 | 77 | 78 | if __name__ == "__main__": 79 | _entry() 80 | -------------------------------------------------------------------------------- /src/bloqade/analog/submission/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/submission/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/submission/base.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from pydantic.v1 import Extra, BaseModel 4 | 5 | from bloqade.analog.submission.ir.braket import BraketTaskSpecification 6 | from bloqade.analog.submission.capabilities import get_capabilities 7 | from bloqade.analog.submission.ir.capabilities import QuEraCapabilities 8 | from bloqade.analog.submission.ir.task_results import ( 9 | QuEraTaskResults, 10 | QuEraTaskStatusCode, 11 | ) 12 | from bloqade.analog.submission.ir.task_specification import QuEraTaskSpecification 13 | 14 | 15 | class ValidationError(Exception): 16 | pass 17 | 18 | 19 | class SubmissionBackend(BaseModel): 20 | class Config: 21 | extra = Extra.forbid 22 | 23 | def get_capabilities(self, use_experimental: bool = False) -> QuEraCapabilities: 24 | return get_capabilities(use_experimental) 25 | 26 | def validate_task( 27 | self, task_ir: Union[BraketTaskSpecification, QuEraTaskSpecification] 28 | ) -> None: 29 | raise NotImplementedError 30 | 31 | def submit_task( 32 | self, task_ir: Union[BraketTaskSpecification, QuEraTaskSpecification] 33 | ) -> str: 34 | raise NotImplementedError 35 | 36 | def cancel_task(self, task_id: str) -> None: 37 | raise NotImplementedError 38 | 39 | def task_results(self, task_id: str) -> QuEraTaskResults: 40 | raise NotImplementedError 41 | 42 | def task_status(self, task_id: str) -> QuEraTaskStatusCode: 43 | raise NotImplementedError 44 | -------------------------------------------------------------------------------- /src/bloqade/analog/submission/braket.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | from braket.aws import AwsDevice, AwsQuantumTask 4 | from pydantic.v1 import PrivateAttr 5 | from beartype.typing import Optional 6 | 7 | import bloqade.analog 8 | from bloqade.analog.submission.base import SubmissionBackend 9 | from bloqade.analog.submission.ir.braket import ( 10 | to_braket_task, 11 | to_quera_capabilities, 12 | from_braket_status_codes, 13 | from_braket_task_results, 14 | ) 15 | from bloqade.analog.submission.ir.capabilities import QuEraCapabilities 16 | from bloqade.analog.submission.ir.task_results import ( 17 | QuEraTaskResults, 18 | QuEraTaskStatusCode, 19 | ) 20 | from bloqade.analog.submission.ir.task_specification import QuEraTaskSpecification 21 | 22 | 23 | class BraketBackend(SubmissionBackend): 24 | device_arn: str = "arn:aws:braket:us-east-1::device/qpu/quera/Aquila" 25 | _device: Optional[AwsDevice] = PrivateAttr(default=None) 26 | 27 | @property 28 | def device(self) -> AwsDevice: 29 | if self._device is None: 30 | self._device = AwsDevice(self.device_arn) 31 | user_agent = f"Bloqade/{bloqade.analog.__version__}" 32 | self._device.aws_session.add_braket_user_agent(user_agent) 33 | 34 | return self._device 35 | 36 | def get_capabilities(self, use_experimental: bool = False) -> QuEraCapabilities: 37 | from botocore.exceptions import ClientError, BotoCoreError 38 | 39 | if use_experimental: 40 | return super().get_capabilities(use_experimental) 41 | 42 | try: 43 | return to_quera_capabilities(self.device.properties.paradigm) 44 | except BotoCoreError: 45 | warnings.warn( 46 | "Could not retrieve device capabilities from braket API. " 47 | "Using local capabilities file for Aquila." 48 | ) 49 | except ClientError: 50 | warnings.warn( 51 | "Could not retrieve device capabilities from braket API. " 52 | "Using local capabilities file for Aquila." 53 | ) 54 | 55 | return super().get_capabilities() 56 | 57 | def submit_task(self, task_ir: QuEraTaskSpecification) -> str: 58 | shots, ahs_program = to_braket_task(task_ir) 59 | task = self.device.run(ahs_program, shots=shots) 60 | return task.id 61 | 62 | def task_results(self, task_id: str) -> QuEraTaskResults: 63 | return from_braket_task_results(AwsQuantumTask(task_id).result()) 64 | 65 | def cancel_task(self, task_id: str) -> None: 66 | AwsQuantumTask(task_id).cancel() 67 | 68 | def task_status(self, task_id: str) -> QuEraTaskStatusCode: 69 | return from_braket_status_codes(AwsQuantumTask(task_id).state()) 70 | 71 | def validate_task(self, task_ir: QuEraTaskSpecification): 72 | pass 73 | 74 | # def validate_task(self, task_ir: QuEraTaskSpecification): 75 | # try: 76 | # task_id = self.submit_task(task_ir) 77 | # except Exception as e: 78 | # if "ValidationException" in str(e) and "validation error" in str(e): 79 | # raise ValidationError(str(e)) 80 | # else: 81 | # raise e 82 | 83 | # # don't want the task to actually run 84 | # try: 85 | # self.cancel_task(task_id) 86 | # except Exception as e: 87 | # return 88 | -------------------------------------------------------------------------------- /src/bloqade/analog/submission/capabilities.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import simplejson as json 4 | 5 | from bloqade.analog.submission.ir.capabilities import QuEraCapabilities 6 | 7 | 8 | # TODO: Create unit converter for capabilities 9 | def get_capabilities(use_experimental: bool = False) -> QuEraCapabilities: 10 | base_path = os.path.dirname(__file__) 11 | if use_experimental: 12 | full_path = os.path.join(base_path, "config", "experimental_capabilities.json") 13 | else: 14 | full_path = os.path.join(base_path, "config", "capabilities.json") 15 | with open(full_path, "r") as io: 16 | return QuEraCapabilities(**json.load(io)) 17 | -------------------------------------------------------------------------------- /src/bloqade/analog/submission/config/aquila_api_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_hostname": "XXXXXXXXXXX.execute-api.us-east-1.amazonaws.com", 3 | "proxy": "XXXXXXXXXXX.execute-api.us-east-1.vpce.amazonaws.com", 4 | "api_stage": "v0", 5 | "qpu_id": "qpu1", 6 | "region": "us-east-1" 7 | } 8 | -------------------------------------------------------------------------------- /src/bloqade/analog/submission/config/capabilities.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.6", 3 | "capabilities": { 4 | "task": { 5 | "number_shots_min": 1, 6 | "number_shots_max": 1000 7 | }, 8 | "lattice": { 9 | "number_qubits_max": 256, 10 | "area": { 11 | "width": 7.5E-5, 12 | "height": 7.6E-5 13 | }, 14 | "geometry": { 15 | "spacing_radial_min": 4.0E-6, 16 | "spacing_vertical_min": 4.0E-6, 17 | "position_resolution": 1.0E-7, 18 | "number_sites_max": 256 19 | } 20 | }, 21 | "rydberg": { 22 | "c6_coefficient": 5.42E-24, 23 | "global": { 24 | "rabi_frequency_min": 0.0, 25 | "rabi_frequency_max": 1.58E7, 26 | "rabi_frequency_resolution": 400.0, 27 | "rabi_frequency_slew_rate_max": 2.5E14, 28 | "detuning_min": -1.25E8, 29 | "detuning_max": 1.25E8, 30 | "detuning_resolution": 0.2, 31 | "detuning_slew_rate_max": 2.5E15, 32 | "phase_min": -99.0, 33 | "phase_max": 99.0, 34 | "phase_resolution": 5.0E-7, 35 | "time_min": 0.0, 36 | "time_max": 4.0E-6, 37 | "time_resolution": 1.0E-9, 38 | "time_delta_min": 5.0E-8 39 | }, 40 | "local": { 41 | "detuning_min": 0.0, 42 | "detuning_max": 1.25E8, 43 | "detuning_slew_rate_max": 1.2566E15, 44 | "site_coefficient_min": 0.0, 45 | "site_coefficient_max": 1.0, 46 | "number_local_detuning_sites": 200, 47 | "spacing_radial_min": 5.0E-6, 48 | "time_resolution": 1.0E-9, 49 | "time_delta_min": 5.0E-8 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/bloqade/analog/submission/config/experimental_capabilities.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.1", 3 | "capabilities": { 4 | "task": { 5 | "number_shots_min": 1, 6 | "number_shots_max": 1000 7 | }, 8 | "lattice": { 9 | "number_qubits_max": 256, 10 | "area": { 11 | "width": 7.5E-5, 12 | "height": 1.28E-4 13 | }, 14 | "geometry": { 15 | "spacing_radial_min": 4.0E-6, 16 | "spacing_vertical_min": 2.0E-6, 17 | "position_resolution": 1.0E-8, 18 | "number_sites_max": 256 19 | } 20 | }, 21 | "rydberg": { 22 | "c6_coefficient": 5.42E-24, 23 | "global": { 24 | "rabi_frequency_min": 0.0, 25 | "rabi_frequency_max": 1.58E7, 26 | "rabi_frequency_resolution": 400.0, 27 | "rabi_frequency_slew_rate_max": 4.0E14, 28 | "detuning_min": -1.25E8, 29 | "detuning_max": 1.25E8, 30 | "detuning_resolution": 0.2, 31 | "detuning_slew_rate_max": 6.0E15, 32 | "phase_min": -99.0, 33 | "phase_max": 99.0, 34 | "phase_resolution": 5.0E-7, 35 | "time_min": 0.0, 36 | "time_max": 4.0E-6, 37 | "time_resolution": 1.0E-9, 38 | "time_delta_min": 5.0E-8 39 | }, 40 | "local": { 41 | "detuning_min": 0.0, 42 | "detuning_max": 1.25E8, 43 | "detuning_slew_rate_max": 1.2566E15, 44 | "site_coefficient_min": 0.0, 45 | "site_coefficient_max": 1.0, 46 | "number_local_detuning_sites": 200, 47 | "spacing_radial_min": 5.0E-6, 48 | "time_resolution": 1.0E-9, 49 | "time_delta_min": 5.0E-8 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/bloqade/analog/submission/config/mock_api_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_hostname": "XXXXXXXXXXX.execute-api.us-east-1.amazonaws.com", 3 | "proxy": "XXXXXXXXXXX.execute-api.us-east-1.vpce.amazonaws.com", 4 | "api_stage": "v0", 5 | "qpu_id": "qpu2-mock", 6 | "region": "us-east-1" 7 | } 8 | -------------------------------------------------------------------------------- /src/bloqade/analog/submission/ir/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/submission/ir/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/submission/load_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | 5 | def load_config(qpu: str): 6 | real_path = os.path.realpath(__file__) 7 | real_path_list = os.path.split(real_path)[:-1] 8 | real_path = os.path.join(*real_path_list) 9 | 10 | if qpu == "Aquila": 11 | with open( 12 | os.path.join(real_path, "config", "aquila_api_config.json"), "r" 13 | ) as f: 14 | return json.load(f) 15 | elif qpu == "Mock": 16 | with open(os.path.join(real_path, "config", "mock_api_config.json"), "r") as f: 17 | return json.load(f) 18 | else: 19 | raise NotImplementedError( 20 | f"QPU {qpu} is not supported. Supported QPUs are Aquila and Mock." 21 | ) 22 | -------------------------------------------------------------------------------- /src/bloqade/analog/submission/mock.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | import numpy as np 4 | 5 | from bloqade.analog.submission.base import SubmissionBackend 6 | from bloqade.analog.submission.ir.task_results import ( 7 | QuEraShotResult, 8 | QuEraTaskResults, 9 | QuEraShotStatusCode, 10 | QuEraTaskStatusCode, 11 | ) 12 | from bloqade.analog.submission.ir.task_specification import QuEraTaskSpecification 13 | 14 | 15 | def simulate_task_results(task: QuEraTaskSpecification, p_full=0.99, p_empty=0.01): 16 | natoms = len(task.lattice.sites) 17 | filling = task.lattice.filling 18 | 19 | pre_sequence_probs = np.array( 20 | [p_full if fill == 1 else p_empty for fill in filling] 21 | ) 22 | post_sequence_probs = np.array(natoms * [0.5]) 23 | 24 | rng = np.random.default_rng() 25 | 26 | shot_outputs = [] 27 | for shot in range(task.nshots): 28 | pre_sequence = rng.binomial(np.ones(natoms, dtype=int), pre_sequence_probs) 29 | post_sequence = rng.binomial( 30 | np.ones(natoms, dtype=int), pre_sequence * post_sequence_probs 31 | ) 32 | 33 | shot_outputs.append( 34 | QuEraShotResult( 35 | shot_status=QuEraShotStatusCode.Completed, 36 | pre_sequence=list(pre_sequence), 37 | post_sequence=list(post_sequence), 38 | ) 39 | ) 40 | 41 | return QuEraTaskResults( 42 | task_status=QuEraTaskStatusCode.Completed, shot_outputs=shot_outputs 43 | ) 44 | 45 | 46 | class MockBackend(SubmissionBackend): 47 | state_file: str = ".mock_state.txt" 48 | submission_error: bool = False 49 | 50 | def submit_task(self, task: QuEraTaskSpecification) -> str: 51 | if self.submission_error: 52 | raise ValueError("mock submission error") 53 | 54 | task_id = str(uuid.uuid4()) 55 | task_results = simulate_task_results(task) 56 | with open(self.state_file, "a") as IO: 57 | IO.write(f"('{task_id}',{task_results.json()})\n") 58 | 59 | return task_id 60 | 61 | def task_results(self, task_id: str) -> QuEraTaskResults: 62 | # lazily search database for task_id 63 | for line in open(self.state_file, "r"): 64 | potential_task_id, task_results = eval(line) 65 | 66 | if potential_task_id == task_id: 67 | return QuEraTaskResults(**task_results) 68 | 69 | raise ValueError(f"unable to fetch results for task_id: {task_id}") 70 | 71 | def cancel_task(self, task_id: str): 72 | pass 73 | 74 | def task_status(self, task_id: str) -> QuEraTaskStatusCode: 75 | return QuEraTaskStatusCode.Completed 76 | -------------------------------------------------------------------------------- /src/bloqade/analog/submission/quera.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic.v1 import PrivateAttr 4 | 5 | from bloqade.analog.submission.base import ValidationError, SubmissionBackend 6 | from bloqade.analog.submission.ir.capabilities import QuEraCapabilities 7 | from bloqade.analog.submission.ir.task_results import ( 8 | QuEraTaskResults, 9 | QuEraTaskStatusCode, 10 | ) 11 | from bloqade.analog.submission.ir.task_specification import QuEraTaskSpecification 12 | 13 | 14 | class QuEraBackend(SubmissionBackend): 15 | api_hostname: str 16 | qpu_id: str 17 | api_stage: str = "v0" 18 | virtual_queue: Optional[str] = None 19 | proxy: Optional[str] = None 20 | # Sigv4Request arguments 21 | region: Optional[str] = None 22 | access_key: Optional[str] = None 23 | secret_key: Optional[str] = None 24 | session_token: Optional[str] = None 25 | session_expires: Optional[int] = None 26 | role_arn: Optional[str] = None 27 | role_session_name: Optional[str] = None 28 | profile: Optional[str] = None 29 | _queue_api: Optional[object] = PrivateAttr(None) 30 | 31 | @property 32 | def queue_api(self): 33 | if self._queue_api is None: 34 | try: 35 | from qcs.api_client.api import QueueApi 36 | except ImportError: 37 | raise RuntimeError("Must install QuEra-QCS-client to use QuEra Cloud") 38 | 39 | kwargs = {k: v for k, v in self.__dict__.items() if v is not None} 40 | self._queue_api = QueueApi(**kwargs) 41 | 42 | return self._queue_api 43 | 44 | def get_capabilities(self, use_experimental: bool = False) -> QuEraCapabilities: 45 | try: 46 | return QuEraCapabilities(**self.queue_api.get_capabilities()) 47 | except BaseException: 48 | return super().get_capabilities(use_experimental) 49 | 50 | def submit_task(self, task_ir: QuEraTaskSpecification) -> str: 51 | return self.queue_api.submit_task( 52 | task_ir.json(by_alias=True, exclude_none=True, exclude_unset=True) 53 | ) 54 | 55 | def task_results(self, task_id: str) -> QuEraTaskResults: 56 | return QuEraTaskResults(**self.queue_api.poll_task_results(task_id)) 57 | 58 | def cancel_task(self, task_id: str): 59 | self.queue_api.cancel_task_in_queue(task_id) 60 | 61 | def task_status(self, task_id: str) -> QuEraTaskStatusCode: 62 | return_body = self.queue_api.get_task_status_in_queue(task_id) 63 | return QuEraTaskStatusCode(return_body) 64 | 65 | def validate_task(self, task_ir: QuEraTaskSpecification): 66 | try: 67 | self.queue_api.validate_task( 68 | task_ir.json(by_alias=True, exclude_none=True, exclude_unset=True) 69 | ) 70 | except self.queue_api.ValidationError as e: 71 | raise ValidationError(str(e)) 72 | 73 | def update_credential( 74 | self, access_key: str = None, secret_key: str = None, session_token: str = None 75 | ): 76 | if secret_key is not None: 77 | self.secret_key = secret_key 78 | if access_key is not None: 79 | self.access_key = access_key 80 | if session_token is not None: 81 | self.session_token = session_token 82 | -------------------------------------------------------------------------------- /src/bloqade/analog/task/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/src/bloqade/analog/task/__init__.py -------------------------------------------------------------------------------- /src/bloqade/analog/task/braket_simulator.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from braket.devices import LocalSimulator 4 | from beartype.typing import Any, Dict, Optional 5 | 6 | from bloqade.analog.serialize import Serializer 7 | from bloqade.analog.task.base import Geometry 8 | from bloqade.analog.builder.base import ParamType 9 | from bloqade.analog.submission.ir.braket import ( 10 | BraketTaskSpecification, 11 | from_braket_task_results, 12 | ) 13 | from bloqade.analog.submission.ir.task_results import QuEraTaskResults 14 | 15 | from .base import LocalTask 16 | 17 | ## keep the old conversion for now, 18 | ## we will remove conversion btwn QuEraTask <-> BraketTask, 19 | ## and specialize/dispatching here. 20 | 21 | 22 | @dataclass 23 | @Serializer.register 24 | class BraketEmulatorTask(LocalTask): 25 | task_ir: BraketTaskSpecification 26 | metadata: Dict[str, ParamType] 27 | task_result_ir: Optional[QuEraTaskResults] = None 28 | 29 | def _geometry(self) -> Geometry: 30 | return Geometry( 31 | sites=self.task_ir.program.setup.ahs_register.sites, 32 | filling=self.task_ir.program.setup.ahs_register.filling, 33 | ) 34 | 35 | def run(self, **kwargs) -> "BraketEmulatorTask": 36 | aws_task = LocalSimulator("braket_ahs").run( 37 | self.task_ir.program, 38 | shots=self.task_ir.nshots, 39 | **kwargs, 40 | ) 41 | self.task_result_ir = from_braket_task_results(aws_task.result()) 42 | return self 43 | 44 | def result(self): 45 | if self.task_result_ir is None: 46 | raise ValueError("Braket simulator job haven't submit yet.") 47 | 48 | return self.task_result_ir 49 | 50 | @property 51 | def nshots(self): 52 | return self.task_ir.nshots 53 | 54 | 55 | @BraketEmulatorTask.set_serializer 56 | def _serialize(obj: BraketEmulatorTask) -> Dict[str, Any]: 57 | return { 58 | "task_ir": obj.task_ir.dict(), 59 | "metadata": obj.metadata, 60 | "task_result_ir": obj.task_result_ir.dict() if obj.task_result_ir else None, 61 | } 62 | 63 | 64 | @BraketEmulatorTask.set_deserializer 65 | def _serializer(d: Dict[str, Any]) -> BraketEmulatorTask: 66 | d["task_ir"] = BraketTaskSpecification(**d["task_ir"]) 67 | d["task_result_ir"] = ( 68 | QuEraTaskResults(**d["task_result_ir"]) if d["task_result_ir"] else None 69 | ) 70 | return BraketEmulatorTask(**d) 71 | -------------------------------------------------------------------------------- /src/bloqade/analog/visualization/__init__.py: -------------------------------------------------------------------------------- 1 | Use_bokeh = True 2 | 3 | if Use_bokeh: 4 | from typing import List 5 | 6 | from .display import ( 7 | figure_ir, 8 | display_ir, 9 | report_figure, 10 | builder_figure, 11 | display_report, 12 | display_builder, 13 | display_task_ir, 14 | ) 15 | from .ir_visualize import get_ir_figure, get_field_figure, get_pulse_figure 16 | from .task_visualize import get_task_ir_figure 17 | from .atom_arrangement_visualize import ( 18 | get_atom_arrangement_figure, 19 | assemble_atom_arrangement_panel, 20 | ) 21 | else: 22 | # display 23 | def display_ir(obj, assignemnts): 24 | raise Warning("Bokeh not installed", UserWarning) 25 | 26 | def display_report(report): 27 | raise Warning("Bokeh not installed", UserWarning) 28 | 29 | def display_task_ir(task_ir): 30 | raise Warning("Bokeh not installed", UserWarning) 31 | 32 | def display_builder(builder, batch_id): 33 | raise Warning("Bokeh not installed", UserWarning) 34 | 35 | # visualization 36 | def get_task_ir_figure(task_ir, **fig_kwargs): 37 | raise Warning("Bokeh not installed", UserWarning) 38 | 39 | # atom arrangement 40 | def get_atom_arrangement_figure( 41 | atom_arng_ir, colors=(), fig_kwargs=None, **assignments 42 | ): 43 | raise Warning("Bokeh not installed", UserWarning) 44 | 45 | def assemble_atom_arrangement_panel(atom_arrangement_plots, keys: List[str]): 46 | raise Warning("Bokeh not installed", UserWarning) 47 | 48 | # ir 49 | def get_ir_figure(ir, **assignments): 50 | raise Warning("Bokeh not installed", UserWarning) 51 | 52 | def get_field_figure(ir_field, title, indicator, **assignments): 53 | raise Warning("Bokeh not installed", UserWarning) 54 | 55 | def get_pulse_figure(ir_pulse, title: str = None, **assginments): 56 | raise Warning("Bokeh not installed", UserWarning) 57 | 58 | # figure: 59 | def figure_ir(obj, assignemnts): 60 | raise Warning("Bokeh not installed", UserWarning) 61 | 62 | def builder_figure(builder, batch_id): 63 | raise Warning("Bokeh not installed", UserWarning) 64 | 65 | def report_figure(report): 66 | raise Warning("Bokeh not installed", UserWarning) 67 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuEraComputing/bloqade-analog/8fcbd4b0bf715dd11989512682d10d9570785b66/tests/__init__.py -------------------------------------------------------------------------------- /tests/_test_compiler_passes.py: -------------------------------------------------------------------------------- 1 | from bloqade.analog import start 2 | from bloqade.analog.ir import analog_circuit 3 | from bloqade.analog.submission.capabilities import get_capabilities 4 | from bloqade.analog.compiler.passes.hardware import ( 5 | assign_circuit, 6 | analyze_channels, 7 | generate_ahs_code, 8 | validate_waveforms, 9 | canonicalize_circuit, 10 | ) 11 | 12 | circuit = ( 13 | start.rydberg.detuning.uniform.piecewise_linear([0.1, 1.2, 0.3], [-10, -10, 10, 10]) 14 | .amplitude.uniform.piecewise_linear([0.1, 1.4, 0.1], [0, 10, 10, 0]) 15 | .parse_circuit() 16 | ) 17 | 18 | seq2 = start.rydberg.detuning.uniform.piecewise_linear( 19 | [0.3, 1.2, 0.3], [10, 10, -10, -10] 20 | ).parse_sequence() 21 | 22 | circuit = analog_circuit.AnalogCircuit(circuit.register, circuit.sequence.append(seq2)) 23 | 24 | assignments = {} 25 | capabilities = get_capabilities() 26 | 27 | 28 | level_couplings = analyze_channels(circuit) 29 | circuit = canonicalize_circuit(circuit, level_couplings) 30 | circuit = assign_circuit(circuit, assignments) 31 | validate_waveforms(level_couplings, circuit) 32 | ahs_code = generate_ahs_code(capabilities, level_couplings, circuit) 33 | # 7. specialize to QuEra/Braket IR 34 | quera_ir, parallel_decoder = ahs_code.generate_quera_ir(shots=100) 35 | braket_ir, parallel_decoder = ahs_code.generate_braket_ir(shots=100) 36 | print(quera_ir.effective_hamiltonian.rydberg.rabi_frequency_amplitude.global_) 37 | -------------------------------------------------------------------------------- /tests/_test_issue.py: -------------------------------------------------------------------------------- 1 | import bloqade.analog.ir.location as location 2 | from bloqade.analog.ir import Linear 3 | 4 | ( 5 | location.Square(3) 6 | .rydberg.detuning.uniform.apply(Linear(start=1.0, stop="x", duration=3.0)) 7 | .location(2) 8 | .scale(3.0) 9 | .apply(Linear(start=1.0, stop="x", duration=3.0)) 10 | ) 11 | 12 | ( 13 | location.Square(3) 14 | .rydberg.detuning.uniform.apply(Linear(start=1.0, stop="x", duration=3.0)) 15 | .location(2) 16 | .scale(3.0) 17 | .apply(Linear(start=1.0, stop="x", duration=3.0)) 18 | ) 19 | -------------------------------------------------------------------------------- /tests/data/config/submit_quera_api.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_hostname": "XXXXXXXXXXX.execute-api.us-east-1.amazonaws.com", 3 | "proxy": "XXXXXXXXXXX.execute-api.us-east-1.vpce.amazonaws.com", 4 | "api_stage": "v0", 5 | "qpu_id": "qpu1-mock", 6 | "region": "us-east-1" 7 | } 8 | -------------------------------------------------------------------------------- /tests/data/expected_pprint_output/list_of_locations_pprint_output.txt: -------------------------------------------------------------------------------- 1 | Atom Positions  2 | ┌────────────────────────────────────────────────────────────────────────────┐ 3 | 20┤ • │ 4 | │ │ 5 | │ │ 6 | 17┤ │ 7 | │ • │ 8 | │ │ 9 | 14┤ │ 10 | │• │ 11 | │ • • │ 12 | 11┤ • │ 13 | │ │ 14 | │ • │ 15 |  8┤ │ 16 | │ │ 17 | │ • │ 18 |  5┤ │ 19 | │ │ 20 | │ • │ 21 |  2┤ •│ 22 | └┬──────────────────┬──────────────────┬─────────────────┬──────────────────┬┘ 23 | 5.0 8.8 12.5 16.2 20.0  24 | y (um) x (um)  25 | -------------------------------------------------------------------------------- /tests/data/expected_pprint_output/rectangular_pprint_defect_count_output.txt: -------------------------------------------------------------------------------- 1 | Atom Positions  2 | ┌──────────────────────────────────────────────────────────────────────────┐ 3 | 4.00┤ •• vacant  • • • • • •│ 4 | │ │ 5 | │ │ 6 | 3.33┤ │ 7 | │• • • • • • •│ 8 | │ │ 9 | 2.67┤ │ 10 | │ │ 11 | │ │ 12 | 2.00┤• • • • • • •│ 13 | │ │ 14 | │ │ 15 | 1.33┤ │ 16 | │• • • • • • •│ 17 | │ │ 18 | 0.67┤ │ 19 | │ │ 20 | │ │ 21 | 0.00┤• • • • • • •│ 22 | └┬─────────────────┬──────────────────┬─────────────────┬─────────────────┬┘ 23 | 0.0 1.5 3.0 4.5 6.0  24 | y (um) x (um)  25 | -------------------------------------------------------------------------------- /tests/data/expected_pprint_output/rectangular_pprint_defect_density_output.txt: -------------------------------------------------------------------------------- 1 | Atom Positions  2 | ┌──────────────────────────────────────────────────────────────────────────┐ 3 | 4.00┤ •• vacant  • • • • • •│ 4 | │ │ 5 | │ │ 6 | 3.33┤ │ 7 | │• • • • • • •│ 8 | │ │ 9 | 2.67┤ │ 10 | │ │ 11 | │ │ 12 | 2.00┤• • • • • • •│ 13 | │ │ 14 | │ │ 15 | 1.33┤ │ 16 | │• • • • • • •│ 17 | │ │ 18 | 0.67┤ │ 19 | │ │ 20 | │ │ 21 | 0.00┤• • • • • • •│ 22 | └┬─────────────────┬──────────────────┬─────────────────┬─────────────────┬┘ 23 | 0.0 1.5 3.0 4.5 6.0  24 | y (um) x (um)  25 | -------------------------------------------------------------------------------- /tests/test_2d_state_compile.py: -------------------------------------------------------------------------------- 1 | from bloqade.analog.atom_arrangement import Square 2 | 3 | 4 | def test_2d_state_compile(): 5 | # Have atoms separated by 5.9 micrometers 6 | L = 3 7 | lattice_spacing = 5.9 8 | 9 | rabi_amplitude_values = [0.0, 15.8, 15.8, 0.0] 10 | rabi_detuning_values = [-16.33, -16.33, "delta_end", "delta_end"] 11 | durations = [0.8, "sweep_time", 0.8] 12 | 13 | prog = ( 14 | Square(L, lattice_spacing=lattice_spacing) 15 | .rydberg.rabi.amplitude.uniform.piecewise_linear( 16 | durations, rabi_amplitude_values 17 | ) 18 | .detuning.uniform.piecewise_linear(durations, rabi_detuning_values) 19 | ) 20 | 21 | batch = prog.assign(delta_end=42.66, sweep_time=2.4) 22 | 23 | bloqade_emu_target = batch.bloqade.python() 24 | braket_emu_target = batch.braket.local_emulator() 25 | quera_aquila_target = batch.parallelize(24).quera.aquila() 26 | braket_aquila_target = batch.parallelize(24).braket.aquila() 27 | 28 | targets = [ 29 | bloqade_emu_target, 30 | braket_emu_target, 31 | quera_aquila_target, 32 | braket_aquila_target, 33 | ] 34 | 35 | for target in targets: 36 | target._compile(10) 37 | -------------------------------------------------------------------------------- /tests/test_adiabatic_compile.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from bloqade.analog import var, cast, start 4 | 5 | 6 | def test_adiabatic_compile(): 7 | detuning_value = var("detuning_value") 8 | durations = cast(["ramp_time", "run_time", "ramp_time"]) 9 | prog = ( 10 | start.add_position([(0, 0), (0, "atom_distance")]) 11 | .rydberg.rabi.amplitude.uniform.piecewise_linear( 12 | durations=durations, values=[0, "rabi_value", "rabi_value", 0] 13 | ) 14 | .detuning.uniform.piecewise_linear( 15 | durations=durations, 16 | values=[ 17 | -detuning_value, 18 | -detuning_value, 19 | detuning_value, 20 | detuning_value, 21 | ], 22 | ) 23 | ) 24 | 25 | distances = np.arange(4, 11, 1) 26 | batch = prog.assign( 27 | ramp_time=1.0, run_time=2.0, rabi_value=15.0, detuning_value=15.0 28 | ).batch_assign(atom_distance=distances) 29 | 30 | bloqade_emu_target = batch.bloqade.python() 31 | braket_emu_target = batch.braket.local_emulator() 32 | quera_aquila_target = batch.parallelize(24).quera.aquila() 33 | braket_aquila_target = batch.parallelize(24).braket.aquila() 34 | 35 | targets = [ 36 | bloqade_emu_target, 37 | braket_emu_target, 38 | quera_aquila_target, 39 | braket_aquila_target, 40 | ] 41 | 42 | for target in targets: 43 | target._compile(10) 44 | -------------------------------------------------------------------------------- /tests/test_assign_to_literal.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from bloqade.analog import cast 4 | from bloqade.analog.ir import scalar 5 | from bloqade.analog.compiler.rewrite.common.assign_variables import AssignBloqadeIR 6 | from bloqade.analog.compiler.rewrite.common.assign_to_literal import AssignToLiteral 7 | 8 | 9 | def test_assign_to_literal(): 10 | a = cast("a") 11 | b = cast("b") 12 | c = cast("c") 13 | 14 | expr = (a - b) * c / 2.0 15 | 16 | a = Decimal("1.0") 17 | b = Decimal("2.0") 18 | c = Decimal("3.0") 19 | 20 | assigned_expr = AssignBloqadeIR(dict(a=a, b=b, c=c)).visit(expr) 21 | 22 | assert AssignToLiteral().visit(assigned_expr) == scalar.Div( 23 | scalar.Mul( 24 | scalar.Add(scalar.Literal(a), scalar.Negative(scalar.Literal(b))), 25 | scalar.Literal(c), 26 | ), 27 | scalar.Literal(2.0), 28 | ) 29 | -------------------------------------------------------------------------------- /tests/test_braket_ir.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from bloqade.analog.submission.ir.braket import to_braket_field 4 | 5 | 6 | def test_to_braket_field_type_error(): 7 | with pytest.raises(TypeError): 8 | to_braket_field("not a field type") 9 | -------------------------------------------------------------------------------- /tests/test_builder_job.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from bloqade.analog.ir.location import Square 4 | 5 | 6 | def test_braket_unsupport_parallel(): 7 | prog = Square(3) 8 | 9 | prog = prog.rydberg.detuning.uniform.piecewise_constant([0.1], [32]) 10 | prog = prog.parallelize(4) 11 | 12 | with pytest.raises(TypeError): 13 | prog.braket.local_emulator().run(shots=10) 14 | -------------------------------------------------------------------------------- /tests/test_builder_old.py: -------------------------------------------------------------------------------- 1 | from bloqade.analog import start 2 | from bloqade.analog.ir import Linear 3 | 4 | wf = Linear(start=1.0, stop="x", duration=3.0) 5 | 6 | seq = ( 7 | start.rydberg.rabi.amplitude.location(1) 8 | .linear(start=1.0, stop=2.0, duration="x") 9 | .location(2) 10 | .linear(start=1.0, stop=2.0, duration="x") 11 | .constant(1.0, 3.0) 12 | .uniform.linear(start=1.0, stop=2.0, duration="x") 13 | .parse_sequence() 14 | ) 15 | 16 | seq = ( 17 | start.rydberg.rabi.amplitude.uniform.apply( 18 | Linear(start=1.0, stop=2.0, duration="x") 19 | ) 20 | .location(1) 21 | .linear(start=1.0, stop=2.0, duration="x") 22 | .parse_sequence() 23 | ) 24 | 25 | # print(seq.seq) 26 | # print(seq.lattice) 27 | # print(seq.assignments) 28 | -------------------------------------------------------------------------------- /tests/test_builder_visual.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | import numpy as np 4 | from bokeh.io import save 5 | 6 | from bloqade.analog.ir.location import Square 7 | from bloqade.analog.visualization import builder_figure 8 | 9 | 10 | def test_builder_vis(): 11 | program = ( 12 | Square(3, lattice_spacing=1 / np.sqrt(Decimal(2))) 13 | .apply_defect_count(4) 14 | .scale(6.1) 15 | .rydberg.detuning.uniform.piecewise_linear( 16 | [0.1, 0.5, 0.1], [-10, -10, "final_detuning", "final_detuning"] 17 | ) 18 | .amplitude.uniform.piecewise_linear([0.1, 0.5, 0.1], [0, 15, 15, 0]) 19 | .batch_assign(final_detuning=np.linspace(0, 50, 51).tolist()) 20 | ) 21 | fig = builder_figure(program, 0) 22 | save(fig) 23 | -------------------------------------------------------------------------------- /tests/test_constants.py: -------------------------------------------------------------------------------- 1 | from math import pi 2 | 3 | from bloqade.analog.constants import RB_C6 4 | 5 | 6 | def test_RB_C6(): 7 | assert RB_C6 == 2 * pi * 862690 8 | -------------------------------------------------------------------------------- /tests/test_costum_submission.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Union 2 | from unittest.mock import patch 3 | 4 | import pytest 5 | import simplejson as json 6 | from requests import Response 7 | 8 | import bloqade.analog.ir.routine.quera # noqa: F401 9 | from bloqade.analog import start 10 | 11 | 12 | def create_response( 13 | status_code: int, content: Union[Dict, List[Dict]], content_type="application/json" 14 | ) -> Response: 15 | response = Response() 16 | response.status_code = status_code 17 | response.headers["Content-Type"] = content_type 18 | response._content = bytes(json.dumps(content, use_decimal=True), "utf-8") 19 | return response 20 | 21 | 22 | @patch("bloqade.analog.ir.routine.quera.request") 23 | def test_custom_submission(request_mock): 24 | body_template = '{{"token": "token", "body":{task_ir}}}' 25 | 26 | task_ids = ["1", "2", "3"] 27 | request_mock.side_effect = [ 28 | create_response(200, {"task_id": task_id}) for task_id in task_ids 29 | ] 30 | 31 | # build bloqade program 32 | program = ( 33 | start.add_position((0, 0)) 34 | .rydberg.rabi.amplitude.uniform.piecewise_linear( 35 | [0.1, "time", 0.1], [0, 15, 15, 0] 36 | ) 37 | .batch_assign(time=[0.0, 0.1, 0.5]) 38 | ) 39 | 40 | # submit and get responses and meta data associated with each task in the batch 41 | responses = program.quera.custom().submit( 42 | 100, "https://my_service.test", body_template 43 | ) 44 | 45 | for task_id, (metadata, response) in zip(task_ids, responses): 46 | response_json = response.json() 47 | assert response_json["task_id"] == task_id 48 | 49 | 50 | @patch("bloqade.analog.ir.routine.quera.request") 51 | def test_custom_submission_error_missing_task_ir_key(request_mock): 52 | body_template = '{{"token": "token", "body":}}' 53 | 54 | task_ids = ["1", "2", "3"] 55 | request_mock.side_effect = [ 56 | create_response(200, {"task_id": task_id}) for task_id in task_ids 57 | ] 58 | 59 | # build bloqade program 60 | program = ( 61 | start.add_position((0, 0)) 62 | .rydberg.rabi.amplitude.uniform.piecewise_linear( 63 | [0.1, "time", 0.1], [0, 15, 15, 0] 64 | ) 65 | .batch_assign(time=[0.0, 0.1, 0.5]) 66 | ) 67 | with pytest.raises(ValueError): 68 | # submit and get responses and meta data associated with each task in the batch 69 | program.quera.custom().submit(100, "https://my_service.test", body_template) 70 | 71 | 72 | @patch("bloqade.analog.ir.routine.quera.request") 73 | def test_custom_submission_error_malformed_template(request_mock): 74 | body_template = '{{"token": token", "body":}}' 75 | 76 | task_ids = ["1", "2", "3"] 77 | request_mock.side_effect = [ 78 | create_response(200, {"task_id": task_id}) for task_id in task_ids 79 | ] 80 | 81 | # build bloqade program 82 | program = ( 83 | start.add_position((0, 0)) 84 | .rydberg.rabi.amplitude.uniform.piecewise_linear( 85 | [0.1, "time", 0.1], [0, 15, 15, 0] 86 | ) 87 | .batch_assign(time=[0.0, 0.1, 0.5]) 88 | ) 89 | with pytest.raises(ValueError): 90 | # submit and get responses and meta data associated with each task in the batch 91 | program.quera.custom().submit(100, "https://my_service.test", body_template) 92 | -------------------------------------------------------------------------------- /tests/test_examples.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from bloqade.analog import cast, start 4 | 5 | 6 | def test_example_2(): 7 | durations = cast(["ramp_time", "run_time", "ramp_time"]) 8 | 9 | def detuning_wf(t, drive_amplitude, drive_frequency): 10 | return drive_amplitude * np.sin(drive_frequency * t) 11 | 12 | floquet_program = ( 13 | start.add_position((0, 0)) 14 | .rydberg.rabi.amplitude.uniform.piecewise_linear( 15 | durations, [0, "rabi_max", "rabi_max", 0] 16 | ) 17 | .detuning.uniform.fn(detuning_wf, sum(durations)) 18 | .sample("min_time_step", "linear") 19 | ) 20 | params = dict( 21 | drive_amplitude=15, 22 | drive_frequency=15, 23 | rabi_max=15, 24 | ramp_time=0.1, 25 | run_time=0.1, 26 | min_time_step=0.05, 27 | ) 28 | floquet_program.assign(**params).parallelize(20).braket.aquila()._compile(100) 29 | -------------------------------------------------------------------------------- /tests/test_floquet_compile.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from bloqade.analog import cast, start 4 | 5 | 6 | def test_floquet_compile(): 7 | durations = cast(["ramp_time", "run_time", "ramp_time"]) 8 | 9 | def detuning_wf(t, drive_amplitude, drive_frequency): 10 | return drive_amplitude * np.sin(drive_frequency * t) 11 | 12 | floquet_program = ( 13 | start.add_position((0, 0)) 14 | .rydberg.rabi.amplitude.uniform.piecewise_linear( 15 | durations, [0, "rabi_max", "rabi_max", 0] 16 | ) 17 | .detuning.uniform.fn(detuning_wf, sum(durations)) 18 | .sample("min_time_step", "linear") 19 | ) 20 | 21 | run_times = np.linspace(0.05, 3.0, 101) 22 | 23 | floquet_job = floquet_program.assign( 24 | ramp_time=0.06, 25 | min_time_step=0.05, 26 | rabi_max=15, 27 | drive_amplitude=15, 28 | drive_frequency=15, 29 | ).batch_assign(run_time=run_times) 30 | 31 | bloqade_emu_target = floquet_job.bloqade.python() 32 | braket_emu_target = floquet_job.braket.local_emulator() 33 | quera_aquila_target = floquet_job.parallelize(24).quera.aquila() 34 | braket_aquila_target = floquet_job.parallelize(24).braket.aquila() 35 | 36 | targets = [ 37 | bloqade_emu_target, 38 | braket_emu_target, 39 | quera_aquila_target, 40 | braket_aquila_target, 41 | ] 42 | 43 | for target in targets: 44 | target._compile(10) 45 | -------------------------------------------------------------------------------- /tests/test_hardware_codegen_lattice.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | import pytest 4 | 5 | from bloqade.analog import cast 6 | from bloqade.analog.ir.location import ListOfLocations, ParallelRegister 7 | from bloqade.analog.ir.analog_circuit import AnalogCircuit 8 | from bloqade.analog.ir.control.sequence import Sequence 9 | from bloqade.analog.submission.ir.parallel import ParallelDecoder, ClusterLocationInfo 10 | from bloqade.analog.submission.capabilities import get_capabilities 11 | from bloqade.analog.compiler.codegen.hardware.lattice import GenerateLattice 12 | 13 | 14 | def test(): 15 | lattice = ListOfLocations().add_position((0, 0)).add_position((0, 6), filling=False) 16 | 17 | capabilities = get_capabilities() 18 | 19 | capabilities.capabilities.lattice.area.height = Decimal("13.0e-6") 20 | capabilities.capabilities.lattice.area.width = Decimal("13.0e-6") 21 | capabilities.capabilities.lattice.geometry.number_sites_max = 4 22 | 23 | circuit = AnalogCircuit(lattice, Sequence({})) 24 | 25 | ahs_lattice_data = GenerateLattice(capabilities).emit(circuit) 26 | 27 | assert ahs_lattice_data.sites == [ 28 | (Decimal("0.0"), Decimal("0.0")), 29 | (Decimal("0.0"), Decimal("6.0")), 30 | ] 31 | assert ahs_lattice_data.filling == [1, 0] 32 | assert ahs_lattice_data.parallel_decoder is None 33 | 34 | parallel_lattice = ParallelRegister(lattice, cast(5)) 35 | 36 | with pytest.raises(ValueError): 37 | ahs_lattice_data = GenerateLattice().emit(parallel_lattice) 38 | 39 | ahs_lattice_data = GenerateLattice(capabilities).emit(parallel_lattice) 40 | 41 | assert ahs_lattice_data.sites == [ 42 | (Decimal("0.0"), Decimal("0.0")), 43 | (Decimal("0.0"), Decimal("6.0")), 44 | (Decimal("5.0"), Decimal("0.0")), 45 | (Decimal("5.0"), Decimal("6.0")), 46 | ] 47 | assert ahs_lattice_data.filling == [1, 0, 1, 0] 48 | assert ahs_lattice_data.parallel_decoder == ParallelDecoder( 49 | [ 50 | ClusterLocationInfo( 51 | cluster_index=(0, 0), global_location_index=0, cluster_location_index=0 52 | ), 53 | ClusterLocationInfo( 54 | cluster_index=(0, 0), global_location_index=1, cluster_location_index=1 55 | ), 56 | ClusterLocationInfo( 57 | cluster_index=(1, 0), global_location_index=2, cluster_location_index=0 58 | ), 59 | ClusterLocationInfo( 60 | cluster_index=(1, 0), global_location_index=3, cluster_location_index=1 61 | ), 62 | ] 63 | ) 64 | -------------------------------------------------------------------------------- /tests/test_ir_visual.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | from bokeh.io import save 4 | from bokeh.models import Span 5 | from bokeh.layouts import row 6 | from bokeh.palettes import Dark2_5 7 | 8 | from bloqade.analog.visualization.ir_visualize import ( 9 | Field_wvfm, 10 | SpacialMod, 11 | mock_data, 12 | mock_register, 13 | assemble_field, 14 | assemble_sequences, 15 | assemble_pulse_panel, 16 | ) 17 | 18 | 19 | def test_mock_data(): 20 | shared_indicator = Span(dimension="height") 21 | 22 | ## Rydberg: 23 | dats, names, spinfo = mock_data(10) 24 | fig = Field_wvfm( 25 | colors=itertools.cycle(Dark2_5), 26 | data_sources=dats, 27 | ch_names=names, 28 | crx_hair_overlay=shared_indicator, 29 | ) 30 | cube = SpacialMod(spinfo) 31 | p1 = assemble_field(cube, fig, "Detuning Fields") 32 | 33 | dats, names, spinfo = mock_data(10) 34 | fig = Field_wvfm( 35 | colors=itertools.cycle(Dark2_5), 36 | data_sources=dats, 37 | ch_names=names, 38 | crx_hair_overlay=shared_indicator, 39 | ) 40 | cube = SpacialMod(spinfo) 41 | p2 = assemble_field(cube, fig, "Rabi amp Fields") 42 | 43 | dats, names, spinfo = mock_data(10) 44 | fig = Field_wvfm( 45 | colors=itertools.cycle(Dark2_5), 46 | data_sources=dats, 47 | ch_names=names, 48 | crx_hair_overlay=shared_indicator, 49 | ) 50 | cube = SpacialMod(spinfo) 51 | p3 = assemble_field(cube, fig, "Rabi phase Fields") 52 | 53 | Panel_Pulse1 = assemble_pulse_panel([p1, p2, p3], "Rydberg") 54 | 55 | shared_indicator = Span(dimension="height") 56 | 57 | ## Hyperfine: 58 | dats, names, spinfo = mock_data(10) 59 | fig = Field_wvfm( 60 | colors=itertools.cycle(Dark2_5), 61 | data_sources=dats, 62 | ch_names=names, 63 | crx_hair_overlay=shared_indicator, 64 | ) 65 | cube = SpacialMod(spinfo) 66 | p1 = assemble_field(cube, fig, "Detuning Fields") 67 | 68 | dats, names, spinfo = mock_data(10) 69 | fig = Field_wvfm( 70 | colors=itertools.cycle(Dark2_5), 71 | data_sources=dats, 72 | ch_names=names, 73 | crx_hair_overlay=shared_indicator, 74 | ) 75 | cube = SpacialMod(spinfo) 76 | p2 = assemble_field(cube, fig, "Rabi amp Fields") 77 | 78 | dats, names, spinfo = mock_data(10) 79 | fig = Field_wvfm( 80 | colors=itertools.cycle(Dark2_5), 81 | data_sources=dats, 82 | ch_names=names, 83 | crx_hair_overlay=shared_indicator, 84 | ) 85 | cube = SpacialMod(spinfo) 86 | p3 = assemble_field(cube, fig, "Rabi phase Fields") 87 | 88 | Panel_Pulse2 = assemble_pulse_panel([p1, p2, p3], "Hyperfine") 89 | 90 | Seq = assemble_sequences([Panel_Pulse1, Panel_Pulse2]) 91 | 92 | save(row(Seq, mock_register())) 93 | -------------------------------------------------------------------------------- /tests/test_is_constant.py: -------------------------------------------------------------------------------- 1 | from bloqade.analog.atom_arrangement import Chain 2 | from bloqade.analog.compiler.analysis.common.is_constant import IsConstant 3 | 4 | 5 | def test_happy_path(): 6 | circuit = ( 7 | Chain(8, lattice_spacing=6.1) 8 | .rydberg.detuning.uniform.constant(1.0, 10.0) 9 | .amplitude.uniform.linear(1.0, 1.0, 5.0) 10 | .linear(1.0, 1.0, 5.0) 11 | .phase.uniform.poly([1.0, 0.0, 0.0, 0.0, 0.0], 10) 12 | .parse_circuit() 13 | ) 14 | 15 | assert IsConstant().scan(circuit) 16 | 17 | circuit = ( 18 | Chain(8, lattice_spacing=6.1) 19 | .rydberg.detuning.uniform.constant(1.0, 5.0) 20 | .uniform.constant(1.0, 10.0) 21 | .slice(2.5, 7.5) 22 | .amplitude.uniform.linear(1.0, 1.0, 5.0) 23 | .linear(1.0, 1.0, 5.0) 24 | .slice(2.5, 7.5) 25 | .record("var") 26 | .phase.uniform.poly([1.0, 0.0, 0.0, 0.0, 0.0], 5) 27 | .parse_circuit() 28 | ) 29 | print(circuit) 30 | # assert False 31 | assert IsConstant().scan(circuit) 32 | 33 | 34 | def test_fail_shape(): 35 | circuit = ( 36 | Chain(8, lattice_spacing=6.1) 37 | .rydberg.detuning.uniform.constant(1.0, 5.0) 38 | .uniform.constant(1.0, 10.0) 39 | .slice(2.5, 7.5) 40 | .amplitude.uniform.linear(1.0, 1.0, 5.0) 41 | .linear(1.0, 1.0, 5.0) 42 | .slice(2.5, 7.5) 43 | .record("var") 44 | .phase.uniform.poly([1.0, 1.0, 0.0, 0.0, 0.0], 5) 45 | .parse_circuit() 46 | ) 47 | print(circuit) 48 | # assert False 49 | assert not IsConstant().scan(circuit) 50 | 51 | circuit = ( 52 | Chain(8, lattice_spacing=6.1) 53 | .rydberg.detuning.uniform.constant(1.0, 5.0) 54 | .uniform.constant(1.0, 10.0) 55 | .slice(2.5, 7.5) 56 | .amplitude.uniform.linear(1.0, 1.0, 5.0) 57 | .linear(1.0, 1.0, 5.0) 58 | .slice(2.5, 7.5) 59 | .record("var") 60 | .phase.uniform.poly([1.0, 1.0, 0.0, 0.0, 0.0], 5) 61 | .amplitude.uniform.fn(lambda t: t, 5) 62 | .sample(0.05) 63 | .parse_circuit() 64 | ) 65 | print(circuit) 66 | # assert False 67 | assert not IsConstant().scan(circuit) 68 | 69 | 70 | def test_fail_duration(): 71 | circuit = ( 72 | Chain(8, lattice_spacing=6.1) 73 | .rydberg.detuning.uniform.constant(1.0, 9.0) 74 | .amplitude.uniform.linear(1.0, 1.0, 5.0) 75 | .linear(1.0, 1.0, 5.0) 76 | .phase.uniform.poly([1.0, 0.0, 0.0, 0.0, 0.0], 10) 77 | .parse_circuit() 78 | ) 79 | 80 | print(circuit) 81 | # assert False 82 | assert not IsConstant().scan(circuit) 83 | 84 | circuit = ( 85 | Chain(8, lattice_spacing=6.1) 86 | .rydberg.detuning.uniform.constant(1.0, 10.0) 87 | .uniform.constant(1.0, 9.0) 88 | .amplitude.uniform.linear(1.0, 1.0, 5.0) 89 | .linear(1.0, 1.0, 5.0) 90 | .phase.uniform.poly([1.0, 0.0, 0.0, 0.0, 0.0], 10) 91 | .parse_circuit() 92 | ) 93 | 94 | print(circuit) 95 | # assert False 96 | assert not IsConstant().scan(circuit) 97 | 98 | 99 | def test_fail_value(): 100 | circuit = ( 101 | Chain(8, lattice_spacing=6.1) 102 | .rydberg.detuning.uniform.constant(1.0, 10.0) 103 | .constant(1.1, 10.0) 104 | .parse_circuit() 105 | ) 106 | 107 | print(circuit) 108 | # assert False 109 | assert not IsConstant().scan(circuit) 110 | -------------------------------------------------------------------------------- /tests/test_lattice_gauge_theory_compile.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from bloqade.analog import cast, piecewise_linear 4 | from bloqade.analog.ir.location import Chain 5 | 6 | 7 | def test_lattice_gauge_theory_compile(): 8 | N_atom = 13 9 | 10 | detuning_ratio = [0] * N_atom 11 | detuning_ratio[1 : (N_atom - 1) : 2] = [1, 1, 1, 1, 1, 1] 12 | detuning_ratio[(N_atom - 1) // 2] = 1 13 | 14 | run_time = cast("run_time") 15 | 16 | rabi_amplitude_wf = piecewise_linear( 17 | durations=[0.1, 2.0, 0.05, run_time, 0.05], 18 | values=[0, 5 * np.pi, 5 * np.pi, 4 * np.pi, 4 * np.pi, 0], 19 | ) 20 | uniform_detuning_wf = piecewise_linear( 21 | durations=[2.1, 0.05, run_time + 0.05], values=[-6 * np.pi, 8 * np.pi, 0, 0] 22 | ) 23 | local_detuning_wf = piecewise_linear( 24 | [0.1, 2.0, 0.05, run_time + 0.05], 25 | values=[0, -8 * 2 * np.pi, -8 * 2 * np.pi, 0, 0], 26 | ) 27 | 28 | program = ( 29 | Chain(N_atom, lattice_spacing=5.5, vertical_chain=True) 30 | .rydberg.rabi.amplitude.uniform.apply(rabi_amplitude_wf) 31 | .detuning.uniform.apply(uniform_detuning_wf) 32 | .scale(detuning_ratio) 33 | .apply(local_detuning_wf) 34 | ) 35 | 36 | run_times = np.arange(0.0, 1.05, 0.05) 37 | batch = program.batch_assign(run_time=run_times) 38 | 39 | bloqade_emu_target = batch.bloqade.python() 40 | braket_emu_target = batch.braket.local_emulator() 41 | quera_aquila_target = batch.parallelize(15).quera.aquila() 42 | braket_aquila_target = batch.parallelize(15).braket.aquila() 43 | 44 | targets = [ 45 | bloqade_emu_target, 46 | braket_emu_target, 47 | quera_aquila_target, 48 | braket_aquila_target, 49 | ] 50 | 51 | for target in targets: 52 | target._compile(10) 53 | -------------------------------------------------------------------------------- /tests/test_migrate.py: -------------------------------------------------------------------------------- 1 | import io 2 | 3 | import simplejson as json 4 | 5 | from bloqade.analog.migrate import _migrate 6 | 7 | 8 | def test_walk_dict(): 9 | 10 | obj = { 11 | "key1": "value1", 12 | "bloqade.key2": "value2", 13 | "bloqade.analog.key3": "value3", 14 | "nested": {"key4": "bloqade.value4", "bloqade.key5": "value5"}, 15 | "list": [{"key6": "value6"}, {"bloqade.key7": "value7"}], 16 | } 17 | 18 | expected = { 19 | "key1": "value1", 20 | "bloqade.analog.key2": "value2", 21 | "bloqade.analog.key3": "value3", 22 | "nested": {"key4": "bloqade.value4", "bloqade.analog.key5": "value5"}, 23 | "list": [{"key6": "value6"}, {"bloqade.analog.key7": "value7"}], 24 | } 25 | 26 | obj_str = json.dumps(obj) 27 | expected_str = json.dumps(expected) 28 | 29 | in_io = io.StringIO(obj_str) 30 | out_io = io.StringIO() 31 | 32 | _migrate(in_io, out_io) 33 | 34 | assert out_io.getvalue() == expected_str 35 | -------------------------------------------------------------------------------- /tests/test_mis_compile.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from bloqade.analog.atom_arrangement import Square 4 | 5 | 6 | def test_mis_compile(): 7 | rng = np.random.default_rng(1234) 8 | 9 | durations = [0.3, 1.6, 0.3] 10 | 11 | mis_udg_program = ( 12 | Square(15, lattice_spacing=5.0) 13 | .apply_defect_density(0.3, rng=rng) 14 | .rydberg.rabi.amplitude.uniform.piecewise_linear( 15 | durations, [0.0, 15.0, 15.0, 0.0] 16 | ) 17 | .detuning.uniform.piecewise_linear( 18 | durations, [-30, -30, "final_detuning", "final_detuning"] 19 | ) 20 | ) 21 | 22 | mis_udg_job = mis_udg_program.batch_assign(final_detuning=np.linspace(0, 80, 41)) 23 | 24 | # skip emulation targets considering not feasible to emulate 25 | # bloqade_emu_target = mis_udg_job.bloqade.python() 26 | # braket_emu_target = mis_udg_job.braket.local_emulator() 27 | quera_aquila_target = mis_udg_job.quera.aquila() 28 | braket_aquila_target = mis_udg_job.braket.aquila() 29 | 30 | targets = [quera_aquila_target, braket_aquila_target] 31 | 32 | for target in targets: 33 | target._compile(10) 34 | -------------------------------------------------------------------------------- /tests/test_misc.py: -------------------------------------------------------------------------------- 1 | import bloqade.analog 2 | 3 | 4 | def test_global_treedepth(): 5 | bloqade.analog.tree_depth(4) 6 | assert bloqade.analog.tree_depth() == 4 7 | bloqade.analog.tree_depth(10) 8 | assert bloqade.analog.tree_depth() == 10 9 | -------------------------------------------------------------------------------- /tests/test_mock_submit.py: -------------------------------------------------------------------------------- 1 | import bloqade.analog.ir.location as location 2 | from bloqade.analog.ir import Linear, Constant 3 | from bloqade.analog.serialize import dumps, loads 4 | 5 | 6 | def test(): 7 | batch = ( 8 | location.Square(6) 9 | .rydberg.detuning.uniform.apply( 10 | Constant("initial_detuning", "up_time") 11 | .append(Linear("initial_detuning", "final_detuning", "anneal_time")) 12 | .append(Constant("final_detuning", "up_time")) 13 | ) 14 | .rabi.amplitude.uniform.apply( 15 | Linear(0.0, "rabi_amplitude_max", "up_time") 16 | .append(Constant("rabi_amplitude_max", "anneal_time")) 17 | .append(Linear("rabi_amplitude_max", 0.0, "up_time")) 18 | ) 19 | .assign( 20 | initial_detuning=-10, 21 | up_time=0.1, 22 | final_detuning=15, 23 | anneal_time=10, 24 | rabi_amplitude_max=15, 25 | ) 26 | .quera.mock() 27 | ._compile(shots=10) 28 | ) 29 | 30 | batch_str = dumps(batch) 31 | assert isinstance(loads(batch_str), type(batch)) 32 | -------------------------------------------------------------------------------- /tests/test_multi_rabi_compile.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from bloqade.analog import start 4 | 5 | 6 | def test_multi_rabi_compile(): 7 | distance = 4.0 8 | 9 | # Example defaults to option 7 10 | geometry = start.add_position( 11 | [ 12 | (0, 0), 13 | (distance, 0), 14 | (-0.5 * distance, distance), 15 | (0.5 * distance, distance), 16 | (1.5 * distance, distance), 17 | (0, 2 * distance), 18 | (distance, 2 * distance), 19 | ] 20 | ) 21 | 22 | sequence = start.rydberg.rabi.amplitude.uniform.piecewise_linear( 23 | durations=["ramp_time", "run_time", "ramp_time"], 24 | values=[0.0, "rabi_drive", "rabi_drive", 0.0], 25 | ).parse_sequence() 26 | 27 | batch = ( 28 | geometry.apply(sequence) 29 | .assign(ramp_time=0.06, rabi_drive=5) 30 | .batch_assign(run_time=0.05 * np.arange(21)) 31 | ) 32 | 33 | bloqade_emu_target = batch.bloqade.python() 34 | braket_emu_target = batch.braket.local_emulator() 35 | quera_aquila_target = batch.parallelize(24).quera.aquila() 36 | braket_aquila_target = batch.parallelize(24).braket.aquila() 37 | 38 | targets = [ 39 | bloqade_emu_target, 40 | braket_emu_target, 41 | quera_aquila_target, 42 | braket_aquila_target, 43 | ] 44 | 45 | for target in targets: 46 | target._compile(10) 47 | -------------------------------------------------------------------------------- /tests/test_noneq_dynamics_compile.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from bloqade.analog.atom_arrangement import Chain 4 | 5 | 6 | def test_non_eq_compile(): 7 | initial_geometry = Chain(2, lattice_spacing="distance") 8 | program_waveforms = ( 9 | initial_geometry.rydberg.rabi.amplitude.uniform.piecewise_linear( 10 | durations=["ramp_time", "run_time", "ramp_time"], 11 | values=[0.0, "rabi_ampl", "rabi_ampl", 0.0], 12 | ) 13 | ) 14 | program_assigned_vars = program_waveforms.assign( 15 | ramp_time=0.06, rabi_ampl=15, distance=8.5 16 | ) 17 | batch = program_assigned_vars.batch_assign(run_time=0.05 * np.arange(31)) 18 | 19 | bloqade_emu_target = batch.bloqade.python() 20 | braket_emu_target = batch.braket.local_emulator() 21 | quera_aquila_target = batch.parallelize(24).quera.aquila() 22 | braket_aquila_target = batch.parallelize(24).braket.aquila() 23 | 24 | targets = [ 25 | bloqade_emu_target, 26 | braket_emu_target, 27 | quera_aquila_target, 28 | braket_aquila_target, 29 | ] 30 | 31 | for target in targets: 32 | target._compile(10) 33 | -------------------------------------------------------------------------------- /tests/test_parallelize.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from bloqade.analog import start 4 | from bloqade.analog.ir.location import Square 5 | 6 | 7 | # create lattice 8 | def test_parallel_task(): 9 | lattice = Square(3, lattice_spacing=6) 10 | 11 | quantum_batch = ( 12 | lattice.rydberg.detuning.uniform.piecewise_linear( 13 | durations=["up_time", "anneal_time", "up_time"], 14 | values=[ 15 | "initial_detuning", 16 | "initial_detuning", 17 | "final_detuning", 18 | "final_detuning", 19 | ], 20 | ) 21 | .rydberg.rabi.amplitude.uniform.piecewise_linear( 22 | durations=["up_time", "anneal_time", "up_time"], 23 | values=[0, "rabi_amplitude_max", "rabi_amplitude_max", 0], 24 | ) 25 | .assign( 26 | initial_detuning=-10, 27 | up_time=0.1, 28 | final_detuning=15, 29 | anneal_time=10, 30 | rabi_amplitude_max=15, 31 | ) 32 | .parallelize(10.0) 33 | .quera.mock() 34 | ._compile(shots=10) 35 | ) 36 | 37 | assert quantum_batch.tasks[0].parallel_decoder 38 | 39 | 40 | def test_error_parallel_noatom(): 41 | with pytest.raises(ValueError): 42 | start.rydberg.detuning.uniform.piecewise_linear( 43 | durations=["up_time", "anneal_time", "up_time"], 44 | values=[ 45 | "initial_detuning", 46 | "initial_detuning", 47 | "final_detuning", 48 | "final_detuning", 49 | ], 50 | ).parallelize(10.0).quera.mock()._compile(shots=1) 51 | -------------------------------------------------------------------------------- /tests/test_printer.py: -------------------------------------------------------------------------------- 1 | from io import StringIO 2 | 3 | from IPython.lib.pretty import PrettyPrinter as PP 4 | 5 | import bloqade.analog.ir.tree_print as trp 6 | from bloqade.analog.ir import Poly, Linear 7 | from bloqade.analog.ir.tree_print import Printer 8 | 9 | trp.color_enabled = False 10 | 11 | 12 | def test_printer(): 13 | wf = Linear(start=0, stop=1, duration=1) 14 | 15 | mystdout = StringIO() 16 | p = PP(mystdout) 17 | 18 | wf._repr_pretty_(p, 1) 19 | 20 | assert ( 21 | mystdout.getvalue() 22 | == "Linear\n" 23 | + "├─ start\n" 24 | + "│ ⇒ Literal: 0\n" 25 | + "├─ stop\n" 26 | + "│ ⇒ Literal: 1\n" 27 | + "└─ duration\n" 28 | + " ⇒ Literal: 1" 29 | ) 30 | 31 | 32 | def test_printer_nodes_list(): 33 | wf = Linear(start=0, stop=1, duration=1) 34 | wf2 = wf + (wf + wf) 35 | 36 | mystdout = StringIO() 37 | p = PP(mystdout) 38 | 39 | pr = Printer(p) 40 | children = wf2.children().copy() # children is list so no print children 41 | assert pr.should_print_annotation(children) is False 42 | 43 | 44 | def test_printer_nodes_dict(): 45 | wf = Poly([1, 2, 3], duration=1) 46 | 47 | mystdout = StringIO() 48 | p = PP(mystdout) 49 | 50 | pr = Printer(p) 51 | children = wf.children().copy() # children is list so no print children 52 | 53 | assert pr.should_print_annotation(children) is True 54 | wf._repr_pretty_(p, 10) 55 | 56 | assert ( 57 | mystdout.getvalue() 58 | == "Poly\n" 59 | + "├─ b\n" 60 | + "│ ⇒ Literal: 1\n" 61 | + "├─ t\n" 62 | + "│ ⇒ Literal: 2\n" 63 | + "├─ t^2\n" 64 | + "│ ⇒ Literal: 3\n" 65 | + "└─ duration\n" 66 | + " ⇒ Literal: 1" 67 | ) 68 | 69 | 70 | def test_printer_nodes_compose_turncate(): 71 | wf = Poly([1, 2, 3], duration=1) 72 | wf1 = Linear(start=0, stop=1, duration=1) 73 | wf2 = wf + (wf[:0.5] + wf1) 74 | 75 | mystdout = StringIO() 76 | p = PP(mystdout) 77 | 78 | wf2._repr_pretty_(p, 0) 79 | 80 | assert mystdout.getvalue() == "+\n├─ Poly\n⋮\n└─ +\n⋮\n" 81 | 82 | 83 | def test_printer_nodes_compose_all(): 84 | wf = Poly([1, 2, 3], duration=1) 85 | wf1 = Linear(start=0, stop=1, duration=1) 86 | wf2 = wf + (wf[:0.5] + wf1) 87 | 88 | mystdout = StringIO() 89 | p = PP(mystdout) 90 | 91 | wf2._repr_pretty_(p, 10) 92 | 93 | assert ( 94 | mystdout.getvalue() 95 | == "+\n" 96 | + "├─ Poly\n" 97 | + "│ ├─ b\n" 98 | + "│ │ ⇒ Literal: 1\n" 99 | + "│ ├─ t\n" 100 | + "│ │ ⇒ Literal: 2\n" 101 | + "│ ├─ t^2\n" 102 | + "│ │ ⇒ Literal: 3\n" 103 | + "│ └─ duration\n" 104 | + "│ ⇒ Literal: 1\n" 105 | + "└─ +\n" 106 | + " ├─ Slice\n" 107 | + " │ ├─ Interval\n" 108 | + " │ │ └─ stop\n" 109 | + " │ │ ⇒ Literal: 0.5\n" 110 | + " │ └─ Poly\n" 111 | + " │ ├─ b\n" 112 | + " │ │ ⇒ Literal: 1\n" 113 | + " │ ├─ t\n" 114 | + " │ │ ⇒ Literal: 2\n" 115 | + " │ ├─ t^2\n" 116 | + " │ │ ⇒ Literal: 3\n" 117 | + " │ └─ duration\n" 118 | + " │ ⇒ Literal: 1\n" 119 | + " └─ Linear\n" 120 | + " ├─ start\n" 121 | + " │ ⇒ Literal: 0\n" 122 | + " ├─ stop\n" 123 | + " │ ⇒ Literal: 1\n" 124 | + " └─ duration\n" 125 | + " ⇒ Literal: 1" 126 | ) 127 | -------------------------------------------------------------------------------- /tests/test_program_visitor.py: -------------------------------------------------------------------------------- 1 | # from bloqade import start 2 | from bloqade.analog import start 3 | from bloqade.analog.ir.location import Square 4 | from bloqade.analog.ir.control.waveform import Linear 5 | 6 | wv_linear = Linear(start=0, stop=4, duration=0.1) 7 | reg = Square(3) 8 | prog = reg.rydberg.detuning.uniform.piecewise_constant([0.1], [0.1]).parallelize(10.0) 9 | para_reg = prog.parse_register() 10 | seq = prog.parse_sequence() 11 | prog2 = start.rydberg.detuning.location(1, 2).piecewise_constant([0.1], [0.1]) 12 | seq2 = prog2.parse_sequence() 13 | 14 | 15 | def test_get_natoms(): 16 | prog = ( 17 | Square(5) 18 | .rydberg.detuning.uniform.piecewise_linear( 19 | [0.1, 0.9, 0.1], 20 | [ 21 | -10, 22 | -10, 23 | 10, 24 | 10, 25 | ], 26 | ) 27 | .hyperfine.detuning.uniform.piecewise_linear( 28 | [0.1, 0.9, 0.1], 29 | [ 30 | -10, 31 | -10, 32 | 10, 33 | 10, 34 | ], 35 | ) 36 | ) 37 | 38 | assert prog.n_atoms == 25 39 | -------------------------------------------------------------------------------- /tests/test_ramsey_compile.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | import numpy as np 4 | 5 | from bloqade.analog import cast, start 6 | 7 | 8 | def test_ramsey_compile(): 9 | plateau_time = (np.pi / 2 - 0.625) / 12.5 10 | wf_durations = cast( 11 | [0.05, plateau_time, 0.05, "run_time", 0.05, plateau_time, 0.05] 12 | ) 13 | rabi_wf_values = [0.0, 12.5, 12.5, 0.0] * 2 # repeat values twice 14 | 15 | ramsey_program = ( 16 | start.add_position((0, 0)) 17 | .rydberg.rabi.amplitude.uniform.piecewise_linear(wf_durations, rabi_wf_values) 18 | .detuning.uniform.constant(10.5, sum(wf_durations)) 19 | ) 20 | 21 | n_steps = 100 22 | max_time = Decimal("3.0") 23 | dt = (max_time - Decimal("0.05")) / n_steps 24 | run_times = [Decimal("0.05") + dt * i for i in range(101)] 25 | 26 | ramsey_job = ramsey_program.batch_assign(run_time=run_times) 27 | 28 | bloqade_emu_target = ramsey_job.bloqade.python() 29 | braket_emu_target = ramsey_job.braket.local_emulator() 30 | quera_aquila_target = ramsey_job.parallelize(24).quera.aquila() 31 | braket_aquila_target = ramsey_job.parallelize(24).braket.aquila() 32 | 33 | targets = [ 34 | bloqade_emu_target, 35 | braket_emu_target, 36 | quera_aquila_target, 37 | braket_aquila_target, 38 | ] 39 | 40 | for target in targets: 41 | target._compile(10) 42 | -------------------------------------------------------------------------------- /tests/test_report_visual.py: -------------------------------------------------------------------------------- 1 | from bloqade.analog.visualization.report_visualize import mock_data, report_visual 2 | 3 | 4 | def test_report_vis_mock(): 5 | dat = mock_data() 6 | 7 | report_visual(*dat) 8 | 9 | # from bokeh.models import SVGIcon 10 | 11 | # p = figure(width=200, height=100, toolbar_location=None) 12 | # p.image_url(url="file:///./logo.png") 13 | # button = Button(label="", icon=SVGIcon(svg=bloqadeICON(), size=50)) 14 | # show(button) 15 | -------------------------------------------------------------------------------- /tests/test_run_callback.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | from bloqade.analog import start 5 | from bloqade.analog.emulate.ir.state_vector import AnalogGate 6 | from bloqade.analog.emulate.codegen.hamiltonian import RydbergHamiltonianCodeGen 7 | from bloqade.analog.compiler.codegen.python.emulator_ir import EmulatorProgramCodeGen 8 | 9 | 10 | def callback(register, *_): 11 | return register.data 12 | 13 | 14 | def test_run_callback(): 15 | program = ( 16 | start.add_position((0, 0)) 17 | .add_position((0, 6.1)) 18 | .rydberg.detuning.uniform.constant(15, 0.1) 19 | .rabi.amplitude.uniform.constant(15, 0.1) 20 | ) 21 | 22 | bloqade_ir = program.parse_circuit() 23 | emulatir_ir = EmulatorProgramCodeGen().emit(bloqade_ir) 24 | hamiltonian = RydbergHamiltonianCodeGen().emit(emulatir_ir) 25 | 26 | state = hamiltonian.space.zero_state(np.complex128) 27 | (expected_result,) = AnalogGate(hamiltonian).apply(state) 28 | 29 | (result_single,) = program.bloqade.python().run_callback(callback) 30 | (result_multi,) = program.bloqade.python().run_callback( 31 | callback, multiprocessing=True, num_workers=1 32 | ) 33 | 34 | np.testing.assert_equal(expected_result.data, result_single.data) 35 | np.testing.assert_equal(expected_result.data, result_multi.data) 36 | 37 | 38 | def callback_exception(*args): 39 | raise ValueError 40 | 41 | 42 | def test_run_callback_exception(): 43 | program = ( 44 | start.add_position((0, 0)) 45 | .add_position((0, 6.1)) 46 | .rydberg.detuning.uniform.constant(15, 1) 47 | .rabi.amplitude.uniform.constant(15, 1) 48 | ) 49 | 50 | # program.bloqade.python().run_callback(callback_exception) 51 | 52 | with pytest.raises(RuntimeError): 53 | program.bloqade.python().run_callback(callback_exception) 54 | 55 | with pytest.raises(RuntimeError): 56 | program.bloqade.python().run_callback( 57 | callback_exception, multiprocessing=True, num_workers=1 58 | ) 59 | 60 | (result_single,) = program.bloqade.python().run_callback( 61 | callback_exception, ignore_exceptions=True 62 | ) 63 | (result_multi,) = program.bloqade.python().run_callback( 64 | callback_exception, multiprocessing=True, num_workers=1, ignore_exceptions=True 65 | ) 66 | 67 | assert isinstance(result_single, ValueError) 68 | assert isinstance(result_multi, ValueError) 69 | 70 | 71 | if __name__ == "__main__": 72 | test_run_callback() 73 | test_run_callback_exception() 74 | -------------------------------------------------------------------------------- /tests/test_scar_compile.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from bloqade.analog import var 4 | from bloqade.analog.atom_arrangement import Chain 5 | 6 | 7 | def test_scar_compile(): 8 | n_atoms = 11 9 | lattice_spacing = 6.1 10 | run_time = var("run_time") 11 | 12 | quantum_scar_program = ( 13 | Chain(n_atoms, lattice_spacing=lattice_spacing) 14 | # define detuning waveform 15 | .rydberg.detuning.uniform.piecewise_linear( 16 | [0.3, 1.6, 0.3], [-18.8, -18.8, 16.3, 16.3] 17 | ) 18 | .piecewise_linear([0.2, 1.6], [16.3, 0.0, 0.0]) 19 | # slice the detuning waveform 20 | .slice(start=0, stop=run_time) 21 | # define rabi waveform 22 | .amplitude.uniform.piecewise_linear([0.3, 1.6, 0.3], [0.0, 15.7, 15.7, 0.0]) 23 | .piecewise_linear([0.2, 1.4, 0.2], [0, 15.7, 15.7, 0]) 24 | # slice waveform, add padding for the linear segment 25 | .slice(start=0, stop=run_time - 0.065) 26 | # record the value of the waveform at the end of the slice to "rabi_value" 27 | .record("rabi_value") 28 | # append segment to waveform that fixes the value of the waveform to 0 29 | # at the end of the waveform 30 | .linear("rabi_value", 0, 0.065) 31 | ) 32 | 33 | # get run times via the following: 34 | prep_times = np.arange(0.2, 2.2, 0.2) 35 | scar_times = np.arange(2.2, 4.01, 0.01) 36 | run_times = np.unique(np.hstack((prep_times, scar_times))) 37 | 38 | batch = quantum_scar_program.batch_assign(run_time=run_times) 39 | 40 | bloqade_emu_target = batch.bloqade.python() 41 | braket_emu_target = batch.braket.local_emulator() 42 | quera_aquila_target = batch.parallelize(24).quera.aquila() 43 | braket_aquila_target = batch.parallelize(24).braket.aquila() 44 | 45 | targets = [ 46 | bloqade_emu_target, 47 | braket_emu_target, 48 | quera_aquila_target, 49 | braket_aquila_target, 50 | ] 51 | 52 | for target in targets: 53 | target._compile(10) 54 | -------------------------------------------------------------------------------- /tests/test_serialize_decorator.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from numbers import Real 3 | 4 | from beartype import beartype 5 | from beartype.typing import Any, Dict, Union 6 | 7 | from bloqade.analog.serialize import Serializer, dumps, loads 8 | 9 | LiteralType = Union[Decimal, Real] 10 | 11 | 12 | @Serializer.register 13 | class A: 14 | def __init__(self, x, y): 15 | self.x = x 16 | self.y = y 17 | 18 | def __eq__(self, __value: object) -> bool: 19 | if isinstance(__value, A): 20 | return self.x == __value.x and self.y == __value.y 21 | 22 | return False 23 | 24 | 25 | @Serializer.register 26 | class B: 27 | def __init__(self, x: float, y): 28 | self._x = x 29 | self._y = y 30 | 31 | def __eq__(self, __value: object) -> bool: 32 | if isinstance(__value, B): 33 | return self._x == __value._x and self._y == __value._y 34 | 35 | 36 | @B.set_serializer 37 | def _serialize(obj: B): 38 | return {"x": obj._x, "y": obj._y} 39 | 40 | 41 | @Serializer.register 42 | class C: 43 | @beartype 44 | def __init__(self, x: LiteralType): 45 | self._x = Decimal(str(x)) 46 | self._y = 2 * x 47 | 48 | def __eq__(self, __value: object) -> bool: 49 | if isinstance(__value, C): 50 | return self._x == __value._x 51 | 52 | return False 53 | 54 | 55 | @C.set_serializer 56 | def _serializer(obj: C) -> Dict[str, Any]: 57 | return {"x": obj._x} 58 | 59 | 60 | @C.set_deserializer 61 | def _deserializer(d: Dict[str, Any]) -> C: 62 | d = {"x": Decimal(d["x"])} 63 | return C(**d) 64 | 65 | 66 | def test(): 67 | assert f'{{"{A.__module__}.A": {{"x": 1, "y": 2}}}}' == dumps(A(1, 2)), repr( 68 | A(1, 2) 69 | ) 70 | assert f'{{"{B.__module__}.B": {{"x": 1, "y": 2}}}}' == dumps(B(1, 2)), repr( 71 | dumps(B(1, 2)) 72 | ) 73 | assert f'{{"{C.__module__}.C": {{"x": 1.23091023939020342342039402304}}}}' == dumps( 74 | C(Decimal("1.23091023939020342342039402304")) 75 | ) 76 | 77 | assert B(C(1), A(1, 2)) == loads(dumps(B(C(1), A(1, 2)))) 78 | assert A(1, B(1, 2)) == loads(dumps(A(1, B(1, 2)))) 79 | assert A(A(A(A(1, 2), 3), 4), C(4)) == loads(dumps(A(A(A(A(1, 2), 3), 4), C(4)))) 80 | 81 | a = A("a", "b") 82 | a = A(a, "c") 83 | a = A(1, a) 84 | a = A(B(a, C(1)), B("A", a)) 85 | a = A(B(1, 2), A(a, C(2))) 86 | 87 | assert a == loads(dumps(a)) 88 | 89 | 90 | test() 91 | -------------------------------------------------------------------------------- /tests/test_single_rabi_compile.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from bloqade.analog import cast, start 4 | 5 | 6 | def test_single_rabi_compile(): 7 | durations = cast(["ramp_time", "run_time", "ramp_time"]) 8 | 9 | rabi_oscillations_program = ( 10 | start.add_position((0, 0)) 11 | .rydberg.rabi.amplitude.uniform.piecewise_linear( 12 | durations=durations, values=[0, "rabi_ampl", "rabi_ampl", 0] 13 | ) 14 | .detuning.uniform.constant(duration=sum(durations), value="detuning_value") 15 | ) 16 | 17 | run_times = np.linspace(0, 3, 101) 18 | 19 | rabi_oscillation_job = rabi_oscillations_program.assign( 20 | ramp_time=0.06, rabi_ampl=15, detuning_value=0.0 21 | ).batch_assign(run_time=run_times) 22 | 23 | bloqade_emu_target = rabi_oscillation_job.bloqade.python() 24 | braket_emu_target = rabi_oscillation_job.braket.local_emulator() 25 | quera_aquila_target = rabi_oscillation_job.parallelize(24).quera.aquila() 26 | braket_aquila_target = rabi_oscillation_job.parallelize(24).braket.aquila() 27 | 28 | targets = [ 29 | bloqade_emu_target, 30 | braket_emu_target, 31 | quera_aquila_target, 32 | braket_aquila_target, 33 | ] 34 | 35 | for target in targets: 36 | target._compile(10) 37 | -------------------------------------------------------------------------------- /tests/test_submission_base.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from bloqade.analog.submission.base import SubmissionBackend 4 | 5 | 6 | def test_submission_base(): 7 | A = SubmissionBackend() 8 | 9 | with pytest.raises(NotImplementedError): 10 | A.cancel_task("1") 11 | 12 | with pytest.raises(NotImplementedError): 13 | A.task_results("1") 14 | 15 | with pytest.raises(NotImplementedError): 16 | A.task_status("1") 17 | -------------------------------------------------------------------------------- /tests/test_task.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | 4 | import pytest 5 | 6 | from bloqade.analog.task.batch import RemoteBatch 7 | from bloqade.analog.atom_arrangement import Chain 8 | 9 | 10 | def test_batch_error(*args): 11 | with pytest.raises(RemoteBatch.SubmissionException): 12 | ( 13 | Chain(5, lattice_spacing=6.1) 14 | .rydberg.detuning.uniform.linear(-10, 10, 3.0) 15 | .quera.mock(submission_error=True) 16 | .run_async(100) 17 | ) 18 | 19 | error_files = glob.glob("partial-batch-errors-*") 20 | batch_files = glob.glob("partial-batch-future-*") 21 | 22 | for error_file, batch_file in zip(error_files, batch_files): 23 | os.remove(error_file) 24 | os.remove(batch_file) 25 | 26 | assert len(error_files) == 1 27 | assert len(batch_files) == 1 28 | 29 | 30 | def test_batch_warn(): 31 | with pytest.warns(): 32 | ( 33 | Chain(5, lattice_spacing=6.1) 34 | .rydberg.detuning.uniform.linear(-10, 10, 3.0) 35 | .quera.mock(submission_error=True) 36 | .run_async(100, ignore_submission_error=True) 37 | ) 38 | 39 | error_files = glob.glob("partial-batch-errors-*") 40 | batch_files = glob.glob("partial-batch-future-*") 41 | 42 | for error_file, batch_file in zip(error_files, batch_files): 43 | os.remove(error_file) 44 | os.remove(batch_file) 45 | 46 | assert len(error_files) == 1 47 | assert len(batch_files) == 1 48 | -------------------------------------------------------------------------------- /tests/test_task2.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from bloqade.analog import start 4 | from bloqade.analog.serialize import dumps, loads 5 | from bloqade.analog.task.quera import QuEraTask 6 | from bloqade.analog.task.braket import BraketTask 7 | from bloqade.analog.submission.quera import QuEraBackend 8 | from bloqade.analog.submission.braket import BraketBackend 9 | 10 | 11 | def test_quera_task(): 12 | backend = QuEraBackend(api_hostname="a", qpu_id="b") 13 | task = QuEraTask( 14 | task_ir=None, task_id=None, metadata={}, backend=backend, parallel_decoder=None 15 | ) 16 | 17 | with pytest.raises(ValueError): 18 | task.fetch() 19 | 20 | assert task._result_exists() is False 21 | 22 | with pytest.raises(ValueError): 23 | task.pull() 24 | 25 | 26 | def test_braket_task(): 27 | backend = BraketBackend() 28 | task = BraketTask( 29 | task_ir=None, task_id=None, metadata={}, backend=backend, parallel_decoder=None 30 | ) 31 | 32 | with pytest.raises(ValueError): 33 | task.fetch() 34 | 35 | assert task._result_exists() is False 36 | 37 | with pytest.raises(ValueError): 38 | task.pull() 39 | 40 | 41 | def test_braket_batch(): 42 | program = ( 43 | start.add_position((0, 0)) 44 | .rydberg.rabi.amplitude.uniform.piecewise_linear( 45 | durations=[0.05, 1, 0.05], values=[0.0, 15.8, 15.8, 0.0] 46 | ) 47 | .detuning.uniform.piecewise_linear(durations=[1.1], values=[0.0, 0.0]) 48 | ) 49 | 50 | output = program.braket.aquila()._compile(10) 51 | 52 | output_str = dumps(output) 53 | assert isinstance(loads(output_str), type(output)) 54 | assert loads(output_str).tasks == output.tasks 55 | assert loads(output_str).name == output.name 56 | 57 | 58 | # 59 | # batch = RemoteBatch.from_json(...) 60 | # batch.fetch() # update results, 61 | # # this is non-blocking. 62 | # # It only pull results if the remote job complete 63 | 64 | # batch.pull() # this is blocking. it will hanging there waiting for job to complete. 65 | # batch.to_json(...) 66 | 67 | # # Get finished tasks (not nessasry sucessfully completed) 68 | # finished_batch = batch.get_finished_tasks() 69 | 70 | # # Get complete tasks (sucessfully completed) 71 | # completed_batch = batch.get_completed_tasks() 72 | 73 | # # Remove failed tasks: 74 | # new_batch = batch.remove_failed_tasks() 75 | -------------------------------------------------------------------------------- /tests/test_types.py: -------------------------------------------------------------------------------- 1 | # from bloqade.analog.julia.types import Vector, Int64 2 | 3 | # print(Vector[Int64]([1, 2, 3])) 4 | 5 | 6 | class Vector: 7 | def __init__(self, *params) -> None: 8 | self.expr = "Vector" 9 | self.params = params 10 | 11 | def __getitem__(self, *params) -> "Vector": 12 | return Vector(*params) 13 | 14 | 15 | Vector(int) 16 | -------------------------------------------------------------------------------- /tests/test_variable_scan.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from bloqade.analog import var, start 4 | from bloqade.analog.atom_arrangement import Chain 5 | from bloqade.analog.ir.control.waveform import to_waveform 6 | from bloqade.analog.compiler.analysis.common.scan_variables import ( 7 | ScanVariables, 8 | ScanVariableResults, 9 | ) 10 | 11 | 12 | def test_1(): 13 | def detuning_wf(t, delta, omega=15): 14 | return delta * np.sin(omega * t) 15 | 16 | circuit = ( 17 | Chain(15, lattice_spacing="lattice_spacing") 18 | .rydberg.detuning.scale("mask") 19 | .fn(detuning_wf, "t") 20 | .amplitude.uniform.constant(15, "t") 21 | .record("m") 22 | .phase.location(1, "a") 23 | .piecewise_linear([0.1, 0.5], [0, 1, 1]) 24 | .parse_circuit() 25 | ) 26 | 27 | scalar_vars = ["t", "omega", "delta", "m", "lattice_spacing", "a"] 28 | vector_vars = ["mask"] 29 | expected_result = ScanVariableResults( 30 | scalar_vars=scalar_vars, 31 | vector_vars=vector_vars, 32 | assigned_scalar_vars=set(), 33 | assigned_vector_vars=set(), 34 | ) 35 | assert expected_result == ScanVariables().scan(circuit) 36 | 37 | 38 | def test_2(): 39 | t = var("t") 40 | 41 | t_2 = var("T").max(t) 42 | t_1 = var("T").min(t) 43 | 44 | delta = var("delta") / (2 * np.pi) 45 | omega_max = var("omega_max") * 2 * np.pi 46 | 47 | @to_waveform(t_2) 48 | def detuning(t, u): 49 | return np.abs(t) * u 50 | 51 | circuit = ( 52 | start.add_position(("x", "y")) 53 | .add_position(("a", "b")) 54 | .rydberg.rabi.amplitude.scale("mask") 55 | .piecewise_linear([0.1, t - 0.2, 0.1], [0, omega_max, omega_max, 0]) 56 | .slice(t_1, t_2) 57 | .uniform.poly([1, 2, 3, 4], t_1) 58 | .detuning.uniform.constant(10, t_2) 59 | .uniform.linear(0, delta, t_1) 60 | .phase.uniform.apply(-(2 * detuning)) 61 | .parallelize(20.0) 62 | .parse_circuit() 63 | ) 64 | 65 | scalar_vars = ["t", "x", "y", "delta", "T", "omega_max", "a", "u", "b"] 66 | vector_vars = ["mask"] 67 | expected_result = ScanVariableResults( 68 | scalar_vars=scalar_vars, 69 | vector_vars=vector_vars, 70 | assigned_scalar_vars=set(), 71 | assigned_vector_vars=set(), 72 | ) 73 | assert expected_result == ScanVariables().scan(circuit) 74 | -------------------------------------------------------------------------------- /tests/test_version.py: -------------------------------------------------------------------------------- 1 | import bloqade.analog 2 | 3 | 4 | def test_version(): 5 | print(bloqade.analog.__version__) 6 | assert bloqade.analog.__version__ 7 | -------------------------------------------------------------------------------- /tests/test_z2_compile.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from bloqade.analog.atom_arrangement import Chain 4 | 5 | 6 | def test_z2_compile(): 7 | # Define relevant parameters for the lattice geometry and pulse schedule 8 | n_atoms = 11 9 | lattice_spacing = 6.1 10 | min_time_step = 0.05 11 | 12 | # Define Rabi amplitude and detuning values. 13 | # Note the addition of a "sweep_time" variable 14 | # for performing sweeps of time values. 15 | rabi_amplitude_values = [0.0, 15.8, 15.8, 0.0] 16 | rabi_detuning_values = [-16.33, -16.33, 16.33, 16.33] 17 | durations = [0.8, "sweep_time", 0.8] 18 | 19 | time_sweep_z2_prog = ( 20 | Chain(n_atoms, lattice_spacing=lattice_spacing) 21 | .rydberg.rabi.amplitude.uniform.piecewise_linear( 22 | durations, rabi_amplitude_values 23 | ) 24 | .detuning.uniform.piecewise_linear(durations, rabi_detuning_values) 25 | ) 26 | 27 | # Allow "sweep_time" to assume values from 0.05 to 2.4 microseconds for a total of 28 | # 20 possible values. 29 | # Starting at exactly 0.0 isn't feasible so we use the `min_time_step` defined 30 | # previously. 31 | time_sweep_z2_job = time_sweep_z2_prog.batch_assign( 32 | sweep_time=np.linspace(min_time_step, 2.4, 20) 33 | ) 34 | 35 | bloqade_emu_target = time_sweep_z2_job.bloqade.python() 36 | braket_emu_target = time_sweep_z2_job.braket.local_emulator() 37 | quera_aquila_target = time_sweep_z2_job.parallelize(24).quera.aquila() 38 | braket_aquila_target = time_sweep_z2_job.parallelize(24).braket.aquila() 39 | 40 | targets = [ 41 | bloqade_emu_target, 42 | braket_emu_target, 43 | quera_aquila_target, 44 | braket_aquila_target, 45 | ] 46 | 47 | for target in targets: 48 | target._compile(10) 49 | --------------------------------------------------------------------------------