diff --git a/.dockerignore b/.dockerignore index 9adbdb833..a8040d1f6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,3 +6,4 @@ openql.egg-info lab env docs +cmake-build-* diff --git a/.github/workflows/assets.yml b/.github/workflows/assets.yml index 2e47bd631..46bce6547 100644 --- a/.github/workflows/assets.yml +++ b/.github/workflows/assets.yml @@ -15,10 +15,10 @@ jobs: strategy: matrix: python: - - '3.5' - '3.6' - '3.7' - '3.8' + - '3.9' steps: - uses: actions/checkout@v2 with: @@ -28,13 +28,13 @@ jobs: python-version: ${{ matrix.python }} - name: Install dependencies run: | - brew install bison flex swig + brew install bison flex swig xquartz echo "/usr/local/opt/bison/bin" >> $GITHUB_PATH echo "/usr/local/opt/flex/bin" >> $GITHUB_PATH python -m pip install --upgrade pip setuptools wheel - name: Build wheel env: - NPROCS: 100 + NPROCS: 5 run: python setup.py bdist_wheel - name: Wheel path id: wheel @@ -68,10 +68,10 @@ jobs: manylinux: - 2014 cpython_version: - - 'cp35-cp35m' - 'cp36-cp36m' - 'cp37-cp37m' - 'cp38-cp38' + - 'cp39-cp39' steps: - uses: actions/checkout@v2 with: @@ -92,7 +92,7 @@ jobs: run: curl -L https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-Linux-x86_64.tar.gz | tar xz --strip-components=1 -C /usr - name: Build wheel env: - NPROCS: 100 + NPROCS: 5 run: | /opt/python/${{ matrix.cpython_version }}/bin/python setup.py bdist_wheel /opt/python/${{ matrix.cpython_version }}/bin/python -m auditwheel repair pybuild/dist/*.whl @@ -120,10 +120,10 @@ jobs: strategy: matrix: python: - - '3.5' - '3.6' - '3.7' - '3.8' + - '3.9' steps: - uses: actions/checkout@v2 with: @@ -145,7 +145,7 @@ jobs: choco install swig --version 4.0.1 - name: Build wheel env: - NPROCS: 100 + NPROCS: 5 run: python setup.py bdist_wheel - name: Wheel path id: wheel @@ -165,60 +165,69 @@ jobs: asset_name: ${{ steps.wheel.outputs.wheel }} asset_content_type: application/zip - conda: - name: Conda wheels - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - - macos-latest - - windows-2016 - python-version: - - '3.6' - - '3.7' - - '3.8' - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - name: Set up conda - uses: conda-incubator/setup-miniconda@v2 - with: - auto-update-conda: true - miniconda-version: "latest" - channel-priority: strict - channels: conda-forge - show-channel-urls: true - use-only-tar-bz2: true - - name: Install Windows dependencies - if: matrix.os == 'windows-2016' - run: choco install winflexbison3 --version 2.5.18.20190508 - - name: Install conda dependencies - run: conda install conda-build conda-verify -y - - name: Build & test - env: - NPROCS: 100 - run: conda build conda-recipe --python=${{ matrix.python-version }} - - name: Wheel path - id: wheel - run: | - python -c "import sys,os; print('##[set-output name=path;]' + os.path.abspath(sys.argv[1])); print('##[set-output name=wheel;]' + os.path.basename(sys.argv[1]));" "$(conda build conda-recipe --python=${{ matrix.python-version }} --output)" - python -c "import sys; print('##[set-output name=os;]' + ('linux' if sys.argv[1].startswith('ubuntu') else 'macos' if sys.argv[1].startswith('macos') else 'windows' if sys.argv[1].startswith('windows') else 0/0))" ${{ matrix.os }} - - uses: actions/upload-artifact@v2 - with: - name: conda-${{ steps.wheel.outputs.os }}-py${{ matrix.python-version }} - path: ${{ steps.wheel.outputs.path }} - - uses: actions/upload-release-asset@v1 - if: ${{ github.event_name == 'release' && github.event.action == 'created' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: ${{ steps.wheel.outputs.path }} - asset_name: ${{ steps.wheel.outputs.wheel }} - asset_content_type: application/x-bzip2 +# NOTE: disabled conda completely because it is INCREDIBLY slow on CI (multiple +# hours!) and occasionally just breaks the runners entirely after ~6 hours. It +# also doesn't appear like anyone is actually using Conda within the lab +# (anymore), as everything goes through pycQED and it is pip-only. +# +# For a release, if conda is really still necessary, it will just have to be +# done manually. +# +# conda: +# name: Conda wheels +# runs-on: ${{ matrix.os }} +# strategy: +# fail-fast: false +# matrix: +# os: +# - ubuntu-latest +# - macos-latest +# - windows-2016 +# python-version: +# - '3.6' +# - '3.7' +# - '3.8' +# - '3.9' +# steps: +# - uses: actions/checkout@v2 +# with: +# submodules: recursive +# - name: Set up conda +# uses: conda-incubator/setup-miniconda@v2 +# with: +# auto-update-conda: true +# miniconda-version: "latest" +# channel-priority: strict +# channels: conda-forge +# show-channel-urls: true +# use-only-tar-bz2: true +# - name: Install Windows dependencies +# if: matrix.os == 'windows-2016' +# run: choco install winflexbison3 --version 2.5.18.20190508 +# - name: Install conda dependencies +# run: conda install conda-build conda-verify -y +# - name: Build & test +# env: +# NPROCS: 5 +# run: conda build conda-recipe --python=${{ matrix.python-version }} +# - name: Wheel path +# id: wheel +# run: | +# python -c "import sys,os; print('##[set-output name=path;]' + os.path.abspath(sys.argv[1])); print('##[set-output name=wheel;]' + os.path.basename(sys.argv[1]));" "$(conda build conda-recipe --python=${{ matrix.python-version }} --output)" +# python -c "import sys; print('##[set-output name=os;]' + ('linux' if sys.argv[1].startswith('ubuntu') else 'macos' if sys.argv[1].startswith('macos') else 'windows' if sys.argv[1].startswith('windows') else 0/0))" ${{ matrix.os }} +# - uses: actions/upload-artifact@v2 +# with: +# name: conda-${{ steps.wheel.outputs.os }}-py${{ matrix.python-version }} +# path: ${{ steps.wheel.outputs.path }} +# - uses: actions/upload-release-asset@v1 +# if: ${{ github.event_name == 'release' && github.event.action == 'created' }} +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# with: +# upload_url: ${{ github.event.release.upload_url }} +# asset_path: ${{ steps.wheel.outputs.path }} +# asset_name: ${{ steps.wheel.outputs.wheel }} +# asset_content_type: application/x-bzip2 publish: name: Publish @@ -227,18 +236,18 @@ jobs: - macos - manylinux - windows - - conda + #- conda runs-on: ubuntu-latest steps: - name: Download artifacts uses: actions/download-artifact@v2 id: download - - name: Publish to Anaconda cloud - run: | - conda install -c anaconda anaconda-client -y - conda run anaconda login --username ${{ secrets.ANACONDA_USER }} --password ${{ secrets.ANACONDA_PASSWORD }} - conda run anaconda upload ${{ steps.download.outputs.download-path }}/conda-*/*.bz2 - conda run anaconda logout + #- name: Publish to Anaconda cloud + # run: | + # conda install -c anaconda anaconda-client -y + # conda run anaconda login --username ${{ secrets.ANACONDA_USER }} --password ${{ secrets.ANACONDA_PASSWORD }} + # conda run anaconda upload ${{ steps.download.outputs.download-path }}/conda-*/*.bz2 + # conda run anaconda logout - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@v1.3.1 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 256e3a805..f9db4de0a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,7 +41,7 @@ jobs: - name: Configure run: cmake . -DCMAKE_BUILD_TYPE=Debug -DOPENQL_BUILD_TESTS=ON -DBUILD_SHARED_LIBS=OFF - name: Build - run: cmake --build . --parallel + run: cmake --build . --parallel 5 - name: Test run: ctest -C Debug --output-on-failure @@ -57,7 +57,7 @@ jobs: mkdir cbuild cd cbuild && cmake ../examples/cpp-standalone-example - name: Build - run: make -C cbuild -j + run: make -C cbuild -j 5 - name: Test run: cd tests && ../cbuild/example @@ -72,10 +72,10 @@ jobs: - macos-latest - windows-latest python: - - '3.5' - '3.6' - '3.7' - '3.8' + - '3.9' steps: - uses: actions/checkout@v2 with: @@ -91,7 +91,7 @@ jobs: - name: Install dependencies if: matrix.os == 'macos-latest' run: | - brew install bison flex swig + brew install bison flex swig xquartz echo "/usr/local/opt/bison/bin" >> $GITHUB_PATH echo "/usr/local/opt/flex/bin" >> $GITHUB_PATH - uses: actions/cache@v2 @@ -117,49 +117,54 @@ jobs: run: echo "OPENQL_BUILD_TYPE=Debug" >> $GITHUB_ENV - name: Build env: - NPROCS: 100 + NPROCS: 5 run: python -m pip install --verbose . - name: Test run: python -m pytest - conda: - name: Conda - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - - macos-latest - #- windows-latest - #- windows-2016 - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - name: Set up conda - uses: conda-incubator/setup-miniconda@v2 - with: - auto-update-conda: true - miniconda-version: "latest" - channel-priority: strict - channels: conda-forge - show-channel-urls: true - use-only-tar-bz2: true - - name: Install Windows dependencies - if: matrix.os == 'windows-2016' || matrix.os == 'windows-latest' - run: choco install winflexbison3 --version 2.5.18.20190508 - - name: Install conda dependencies - run: conda install conda-build conda-verify -y - - name: Build & test - env: - NPROCS: 100 - run: conda build conda-recipe - - name: Install - # This doesn't seem to work on Windows in CI because everything appears - # to build in a non-default environment and conda is broken for that, - # see https://github.com/conda/conda/issues/7758 - # Note that conda build already does a test install in a fresh - # environment, so this is a bit redundant anyway. - if: matrix.os != 'windows-2016' && matrix.os != 'windows-latest' - run: conda install openql --use-local +# NOTE: disabled conda completely because it is INCREDIBLY slow on CI (multiple +# hours!) and occasionally just breaks the runners entirely after ~6 hours. It +# also doesn't appear like anyone is actually using Conda within the lab +# (anymore), as everything goes through pycQED and it is pip-only. +# +# conda: +# name: Conda +# runs-on: ${{ matrix.os }} +# strategy: +# fail-fast: false +# matrix: +# os: +# - ubuntu-latest +# - macos-latest +# #- windows-latest +# #- windows-2016 +# steps: +# - uses: actions/checkout@v2 +# with: +# submodules: recursive +# - name: Set up conda +# uses: conda-incubator/setup-miniconda@v2 +# with: +# auto-update-conda: true +# miniconda-version: "latest" +# channel-priority: strict +# channels: conda-forge +# show-channel-urls: true +# use-only-tar-bz2: true +# - name: Install Windows dependencies +# if: matrix.os == 'windows-2016' || matrix.os == 'windows-latest' +# run: choco install winflexbison3 --version 2.5.18.20190508 +# - name: Install conda dependencies +# run: conda install conda-build conda-verify -y +# - name: Build & test +# env: +# NPROCS: 5 +# run: conda build conda-recipe +# - name: Install +# # This doesn't seem to work on Windows in CI because everything appears +# # to build in a non-default environment and conda is broken for that, +# # see https://github.com/conda/conda/issues/7758 +# # Note that conda build already does a test install in a fresh +# # environment, so this is a bit redundant anyway. +# if: matrix.os != 'windows-2016' && matrix.os != 'windows-latest' +# run: conda install openql --use-local diff --git a/.gitignore b/.gitignore index e5965beb5..5c975b1bc 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,7 @@ swig/openql/openql.py swig/qutechopenql.egg-info # Test files -test_output +test_output* .ipynb_checkpoints tests/visualizer/visualizer_example_output/ diff --git a/CHANGELOG.md b/CHANGELOG.md index bd9a9f1b1..c41bde208 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ -# Change Log +# Changelog All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## [ next ] - [ TBD ] ### Added -- interface (C++ and Python) to compile cQASM 1.0 +- interface (C++ and Python) to compile cQASM 1.x - allow 'wait' and 'barrier' in JSON section 'gate_decomposition' - CC backend: - improved reporting on JSON semantic errors @@ -19,6 +19,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). - added compile option "--backend_cc_verbose" ### Changed +- pass management: instead of a hardcoded compilation strategy, the strategy can be adjusted and fine-tuned manually +- pass options: instead of doing everything with global options, global options were replaced with pass options as much as possible +- major internal refactoring and restructuring to facilitate the above two things - CC backend: - renamed JSON field "signal_ref" to "ref_signal" - renamed JSON field "ref_signals_type" to "signal_type" @@ -27,8 +30,17 @@ This project adheres to [Semantic Versioning](http://semver.org/). - JSON field "instruction/type" no longer used by backend, use "instruction/cc/readout_mode" to flag measurement instructions - allow specification of 2 triggers in JSON field "control_modes/*/trigger_bits" to support dual-QWG - changed label in generated code from "mainLoop" to "__mainLoop". Do not start kernel names with "__" (this should be specified by the API) + - removed initial 1 cycle (20 ns) delay at start of kernels (resulting from bundle start_cycle starting at 1) + - correctly handle kernel names containing "_" in conjunction with looping (formerly duplicate labels could arise) + - added "seq_out 0,1" to program start to allow tracing of actual program start ### Removed +- CC-light code generation, as the CC-light is being phased out in the lab, and its many passes were obstacles for pass management and refactoring +- rotation optimization based on matrices; matrices in general were removed entirely because no one was using it +- the commute variation pass, as it has been superseded by in-place commutations within the scheduler +- the toffoli decomposition pass, as it wasn't really used; to decompose a toffoli gate, use generic platform-driven decomposition instead +- the defunct fidelity estimation logic from metrics.cc; this may be added again later, but requires lots of cleanup and isn't currently in use +- quantumsim and qsoverlay output; apparently this was no longer being used, and it was quite intertwined with the CC-light backend ### Fixed diff --git a/CMakeLists.txt b/CMakeLists.txt index d4a676438..919569e17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -176,7 +176,34 @@ set(CMAKE_CXX_EXTENSIONS OFF) # Windows weirdness: need a .lib file to link against a DLL at compile-time # (I think), but only the .dll is generated when there are no exported symbols. # This sort of fixes that (https://stackoverflow.com/questions/1941443) -SET(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + + +#=============================================================================# +# Compile code generators # +#=============================================================================# + +# Simple program that generates resource include files, to include the contents +# of a file as a constant inside the OpenQL library. +add_executable(resource src/resource/main.cpp) +function(create_resource fname) + set(infile "${CMAKE_CURRENT_SOURCE_DIR}/${fname}") + get_filename_component(outdir "${CMAKE_CURRENT_BINARY_DIR}/${fname}" DIRECTORY) + get_filename_component(outname "${CMAKE_CURRENT_BINARY_DIR}/${fname}" NAME_WE) + set(outfile "${outdir}/${outname}.inc") + add_custom_command( + OUTPUT "${outfile}" + COMMAND "${CMAKE_COMMAND}" -E make_directory "${outdir}" + COMMAND resource "${infile}" "${outdir}" "${outname}" + DEPENDS "${infile}" resource + ) + string(REGEX REPLACE "[\\./\\]" "_" target_name "${fname}") + add_custom_target( + ${target_name} + DEPENDS "${outfile}" + ) + add_dependencies(ql ${target_name}) +endfunction() #=============================================================================# @@ -187,66 +214,111 @@ SET(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) # or as a static library based on BUILD_SHARED_LIBS; add_library switches # automatically. add_library(ql - "${CMAKE_CURRENT_SOURCE_DIR}/src/platform.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/program.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/compiler.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/decompose_toffoli.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/buffer_insertion.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/latency_compensation.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/write_sweep_points.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/optimizer.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/clifford.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/passes/passmanager.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/passes/passes.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/visualizer_cimg.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/visualizer_common.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/visualizer_circuit.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/visualizer_interaction.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/visualizer_mapping.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/report.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/utils/exception.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/utils/logger.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/utils/str.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/utils/num.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/utils/filesystem.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/utils/json.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/arch/cc/backend_cc.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/arch/cc/codegen_cc.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/arch/cc/datapath_cc.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/arch/cc/settings_cc.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/arch/cc/vcd_cc.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/utils/vcd.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/cqasm/cqasm_reader.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/unitary.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/mapper.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/circuit.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/classical.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/eqasm_compiler.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/gate.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/hardware_configuration.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/interactionMatrix.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/ir.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/kernel.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/metrics.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/openql_i.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/options.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/arch/cc_light/cc_light_eqasm_compiler.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/qsoverlay.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/resource_manager.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/scheduler.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/commute_variation.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/arch/cc_light/cc_light_eqasm.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/arch/cc_light/cc_light_resource_manager.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/utils/num.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/utils/str.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/utils/rangemap.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/utils/exception.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/utils/logger.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/utils/filesystem.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/utils/json.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/utils/vcd.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/utils/options.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/com/options.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/com/unitary.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/com/qubit_mapping.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/com/metrics.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/com/interaction_matrix.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/plat/platform.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/plat/topology.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/ir/gate.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/ir/classical.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/ir/bundle.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/ir/kernel.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/ir/program.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/rmgr/resource_types/base.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/rmgr/types.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/rmgr/factory.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/rmgr/state.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/rmgr/manager.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/resource/qubit.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/resource/instrument.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/resource/inter_core_channel.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pmgr/pass_types/base.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pmgr/pass_types/specializations.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pmgr/condition.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pmgr/group.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pmgr/factory.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pmgr/manager.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/ana/statistics/annotations.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/ana/statistics/report.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/ana/statistics/clean.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/ana/visualize/detail/types.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/ana/visualize/detail/common.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/ana/visualize/detail/image.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/ana/visualize/detail/circuit.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/ana/visualize/detail/interaction.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/ana/visualize/detail/mapping.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/ana/visualize/circuit.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/ana/visualize/interaction.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/ana/visualize/mapping.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/io/cqasm/detail/cqasm_reader.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/io/cqasm/read.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/io/cqasm/report.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/io/sweep_points/write.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/opt/clifford/detail/clifford.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/opt/clifford/optimize.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/sch/schedule/detail/scheduler.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/sch/schedule/schedule.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/map/qubits/place_mip/detail/algorithm.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/map/qubits/place_mip/place_mip.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/map/qubits/map/detail/options.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/map/qubits/map/detail/free_cycle.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/map/qubits/map/detail/past.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/map/qubits/map/detail/alter.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/map/qubits/map/detail/future.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/map/qubits/map/detail/mapper.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/pass/map/qubits/map/map.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/arch/info_base.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/arch/architecture.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/arch/factory.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/arch/cc/info.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/arch/cc/pass/gen/vq1asm/detail/backend.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/arch/cc/pass/gen/vq1asm/detail/codegen.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/arch/cc/pass/gen/vq1asm/detail/datapath.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/arch/cc/pass/gen/vq1asm/detail/settings.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/arch/cc/pass/gen/vq1asm/detail/vcd.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/arch/cc/pass/gen/vq1asm/vq1asm.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/arch/cc_light/info.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/arch/none/info.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/api/misc.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/api/pass.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/api/compiler.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/api/platform.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/api/creg.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/api/operation.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/api/unitary.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/api/kernel.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/api/program.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/api/cqasm_reader.cc" ) +# Specify resources. +create_resource("src/ql/arch/cc/resources/hwconf_default.json") +create_resource("src/ql/arch/cc_light/resources/hwconf_default.json") +create_resource("src/ql/arch/cc_light/resources/hwconf_s5.json") +create_resource("src/ql/arch/cc_light/resources/hwconf_s7.json") +create_resource("src/ql/arch/cc_light/resources/hwconf_s17.json") +create_resource("src/ql/arch/none/resources/hwconf_default.json") + # Generate a header file with configuration options that cannot be compiled # (entirely) into the shared/static library due to use of templates. set(QL_CHECKED_VEC ${OPENQL_CHECKED_VEC}) set(QL_CHECKED_LIST ${OPENQL_CHECKED_LIST}) set(QL_CHECKED_MAP ${OPENQL_CHECKED_MAP}) +set(QL_SHARED_LIB ${BUILD_SHARED_LIBS}) configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/src/ql_config.h.template" - "${CMAKE_CURRENT_BINARY_DIR}/include/ql_config.h" + "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/config.h.template" + "${CMAKE_CURRENT_BINARY_DIR}/include/ql/config.h" ) # This definition is used to define OPENQL_DECLSPEC for __declspec. More info: @@ -261,7 +333,9 @@ target_compile_definitions(ql PUBLIC OPT_DECOMPOSE_WAIT_BARRIER) # and they'r all in the source directory. Note the / at the end of the path; # this is necessary for the header files to be installed in the right location. target_include_directories(ql - PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src/" + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/" + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/src/" + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/" PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/include/" ) @@ -369,19 +443,28 @@ add_subdirectory(deps/libqasm) target_link_libraries(ql PUBLIC cqasm) -# X11 ------------------------------------------------------------------------- +# X11/CImg --------------------------------------------------------------------- # Only enable the visualizer if building on Windows or the X11 library is found when building on Linux or Mac. if(WIN32) - target_compile_definitions(ql PRIVATE WITH_VISUALIZER) + set(QL_VISUALIZER yes) else() find_package(X11) if(X11_FOUND) - target_link_libraries(ql PUBLIC X11) - target_compile_definitions(ql PRIVATE WITH_VISUALIZER) + set(QL_VISUALIZER yes) + message("X11 libraries: ${X11_LIBRARIES}") + target_link_libraries(ql PUBLIC ${X11_LIBRARIES}) + message("X11 include path: ${X11_INCLUDE_DIR}") + target_include_directories(ql PRIVATE "${X11_INCLUDE_DIR}") + else() + set(QL_VISUALIZER no) endif() endif() +if(QL_VISUALIZER) + target_compile_definitions(ql PRIVATE WITH_VISUALIZER) + target_include_directories(ql PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/deps/cimg/include/") +endif() # backward-cpp ---------------------------------------------------------------- @@ -403,7 +486,7 @@ set_property(TARGET ql APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${BACKWARD_LIBRA if(OPENQL_BUILD_TESTS) enable_testing() - # Convenience function to add a test. + # Convenience function to add an integration test. function(add_openql_test name source workdir) add_executable("${name}" "${CMAKE_CURRENT_SOURCE_DIR}/${source}") target_link_libraries("${name}" ql) @@ -414,10 +497,35 @@ if(OPENQL_BUILD_TESTS) ) endfunction() - # Include the directories containing tests. + # Include the directories containing integration tests. add_subdirectory(tests) add_subdirectory(examples) + # Convenience function to add a unit test. + function(add_openql_unit_test source) + string(REPLACE "/" "_" name ${source}) + string(REPLACE "_tests_" "_" name ${name}) + string(REPLACE ".cc" "" name ${name}) + set(name test_${name}) + add_executable("${name}" "${CMAKE_CURRENT_SOURCE_DIR}/src/ql/${source}") + target_link_libraries("${name}" ql) + add_test( + NAME "${name}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/tests" + COMMAND "${name}" + ) + endfunction() + + # Register unit tests. + file( + GLOB_RECURSE unit_tests + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/src/ql + ${CMAKE_CURRENT_SOURCE_DIR}/src/ql/*/tests/*.cc + ) + foreach(unit_test ${unit_tests}) + add_openql_unit_test(${unit_test}) + endforeach() + endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ce6dd1d3..750405bd5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,5 @@ -OpenQL C++ Coding Conventions -============================= +C++ coding conventions +====================== In order to maintain the code homogeneous and consistent, all contibutors are invited to follow this coding convention. @@ -7,11 +7,16 @@ invited to follow this coding convention. NOTE: at the time of writing, not all of OpenQL has been converted to this code style completely yet. +In general, consistency is considered to be more important than any of these +rules. If a significant piece of code violates a rule consistently, either +change the entire piece of code to conform, or make your changes in the same +style as the original code. + ## File and directory organization C++ header files should be named `.h`. Header files private to OpenQL go in -the `src` directory, header files that a user needs to access as well (the vast -majority) go in `include`. +the `src` directory, preferably in a `detail` subdirectory. Header files that +a user needs to access as well (the vast majority) go in `include`. All definitions must go into source files; header files should only declare things. Therefore, almost all header files need a corresponding source file. @@ -20,6 +25,21 @@ directory as the corresponding header file. All filenames are lowercase, separated by `_` when composed of multiple words. +The filename and directory structure (loosely) follows the namespace structure +of OpenQL and vice versa. When a namespace is only comprised of only a single +file, the filename will be the name of the namespace, and the directory it's +placed in is the name of the parent namespace (and so on). When a namespace +consists of multiple files, the entire namespace path is represented as +directories, and the contained files should be named after the (main) class +(in lower_case) or functionality that they provide. + +Subdirectories in `src/ql` may include a `tests` directory. Any `*.cc` file in +such a directory is automatically interpreted as a unit test file by the build +system. Note however that when you add or remove a test, you must manually +regenerate the CMake project. A unit test simply consists of a C++ program with +a `main()`, that must return zero on success or nonzero on failure. Unit tests +are run using the *toplevel* `tests` directory as the working directory. + ## Naming conventions To be consistent with especially Python (since we share an API between it and @@ -37,23 +57,314 @@ largest amount of conflicting styles. Therefore, it makes more sense to just stick to Python. The only annoying conflict is that the standard library types are lowercase. -Since OpenQL is a library, it's important not to pollute the global namespace -with stuff. Imagine, for instance, if OpenQL would define the type `Bit` to -represent a classical bit in the global namespace, and someone using the library -from C++ also includes a bit manipulation library that happens to also define -`Bit`; this would be a naming conflict that's impossible to resolve for the -user. Therefore, everything defined by OpenQL should be in the `ql` namespace, -and all preprocessor macros (which can't be namespaced) should start with `QL_`. +When naming things, try to be explicit and precise, but only within the context +of the current namespace. For example, if you have a class representing a red +apple, and you place it in namespace `apple`, call the class `Red` instead of +`RedApple`. This saves you typing within the `apple` namespace, doesn't cost +someone outside your namespace much extra typing for occasional apple usage +(`apple::Red` isn't much longer than `RedApple` after all), and someone using +lots of apples within some scope can just do `using namespace apple` locally +to save more typing. + +When you use polymorphism for a group of objects, the base class is typically +called `Base`. Continuing with the apple example, the `apple` namespace may +have a class `Base` declared in `base.h`, `Red : public Base` in `red.h`, and +`Green : public Base` in `green.h`. Using `Base` instead of `Apple` avoids +annoying constructions like `apple::Apple`. + +Avoid abbreviations of "words" within a name, except maybe for very local +variables like loop iterators. A little typing overhead while writing the code +saves a lot of overhead when someone else later has to read and understand your +code. However, typedefs (using the `using` keyword, C-style `typedef`s are +comparatively hard to read) are encouraged, to remove parts of names or +namespace paths that are obvious within context. + +## Namespaces + +Since OpenQL may be used as a C++ library, it's common courtesy not to pollute +the global namespace with stuff. Imagine, for instance, if OpenQL would define +the type `Bit` to represent a classical bit in the global namespace, and +someone using the library from C++ also includes a bit manipulation library +that happens to also define `Bit`; this would be a naming conflict that's +impossible to resolve for the user. Therefore, everything defined by OpenQL +should be within the `ql` namespace, and all preprocessor macros (which can't +be namespaced) should start with `QL_`. + +Furthermore, nothing except the main C++-style `openql` header in `include` +should define anything directly in `ql`. This namespace is reserved for the +API layer that the user is expected to access, and must thus remain as +consistent from version to version as possible. The main header currently +does a `using namespace api` to pull the contents of `ql::api` into `ql`, +but if internal changes are made to OpenQL again later, this translation may +become more complex. + +OpenQL has a well-defined namespace tree used to structure its components and +keep things disjoint. Roughly speaking, the namespaces serve as library for +dependent namespaces, although some dependency cycles still remain at this +abstraction level. The `ql` subnamespaces, roughly ordered by dependencies, +are: + + - `utils`: extensions to (standard) libraries, wrappers, etc. not specific to + OpenQL or compilers in any way. + + - `plat`: the platform tree. Contains most of the data structures needed to + describe a quantum platform. This should be light on actual functionality, + such that it might be generated by tree-gen at some point. + + - `ir`: intermediate representation. Contains most of the data structures + needed to represent a quantum program as it's being compiled. This should be + light on actual functionality, such that it might be generated by tree-gen + at some point. + + - `com`: common operations. This contains all OpenQL/compiler-specific code + operating on the platform and IR trees that is reusable for various passes. + For example DFG or CFG construction might live here. + + - `pmgr`: pass management. This contains all the logic that manages the + compilation process. + + - `pmgr::pass_types`: defines the abstract base classes for the compiler + passes. + + - `pass`: pass implementations. This contains a subtree of namespaces that + eventually define the architecture-agnostic compiler passes of OpenQL. This + tree should correspond exactly to the namespace paths in the path types as + the pass factory knows them. The first namespace level is standardized as + follows: + + - `pre`: passes that perform pre-processing of the platform tree. + (NOTE: at the time of writing these don't exist yet, and pass management + isn't quite ready for it yet due to issues with backward compatibility of + the API) + + - `io`: I/O passes that load the IR from a file or save (parts of the IR) + to a file without significant transformation. Mostly cQASM, but would + also include conversion of the IR to different formats (OpenQASM? + QuantumSim?). + + - `ana`: passes that leave the IR and platform as is (save for + annotations), and only analyze the content of the platform/IR. For + example statistics reporting, visualization, error checking, consistency + checking for debugging, etc. + + - `dec`: passes that decompose code (instructions, gates, etc) to more + primitive components or otherwise lower algorithm abstraction level. + Should includes of course gate decomposition passes (once that + functionality is pulled out of Kernel), but something like reduction of + structured control flow to only labels and goto's would also go here. + + - `map` passes that map qubits or classical storage elements to something + closer to hardware. Right now that would be "the mapper," but would also + include a hypothetical pass that automatically applies some error + correction code to the user-specified algorithm, mapping variables to + classical registers and memory, reduction to single-static-assignment + form, etc. + + - `opt`: optimization passes, i.e. passes that do not lower IR abstraction + level, but instead transmute the IR to a "better" equivalent + representation. + + - `sch` passes that shuffle instructions around and add timing information. + + - `gen` passes that internally convert the common IR into their own IR to + reduce it further, to eventually generate architecture-specific assembly + or machine code. These should only ever be part of `arch`. + + - `misc`: any passes that don't fit in the above categories, for example a + Python pass wrapper if we ever make one, which could logically be any + kind of pass. + + - `dnu`: "Do Not Use:" code exists only for compatibility purposes, only + works in very particular cases, is generally unfinished, or is so old + that we're not sure if it even works anymore. This receives special + treatment in the pass factory: passes prefixed with `dnu` must be + explicitly enabled in the compiler configuration file. + + - `io`..`misc`: the other categories reappear as namespaces within + `dnu`. + + - `rmgr`: resource management. This contains the logic that functionally + describes the scheduling resources of a platform, used to define for + example instrument constraints. + + - `pmgr::resource_types`: defines the abstract base classes for the + scheduling resources. + + - `resource`: defines the architecture-agnostic scheduling resources built + into OpenQL. + + - `resource::dnu`: similar to `pass::dnu`, defunct or work-in-progress + resources should be placed in here. + + - `arch::`: the place for all architecture-specific stuff. Specifically, + this may include `com`, `pass`, and `resource` sub-namespaces that provide + architecture-specific additions or overrides for the respective `ql` + subnamespaces. + + - `api`: this namespace contains all user-facing API wrappers. + +Private functionality for a logical piece of code within OpenQL (usually a +pass) should go into a subnamespace named `detail`. This namespace should only +have files in `src`, and as such, the non-`detail` parts of the code should +only refer to it from `.cc` files (so NOT from the `include` header files). +This enforces sectioning off local implementation details from the rest of the +OpenQL code, preventing excessive compilation time by keeping the (public) +header files as lean as possible. + +Within a local namespace, use whatever you want (`using namespace` etc) as you +see fit, although more selective inclusions and abbreviations using +`namespace x = ...` and `using T = ...` is preferred. + +As for code style, please stick to the following. + +```cpp +// Namespaces do not receive indentation, because then everything would be +// indented. But the closing brace must be clearly marked as such (using the +// depicted style) to compensate. +namespace ql { + +... // namespace contents are not indented... + +} // namespace ql + +// "using namespace" is allowed only in .cc files, private header files, or +// in local scopes. If you're working deep inside the namespace tree, it is +// sometimes useful to pull another namespace into it, which is fine, but +// it's preferred to include things selectively with `using x = y` or to +// abbreviate namespaces if needed using `namespace x = a::b::c`. +``` -## Utils types and functions +## Header files and `#include` syntax + +Some general rules for `#include` directive consistency. + + - Always use `#include "ql/..."` to refer to public header files of OpenQL. + That is, use the `""` syntax rather than the `<>` syntax, and use the + full path from `ql` onward. + - Usage of relative paths is allowed only to refer to private/`detail` header + files. + - The first directive in a `.cc` file must be inclusion of its respective + header file. The next line should be blank. Any header files needed for the + `.cc` file that are not needed for the corresponding `.h` file follow after + this blank line. + - Try to keep `#include` directives ordered as follows: + - system header files (standard library, etc); + - OpenQL's dependencies from the `deps` folder (lemon, libqasm, etc); and + - include OpenQL's own headers in the same order that the namespaces are + listed in the previous section. + +Sometimes you may end up with an include dependency loop. For example, the +platform structure includes a reference to an architecture structure, but the +architecture-specific logic certainly makes use of the platform structure. The +typical symptom is an error message that some type has not been declared yet, +with a long include chain at the top. This can usually be bypassed by making +a `declarations.h` file for the namespace for which forward references are +needed. This header file should make forward declarations for the types defined +in the namespace (just `class X;` etc.) and declare any pointer/reference +typedefs needed for them. Note that the files that actually define the classes +should always include this file as well, so the compiler will actually warn you +when there are inconsistencies! + +## "Runtime" documentation and dump() functions + +In order to aid the synchronization of the user-facing documentation and the +internal codebase, and to make it easier for users to access the documentation, +a good portion of the documentation is placed in the OpenQL codebase itself as +strings. These strings can then be queried via the API by the user directly, or +by the ReadTheDocs/Sphinx conf.py script to generate online documentation pages +from them. Consistency is key for making this all work smoothly: +inconsistensies are not only ugly when reading the documentation (say for +instance that one person uses regular English interpunction while the other +uses a more comment-like lack of interpunction and capitalization), but may +also easily break the generators. After all, the output of these documentation +functions is fed through some Python magic to Sphinx' reStructuredText parser. + +In order to make the documentation readable from within Python as well, +indentation is used for sectioning, rather than RST section headers. This means +that each documentation printing function needs to be aware of the current +indentation level; simply returning a string is not enough. To solve this and +a few other problems all functions that print documentation-like information to +the user must have the following signature: + +`dump_*(std::ostream &os = std::cout, const utils::Str &line_prefix = "")` + +The following contract must be adhered to: + + - at least one line must be written to `os`; + - all lines must start with `line_prefix` and end with `"\n"`; + - the `` stream state of `os` must not be mangled; + - the stream should be flushed at the end (either via `std::endl` or an + explicit call to `flush()`). + +To open a subsection in the output stream (for a recursive call to a dump +function, for instance): + + - there must be at least one blank line (or the start of the input) before + the section header; + - the section header must have the same indentation level as the parent + (so whatever is in `line_prefix`); + - the header must be exactly of the form `* *\n`; and + - the body of the section must be indented by two additional spaces. + +Do NOT use RST or markdown headers in the section bodies; use only indented +sections as described above. Violating this rule or any of the other rules +above will likely break the converter for the RTD pages. + +The text inside the documentation strings is interpreted as *markdown*, +converted to reStructuredText for Sphinx/ReadTheDocs via `m2r2`. Markdown is +used rather than RST because it's way more pleasing to read raw, for example +when dumped from within an interactive Python interpreter. `m2r2` passes most +RST tags straight through however, so you still need to be careful not to +accidentally put something that looks like RST in a docstring. + +In addition `m2r2`'s logic, the following conversions are made: + + - section headers are detected and converted to appropriate RST header + levels; + - section bodies are un-indented; and + - `NOTE: ` or `WARNING: ` at the start of a markdown paragraph (blank line + before and after) is converted to an RST `.. note::`/`.. warning::` block. + The first letter of the sentence (fragment) following the header is + automatically capitalized, so it can be lowercase in the raw output while + still being appropriately capitalized on ReadTheDocs. + +To aid writing these long documentation strings inside C++, two functions are +available in `utils/str.h`: + + - `dump_str`: useful for writing long dumpable strings by means of + manually-wrapped raw strings. For example: + + ```c++ + utils::dump_str(os, line_prefix, R"( + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum at + lacus porttitor mi consectetur ultrices. Aenean malesuada tristique nisl, + eu ultrices enim sodales eu. Cras sed nulla enim. Nunc pretium pretium + tortor, ut cursus nulla commodo sit amet. + )"); + ``` + + `dump_str` ensures that the C++ indentation level is stripped from each line + of the raw string, and that line_prefix is inserted before each line. + + - `wrap_str`: similar to the above, but assumes that the input is not wrapped + yet. This is more useful for shorter pieces of text where you don't want to + be bothered by wrapping manually, or generated text where doing so + consistently would otherwise be impossible. However, while the wrapper tries + to be smart about maintaining indentation for multiple paragraphs in its + input, it is not infallible. Hence, for long pieces of relatively + complicated documentation code (that includes code blocks etc.) `dump_str` + is more helpful. + +## Utility types and functions The `ql::utils` namespace provides a bunch of typedefs and wrappers for C++ standard library stuff, modified to improve safety, reduce undefined behavior, simplify stuff where OpenQL doesn't need the full expressive power of the standard library, improve consistency in terms of naming conventions, or just -to reduce typing. In cc files there is usually a `using namespace utils` to -reduce typing further (but don't do this in header files!). You should use -types and functions from here as much as possible. Here are the important ones. +to reduce typing. In cc files there is sometimes a `using namespace utils` to +reduce typing further, but never do this in header files! You should use +types and functions from here as much as possible. Here are some important +ones. - From `utils/num.h`: - `Bool` for booleans; @@ -319,24 +630,6 @@ do { for (auto &el : sequence) { el = 0; } ``` -## Namespaces - -```cpp -// Namespaces do not receive indentation, because then everything would be -// indented. But the closing brace must be clearly marked as such (using the -// depicted style) to compensate. -namespace ql { - -... // namespace contents are not indented... - -} // namespace ql - -// "using namespace" is allowed only in .cc files, NEVER in header files, and -// CERTAINLY NEVER in the global namespace; a single "using namespace" in any -// header completely destroys the idea behind namespaces (that is, preventing -// name conflicts or long names with many redundant parts in them). -``` - ## Enumerations ```cpp @@ -392,13 +685,20 @@ public: // less magical, and honestly, I doubt you'll notice the performance penalty of // the lack of inlining in practice (and there's always LTO nowadays). -// AVOID "virgin constructors": the constructor of a class must also initialize -// that class. C++ utilizes the RAII principle (resource acquisition is -// initialization): if you have a class instance, you may assume it has been -// initialized. These virgin constructors combined with an init method nuke -// this principle and result in the worst of both worlds; you still have a -// complaining compiler, but don't have any of the benefits. They simply should -// not exist. +// Avoid "virgin constructors": the constructor of a class should also +// initialize that class. C++ utilizes the RAII principle (resource acquisition +// is initialization): if you have a class instance, you should be able to +// assume it has already been initialized and that you can use it. Virgin +// constructors combined with an init method nuke this principle, making it +// relatively easy to accidentally get undefined behavior by using an object +// before initializing it. If you're faced with spaghetti in the member +// initialization part of a constructor (the part between the : and the +// function body) because members don't have a virgin constructor, wrap them in +// utils::Opt or utils::Ptr and call emplace() on them instead of what would +// have been init(); this lets you do everything that you would be able to do +// with the virgin constructor method, but without breaking RAII and with +// runtime detection of use-before-init (since dereferencing an empty Opt or +// Ptr will throw an exception). // Constructions where you think you need this should be avoided. If you really // need it anyway, use ql::Opt instead of T directly. You can then use diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b17765cc6..301edf617 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -88,3 +88,5 @@ Note: please fill your contributions in this file - [Jeroen van Straten](https://github.com/jvanstraten) - tutorial on DQCsim + OpenQL interoperation - doxygen documentation + - improved pass management + - extensive cleanup; basically a rewrite of everything at this point diff --git a/README.md b/README.md index d18bfbbfa..65bba5f12 100644 --- a/README.md +++ b/README.md @@ -6,44 +6,17 @@ [![PyPI](https://badgen.net/pypi/v/qutechopenql)](https://pypi.org/project/qutechopenql/) [![Anaconda](https://anaconda.org/qe-lab/openql/badges/version.svg)](https://anaconda.org/qe-lab/openql/) -OpenQL is a framework for high-level quantum programming in C++/Python. The framework provides a compiler for compiling and optimizing quantum code. The compiler produces the intermediate quantum assembly language and the compiled micro-code for various target platforms. While the microcode is platform-specific, the quantum assembly code (qasm) is hardware-agnostic and can be simulated on the QX simulator. For detailed documentation see [here](https://openql.readthedocs.io). - -## Supported Patforms - -* Linux -* Windows -* OSX - -## Installation - -OpenQL can be installed in a number of ways, See [Installation](https://openql.readthedocs.io/en/latest/installation.html) for details. - -## Usage - -Example python tests and programs can be found in the 'tests' and 'examples' directories. These can be executed as 'python tests/simplePyTest.py'. - -Example C++ tests and programs can be found in 'tests' and 'examples' -directories. Executables for these will be generated in 'cbuild/tests' and 'cbuild/examples' directory. An executable can be executed as: './example' - - - -# Changelog - -Please read [CHANGELOG.md](CHANGELOG.md) for more details. - - -# Contributing - -Please read [CONTRIBUTING.md](CONTRIBUTING.md) for more details. -Typically you will be need to perform the following steps to contribute: - -1. `Fork` this repository -1. Create a `branch` -1. `Commit` your changes -1. `Push` your `commits` to the `branch` -1. Submit a `pull request` - -You can find more information about Pull Requests [here](https://help.github.com/categories/collaborating-on-projects-using-pull-requests/) - -# Contributors -Please see [CONTRIBUTORS.md](CONTRIBUTORS.md). +OpenQL is a framework for high-level quantum programming in C++/Python. The +framework provides a compiler for compiling and optimizing quantum code. The +compiler produces the intermediate quantum assembly language and the compiled +micro-code for various target platforms. While the microcode is +platform-specific, the quantum assembly code +(in [cQASM](https://libqasm.readthedocs.io/) format) is hardware-agnostic and +can be simulated on the QX simulator. + +OpenQL's source code is released under the Apache 2.0 license. Some optional +components have GPL-licensed dependencies (GLPK); therefore, binary builds with +these options enabled are governed by the conditions of the GPL. + +For detailed user and contributor documentation, visit the +[the ReadTheDocs](https://openql.readthedocs.io) page! diff --git a/RELEASE.md b/RELEASE.md index a55027fd4..98eb16505 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -10,9 +10,10 @@ secrets are configured correctly. is to be released. For example, `release-0.0.1`, but the suffix doesn't matter. - - Change the version in `src/version.h` (this is the only place where the - version is hardcoded) and change any other files where applicable - (changelog, etc), then commit and make a PR for it. + - Change the version in `include/ql/version.h` (this is the only functional + place where the version is hardcoded) and change any other files where + applicable (changelog, etc) and update `CHANGELOG.md` accordingly, then + commit and make a PR for it. - CI will now run not only the test workflow, but also the assets workflow. The assets workflow builds the wheels and conda packages, but publishes them @@ -21,6 +22,10 @@ secrets are configured correctly. CI might not catch. To find them, go to Actions -> Assets workflow -> click the run for your branch -> Artifacts. + - Always delete the artifacts of the assets runs when you're done with them + or don't need them! OpenQL's binaries are quite big, so if you don't do + this, GitHub will soon start rejecting new artifacts due to storage quota. + - If the test or assets workflows fail, fix it before merging the PR (of course). @@ -30,12 +35,24 @@ secrets are configured correctly. - If needed, also merge to `master`. - Draft a new release through the GitHub interface. Set the "tag version" - to the same version you put in `src/version.h`, the title to - "Release `version`: `name`", and write a short changelog in the body. - For releases that don't go to master, check the "prerelease" box, so it - won't show up as the latest release. + to the same version you put in `include/ql/version.h`, the title to + "Release `version`: `name`", and write release notes in the body. The + release notes should include at least: + + - a summary of what has changed, what is new, and what is incompatible + with the previous version; + - if there are incompatibilities, what the user can do for mitigation; + and + - what has been deprecated and may be removed in a later version. + + In principle, these things apply only to the public API. For releases that + don't go to master, check the "prerelease" box, so it won't show up as the + latest release. - CI will run the assets workflow again, now with the new version string baked into the wheels and packages. When done, these wheels and packages are automatically added to the GitHub release, and if the secrets/API keys for PyPI and conda are correct, CI will publish them there. + + - Remove the temporary `release-*` branch. Users can find particular releases + via the tag that GitHub automatically added when you drafted the release. diff --git a/src/CImg.h b/deps/cimg/include/CImg.h similarity index 100% rename from src/CImg.h rename to deps/cimg/include/CImg.h diff --git a/deps/libqasm b/deps/libqasm index e80f9b8fd..bdc82d79f 160000 --- a/deps/libqasm +++ b/deps/libqasm @@ -1 +1 @@ -Subproject commit e80f9b8fd85fe19a27331ee73565c7bd16268cc7 +Subproject commit bdc82d79ff3b7380a76a041ecb2e6eed844d788a diff --git a/docs/.gitignore b/docs/.gitignore index 2aa1c15ad..efd531359 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,2 @@ /doxygen/ +/gen/ diff --git a/docs/Doxyfile b/docs/Doxyfile index f16dd1089..88bb910ad 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -792,6 +792,7 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = ../src \ + ../include \ main-page.dox # This tag can be used to specify the character encoding of the source files diff --git a/docs/api/CReg.rst b/docs/api/CReg.rst deleted file mode 100644 index 7c41e9e00..000000000 --- a/docs/api/CReg.rst +++ /dev/null @@ -1,27 +0,0 @@ -CReg -==== - -.. currentmodule:: openql.openql - -.. autoclass:: CReg - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~CReg.__init__ - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~CReg.creg - \ No newline at end of file diff --git a/docs/api/Compiler.rst b/docs/api/Compiler.rst deleted file mode 100644 index 9c8cd83df..000000000 --- a/docs/api/Compiler.rst +++ /dev/null @@ -1,30 +0,0 @@ -.. _compiler_api: - -Compiler -========== - -.. currentmodule:: openql.openql - -.. autoclass:: Compiler - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~Compiler.__init__ - ~Compiler.compile - ~Compiler.add_pass - ~Compiler.add_pass_alias - ~Compiler.set_pass_option - - .. rubric:: Attributes - - .. autosummary:: - - ~Compiler.name - - diff --git a/docs/api/Kernel.rst b/docs/api/Kernel.rst deleted file mode 100644 index b8f51bb51..000000000 --- a/docs/api/Kernel.rst +++ /dev/null @@ -1,65 +0,0 @@ -Kernel -====== - -.. currentmodule:: openql.openql - -.. autoclass:: Kernel - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~Kernel.__init__ - ~Kernel.barrier - ~Kernel.classical - ~Kernel.clifford - ~Kernel.cnot - ~Kernel.conjugate - ~Kernel.controlled - ~Kernel.cphase - ~Kernel.cz - ~Kernel.display - ~Kernel.gate - ~Kernel.get_custom_instructions - ~Kernel.hadamard - ~Kernel.identity - ~Kernel.measure - ~Kernel.mrx90 - ~Kernel.mry90 - ~Kernel.prepz - ~Kernel.rx - ~Kernel.rx180 - ~Kernel.rx90 - ~Kernel.ry - ~Kernel.ry180 - ~Kernel.ry90 - ~Kernel.rz - ~Kernel.s - ~Kernel.sdag - ~Kernel.t - ~Kernel.tdag - ~Kernel.toffoli - ~Kernel.wait - ~Kernel.x - ~Kernel.y - ~Kernel.z - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~Kernel.creg_count - ~Kernel.kernel - ~Kernel.name - ~Kernel.platform - ~Kernel.qubit_count - - \ No newline at end of file diff --git a/docs/api/Operation.rst b/docs/api/Operation.rst deleted file mode 100644 index 5c2c44669..000000000 --- a/docs/api/Operation.rst +++ /dev/null @@ -1,27 +0,0 @@ -Operation -========= - -.. currentmodule:: openql.openql - -.. autoclass:: Operation - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~Operation.__init__ - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~Operation.operation - \ No newline at end of file diff --git a/docs/api/Platform.rst b/docs/api/Platform.rst deleted file mode 100644 index d0defc59b..000000000 --- a/docs/api/Platform.rst +++ /dev/null @@ -1,30 +0,0 @@ -Platform -======== - -.. currentmodule:: openql.openql - -.. autoclass:: Platform - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~Platform.__init__ - ~Platform.get_qubit_number - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~Platform.config_file - ~Platform.name - ~Platform.platform - \ No newline at end of file diff --git a/docs/api/Program.rst b/docs/api/Program.rst deleted file mode 100644 index eb2ff5266..000000000 --- a/docs/api/Program.rst +++ /dev/null @@ -1,45 +0,0 @@ -Program -======= - -.. currentmodule:: openql.openql - -.. autoclass:: Program - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~Program.__init__ - ~Program.add_do_while - ~Program.add_for - ~Program.add_if - ~Program.add_if_else - ~Program.add_kernel - ~Program.add_program - ~Program.compile - ~Program.get_sweep_points - ~Program.microcode - ~Program.print_interaction_matrix - ~Program.qasm - ~Program.set_sweep_points - ~Program.write_interaction_matrix - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~Program.creg_count - ~Program.name - ~Program.platform - ~Program.program - ~Program.qubit_count - - \ No newline at end of file diff --git a/docs/api/openql.rst b/docs/api/openql.rst deleted file mode 100644 index 750e7f899..000000000 --- a/docs/api/openql.rst +++ /dev/null @@ -1,31 +0,0 @@ -openql -====== - -.. automodule:: openql.openql - :noindex: - - - - .. rubric:: Functions - - .. autosummary:: - - get_option - get_version - print_options - set_option - - - - - - .. rubric:: Classes - - .. autosummary:: - - CReg - Kernel - Operation - Platform - Program - Compiler diff --git a/docs/changelog.rst b/docs/changelog.rst deleted file mode 100644 index 6336d7666..000000000 --- a/docs/changelog.rst +++ /dev/null @@ -1 +0,0 @@ -.. mdinclude:: ../CHANGELOG.md diff --git a/docs/colophon/changelog.rst b/docs/colophon/changelog.rst new file mode 100644 index 000000000..ab37940f4 --- /dev/null +++ b/docs/colophon/changelog.rst @@ -0,0 +1 @@ +.. mdinclude:: ../../CHANGELOG.md diff --git a/docs/colophon/contributors.rst b/docs/colophon/contributors.rst new file mode 100644 index 000000000..98fc40ca5 --- /dev/null +++ b/docs/colophon/contributors.rst @@ -0,0 +1,4 @@ +Contributors +============= + +.. mdinclude:: ../../CONTRIBUTORS.md diff --git a/docs/conf.py b/docs/conf.py index 4d16a94c7..4a04526f2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,8 +19,8 @@ import os import sys original_workdir = os.getcwd() +docs_dir = os.path.dirname(__file__) try: - docs_dir = os.path.dirname(__file__) os.chdir(docs_dir) if not os.path.exists('doxygen/doxy'): subprocess.check_call(['doxygen']) @@ -30,10 +30,179 @@ html_extra_path = ['doxygen'] +# -- Generate RST files from runtime docs ------------------------------------ +import openql as ql +import m2r2 +import re + +def docs_to_rst_magic(text, header_level=1): + """Conversion magic for converting from OpenQL runtime docs to ReadTheDocs + RST files.""" + + # Perform conversion magic. + output = [] + rst_block = False + remove = False + blank = True + indent = 0 + for line in text.split('\n'): + + # Handle blank lines. + if not line.strip(): + rst_block = False + remove = False + blank = True + continue + + # Drop lines if we're in a removed section. + if remove: + continue + + # Strip section indentation. + while not line.startswith(' '*indent): + indent -= 1 + header_level -= 1 + line = line[indent*2:] + + # Handle the first line of a block of text, i.e. after a blank line. + if blank: + blank = False + output.append('') + + # Handle section header. + if line.startswith('* ') and line.endswith(' *'): + output.append('#'*header_level + ' ' + line[2:-2]) + indent += 1 + header_level += 1 + continue + + # Handle "note" block. + elif line.startswith('NOTE: '): + output.append('.. note::') + rst_block = True + line = line[6:] + + # Handle removed note blocks. This is used for "X is not included + # in this build" notifications that are naturally the case when + # ReadTheDocs is building OpenQL. + elif line.startswith('NOTE*: '): + remove = True + continue + + # Handle "warning" block. + elif line.startswith('WARNING: '): + output.append('.. warning::') + rst_block = True + line = line[9:] + + # A new RST block (note or warning) was opened, which means we need + # to capitalize the first letter. + if rst_block: + output.append(' ' + m2r2.convert(line[:1].upper() + line[1:]).strip()) + continue + + # Indent followup lines of RST blocks. + if rst_block: + line = ' ' + m2r2.convert(line).strip() + + # Finished converting stuff. + output.append(line) + + # Convert back to normal text. + text = '\n'.join(output) + '\n' + + # Convert markdown syntax to RST. + text = m2r2.convert(text) + + # m2r2 is a bit overzealous about things that look like HTML tags. After + # all, markdown permits insertion of raw HTML, and RST doesn't, so it + # does its best to convert things. That's not what we want; syntax like + # is used all over the place as placeholders within code blocks + # and such, and there absolutely should never be raw HTML in the + # docstrings anyway. So we just revert m2r2's hard work in this respect + # by stripping all :raw-html-m2r:`` blocks. + text = re.sub(r'(?:\\ )?:raw-html-m2r:`([^`]+)`(?:\\ )?', r'\1', text) + + return text + +def get_version(verbose=0): + """ Extract version information from source code """ + + matcher = re.compile('[\t ]*#define[\t ]+OPENQL_VERSION_STRING[\t ]+"(.*)"') + with open(os.path.join('..', 'include', 'ql', 'version.h'), 'r') as f: + for ln in f: + m = matcher.match(ln) + if m: + version = m.group(1) + break + else: + raise Exception('failed to parse version string from include/ql/version.h') + + return version + +if not os.path.exists('gen'): + os.makedirs('gen') + +# Version in installation instructions. +with open('manual/installation.rst.template', 'r') as f: + docs = f.read() +docs = docs.replace('{version}', get_version()) +with open('gen/manual_installation.rst', 'w') as f: + f.write(docs) + +# Architecture list. +with open('reference/architectures.rst.template', 'r') as f: + docs = f.read() +docs = docs.replace('{architectures}', docs_to_rst_magic(ql.dump_architectures(), 2)) +with open('gen/reference_architectures.rst', 'w') as f: + f.write(docs) + +# Global option list. +with open('reference/options.rst.template', 'r') as f: + docs = f.read() +docs = docs.replace('{options}', docs_to_rst_magic(ql.dump_options(), 2)) +with open('gen/reference_options.rst', 'w') as f: + f.write(docs) + +# Pass list. +with open('reference/passes.rst.template', 'r') as f: + docs = f.read() +docs = docs.replace('{passes}', docs_to_rst_magic(ql.dump_passes(), 2)) +with open('gen/reference_passes.rst', 'w') as f: + f.write(docs) + +# Resource list. +with open('reference/resources.rst.template', 'r') as f: + docs = f.read() +docs = docs.replace('{resources}', docs_to_rst_magic(ql.dump_resources(), 2)) +with open('gen/reference_resources.rst', 'w') as f: + f.write(docs) + +# Configuration file reference. +with open('reference/configuration.rst.template', 'r') as f: + docs = f.read() +docs = docs.replace('{platform}', docs_to_rst_magic(ql.dump_platform_docs(), 3)) +docs = docs.replace('{compiler}', docs_to_rst_magic(ql.dump_compiler_docs(), 3)) +with open('gen/reference_configuration.rst', 'w') as f: + f.write(docs) + +# Output of simple.py. +import shutil +original_workdir = os.getcwd() +examples_dir = os.path.join(os.path.dirname(__file__), '..', 'examples') +try: + os.chdir(examples_dir) + subprocess.check_call([sys.executable, os.path.join(examples_dir, 'simple.py')]) +finally: + os.chdir(original_workdir) +shutil.copyfile(os.path.join(examples_dir, 'output', 'my_program.qasm'), 'gen/my_program.qasm') +shutil.copyfile(os.path.join(examples_dir, 'output', 'my_program_scheduled.qasm'), 'gen/my_program_scheduled.qasm') + + # -- Project information ----------------------------------------------------- project = 'OpenQL' -copyright = '2016, Nader Khammassi & Imran Ashraf, QuTech, TU Delft' +copyright = '2016-2021, QuTech, TU Delft' author = 'QuTech, TU Delft' master_doc = 'index' @@ -44,11 +213,11 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'm2r', + 'm2r2', 'sphinx.ext.todo', - 'sphinx.ext.autodoc', - 'sphinx.ext.napoleon', - 'sphinx.ext.autosummary' + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx.ext.autosummary' ] autodoc_default_flags = ['members'] @@ -69,6 +238,13 @@ 'platform_*.rst', 'mapping.rst', 'scheduling.rst', 'decomposition.rst', 'optimization.rst', 'scheduling_ccl.rst', 'scheduling_cc.rst'] +def skip(app, what, name, obj, would_skip, options): + if name == "__init__": + return False + return would_skip + +def setup(app): + app.connect("autodoc-skip-member", skip) # -- Options for HTML output ------------------------------------------------- @@ -81,7 +257,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +#html_static_path = ['_static'] [extensions] @@ -89,3 +265,5 @@ # to enable figure numbering numfig = True + +autodoc_member_order = 'bysource' diff --git a/docs/contributors.rst b/docs/contributors.rst deleted file mode 100644 index 0c4c101a2..000000000 --- a/docs/contributors.rst +++ /dev/null @@ -1,4 +0,0 @@ -Contributors -============= - -.. mdinclude:: ../CONTRIBUTORS.md diff --git a/docs/cppref.rst b/docs/cppref.rst deleted file mode 100644 index e546d2026..000000000 --- a/docs/cppref.rst +++ /dev/null @@ -1,11 +0,0 @@ -Developer Documentation -======================= - -Documentation for the C++ source code is `generated by Doxygen `_. - -This is not intended as an API reference, as it includes internal stuff. If you -just want to *use* OpenQL, it's strongly recommended to use it from Python, -because OpenQL's internals change a lot. Nevertheless, you *can* use it -straight from C++. To do that, first make sure you're familiar with the Python -API, then look at the examples folder of the repository to see more or less how -the API calls map, and only then look in the Doxygen documentation for details. diff --git a/docs/developer/automation.rst b/docs/developer/automation.rst new file mode 100644 index 000000000..d705b9f00 --- /dev/null +++ b/docs/developer/automation.rst @@ -0,0 +1,44 @@ +.. _dev_automation: + +Build automation +================ + +OpenQL employs continuous integration based on GitHub Actions, to ensure that new features or modifications actually +work on all supported systems. The files to this end are in the ``.github`` directory of the repository. Furthermore, +ReadTheDocs can build and publish documentation for OpenQL automatically, for which the files are in the ``docs`` +folder (specifically, it uses the ``docs/requirements-docs.txt`` for a pip package dependency list and then just runs +the makefile in ``docs``. + + +Integration tests +----------------- + +The integration tests are run when you push to a branch for which a pull request is open, or when ``develop`` changes. +The suite runs the following things: + +- the Python test suite; +- the ctest suite (tests and examples); and +- the standalone C++ test, built completely out of context. + +The former two are run on ``ubuntu-latest``, ``macos-latest``, and ``windows-latest`` for all active Python versions +(currently 3.6 through 3.9). The latter is only run on ``ubuntu-latest``, as it doesn't check much except inclusion +of the project via CMake. + +.. note:: + To test an incomplete branch that you're still working on, please open a draft pull request. + + +Release automation +------------------ + +Release artifact generation triggers on a push to a branch that starts with ``release_`` (used for testing) or when +a new release is made via GitHub and/or a tag is pushed. + +.. warning:: + Please don't do any of these things until you have read the :ref:`dev_release`! + + +ReadTheDocs automation +---------------------- + +TODO: Razvan, anything noteworthy here? diff --git a/docs/developer/build.rst b/docs/developer/build.rst new file mode 100644 index 000000000..a6d135f9a --- /dev/null +++ b/docs/developer/build.rst @@ -0,0 +1,323 @@ +.. _dev_build: + +Build instructions +================== + +This page documents how OpenQL and its documentation pages can be built and installed from scratch. + +.. note:: + It is very difficult to maintain these instructions, due to there being so many supported environments, + and due to externally-maintained dependencies. Therefore, please + `let the OpenQL maintainers know `_ if you run into any + difficulties with these instructions. If you're a new maintainer, update them accordingly via a PR, but + be mindful that something that works on your machine might not work on everyone's machine! + +Dependencies +------------ + +The following packages are required to compile OpenQL from sources: + +- a C++ compiler with C++11 support (Linux: gcc, MacOS: LLVM/clang, Windows: MSVC 2015 with update 3 or above) +- git +- flex > 2.6 +- bison > 3.0 +- cmake >= 3.0 +- swig (Linux: >= 3.0.12, Windows: >= 4.0.0) +- Python 3.x + pip, with the following packages: + - ``plumbum`` + - ``wheel`` + - [Optional] ``pytest`` (for testing) + - [Optional] ``numpy`` (for testing) + - [Optional] ``libqasm`` (for testing) + - [Optional] ``sphinx==3.5.4`` (for documentation generation) + - [Optional] ``sphinx-rtd-theme`` (for documentation generation) + - [Optional] ``m2r2`` (for documentation generation) +- [Optional] Doxygen (for documentation generation) +- [Optional] Graphviz Dot utility (to convert graphs from dot to pdf, png etc) +- [Optional] XDot (to visualize generated graphs in dot format) +- [Optional] GLPK (if you want initial placement support) +- [Optional] make (required for documentation generation; other CMake backends can be used for everything else) +- [Optional, MacOS only] XQuartz (only if you want to use the visualizer) + +.. note:: + The connection between Sphinx' and SWIG's autodoc functionalities is very iffy, but aside from tracking everything + manually or forking SWIG there is not much that can be done about it. Because of this, not all Sphinx versions will + build correctly, hence why the Sphinx version is pinned. Sphinx 4.x for example crashes on getting the function + signature of property getters/setters. + +Windows-specific instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. note:: + The current maintainers of OpenQL all use either Linux or MacOS. While we've checked that these instructions + should work on a clean Windows install, things may go out of date. Please let us know if you encounter + difficulties with these instructions. + +Dependencies can be installed with: + +- `win_flex_bison 2.5.20 `_ +- `cmake 3.15.3 `_ +- `swigwin 4.0.0 `_ + +Make sure the above mentioned binaries are added to the system path. + +For initial placement support, you'll also need +`winglpk 4.6.5 `_. +But just adding this directory to the system path is not enough for CMake to find it. Instead, the toplevel +CMake script listens to the ``WINGLPK_ROOT_DIR`` environment variable. Set that to the root directory of what's +in that zip file instead. + +Alternatively, you can use Chocolatey to install packages. This is how CI currently does it. They just chain to +sourceforge downloads, though. + +The actual build and install should be done with PowerShell, for which some modifications (may?) need to be made +first. + +- Use Power Shell for installation +- Set execution policy by: + +:: + + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned + +- Install PowerShell Community Extensions: + +:: + + Install-Module -AllowClobber -Name Pscx -RequiredVersion 3.2.2 + +- MSVC 2015 should be added to the path by using the following command: + +:: + + Invoke-BatchFile "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 + +- but when you installed Microsoft Visual Studio Community Edition do: + +:: + + Invoke-BatchFile "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 + +- To make your life easier, you can add this command to the profile you are using for power shell, avoiding the need to manually run this command every time you open a power shell. You can see the path of this profile by `echo $PROFILE`. Create/Edit this file to add the above command. + +- Python.exe, win_flex.exe, win_bison.exe and swig.exe should be in the path of power shell. To test if swig.exe is the path, run: + +:: + + Get-Command swig + +- To show the currently defined environment variables do: + +:: + + Gci env: + +- Make sure the following variables are defined: + + - PYTHON_INCLUDE (should point to the directory containing Python.h) + - PYTHON_LIB (should point to the python library pythonXX.lib, where XX is for the python version number) + +- To set an environment variable in an expression use this syntax: + +:: + + $env:EnvVariableName = "new-value" + +MacOS-specific instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. note:: + These instructions have not been carefully vetted. If you run into issues, please let the maintainers know. + +All dependencies can be installed using `Homebrew `_ and pip: + +:: + + brew update + brew install llvm flex bison cmake swig python3 doxygen graphviz glpk xquartz + pip3 install wheel plumbum pytest numpy sphinx==3.5.4 sphinx-rtd-theme m2r2 + +Make sure the above mentioned binaries are added to the system path in front of ``/usr/bin``, otherwise CMake finds the default versions. + +Linux-specific instructions +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Honestly, if you're already used to developing on Linux, and you're using a self-respecting Linux +distribution, you should have no problems installing these dependencies. None of them are particularly +special, so they should all be available in your package manager. + +If you're for some reason using CentOS, you'll need to use a ``devtoolset`` compiler, because the one +shipped with it is too old. Likewise, CentOS ships with cmake 2.9 installed in ``/usr/bin`` and depends +on this; while ``cmake3`` is in the package manager, you actually need to call ``cmake3`` instead of +``cmake``, which ``setup.py`` is not smart enough for. On CentOS or other batteries-not-included systems +you might also have to compile some dependencies manually (``swig``, ``flex``, ``bison``, and their +dependencies ``m4`` and possibly ``gettext``), but they shouldn't give you too much drama. ``cmake`` has +distro-agnostic binary distributions on github that are a only ``wget`` and ``tar xzv`` away. ``glpk`` +might be a bigger issue; I haven't tried. + + +Obtaining OpenQL +---------------- + +OpenQL sources for each release can be downloaded from github +`releases `_ as .zip or .tar.gz archive. OpenQL can also be +cloned by: + +:: + + git clone https://github.com/QE-Lab/OpenQL.git --recursive + +Note the ``--recursive``: the repository depends on various submodules. If you forgot the ``--recursive``, +you can get/synchronize them later with ``git submodule update --init --recursive``. + + +Building the ``qutechopenql`` Python package +-------------------------------------------- + +Running the following command in a terminal/Power Shell from the root of the OpenQL repository should install the +``qutechopenql`` package: + +:: + + pip install -v . + +Or in editable mode by the command: + +:: + + pip install -v -e . + +Editable mode has the advantage that you'll get incremental compilation if you ever change OpenQL's C++ files, but it's +a bit more fragile in that things will break if you move the OpenQL repository around later. Specifically, editable mode +just installs an absolute path link to your clone of the OpenQL repository, so if you move it, the link breaks. You'd have +to remember to uninstall if you ever end up moving it. + +.. note:: + Depending on your system configuration, you may need to use ``pip3``, ``python -m pip`` or ``python3 -m pip`` instead + of ``pip``. You may also need to add ``--user`` to the flags or prefix ``sudo``. An exhaustive list of which is needed + when is out of scope here; instead, just look for pip usage instructions for your particular operating system online. + This works the same for any other Python package. + +.. warning:: + NEVER install with ``python3 setup.py install`` (or similar) directly! This always leads to all kinds of confusion, + because ``setuptools`` does not inform ``pip`` that the package is installed, allowing ``pip`` to go out of sync. + +.. note:: + The ``setup.py`` script (as invoked by pip in the above commands, again, do not invoke it directly!) listens to a number + of environment variables to configure the installation and the compilation process. The most important ones are: + + - ``OPENQL_ENABLE_INITIAL_PLACEMENT``: if defined (value doesn't metter), initial placement support will be enabled. + - ``OPENQL_DISABLE_UNITARY``: if defined (value doesn't matter), unitary decomposition is disabled. This speeds up + compile time if you don't need it. + - ``NPROCS``: sets the number of parallel processes to use when compiling (must be a number if defined). Without + this, it won't multithread, so it'll be much slower. + + In bash-like terminals, you can just put them in front of the pip command like so: ``NPROCS=10 pip ...``. In + Powershell, you can use ``$env:NPROCS = '10'`` in a command preceding the ``pip`` command. + +.. note:: + You may find that CMake notes that some packages it's looking for are missing. This is fine: some things are only + needed for optional components (which will automatically disable themselves when dependencies are missing) and + some things are only quality-of-life things, for example for generating backtraces for the exception messages. + As long as the tests pass, the core OpenQL components should all work. + +Once installed, and assuming you have the requisite optional dependencies installed, you can run the test suite (still +from the root of the OpenQL repository) using + +:: + pytest -v + +.. note:: + If ``pytest`` is unrecognized, you should be able to use ``python -m pytest`` or ``python3 -m pytest`` instead + (making sure to use the same Python version that the ``pip`` you installed the package with corresponds to). + +Conda vs pip +^^^^^^^^^^^^ + +A conda recipe also exists in the repository. However, it is in a state of disuse, as conda's ridiculous NP-complete +dependency solver implementation is too heavy for CI (it can take literal hours), and none of the maintainers use it. +Your mileage may vary. + + +Building the C++ tests and programs +----------------------------------- + +Existing tests and programs can be compiled by the following instructions. You +can use any existing example as a starting point for your own programs, but +refer to ``examples/cpp-standalone-example`` for the build system. + +The tests are run with the ``tests`` directory as the working directory, so +they can find their JSON files. The results end up in ``tests/test_output``. + + +Linux/MacOS +^^^^^^^^^^^ + +Existing tests and examples can be compiled and run using the following commands: + +:: + + mkdir cbuild + cd cbuild + cmake .. -DOPENQL_BUILD_TESTS=ON # configure the build + make # actually build OpenQL and the tests + make test # run the tests + + +Windows +^^^^^^^ + +Existing tests and examples can be compiled and run using the following commands: + +:: + + mkdir cbuild + cd cbuild + cmake .. -DOPENQL_BUILD_TESTS=ON -DBUILD_SHARED_LIBS=OFF # configure the build + cmake --build . # actually build OpenQL and the tests + cmake --build . --target RUN_TESTS # run the tests + +.. note:: + + ``-DBUILD_SHARED_LIBS=OFF`` is needed on Windows only because the + executables can't find the OpenQL DLL in the build tree that MSVC + generates, and static linking works around that. It works just fine when + you manually place the DLL in the same directory as the test executables + though, so this is just a limitation of the current build system for the + tests. + +Other CMake flags +^^^^^^^^^^^^^^^^^ + +CMake accepts a number of flags in addition to the ``-DOPENQL_BUILD_TESTS=ON`` +flag used above: + + - ``-DWITH_INITIAL_PLACEMENT=ON``: enables initial placement. + - ``-DWITH_UNITARY_DECOMPOSITION=OFF``: disables unitary composition (vastly + speeds up compile time if you don't need it). + - ``-DCMAKE_BUILD_TYPE=Debug``: builds in debug rather than release mode + (less optimizations, more debug symbols). + - ``-DBUILD_SHARED_LIBS=OFF``: build static libraries rather than dynamic + ones. Note that static libraries are not nearly as well tested, but they + should work if you need them. + + +Building the documentation +-------------------------- + +If you want, you can build the ReadTheDocs and doxygen documentation locally for your particular version of OpenQL. +Assuming you have installed the required dependencies to do so, the procedure is as follows. + +:: + + # first build/install the qutechopenql Python package! + cd docs + rm -rf doxygen # optional: ensures all doxygen pages are rebuilt + make clean # optional: ensures all Sphinx pages are rebuilt + make html + +The main page for the documentation will be generated at ``docs/_build/html/index.html``. + +.. note:: + The Doxygen pages are never automatically rebuilt, as there is no dependency analysis here. You will always need + to remove the doxygen output directory manually before calling ``make html`` to trigger a rebuild. diff --git a/docs/developer/conventions.rst b/docs/developer/conventions.rst new file mode 100644 index 000000000..d95c5ae8c --- /dev/null +++ b/docs/developer/conventions.rst @@ -0,0 +1,3 @@ +.. _dev_conventions: + +.. mdinclude:: ../../CONTRIBUTING.md diff --git a/docs/developer/doxygen.rst b/docs/developer/doxygen.rst new file mode 100644 index 000000000..a3a5071cd --- /dev/null +++ b/docs/developer/doxygen.rst @@ -0,0 +1,6 @@ +.. _dev_doxygen: + +Doxygen documentation +===================== + +Documentation for the C++ source code is `generated by Doxygen `_. Properly documenting everything with docstrings is unfortunately still an ongoing process, but at the very least all the functions and classes should be there, as automatically documented by Doxygen. diff --git a/docs/developer/readme.rst b/docs/developer/readme.rst new file mode 100644 index 000000000..2d6eba908 --- /dev/null +++ b/docs/developer/readme.rst @@ -0,0 +1,63 @@ +.. _dev_readme: + +Where to begin +============== + +So you want to contribute to OpenQL? Or perhaps you're employed to help +maintain it? Great! + +OpenQL has grown to be quite a large project, so you may be feeling a bit +overwhelmed. I know I was. I'm assuming you already know what OpenQL is when +you get here, otherwise please read through the user documentation first. +But after that, where to begin...? + +First of all, you should make sure that you're able to build and test OpenQL +on your own machine. So follow the :ref:`dev_build`, and if you run into +any problems, ask an existing maintainer or open an issue. + +If you'll be touching the Python API, you'll also want to follow the +instructions for building the documentation locally; there's all kinds of +generation magic from the API docstrings and documentation getters that might +fall over if you change the wrong thing, and documentation generation is not +currently tested by CI. + +Once done, you'll want to get some sort of IDE configured, so you can click +through the code. I use CLion; they have free educational licenses for anyone +with a university e-mail address, and it works okay. + +Before changing anything, please read through the *whole* section on +:ref:`dev_conventions` or ``CONTRIBUTING.md`` (the content is the same). This +section describes more than just what should be capitalized and whether +braces go on the same or the next line for consistency; it also goes over +the general organization of the code, how to include things to make sure +everything works everywhere, and what rules need to be followed with regards +to the documentation ``dump_*()`` functions in order for the reStructuredText +generators in ``docs/`` to keep working. + +Familiarize yourself with what's available in the ``ql::utils`` namespace. +This was added as a wrapper around the C++ standard library to offer additional +runtime safety, improve type naming consistency with the non-STL types defined +by OpenQL itself, and improve debugging. Depending on how used you are to C++ +programming, you'll probably either love it, hate it, or both. But please, +*please* use it anyway, to keep OpenQL's codebase consistent. + +In general, please think twice if you feel the need to type ``std::`` or +include a standard library header directly. Most things are wrapped (although +it's virtually impossible to be complete). + +Avoid adding new native dependencies. If you *really* need to, the build system +should be made smart enough for things to work out of the box even if the +dependency is not installed: your additions should automatically be disabled if +they can't be built, but the rest of OpenQL can. You can do this via +preprocessor macros, but be aware that you can only use those in ``src``! Files +in ``include`` are public, and can thus be built with any preprocessor macro +set when included by user C++ code. You can look at the code for unitary +decomposition, MIP-based placement, or the visualizer if you're not sure how to +work with these constraints; those pieces of code all do this. + +When you've added something, don't forget to add yourself to +``CONTRIBUTORS.md``! + +These were just some general pointers I came up with on a whim, so this is most +likely not complete. If you feel like something is missing, feel free to add to +this list! diff --git a/docs/developer/release.rst b/docs/developer/release.rst new file mode 100644 index 000000000..5aeb2045f --- /dev/null +++ b/docs/developer/release.rst @@ -0,0 +1,3 @@ +.. _dev_release: + +.. mdinclude:: ../../RELEASE.md diff --git a/docs/dqcsim_example.rst b/docs/dqcsim_example.rst deleted file mode 100644 index 1f681cfdf..000000000 --- a/docs/dqcsim_example.rst +++ /dev/null @@ -1,137 +0,0 @@ -DQCsim Simulation -================= - -This tutorial modifies the QX simulation tutorial to use `DQCsim `__. In short, DQCsim is a framework that allows simulations to be constructed by chaining plugins operating on a stream of gates and measurement results, thus making it easier to play around with error models, gather runtime statistics, and connect different quantum simulators to different algorithm file formats. In this tutorial, we will use it to simulate the toy example modelling an 8-faced die with QX and QuantumSim's error models. - -Note that DQCsim currently does not work on Windows. If you're using a Windows workstation, you'll need to work in a virtual machine or on a Linux server. - -Dependencies ------------- - -DQCsim and the plugins we'll be using can be installed using pip as follows: - -.. code:: - - python -m pip install dqcsim dqcsim-qx dqcsim-quantumsim dqcsim-cqasm - - -You'll probably need to prefix `sudo` to make that work, and depending on your Linux distribution you may need to substitute `python3`. If you don't have superuser access, you can add the `--user` flag, but you'll need to make sure that DQCsim's executables are in your system path. The easiest way to do that is figure out the path using `python -m pip uninstall dqcsim`, observe the directory that the `bin/dqcsim` file lives in, and add that to your path using `export PATH=$PATH:...`, replacing the `...` with the listed path from `/` to `bin`. - -We'll also need to add some modules to the Python file from the QX die example: - -.. code:: python - - from dqcsim.host import * - import shutil - - -Replicating the QXelarator results ----------------------------------- - -The results we got when using QX directly are pretty easy to replicate. Here's how: - -.. code:: python - - def dice_execute_singleshot(): - print('executing 8-face dice program on DQCsim using QX') - - # DQCsim disambiguates between input file formats based on file extension. - # .qasm is already in use for OpenQASM files, so DQCsim uses .cq for cQASM - shutil.copyfile('test_output/dice.qasm', 'test_output/dice.cq') - - # open the simulation context and run the simulation. the cQASM frontend - # returns the results as a JSON object for us to parse througn run() - with Simulator('test_output/dice.cq', 'qx') as sim: - results = sim.run() - - # parse the measurement results - res = [results['qubits'][q]['value'] for q in range(nqubits)] - - # convert the measurement results from 3 qubits to dice face value - dice_face = reduce(lambda x, y: 2*x+y, res, 0) +1 - print('Dice face : {}'.format(dice_face)) - - -The key is the `Simulator('test_output/dice.cq', 'qx')` expression wrapped in the `with` block, which constructs a DQCsim simulation using the `cq` frontend (based on the file extension, that's why we have to make a copy and rename OpenQL's output first) and the `qx` backend, wrapping the libqasm cQASM parser and QX's internals respectively. - -Enabling QX's depolarizing channel error model ----------------------------------------------- - -While not exactly useful for this particular algorithm, we can use DQCsim to enable QX's error model without having to edit the cQASM file. The easiest way to do that is to add a line before `sim.run()` to form - -.. code:: python - - with Simulator('test_output/dice.cq', 'qx') as sim: - sim.arb('back', 'qx', 'error', model='depolarizing_channel', error_probability=0.2) - results = sim.run() - - -This requires some explanation. The `sim.arb()` function (docs `here `__) instructs DQCsim to send a so-called ArbCmd (short for "arbitrary command") to one of its plugins. In short, ArbCmds are DQCsim's way to let its users communicate intent between plugins, without DQCsim itself needing to know what's going on. DQCsim has no concept of error models and the likes built-in, so we need to use ArbCmds to configure them. - -Its first argument specifies the plugin that the ArbCmd is intended for, where `'back'` is simply the default name for the backend plugin. You could also use the integer 1 to select the second plugin from the front, or -1 to select the first plugin from the back, as if it's indexing a Python list. - -The second and third argument specify the interface and operation identifiers respectively. The interface identifier is usually just the name of the plugin, acting like a namespace or the name of a class, while the operation identifier specifies what to do, acting as a function or method name. You'll have to read the `plugin documentation `__ to see which interface/operation pairs are supported. Usually these are listed in the form `.`, as if we're using a parameter named `` from a class named ``. - -Note that the semantics of ArbCmds are defined such that plugins will happily ignore any ArbCmd specifying an interface they don't support, but will complain when they support the interface but don't understand the operation. More information and the rationale for this can be found `here `__. - -Any remaining arguments are interpreted as arguments. Specifically, keyword arguments are transformed into the keys and values of a JSON object, in this case `{"model": "depolarizing_channel", "error_probability": 0.2}`. Positional arguments are interpreted as binary strings, but those are out of the scope of this tutorial (they're not that relevant in the Python world). Again, you'll have to read the plugin documentation to see what arguments are expected. - -You won't be able to see much in the result of the algorithm, because it was already purely random. But you may notice that the log output of DQCsim now includes a `Depolarizing channel model inserted ... errors` from the backend. - -Using QuantumSim instead ------------------------- - -More interesting in terms of DQCsim's functionality is just how easy it is to change the simulator. All you have to do to simulate using QuantumSim instead of QX is change the `'qx'` in the `Simulation` constructor with `'quantumsim'`. - -While QuantumSim is capable of much more, its `DQCsim plugin `__ currently only supports a qubit error model based on t1/t2 times. The arb for that, along with the modified `Simulator` constructor, looks like this: - -.. code:: python - - with Simulator('test_output/dice.cq', 'quantumsim') as sim: - sim.arb('back', 'quantumsim', 'error', t1=10.0, t2=20.0) - results = sim.run() - - -For that to have any merit whatsoever, you'll have to modify the code such that we're at least simulating OpenQL's scheduled output, because it's based entirely on the timing of the circuit: - -.. code:: python - - shutil.copyfile('test_output/dice_scheduled.qasm', 'test_output/dice.cq') - - -One thing the QuantumSim plugin does that the QX plugin doesn't is report the actual probability of a qubit measurement result. The `results` variable looks like this: - -.. code:: json - - { - "qubits": [ - { - "value": 0, - "raw": 0, - "average": 0.0, - "json": {"probability": 0.5}, - "binary": [[0, 0, 0, 0, 0, 0, 224, 63]] - }, - { - "value": 0, - "raw": 0, - "average": 0.0, - "json": {"probability": 0.5}, - "binary": [[0, 0, 0, 0, 0, 0, 224, 63]] - }, - { - "value": 0, - "raw": 0, - "average": 0.0, - "json": {"probability": 0.5}, - "binary": [[0, 0, 0, 0, 0, 0, 224, 63]] - } - ] - } - -In particular, the `"json"` parameter lists data that the cQASM frontend received from the backend but doesn't know about, in this case showing that the probability for this outcome was exactly 0.5 for each of the three individual measurements. - -Further reading ---------------- - -A more extensive Python tutorial for DQCsim can be found `here `__. It (intentionally) does not depend on any of the plugins and doesn't use OpenQL, but hopefully the above illustrates that swapping out plugins is about the easiest thing you can do with DQCsim. diff --git a/docs/feedback.rst b/docs/feedback.rst deleted file mode 100644 index 251f8e536..000000000 --- a/docs/feedback.rst +++ /dev/null @@ -1,169 +0,0 @@ -Feedback latencies in QuSurf architecture ------------------------------------------ - -.. list-table:: Latencies - :widths: 20 15 25 40 - :header-rows: 1 - - * - Identifier - - Latency [ns] - - Condition - - Description - * - tCcInputDio - - ~23 - - - - delay of DIO input interface and serializer - * - tCcSyncDio - - ~10 (0-20) - - - - synchronize incoming signal on DIO interface to 50 MHz grid. Depends on arrival time and DIO timing calibration - * - tCcDistDsm - - 20 - - - - read DIO interface and dispatch DSM data distribution - * - tCcWaitDsm - - 80 - - S-17 (3 parallel 8 bit transfers) - - wait for DSM transfers to be completed - * - tCcSyncDsm - - - - - - - * - tCcCondgate - - 20 - - - - output a gate conditional on DSM data - * - tCcOutputDio - - ~10 - - - - delay of serializer and DIO output interface - * - **tCcDioToDio** - - **~163** - - S-17 - - total latency from DIO data arriving to DIO output, depends on DIO timing calibration - * - - - - - - - - * - tCcCondBreak - - 150 - - - - perform a break conditional on DSM data - * - - - - - - - - * - - - - - - - - * - tHdawgTriggerDio - - 180 - - HDAWG8 v2, filter disabled, no output delay - - delay from DIO trigger to first analog output - * - tHdawgFilter - - 30 - - - - extra delay if the filter is enabled at all (not bypassed) - * - tHdawgFilterHighPass - - 40 - - - - extra delay if high pass filter is enabled - * - tHdawgFilterExpComp - - 36.67 - - - - extra delay per enabled exponential compensation filter stage (8 stages available) - * - tHdawgFilterBounceComp - - 13.33 - - - - extra delay if bounce compensation filter is enabled - * - tHdawgFilterFir - - 56.67 - - - - extra delay if FIR filter is enabled - * - tHdawgOutputDelay - - 0-TBD - - - - output delay configurable by user (/DEV..../SIGOUTS/n/DELAY) - * - - - - - - - - * - tUhfqaTriggerDio - - - - - - delay from DIO trigger to first analog output. Depends on number of codeword possibilities in sequencing program - * - tUhfqa5stateDiscr - - 168 - - UHFQA-5, no bypass - - state discrimination latency, from TBD to TBD - * - tUhfqa9stateDiscr - - 261 - - UHFQA-9, no bypass - - state discrimination latency, from TBD to TBD - * - tUhfqaReadoutProcessing - - 140 - - Deskew, Rotation, and Crosstalk units bypassed - - delay between the end of a readout pulse at the Signal Inputs and the QA Result Trigger on any Trigger output - * - tUhfqaDeskew - - ?? - - - - delay introduced by enabling Deskew unit - * - tUhfqaRotation - - ?? - - - - delay introduced by enabling Rotation unit - * - tUhfqaCrosstalk - - ?? - - - - delay introduced by enabling Crosstalk unit - * - tUhfqaReadoutProcessing - - 400 - - Deskew, Rotation, and Crosstalk units enabled - - delay between the end of a readout pulse at the Signal Inputs and the QA Result Trigger on any Trigger output - * - tUhfqaHoldoff - - - - - - - * - - - - - - - - * - tQwgTriggerDio - - 80 - - using LVDS input - - delay from DIO trigger to first analog output. Includes sideband modulation and mixer correction - * - - - - - - - - * - - - - - - - - * - tVsmDelay - - 12 - - VSM v3 - - delay from digital input to signal starts turning on/off - * - tVsmTransition - - - - - - transition time of VSM switch from on to off or vice versa - * - - - - - - - - -FIXME: - -- how does tUhfqa*stateDiscr relate to tUhfqaReadoutProcessing? - -Information sources: - -- tHdawgTriggerDio: table 5.5 of https://docs.zhinst.com/pdf/ziHDAWG_UserManual.pdf (revision 21.02.0) -- tHdawgFilter*: section 4.6.2 of same document -- tCc*: CC-SiteVisitVirtual-20200506.pptx -- tUhfqaReadoutProcessing: ziUHFQA_UserManual.pdf (revision 21.02.01) -- tUhf*: QuSurf_MetricsTables_201015-Please-update-for-TEM5.docx -- tQwg*: 20171511_pitch_qwg_final.pptx - diff --git a/docs/index.rst b/docs/index.rst index fe34400d4..3b3fd3dc9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,68 +3,99 @@ Welcome to OpenQL's documentation! OpenQL is a framework for high-level quantum programming in C++/Python. The framework provides a compiler for compiling and optimizing quantum code. -The compiler produces quantum assembly and instruction-level code for various target platforms. -While the instruction-level code is platform-specific, the quantum assembly code (QASM) is hardware-agnostic and can be simulated on one of the simulators. - +Compared to competing frameworks, such as Qiskit, OpenQL's focus lies more on +retargetability and compiling all the way down to assembly code for the various +control (micro)architectures used by QuTech, and less on high-level constructs +such as circuit conjugation: in general, the input you provide is a complete +circuit and a platform description, and the output is an equivalent circuit that +complies to platform constraints and/or machine code for running that circuit +on a real quantum computer. + +How to read the documentation +----------------------------- + +The documentation is roughly split into three main parts: + + - the user manual; + - the user reference; and + - the developer documentation. + +The user manual portion is intended to be read like a book, to give new users +an overview of how to use OpenQL and build intuition for what does what. It +culminates in a few tutorials that take you from a basic algorithm all the way +to simulation of the compiled algorithm. The reference may then be used for more +exhaustive information about particular topics of interest to you, such as +particular API functions, passes, architectures, and so on. Note that most of +the contents of the reference section are also available from within Python +using the various ``dump_*()`` functions; this document only provides a more +pleasingly laid-out version of the same information. + +The developer documentation is only intended for (new) contributors. That is to +say: unless you're intending to understand or change OpenQL's internal C++ +implementation, the information here is not relevant to you. Rather, the section +provides an overview of the codebase and the conventions used, and includes +internal interface documentation as generated by Doxygen. Note however, that the +intention is that the code is "self-documenting," in the sense that the relevant +documentation is placed inside the code as long comment blocks, to incentivize +keeping the code and documentation synchronized. Ideally this would all be +generated into the Doxygen documentation, but not everything has been converted +to Doxygen-recognized docstrings yet. .. toctree:: :maxdepth: 2 - :caption: Getting Started - - overview - installation - start + :caption: User manual + manual/concepts + gen/manual_installation + manual/first_program + manual/qx + manual/dqcsim + manual/where_to_go_from_here .. toctree:: :maxdepth: 2 - :caption: OpenQL Basics - - program - kernel - quantum_gate - classical_instructions - platform - compiler - compiler_passes - visualizer + :caption: Reference + reference/python + reference/cpp + gen/reference_configuration + gen/reference_architectures + gen/reference_options + gen/reference_passes + gen/reference_resources .. toctree:: - :maxdepth: 1 - :caption: Colophon - - changelog - contributors - - -.. toctree:: - :maxdepth: 1 - :caption: API Reference + :maxdepth: 2 + :caption: Developer documentation - api/openql - api/Kernel - api/Program - api/Compiler - api/Platform - api/Operation - api/CReg + developer/readme + developer/build + developer/automation + developer/release + developer/conventions + developer/doxygen .. toctree:: - :maxdepth: 3 - :caption: Tutorials: + :maxdepth: 2 + :caption: Colophon - qx_example - dqcsim_example + colophon/changelog + colophon/contributors .. toctree:: - :maxdepth: 1 - :caption: Developer Documentation - - cppref + :maxdepth: 2 + :caption: Old pages + old/program + old/kernel + old/quantum_gate + old/classical_instructions + old/platform + old/compiler + old/compiler_passes Indices and tables +------------------ * :ref:`genindex` * :ref:`modindex` diff --git a/docs/installation.rst b/docs/installation.rst deleted file mode 100644 index 5b5c71181..000000000 --- a/docs/installation.rst +++ /dev/null @@ -1,304 +0,0 @@ -Installation -============ - -OpenQL is supported on Linux, Windows and OSX. OpenQL can be installed on -these platforms as a pre-built package as well as can be compiled from -sources. - -- Pre-built package - - python package using pip - - conda package -- Compilation from sources - - Windows - - Linux - - OSx - - -Installing the pre-built package --------------------------------- - -Pre-built packages are available for OpenQL. - -.. note:: - - Initial placement is currently not included with the binary distribution - due to a license incompatibility with one of the dependencies (GLPK, to - be specific). Until this is resolved somehow, you'll have to build from - source if you need it. - - -Pre-built Wheels -^^^^^^^^^^^^^^^^ - -This is perhaps the easiest way to get OpenQL running on your machine. - -Pre-built OpenQL wheels are available for 64-bit Windows, Linux and OSX. These -wheels are available for Python 3.5, 3.6 and 3.7. OpenQL can be installed by -the command: - -:: - - pip install qutechopenql - - -.. note:: - - ``python`` refers to Python 3 and ``pip`` refers to Pip 3, corresponding to Python version 3.5, 3.6 or 3.7. - - -Conda package -^^^^^^^^^^^^^ - -OpenQL can be installed as a conda package (currently on Linux and Windows only) by: - -:: - - conda install -c qe-lab openql - - -Conda packages can also be built locally by using the recipe available in the conda-recipe directory, -by running the following command from the OpenQL directory: - -:: - - conda build conda-recipe/. - -The generated package can then be installed by: - -:: - - conda install openql --use-local - - -Compilation from sources ------------------------- - -Compiling OpenQL from sources involves: - -- Setting-up required packages -- Obtaining OpenQL - - -Required Packages -^^^^^^^^^^^^^^^^^ - -The following packages are required to compile OpenQL from sources: - -- g++ compiler with C++11 support (Linux) -- MSVC 2015 with update 3 or above (Windows) -- flex (> 2.6) -- bison (> 3.0) -- cmake (>= 3.0) -- swig (Linux: 3.0.12, Windows: 4.0.0) -- Python (3.5, 3.6, 3.7) -- [Optional] pytest used for running tests -- [Optional] Graphviz Dot utility to convert graphs from dot to pdf, png etc -- [Optional] XDot to visualize generated graphs in dot format -- [Optional] GLPK if you want initial placement support - - -Notes for Windows Users -^^^^^^^^^^^^^^^^^^^^^^^ -Dependencies can be installed with: - -- `win_flex_bison 2.5.20 `_ -- `cmake 3.15.3 `_ -- `swigwin 4.0.0 `_ - -Make sure the above mentioned binaries are added to the system path. - -For initial placement support, you'll also need -`winglpk 4.6.5 `_. -But just adding this directory to the system path is not enough for CMake to find it. Instead, the toplevel -CMake script listens to the ``WINGLPK_ROOT_DIR`` environment variable. Set that to the root directory of what's -in that zip file instead. - - -- Use Power Shell for installation -- Set execution policy by: - -:: - - Set-ExecutionPolicy -ExecutionPolicy RemoteSigned - -- Install [PowerShell Community Extensions] (https://www.google.com "PowerShell Community Extensions") - -:: - - Install-Module -AllowClobber -Name Pscx -RequiredVersion 3.2.2 - -- MSVC 2015 should be added to the path by using the following command: - -:: - - Invoke-BatchFile "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 - -- but when you installed Microsoft Visual Studio Community Edition do: - -:: - - Invoke-BatchFile "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 - -- To make your life easier, you can add this command to the profile you are using for power shell, avoiding the need to manually run this command every time you open a power shell. You can see the path of this profile by `echo $PROFILE`. Create/Edit this file to add the above command. - -- Python.exe, win_flex.exe, win_bison.exe and swig.exe should be in the path of power shell. To test if swig.exe is the path, run: - -:: - - Get-Command swig - -- To show the currently defined environment variables do: - -:: - - Gci env: - -- Make sure the following variables are defined: - - - PYTHON_INCLUDE (should point to the directory containing Python.h) - - PYTHON_LIB (should point to the python library pythonXX.lib, where XX is for the python version number) - -- To set an environment variable in an expression use this syntax: - -:: - - $env:EnvVariableName = "new-value" - -Notes for macOS Users -^^^^^^^^^^^^^^^^^^^^^^^ -Dependencies can be installed using `Homebrew `_: - -:: - - brew install flex bison - -Make sure the above mentioned binaries are added to the system path in front of ``/usr/bin``, otherwise CMake finds the default versions. - -Obtaining OpenQL -^^^^^^^^^^^^^^^^ - -OpenQL sources for each release can be downloaded from github `releases `_ as .zip or .tar.gz archive. OpenQL can also be cloned by: - -:: - - git clone https://github.com/QE-Lab/OpenQL.git --recursive - - -Compiling OpenQL as Python Package -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Running the following command in the python (virtual) environment in Terminal/Power Shell should install the openql package: - -:: - - cd OpenQL - git submodule update --init --recursive - pip install -v - -Or in editable mode by the command: - -:: - - pip install -v -e . - -Editable mode has the advantage that you'll get incremental compilation if you ever change OpenQL's C++ files, but it's -a bit more fragile in that things will break if you move the OpenQL repository around later. Specifically, editable mode -just installs an absolute path link to your clone of the OpenQL repository, so if you move it, the link breaks. You'd have -to remember to uninstall if you ever end up moving it. - -.. note:: - - The ``setup.py`` script (as invoked by pip in the above commands) listens to a number of environment variables to - configure the installation and the compilation process. The most important ones are: - - - ``OPENQL_ENABLE_INITIAL_PLACEMENT``: if defined (value doesn't metter), initial placement support will be enabled. - - ``OPENQL_DISABLE_UNITARY``: if defined (value doesn't matter), unitary decomposition is disabled. This speeds up - compile time if you don't need it. - - ``NPROCS``: sets the number of parallel processes to use when compiling (must be a number if defined). Without - this, it won't multithread, so it'll be much slower. - - In bash-like terminals, you can just put them in front of the pip command like so: ``NPROCS=10 pip ...``. In - Powershell, you can use ``$env:NPROCS = '10'`` in a command preceding the ``pip`` command. - - -Running the tests -................. - -In order to pass all the python tests, the openql package should be installed in editable mode. -Also, *qisa-as* and *libqasm* should be installed first. Follow `qisa-as `_ -and `libqasm `_ instructions to install python interfaces of these modules. -Once *qisa-as* and *libqasm* are installed, you can run all the tests by: - -:: - - pytest -v - - -or - -:: - - python -m pytest - - -Compiling C++ OpenQL tests and programs -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Existing tests and programs can be compiled by the following instructions. You -can use any existing example as a starting point for your own programs, but -refer to ``examples/cpp-standalone-example`` for the build system. - -The tests are run with the ``tests`` directory as the working directory, so -they can find their JSON files. The results end up in ``tests/test_output``. - - -Linux/OSX -......... - -Existing tests and examples can be compiled and run using the following commands: - -:: - - mkdir cbuild - cd cbuild - cmake .. -DOPENQL_BUILD_TESTS=ON # configure the build - make # actually build OpenQL and the tests - make test # run the tests - - -Windows -....... - -Existing tests and examples can be compiled and run using the following commands: - -:: - - mkdir cbuild - cd cbuild - cmake .. -DOPENQL_BUILD_TESTS=ON -DBUILD_SHARED_LIBS=OFF # configure the build - cmake --build . # actually build OpenQL and the tests - cmake --build . --target RUN_TESTS # run the tests - -.. note:: - - ``-DBUILD_SHARED_LIBS=OFF`` is needed on Windows only because the - executables can't find the OpenQL DLL in the build tree that MSVC - generates, and static linking works around that. It works just fine when - you manually place the DLL in the same directory as the test executables - though, so this is just a limitation of the current build system for the - tests. - -Other CMake flags -................. - -CMake accepts a number of flags in addition to the ``-DOPENQL_BUILD_TESTS=ON`` -flag used above: - - - ``-DWITH_INITIAL_PLACEMENT=ON``: enables initial placement. - - ``-DWITH_UNITARY_DECOMPOSITION=OFF``: disables unitary composition (vastly - speeds up compile time if you don't need it). - - ``-DCMAKE_BUILD_TYPE=Debug``: builds in debug rather than release mode - (less optimizations, more debug symbols). - - ``-DBUILD_SHARED_LIBS=OFF``: build static libraries rather than dynamic - ones. Note that static libraries are not nearly as well tested, but they - should work if you need them. diff --git a/docs/manual/concepts.rst b/docs/manual/concepts.rst new file mode 100644 index 000000000..e82a60abc --- /dev/null +++ b/docs/manual/concepts.rst @@ -0,0 +1,203 @@ +Concepts +======== + +This section goes over some key concepts that you should understand before +doing anything with OpenQL. + +OpenQL versus other compilers +----------------------------- + +A key difference between the OpenQL compiler and traditional compilers is that +OpenQL is a library rather than an application. That means that you can't just +invoke OpenQL on the command line given some input file. Instead, your input +file is (usually) a Python script that imports the OpenQL module, builds a +representation of the algorithm that OpenQL understands as it runs, and +eventually tells OpenQL to compile that algorithm representation somehow. The +output of the compilation process is then usually written to output files, +though the behavior of the compiler depends entirely on its configuration. + +Also different compared to most compilers is that OpenQL is inherently +retargetable. Whereas with for instance ``gcc`` the target architecture is +built right into it, with OpenQL you can compile code for many different kinds +of control architectures and quantum devices. This target architecture is +described via the platform configuration structure. + +Platform configuration +---------------------- + +In OpenQL, the *platform configuration* is what determines what quantum device +and control architecture will be compiled for, also known as the compilation +*target*. It ultimately defines the subset of describable quantum circuits that +can actually be executed on the target by way of a set of constraints and +reduction rules. Here are some examples of things described in the structure. + + - The primitive instruction set, along with decomposition rules for common + gates that cannot be directly represented. + - The number of usable qubits within the device and their connectivity. + - Control and instrument constraints on available gate parallelism. + +The goal for the compiler is to take the user-specified algorithm and convert +it to a behaviorally equivalent circuit within this set, preferably the most +optimal one it can find. + +As of version 0.9, OpenQL has +:ref:`a bunch of default target descriptions built into it`. +You can use them directly if they're good enough for your use case, or you can +use them as a baseline for making your own. The complete configuration +structure is defined :ref:`here`. + +Quantum algorithm representation +-------------------------------- + +.. note:: + This is *not* a description of the current implementation of the + intermediate representation of the compiler, but rather an overview of what + it behaves like from a user perspective. + +OpenQL models a quantum algorithm as follows: + + - a complete algorithm is referred to as a *program*; + - a program consists of one or more *kernels*; and + - each kernel consists of one or more "statically-scheduled" *gates* (a.k.a. + instructions) without control-flow within the kernel. + +Typically, most of the Python or C++ program using the OpenQL compiler consists +of building an algorithm using this model, although it's also possible to build +it from a cQASM file using the cQASM reader pass. + +Depending on your background, "static," "scheduled," and "control-flow" may +require further explanation. + + - "Static" just means "known by the compiler," or equivalently, "not dependent + on information only known at runtime." In the world of quantum computing, + this typically just means "not dependent on measurement results." + - The "schedule" is what defines when a gate is applied, in this context + relative to the start of the kernel. So, "statically-scheduled" means that + (if a gate is applied) that gate must always be applied at the same time + with respect to the start of the kernel. + - "Control-flow" is *almost* anything to do with conditional statements (like + ``if``) and loops. More formally, anything that results in a classical + branch instruction is considered to be control-flow. Note that OpenQL also + supports a special case for ``if``-like constructs called + *conditional gate execution* that does not rely on control-flow; we'll get + to that. + +The result of the above is that a kernel behaves just like how you would +traditionally draw a quantum circuit, with time on the X-axis and the qubits +and classical bits on the Y-axis in the form of horizontal lines. + +More complex algorithms that include control-flow can be specified using +multiple kernels. Say, for instance, that you have an initialization circuit, +then a circuit that you want to repeat until some qubit measures as 1, followed +by a circuit that does some final measurements. The first and last circuit +would then be added to the program as a normal kernel, while the second would +be added as a do-while kernel. + +.. warning:: + Most architectures and parts of OpenQL don't fully support nonstandard + kernel types yet. Some (mapping) don't support multiple kernels in any form. + For now, you have to check the documentation of the passes used by the + particular compiler configuration that you intend to use to see what's + supported and what isn't. + +Control-flow based on measurement results tends to be a costly operation in +most architectures, because the time from sending a measurement gate to the +instruments to being able to act on the measurement result tends to be quite +long compared to the coherence time of NISQ-era qubits. However, sometimes +part of this pipeline can be avoided. Say, for instance, that you want to +apply an X gate on some qubit only if some other qubit measured as 1. If the +instruments themselves (or at least a deeper part of the control architecture) +are capable of turning an X gate into an identity/no-op gate based on a +measurement, this and subsequent gates can already be queued up before the +measurement has actually taken place. This is the conditional gate execution +we alluded to earlier. Using this scheme, the condition for whether the gate +is executed or not is encoded as part of the gate, instead of being part of +the program's control-flow. + +Gate representation +------------------- + +Gates in OpenQL fundamentally consist of a name, some set of operands, and a +condition. The gate names available for use are defined within the platform +configuration file, along with some of their semantics, such as the gate +duration. + +.. warning:: + As of version 0.9, OpenQL also still assigns semantics and makes + assumptions based on the name of a gate however. For example, an ``x`` gate + is assumed to commute with an ``x90`` gate, and both are assumed to have a + single qubit operand and nothing else, or things will probably break. This + behavior is unfortunately largely undocumented, so you'll have to search + through the code for it. Obviously this is not an ideal situation, and thus + this is something that we want to get rid of. All semantics needed by OpenQL + should, down the line, be specified in the platform configuration, or, for + backward compatibility, be inferred from the gate name in a documented way. + +The operand set for each gate consists of the following: + + - zero or more qubit operands; + - zero or more creg operands; + - zero or more breg operands; + - zero or one literal integer operand*; and + - zero or one angle operand. + +Here, "cregs" refer to classical integer registers, and "bregs" refer to +classical bit registers. The former are used for loops and other control flow, +while the latter are used for conditional execution. + +Finally, the gate's condition consists of a boolean function applied to zero, +one, or two bregs. Unconditional gates are simply modelled using a unit-one +boolean function acting on zero bregs. + +Configuring the compilation process +----------------------------------- + +We've now described the way in which you specify the input and the target for +the compiler, but there's one more thing OpenQL must know: *how* to compile +for the given target. This is also known as the compilation *strategy*. When +the strategy is incorrect or insufficient, the resulting circuits may not +actually be completely valid for the target, unless the incoming algorithm is +carefully written such that constraints not dealt with by OpenQL have already +been met. + +Generally, the compilation process consists of the following steps: + + - decomposition; + - optimization; + - mapping; + - scheduling; and + - code generation. + +Decomposition is the act of converting gates that cannot be executed using a +single instruction in the target gateset into a list of gates that have the +same behavior. For example, a SWAP gate may be decomposed into three CNOT +gates. + +Optimization tries to reduce the algorithm to a more compact form. This is +particularly relevant after decomposition, as the decomposition rules may +lead to sequences of gates that trivially cancel each other out. + +Mapping is the act of changing the qubit indices in the circuit such that the +connectivity constraints of the target device are met. For complex circuits, +no single mapping will suffice (or it may be too time-consuming to compute, +as this is an NP problem); in this case, SWAP gates will be inserted to route +non-nearest-neighbor qubits toward each other. + +Scheduling is the act of assigning cycle numbers to each gate in a kernel. +This can of course be done trivially by assigning monotonously increasing +cycle numbers to each gate in the order in which they were written by the +user, but this is highly inefficient; instead, heuristics and commutation +rules are used to try to find a more optimal solution that makes efficient +use of the parallelism provided by the control architecture. + +Finally, code generation takes the completed program and converts it to the +assembly or machine-code format that the architecture-specific tools expect +at their input. + +A strategy consists of a list of :ref:`passes`, along with +pass-specific configuration options for each pass. OpenQL provides default +pass lists for the available architectures, as listed in the +:ref:`architecture reference`. You can modify this default +strategy using API calls prior to compilation if need be, or you can override +the defaults entirely by writing a +:ref:`compiler configuration file`. diff --git a/docs/manual/dqcsim.rst b/docs/manual/dqcsim.rst new file mode 100644 index 000000000..0f92113d1 --- /dev/null +++ b/docs/manual/dqcsim.rst @@ -0,0 +1,200 @@ +DQCsim Simulation +================= + +This tutorial modifies the QX simulation tutorial to use +`DQCsim `_. In short, DQCsim is a framework +that allows simulations to be constructed by chaining plugins operating on a +stream of gates and measurement results, thus making it easier to play around +with error models, gather runtime statistics, and connect different quantum +simulators to different algorithm file formats. In this tutorial, we will use +it to simulate the toy example modelling an 8-faced die with QX and +QuantumSim's error models. + +Note that DQCsim currently does not work on Windows. If you're using a Windows +workstation, you'll need to work in a virtual machine or on a Linux server. + +Dependencies +------------ + +DQCsim and the plugins we'll be using can be installed using pip as follows: + +.. code:: + + python -m pip install dqcsim dqcsim-qx dqcsim-quantumsim dqcsim-cqasm + +You'll probably need to prefix ``sudo`` to make that work, and depending on +your Linux distribution you may need to substitute ``python3``. If you don't +have superuser access, you can add the ``--user`` flag, but you'll need to make +sure that DQCsim's executables are in your system path. The easiest way to do +that is figure out the path using ``python -m pip uninstall dqcsim``, observe +the directory that the ``bin/dqcsim`` file lives in, and add that to your path +using ``export PATH=$PATH:...``, replacing the ``...`` with the listed path +from ``/`` to ``bin``. + +We'll also need to add some modules to the Python file from the QX die example: + +.. code:: python + + from dqcsim.host import * + import shutil + + +Replicating the QXelarator results +---------------------------------- + +The results we got when using QX directly are pretty easy to replicate. Here's how: + +.. code:: python + + def dice_execute_singleshot(): + print('executing 8-face dice program on DQCsim using QX') + + # DQCsim disambiguates between input file formats based on file extension. + # .qasm is already in use for OpenQASM files, so DQCsim uses .cq for cQASM + shutil.copyfile('output/dice.qasm', 'output/dice.cq') + + # open the simulation context and run the simulation. the cQASM frontend + # returns the results as a JSON object for us to parse througn run() + with Simulator('output/dice.cq', 'qx') as sim: + results = sim.run() + + # parse the measurement results + res = [results['qubits'][q]['value'] for q in range(nqubits)] + + # convert the measurement results from 3 qubits to dice face value + dice_face = reduce(lambda x, y: 2*x+y, res, 0) +1 + print('Dice face : {}'.format(dice_face)) + + +The key is the ``Simulator('test_output/dice.cq', 'qx')`` expression wrapped in +the ``with`` block, which constructs a DQCsim simulation using the ``cq`` +frontend (based on the file extension, that's why we have to make a copy and +rename OpenQL's output first) and the ``qx`` backend, wrapping the libqasm +cQASM parser and QX's internals respectively. + +Enabling QX's depolarizing channel error model +---------------------------------------------- + +While not exactly useful for this particular algorithm, we can use DQCsim to +enable QX's error model without having to edit the cQASM file. The easiest way +to do that is to add a line before ``sim.run()`` to form + +.. code:: python + + with Simulator('test_output/dice.cq', 'qx') as sim: + sim.arb('back', 'qx', 'error', model='depolarizing_channel', error_probability=0.2) + results = sim.run() + + +This requires some explanation. The ``sim.arb()`` function (docs +`here `_) +instructs DQCsim to send a so-called ArbCmd (short for "arbitrary command") +to one of its plugins. In short, ArbCmds are DQCsim's way to let its users +communicate intent between plugins, without DQCsim itself needing to know +what's going on. DQCsim has no concept of error models and the likes built-in, +so we need to use ArbCmds to configure them. + +Its first argument specifies the plugin that the ArbCmd is intended for, where +``'back'`` is simply the default name for the backend plugin. You could also +use the integer 1 to select the second plugin from the front, or -1 to select +the first plugin from the back, as if it's indexing a Python list. + +The second and third argument specify the interface and operation identifiers +respectively. The interface identifier is usually just the name of the plugin, +acting like a namespace or the name of a class, while the operation identifier +specifies what to do, acting as a function or method name. You'll have to read +the `plugin documentation `_ to see which +interface/operation pairs are supported. Usually these are listed in the form +``.``, as if we're using a parameter named +```` from a class named ````. + +Note that the semantics of ArbCmds are defined such that plugins will happily +ignore any ArbCmd specifying an interface they don't support, but will complain +when they support the interface but don't understand the operation. More +information and the rationale for this can be found +`here `_. + +Any remaining arguments are interpreted as arguments. Specifically, keyword +arguments are transformed into the keys and values of a JSON object, in +this case ``{"model": "depolarizing_channel", "error_probability": 0.2}``. +Positional arguments are interpreted as binary strings, but those are out of +the scope of this tutorial (they're not that relevant in the Python world). +Again, you'll have to read the plugin documentation to see what arguments are +expected. + +You won't be able to see much in the result of the algorithm, because it was +already purely random. But you may notice that the log output of DQCsim now +includes a `Depolarizing channel model inserted ... errors` from the backend. + +Using QuantumSim instead +------------------------ + +More interesting in terms of DQCsim's functionality is just how easy it is to +change the simulator. All you have to do to simulate using QuantumSim instead +of QX is change the ``'qx'`` in the ``Simulation`` constructor with +``'quantumsim'``. + +While QuantumSim is capable of much more, its +`DQCsim plugin `_ currently +only supports a qubit error model based on t1/t2 times. The arb for that, +along with the modified `Simulator` constructor, looks like this: + +.. code:: python + + with Simulator('test_output/dice.cq', 'quantumsim') as sim: + sim.arb('back', 'quantumsim', 'error', t1=10.0, t2=20.0) + results = sim.run() + +For that to have any merit whatsoever, you'll have to modify the code such that +we're at least simulating OpenQL's scheduled output, because it's based +entirely on the timing of the circuit: + +.. code:: python + + shutil.copyfile('output/dice_scheduled.qasm', 'output/dice.cq') + +One thing the QuantumSim plugin does that the QX plugin doesn't is report the +actual probability of a qubit measurement result. The `results` variable looks +like this: + +.. code:: json + + { + "qubits": [ + { + "value": 0, + "raw": 0, + "average": 0.0, + "json": {"probability": 0.5}, + "binary": [[0, 0, 0, 0, 0, 0, 224, 63]] + }, + { + "value": 0, + "raw": 0, + "average": 0.0, + "json": {"probability": 0.5}, + "binary": [[0, 0, 0, 0, 0, 0, 224, 63]] + }, + { + "value": 0, + "raw": 0, + "average": 0.0, + "json": {"probability": 0.5}, + "binary": [[0, 0, 0, 0, 0, 0, 224, 63]] + } + ] + } + +In particular, the ``"json"`` parameter lists data that the cQASM frontend +received from the backend but doesn't know about, in this case showing that +the probability for this outcome was exactly 0.5 for each of the three +individual measurements. + +Further reading +--------------- + +A more extensive Python tutorial for DQCsim can be found +`here `_. It +(intentionally) does not depend on any of the plugins and doesn't use OpenQL, +but hopefully the above illustrates that swapping out plugins is about the +easiest thing you can do with DQCsim. diff --git a/docs/manual/first_program.rst b/docs/manual/first_program.rst new file mode 100644 index 000000000..7524fac62 --- /dev/null +++ b/docs/manual/first_program.rst @@ -0,0 +1,194 @@ +.. _creating_your_first_program: + +Creating your first program +=========================== + +In the OpenQL framework, the quantum :class:`Program `, its +:class:`Kernel `\ s, and its :meth:`gate `\ s +are created using API calls contained in a C++ or Python 3 program. You then +run this program in order to compile the quantum program. In the manual, we'll +use Python exclusively, but +:ref:`the API is largely identical in C++ `. + +You can set up Python however you like; via a Jupyter/IPython notebook, a +Python file created in a text editor that you then run, various Python IDEs +like IDLE, and so on. Just make sure that OpenQL is actually installed for the +interpreter that your preferred environment uses. + +The very first step is to import the OpenQL module: + +.. code-block:: python + + import openql as ql + +.. note:: + + In versions before 0.9, you had to use the more verbose + ``from openql import openql as ql`` syntax. This is still supported for + backward compatibility, but is deprecated. + +Next, you should call the +:func:`initialize() ` function: + +.. code-block:: python + + ql.initialize() + +This function ensures that OpenQL is (re)initialized to its default +configuration. This is especially important in the context of a test suite or +IPython notebook, where one might want to do multiple compilation runs in a +single Python instance. For backward compatibility, OpenQL will automatically +call this function when you first use a dependent API function, warning you as +it does. + +After initialization, you may want to change some of OpenQL's global +:ref:`options `. One of the important ones is ``output_dir``, +which is used to specify which directory the compiler's output will be placed +in. If you don't set it, OpenQL will default to outputting to a ``test_output`` +directory within the current working directory. Another important one is +``log_level``, which sets the verbosity of OpenQL's logging; if you don't set +that one, you won't see any log messages, at least until something has already +gone horribly wrong. + +.. code-block:: python + + ql.set_option('output_dir', 'output') + ql.set_option('log_level', 'LOG_INFO') + +.. note:: + + OpenQL will automatically recursively create directories whenever it tries + to write a file. Thus, you don't have to manually create the output + directory. + +.. note:: + + As of version 0.9, you can also opt to use the more powerful, but slightly + more complicated :class:`Compiler ` API to set options + and manipulate the compilation strategy. Since version 0.9, almost all of + the global options have no effect other than manipulating the default + compilation strategy and pass options. You can obtain a reference to the + :class:`Compiler ` object used by a + :class:`Platform ` or :class:`Program ` + using the :meth:`get_compiler() ` method, + or you can construct a :class:`Compiler ` manually and + use its :meth:`compile() ` function to compile + the program (rather than ``program.compile()``). + +Before you can start building a quantum program, you must create a +:class:`Platform ` object. One of its constructor parameters +is either the name of the :ref:`architecture` you want to +compile for, a reference to a +:ref:`platform configuration file`, or (via +the :meth:`from_json() ` constructor) a JSON +object specified by way of Python dictionaries, lists, strings, integers, and +booleans with the same structure as the platform configuration file. The +platform configuration is consulted by the APIs creating the program, kernels, +and gates, to generate the matching internal representation of each gate. + +For now, let's use the "none" architecture: + +.. code-block:: python + + platform = ql.Platform('my_platform', 'none') + +This is a basic architecture that is most useful for simulating with QX. The +first argument is only used to identify the platform in error messages; you can +set it to whatever you like. + +After creating the platform, the :class:`program ` and its +:class:`Kernel `\ s may be created. The program and kernel +constructors take the program/kernel name, the associated platform, and the +number of qubits used in it as parameters. We'll use 3 in this example: + +.. code-block:: python + + nqubits = 3 + program = ql.Program('my_program', platform, nqubits) + kernel = ql.Kernel('my_kernel', platform, nqubits) + +When needed, the number of used CRegs (classical integer registers) and BRegs +(bit registers) used by the program/kernel must also be specified, but we don't +use these for now. + +Again, the first argument is just a name. However, unlike for the platform, the +name is actually used. Specifically, the program name is used as a prefix for +the output files, and the kernel names are used in various places where a +unique name is needed (thus, they must actually be unique). + +Once you have a kernel, you can add gates to it: + +.. code:: python + + for i in range(nqubits): + kernel.prepz(i) + + kernel.x(0) + kernel.h(1) + kernel.cz(2, 0) + kernel.measure(0) + kernel.measure(1) + +Most gates have a shorthand function, as used above. However, some +architecture-specific gates might not, or might need additional arguments. For +these cases, the :meth:`gate() ` method can be used. + +.. note:: + + You can only add gates that are registered via the instruction set + definition in the platform configuration structure, or for which a + decomposition rule exists. If a gate doesn't exist there, you will + immediately get an exception. In future versions, this exception may be + delayed to when you call :meth:`compile() `. + +When you're done adding gates to a kernel, you can add the kernel to the +program using :meth:`add_kernel() `: + +.. code:: python + + program.add_kernel(kernel) + +.. note:: + + The number of qubits, CRegs, and BRegs used by a kernel must be less than + or equal to the number used by the program, and the number for the program + must be less than or equal to what the number available in the platform. + Also, a kernel can only be added to a program when the kernel and program + were constructed using the same platform. + +Finally, when you have completed the program, you can compile it using the +:meth:`compile() `_ +format. + +The second is a little more interesting: + +.. literalinclude:: ../gen/my_program_scheduled.qasm + +It is generated after basic ALAP (as late as possible) scheduling, using the +(rather arbitrary) instruction durations specified in the default platform +configuration file for the "none" architecture. + +Depending on the architecture and compiler configuration, different output +files may be generated. The above only applies because of the default pass +list doe the "none" architecture: a cQASM writer, followed by a scheduler, +followed by another cQASM writer. This is fully configurable. diff --git a/docs/manual/installation.rst.template b/docs/manual/installation.rst.template new file mode 100644 index 000000000..53e2faf36 --- /dev/null +++ b/docs/manual/installation.rst.template @@ -0,0 +1,51 @@ +Installation +============ + +OpenQL is available from PyPI as a pre-built package for Windows, MacOS, and +Linux for all active Python 3.x versions. Once you have Python and have access +to a command line you can get it as follows: + +:: + + pip install qutechopenql + +after which you should be able to run + +:: + + python -c 'import openql; print(openql.get_version())' + +to see if it works. + +.. note:: + Depending on your OS and Python configuration, you may need to use + ``python3`` instead of just ``python`` to disambiguate with a Python 2.7 + installation, and/or use ``pip3`` or ``python -m pip`` instead of just + ``pip``. You may also need to add ``--user`` at the end of the ``pip`` + command to avoid permission problems. If you're unsure of what all of the + above means, first read up on how Python and pip work on your operating + system in the relevant Python (or Linux distribution) documentation; + installation of Python packages is rather fundamental to Python and out of + scope for this manual. + +.. warning:: + The documentation you're reading now is generated for version **{version}**. + If there is a mismatch, be aware that there may be an API mismatch as well! + The reference information can, however, be queried from within Python using + the ``help()`` builtin and (from 0.8.1.dev6 onwards) using the various + ``dump_*()`` functions. + +If you're on MacOS and want to use the visualizer, you'll need XQuartz in +addition. You can install this using `Brew `_. + +Some of OpenQL's components are optional when OpenQL itself is compiled. In +general, the pre-built package includes everything, *except* for initial +placement due to a license conflict. If you need initial placement, you'll +need to :ref:`compile manually`. + +OpenQL used to support Conda in addition to PyPI/pip for Python package +management, but ultimately this was disabled due to excessive time spent on +dependency resolution. Nevertheless, the Conda recipe is still available, so +it may or may not work, but for this you'll also have to +:ref:`compile manually`, as the prebuilt Conda packages are likely +out of date. diff --git a/docs/manual/qx.rst b/docs/manual/qx.rst new file mode 100644 index 000000000..05d9787d0 --- /dev/null +++ b/docs/manual/qx.rst @@ -0,0 +1,148 @@ +Simulation using QX +=================== + +This tutorial explains how to compile an OpenQL program and execute it on QX. +We will use the example of rolling an 8-faced dice. Rolling this dice results +in 1 out of 8 outcomes. The complete code for this example is available in +``examples/dice.py``. You can also copy the snippits over to your own script +as we walk through it. + +.. figure:: ../figures/dice.png + :width: 400px + :align: center + :alt: 8-faced Dices + :figclass: align-center + +We start by importing openql, qxelerator and some python packages. We also set +some options for openql. For this example we will be using 3 qubits. All this +is done by the following code snippet: + +.. code:: python + + from openql import openql as ql + import qxelarator + from functools import reduce + import os + import matplotlib.pyplot as plt + + ql.set_option('output_dir', 'output') + ql.set_option('log_level', 'LOG_INFO') + + nqubits = 3 + +Next, we create a platform, a program and a kernel. We populate the kernel with +3 hadamard gates being applied on each qubits. This will put each qubit in +superposition. Measuring each qubit will collapse the state resulting in +getting either 0 or 1. This is done by dice_compile() as shown below: + +.. code:: python + + def dice_compile(): + platform = ql.Platform('myPlatform', 'none') + p = ql.Program('dice', platform, nqubits) + k = ql.Kernel('aKernel', platform, nqubits) + + for q in range(nqubits): + k.gate('h', [q]) + + for q in range(nqubits): + k.gate('measure', [q]) + + p.add_kernel(k) + p.compile() + +Compiling the above code snippet will produce the following quantum assembly +code in `cQASM 1.0 `_ +format: + + - ``output/dice.qasm``, the generated un-scheduled cQASM code; and + - ``output/dice_scheduled.qasm``, the generated cQASM code after scheduling. + +For instance, ``dice.qasm`` contents are shown below: + +.. parsed-literal:: + + version 1.0 + # this file has been automatically generated by the OpenQL compiler please do not modify it manually. + qubits 3 + + .aKernel + h q[0] + h q[1] + h q[2] + measure q[0] + measure q[1] + measure q[2] + +These cQASM codes can be simulated on `QX simulator `_. +For this we are using the simplified python interface to QX known as +`QXelarator `_. +This is done by the following code snippet: + +.. code:: python + + def dice_execute_singleshot(): + print('executing 8-face dice program on qxelarator') + qx = qxelarator.QX() + + # set the qasm to be executed + qx.set('output/dice.qasm') + + # execute the qasm + qx.execute() + + # get the measurement results + res = [int(qx.get_measurement_outcome(q)) for q in range(nqubits)] + + # convert the measurement results from 3 qubits to dice face value + dice_face = reduce(lambda x, y: 2*x+y, res, 0) + 1 + print('Dice face : {}'.format(dice_face)) + + +Running ``dice.py`` will produce output as shown below: + +.. parsed-literal:: + + Dice face : 2 + +where the Dice face can be any number between 1 and 8. + +Next we can also roll the dice 100000 times and plot the frequency of occurance +of each face by the following code snippet: + +.. code:: python + + def plot_histogram(dice_faces): + plt.hist(dice_faces, bins=8, color='#0504aa',alpha=0.7, rwidth=0.85) + plt.grid(axis='y', alpha=0.75) + plt.xlabel('Dice Face',fontsize=15) + plt.ylabel('Frequency',fontsize=15) + plt.xticks(fontsize=15) + plt.yticks(fontsize=15) + plt.ylabel('Frequency',fontsize=15) + plt.title('Histogram',fontsize=15) + plt.show() + plt.savefig('hist.png') + + def dice_execute_multishot(): + print('executing 8-face dice program on qxelarator') + qx = qxelarator.QX() + qx.set('output/dice.qasm') + dice_faces = [] + ntests = 100 + for i in range(ntests): + qx.execute() + res = [int(qx.get_measurement_outcome(q)) for q in range(nqubits)] + dice_face = reduce(lambda x, y: 2*x+y, res, 0) +1 + dice_faces.append(dice_face) + + plot_histogram(dice_faces) + +This will produce the histogram similar to the one shown below: + +.. figure:: ../figures/dice_hist.png + :width: 600px + :align: center + :alt: Histogram + :figclass: align-center + diff --git a/docs/manual/where_to_go_from_here.rst b/docs/manual/where_to_go_from_here.rst new file mode 100644 index 000000000..d08a13103 --- /dev/null +++ b/docs/manual/where_to_go_from_here.rst @@ -0,0 +1,8 @@ +Where to go from here +===================== + +This manual is not really complete yet, but hopefully the most important things +have been treated, and you can figure out the rest from the much more complete +:ref:`reference `. If not, you may try to read through some of the +:ref:`older pages `. These have not yet been updated for version 0.9, +but may still provide useful background information. diff --git a/docs/classical_instructions.rst b/docs/old/classical_instructions.rst similarity index 99% rename from docs/classical_instructions.rst rename to docs/old/classical_instructions.rst index a13bab01e..db082d672 100644 --- a/docs/classical_instructions.rst +++ b/docs/old/classical_instructions.rst @@ -3,6 +3,10 @@ Classical Instructions ====================== +.. warning:: + This page has not been revised yet since modularization and refactoring, + and may thus be out of date. + OpenQL supports a mix of quantum and classical computing at the gate level. Please recall that classical gates are gates that don't have any qubit as operand, only zero or more classical registers and execute in classical hardware. diff --git a/docs/compiler.rst b/docs/old/compiler.rst similarity index 98% rename from docs/compiler.rst rename to docs/old/compiler.rst index 254f25e97..f6cf9f772 100644 --- a/docs/compiler.rst +++ b/docs/old/compiler.rst @@ -3,6 +3,10 @@ Compiler ========= +.. warning:: + This page has not been revised yet since modularization and refactoring, + and may thus be out of date. + To compile a program, the user needs to configure a compiler first. Until version 0.8, this program compilation was done using a monolithic hard-coded sequence of compiler passes inside the program itself when ``program.compile()`` function was called. This is the legacy operation mode, which is currently described in the :ref:`Program` documentation page. However, starting with version 0.8.0.dev1, the programer has the ability to configure its own pass sequence using the :ref:`Compiler API `. There are two options on how to configure a compiler. The first and the most straightforward is to define a compiler object giving it as the second parameter the name of a json configuration, similar of how the :ref:`Platform` is defined. The following code line shows an example of a such initialization: diff --git a/docs/compiler_passes.rst b/docs/old/compiler_passes.rst similarity index 97% rename from docs/compiler_passes.rst rename to docs/old/compiler_passes.rst index 2376e4213..452f7d045 100644 --- a/docs/compiler_passes.rst +++ b/docs/old/compiler_passes.rst @@ -3,6 +3,10 @@ Compiler Passes =============== +.. warning:: + This page has not been revised yet since modularization and refactoring, + and may thus be out of date. + Most of the passes in their function and implementation are platform independent, deriving their platform dependent information from options and/or the configuration file. This holds also for mapping, although one wouldn't think so first, @@ -202,7 +206,7 @@ by its parameterization by the platform configuration file. See :ref:`platform`. -.. include:: decomposition.rst -.. include:: optimization.rst -.. include:: scheduling.rst -.. include:: mapping.rst +.. include:: passes/decomposition.rst +.. include:: passes/optimization.rst +.. include:: passes/scheduling.rst +.. include:: passes/mapping.rst diff --git a/docs/kernel.rst b/docs/old/kernel.rst similarity index 99% rename from docs/kernel.rst rename to docs/old/kernel.rst index 3b5e7db9b..ccfda2030 100644 --- a/docs/kernel.rst +++ b/docs/old/kernel.rst @@ -3,6 +3,10 @@ Kernel ====== +.. warning:: + This page has not been revised yet since modularization and refactoring, + and may thus be out of date. + A kernel conventionally models a circuit with quantum gates ending in one or more measurements. In OpenQL, this has been extended with: diff --git a/docs/decomposition.rst b/docs/old/passes/decomposition.rst similarity index 100% rename from docs/decomposition.rst rename to docs/old/passes/decomposition.rst diff --git a/docs/mapping.rst b/docs/old/passes/mapping.rst similarity index 100% rename from docs/mapping.rst rename to docs/old/passes/mapping.rst diff --git a/docs/optimization.rst b/docs/old/passes/optimization.rst similarity index 100% rename from docs/optimization.rst rename to docs/old/passes/optimization.rst diff --git a/docs/scheduling.rst b/docs/old/passes/scheduling.rst similarity index 99% rename from docs/scheduling.rst rename to docs/old/passes/scheduling.rst index 7369c85d4..6423cd1f7 100644 --- a/docs/scheduling.rst +++ b/docs/old/passes/scheduling.rst @@ -309,7 +309,3 @@ Scheduling for hardware platforms - Scheduling for CC-Light platform - Scheduling for CC platform - Scheduling for CBox platform - - -.. include:: scheduling_ccl.rst -.. include:: scheduling_cc.rst diff --git a/docs/old/platform.rst b/docs/old/platform.rst new file mode 100644 index 000000000..27c36e62e --- /dev/null +++ b/docs/old/platform.rst @@ -0,0 +1,22 @@ +.. _platform: + +Platforms and architectures +=========================== + +OpenQL supports various target platforms. These platforms can be software simulators or architectures targetting hardware quantum computers. + +In principle, platforms are described entirely via a JSON configuration file; as few architecture-dependent things as possible are actually hardcoded in OpenQL. This means, however, that the platform configuration file is quite extensive, making the learning curve for making one from scratch or even adjusting one pretty steep. Furthermore, actually compiling for a particular platform may also require a custom compilation strategy (i.e. which steps/passes the compiler takes/does to actually compile the program), further complicating things. Therefore, OpenQL ships with a bunch of logic to generate sane default settings for particular architectures that we support out of the box. These architectures, and the defaults they provide, are listed in the :ref:`ref_architectures` section. + +A platform can be created in OpenQL by using one of the various constructors for the ``ql.Platform()`` class. The most commonly used way to do obtain a platform object is as follows: + +.. code:: python + + platform = ql.Platform('', ``) + +Here, ```` is anything you want it to be (it's only used for logging) and ```` can be a recognized architecture name, such as `"none"` or `"cc"`, or it can point to a platform configuration file. For example, a platform with the name ``CCL_platform`` that uses the defaults for CC-light can be created as: + +.. code:: python + + platform = ql.Platform('CCL_platform', 'cc_light') + +For more details, see also the :ref:`ref_python`, :ref:`ref_configuration`, and :ref:`ref_architectures` sections. diff --git a/docs/program.rst b/docs/old/program.rst similarity index 96% rename from docs/program.rst rename to docs/old/program.rst index 20376322d..2f8b990dd 100644 --- a/docs/program.rst +++ b/docs/old/program.rst @@ -3,6 +3,10 @@ Program ======= +.. warning:: + This page has not been revised yet since modularization and refactoring, + and may thus be out of date. + In the OpenQL programming model, one first creates the platform object and then with it the program object. After that, one creates kernels with gates and adds these kernels to the program. Finally, one compiles the program and executes it. diff --git a/docs/quantum_gate.rst b/docs/old/quantum_gate.rst similarity index 99% rename from docs/quantum_gate.rst rename to docs/old/quantum_gate.rst index c1b545478..93ff659d1 100644 --- a/docs/quantum_gate.rst +++ b/docs/old/quantum_gate.rst @@ -3,6 +3,10 @@ Quantum Gates ============= +.. warning:: + This page has not been revised yet since modularization and refactoring, + and may thus be out of date. + Gates in OpenQL are the constructs that refer to operations to be executed somehow on the computing platform. A gate refers to an operation and to zero or more operands. diff --git a/docs/overview.rst b/docs/overview.rst deleted file mode 100644 index 868cffc5c..000000000 --- a/docs/overview.rst +++ /dev/null @@ -1,81 +0,0 @@ -Overview -======== - -OpenQL is a framework for high-level quantum programming in C++/Python. -The framework provides a compiler for compiling and optimizing quantum code. - -This document -------------- - -The first three chapters introduce OpenQL, -help to install it, -and show how to create a first OpenQL program. -They are here for people who want to get going with OpenQL as quickly as possible. -For people just wanting an overview of OpenQL, these, except for the installation chapter, are a must read. - -Further chapters introduce to the basic concepts of OpenQL. -They contain a lot of conceptual texts, and inevitable for a good understanding of the system. -What is a program, what is a kernel and to which extent are classical instructions supported? -What kind of gates does OpenQL support, which are the internal and which are the external representations? -Omni-present in OpenQL is the platform, literally in the form of the platform configuration file -that parameterizes most passes on the supported platform. -And finally the compiler passes, in a summary as well as in an extensive description with functional description -and sets of options listened too. - -The document concludes with lists of APIs and indices. - -OpenQL compiler structure -------------------------- - -An OpenQL compiler reads a quantum program written in some external representation, -performs several analysis and transformation passes on it, -and prints the result to an external representation again. -Internally in the compiler the passes operate on a common internal representation of the program, -IR for short, which is equal to all passes. - -Understanding this internal representation is key -to understanding the operation of an OpenQL compiler. -It is structured as an attributed tree of objects. - -At the top one finds the (internal representation of the) program. -Its main component is the vector of kernels. -Each ordinary kernel (object) contains a single circuit which basically is a vector of gates. -Gates in OpenQL are the constructs -that refer to operations to be executed somehow on the computing platform. -These can be quantum gates as well as classical gates; -the latter deal with classical arithmetic and measurement results. -A circuit of a kernel is always executed from start to end. -There are special kernels without a circuit that take care of control flow between kernels. -But for ordinary kernels -after the last gate control is transferred to the next kernel. - -All passes operate at the program level. -Each performs its work on all kernels before it completes and another pass can run. -The order of the passes is predefined by OpenQL, -but there are ways to enable/disable individual passes. -The effect of a pass is to update the internal representation. -This can amount to computing attributes, replacing gates by other ones, -rearranging gates, and so on. - -The objective of an OpenQL compiler is -to produce an output external representation of the input program -that satisfies the needs of what comes next. -What comes next is represented in OpenQL by the (target) platform. -These platforms can be software simulators or architectures targetting hardware quantum computers. - -To the compiler this platform is described by a *platform configuration file*, -a file in JSON format, -which contains several sections with descriptions of attributes of the platform. -Examples of these are the number of qubits, -the supported set of primitive gates with their attributes, -the connection graph between the qubits (also called the topology of the grid), -and the classical control constraints imposed by the control electronics -of the hardware of the platform. -It also specifies for which hardware platform family it contains the configuration. -These hardware platform families (called architectures) are built-in into the OpenQL compiler, -and the compiler, after having executed some platform independent passes, -will enter the architecture-specific part of the compiler -where it executes several platform dependent passes. -When compiling for a hardware quantum computer target, -the last ones of these will generate some form -of low-level assembly code corresponding to the particular instruction set of the platform. diff --git a/docs/platform.rst b/docs/platform.rst deleted file mode 100644 index cd3bfaab1..000000000 --- a/docs/platform.rst +++ /dev/null @@ -1,332 +0,0 @@ -.. _platform: - -Platform -======== - -OpenQL supports various target platforms. -These platforms can be software simulators or architectures targetting hardware quantum computers. -The following platforms are supported by OpenQL: - -- software simulator platforms - - :ref:`qxplatform` - - :ref:`qsimplatform` -- hardware platforms - - :ref:`cclplatform` - - :ref:`ccplatform` - - :ref:`cboxplatform` - -:Note: Quantumsim and QX are not really platforms. They are means to simulate a particular (hardware) platform. Qasm files for use by QX and python scripts to interface to quantumsim are generated for any hardware platform under the control of options. See the descriptions of the QX and Quantumsim platforms referred to above. - -:Note: We are planning to use DQCsim, a platform to connect to simulators. In that context, software simulator platforms are connected to by DQCsim, and OpenQL just provides compilation support to a particular hardware platform. - -A platform can be created in OpenQL by using the ``Platform()`` API as shown below: - -.. code:: python - - platform = ql.Platform('', ) - -For example, -a platform with the name ``CCL_platform`` and using ``hardware_config_cc_light.json`` as platform configuration file -can be created as: - -.. code:: python - - platform = ql.Platform('CCL_platform', 'hardware_config_cc_light.json') - - -Platform Configuration File ---------------------------- - -The platform configuration file describes the target platform in JSON format. -The information in this file is used by all platform independent compiler passes. -The parameterization by this information makes these platform independent compiler passes -in source code independent of the platform but in effective function dependent on the platform. - -A platform configuration file consists of several sections (in arbitrary order) which are described below. -Most of these are mandatory; the specification of the ``topology`` and the ``resources`` sections are optional. - -For example (the ``...`` contains the specification of the respective section): - -.. code-block:: html - :linenos: - - { - "eqasm_compiler" : "cc_light_compiler", - - "hardware_settings": - { - "qubit_number": 7, - "cycle_time" : 20, - ... - }, - - "topology": - { - ... - }, - - "resources": - { - ... - }, - - "instructions": - { - ... - }, - - "gate_decomposition": - { - ... - } - } - - -The platform comfiguration file for its structure is platform independent. -It can be extended at will with more sections and more attributes for platform dependent purposes. - -The sections below describe sections and attributes that are used by platform independent compiler passes. - -Please refer to the sections of the specific platforms for full examples and for the description of any additional attributes. - - -Attribute eqasm_compiler -^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``eqasm_compiler`` attribute specifies the backend compiler to be used for this platform. -After the passes of the platform independent compiler have been called, -the platform independent compiler switches out to the backend compiler to run the platform dependent passes. -The specification of this attribute is mandatory. -The ``eqasm_compiler`` attribute can take the following values; these correspond to the platforms that are supported: - -* ``none``: no backend compiler is called -* ``qx``: no backend compiler is called; see above how to generate a qasm file for QX -* ``cc_light_compiler``: backend compiler for CC_Light -* ``eqasm_backend_cc``: backend compiler for CC -* ``qumis_compiler``: backend compiler to CBOX - - -Section hardware_settings -^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``hardware_settings`` section specifies various parameters describing the platform. -These include the ``qubit_number'' and ``cycle_time`` which are generally used, -and the buffer delays, only used by the rcscheduler, -which are related to control electronics in the experiments (for hardware backends). -The specification of this section is mandatory. - -For example: - -.. code-block:: html - :linenos: - - "hardware_settings": - { - "qubit_number": 7, - "cycle_time" : 20, - - "mw_mw_buffer": 0, - "mw_flux_buffer": 0, - "mw_readout_buffer": 0, - "flux_mw_buffer": 0, - "flux_flux_buffer": 0, - "flux_readout_buffer": 0, - "readout_mw_buffer": 0, - "readout_flux_buffer": 0, - "readout_readout_buffer": 0 - } - -In this: - -* ``qubit_number`` indicates the number of (real) qubits available in the platform. - Gates and instructions that addresss qubits do this by providing a qubit index in the range of 0 to qubit_number-1. - Using an index outside this range will raise an error. - -* ``cycle_time`` is the clock cycle time. - As all other timing specifications in the configuration file it is specified in nanoseconds. - Only at multiples of this cycle time, instructions can start executing. - The schedulers assign a cycle value to each gate, which means that that gate can start executing - a number of nanoseconds after program execution start - that equals that cycle value multiplied by the clock cycle_time value. - -* The other entries of the ``hardware_settings`` section specify various buffer times to be - inserted between various operations due to control electronics setup. For example, - ``mw_mw_buffer`` can be used to specify time to be inserted between a microwave - operation followed by another microwave operation. See :ref:`scheduling` for details. - - -Section topology -^^^^^^^^^^^^^^^^ - -Specifies the qubit topology as the connection graph of the qubits of the platform. -This is primarily used by the mapping pass; this section is optional. -It specifies the mapping of qubit indices to qubit positions in the platform, as well as the mapping of connection indices to connections in the platform. -A connection is a directed connection in the platform between a pair of qubits that supports qubit interaction. -It is directed to distinguish the control and target qubits of two-qubit gates. -In a platform topology's connection graph, qubits are the nodes, and connection are the edges. - -It looks like (the ``...`` contains further specifications): - -.. code-block:: html - :linenos: - - "topology" : - { - "x_size": 5, - "y_size": 3, - "qubits": - [ - { "id": 0, "x": 1, "y": 2 }, - ... - ], - "edges": - [ - { "id": 0, "src": 2, "dst": 0 }, - ... - ] - }, - - -The ``topology`` section starts with -the specification of the two dimensions of a rectangular qubit grid by specifying ``x_size`` and ``y_size``. -The positions of the real qubits of the platform are defined relative to this (artificial) grid. -The coordinates in the X direction are 0 to x_size-1. -In the Y direction they are 0 to y_size-1. -Next, for each available qubit in the platform, its position in the grid is specified: -the ``id`` specifies the particular qubit's index, and ``x`` and ``y`` specify its position in the grid, -as coordinates in the X and Y direction, respectively. -Please note that not every position in the x_size by y_size grid needs to correspond to a qubit. - -Qubits are connected in directed pairs, called edges. -Edge indices form a contigous range starting from 0. -Each edge in the topology is given an ``id`` which denotes its index, and a source (control) and destination (target) qubit index by ``src`` and ``dst``, respectively. This means that there can be edges between the same pair of qubits but in opposite directions. -The qubit indices specified here must correspond to available qubits in the platform. - -For a full example of this section, please refer to :ref:`cclplatform`. - - -Section resources -^^^^^^^^^^^^^^^^^ - -Specify the classical control constraints of the platform. -This section is optional. -These constraints are used by the resource manager, that on its turn is used by the scheduling and mapping passes. -These classical control constraints are described as restrictions on concurrent access to resources of predefined resource types. -Specification of these resources affects -scheduling and mapping of gates. - -The ``resources`` section specifies zero or more resource types -that are predefined by the mandatory platform dependent resource manager. -For CC-Light, these resource types are ``qubits``, ``qwgs``, ``meas_units``, ``edges``, and ``detuned_qubits``. -The presence of one in the configuration file -indicates that the resource-constrained scheduler should take it into account -when trying to schedule operations in parallel, i.e. with overlapping executions. -Although their names suggest otherwise, they are just vehicles to configure the scheduler -and need not correspond to real resources present in the hardware. -This also implies that they can be easily reused for other platforms. - -For a full example of this section, including an extensive description of the various resource types, -please refer to :ref:`cclplatform`. -For a description of their use by the scheduler, please refer to :ref:`scheduling`. - -Section instructions -^^^^^^^^^^^^^^^^^^^^ - -Specifies the list of primitive gates supported by the platform. -Creation of a primitive custom gate takes its parameters from this specification to initialize the gate's attributes. - -Examples of a 1-qubit and a 2-qubit instruction are shown below: - -.. code-block:: html - :linenos: - - "instructions": { - "x q0": { - "duration": 40, - "latency": 0, - "qubits": ["q0"], - "matrix": [ [0.0,0.0], [1.0,0.0], - [1.0,0.0], [0.0,0.0] - ], - "disable_optimization": false, - "type": "mw" - }, - "cnot q2,q0": { - "duration": 80, - "latency": 0, - "qubits": ["q2","q0"], - "matrix": [ [0.1,0.0], [0.0,0.0], [0.0,0.0], [0.0,0.0], - [0.0,0.0], [1.0,0.0], [0.0,0.0], [0.0,0.0], - [0.0,0.0], [0.0,0.0], [0.0,0.0], [1.0,0.0], - [0.0,0.0], [0.0,0.0], [1.0,0.0], [0.0,0.0], - ], - "disable_optimization": true, - "type": "flux" - }, - ... - } - -``x q0`` is the name of the instruction which will be used to refer to this -instruction inside the OpenQL program. ``x`` would also be allowed as name. The former defines a ``specialized gate``, the latter defines a ``generalized gate``; please refer to :ref:`quantum_gates` for the definitions of these terms and to :ref:`input_external_representation` for the use of these two forms of instruction definitions. - -* ``duration`` specifies the time duration required to complete this instruction. - -* ``latency``; due to control electronics, it - is sometimes required to add a positive or negative latency to an instruction. - This can be specified by the ``latency`` field. This field is divided by cycle - time and rounded up to obtain an integer number of cycles. After scheduling is - performed, an instruction is shifted back or forth in time depending upon - the calculated cycles corresponding to the latency field. - -* ``qubits`` refer to the list of qubit operands. - - :Note: This field has to match the operands in the name of the instruction, if specified there. This is checked. Otherwise there is no use of this field. So there is redundancy here. - -* ``matrix`` specifies the process matrix representing this instruction. - If optimization is enabled, this matrix will be used by the optimizer to fuse - operations together, as discussed in :ref:`optimization`. This can be left - un-specified if optimization is disabled. - -* ``disable_optimization`` is used to enable/disable optimization of this - instruction. Setting ``disable_optimization`` to ``true`` will mean that this - instruction cannot be compiled away during optimization. - - :Note: This is not implemented. Propose to do so. Then have to define what is exactly means: compiling away is interpreted as the gate with this flag ``true`` will never be deleted from a circuit once created, nor that the circuit that contains it will be deleted. - -* ``type`` indicates whether the instruction is a microwave (``mw``), flux (``flux``) or readout (``readout``). - This is used in CC-Light by the resource manager to select the resources of a gate for scheduling. - - - -Section gate_decomposition -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Specifies a list of gates defined by decomposition into primitive gates. - -Examples of two decompositions are shown below. -``%0`` and ``%1`` refer to the first argument and the second argument. This means -according to the decomposition on Line 2, ``rx180 %0`` will allow us to -decompose ``rx180 q0`` to ``x q0``. Similarly, the decomposition on Line 3 will -allow us to decompose ``cnot q2, q0`` to three instructions, namely: -``ry90 q0``, ``cz q2, q0`` and ``ry90 q0``. - -.. code-block:: html - :linenos: - - "gate_decomposition": { - "rx180 %0" : ["x %0"], - "cnot %0,%1" : ["ry90 %1","cz %0,%1","ry90 %1"] - } - -These decompositions are simple macros (in-place substitutions) which allow -programmer to manually specify a decomposition. These take place at the time -of creation of a gate in a kernel. This means the scheduler will schedule decomposed -instructions. OpenQL can also perform Control and Unitary decompositions which -are discussed in :ref:`decomposition`. - - -.. include:: platform_qx.rst -.. include:: platform_quantumsim.rst -.. include:: platform_ccl.rst -.. include:: platform_cc.rst -.. include:: platform_cbox.rst diff --git a/docs/platform_cbox.rst b/docs/platform_cbox.rst deleted file mode 100644 index 9f4f48ecc..000000000 --- a/docs/platform_cbox.rst +++ /dev/null @@ -1,7 +0,0 @@ -.. _cboxplatform: - -CBox Platform -------------- - -Details of configuration file for CBox hardware platform. [TBD] - diff --git a/docs/platform_cc.rst b/docs/platform_cc.rst deleted file mode 100644 index d5ad3151b..000000000 --- a/docs/platform_cc.rst +++ /dev/null @@ -1,545 +0,0 @@ -.. _ccplatform: - -Central Controller Platform Configuration ------------------------------------------ - - -CC configuration file -^^^^^^^^^^^^^^^^^^^^^ - -.. FIXME: improve readability - -This section describes the JSON configuration file format for OpenQL in conjunction -with the Central Controller (CC) backend. Note that for the CC - contrary to the CC-light - the final hardware output is entirely *determined* by the contents of the configuration file, there is no built-in knowledge of instrument connectivity or codeword organization. - -The CC configuration file consists of several sections described below. - -To select the CC backend, the following is required: - -.. code:: - - "eqasm_compiler" : "eqasm_backend_cc", - -``resources`` unused for the CC backend, section may be empty - -``topology`` unused for the CC backend, section may be empty - -``alias`` unused by OpenQL - -``hardware_settings`` is used to configure various -hardware settings of the platform as shown below. These settings affect the -scheduling of instructions. Please refer to :ref:`platform` for a full description and an example. - -The following settings are not used by the CC backend: - -* hardware_settings/mw_mw_buffer -* hardware_settings/mw_flux_buffer -* hardware_settings/mw_readout_buffer -* hardware_settings/flux_mw_buffer -* hardware_settings/flux_flux_buffer -* hardware_settings/flux_readout_buffer -* hardware_settings/readout_mw_buffer -* hardware_settings/readout_flux_buffer -* hardware_settings/readout_readout_buffer - -All settings related to the CC backend are in section ``hardware_settings/eqasm_backend_cc`` of the configuration file. This section is divided into several subsections as shown below. - -Instrument definitions -********************** - -Subsection ``instrument_definitions`` defines immutable properties of instruments, i.e. independent of the actual control setup: - -.. code-block:: none - :linenos: - - "instrument_definitions": { - "qutech-qwg": { - "channels": 4, - "control_group_sizes": [1, 4], - }, - "zi-hdawg": { - "channels": 8, - "control_group_sizes": [1, 2, 4, 8], // NB: size=1 needs special treatment of waveforms because one AWG unit drives 2 channels - }, - "qutech-vsm": { - "channels": 32, - "control_group_sizes": [1], - }, - "zi-uhfqa": { - "channels": 9, - "control_group_sizes": [1], - } - }, // instrument_definitions - -Where: - -* ``channels`` defines the number of logical channels of the instrument. For most instruments there is one logical channel per physical channel, but the 'zi-uhfqa' provides 9 logical channels on one physical channel pair. -* ``control_group_sizes`` states possible arrangements of channels operating as a vector - -.. FIXME: add example -.. // * ``latency`` latency from trigger to output in [ns]. FIXME: deprecated -.. FIXME: describe concept of 'group' - - -Control modes -************* - -Subsection ``control_modes`` defines modes to control instruments. These define which bits are used to control groups of channels and/or get back measurement results: - -.. code-block:: JSON - :linenos: - - "control_modes": { - "awg8-mw-vsm-hack": { // ZI_HDAWG8.py::cfg_codeword_protocol() == 'microwave'. Old hack to skip DIO[8] - "control_bits": [ - [7,6,5,4,3,2,1,0], // group 0 - [16,15,14,13,12,11,10,9] // group 1 - ], - "trigger_bits": [31] - }, - "awg8-mw-vsm": { // the way the mode above should have been - "control_bits": [ - [7,6,5,4,3,2,1,0], // group 0 - [15,14,13,12,11,10,9,8] // group 1 - ], - "trigger_bits": [31] - }, - "awg8-mw-direct-iq": { // just I&Q to generate microwave without VSM. HDAWG8: "new_novsm_microwave" - "control_bits": [ - [6,5,4,3,2,1,0], // group 0 - [13,12,11,10,9,8,7], // group 1 - [22,21,20,19,18,17,16], // group 2. NB: starts at bit 16 so twin-QWG can also support it - [29,28,27,26,25,24,23] // group 4 - ], - "trigger_bits": [15,31] - }, - "awg8-flux": { // ZI_HDAWG8.py::cfg_codeword_protocol() == 'flux' - // NB: please note that internally one AWG unit handles 2 channels, which requires special handling of the waveforms - "control_bits": [ - [2,1,0], // group 0 - [5,4,3], - [8,7,6], - [11,10,9], - [18,17,16], // group 4. NB: starts at bit 16 so twin-QWG can also support it - [21,20,19], - [24,23,22], - [27,26,25] // group 7 - ], - "trigger_bits": [31] - }, - "awg8-flux-vector-8": { // single code word for 8 flux channels. - "control_bits": [ - [7,6,5,4,3,2,1,0] - ], - "trigger_bits": [31] - }, - "uhfqa-9ch": { - "control_bits": [[17],[18],[19],[20],[21],[22],[23],[24],[25]], // group[0:8] - "trigger_bits": [16], - "result_bits": [[1],[2],[3],[4],[5],[6],[7],[8],[9]], // group[0:8] - "data_valid_bits": [0] - }, - "vsm-32ch":{ - "control_bits": [ - [0],[1],[2],[3],[4],[5],[6],[7], // group[0:7] - [8],[9],[10],[11],[12],[13],[14],[15], // group[8:15] - [16],[17],[18],[19],[20],[21],[22],[23], // group[16:23] - [24],[25],[26],[27],[28],[28],[30],[31] // group[24:31] - ], - "trigger_bits": [] // no trigger - } - }, // control_modes - -Where: - -* ```` is a name which can be referred to from key ``instruments/[]/ref_control_mode`` -* ``control_bits`` defines G groups of B bits, with: - - - G determines which the ``instrument_definitions//control_group_sizes`` used - - B is an ordered list of bits (MSB to LSB) used for the code word -* ``trigger_bits`` vector of bits used to trigger the instrument. Must either be size 1 (common trigger) or size G (separate trigger per group), or 2 (common trigger duplicated on 2 bits, to support dual-QWG) -* ``result_bits`` reserved for future use -* ``data_valid_bits`` reserved for future use - - -Signals -******* - -Subsection ``signals`` provides a signal library that gate definitions can refer to: - -.. code-block:: JSON - :linenos: - - "signals": { - "single-qubit-mw": [ - { "type": "mw", - "operand_idx": 0, - "value": [ - "{gateName}-{instrumentName}:{instrumentGroup}-gi", - "{gateName}-{instrumentName}:{instrumentGroup}-gq", - "{gateName}-{instrumentName}:{instrumentGroup}-di", - "{gateName}-{instrumentName}:{instrumentGroup}-dq" - ] - }, - { "type": "switch", - "operand_idx": 0, - "value": ["dummy"] // NB: no actual signal is generated - } - ], - "two-qubit-flux": [ - { "type": "flux", - "operand_idx": 0, // control - "value": ["flux-0-{qubit}"] - }, - { "type": "flux", - "operand_idx": 1, // target - "value": ["flux-1-{qubit}"] - } - ] - }, // signals - -Where: - -* ```` is a name which can be referred to from key ``instructions/<>/cc/ref_signal``. It defines an array of records with the fields below: - - * ``type`` defines a signal type. This is used to select an instrument that provides that signal type through key ``instruments/*/signal_type``. The types are entirely user defined, there is no builtin notion of their meaning. - * ``operand_idx`` states the operand index of the instruction/gate this signal refers to. Signals must be defined for all operand_idx the gate refers to, so a 3-qubit gate needs to define 0 through 2. Several signals with the same operand_idx can be defined to select several signal types, as shown in "single-qubit-mw" which has both "mw" (provided by an AWG) and "switch" (provided by a VSM) - * ``value`` defines a vector of signal names. Supports the following macro expansions: - - * {gateName} - * {instrumentName} - * {instrumentGroup} - * {qubit} - -.. FIXME: - rewrite field 'operand_idx - describe the (future) use of field 'value' - expand - - -Instruments -*********** - -Subsection ``instruments`` defines instruments used in this setup, their configuration and connectivity. - -.. code-block:: JSON - :linenos: - - "instruments": [ - // readout. - { - "name": "ro_0", - "qubits": [[6], [11], [], [], [], [], [], [], []], - "signal_type": "measure", - "ref_instrument_definition": "zi-uhfqa", - "ref_control_mode": "uhfqa-9ch", - "controller": { - "name": "cc", - "slot": 0, - "io_module": "CC-CONN-DIO" - } - }, - // ... - - // microwave. - { - "name": "mw_0", - "qubits": [ // data qubits: - [2, 8, 14], // [freq L] - [1, 4, 6, 10, 12, 15] // [freq H] - ], - "signal_type": "mw", - "ref_instrument_definition": "zi-hdawg", - "ref_control_mode": "awg8-mw-vsm-hack", - "controller": { - "name": "cc", - "slot": 3, - "io_module": "CC-CONN-DIO-DIFF" - } - }, - // ... - - // VSM - { - "name": "vsm_0", - "qubits": [ - [2], [8], [14], [], [], [], [], [], // [freq L] - [1], [4], [6], [10], [12], [15], [], [], // [freq H] - [0], [5], [9], [13], [], [], [], [], // [freq Mg] - [3], [7], [11], [16], [], [], [], [] // [freq My] - ], - "signal_type": "switch", - "ref_instrument_definition": "qutech-vsm", - "ref_control_mode": "vsm-32ch", - "controller": { - "name": "cc", - "slot": 5, - "io_module": "cc-conn-vsm" - } - }, - - // flux - { - "name": "flux_0", - "qubits": [[0], [1], [2], [3], [4], [5], [6], [7]], - "signal_type": "flux", - "ref_instrument_definition": "zi-hdawg", - "ref_control_mode": "awg8-flux", - "controller": { - "name": "cc", - "slot": 6, - "io_module": "CC-CONN-DIO-DIFF" - } - }, - // ... - ] // instruments - -Where: - -* ``name`` a friendly name for the instrument -* ``ref_instrument_definition`` selects record under ``instrument_definitions``, which must exist or an error is raised -* ``ref_control_mode`` selects record under ``control_modes``, which must exist or an error is raised -* ``signal_type`` defines which signal type this instrument instance provides. -* ``qubits`` G groups of 1 or more qubits. G must match one of the available group sizes of ``instrument_definitions//control_group_sizes``. If more than 1 qubits are stated per group - e.g. for an AWG used in conjunction with a VSM - they may not produce conflicting signals at any time slot, or an error is raised -* ``force_cond_gates_on`` optional, reserved for future use -* ``controller/slot`` the slot number of the CC this instrument is connected to -* ``controller/name`` reserved for future use -* ``controller/io_module`` reserved for future use - -.. FIXME: describe matching process of 'signal_type' against 'signals/*/type' - -Additions to section 'instructions' -*********************************** - -The CC backend extends section ``instructions/`` with a subsection ``cc`` as shown in the example below: - -.. code-block:: JSON - :linenos: - - "ry180": { - "duration": 20, - "matrix": [ [0.0,1.0], [1.0,0.0], [1.0,0.0], [0.0,0.0] ], - "cc": { - "ref_signal": "single-qubit-mw", - "static_codeword_override": [2] - } - }, - "cz_park": { - "duration": 40, - "matrix": [ [0.0,1.0], [1.0,0.0], [1.0,0.0], [0.0,0.0] ], - "cc": { - "signal": [ - { "type": "flux", - "operand_idx": 0, // control - "value": ["flux-0-{qubit}"] - }, - { "type": "flux", - "operand_idx": 1, // target - "value": ["flux-1-{qubit}"] - }, - { "type": "flux", - "operand_idx": 2, // park - "value": ["park_cz-{qubit}"] - } - ], - "static_codeword_override": [1,2,3] - } - } - "_wait_uhfqa": { - "duration": 220, - "matrix": [ [0.0,1.0], [1.0,0.0], [1.0,0.0], [0.0,0.0] ], - "cc": { - "signal": [] - } - }, - "_dist_dsm": { - "duration": 20, - "matrix": [ [0.0,1.0], [1.0,0.0], [1.0,0.0], [0.0,0.0] ], - "cc": { - "readout_mode": "feedback", - "signal": [ - { "type": "measure", - "operand_idx": 0, - "value": [] - } - ] - } - }, - "_wait_dsm": { - "duration": 80, - "matrix": [ [0.0,1.0], [1.0,0.0], [1.0,0.0], [0.0,0.0] ], - "cc": { - "signal": [] - } - }, - "if_1_break": { - "duration": 60, - "matrix": [ [0.0,1.0], [1.0,0.0], [1.0,0.0], [0.0,0.0] ], - "cc": { - "signal": [], - "pragma": { - "break": 1 - } - } - } - -Where: - -* ``cc/ref_signal`` points to a signal definition in ``hardware_settings/eqasm_backend_cc/signals``, which must exist or an error is raised -* ``cc/signal`` defines a signal in place, in an identical fashion as ``hardware_settings/eqasm_backend_cc/signals``. May be empty (``[]``) to disable signal generation. -* ``cc/static_codeword_override`` provides a user defined array of codeword (one entry per operand) for this instruction. Currently, this key is compulsory (if signal is non-empty), but in the future, codewords will be assigned automatically to make better use of limited codeword space -* ``cc/readout_mode`` defines an instruction to perform readout if non-empty. If the value "feedback" is used, code is generated to read and distribute the instrument result. -* ``cc/pragma/break`` enables special functionality which makes the gate break out of a for loop if the associated qubit was measured as 1 (``"pragma" { "break": 1 }``) or 0 (``"pragma" { "break": 0 }`` - -The following standard OpenQL fields are used: - -* ```` name for the instruction. The following syntaxes can be used for instruction names: - - - "" - - "" -* ``duration`` duration in [ns] -* ``matrix`` the process matrix. Required, but only used if optimization is enabled -* ``latency`` optional instruction latency in [ns], used by scheduler -* ``qubits`` optional - -.. FIXME: special treatment of names by scheduler/backend - - "readout" : backend - - "measure" - -The following fields in 'instructions' are not used by the CC backend: - -* ``type`` -* ``cc_light_instr`` -* ``cc_light_instr_type`` -* ``cc_light_cond`` -* ``cc_light_opcode`` -* ``cc_light_codeword`` -* ``cc_light_left_codeword`` -* ``cc_light_right_codeword`` -* ``disable_optimization`` not implemented in OpenQL - -Program flow feedback -************************************************ - -To support Repeat Until Success type experiments, two special fields were added to the gate definition for the CC, as -shown in the previous section: - -- the `"readout_mode": "feedback"` clause in the `"_dist_dsm"` gate causes the backend to generate code to retrieve the - measurement result from the DIO interface and distribute it across the CC; -- the `"pragma": { "break": 1 }` clause in the `"if_1_break"` gate causes the backend to generate code to break out - of a OpenQL loop if the associated qubit is read as 1 (or similarly if 0). - -For convenience, the gate decomposition section can be extended with -`"measure_fb %0": ["measure %0", "_wait_uhfqa %0", "_dist_dsm %0", "_wait_dsm %0"]` - -This creates a `measure_fb` instruction consisting of four parts: -- triggering a measurement (on the UHFQA) -- waiting for the internal processing time of the UHFQA -- retrieve the measurement result, and distribute it across the CC -- wait fot the data distribution to finish - -The following example code contains a real RUS experiment using PycQED: - -.. code-block:: Python - - from pycqed.measurement.openql_experiments import openql_helpers as oqh - for i, angle in enumerate(angles): - oqh.ql.set_option('output_dir', 'd:\\githubrepos\\pycqed_py3\\pycqed\\measurement\\openql_experiments\\output') - p = oqh.create_program('feedback_{}'.format(angle), config_fn) - k = oqh.create_kernel("initialize_block_{}".format(angle), p) - - # Initialize - k.prepz(qidx) - - # Block do once (prepare |1>) - k.gate("rx180", [qidx]) - p.add_kernel(k) - - # Begin conditional block - q = oqh.create_kernel("conditional_block_{}".format(angle), p) - # Repeat until success 0 - q.gate("measure_fb", [qidx]) - q.gate("if_0_break", [qidx]) - - # Correction for result 1 - q.gate("rx180", [qidx]) - p.add_for(q, 1000000) - - # Block finalize - r = oqh.create_kernel("finalize_block_{}".format(angle), p) - cw_idx = angle // 20 + 9 - r.gate('cw_{:02}'.format(cw_idx), [qidx]) - - # Final measurement - r.gate("measure_fb", [qidx]) - p.add_kernel(r) - - oqh.compile(p, extra_openql_options=[('backend_cc_run_once', 'yes')]) - -Caveats: - -- it is not possible to mix `measure_fb` and `measure` in a single program. This is a consequence of the way measurements - are read from the input DIO interface of the CC: every measurement (both from `measure_fb` and `measure`) is pushed - onto an input FIFO. This FIFO is only popped by a `measure_fb` instruction. If the two types are mixed, misalignment - occurs between what is written and read. No check is currently performed by the backend. -- `break` statements may only occur inside a `for` loop. No check is currently performed by the backend. -- `break` statements implicitly refer to the last `measure_fb` earlier in code as a result of implicit allocation of - variables. - -These limitations will vanish when integration with cQASM 2.0 is completed. - - -.. FIXME: - add conditional gates - - -.. FIXME: TBW - Converting quantum gates to instrument codewords - ******************************************************* - - -Compiler options -^^^^^^^^^^^^^^^^ - -The following OpenQL compiler options are specific for the CC backend: - -* ``backend_cc_run_once`` create a .vq1asm program that runs once instead of repeating indefinitely (default "no" to maintain compatibility, alternatively "yes") -* ``backend_cc_verbose`` add verbose comments to generated .vq1asm file (default "yes", alternatively "no") -* ``backend_cc_map_input_file`` name of CC input map file, default "". Reserved for future extension to generate codewords automatically - -FIXME: refer to standard options - - -CC backend output files -^^^^^^^^^^^^^^^^^^^^^^^ - -.vq1asm: 'Vectored Q1 assembly' file for the Central Controller - -.vcd: timing file, can be viewed using GTKWave (http://gtkwave.sourceforge.net) - -Standard OpenQL features -^^^^^^^^^^^^^^^^^^^^^^^^ - -FIXME: just refer to relevant section. Kept here until we're sure this has been absorbed elsewhere - - -Parametrized gate-decomposition -******************************* - -Parametrized gate decompositions can be specified in gate_decomposition section, as shown below: - - "rx180 %0" : ["x %0"] - -Based on this, k.gate('rx180', 3) will be decomposed to x(q3). Similarly, multi-qubit gate-decompositions can be -specified as: - - "cnot %0,%1" : ["ry90 %0", "cz %0,%1", "ry90 %1"] - - -Specialized gate-decomposition -****************************** - -Specialized gate decompositions can be specified in gate_decomposition section, as shown below: - - "rx180 q0" : ["x q0"] - "cz_park q0,q1" : ["cz q0,q1", "park q3"] diff --git a/docs/platform_ccl.rst b/docs/platform_ccl.rst deleted file mode 100755 index 146b8cda1..000000000 --- a/docs/platform_ccl.rst +++ /dev/null @@ -1,389 +0,0 @@ -.. _cclplatform: - -CC-Light Platform ------------------ - -The file `hardware_config_cc_light.json -`_ -available inside the ``tests`` directory is an example configuration file for -the CC-Light platform with 7 qubits. - -This file consists of several sections (in arbitrary order) which are described below. - -``eqasm_compiler`` specifies the backend compiler to be used for this CC-Light platform, -which in this case has the name ``cc_light_compiler``. -The backend compiler is called after the platform independent passes, and calls several private passes by itself. -This backend compiler and its passes are described in detail in :ref:`compiler_passes`. -One of these is the code generation pass. - -.. code:: - - "eqasm_compiler" : "cc_light_compiler", - -``hardware_settings`` is used to configure various -hardware settings of the platform as shown below. These settings affect the -scheduling of instructions. Please refer to :ref:`platform` for a full description and an example. - -``topology`` specifies the mapping of qubit indices to qubit positions in the platform, as well as the mapping of connection indices to connections in the platform. -A connection is a directed connection in the platform between a pair of qubits that supports qubit interaction. -It is directed to distinguish the control and target qubits of two-qubit gates. -In a platform topology's connection graph, qubits are the nodes, and connection are the edges. - -Figure :numref:`fig_qubit_numbering_ccl` shows these numberings in the 7 qubit CC-Light platform. - -.. _fig_qubit_numbering_ccl: - -.. figure:: ./qubit_number.png - :width: 800px - :align: center - :alt: Connection graph with qubit and connection (edge) numbering in the 7 qubits CC-Light Platform - :figclass: align-center - - Connection graph with qubit and connection (edge) numbering in the 7 qubits CC-Light Platform - - -The ``topology`` section starts with -the specification of the two dimensions of a rectangular qubit grid by specifying ``x_size`` and ``y_size``. -The positions of the real qubits of the platform are defined relative to this (artificial) grid. -The coordinates in the X direction are 0 to x_size-1. -In the Y direction they are 0 to y_size-1. -Next, for each available qubit in the platform, its position in the grid is specified: -the ``id`` specifies the particular qubit's index, and ``x`` and ``y`` specify its position in the grid, -as coordinates in the X and Y direction, respectively. -Please note that not every position in the x_size by y_size grid needs to correspond to a qubit. - -Qubits are connected in directed pairs, called edges. -Edge indices form a contigous range starting from 0. -Each edge in the topology is given an ``id`` which denotes its index, and a source (control) and destination (target) qubit index by ``src`` and ``dst``, respectively. This means that although Edge 0 and Edge 8 are -between qubit 0 and qubit 2, they are different as these edges are in opposite directions. -The qubit indices specified here must correspond to available qubits in the platform. - -.. code-block:: html - :linenos: - - "topology" : { - "x_size": 5, - "y_size": 3, - "qubits": - [ - { "id": 0, "x": 1, "y": 2 }, - { "id": 1, "x": 3, "y": 2 }, - { "id": 2, "x": 0, "y": 1 }, - { "id": 3, "x": 2, "y": 1 }, - { "id": 4, "x": 4, "y": 1 }, - { "id": 5, "x": 1, "y": 0 }, - { "id": 6, "x": 3, "y": 0 } - ], - "edges": - [ - { "id": 0, "src": 2, "dst": 0 }, - { "id": 1, "src": 0, "dst": 3 }, - { "id": 2, "src": 3, "dst": 1 }, - { "id": 3, "src": 1, "dst": 4 }, - { "id": 4, "src": 2, "dst": 5 }, - { "id": 5, "src": 5, "dst": 3 }, - { "id": 6, "src": 3, "dst": 6 }, - { "id": 7, "src": 6, "dst": 4 }, - { "id": 8, "src": 0, "dst": 2 }, - { "id": 9, "src": 3, "dst": 0 }, - { "id": 10, "src": 1, "dst": 3 }, - { "id": 11, "src": 4, "dst": 1 }, - { "id": 12, "src": 5, "dst": 2 }, - { "id": 13, "src": 3, "dst": 5 }, - { "id": 14, "src": 6, "dst": 3 }, - { "id": 15, "src": 4, "dst": 6 } - ] - }, - - -These mappings are used in: - -* the QISA, the instruction set of the platform, notably in the instructions that set the masks stored in the mask registers that are used in the instructions of two-qubit gates to address the operands. -* the mapper pass that maps virtual qubit indices to real qubit indices. It is described in detail in :ref:`mapping`. -* the postdecomposition pass that maps two-qubit flux instructions to sets of one-qubit flux instructions. - - -``resources`` is the section that is used to specify/configure various resource types available -in the platform as discussed below. Specification of these resource types affects -scheduling and mapping of gates. The configuration of the various resource types -in `hardware_config_cc_light.json -`_ -assumes that the CC-Light architecture has the following relations between devices, connections, qubits and operations: - -.. _table_ccl_connections: - -.. table:: - :align: center - - ===================== ============= ============= =================== - Device Name DIO connector Target qubits Operation Type - ===================== ============= ============= =================== - UHFQC-0 DIO1 0, 2, 3, 5, 6 measurement - UHFQC-1 DIO2 1, 4 measurement - AWG-8 0, channel 0~6 DIO3 0~6 flux - AWG-8 1, channel 0 DIO4 0,1 microwave - AWG-8 1, channel 1 DIO4 5,6 microwave - AWG-8 2, channel 0 DIO5 2,3,4 microwave - VSM -- 0~6 microwave masking - ===================== ============= ============= =================== - -The ``resources`` section specifies zero or more resource types. -Each of these must be predefined by the platform's resource manager. -For CC-Light, these resource types are ``qubits``, ``qwgs``, ``meas_units``, ``edges`` and ``detuned_qubits``. -The presence of one in the configuration file -indicates that the resource-constrained scheduler should take it into account -when trying to schedule operations in parallel, i.e. with overlapping executions; -absence of one in the configuration file thus indicates that this resource is ignored by the scheduler. -Although their names suggest otherwise, they are just vehicles to configure the scheduler -and need not correspond to real resources present in the hardware. - -``qubits``: That one qubit can only be involved in one operation at each particular cycle, -is specified by the ``qubits`` resource type, as shown -below. ``count`` needs to be at least the number of available qubits. - -.. code-block:: html - :linenos: - - "qubits": - { - "count": 7 - }, - -So, when this resource type is included in the configuration in this way, -it will guarantee that the resource-constrained scheduler will never schedule two operations in parallel -when these share a qubit index in the range of 0 to count-1 as operand. - -``qwgs``: This resource type specifies, when configured, several sets of qubit indices. -For each set it specifies that when one of the qubits in the set is in use in a particular cycle -by an instruction of 'mw' type (single-qubit rotation gates usually), -that when one of the other qubits in the set is in use by an instruction of 'mw' type, -that instruction must be doing the same operation. -In CC-light, this models QWG wave generators that only can generate one type of wave at the same time, -and in which each wave generator is connected through a switch to a subset of the qubits. - -.. code-block:: html - :linenos: - - "qwgs" : - { - "count": 3, - "connection_map": - { - "0" : [0, 1], - "1" : [2, 3, 4], - "2" : [5, 6] - } - }, - -The number of sets (waveform generators) is specified by the ``count`` field. In -the ``connection_map`` it is specified which waveform generator is connected to which qubits. -Each qubit that can be used by an instruction of 'mw' type, -should be specified at most once in the combination of sets of connected qubits. -For instance, the line with ``"0"`` specifies that ``qwg 0`` is connected to -qubits 0 and 1. This is based on the ``AWG-8 1, channel 0`` entry in -Table :numref:`table_ccl_connections`. This information is utilized by the -scheduler to perform resource-constraint aware scheduling of gates. - -``meas_units``: This resource type is similar to ``qwgs``; the difference is -that it is not constraining on the operations to be equal -but on the start cycle of measurement to be equal. -It specifies, when configured, several sets of qubit indices. -For each set it specifies that when one of the qubits in the set is in use in a particular cycle -by an instruction of 'readout' type (measurement gates usually) -that when one of the other qubits in the set is in use by an instruction of 'readout' type -the latter must also have started in that cycle. -In CC-light, this models measurement units that each can only measure multiple qubits at the same time -when the measurements of those qubits start in the same cycle. - -There are ``count`` number of sets (measurement units). For each -measurement unit it is described which set of qubits it controls. -Each qubit that can be used by an instruction of 'readout' type, -should be specified at most once in the combination of sets of connected qubits. - -.. code-block:: html - :linenos: - - "meas_units" : - { - "count": 2, - "connection_map": - { - "0" : [0, 2, 3, 5, 6], - "1" : [1, 4] - } - }, - - -``edges``: This resource type specifies, when present, for each directed qubit pair corresponding -to a directed connection in the platform (``edge``), which set of other edges -cannot execute a two-qubit gate in parallel. - -Two-qubit flux gates (instructions of ``flux`` type) are controlled by -qubit-selective frequency detuning. Frequency-detuning may cause neighbor -qubits (qubits connected by an edge) to inadvertently engage in a two-qubit flux -gate as well. This happens when two connected qubits are both executing a -two-qubit flux gate. Therefore, for each edge executing a two-qubit gate, -certain other edges should not execute a two-qubit gate. - -Edges and the constraints imposed by these edges are specified in the ``edges`` section. -``count`` specifies at least the number of edges between qubits in the platform. -``connection_map`` specifies connections. -For example, the entry with "0" specifies for Edge 0 a constraint on Edge 2 and Edge 10. -This means, if Edge 0 is in use by a two-qubit flux gate, -a two-qubit flux gate on Edge 2 and Edge 10 will not be scheduled, until the one on Edge 0 completes. - -When ``edges`` is present as a resource type, each edge of the platform must appear in the ``connection_map``. -Providing an empty list for an edge in the ``connection_map`` will result -in not applying any edge constraint during scheduling. - -.. code-block:: html - :linenos: - - "edges": - { - "count": 16, - "connection_map": - { - "0": [2, 10], - "1": [3, 11], - "2": [0, 8], - "3": [1, 9], - "4": [6, 14], - "5": [7, 15], - "6": [4, 12], - "7": [5, 13], - "8": [2, 10], - "9": [3, 11], - "10": [0, 8], - "11": [1, 9], - "12": [6, 14], - "13": [7, 15], - "14": [4, 12], - "15": [5, 13] - } - }, - - -``detuned_qubits``: Constraints on executing two-qubit gates in parallel to other gates, -are specified in this ``detuned_qubits`` section, when present. -For each edge, the set of qubits is specified that cannot execute a gate -when on the particular edge a two-qubit gate is executed; -at the same time, this resource type specifies implicitly for each qubit -when it would be executing a gate, on which edges a two-qubit gate cannot execute in parallel. - -There are at least ``count`` number of qubits involved. -When ``detuned_qubits`` is present as a resource type, -each edge of the platform must appear in the ``connection_map``. -Providing an empty set of qubits for an edge in the ``connection_map`` will result -in not applying the ``detuned_qubits`` constraint related to this edge during scheduling. -Not all qubits need to be involved in this type of constraint with some edge. -In the example below, Qubit 0 and Qubit 1 are examples of qubits executing a gate on which -can be in parallel to executing a two-qubit gate on any pair of qubits. - -A two-qubit flux gate lowers the frequency of its source qubit to get near the frequency of -its target qubit. Any two qubits which have near frequencies execute a -two-qubit flux gate. To prevent any neighbor qubit of the source qubit that has -the same frequency as the target qubit to interact as well, those neighbors must -have their frequency detuned (lowered out of the way). A detuned qubit cannot -execute a single-qubit rotation (an instruction of 'mw' type). - -.. code-block:: html - :linenos: - - "detuned_qubits": - { - "count": 7, - "connection_map": - { - "0": [3], - "1": [2], - "2": [4], - "3": [3], - "4": [], - "5": [6], - "6": [5], - "7": [], - "8": [3], - "9": [2], - "10": [4], - "11": [3], - "12": [], - "13": [6], - "14": [5], - "15": [] - } - } - - -``instructions``: Instructions can be specified/configured in the ``instructions`` section. -Examples of a 1-qubit and a 2-qubit instruction are shown below: - -.. code-block:: html - :linenos: - - "instructions": { - "x q0": { - "duration": 40, - "latency": 0, - "qubits": ["q0"], - "matrix": [ [0.0,0.0], [1.0,0.0], - [1.0,0.0], [0.0,0.0] - ], - "disable_optimization": false, - "type": "mw", - "cc_light_instr_type": "single_qubit_gate", - "cc_light_instr": "x", - "cc_light_codeword": 60, - "cc_light_opcode": 6 - }, - "cnot q2,q0": { - "duration": 80, - "latency": 0, - "qubits": ["q2","q0"], - "matrix": [ [0.1,0.0], [0.0,0.0], [0.0,0.0], [0.0,0.0], - [0.0,0.0], [1.0,0.0], [0.0,0.0], [0.0,0.0], - [0.0,0.0], [0.0,0.0], [0.0,0.0], [1.0,0.0], - [0.0,0.0], [0.0,0.0], [1.0,0.0], [0.0,0.0], - ], - "disable_optimization": true, - "type": "flux", - "cc_light_instr_type": "two_qubit_gate", - "cc_light_instr": "cnot", - "cc_light_right_codeword": 127, - "cc_light_left_codeword": 135, - "cc_light_opcode": 128 - }, - ... - } - -Please refer to :ref:`platform` for a description of the CC-Light independent attributes. -The CC-Light dependent attributes are: - -``cc_light_instr_type`` is used to -specify the type of instruction based on the number of expected qubits. -Please refer to :ref:`scheduling` for its use by the rcscheduler. - -``cc_light_instr`` specifies the name of this instruction used in CC-Light architecture. This name -is used in the generation of the output code and in the implementation of the checking of the ``qwg`` resource. -Please refer to :ref:`scheduling` for its use by the rcscheduler. - -``cc_light_codeword``, ``cc_light_right_codeword``, ``cc_light_left_codeword`` -and ``cc_light_opcode`` are used in the generation of the control store file for -CC-Light platform. For single qubit instructions, ``cc_light_codeword`` refers -to the codeword to be used for this instruction. Recall that the quantum pipeline -contains a VLIW front end with two VLIW lanes, each lane processing one -quantum operation. ``cc_light_right_codeword`` and ``cc_light_left_codeword`` -are used to specify the codewords used for the left and right operation in -two-qubit instruction. ``cc_light_opcode`` specifies the opcode used for this -instruction. - -.. warning:: - At the moment, generation of the control-store file is disabled in - the compiler as this was not being used in experiments. - - -``gate_decomposition`` Gate decompositions can also be specified in the configuration file in the -``gate_decomposition`` section. Please refer to :ref:`platform` for a description and full example of this section. - - - diff --git a/docs/platform_quantumsim.rst b/docs/platform_quantumsim.rst deleted file mode 100644 index ec818c841..000000000 --- a/docs/platform_quantumsim.rst +++ /dev/null @@ -1,29 +0,0 @@ -.. _qsimplatform: - -Quantumsim Platform -------------------- - -The OpenQL compiler is able to generate a python script to interface to quantumsim. -This script generation is controlled by option *quantumsim*: - -- no: - No script to interface to quantumsim is generated. - -- yes: - A python script is generated to interface with a standard version of quantumsim. - -- qsoverlay: - A python script is generated to interface with the qsoverlay module on top of quantumsim. - -The scripts are generated in the default output directory. - -Unlike in previous releases, quantumsim is not considered a platform. -It is a means to simulate a particular (hardware) platform. - -The quantumsim option is checked in the CC-Light backend in two places: - -- just when entering the backend after *decomposition before scheduling* - -- just before generating QISA, i.e. after *mapping*, *rcscheduling* and *decomposition after scheduling*. - -[TBD] diff --git a/docs/platform_qx.rst b/docs/platform_qx.rst deleted file mode 100644 index 29666dd9a..000000000 --- a/docs/platform_qx.rst +++ /dev/null @@ -1,23 +0,0 @@ -.. _qxplatform: - -QX Platform ----------- - -Details of the configuration file for the QX simulator platform. [TBD] - -The OpenQL compiler is able to generate a qasm file to interface to QX. -This qasm file generation is controlled by option *write_qasm_files*: - -- yes: - Qasm files are generated before and after most of the passes. - -- no: - No qasm files are generated. - -The qasm files are generated in the default output directory. -The qasm file to be used for QX is named by the program name suffixed with .qasm. - -Unlike in previous releases, quantumsim is not considered a platform. -It is a means to simulate a particular (hardware) platform without timing. - -[TBD] diff --git a/docs/qubit_number.png b/docs/qubit_number.png deleted file mode 100644 index 6d12419d6..000000000 Binary files a/docs/qubit_number.png and /dev/null differ diff --git a/docs/qx_example.rst b/docs/qx_example.rst deleted file mode 100644 index 30de9074e..000000000 --- a/docs/qx_example.rst +++ /dev/null @@ -1,149 +0,0 @@ -QX Simulation -============= - -This tutorial explains how to compile an OpenQL program and execute it on QX. We will use the example of rolling an 8-faced dice. Rolling this dice results in 1 out of 8 outcomes. The complete code for this example is available in ``examples/dice.py``. You can also copy the snippits over to your own script as we walk through it. - -.. figure:: figures/dice.png - :width: 400px - :align: center - :alt: 8-faced Dices - :figclass: align-center - - -OpenQL Program --------------- - -We start by importing openql, qxelerator and some python packages. We also set some options for openql. For this example we will be using 3 qubits. All this is done by the following code snippet: - -.. code:: python - - from openql import openql as ql - import qxelarator - from functools import reduce - import os - import matplotlib.pyplot as plt - - - curdir = os.path.dirname(__file__) - output_dir = os.path.join(curdir, 'test_output') - - ql.set_option('output_dir', output_dir) - ql.set_option('write_qasm_files', 'yes') - ql.set_option('scheduler', 'ASAP') - ql.set_option('log_level', 'LOG_INFO') - - nqubits = 3 - - -Next we create a platform, a program and a kernel. We populate the kernel with 3 hadamard gates being applied on each qubits. This will put each qubit in superposition. Measuring each qubit will collapse the state resulting in getting either 0 or 1. This is done by dice_compile() as shown below: - -.. code:: python - - def dice_compile(): - print('compiling 8-face dice program by openql') - config = os.path.join(curdir, '../tests/hardware_config_qx.json') - - platform = ql.Platform("myPlatform", config) - p = ql.Program('dice', platform, nqubits) - k = ql.Kernel('aKernel', platform, nqubits) - - for q in range(nqubits): - k.gate('h', [q]) - - for q in range(nqubits): - k.gate('measure', [q]) - - p.add_kernel(k) - p.compile() - - -Compiling the above code snippet will produce the following quantum assembly code in `cQASM v1.0 `_ format: - -- test_output/dice.qasm which is the generated un-scheduled qasm code -- test_output/dice_scheduled.qasm which is the generated qasm code after scheduling - -For instance, dice.qasm contents are shown below: - -.. parsed-literal:: - - version 1.0 - # this file has been automatically generated by the OpenQL compiler please do not modify it manually. - qubits 3 - - .aKernel - h q[0] - h q[1] - h q[2] - measure q[0] - measure q[1] - measure q[2] - -These cQASM codes can be simulated on `QX simulator `_. For this we are using the simplified python interface to QX known as `QXelarator `_. This is done by the following code snippet: - -.. code:: python - - def dice_execute_singleshot(): - print('executing 8-face dice program on qxelarator') - qx = qxelarator.QX() - - # set the qasm to be executed - qx.set('test_output/dice.qasm') - - # execute the qasm - qx.execute() - - # get the measurement results - res = [int(qx.get_measurement_outcome(q)) for q in range(nqubits)] - - # convert the measurement results from 3 qubits to dice face value - dice_face = reduce(lambda x, y: 2*x+y, res, 0) + 1 - print('Dice face : {}'.format(dice_face)) - - -Running ``dice.py`` will produce output as shown below: - -.. parsed-literal:: - - Dice face : 2 - -where, the Dice face can be any number between 1 and 8. - - -Next we can also roll the dice 100000 times and plot the frequency of occurance of each face by the following code snippet: - -.. code:: python - - def plot_histogram(dice_faces): - plt.hist(dice_faces, bins=8, color='#0504aa',alpha=0.7, rwidth=0.85) - plt.grid(axis='y', alpha=0.75) - plt.xlabel('Dice Face',fontsize=15) - plt.ylabel('Frequency',fontsize=15) - plt.xticks(fontsize=15) - plt.yticks(fontsize=15) - plt.ylabel('Frequency',fontsize=15) - plt.title('Histogram',fontsize=15) - plt.show() - plt.savefig('hist.png') - - def dice_execute_multishot(): - print('executing 8-face dice program on qxelarator') - qx = qxelarator.QX() - qx.set('test_output/dice.qasm') - dice_faces = [] - ntests = 100 - for i in range(ntests): - qx.execute() - res = [int(qx.get_measurement_outcome(q)) for q in range(nqubits)] - dice_face = reduce(lambda x, y: 2*x+y, res, 0) +1 - dice_faces.append(dice_face) - - plot_histogram(dice_faces) - -This will produce the histogram similar to the one shown below: - -.. figure:: figures/dice_hist.png - :width: 600px - :align: center - :alt: Histogram - :figclass: align-center - diff --git a/docs/reference/architectures.rst.template b/docs/reference/architectures.rst.template new file mode 100644 index 000000000..9c8bc4889 --- /dev/null +++ b/docs/reference/architectures.rst.template @@ -0,0 +1,66 @@ +.. _ref_architectures: + +Supported architectures +======================= + +This section lists the backend architectures currently supported by OpenQL. + +Architectures are organized into *families*, *variants*, and *platforms*. The +architecture family typically refers to a particular control architecture, and +may include custom passes and scheduler resources needed to compile for that +control architecture. The variant usually refers to the kind of qubit chip being +controlled by said control architecture, such as surface-5, surface-7, or +surface-17 for CC-light. Finally, architecture variants may be configured with +a JSON configuration file to get a complete target description, referred to as +the platform. + +.. note:: + In older versions of OpenQL, there was no hierarchical definition like this. + Instead, there was only a platform, defined by a hard-to-write JSON + configuration file, with a backend selection using the ``"eqasm_compiler"`` + key in the configuration file. The new system is fully backward-compatible: + you can still just pass any custom JSON configuration file to the platform + constructor, in which case the architecture family will be chosen based on + the value in ``"eqasm_compiler"``, and the default variant for that family + will be used. The purpose of the new system is to make the learning curve + for using OpenQL less steep; instead of having to pluck one of the many JSON + files from OpenQL's ``tests`` directory and usually needing to modify it + manually, you can start off by just using one of the architecture variants as + default. Furthermore, each architecture variant may have its own set of + defaults and preprocessing rules for the platform configuration file, such + that even if you do need to make changes, it should be way easier to do so. + +The active architecture is selected in one of the following ways: + + - by specifying the namespace name/variant pair for the desired architecture + directly to the ``ql.Platform`` constructor (the architecture will then use + the default platform configuration for that pair); + - by specifying a recognized string for the ``"eqasm_compiler"`` key in the + platform configuration file; + - if ``"eqasm_compiler"`` is instead used for an inline compiler + configuration, by setting ``"eqasm_compiler"."architecture"`` to the + namespace name and variant of the desired architecture; + - if none of the above apply, the dummy `"none"` architecture will be + selected. + +The variant is separated from the namespace name or ``eqasm_compiler`` name +using a ``.``\ . + +Ultimately, the architecture variant system only serves to inject sane defaults +into the platform and compiler configuration structures, and to separate +unavoidable architecture-specific logic from architecture-agnostic logic in +OpenQL's codebase. That is, everything boils down to these (internal) platform +and compiler configuration structures during platform construction, after which +everything is agnostic to the selected architecture. This means that if you want +to compile for a new architecture that's sufficiently similar to an existing +one to not need any new passes or resources, you may not even have to change or +add to OpenQL's codebase; you can just use the ``none.default`` architecture and +build the platform and compiler configuration structures from scratch. This is +intentional: the control architectures are still as much in flux as the quantum +chips themselves, so being able to quickly piece together a compiler for an +architecture we haven't even thought of yet is important. It's also very useful +for design-space exploration, and doing research into compilation strategies and +control architectures that will become relevant only when the quantum chips +mature further. + +{architectures} diff --git a/docs/reference/configuration.rst.template b/docs/reference/configuration.rst.template new file mode 100644 index 000000000..3e8e49883 --- /dev/null +++ b/docs/reference/configuration.rst.template @@ -0,0 +1,30 @@ +.. _ref_configuration: + +Configuration +============= + +Configurability is a primary design goal of OpenQL: instead of hardcoding the way in which an algorithm is compiled for a particular platform, both the platform and the strategy for compiling to it are completely configurable. As such, OpenQL has quite a complex configuration system. + +Most of the configuration is provided to OpenQL via JSON files. OpenQL uses a superset of the JSON file format for all input files, namely one that allows ``//``-based single-line comments; therefore, a configuration file written for OpenQL is not strictly valid JSON, but OpenQL can parse any valid JSON file (as long as it complies with the expected structure). + +The two most important configuration file types are the *platform* and *compiler* configuration files. + + - The platform configuration file includes everything OpenQL needs to know about the target platform (i.e., what the quantum chip and microarchitecture looks like), and optionally includes information about how to compile for it. This file is passed to OpenQL when you construct a ``ql.Platform``. OpenQL also has a number of default platform configuration files built into it; one for each architecture variant. Furthermore, architecture variants may include preprocessing logic for the platform configuration file, such that repetitive things for a particular platform can automatically be expanded; in this case, the description below documents the *resulting* structure, not necessarily what you would write (although the preprocessing logic should be minimal, such as only providing additional default values). See also the section on supported architectures. + + - The compiler configuration file describes the steps that OpenQL should take to transform the incoming program to something that can run on the platform (or at least is no further away from being able to run on it). This is also referred to as the pass list or compilation strategy. Besides configuration via JSON, the strategy can also be configured using the Python/C++ API directly; this is particularly useful when you for example only want to insert a visualizer pass into the existing, default pass list, or when you're doing design-space exploration to determine the optimal compilation strategy for a particular algorithm. + +The structure of these files is documented below. + +.. _ref_platform_configuration: + +Platform configuration +---------------------- + +{platform} + +.. _ref_compiler_configuration: + +Compiler configuration +---------------------- + +{compiler} diff --git a/docs/reference/cpp.rst b/docs/reference/cpp.rst new file mode 100644 index 000000000..250de1999 --- /dev/null +++ b/docs/reference/cpp.rst @@ -0,0 +1,22 @@ +.. _ref_cpp: + +C++ API +======= + +If you're more of a C++ than a Python person, the same API exposed to Python can also be used from within C++. + +There is currently no supported way to install OpenQL as a system library. Instead, you can use CMake to include OpenQL as a dependency of your program. This is pretty straight-forward: + +.. literalinclude:: ../../examples/cpp-standalone-example/CMakeLists.txt + :language: cmake + +With that configuration, ``#include `` becomes available, which places the OpenQL API in the ``ql`` namespace. Here's a basic example of what a program might look like: + +.. literalinclude:: ../../examples/cpp-standalone-example/example.cc + :language: c++ + +The API is documented `here `_. + +.. note:: + + The API classes are merely wrappers of the classes used internally by OpenQL. You can of course also use the internal classes, but their interfaces should not be assumed to be stable from version to version. diff --git a/docs/reference/options.rst.template b/docs/reference/options.rst.template new file mode 100644 index 000000000..e1e4135e9 --- /dev/null +++ b/docs/reference/options.rst.template @@ -0,0 +1,14 @@ +.. _ref_options: + +Supported global options +======================== + +This section lists all the global options currently supported by OpenQL. + +.. note:: + Most of these options exist only for backward compatibility, having been + superseded by pass options. They will be used only when the pass list is + automatically generated to mimic legacy behavior, or when compatibility + mode is enabled in the compiler configuration file. + +{options} diff --git a/docs/reference/passes.rst.template b/docs/reference/passes.rst.template new file mode 100644 index 000000000..d1fbdbb08 --- /dev/null +++ b/docs/reference/passes.rst.template @@ -0,0 +1,9 @@ +.. _ref_passes: + +Supported passes +================ + +This section lists all the compiler pass types currently available within +OpenQL. + +{passes} diff --git a/docs/reference/python.rst b/docs/reference/python.rst new file mode 100644 index 000000000..8eacb6c11 --- /dev/null +++ b/docs/reference/python.rst @@ -0,0 +1,167 @@ +.. _ref_python: + +Python API +========== + +To use OpenQL from Python, you need to install the ``qutechopenql`` module +using ``pip``, and then + +.. code-block:: python3 + + import openql as ql + +.. note:: + It used to be necessary to use ``import openql.openql as ql``. This is + still supported for backward compatibility. + +The typical usage pattern for OpenQL is as follows: + + - call :func:`initialize() ` to initialize the OpenQL + library and clean up any leftovers from compiling a previous program; + - set some global options with :func:`set_option() `; + - build a :class:`Platform `; + - build a :class:`Program ` using the platform; + - build one or more :class:`Kernel ` s using the platform, + and add them to the program with + :meth:`add_kernel() `; + - compile the program with :meth:`compile() `. + +.. note:: + The :func:`initialize() ` didn't use to exist. Therefore, + for backward compatibility, it is called automatically by the constructor + of :class:`Platform `, the constructor of + :class:`Compiler `, or by + :func:`set_option() ` if it has not been called yet + within this Python interpreter. + +.. warning:: + Calling :meth:`Program.compile() ` or + :meth:`Compiler.compile() ` multiple times on the + same program is currently *not* a supported use case: the ``compile()`` + function mutates the contents of the program as compilation progresses. + There are currently no API methods on ``Program`` or ``Kernel`` to read + back the compilation result, but these may be added in the future. + Therefore, if you want to compile a program multiple times, you'll have to + rebuild the program from scratch each time. + +For more advanced usage of the OpenQL compiler, the default compilation +strategy might not be good enough, or the global options may be too restrictive +for what you want. For this reason, the :class:`Compiler ` +interface was recently added. The easiest way to make use of it is through +:meth:`Platform.get_compiler() ` or +:meth:`Program.get_compiler() `; this returns a +reference that allows you to change the default compilation strategy or set +options for particular passes. Once you do this, however, any changes made to +global options will cease to have an effect on that particular +Platform/Program/Compiler triplet; you *must* use +:meth:`Compiler.set_option() ` and friends from +that point onwards. Note that the names of the options in this interface have +been revised compared to the global options, so you can't just replace a +global ``set_option()`` with a ``Compiler.set_option()`` without a bit of work. + +Index +----- + +.. automodule:: openql + :noindex: + + + .. rubric:: Regular functions + + .. autosummary:: + + initialize + ensure_initialized + get_version + set_option + get_option + + .. rubric:: Classes + + .. autosummary:: + + Platform + Program + Kernel + CReg + Operation + Unitary + Compiler + Pass + cQasmReader + + .. rubric:: Documentation retrieval functions + + .. autosummary:: + + print_options + dump_options + print_architectures + dump_architectures + print_passes + dump_passes + print_resources + dump_resources + print_platform_docs + dump_platform_docs + +Platform class +-------------- + +.. automodule:: openql + :members: Platform + +Program class +------------- + +.. automodule:: openql + :members: Program + +Kernel class +------------ + +.. automodule:: openql + :members: Kernel + +CReg class +---------- + +.. automodule:: openql + :members: CReg + +Operation class +--------------- + +.. automodule:: openql + :members: Operation + +Unitary class +------------- + +.. automodule:: openql + :members: Unitary + +Compiler class +-------------- + +.. automodule:: openql + :members: Compiler + +Pass class +---------- + +.. automodule:: openql + :members: Pass + +cQasmReader class +----------------- + +.. automodule:: openql + :members: cQasmReader + +Functions and miscellaneous +--------------------------- + +.. automodule:: openql + :members: + :exclude-members: Platform Program Kernel CReg Operation Unitary Compiler Pass cQasmReader diff --git a/docs/reference/resources.rst.template b/docs/reference/resources.rst.template new file mode 100644 index 000000000..a2c959038 --- /dev/null +++ b/docs/reference/resources.rst.template @@ -0,0 +1,86 @@ +.. _ref_resources: + +Supported resources +=================== + +This section lists the scheduler resource types currently supported by OpenQL. + +Roughly speaking, resources control whether two (or more) quantum gates may +execute in parallel, and under what conditions. The most obvious one is that +two quantum gates operating on the same qubit physically cannot be executed at +the same time, but quantum chips typically have more subtle constraints as +well. For example, execution of an X gate on one qubit may require generation +of a particular waveform by a waveform generator shared between a number of +qubits; in this case, it might be possible to do an X gate on another qubit +in parallel, but not a Y gate. + +Resources are of course used by the (resource-constrained) scheduler, but other +passes may also make use of them. For example, the mapper uses them in its +heuristic routing algorithm to try to overlap swaps with the rest of the +circuit as much as possible, in such a way that resource constraints are not +violated. + +Resource specification +---------------------- + +Resources are specified using the ``"resources"`` section of the platform +configuration file. Two flavors are supported for its contents: one for +compatibility with older platform configuration files, and one extended +structure. The extended structure has the following syntax. + +.. code:: + + "resources": { + "architecture": , + "dnu": , + "resources": { + "": { + "type": "", + "config": { + + } + } + ... + } + } + +The optional ``"architecture"`` key may be used to make shorthands for +architecture- specific resources, normally prefixed with +``"arch.."``. If it's not specified or an empty string, +the architecture is derived from the ``"eqasm_compiler"`` key. + +The optional ``"dnu"`` key may be used to specify a list of do-not-use +resource types (experimental, deprecated, or any other resource that's +considered unfit for "production" use) that you explicitly want to use, +including the "dnu" namespace they are defined in. Once specified, you'll +be able to use the resource type without the ``"dnu"`` namespace element. +For example, if you would include ``"dnu.whatever"`` in the list, the +resource type ``"whatever"`` may be used to add the resource. + +The ``"resources"`` subkey specifies the actual resource list. This +consists of a map from unique resource names matching ``[a-zA-Z0-9_\-]+`` +to a resource configuration. The configuration object must have a +``"type"`` key, which must identify a resource type that OpenQL knows +about; the type names are listed in the sections below. The ``"config"`` +key is optional, and is used to pass type-specific configuration data to +the resource. If not specified, an empty JSON object will be passed to +the resource instead. + +If the ``"resources"`` **sub**\ key is not present, the old structure is +used instead. This has the following, simpler form: + +.. code:: + + "resources": { + "": { + + }, + ... + } + + +This is limited to one resource per type alias. The names for the resources +are inferred, and the architecture namespace is in this case always based +upon the contents of the ``"eqasm_compiler"`` key. + +{resources} diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 56e6979f5..aaea74707 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,3 +1,2 @@ -sphinx<2 -sphinx-autodoc-typehints==1.5.0 -m2r +sphinx==3.5.4 +m2r2 diff --git a/docs/scheduling_cc.rst b/docs/scheduling_cc.rst deleted file mode 100644 index 426cbb5e5..000000000 --- a/docs/scheduling_cc.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. _cc_scheduling: - -Scheduling for CC platform -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This section will document how OpenQL schedules gates for the CC Platform. -It will also higlighted how constraints mentioned in -the platform configuration file affect scheduling. diff --git a/docs/scheduling_ccl.rst b/docs/scheduling_ccl.rst deleted file mode 100644 index 576316ac4..000000000 --- a/docs/scheduling_ccl.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. _ccl_scheduling: - -Scheduling for CC-Light platform -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This section will document how OpenQL schedules gates for the CC-Light -Platform. It will also highlight how constraints mentioned in -:ref:`cclplatform` affect scheduling. diff --git a/docs/start.rst b/docs/start.rst deleted file mode 100644 index 38147e5ea..000000000 --- a/docs/start.rst +++ /dev/null @@ -1,143 +0,0 @@ -.. _creating_your_first_program: - -Creating your first Program -=========================== - -In the OpenQL framework, -the quantum program (including kernels and gates) -is created by API calls which are contained in a C++ or Python program. - -But before this is done, -the platform object is created by an API call -that takes the name of the platform configuration file as one of its parameters. -This platform configuration file is consulted by the APIs creating the program, -kernels and gates to generate the matching internal representation of each gate. - -After creating the platform, the program and kernels are created. -The program creation API takes the program name, the platform object -and the number of qubits that are used in the program as parameters. -And similarly for each kernel. -After this, each kernel can be populated with gates. -This is again done by API calls, one per gate. - -After having added each kernel to the program, the program can be compiled. -This leaves several output files in the *test_output* directory. -When compiling for CC-Light which is one of the hardware platforms of OpenQL, -one will find there a *.qisa* file which then can be executed on the platform. -But one will also find there several *.qasm* files which can be simulated by e.g. QX. - -Let us start creating a program. - -To begin working with OpenQL, you can start up python however you like. You can open a jupyter notebook (type ``jupyter notebook`` in your terminal), open an interactive python notebook in your terminal (with ``ipython3``), or simply launch python in your terminal (by typing ``python3``). - -.. _helloworld: - -Hello World ------------ - -In this section we will run 'Hello World' example of OpenQL. The first step is to import openql which can be done by: - -.. code:: python - - from openql import openql as ql - - -Next, create a platform by: - -.. code:: python - - platform = ql.Platform("myPlatform", config_file_name) - -where, ``config_file_name`` is the name of the configuration file in JSON format -which specifies the platform, e.g. ``hardware_config_cc_light.json``. For details, refer to :ref:`platform`. -Note that you can find these files in the `tests` directory of the OpenQL repository; you should copy the -file over from there to wherever you're running Python from (same directory as the Jupyter/IPython notebook, -same directory as where you ran Python from for its terminal, or wherever you placed your Python script). - -For this example we will be working on 3 qubits. So let us define a variable so that we can use it at multiple places in our code. - -.. code:: python - - nqubits = 3 - - -Create a program - -.. code:: python - - p = ql.Program("aProgram", platform, nqubits) - -Create a kernel - -.. code:: python - - k = ql.Kernel("aKernel", platform, nqubits) - -Populate this kernel using default and custom gates - -.. code:: python - - for i in range(nqubits): - k.gate('prepz', [i]) - - k.gate('x', [0]) - k.gate('h', [1]) - k.gate('cz', [2, 0]) - k.gate('measure', [0]) - k.gate('measure', [1]) - -Add the kernel to the program - -.. code:: python - - p.add_kernel(k) - -Compile the program - -.. code:: python - - p.compile() - - -This will generate the output files in the *test_output* directory. - -A good place to get started with with your own programs might be to copy `examples/getting_started.py` to some folder of your choice and start modifying it. For further examples, have a look at the test programs inside the "tests" directory. - -.. todo:: - - discuss the generated output files - - -Notebooks ---------- - -Following Jupyter notebooks are available in the ``/examples/notebooks`` directory: - -ccLightClassicalDemo.ipynb - This notebook provides an introduction to compilation for ccLight with an emphasis on: - - - hybrid quantum/classical code generation - - control-flow in terms of: - - if, if-else - - for loop - - do-while loop - - getting measurement results - - -Examples --------- - -Following Jupyter notebooks are available in the ``/examples`` directory: - -getting_started.py - The Hello World example discussed in helloworld_ section. - -rb_single.py - Single qubit randomized benchmarking. - - -Tests ------ - -Various tests are also available in the ``/tests`` directory which can also be used as examples testing various features of OpenQL. - diff --git a/docs/visualizer.rst b/docs/visualizer.rst deleted file mode 100644 index 55820a09b..000000000 --- a/docs/visualizer.rst +++ /dev/null @@ -1,187 +0,0 @@ -.. _visualizer: - -========== -Visualizer -========== - -The visualizer is a special compiler pass that will visualize the quantum circuit being compiled. Three different types of visualization can be generated: - -* **basic circuit visualization**: displays the circuit as an abstract set of gates acting on qubits -* **pulse visualization**: specifically designed for the quantum hardware designed by the DiCarlo lab, this visualization displays the RF pulses that control the quantum hardware -* **a mapping graph**: shows the evolution of the mapping between logical and real qubits as the circuit runs each cycle -* **the qubit interaction graph**: displays the interactions between qubits in the circuit - -All of these visualization types can be customised to turn features on or off and to change the looks of the visualization. This is done by way of the -visualizer configuration file, see section :ref:`visualizer_configuration`. - ------------------------- -General visualizer usage ------------------------- -The visualizer can be ran by adding the visualizer pass to the compiler, providing the pass with the paths to the configuration files, and compiling the -quantum program you want to run. Python example: - -.. code:: python - - c = ql.Compiler("example_compiler") - - c.add_pass("Visualizer"); - c.set_pass_option("Visualizer", "visualizer_type", "") - c.set_pass_option("Visualizer", "visualizer_config_path", "")); - - # only necessary when using the pulse visualization - c.set_pass_option("Visualizer", "visualizer_waveform_mapping_path", "")); - - # add program and gates - - c.compile(program) - -There are three different pass options for the visualizer, each with a default value if no user-specified value is provided: - -+----------------------------------+------------------------+ -| option | default value | -+==================================+========================+ -| visualizer_type | CIRCUIT | -+----------------------------------+------------------------+ -| visualizer_config_path | visualizer_config.json | -+----------------------------------+------------------------+ -| visualizer_waveform_mapping_path | waveform_mapping.json | -+----------------------------------+------------------------+ - -The first option, ``visualizer_type`` determines the type of visualization to use. ``CIRCUIT`` for the basic circuit visualization, ``MAPPING_GRAPH`` for -the mapping graph visualization and ``INTERACTION_GRAPH`` for the qubit interaction graph. The visualization parameters are read from the configuration file -specified by the ``visualizer_config_path`` pass option. When using the pulse visualization, the waveform mapping configuration file, stored in the -``visualizer_waveform_mapping_path`` pass option is used to determine the mapping between gates and pulses. - ---------------------- -Circuit visualization ---------------------- - -The circuit visualizater produces an image of the circuit containing the operations on each qubit per cycle. An OpenQL program can use default gates or -custom gates. Note that default gates will be deprecated at some point in the future. - -Custom gates ------------- - -When using custom gates the default gate visualizations are not used and the visualization needs to be defined by the user. In the ``instructions`` -section of the visualizer configuration file, each instruction 'type' has its own corresponding description of gate visualization parameters. -These instruction types are mapped to actual custom instructions from the hardware configuration file by including an additional attribute to each -custom instruction, describing its visualization type: - -.. code: json - - { - "h q1": { - "duration": 40, - "latency": 0, - "qubits": ["q1"], - "matrix": [ [0.0,1.0], [1.0,0.0], [1.0,0.0], [0.0,0.0] ], - "disable_optimization": false, - "type": "mw", - "cc_light_instr_type": "single_qubit_gate", - "cc_light_instr": "h", - "cc_light_codeword": 91, - "cc_light_opcode": 9, - "visual_type": "h" - } - } - -This custom Hadamard gate defined on qubit 1 has one additional attribute ``visual_type`` describing its visualization type. The value of this -attribute links to a key in the visualizer configuration file, which has the description of the gate visualization parameters that will be used -to visualize this custom instruction. Note that this allows multiple custom instructions to share the same visualization parameters, without having -to duplicate the parameters. - -In the ``instructions`` section of the visualizer configuration file the gate visualization parameters are described like so: - -.. code:: json - - { - "h": { - "connectionColor": [0, 0, 0], - "nodes": [ - { - "type": "GATE", - "radius": 13, - "displayName": "H", - "fontHeight": 13, - "fontColor": [255, 255, 255], - "backgroundColor": [70, 210, 230], - "outlineColor": [70, 210, 230] - } - ] - } - } - -Each gate has a `connectionColor` which defines the color of the connection line for multi-operand gates, and an array of 'nodes'. -A node is the visualization of the gate acting on a specific qubit or classical bit. If a Hadamard gate is acting on qubit 3, that is -represented by one node. If a CNOT gate is acting on qubits 1 and 2, it will have two nodes, one describing the visualization of the -CNOT gate at qubit 1 and one describing the visualization on qubit 2. A measurement gate measuring qubit 5 and storing the result in -classical bit 0 will again have two nodes. - -Each node has several attributes describing its visualization: - -* ``type``: the visualization type of the node, see below for a list of the available types -* ``radius``: the radius of the node in pixels -* ``displayName``: text that will be displayed on the node (for example 'H' will be displayed on the Hadamard gate in the example above) -* ``fontHeight``: the height of the font in pixels used by the `displayName` -* ``fontColor``: the color of the font used by the `displayName` -* ``backgroundColor``: the background color of the node -* ``outlineColor``: the color of the edge-line of the node - -The colors are defined as RGB arrays: ``[R, G, B]``. - -The type of the nodes can be one of the following: - -* ``NONE``: the node will not be visible -* ``GATE``: a square representing a gate -* ``CONTROL``: a small filled circle -* ``NOT``: a circle outline with cross inside (a CNOT cross) -* ``CROSS``: a diagonal cross - -When a gate has multiple operands, each operand should have a node associated with it. Simply create as many nodes in the node array as -there are operands and define a type and visual parameters for it. Don't forget the comma to seperate each node in the array. -Note that nodes are coupled to each operand sequentially, i.e. the first node in the node array will be used for the first qubit in the operand vector. - -Pulse Visualization -------------------- - -Along with an abstract representation of the gates used in the quantum circuit, the gates can also be represented by the RF pulses used in the real -hardware. Note that this feature was specifically made for use by the DiCarlo lab. - -Each qubit consists of three lines, the microwave, flux and readout lines, controlling single-qubit gates, two-qubit gates and readouts respectively. -The waveforms used by the hardware should be stored in the waveform mapping configuration file. Then, in the hardware configuration file the -``cc_light_codeword`` and ``qubits`` attributes of each instruction are used as key into the table contained in the waveform mapping file to find the -corresponding waveform for the specific instruction and qubit (waveforms for the same instruction can be different for different qubits). Note that -a two-qubit gate has two codeword attributes: ``cc_light_right_codeword`` and ``cc_light_left_codeword``, one for each qubit. - -In the waveform mapping configuration file, the waveforms are grouped by codeword first and then by addressed qubit. The waveforms themselves are -stored as an array of real numbers. The scale of these numbers does not matter, the visualizer will automatically scale the pulses to fit inside the -graph. The time between samples is determined by the sample rate (the sample rate can be different for each of the three lines). - -The configuration parameters of the qubit interaction graph are stored in the ``circuit`` section in the visualizer configuration file under the -``pulses`` attribute. See :ref:`visualizer_configuration` for a description of the available options. - ------------------------ -Qubit Interaction Graph ------------------------ - -The qubit interaction graph visualizes the interactions between each of the qubits in the circuit. If a gate acts on two or more qubits, those -qubits interact with each other and an edge will be drawn in the graph, with a number indicating the amount of times those qubits have interacted -with each other. Note that the visualization of this is very simple, and the DOT graph the visualizer can produce should be used with the user's -favorite graphing software to create a better looking graph. - -The configuration parameters of the qubit interaction graph are stored in the ``interactionGraph`` section in the visualizer configuration file. -See :ref:`visualizer_configuration` for a description of the available options. - -------------- -Mapping Graph -------------- - -The mapping graph tracks the journey of the virtual qubits through the real topology of the quantum hardware as the cycles of the quantum program -are executed. The virtual qubits change location whenever a swap/move gate (or their decomposed parts) is finished executing. For convenience, the -abstract circuit representation of the quantum program is shown above the qubit mappings for each cycle. - -The topology of the quantum hardware is taken from the topology section in the hardware configuration file, together with the edges between the qubits. -If no topology is defined, the qubits will simply be spaced sequentially in a grid structure without edges being shown. - -The configuration parameters of the mapping graph are stored in the ``mappingGraph`` section in the visualizer configuration file. -See :ref:`visualizer_configuration` for a description of the available options. \ No newline at end of file diff --git a/docs/visualizer_configuration.rst b/docs/visualizer_configuration.rst deleted file mode 100644 index af372650d..000000000 --- a/docs/visualizer_configuration.rst +++ /dev/null @@ -1,211 +0,0 @@ -.. _visualizer_configuration: - -======================== -Visualizer configuration -======================== - -The visualizer is configured by way of the visualizer configuration file. Each attribute has a default setting, so many can be omitted if no change -is wanted. - --------------------------------- -General visualization parameters --------------------------------- - -The visualizer configuration file has several top-level sections: - -* ``circuit``: contains options for the circuit visualization, including pulse visualization -* ``interactionGraph``: contains options for the interaction graph -* ``mappingGraph``: contains options for the mapping graph - -In addition there are also two top-level attributes: - -* ``saveImage``: whether the generated image should be saved to disk -* ``backgroundColor``: the background color of the generated image - -Each of the three top-level sections will be discussed below. - ---------------------- -Circuit visualization ---------------------- - -The ``circuit`` section has several child sections: - -* ``cycles``: contains parameters that govern cycle labels, edges, cycle compression and cutting -* ``bitLines``: defines the labels and lines, including grouping lines for both quantum and classical bitLines -* ``grid``: defines several parameters of the image grid -* ``gateDurationOutlines``: controls parameters for gate duration outlines -* ``measurements``: several parameters controlling measurement visualization -* ``pulses``: parameters for pulse visualization -* ``instructions``: a map of instruction types (the keys) with that type's gate visualization as value, used for custom instructions - -Example configuration (self-explanatory attributes have no description): - -.. code:: javascript - - "cycles": - { - // parameters for the labels above each cycle - "labels": - { - "show": true, - // whether the cycle labels should be shown in nanoseconds or cycle numbers - "inNanoSeconds": false, - // the height of the cycle label row - "rowHeight": 24, - "fontHeight": 13, - "fontColor": [0, 0, 0] - }, - // parameters for the vertical edges between cycles - "edges": - { - "show": true, - "color": [0, 0, 0], - "alpha": 0.2 - }, - // parameters for the cutting of cycles (cycles are cut when no new gates are started) - "cutting": - { - "cut": true, - // how many cycles should be without a gate starting before the cycle is cut - "emptyCycleThreshold": 2, - "cutCycleWidth": 16, - // a multiplier on the width of the cut cycles - "cutCycleWidthModifier": 0.5 - }, - // cycles are compressed by reducing each gate's duration to one cycle - "compressCycles": false, - // partioning a cycle means that each gate in that cycle gets its own column within the cycle - // this can be done to remove visual overlap - "partitionCyclesWithOverlap": true - }, - "bitLines": - { - // parameters for the labels on each quantum or classical bit line - "labels": - { - "show": true, - // the width of the label column - "columnWidth": 32, - "fontHeight": 13, - // the colors of quantum and classical bit labels - "qbitColor": [0, 0, 0], - "cbitColor": [128, 128, 128] - }, - // parameters specifically for quantum bit lines - "quantum": - { - "color": [0, 0, 0] - }, - // parameters specifically for classical bit lines - "classical": - { - "show": true, - // grouping classical bit lines collapses them into a double line to reduce visual clutter - "group": false, - // controls the gap between the double line indicating the collapsed classical lines - "groupedLineGap": 2, - "color": [128, 128, 128] - }, - // parameters for the horizontal edges between bit lines - "edges": - { - "show": false, - "thickness": 5, - "color": [0, 0, 0], - "alpha": 0.4 - } - }, - "grid": - { - // the size of each cell formed by a the crossing of a single bit line and cycle - "cellSize": 32, - // the border at the edges of the generated image - "borderSize": 32 - }, - "gateDurationOutlines": - { - "show": true, - // the gap between the edge of the cell and the gate duration outline - "gap": 2, - // the filled background alpha - "fillAlpha": 0.2, - // the outline alpha - "outlineAlpha": 0.3, - "outlineColor": [0, 0, 0] - }, - "measurements": - { - // whether to draw a connection from the measurement gate to the classical line it stores the result in - "drawConnection": true, - // the gap between the double line representing the connection - "lineSpacing": 2, - "arrowSize": 10 - , - "pulses": - { - // set this to true to use the pulse visualization - "displayGatesAsPulses": false, - // these heights control the line row heights - "pulseRowHeightMicrowave": 32, - "pulseRowHeightFlux": 32, - "pulseRowHeightReadout": 32, - // these colors control the line colors - "pulseColorMicrowave": [0, 0, 255], - "pulseColorFlux": [255, 0, 0], - "pulseColorReadout": [0, 255, 0] - } - -------------------------------------- -Qubit interaction graph visualization -------------------------------------- - -Example configuration (self-explanatory attributes have no description): - -.. code:: javascript - - "interactionGraph": - { - // whether a DOT file should be generated for use with graphing software - "outputDotFile": true, - "borderWidth": 32, - // the minimum radius of the circle on which the qubits are placed - "minInteractionCircleRadius": 100, - "interactionCircleRadiusModifier": 3.0, - "qubitRadius": 17, - "labelFontHeight": 13, - "circleOutlineColor": [0, 0, 0], - "circleFillColor": [255, 255, 255], - "labelColor": [0, 0, 0], - "edgeColor": [0, 0, 0] - } - ---------------------------- -Mapping graph visualization ---------------------------- - -Example configuration (self-explanatory attributes have no description): - -.. code:: javascript - - "mappingGraph": - { - // whether qubits should be filled with the corresponding logical qubit index in the first cycle - "initDefaultVirtuals": false, - // give each distinct virtual qubit a color - "showVirtualColors": true, - // show the real qubit indices above the qubits - "showRealIndices": true, - // whether to use the topology from the hardware configuration file - "useTopology": true, - // parameters for controlling the layout - "qubitRadius": 15, - "qubitSpacing": 7, - "fontHeightReal": 13, - "fontHeightVirtual": 13, - "textColorReal": [0, 0, 255], - "textColorVirtual": [255, 0, 0], - // the gap between the qubit and the real index - "realIndexSpacing": 1, - "qubitFillColor": [255, 255, 255], - "qubitOutlineColor": [0, 0, 0] - } \ No newline at end of file diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 000000000..9af26c1e9 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,2 @@ +/output/ +/hist.png diff --git a/examples/cpp-standalone-example/example.cc b/examples/cpp-standalone-example/example.cc index 986b5d02d..b8113e195 100644 --- a/examples/cpp-standalone-example/example.cc +++ b/examples/cpp-standalone-example/example.cc @@ -1,30 +1,29 @@ #include -#include +#include -int main(int argc, char ** argv) -{ - // create platform - ql::quantum_platform platf("seven_qubits_chip", "hardware_config_cc_light.json"); +int main(int argc, char **argv) { + // create platform + auto platf = ql::Platform("seven_qubits_chip", "cc_light"); - // create program - ql::quantum_program prog("aProgram", platf, 2); + // create program + auto prog = ql::Program("aProgram", platf, 2); - // create kernel - ql::quantum_kernel k("aKernel", platf, 2); + // create kernel + auto k = ql::Kernel("aKernel", platf, 2); - k.gate("prepz", 0); - k.gate("prepz", 1); - k.gate("x", 0); - k.gate("y", 1); - k.measure(0); - k.measure(1); + k.gate("prepz", 0); + k.gate("prepz", 1); + k.gate("x", 0); + k.gate("y", 1); + k.measure(0); + k.measure(1); - // add kernel to program - prog.add(k); + // add kernel to program + prog.add_kernel(k); - // compile the program - prog.compile(); + // compile the program + prog.compile(); - std::cout << "Seems good to me!" << std::endl; - return 0; + std::cout << "Seems good to me!" << std::endl; + return 0; } diff --git a/examples/dice.py b/examples/dice.py index d91fca012..a7154e8a5 100644 --- a/examples/dice.py +++ b/examples/dice.py @@ -4,22 +4,15 @@ import os import matplotlib.pyplot as plt - -curdir = os.path.dirname(__file__) -output_dir = os.path.join(curdir, 'test_output') - -ql.set_option('output_dir', output_dir) -ql.set_option('write_qasm_files', 'yes') -ql.set_option('scheduler', 'ASAP') +ql.set_option('output_dir', 'output') ql.set_option('log_level', 'LOG_INFO') nqubits = 3 def dice_compile(): print('compiling 8-face dice program by openql') - config = os.path.join(curdir, '../tests/hardware_config_qx.json') - platform = ql.Platform("myPlatform", config) + platform = ql.Platform('myPlatform', 'none') p = ql.Program('dice', platform, nqubits) k = ql.Kernel('aKernel', platform, nqubits) @@ -47,7 +40,7 @@ def plot_histogram(dice_faces): def dice_execute_singleshot(): print('executing 8-face dice program on qxelarator') qx = qxelarator.QX() - qx.set('test_output/dice.qasm') + qx.set('output/dice.qasm') qx.execute() res = [int(qx.get_measurement_outcome(q)) for q in range(nqubits)] dice_face = reduce(lambda x, y: 2*x+y, res, 0) + 1 @@ -56,7 +49,7 @@ def dice_execute_singleshot(): def dice_execute_multishot(): print('executing 8-face dice program on qxelarator') qx = qxelarator.QX() - qx.set('test_output/dice.qasm') + qx.set('output/dice.qasm') dice_faces = [] ntests = 100 for i in range(ntests): @@ -70,4 +63,4 @@ def dice_execute_multishot(): if __name__ == '__main__': dice_compile() dice_execute_singleshot() - # dice_execute_multishot() + dice_execute_multishot() diff --git a/examples/multi_qubits_randomized_benchmarking.cc b/examples/multi_qubits_randomized_benchmarking.cc index c63dc275d..3d4b5901c 100644 --- a/examples/multi_qubits_randomized_benchmarking.cc +++ b/examples/multi_qubits_randomized_benchmarking.cc @@ -8,7 +8,7 @@ #include -#include +#include // clifford inverse lookup table for grounded state const size_t inv_clifford_lut_gs[] = {0, 2, 1, 3, 8, 10, 6, 11, 4, 9, 5, 7, 12, 16, 23, 21, 13, 17, 18, 19, 20, 15, 22, 14}; @@ -20,118 +20,106 @@ typedef std::vector cliffords_t; /** * build rb circuit */ -void build_rb(int num_cliffords, ql::quantum_kernel& k, int qubits=1, bool different=false) -{ - assert((num_cliffords%2) == 0); - int n = num_cliffords/2; - - if (!different) - { - cliffords_t cl; - cliffords_t inv_cl; - - // add the clifford and its reverse - for (int i=0; iset_sweep_points(sweep_points, num_circuits); + //rb->set_config_file("rb_config.json"); - // create program - std::stringstream prog_name; - prog_name << "rb_" << num_qubits << "_" << (different ? "diff" : "same"); - ql::quantum_program rb(prog_name.str(), starmon, num_qubits); - rb.set_sweep_points(sweep_points, num_circuits); - rb.set_config_file("rb_config.json"); + // create subcircuit + std::stringstream name; + name << "rb_" << num_qubits; + auto kernel = ql::Kernel(name.str(),starmon,num_qubits); + build_rb(num_cliffords, kernel, num_qubits, different); + rb.add_kernel(kernel); - // create subcircuit - std::stringstream name; - name << "rb_" << num_qubits; - ql::quantum_kernel kernel(name.str(),starmon,num_qubits); - build_rb(num_cliffords, kernel, num_qubits, different); - rb.add(kernel); + // compile the program + rb.compile(); - // compile the program - rb.compile(); - - return 0; + return 0; } diff --git a/examples/randomized_benchmarking.cc b/examples/randomized_benchmarking.cc index 24efa3989..a8bcf6b4e 100644 --- a/examples/randomized_benchmarking.cc +++ b/examples/randomized_benchmarking.cc @@ -22,8 +22,7 @@ typedef std::vector cliffords_t; /** * build rb circuit */ -void build_rb(int num_cliffords, ql::quantum_kernel& k) -{ +void build_rb(int num_cliffords, ql::Kernel &k) { assert((num_cliffords%2) == 0); int n = num_cliffords/2; @@ -31,8 +30,7 @@ void build_rb(int num_cliffords, ql::quantum_kernel& k) cliffords_t inv_cl; // add the clifford and its reverse - for (int i=0; i sweep_points = { 2, 4, 8, 16, 32, 64, 128, 256, 512, 512.25, 512.75, 513.25, 513.75 }; // sizes of the clifford circuits per randomization for (int r=0; r cliffords_t; /** * build rb circuit */ -void build_rb(int num_cliffords, ql::quantum_kernel& k) -{ - assert((num_cliffords%2) == 0); - int n = num_cliffords/2; - - cliffords_t cl; - cliffords_t inv_cl; - - // add the clifford and its reverse - for (int i=0; i sweep_points = { 2, 4, 8, 16 }; // sizes of the clifford circuits per randomization - // create platform - ql::quantum_platform qx_platform("qx_simulator","hardware_config_qx.json"); + // create platform + auto qx_platform = ql::Platform("qx_simulator", "none"); - // print info - qx_platform.print_info(); + // print info + qx_platform.get_info(); - ql::quantum_program rb("rb", qx_platform, 1); + auto rb = ql::Program("rb", qx_platform, 1); - rb.set_sweep_points(sweep_points, num_circuits); + rb.set_sweep_points(sweep_points); - ql::quantum_kernel kernel("rb1024", qx_platform, 1); + auto kernel = ql::Kernel("rb1024", qx_platform, 1); - build_rb(1024, kernel); + build_rb(1024, kernel); - rb.add(kernel); - rb.compile(); + rb.add_kernel(kernel); + rb.compile(); - // std::cout<< rb.qasm() << std::endl; - // std::cout << rb.qasm() << std::endl; + // std::cout<< rb.qasm() << std::endl; + // std::cout << rb.qasm() << std::endl; - return 0; + return 0; } diff --git a/examples/simple.cc b/examples/simple.cc index 6b989f507..acb23ae58 100644 --- a/examples/simple.cc +++ b/examples/simple.cc @@ -1,29 +1,28 @@ #include -#include +#include -int main(int argc, char ** argv) -{ - // create platform - ql::quantum_platform platf("seven_qubits_chip", "hardware_config_cc_light.json"); +int main(int argc, char **argv) { + // create platform + auto platf = ql::Platform("seven_qubits_chip", "cc_light"); - // create program - ql::quantum_program prog("aProgram", platf, 2); + // create program + auto prog = ql::Program("aProgram", platf, 2); - // create kernel - ql::quantum_kernel k("aKernel", platf, 2); + // create kernel + auto k = ql::Kernel("aKernel", platf, 2); - k.gate("prepz", 0); - k.gate("prepz", 1); - k.gate("x", 0); - k.gate("y", 1); - k.measure(0); - k.measure(1); + k.gate("prepz", 0); + k.gate("prepz", 1); + k.gate("x", 0); + k.gate("y", 1); + k.measure(0); + k.measure(1); - // add kernel to program - prog.add(k); + // add kernel to program + prog.add_kernel(k); - // compile the program - prog.compile(); + // compile the program + prog.compile(); - return 0; + return 0; } diff --git a/examples/simple.py b/examples/simple.py new file mode 100644 index 000000000..88a7c088c --- /dev/null +++ b/examples/simple.py @@ -0,0 +1,25 @@ +import openql as ql + +ql.initialize() + +ql.set_option('output_dir', 'output') +ql.set_option('log_level', 'LOG_INFO') + +platform = ql.Platform('my_platform', 'none') + +nqubits = 3 +program = ql.Program('my_program', platform, nqubits) +kernel = ql.Kernel('my_kernel', platform, nqubits) + +for i in range(nqubits): + kernel.prepz(i) + +kernel.x(0) +kernel.hadamard(1) +kernel.cz(2, 0) +kernel.measure(0) +kernel.measure(1) + +program.add_kernel(kernel) + +program.compile() diff --git a/include/openql b/include/openql new file mode 100644 index 000000000..e8f65c0ab --- /dev/null +++ b/include/openql @@ -0,0 +1,7 @@ +/** \file + * Main OpenQL header, C++ style. + */ + +#pragma once + +#include "openql.h" diff --git a/include/openql.h b/include/openql.h new file mode 100644 index 000000000..c3aa8189f --- /dev/null +++ b/include/openql.h @@ -0,0 +1,18 @@ +/** \file + * Main OpenQL header. + */ + +#pragma once + +#include "ql/api/api.h" + +namespace ql { + +// The API is defined in its own folder in the source tree. The namespaces mimic +// that for consistency. However, making users use ql::api everywhere in user +// code would be a bit weird, so we pull the api namespace into the root ql +// namespace. The rest of OpenQL doesn't define anything in there directly, so +// this should be safe to do. +using namespace api; + +} // namespace ql diff --git a/include/openql_i.h b/include/openql_i.h new file mode 100644 index 000000000..17cc31e89 --- /dev/null +++ b/include/openql_i.h @@ -0,0 +1,14 @@ +/** \file + * Compatibility header for C++ programs which used to #include "openql_i.h" + */ + +#pragma once + +#warning "Use of the openql_i.h header is deprecated. Please use #include !" + +#include "openql.h" +#include "ql/api/api.h" + +// openql_i.h used to declare everything in the root namespace, so we'll have to +// mimic that with an ugly using namespace call. +using namespace ql::api; diff --git a/include/ql/api/api.h b/include/ql/api/api.h new file mode 100644 index 000000000..a9b29b984 --- /dev/null +++ b/include/ql/api/api.h @@ -0,0 +1,37 @@ +/** \file + * Main header for the external API to OpenQL. + */ + +#pragma once + +//============================================================================// +// W A R N I N G // +//----------------------------------------------------------------------------// +// Additions to/removals from the API (classes & global functions) must be // +// manually kept in sync with the __all__ declaration in // +// python/openql/__init__.py // +//----------------------------------------------------------------------------// +// Additions to/removals from the API fileset must manually be kept in sync // +// with the python/openql.i (don't forget to add a .i subfile in src as well) // +//----------------------------------------------------------------------------// +// Additions to/removals from the set of automatically-wrapped or SWIG // +// STL template expansion types that the API uses must be kept in sync with // +// the C++ to Python typemap in the docstring monkey-patching logic of // +// python/openql/__init__.py // +//----------------------------------------------------------------------------// +// After changing anything, make sure to check if documentation generation // +// still works ("make html" in docs) // +//============================================================================// + +#include "ql/api/misc.h" +#include "ql/api/declarations.h" +#include "ql/api/pass.h" +#include "ql/api/compiler.h" +#include "ql/api/platform.h" +#include "ql/api/creg.h" +#include "ql/api/operation.h" +#include "ql/api/unitary.h" +#include "ql/api/kernel.h" +#include "ql/api/program.h" +#include "ql/api/cqasm_reader.h" + diff --git a/include/ql/api/compiler.h b/include/ql/api/compiler.h new file mode 100644 index 000000000..123fd6bfa --- /dev/null +++ b/include/ql/api/compiler.h @@ -0,0 +1,362 @@ +/** \file + * API header for accessing the compiler's pass management logic. + */ + +#pragma once + +#include "ql/config.h" +#include "ql/pmgr/manager.h" +#include "ql/api/declarations.h" + +//============================================================================// +// W A R N I N G // +//----------------------------------------------------------------------------// +// Docstrings in this file must manually be kept in sync with // +// compiler.i! This should be automated at some point, but isn't yet. // +//============================================================================// + +namespace ql { +namespace api { + +/** + * Wrapper for the compiler/pass manager. + */ +class Compiler { +private: + friend class Platform; + friend class Program; + + /** + * The linked pass manager. + */ + ql::pmgr::Ref pass_manager; + + /** + * Constructor used internally to build a compiler object that belongs to + * a platform. + */ + explicit Compiler(const ql::pmgr::Ref &pass_manager); + +public: + + /** + * User-given name for this compiler. + * + * NOTE: not actually used for anything. It's only here for consistency with + * the rest of the API objects. + */ + std::string name; + + /** + * Creates an empty compiler, with no specified architecture. + */ + explicit Compiler(const std::string &name=""); + + /** + * Creates a compiler configuration from the given JSON file. + */ + explicit Compiler(const std::string &name, const std::string &filename); + + /** + * Creates a default compiler for the given platform. + */ + explicit Compiler(const std::string &name, const Platform &platform); + + /** + * Prints documentation for all available pass types, as well as the option + * documentation for the passes. + */ + void print_pass_types() const; + + /** + * Returns documentation for all available pass types, as well as the option + * documentation for the passes. + */ + std::string dump_pass_types() const; + + /** + * Prints the currently configured compilation strategy. + */ + void print_strategy() const; + + /** + * Returns the currently configured compilation strategy as a string. + */ + std::string dump_strategy() const; + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Sets a pass option. Periods are used as hierarchy separators; the last + * element will be the option name, and the preceding elements represent + * pass instance names. Furthermore, wildcards may be used for the pass name + * elements (asterisks for zero or more characters and a question mark for a + * single character) to select multiple or all immediate sub-passes of that + * group, and a double asterisk may be used for the element before the + * option name to chain to set_option_recursively() instead. The return + * value is the number of passes that were affected; passes are only + * affected when they are selected by the option path AND have an option + * with the specified name. If must_exist is set an exception will be thrown + * if none of the passes were affected, otherwise 0 will be returned. + */ +#else + /** + * Sets a pass option. The path must consist of the target pass instance name + * and the option name, separated by a period. The former may include * or ? + * wildcards. If must_exist is set an exception will be thrown if none of the + * passes were affected, otherwise 0 will be returned. + */ +#endif + size_t set_option( + const std::string &path, + const std::string &value, + bool must_exist = true + ); + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Sets an option for all passes recursively. The return value is the number + * of passes that were affected; passes are only affected when they have an + * option with the specified name. If must_exist is set an exception will be + * thrown if none of the passes were affected, otherwise 0 will be returned. + */ + size_t set_option_recursively( + const std::string &option, + const std::string &value, + bool must_exist = true + ); +#endif + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Returns the current value of an option. Periods are used as hierarchy + * separators; the last element will be the option name, and the preceding + * elements represent pass instance names. + */ +#else + /** + * Returns the current value of an option. path must consist of the pass + * instance name and the option name separated by a period. + */ +#endif + std::string get_option(const std::string &path) const; + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Appends a pass to the end of the pass list. If type_name is empty + * or unspecified, a generic subgroup is added. Returns a reference to the + * constructed pass. + */ + Pass append_pass( + const std::string &type_name = "", + const std::string &instance_name = "", + const std::map &options = {} + ); +#else + /** + * Appends a pass to the end of the pass list. Returns a reference to the + * constructed pass. + */ + Pass append_pass( + const std::string &type_name, + const std::string &instance_name = "", + const std::map &options = {} + ); +#endif + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Appends a pass to the beginning of the pass list. If type_name is empty + * or unspecified, a generic subgroup is added. Returns a reference to the + * constructed pass. + */ + Pass prefix_pass( + const std::string &type_name = "", + const std::string &instance_name = "", + const std::map &options = {} + ); +#else + /** + * Appends a pass to the beginning of the pass list. Returns a reference to the + * constructed pass. + */ + Pass prefix_pass( + const std::string &type_name, + const std::string &instance_name = "", + const std::map &options = {} + ); +#endif + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Inserts a pass immediately after the target pass (named by instance). If + * target does not exist, an exception is thrown. If type_name is empty or + * unspecified, a generic subgroup is added. Returns a reference to the + * constructed pass. Periods may be used in target to traverse deeper into + * the pass hierarchy. + */ + Pass insert_pass_after( + const std::string &target, + const std::string &type_name = "", + const std::string &instance_name = "", + const std::map &options = {} + ); +#else + /** + * Inserts a pass immediately after the target pass (named by instance). If + * target does not exist, an exception is thrown. Returns a reference to the + * constructed pass. + */ + Pass insert_pass_after( + const std::string &target, + const std::string &type_name, + const std::string &instance_name = "", + const std::map &options = {} + ); +#endif + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Inserts a pass immediately before the target pass (named by instance). If + * target does not exist, an exception is thrown. If type_name is empty or + * unspecified, a generic subgroup is added. Returns a reference to the + * constructed pass. Periods may be used in target to traverse deeper into + * the pass hierarchy. + */ + Pass insert_pass_before( + const std::string &target, + const std::string &type_name = "", + const std::string &instance_name = "", + const std::map &options = {} + ); +#else + /** + * Inserts a pass immediately before the target pass (named by instance). If + * target does not exist, an exception is thrown. Returns a reference to the + * constructed pass. + */ + Pass insert_pass_before( + const std::string &target, + const std::string &type_name, + const std::string &instance_name = "", + const std::map &options = {} + ); +#endif + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Looks for the pass with the target instance name, and embeds it into a + * newly generated group. The group will assume the name of the original + * pass, while the original pass will be renamed as specified by sub_name. + * Note that this ultimately does not modify the pass order. If target does + * not exist or this pass is not a group of sub-passes, an exception is + * thrown. Returns a reference to the constructed group. Periods may be used + * in target to traverse deeper into the pass hierarchy. + */ + Pass group_pass( + const std::string &target, + const std::string &sub_name = "main" + ); +#endif + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Like group_pass(), but groups an inclusive range of passes into a + * group with the given name, leaving the original pass names unchanged. + * Periods may be used in from/to to traverse deeper into the pass + * hierarchy, but the hierarchy prefix must be the same for from and to. + */ + Pass group_passes( + const std::string &from, + const std::string &to, + const std::string &group_name + ); +#endif + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Looks for an unconditional pass group with the target instance name and + * flattens its contained passes into its parent group. The names of the + * passes found in the collapsed group are prefixed with name_prefix before + * they are added to the parent group. Note that this ultimately does not + * modify the pass order. If the target instance name does not exist or is + * not an unconditional group, an exception is thrown. Periods may be used + * in target to traverse deeper into the pass hierarchy. + */ + void flatten_subgroup( + const std::string &target, + const std::string &name_prefix = "" + ); +#endif + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Returns a reference to the pass with the given instance name. If no such + * pass exists, an exception is thrown. Periods may be used as hierarchy + * separators to get nested sub-passes. + */ +#else + /** + * Returns a reference to the pass with the given instance name. If no such + * pass exists, an exception is thrown. + */ +#endif + Pass get_pass(const std::string &target) const; + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Returns whether a pass with the target instance name exists. Periods may + * be used in target to traverse deeper into the pass hierarchy. + */ +#else + /** + * Returns whether a pass with the target instance name exists. + */ +#endif + bool does_pass_exist(const std::string &target) const; + + /** + * Returns the total number of passes in the root hierarchy. + */ + size_t get_num_passes() const; + + /** + * Returns a vector with references to all passes in the root hierarchy. + */ + std::vector get_passes() const; + + /** + * Returns an indexable list of references to all passes with the given + * type within the root hierarchy. + */ + std::vector get_passes_by_type(const std::string &target) const; + + /** + * Removes the pass with the given target instance name, or throws an + * exception if no such pass exists. + */ + void remove_pass(const std::string &target); + + /** + * Clears the entire pass list. + */ + void clear_passes(); + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Constructs all passes recursively. This freezes the pass options, but + * allows subtrees to be modified. + */ + void construct(); +#endif + + /** + * Ensures that all passes have been constructed, and then runs the passes + * on the given program. This is the same as Program.compile() when the + * program is referencing the same compiler. + */ + void compile(const Program &program); + +}; + +} // namespace api +} // namespace ql diff --git a/include/ql/api/cqasm_reader.h b/include/ql/api/cqasm_reader.h new file mode 100644 index 000000000..09e4150f9 --- /dev/null +++ b/include/ql/api/cqasm_reader.h @@ -0,0 +1,80 @@ +/** \file + * API header for accessing the cQASM reader. + */ + +#pragma once + +#include "ql/pass/io/cqasm/read.h" +#include "ql/api/declarations.h" +#include "ql/api/platform.h" +#include "ql/api/program.h" + +//============================================================================// +// W A R N I N G // +//----------------------------------------------------------------------------// +// Docstrings in this file must manually be kept in sync with // +// cqasm_reader.i! This should be automated at some point, but isn't yet. // +//============================================================================// + +namespace ql { +namespace api { + +/** + * cQASM reader interface. + */ +class cQasmReader { +private: + + /** + * The wrapped cQASM reader. + */ + ql::utils::Ptr cqasm_reader; + +public: + + /** + * The platform associated with the reader. + */ + const Platform platform; + + /** + * The program that the cQASM circuits will be added to. + */ + const Program program; + + /** + * Builds a cQASM reader for the given platform and program, optionally + * using a custom instruction set configuration file. This is an old + * interface; the platform argument is redundant. + */ + cQasmReader( + const Platform &platform, + const Program &program, + const std::string &gateset_fname = "" + ); + + /** + * Builds a cQASM reader for the given program, optionally using a custom + * instruction set configuration file. + */ + cQasmReader( + const Program &program, + const std::string &gateset_fname = "" + ); + + /** + * Interprets a string as cQASM file and adds its contents to the program + * associated with this reader. + */ + void string2circuit(const std::string &cqasm_str); + + /** + * Interprets a cQASM file and adds its contents to the program associated + * with this reader. + */ + void file2circuit(const std::string &cqasm_file_path); + +}; + +} // namespace api +} // namespace ql diff --git a/include/ql/api/creg.h b/include/ql/api/creg.h new file mode 100644 index 000000000..5a8b998bf --- /dev/null +++ b/include/ql/api/creg.h @@ -0,0 +1,43 @@ +/** \file + * API header for using classical integer registers. + */ + +#pragma once + +#include "ql/ir/ir.h" +#include "ql/api/declarations.h" + +//============================================================================// +// W A R N I N G // +//----------------------------------------------------------------------------// +// Docstrings in this file must manually be kept in sync with // +// creg.i! This should be automated at some point, but isn't yet. // +//============================================================================// + +namespace ql { +namespace api { + +/** + * Represents a classical 32-bit integer register. + */ +class CReg { +private: + friend class Operation; + friend class Kernel; + + /** + * The wrapped control register object. + */ + ql::utils::Ptr creg; + +public: + + /** + * Creates a register with the given index. + */ + explicit CReg(size_t id); + +}; + +} // namespace api +} // namespace ql diff --git a/include/ql/api/declarations.h b/include/ql/api/declarations.h new file mode 100644 index 000000000..a73a3e051 --- /dev/null +++ b/include/ql/api/declarations.h @@ -0,0 +1,22 @@ +/** \file + * Forward declarations for the API classes. + */ + +#pragma once + +namespace ql { +namespace api { + +// Forward declarations for classes. +class Pass; +class Compiler; +class Platform; +class CReg; +class Operation; +class Unitary; +class Program; +class Kernel; +class cQasmReader; + +} // namespace api +} // namespace ql diff --git a/include/ql/api/kernel.h b/include/ql/api/kernel.h new file mode 100644 index 000000000..2f3dcf643 --- /dev/null +++ b/include/ql/api/kernel.h @@ -0,0 +1,429 @@ +/** \file + * API header for using quantum kernels. + */ + +#pragma once + +#include "ql/ir/ir.h" +#include "ql/api/declarations.h" +#include "ql/api/platform.h" + +//============================================================================// +// W A R N I N G // +//----------------------------------------------------------------------------// +// Docstrings in this file must manually be kept in sync with // +// kernel.i! This should be automated at some point, but isn't yet. // +//============================================================================// + +namespace ql { +namespace api { + +/** + * Represents a kernel of a quantum program, a.k.a. a basic block. Kernels are + * just sequences of gates with no classical control-flow in between: they may + * end in a (conditional) branch to the start of another kernel, but otherwise, + * they may only consist of quantum gates and mixed quantum-classical data flow + * operations. + * + * Currently, kernels can be constructed only by adding gates and classical data + * flow instructions in the order in which they are to be executed, and there is + * no way to get information about which gates are in the kernel after the fact. + * If you need this kind of bookkeeping, you will have to wrap OpenQL's kernels + * for now. + * + * Classical flow-control is configured when a completed kernel is added to a + * program, via basic structured control-flow paradigms (if-else, do-while, and + * loops with a fixed iteration count). + * + * NOTE: the way gates are represented in OpenQL is on the list to be completely + * revised. Currently OpenQL works using a mixture of "default gates" and the + * "custom gates" that you can specify in the platform configuration file, but + * these two things are not orthogonal and largely incompatible with each other, + * yet are currently used interchangeably. Furthermore, there is no proper way + * to specify lists of generic arguments to a gate, leading to lots of code + * duplication inside OpenQL and long gate() argument lists. Finally, the + * semantics of gates are largely derived by undocumented and somewhat heuristic + * string comparisons with the names of gates, which is terrible design in + * combination with user-specified instruction sets via the platform + * configuration file. The interface for adding simple *quantum* gates to a + * kernel is something we want to keep 100% backward compatible, but the more + * advanced gate() signatures may change in the (near) future. + * + * NOTE: classical logic is on the list to be completely revised. This interface + * may change in the (near) future. + * + * NOTE: the higher-order functions for constructing controlled kernels and + * conjugating kernels have not been maintained for a while and thus probably + * won't work right. They may be removed entirely in a later version of OpenQL. + */ +class Kernel { +private: + friend class Program; + + /** + * The wrapped kernel object. + */ + ql::ir::KernelRef kernel; + +public: + + /** + * The name of the kernel as given by the user. + */ + const std::string name; + + /** + * The platform that the kernel was built for. + */ + const Platform platform; + + /** + * The number of (virtual) qubits allocated for the kernel. + */ + const size_t qubit_count; + + /** + * The number of classical integer registers allocated for the kernel. + */ + const size_t creg_count; + + /** + * The number of classical bit registers allocated for the kernel. + */ + const size_t breg_count; + + /** + * Creates a new kernel with the given name, using the given platform. + * The third, fourth, and fifth arguments optionally specify the desired + * number of qubits, classical integer registers, and classical bit + * registers. If not specified, the number of qubits is taken from the + * platform, and no classical or bit registers will be allocated. + */ + Kernel( + const std::string &name, + const Platform &platform, + size_t qubit_count = 0, + size_t creg_count = 0, + size_t breg_count = 0 + ); + + /** + * Old alias for dump_custom_instructions(). Deprecated. + */ + std::string get_custom_instructions() const; + + /** + * Prints a list of all custom gates supported by the platform. + */ + void print_custom_instructions() const; + + /** + * Returns the result of print_custom_instructions() as a string. + */ + std::string dump_custom_instructions() const; + + /** + * Sets the condition for all gates subsequently added to this kernel. + * Thus, essentially shorthand notation. Reset with gate_clear_condition(). + */ + void gate_preset_condition( + const std::string &condstring, + const std::vector &condregs + ); + + /** + * Clears a condition previously set via gate_preset_condition(). + */ + void gate_clear_condition(); + + /** + * Shorthand for appending the given gate name with a single qubit. + */ + void gate(const std::string &name, size_t q0); + + /** + * Shorthand for appending the given gate name with two qubits. + */ + void gate(const std::string &name, size_t q0, size_t q1); + + /** + * Main function for appending arbitrary quantum gates. + * + * Note that OpenQL currently uses string comparisons with gate names all + * over the place to derive functionality, and to derive what the actual + * arguments do. This is inherently a bad idea and something we want to + * move away from, so documenting it all would not be worthwhile. + * + * For conditional gates, the following condition strings are supported: + * + * - "COND_ALWAYS" or "1": no condition; gate is always executed. + * - "COND_NEVER" or "0": no condition; gate is never executed. + * - "COND_UNARY" or "" (empty): gate is executed if the single bit + * specified via condregs is 1. + * - "COND_NOT" or "!": gate is executed if the single bit specified via + * condregs is 0. + * - "COND_AND" or "&": gate is executed if the two bits specified via + * condregs are both 1. + * - "COND_NAND" or "!&": gate is executed if either of the two bits + * specified via condregs is zero. + * - "COND_OR" or "|": gate is executed if either of the two bits specified + * via condregs is one. + * - "COND_NOR" or "!|": no condition; gate is always executed. + */ + void gate( + const std::string &name, + const std::vector &qubits, + size_t duration = 0, + double angle = 0.0, + const std::vector &bregs = {}, + const std::string &condstring = "COND_ALWAYS", + const std::vector &condregs = {} + ); + + /** + * Main function for appending mixed quantum-classical gates involving + * integer registers. + */ + void gate( + const std::string &name, + const std::vector &qubits, + const CReg &destination + ); + + /** + * Appends a unitary gate to the circuit. The size of the unitary gate must + * of course align with the number of qubits presented. + */ + void gate(const Unitary &u, const std::vector &qubits); + + /** + * Alternative function for appending normal conditional quantum gates. + * Avoids having to specify duration, angle, and bregs. + */ + void condgate( + const std::string &name, + const std::vector &qubits, + const std::string &condstring, + const std::vector &condregs + ); + + /** + * Appends a classical assignment gate to the circuit. The classical integer + * register is assigned to the result of the given operation. + */ + void classical(const CReg &destination, const Operation &operation); + + /** + * Appends a classical gate without operands. Only "nop" is currently (more + * or less) supported. + */ + void classical(const std::string &operation); + + /** + * Shorthand for appending an "identity" gate with a single qubit. + */ + void identity(size_t q0); + + /** + * Shorthand for appending a "hadamard" gate with a single qubit. + */ + void hadamard(size_t q0); + + /** + * Shorthand for appending an "s" gate with a single qubit. + */ + void s(size_t q0); + + /** + * Shorthand for appending an "sdag" gate with a single qubit. + */ + void sdag(size_t q0); + + /** + * Shorthand for appending a "t" gate with a single qubit. + */ + void t(size_t q0); + + /** + * Shorthand for appending a "tdag" gate with a single qubit. + */ + void tdag(size_t q0); + + /** + * Shorthand for appending an "x" gate with a single qubit. + */ + void x(size_t q0); + + /** + * Shorthand for appending a "y" gate with a single qubit. + */ + void y(size_t q0); + + /** + * Shorthand for appending a "z" gate with a single qubit. + */ + void z(size_t q0); + + /** + * Shorthand for appending an "rx90" gate with a single qubit. + */ + void rx90(size_t q0); + + /** + * Shorthand for appending an "mrx90" gate with a single qubit. + */ + void mrx90(size_t q0); + + /** + * Shorthand for appending an "rx180" gate with a single qubit. + */ + void rx180(size_t q0); + + /** + * Shorthand for appending an "ry90" gate with a single qubit. + */ + void ry90(size_t q0); + + /** + * Shorthand for appending an "mry90" gate with a single qubit. + */ + void mry90(size_t q0); + + /** + * Shorthand for appending an "ry180" gate with a single qubit. + */ + void ry180(size_t q0); + + /** + * Shorthand for appending an "rx" gate with a single qubit and the given + * rotation in radians. + */ + void rx(size_t q0, double angle); + + /** + * Shorthand for appending an "ry" gate with a single qubit and the given + * rotation in radians. + */ + void ry(size_t q0, double angle); + + /** + * Shorthand for appending an "rz" gate with a single qubit and the given + * rotation in radians. + */ + void rz(size_t q0, double angle); + + /** + * Shorthand for appending a "measure" gate with a single qubit and implicit + * result bit register. + */ + void measure(size_t q0); + + /** + * Shorthand for appending a "measure" gate with a single qubit and explicit + * result bit register. + */ + void measure(size_t q0, size_t b0); + + /** + * Shorthand for appending a "prepz" gate with a single qubit. + */ + void prepz(size_t q0); + + /** + * Shorthand for appending a "cnot" gate with two qubits. + */ + void cnot(size_t q0, size_t q1); + + /** + * Shorthand for appending a "cphase" gate with two qubits. + */ + void cphase(size_t q0, size_t q1); + + /** + * Shorthand for appending a "cz" gate with two qubits. + */ + void cz(size_t q0, size_t q1); + + /** + * Shorthand for appending a "toffoli" gate with three qubits. + */ + void toffoli(size_t q0, size_t q1, size_t q2); + + /** + * Shorthand for appending the Clifford gate with the specific number using + * the minimal number of rx90, rx180, mrx90, ry90, ry180, and mry90 gates. + * The expansions are as follows: + * + * - 0: no gates inserted. + * - 1: ry90; rx90 + * - 2: mrx90, mry90 + * - 3: rx180 + * - 4: mry90, mrx90 + * - 5: rx90, mry90 + * - 6: ry180 + * - 7: mry90, rx90 + * - 8: rx90, ry90 + * - 9: rx180, ry180 + * - 10: ry90, mrx90 + * - 11: mrx90, ry90 + * - 12: ry90, rx180 + * - 13: mrx90 + * - 14: rx90, mry90, mrx90 + * - 15: mry90 + * - 16: rx90 + * - 17: rx90, ry90, rx90 + * - 18: mry90, rx180 + * - 19: rx90, ry180 + * - 20: rx90, mry90, rx90 + * - 21: ry90 + * - 22: mrx90, ry180 + * - 23: rx90, ry90, mrx90 + */ + void clifford(int id, size_t q0); + + /** + * Shorthand for appending a "wait" gate with the specified qubits and + * duration in nanoseconds. If no qubits are specified, the wait applies to + * all qubits instead (a wait with no qubits is meaningless). Note that the + * duration will usually end up being rounded up to multiples of the + * platform's cycle time. + */ + void wait(const std::vector &qubits, size_t duration); + + /** + * Shorthand for appending a "wait" gate with the specified qubits and + * duration 0. If no qubits are specified, the wait applies to all qubits + * instead (a wait with no qubits is meaningless). + */ + void barrier(const std::vector &qubits = std::vector()); + + /** + * Shorthand for appending a "display" gate with no qubits. + */ + void display(); + + /** + * Appends a controlled kernel. The number of control and ancilla qubits + * must be equal. + * + * NOTE: this high-level functionality is poorly/not maintained, and relies + * on default gates, which are on the list for removal. + */ + void controlled( + const Kernel &k, + const std::vector &control_qubits, + const std::vector &ancilla_qubits + ); + + /** + * Appends the conjugate of the given kernel to this kernel. + * + * NOTE: this high-level functionality is poorly/not maintained, and relies + * on default gates, which are on the list for removal. + */ + void conjugate(const Kernel &k); + +}; + +} // namespace api +} // namespace ql diff --git a/include/ql/api/misc.h b/include/ql/api/misc.h new file mode 100644 index 000000000..cf290da32 --- /dev/null +++ b/include/ql/api/misc.h @@ -0,0 +1,116 @@ +/** \file + * Defines miscellaneous API functions. + */ + +#pragma once + +#include + +//============================================================================// +// W A R N I N G // +//----------------------------------------------------------------------------// +// Docstrings in this file must manually be kept in sync with // +// misc.i! This should be automated at some point, but isn't yet. // +//============================================================================// + +namespace ql { +namespace api { + +/** + * Initializes the OpenQL library, for as far as this must be done. This should + * ideally be called by the user (in Python) before anything else, but + * set_option() and the constructors of Compiler and Platform will automatically + * call this when it hasn't been done yet as well. + * + * Currently this just resets the options to their default values to give the + * user a clean slate to work with in terms of global variables (in case someone + * else has used the library in the same interpreter before them, for instance, + * as might happen with ipython/Jupyter in a shared notebook server, or during + * test suites), but it may initialize more things in the future. + */ +void initialize(); + +/** + * Calls initialize() if it hasn't been called yet. + */ +void ensure_initialized(); + +/** + * Returns the compiler's version string. + */ +std::string get_version(); + +/** + * Sets a global option for the compiler. Use print_options() to get a list of + * all available options. + */ +void set_option(const std::string &option, const std::string &value); + +/** + * Returns the current value for a global option. Use print_options() to get a + * list of all available options. + */ +std::string get_option(const std::string &option); + +/** + * Prints the documentation for all available global options. + */ +void print_options(); + +/** + * Returns the result of print_options() as a string. + */ +std::string dump_options(); + +/** + * Prints the documentation for all available target architectures. + */ +void print_architectures(); + +/** + * Returns the result of print_architectures() as a string. + */ +std::string dump_architectures(); + +/** + * Prints the documentation for all available passes. + */ +void print_passes(); + +/** + * Returns the result of print_passes() as a string. + */ +std::string dump_passes(); + +/** + * Prints the documentation for all available scheduler resources. + */ +void print_resources(); + +/** + * Returns the result of print_resources() as a string. + */ +std::string dump_resources(); + +/** + * Prints the documentation for platform configuration files. + */ +void print_platform_docs(); + +/** + * Returns the result of print_platform_docs() as a string. + */ +std::string dump_platform_docs(); + +/** + * Prints the documentation for compiler configuration files. + */ +void print_compiler_docs(); + +/** + * Returns the result of print_compiler_docs() as a string. + */ +std::string dump_compiler_docs(); + +} // namespace api +} // namespace ql diff --git a/include/ql/api/operation.h b/include/ql/api/operation.h new file mode 100644 index 000000000..180d43189 --- /dev/null +++ b/include/ql/api/operation.h @@ -0,0 +1,75 @@ +/** \file + * API header for using classical operations. + */ + +#pragma once + +#include "ql/ir/ir.h" +#include "ql/api/declarations.h" +#include "ql/api/creg.h" + +//============================================================================// +// W A R N I N G // +//----------------------------------------------------------------------------// +// Docstrings in this file must manually be kept in sync with // +// operation.i! This should be automated at some point, but isn't yet. // +//============================================================================// + +namespace ql { +namespace api { + +/** + * Represents a classical operation. + */ +class Operation { +private: + friend class Kernel; + friend class Program; + + /** + * The wrapped classical operation object. + */ + ql::utils::Ptr operation; + +public: + + /** + * Creates a classical binary operation between two classical registers. The + * operation is specified as a string, of which the following are supported: + * - "+": addition. + * - "-": subtraction. + * - "&": bitwise AND. + * - "|": bitwise OR. + * - "^": bitwise XOR. + * - "==": equality. + * - "!=": inequality. + * - ">": greater-than. + * - ">=": greater-or-equal. + * - "<": less-than. + * - "<=": less-or-equal. + */ + Operation(const CReg &lop, const std::string &op, const CReg &rop); + + /** + * Creates a classical unary operation on a register. The operation is + * specified as a string, of which currently only "~" (bitwise NOT) is + * supported. + */ + Operation(const std::string &op, const CReg &rop); + + /** + * Creates a classical "operation" that just returns the value of the given + * register. + */ + explicit Operation(const CReg &lop); + + /** + * Creates a classical "operation" that just returns the given integer + * value. + */ + explicit Operation(int val); + +}; + +} // namespace api +} // namespace ql diff --git a/include/ql/api/pass.h b/include/ql/api/pass.h new file mode 100644 index 000000000..959e8c40d --- /dev/null +++ b/include/ql/api/pass.h @@ -0,0 +1,344 @@ +/** \file + * API header for modifying compiler pass parameters. + */ + +#pragma once + +#include "ql/config.h" +#include "ql/pmgr/pass_types/base.h" +#include "ql/api/declarations.h" + +//============================================================================// +// W A R N I N G // +//----------------------------------------------------------------------------// +// Docstrings in this file must manually be kept in sync with // +// pass.i! This should be automated at some point, but isn't yet. // +//============================================================================// + +namespace ql { +namespace api { + +/** + * Wrapper for a pass that belongs to some pass manager. + */ +class Pass { +private: + friend class Compiler; + + /** + * The linked pass. + */ + ql::pmgr::PassRef pass; + + /** + * Constructor used internally to build a pass object that belongs to + * a compiler. + * + * NOTE: the dummy boolean is because the SWIG wrapper otherwise generates + * some inane ambiguity error with the copy/move constructor (even though + * this is private and a different type). + */ + explicit Pass(const ql::pmgr::PassRef &pass, bool dummy); + +public: + + /** + * Default constructor, only exists because the SWIG wrapper breaks + * otherwise. Pass objects constructed this way cannot be used! You can only + * use Pass objects returned by Compiler. + */ + Pass() = default; + + /** + * Returns the full, desugared type name that this pass was constructed + * with. + */ + const std::string &get_type() const; + + /** + * Returns the instance name of the pass within the surrounding group. + */ + const std::string &get_name() const; + + /** + * Prints the documentation for this pass. + */ + void print_pass_documentation() const; + + /** + * Returns the documentation for this pass as a string. + */ + std::string dump_pass_documentation() const; + + /** + * Prints the current state of the options. If only_set is set to true, only + * the options that were explicitly configured are dumped. + */ + void print_options(bool only_set = false) const; + + /** + * Returns the string printed by print_options(). + */ + std::string dump_options(bool only_set = false) const; + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Prints the entire compilation strategy including configured options of + * this pass and all sub-passes. + */ + void print_strategy() const; +#endif + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Returns the string printed by print_strategy(). + */ + std::string dump_strategy() const; +#endif + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Sets an option. Periods may be used as hierarchy separators to set + * options for sub-passes; the last element will be the option name, and the + * preceding elements represent pass instance names. Furthermore, wildcards + * may be used for the pass name elements (asterisks for zero or more + * characters and a question mark for a single character) to select multiple + * or all immediate sub-passes of that group, and a double asterisk may be + * used for the element before the option name to chain to + * set_option_recursively() instead. The return value is the number of + * passes that were affected; passes are only affected when they are + * selected by the option path AND have an option with the specified name. + * If must_exist is set an exception will be thrown if none of the passes + * were affected, otherwise 0 will be returned. + */ + size_t set_option( + const std::string &option, + const std::string &value, + bool must_exist = true + ); +#else + /** + * Sets an option. + */ + void set_option( + const std::string &option, + const std::string &value + ); +#endif + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Sets an option for all sub-passes recursively. The return value is the + * number of passes that were affected; passes are only affected when they + * have an option with the specified name. If must_exist is set an exception + * will be thrown if none of the passes were affected, otherwise 0 will be + * returned. + */ + size_t set_option_recursively( + const std::string &option, + const std::string &value, + bool must_exist = true + ); +#endif + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Returns the current value of an option. Periods may be used as hierarchy + * separators to get options from sub-passes (if any). + */ +#else + /** + * Returns the current value of an option. + */ +#endif + std::string get_option(const std::string &option) const; + +#ifdef QL_HIERARCHICAL_PASS_MANAGEMENT + /** + * Constructs this pass. During construction, the pass implementation may + * decide, based on its options, to become a group of passes or a normal + * pass. If it decides to become a group, the group may be introspected or + * modified by the user. The options are frozen after this, so set_option() + * will start throwing exceptions when called. construct() may be called any + * number of times, but becomes no-op after the first call. + */ + void construct(); + + /** + * Returns whether this pass has been constructed yet. + */ + bool is_constructed() const; + + /** + * Returns whether this pass has configurable sub-passes. + */ + bool is_group() const; + + /** + * Returns whether this pass is a simple group of which the sub-passes can + * be collapsed into the parent pass group without affecting the strategy. + */ + bool is_collapsible() const; + + /** + * Returns whether this is the root pass group in a pass manager. + */ + bool is_root() const; + + /** + * Returns whether this pass contains a conditionally-executed group. + */ + bool is_conditional() const; + + /** + * If this pass constructed into a group of passes, appends a pass to the + * end of its pass list. Otherwise, an exception is thrown. If type_name is + * empty or unspecified, a generic subgroup is added. Returns a reference to + * the constructed pass. + */ + Pass append_sub_pass( + const std::string &type_name = "", + const std::string &instance_name = "", + const std::map &options = {} + ); + + /** + * If this pass constructed into a group of passes, appends a pass to the + * beginning of its pass list. Otherwise, an exception is thrown. If + * type_name is empty or unspecified, a generic subgroup is added. Returns a + * reference to the constructed pass. + */ + Pass prefix_sub_pass( + const std::string &type_name = "", + const std::string &instance_name = "", + const std::map &options = {} + ); + + /** + * If this pass constructed into a group of passes, inserts a pass + * immediately after the target pass (named by instance). If target does not + * exist or this pass is not a group of sub-passes, an exception is thrown. + * If type_name is empty or unspecified, a generic subgroup is added. + * Returns a reference to the constructed pass. Periods may be used in + * target to traverse deeper into the pass hierarchy. + */ + Pass insert_sub_pass_after( + const std::string &target, + const std::string &type_name = "", + const std::string &instance_name = "", + const std::map &options = {} + ); + + /** + * If this pass constructed into a group of passes, inserts a pass + * immediately before the target pass (named by instance). If target does + * not exist or this pass is not a group of sub-passes, an exception is + * thrown. If type_name is empty or unspecified, a generic subgroup is + * added. Returns a reference to the constructed pass. Periods may be used + * in target to traverse deeper into the pass hierarchy. + */ + Pass insert_sub_pass_before( + const std::string &target, + const std::string &type_name = "", + const std::string &instance_name = "", + const std::map &options = {} + ); + + /** + * If this pass constructed into a group of passes, looks for the pass with + * the target instance name, and embeds it into a newly generated group. The + * group will assume the name of the original pass, while the original pass + * will be renamed as specified by sub_name. Note that this ultimately does + * not modify the pass order. If target does not exist or this pass is not a + * group of sub-passes, an exception is thrown. Returns a reference to the + * constructed group. Periods may be used in target to traverse deeper into + * the pass hierarchy. + */ + Pass group_sub_pass( + const std::string &target, + const std::string &sub_name = "main" + ); + + /** + * Like group_sub_pass(), but groups an inclusive range of passes into a + * group with the given name, leaving the original pass names unchanged. + * Periods may be used in from/to to traverse deeper into the pass + * hierarchy, but the hierarchy prefix must be the same for from and to. + */ + Pass group_sub_passes( + const std::string &from, + const std::string &to, + const std::string &group_name + ); + + /** + * If this pass constructed into a group of passes, looks for the pass with + * the target instance name, treats it as a generic group, and flattens its + * contained passes into the list of sub-passes of its parent. The names of + * the passes found in the collapsed subgroup are prefixed with name_prefix + * before they are added to the parent group. Note that this ultimately does + * not modify the pass order. If target does not exist, does not construct + * into a group of passes (construct() is called automatically), or this + * pass is not a group of sub-passes, an exception is thrown. Periods may be + * used in target to traverse deeper into the pass hierarchy. + */ + void flatten_subgroup( + const std::string &target, + const std::string &name_prefix = "" + ); + + /** + * If this pass constructed into a group of passes, returns a reference to + * the pass with the given instance name. If target does not exist or this + * pass is not a group of sub-passes, an exception is thrown. Periods may be + * used as hierarchy separators to get nested sub-passes. + */ + Pass get_sub_pass(const std::string &target) const; + + /** + * If this pass constructed into a group of passes, returns whether a + * sub-pass with the target instance name exists. Otherwise, an exception is + * thrown. Periods may be used in target to traverse deeper into the pass + * hierarchy. + */ + bool does_sub_pass_exist(const std::string &target) const; + + /** + * If this pass constructed into a group of passes, returns the total number + * of immediate sub-passes. Otherwise, an exception is thrown. + */ + size_t get_num_sub_passes() const; + + /** + * If this pass constructed into a group of passes, returns a reference to + * the list containing all the sub-passes. Otherwise, an exception is + * thrown. + */ + std::vector get_sub_passes() const; + + /** + * If this pass constructed into a group of passes, returns an indexable + * list of references to all immediate sub-passes with the given type. + * Otherwise, an exception is thrown. + */ + std::vector get_sub_passes_by_type(const std::string &target) const; + + /** + * If this pass constructed into a group of passes, removes the sub-pass + * with the target instance name. If target does not exist or this pass is + * not a group of sub-passes, an exception is thrown. Periods may be used in + * target to traverse deeper into the pass hierarchy. + */ + void remove_sub_pass(const std::string &target); + + /** + * If this pass constructed into a group of passes, removes all sub-passes. + * Otherwise, an exception is thrown. + */ + void clear_sub_passes(); +#endif + +}; + +} // namespace api +} // namespace ql diff --git a/include/ql/api/platform.h b/include/ql/api/platform.h new file mode 100644 index 000000000..f7863fc1d --- /dev/null +++ b/include/ql/api/platform.h @@ -0,0 +1,174 @@ +/** \file + * API header for loading and managing quantum platform information. + */ + +#pragma once + +#include "ql/plat/platform.h" +#include "ql/pmgr/manager.h" +#include "ql/api/declarations.h" +#include "ql/api/compiler.h" + +//============================================================================// +// W A R N I N G // +//----------------------------------------------------------------------------// +// Docstrings in this file must manually be kept in sync with // +// platform.i! This should be automated at some point, but isn't yet. // +//============================================================================// + +namespace ql { +namespace api { + +/** + * Quantum platform description. This describes everything that the compiler + * needs to know about the target quantum chip, instruments, etc. Platforms are + * created from JSON (+ comments) configuration files: there is no way to + * modify a platform using the API, and introspection is limited. The syntax of + * the platform configuration file is too extensive to describe here. + * + * In addition to the platform itself, the Platform object provides an interface + * for obtaining a Compiler object. This object describes the *strategy* for + * transforming the quantum algorithm to something that can be executed on the + * device described by the platform. You can think of the difference between + * them as the difference between a verb and a noun: the platform describes + * something that just exists, while the compilation strategy describes how to + * get there. + * + * The (initial) strategy can be set using a separate configuration file + * (compiler_config_file), directly from within the platform configuration file, + * or one can be inferred based on the previously hardcoded defaults. Unlike the + * platform itself however, an extensive API is available for adjusting the + * strategy as you see fit; just use get_compiler() to get a reference to a + * Compiler object that may be used for this purpose. If you don't do anything + * with the compiler methods and object, don't specify the compiler_config_file + * parameter, and the "eqasm_compiler" key of the platform configuration file + * refers to one of the previously-hardcoded compiler, a strategy will be + * generated to mimic the old logic for backward compatibility. + */ +class Platform { +private: + friend class Compiler; + friend class Kernel; + friend class Program; + friend class cQasmReader; + + /** + * The wrapped platform. + */ + ql::plat::PlatformRef platform; + + /** + * Wrapped pass manager. If this is non-null, it will be used for + * Program.compile for programs constructed using this platform. + */ + ql::pmgr::Ref pass_manager; + + /** + * Internal constructor to implement from_json. The dummy argument serves + * only to differentiate with the public constructors. + */ + Platform( + const std::string &name, + const std::string &platform_config_json, + const std::string &compiler_config, + bool dummy + ); + +public: + + /** + * The user-given name of the platform. + */ + std::string name; + + /** + * The architecture variant name or configuration file that the platform was + * loaded from. + */ + std::string config_file; + + /** + * Constructs a platform. name is any name the user wants to give to the + * platform; it is only used for report messages. platform_config must be + * a recognized architecture (variant) name, or must point to a JSON file + * that represents the platform directly. Optionally, compiler_config can be + * specified to override the compiler configuration specified by the + * platform (if any). + */ + Platform( + const std::string &name, + const std::string &platform_config, + const std::string &compiler_config = "" + ); + + /** + * Shorthand for constructing a platform. name is used both for the + * user-given name of the platform and for the architecture/variant + * configuration string. + */ + explicit Platform( + const std::string &name = "none" + ); + + /** + * Builds a platform from the given JSON *data*. + */ + static Platform from_json_string( + const std::string &name, + const std::string &platform_config_json, + const std::string &compiler_config = "" + ); + + /** + * Returns the default platform JSON configuration file for the given + * platform configuration string. This can be either an architecture name, + * an architecture variant name, or a JSON configuration filename. In the + * latter case, this just loads the file into a string and returns it. + */ + static std::string get_platform_json_string(const std::string &platform_config="none"); + + /** + * Returns the number of qubits in the platform. + */ + size_t get_qubit_number() const; + + /** + * Prints some basic information about the platform. + */ + void print_info() const; + + /** + * Returns the result of print_info() as a string. + */ + std::string dump_info() const; + + /** + * Old alias for dump_info(). Deprecated. + */ + std::string get_info() const; + + /** + * Returns whether a custom compiler configuration has been attached to this + * platform. When this is the case, programs constructed from this platform + * will use it to implement Program.compile(), rather than generating the + * compiler in-place from defaults and global options during the call. + */ + bool has_compiler(); + + /** + * Returns the custom compiler configuration associated with this platform. + * If no such configuration exists yet, the default one is created, + * attached, and returned. + */ + Compiler get_compiler(); + + /** + * Sets the compiler associated with this platform. Any programs constructed + * from this platform after this call will use the given compiler. + */ + void set_compiler(const Compiler &compiler); + +}; + +} // namespace api +} // namespace ql diff --git a/include/ql/api/program.h b/include/ql/api/program.h new file mode 100644 index 000000000..5b3f056c1 --- /dev/null +++ b/include/ql/api/program.h @@ -0,0 +1,207 @@ +/** \file + * API header for using quantum programs. + */ + +#pragma once + +#include "ql/ir/ir.h" +#include "ql/pmgr/manager.h" +#include "ql/api/declarations.h" +#include "ql/api/platform.h" +#include "ql/api/program.h" + +//============================================================================// +// W A R N I N G // +//----------------------------------------------------------------------------// +// Docstrings in this file must manually be kept in sync with // +// program.i! This should be automated at some point, but isn't yet. // +//============================================================================// + +namespace ql { +namespace api { + +/** + * Represents a complete quantum program. + */ +class Program { +private: + friend class Compiler; + friend class cQasmReader; + + /** + * The wrapped program. + */ + ql::ir::ProgramRef program; + + /** + * The pass manager that was associated with the platform when this program + * was constructed, if any. If set, it must be used for compile(). + * Otherwise, compile() should generate it in-place. + */ + ql::pmgr::Ref pass_manager; + +public: + + /** + * The name given to the program by the user. + */ + const std::string name; + + /** + * The platform associated with the program. + */ + const Platform platform; + + /** + * The number of (virtual) qubits allocated for the program. + */ + const size_t qubit_count; + + /** + * The number of classical integer registers allocated for the program. + */ + const size_t creg_count; + + /** + * The number of classical bit registers allocated for the program. + */ + const size_t breg_count; + + /** + * Creates a new program with the given name, using the given platform. + * The third, fourth, and fifth arguments optionally specify the desired + * number of qubits, classical integer registers, and classical bit + * registers. If not specified, the number of qubits is taken from the + * platform, and no classical or bit registers will be allocated. + */ + Program( + const std::string &name, + const Platform &platform, + size_t qubit_count = 0, + size_t creg_count = 0, + size_t breg_count = 0 + ); + + /** + * Adds an unconditionally-executed kernel to the end of the program. + */ + void add_kernel(const Kernel &k); + + /** + * Adds an unconditionally-executed subprogram to the end of the program. + */ + void add_program(const Program &p); + + /** + * Adds a conditionally-executed kernel to the end of the program. The + * kernel will be executed if the given classical condition evaluates to + * true. + */ + void add_if(const Kernel &k, const Operation &operation); + + /** + * Adds a conditionally-executed subprogram to the end of the program. The + * kernel will be executed if the given classical condition evaluates to + * true. + */ + void add_if(const Program &p, const Operation &operation); + + /** + * Adds two conditionally-executed kernels with inverted conditions to the + * end of the program. The first kernel will be executed if the given + * classical condition evaluates to true; the second kernel will be executed + * if it evaluates to false. + */ + void add_if_else(const Kernel &k_if, const Kernel &k_else, const Operation &operation); + + /** + * Adds two conditionally-executed subprograms with inverted conditions to + * the end of the program. The first kernel will be executed if the given + * classical condition evaluates to true; the second kernel will be executed + * if it evaluates to false. + */ + void add_if_else(const Program &p_if, const Program &p_else, const Operation &operation); + + /** + * Adds a kernel that will be repeated until the given classical condition + * evaluates to true. The kernel is executed at least once, since the + * condition is evaluated at the end of the loop body. + */ + void add_do_while(const Kernel &k, const Operation &operation); + + /** + * Adds a subprogram that will be repeated until the given classical + * condition evaluates to true. The subprogram is executed at least once, + * since the condition is evaluated at the end of the loop body. + */ + void add_do_while(const Program &p, const Operation &operation); + + /** + * Adds an unconditionally-executed kernel that will loop for the given + * number of iterations. + */ + void add_for(const Kernel &k, size_t iterations); + + /** + * Adds an unconditionally-executed subprogram that will loop for the given + * number of iterations. + */ + void add_for(const Program &p, size_t iterations); + + /** + * Sets sweep point information for the program. + */ + void set_sweep_points(const std::vector &sweep_points); + + /** + * Returns the configured sweep point information for the program. + */ + std::vector get_sweep_points() const; + + /** + * Sets the name of the file that the sweep points will be written to. + */ + void set_config_file(const std::string &config_file_name); + + /** + * Whether a custom compiler configuration has been attached to this + * program. When this is the case, it will be used to implement compile(), + * rather than generating the compiler in-place from defaults and global + * options during the call. + */ + bool has_compiler(); + + /** + * Returns the custom compiler configuration associated with this program. + * If no such configuration exists yet, the default one is created, + * attached, and returned. + */ + Compiler get_compiler(); + + /** + * Sets the compiler associated with this program. It will then be used for + * compile(). + */ + void set_compiler(const Compiler &compiler); + + /** + * Compiles the program. + */ + void compile(); + + /** + * Prints the interaction matrix for each kernel in the program. + */ + void print_interaction_matrix() const; + + /** + * Writes the interaction matrix for each kernel in the program to a file. + * This is one of the few functions that still uses the global output_dir + * option. + */ + void write_interaction_matrix() const; + +}; + +} // namespace api +} // namespace ql diff --git a/include/ql/api/unitary.h b/include/ql/api/unitary.h new file mode 100644 index 000000000..065325074 --- /dev/null +++ b/include/ql/api/unitary.h @@ -0,0 +1,60 @@ +/** \file + * API header for defining unitary gates for the unitary decomposition logic. + */ + +#pragma once + +#include "ql/com/unitary.h" +#include "ql/api/declarations.h" + +//============================================================================// +// W A R N I N G // +//----------------------------------------------------------------------------// +// Docstrings in this file must manually be kept in sync with // +// unitary.i! This should be automated at some point, but isn't yet. // +//============================================================================// + +namespace ql { +namespace api { + +/** + * Unitary matrix interface. + */ +class Unitary { +private: + friend class Kernel; + + /** + * The wrapped unitary gate. + */ + ql::utils::Ptr unitary; + +public: + + /** + * The name given to the unitary gate. + */ + const std::string name; + + /** + * Creates a unitary gate from the given row-major, square, unitary + * matrix. + */ + Unitary(const std::string &name, const std::vector> &matrix); + + /** + * Explicitly decomposes the gate. Does not need to be called; it will be + * called automatically when the gate is added to the kernel. + */ + void decompose(); + + /** + * Returns whether OpenQL was built with unitary decomposition support + * enabled. + */ + static bool is_decompose_support_enabled(); + +}; + +} // namespace api +} // namespace ql diff --git a/include/ql/arch/architecture.h b/include/ql/arch/architecture.h new file mode 100644 index 000000000..02cca21ca --- /dev/null +++ b/include/ql/arch/architecture.h @@ -0,0 +1,72 @@ +/** \file + * Structure for retaining information about a particular variant of an + * architecture. + */ + +#pragma once + +#include +#include "ql/utils/str.h" +#include "ql/utils/ptr.h" +#include "ql/utils/json.h" +#include "ql/arch/declarations.h" +#include "ql/arch/info_base.h" +#include "ql/pmgr/declarations.h" + +namespace ql { +namespace arch { + +/** + * Representation of some variant of some architecture family. + */ +class Architecture { +protected: + friend class Factory; + + /** + * Constructs an architecture. This should only be used by the factory. + */ + Architecture(const CInfoRef &family, const utils::Str &variant); + +public: + + /** + * Information structure for the architecture family. + */ + CInfoRef family; + + /** + * Name of the particular architecture variant. + */ + utils::Str variant; + + /** + * Returns a user-friendly name for this architecture variant. + */ + utils::Str get_friendly_name() const; + + /** + * Generates JSON for the default configuration of this architecture + * variant. + */ + utils::Str get_default_platform() const; + + /** + * Preprocesses/desugars the platform JSON data for this particular + * architecture variant. + */ + void preprocess_platform(utils::Json &data) const; + + /** + * Adds the default "backend passes" for this platform. Called by + * pmgr::Manager::from_defaults() when no compiler configuration file is + * specified. This typically includes at least the architecture-specific + * code generation pass, but anything after prescheduling and optimization + * is considered a backend pass. + */ + void populate_backend_passes(pmgr::Manager &manager) const; + +}; + +} // namespace arch +} // namespace ql diff --git a/include/ql/arch/cc/info.h b/include/ql/arch/cc/info.h new file mode 100644 index 000000000..9819fca8f --- /dev/null +++ b/include/ql/arch/cc/info.h @@ -0,0 +1,79 @@ +/** \file + * Defines information about the CC architecture. + */ + +#pragma once + +#include +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/set.h" +#include "ql/utils/ptr.h" +#include "ql/utils/json.h" +#include "ql/pmgr/manager.h" +#include "ql/arch/info_base.h" + +namespace ql { +namespace arch { +namespace cc { + +/** + * Architecture-specific information class for CC. + */ +class Info : public InfoBase { +public: + + /** + * Writes the documentation for this architecture to the given output + * stream. + */ + void dump_docs(std::ostream &os, const utils::Str &line_prefix) const override; + + /** + * Returns a user-friendly type name for this architecture. Used for + * documentation generation. + */ + utils::Str get_friendly_name() const override; + + /** + * Returns the name of the namespace for this architecture. + */ + utils::Str get_namespace_name() const override; + + /** + * Returns a list of strings accepted for the "eqasm_compiler" key in the + * platform configuration file. This can be more than one, to support both + * legacy (inconsistent) names and the new namespace names. The returned + * set must include at least the name of the namespace. + */ + utils::List get_eqasm_compiler_names() const override; + + /** + * Should generate a sane default platform JSON file for the given variant + * of this architecture. This JSON data will still be preprocessed by + * preprocess_platform(). + */ + utils::Str get_default_platform(const utils::Str &variant) const override; + + /** + * Preprocessing logic for the platform JSON configuration file. May be used + * to generate/expand certain things that are always the same for that + * platform, to save typing in the configuration file (and reduce the amount + * of mistakes made). + */ + void preprocess_platform(utils::Json &data, const utils::Str &variant) const override; + + /** + * Adds the default "backend passes" for this platform. Called by + * pmgr::Manager::from_defaults() when no compiler configuration file is + * specified. This typically includes at least the architecture-specific + * code generation pass, but anything after prescheduling and optimization + * is considered a backend pass. + */ + void populate_backend_passes(pmgr::Manager &manager, const utils::Str &variant) const override; + +}; + +} // namespace cc +} // namespace arch +} // namespace ql diff --git a/include/ql/arch/cc/pass/gen/vq1asm/vq1asm.h b/include/ql/arch/cc/pass/gen/vq1asm/vq1asm.h new file mode 100644 index 000000000..74e62203f --- /dev/null +++ b/include/ql/arch/cc/pass/gen/vq1asm/vq1asm.h @@ -0,0 +1,67 @@ +/** \file + * Defines the QuTech Central Controller Q1 processor assembly generator pass. + */ + +#pragma once + +#include "ql/com/options.h" +#include "ql/pmgr/pass_types/specializations.h" + +namespace ql { +namespace arch { +namespace cc { +namespace pass { +namespace gen { +namespace vq1asm { + +/** + * QuTech Central Controller Q1 processor assembly generator pass. + */ +class GenerateVQ1AsmPass : public pmgr::pass_types::ProgramTransformation { +protected: + + /** + * Dumps docs for the code generator. + */ + void dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Returns a user-friendly type name for this pass. + */ + utils::Str get_friendly_type() const override; + + /** + * Constructs a code generator. + */ + GenerateVQ1AsmPass( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Runs the code generator. + */ + utils::Int run( + const ir::ProgramRef &program, + const pmgr::pass_types::Context &context + ) const override; + +}; + +/** + * Shorthand for referring to the pass using namespace notation. + */ +using Pass = GenerateVQ1AsmPass; + +} // namespace vq1asm +} // namespace gen +} // namespace pass +} // namespace cc +} // namespace arch +} // namespace ql diff --git a/include/ql/arch/cc_light/info.h b/include/ql/arch/cc_light/info.h new file mode 100644 index 000000000..507804e8c --- /dev/null +++ b/include/ql/arch/cc_light/info.h @@ -0,0 +1,92 @@ +/** \file + * Defines information about the CC-light architecture. + */ + +#pragma once + +#include +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/set.h" +#include "ql/utils/ptr.h" +#include "ql/utils/json.h" +#include "ql/pmgr/manager.h" +#include "ql/arch/info_base.h" + +namespace ql { +namespace arch { +namespace cc_light { + +/** + * Architecture-specific information class for CC-light. + */ +class Info : public InfoBase { +public: + + /** + * Writes the documentation for this architecture to the given output + * stream. + */ + void dump_docs(std::ostream &os, const utils::Str &line_prefix) const override; + + /** + * Returns a user-friendly type name for this architecture. Used for + * documentation generation. + */ + utils::Str get_friendly_name() const override; + + /** + * Returns the name of the namespace for this architecture. + */ + utils::Str get_namespace_name() const override; + + /** + * Returns a list of strings accepted for the "eqasm_compiler" key in the + * platform configuration file. This can be more than one, to support both + * legacy (inconsistent) names and the new namespace names. The returned + * set must include at least the name of the namespace. + */ + utils::List get_eqasm_compiler_names() const override; + + /** + * Returns a list of platform variants for this architecture. For instance, + * the CC-light may control different kinds of chips (surface-5, surface-7, + * surface-17, etc), yet still in essence be a CC-light. Variants may be + * specified by the user by adding a dot-separated suffix to the + * "eqasm_compiler" key or architecture namespace. If specified, the variant + * must match a variant from this list. If not specified, the first variant + * returned by this function serves as the default value. + */ + utils::List get_variant_names() const override; + + /** + * Writes documentation for a particular variant of this architecture to the + * given output stream. + */ + void dump_variant_docs( + const utils::Str &variant, + std::ostream &os, + const utils::Str &line_prefix + ) const override; + + /** + * Should generate a sane default platform JSON file for the given variant + * of this architecture. This JSON data will still be preprocessed by + * preprocess_platform(). + */ + utils::Str get_default_platform(const utils::Str &variant) const override; + + /** + * Adds the default "backend passes" for this platform. Called by + * pmgr::Manager::from_defaults() when no compiler configuration file is + * specified. This typically includes at least the architecture-specific + * code generation pass, but anything after prescheduling and optimization + * is considered a backend pass. + */ + void populate_backend_passes(pmgr::Manager &manager, const utils::Str &variant) const override; + +}; + +} // namespace cc_light +} // namespace arch +} // namespace ql diff --git a/include/ql/arch/declarations.h b/include/ql/arch/declarations.h new file mode 100644 index 000000000..ba205dbf1 --- /dev/null +++ b/include/ql/arch/declarations.h @@ -0,0 +1,41 @@ +/** \file + * Structure for retaining information about a particular variant of an + * architecture. + */ + +#pragma once + +#include +#include "ql/utils/str.h" +#include "ql/utils/ptr.h" + +namespace ql { +namespace arch { + +// Forward declarations. +class Factory; +class InfoBase; +class Architecture; + +/** + * Shared pointer reference to an architecture information wrapper. + */ +using InfoRef = utils::Ptr; + +/** + * Immutable shared pointer reference to an architecture information wrapper. + */ +using CInfoRef = utils::Ptr; + +/** + * Shared pointer reference to an architecture variant wrapper. + */ +using ArchitectureRef = utils::Ptr; + +/** + * Immutable shared pointer reference to an architecture variant wrapper. + */ +using CArchitectureRef = utils::Ptr; + +} // namespace arch +} // namespace ql diff --git a/include/ql/arch/factory.h b/include/ql/arch/factory.h new file mode 100644 index 000000000..7ce7b6476 --- /dev/null +++ b/include/ql/arch/factory.h @@ -0,0 +1,92 @@ +/** \file + * Resource factory implementation. + */ + +#pragma once + +#include +#include "ql/utils/ptr.h" +#include "ql/utils/str.h" +#include "ql/utils/pair.h" +#include "ql/utils/map.h" +#include "ql/utils/json.h" +#include "ql/arch/info_base.h" +#include "ql/arch/architecture.h" + +namespace ql { +namespace arch { + +/** + * Factory class for constructing architecture wrappers. + */ +class Factory { +private: + + /** + * Map from architecture namespace name to a constructor function for that + * particular architecture type. + */ + utils::Map namespace_names = {}; + + /** + * Map from "eqasm_compiler" key value to a constructor function for that + * particular architecture type. + */ + utils::Map eqasm_compiler_names = {}; + +public: + + /** + * Constructs a default architecture factory for OpenQL. + */ + Factory(); + +private: + + /** + * Implementation of build_from_namespace() and build_from_eqasm_compiler(), + * using the given map for the lookup. + */ + CArchitectureRef build_from_map( + const utils::Map &map, + const utils::Str &str + ) const; + +public: + + /** + * Registers an architecture class with the given type name. + */ + template + void register_architecture() { + InfoRef architecture; + architecture.emplace(); + namespace_names.set(architecture->get_namespace_name()) = architecture; + for (const auto &name : architecture->get_eqasm_compiler_names()) { + eqasm_compiler_names.set(name) = architecture; + } + } + + /** + * Builds an architecture from an "eqasm_compiler" name. Returns a reference + * to the architecture variant object if one was found. Otherwise, an empty + * reference is returned. + */ + CArchitectureRef build_from_namespace(const utils::Str &namspace) const; + + /** + * Builds an architecture from an "eqasm_compiler" name. Returns a reference + * to the architecture variant object if one was found. Otherwise, an empty + * reference is returned. + */ + CArchitectureRef build_from_eqasm_compiler(const utils::Str &eqasm_compiler) const; + + /** + * Dumps documentation for all architectures known by this factory. + */ + void dump_architectures(std::ostream &os = std::cout, const utils::Str &line_prefix = "") const; + +}; + +} // namespace arch +} // namespacq ql diff --git a/include/ql/arch/info_base.h b/include/ql/arch/info_base.h new file mode 100644 index 000000000..d14fb432b --- /dev/null +++ b/include/ql/arch/info_base.h @@ -0,0 +1,99 @@ +/** \file + * Base class for common architecture-specific logic. + */ + +#pragma once + +#include +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/list.h" +#include "ql/utils/ptr.h" +#include "ql/utils/json.h" +#include "ql/pmgr/declarations.h" +#include "ql/arch/declarations.h" + +namespace ql { +namespace arch { + +/** + * Base class for architecture information retrieval and some miscellaneous + * architecture-specific logic. + */ +class InfoBase { +public: + + /** + * Writes the documentation for this architecture to the given output + * stream. + */ + virtual void dump_docs(std::ostream &os, const utils::Str &line_prefix) const = 0; + + /** + * Returns a user-friendly type name for this architecture. Used for + * documentation generation. + */ + virtual utils::Str get_friendly_name() const = 0; + + /** + * Returns the name of the namespace for this architecture. + */ + virtual utils::Str get_namespace_name() const = 0; + + /** + * Returns a list of strings accepted for the "eqasm_compiler" key in the + * platform configuration file. This can be more than one, to support both + * legacy (inconsistent) names and the new namespace names. The returned + * set must include at least the name of the namespace. + */ + virtual utils::List get_eqasm_compiler_names() const; + + /** + * Returns a list of platform variants for this architecture. For instance, + * the CC-light may control different kinds of chips (surface-5, surface-7, + * surface-17, etc), yet still in essence be a CC-light. Variants may be + * specified by the user by adding a dot-separated suffix to the + * "eqasm_compiler" key or architecture namespace. If specified, the variant + * must match a variant from this list. If not specified, the first variant + * returned by this function serves as the default value. + */ + virtual utils::List get_variant_names() const; + + /** + * Writes documentation for a particular variant of this architecture to the + * given output stream. + */ + virtual void dump_variant_docs( + const utils::Str &variant, + std::ostream &os, + const utils::Str &line_prefix + ) const; + + /** + * Should generate a sane default platform JSON file for the given variant + * of this architecture. This JSON data will still be preprocessed by + * preprocess_platform(). + */ + virtual utils::Str get_default_platform(const utils::Str &variant) const = 0; + + /** + * Preprocessing logic for the platform JSON configuration file. May be used + * to generate/expand certain things that are always the same for that + * platform, to save typing in the configuration file (and reduce the amount + * of mistakes made). + */ + virtual void preprocess_platform(utils::Json &data, const utils::Str &variant) const; + + /** + * Adds the default "backend passes" for this platform. Called by + * pmgr::Manager::from_defaults() when no compiler configuration file is + * specified. This typically includes at least the architecture-specific + * code generation pass, but anything after prescheduling and optimization + * is considered a backend pass. + */ + virtual void populate_backend_passes(pmgr::Manager &manager, const utils::Str &variant) const; + +}; + +} // namespace arch +} // namespace ql diff --git a/include/ql/arch/none/info.h b/include/ql/arch/none/info.h new file mode 100644 index 000000000..10dd2845d --- /dev/null +++ b/include/ql/arch/none/info.h @@ -0,0 +1,66 @@ +/** \file + * Defines information about the no-op architecture. + */ + +#pragma once + +#include +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/set.h" +#include "ql/utils/ptr.h" +#include "ql/utils/json.h" +#include "ql/pmgr/manager.h" +#include "ql/arch/info_base.h" + +namespace ql { +namespace arch { +namespace none { + +/** + * No-op architecture-specific information class. + */ +class Info : public InfoBase { +public: + + /** + * Writes the documentation for this architecture to the given output + * stream. + */ + void dump_docs(std::ostream &os, const utils::Str &line_prefix) const override; + + /** + * Returns a user-friendly type name for this architecture. Used for + * documentation generation. + */ + utils::Str get_friendly_name() const override; + + /** + * Returns the name of the namespace for this architecture. + */ + utils::Str get_namespace_name() const override; + + /** + * Returns a list of strings accepted for the "eqasm_compiler" key in the + * platform configuration file. This can be more than one, to support both + * legacy (inconsistent) names and the new namespace names. The returned + * set must include at least the name of the namespace. + */ + utils::List get_eqasm_compiler_names() const override; + + /** + * Should generate a sane default platform JSON file, for when the user + * constructs a Platform without JSON data. This is done by specifying an + * architecture namespace identifier instead of a JSON filename. Optionally, + * the user may specify a variant suffix, separated using a dot, to select + * a variation of the architecture; for instance, for CC-light, there might + * be variations for surface-5, surface-7, and surface-17. This JSON data + * will still be preprocessed by preprocess_platform(). + */ + utils::Str get_default_platform(const utils::Str &variant) const override; + +}; + +} // namespace none +} // namespace arch +} // namespace ql diff --git a/include/ql/com/interaction_matrix.h b/include/ql/com/interaction_matrix.h new file mode 100644 index 000000000..b60898142 --- /dev/null +++ b/include/ql/com/interaction_matrix.h @@ -0,0 +1,75 @@ +/** \file + * Qubit interaction matrix generator. + */ + +#pragma once + +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/vec.h" +#include "ql/ir/ir.h" + +namespace ql { +namespace com { + +// TODO JvS: this is really just an "advanced" metric, and should be written +// as such sometime. + +/** + * Utility for counting the number of two-qubit gates, grouped by their qubit + * operands. + */ +class InteractionMatrix { +private: + + /** + * Shorthand for the matrix type. + */ + using Matrix = utils::Vec>; + + /** + * Size of the matrix, i.e. the number of qubits. + */ + const utils::UInt size; + + /** + * Square matrix of unsigned integers, representing the number of two-qubit + * gates spanning the indexed qubits. Operand order is not respected; the + * matrix is symmetric. + */ + Matrix matrix; + +public: + + /** + * Computes the interaction matrix for the given kernel. + */ + InteractionMatrix(const ir::KernelRef &kernel); + + /** + * Returns the embedded matrix. + */ + const Matrix &get_matrix() const; + + /** + * Returns the matrix as a string. + */ + utils::Str get_string() const; + + /** + * Constructs interaction matrices for each kernel in the program, and + * reports the results to the given output stream. + */ + static void dump_for_program(const ir::ProgramRef &program, std::ostream &os=std::cout); + + /** + * Same as dump_for_program(), but writes the result to files in the + * current globally-configured output directory, using the names + * "InteractionMatrix.dat". + */ + static void write_for_program(const utils::Str &output_prefix, const ir::ProgramRef &program); + +}; + +} // namespace com +} // namespace ql diff --git a/include/ql/com/metrics.h b/include/ql/com/metrics.h new file mode 100644 index 000000000..d1cb0da33 --- /dev/null +++ b/include/ql/com/metrics.h @@ -0,0 +1,199 @@ +/** \file + * Utility functions for extracting statistics/metrics from programs and + * kernels. + * + * Usage is for instance com::metrics::compute(kernel). + */ + +#pragma once + +#include "ql/utils/num.h" +#include "ql/utils/map.h" +#include "ql/utils/exception.h" +#include "ql/ir/ir.h" + +namespace ql { +namespace com { +namespace metrics { + +/** + * Base class for a metric. T is the type returned when the metric is computed. + */ +template +class Metric { +public: + + /** + * The type returned by get_result(). + */ + using ReturnType = T; + + /** + * Updates the metric using the given kernel. Default implementation throws + * an unimplemented exception. + */ + virtual void process_gate(const ir::GateRef &gate) { + throw utils::Exception("metric is not implemented for gates"); + } + + /** + * Updates the metric using the given kernel. Default implementation just + * calls process_gate for each contained gate. + */ + virtual void process_kernel(const ir::KernelRef &kernel) { + for (const auto &gate : kernel->gates) { + process_gate(gate); + } + } + + /** + * Updates the metric using the given program. Default implementation just + * calls process_kernel for each contained kernel. + */ + virtual void process_program(const ir::ProgramRef &program) { + for (const auto &kernel : program->kernels) { + process_kernel(kernel); + } + } + + /** + * Virtual destructor. + */ + virtual ~Metric() = default; + + /** + * Returns the results gathered thus far. + */ + virtual T get_result() = 0; + +}; + +/** + * Computes the given metric for the given gate. + */ +template +typename M::ReturnType compute(const ir::GateRef &gate) { + M metric; + metric.process_gate(gate); + return metric.get_result(); +} + +/** + * Computes the given metric for the given kernel. + */ +template +typename M::ReturnType compute(const ir::KernelRef &kernel) { + M metric; + metric.process_kernel(kernel); + return metric.get_result(); +} + +/** + * Computes the given metric for the given program. + */ +template +typename M::ReturnType compute(const ir::ProgramRef &program) { + M metric; + metric.process_program(program); + return metric.get_result(); +} + +/** + * A metric that just returns a simple C++ primitive value with the given + * initial value. + */ +template +class SimpleValueMetric : public Metric { +protected: + + /** + * The metric as computed thus far. + */ + T value = INITIAL_VALUE; + +public: + + /** + * Returns the results gathered thus far. + */ + T get_result() final { + return value; + } + +}; + +/** + * A metric that just returns a C++ class using the default constructor as the + * default value. + */ +template +class SimpleClassMetric : public Metric { +protected: + + /** + * The metric as computed thus far. + */ + T value{}; + +public: + + /** + * Returns the results gathered thus far. + */ + T get_result() final { + return value; + } + +}; + +/** + * A metric that counts the number of classical operations. + */ +class ClassicalOperationCount : public SimpleValueMetric { +public: + void process_gate(const ir::GateRef &gate) override; +}; + +/** + * A metric that counts the number of quantum gates. + */ +class QuantumGateCount : public SimpleValueMetric { +public: + void process_gate(const ir::GateRef &gate) override; +}; + +/** + * A metric that counts the number of multi-qubit quantum gates. + */ +class MultiQubitGateCount : public SimpleValueMetric { +public: + void process_gate(const ir::GateRef &gate) override; +}; + +/** + * A metric that counts the number of times each qubit is used. + */ +class QubitUsageCount : public SimpleClassMetric> { +public: + void process_gate(const ir::GateRef &gate) override; +}; + +/** + * A metric that counts the number of cycles each qubit is used for. + */ +class QubitUsedCycleCount : public SimpleClassMetric> { +public: + void process_kernel(const ir::KernelRef &kernel) override; +}; + +/** + * A metric that returns the duration of a scheduled kernel in cycles. + */ +class Latency : public SimpleValueMetric { +public: + void process_kernel(const ir::KernelRef &kernel) override; +}; + +} // namespace metrics +} // namespace com +} // namespace ql diff --git a/include/ql/com/options.h b/include/ql/com/options.h new file mode 100644 index 000000000..2c1058371 --- /dev/null +++ b/include/ql/com/options.h @@ -0,0 +1,38 @@ +/** \file + * Provides access to OpenQL's global options. + */ + +#pragma once + +#include "ql/utils/options.h" +#include "ql/utils/compat.h" + +namespace ql { +namespace com { +namespace options { + +/** + * Makes a new options record for OpenQL. + */ +utils::Options make_ql_options(); + +/** + * Global options object for all of OpenQL. + */ +QL_GLOBAL extern utils::Options global; + +/** + * Convenience function for getting an option value as a string from the global + * options record. + */ +const utils::Str &get(const utils::Str &key); + +/** + * Convenience function for setting an option value for the global options + * record. + */ +void set(const utils::Str &key, const utils::Str &value); + +} // namespace options +} // namespace com +} // namespace ql diff --git a/include/ql/com/qubit_mapping.h b/include/ql/com/qubit_mapping.h new file mode 100644 index 000000000..ceb8bb3f8 --- /dev/null +++ b/include/ql/com/qubit_mapping.h @@ -0,0 +1,170 @@ +/** \file + * Virtual to real qubit mapping state tracker. + */ + +#pragma once + +#include +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/vec.h" + +namespace ql { +namespace com { + +/** + * Value used to specify that a virtual qubit has no real qubit associated with + * it. + */ +const utils::UInt UNDEFINED_QUBIT = utils::MAX; + +/** + * The state of a real qubit. + */ +enum struct QubitState { + + /** + * Qubit has no relevant state needing preservation, i.e. is garbage. + */ + NONE, + + /** + * Qubit has initialized state suitable for replacing swap by move. + */ + INITIALIZED, + + /** + * Qubit has a unique state which must be preserved. + */ + LIVE + +}; + +/** + * Converts QubitState to a string. + */ +std::ostream &operator<<(std::ostream &os, QubitState qs); + +/** + * Virtual to real qubit mapping. Maintains the mapping (in both directions), as + * well as information about whether the state of a real qubit is in use or not. + */ +class QubitMapping { +private: + + /** + * Size of the map. + */ + utils::UInt nq; + + /** + * Maps virtual qubit indices to real qubit indices or UNDEFINED_QUBIT. + */ + utils::Vec virt_to_real; + + /** + * Maps real qubit indices to their state. + */ + utils::Vec real_state; + +public: + + /** + * Creates a virtual to real qubit map with the given number of qubits. + * + * The mapping starts off undefined for all virtual qubits, unless + * one_to_one is set, in which case virtual qubit i maps to real qubit i for + * all qubits. The state of the qubits is initialized as specified. + */ + explicit QubitMapping( + utils::UInt num_qubits = 0, + utils::Bool one_to_one = false, + QubitState initial_state = QubitState::NONE + ); + + /** + * Resizes/reinitializes the map. + * + * Newly added qubits start off with an undefined mapping, unless one_to_one + * is set, in which case virtual qubit i maps to real qubit i for all + * qubits. The state of the new qubits is initialized as specified. + */ + void resize( + utils::UInt num_qubits, + utils::Bool one_to_one = false, + QubitState initial_state = QubitState::NONE + ); + + /** + * Map virtual qubit index to real qubit index. + */ + utils::UInt &operator[](utils::UInt v); + + /** + * Map virtual qubit index to real qubit index. + */ + const utils::UInt &operator[](utils::UInt v) const; + + /** + * Returns the underlying virtual to real qubit vector. + */ + const utils::Vec &get_virt_to_real() const; + + /** + * Map real qubit to the virtual qubit index that is mapped to it (i.e. + * backward map). When none, return UNDEFINED_QUBIT. This currently loops + * over all qubits, so it isn't particularly fast. + */ + utils::UInt get_virtual(utils::UInt real) const; + + /** + * Returns the current state for the given real qubit. + */ + QubitState get_state(utils::UInt real) const; + + /** + * Sets the state for the given real qubit. + */ + void set_state(utils::UInt real, QubitState state); + + /** + * Returns the underlying qubit state vector. + */ + const utils::Vec &get_state() const; + + /** + * Allocate a real qubit for the given unmapped virtual qubit. + */ + utils::UInt allocate(utils::UInt virt); + + /** + * Updates the mapping to reflect a swap for the given real qubit indices, + * so when v0 was in r0 and v1 was in r1, then v0 is now in r1 and v1 is now + * in r0. + */ + void swap(utils::UInt r0, utils::UInt r1); + + /** + * Returns a string representation of the state of the given real qubit. + */ + utils::Str real_to_string(utils::UInt real) const; + + /** + * Returns a string representation of the state of the given virtual qubit. + */ + utils::Str virtual_to_string(utils::UInt virt) const; + + /** + * Returns a string representation of the virtual to physical qubit mapping. + */ + utils::Str mapping_to_string() const; + + /** + * Dumps the state of this mapping to the given stream. + */ + void dump_state(std::ostream &os=std::cout, const utils::Str &line_prefix="") const; + +}; + +} // namespace com +} // namespace ql diff --git a/include/ql/com/unitary.h b/include/ql/com/unitary.h new file mode 100644 index 000000000..190c74c49 --- /dev/null +++ b/include/ql/com/unitary.h @@ -0,0 +1,81 @@ +/** \file + * Unitary matrix (decomposition) implementation. + */ + +#pragma once + +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/vec.h" +#include "ql/ir/ir.h" + +namespace ql { +namespace com { + +/** + * Unitary matrix decomposition class. + * + * This is actually just a wrapper for the real thing, which lives entirely + * inside unitary.cc. This allows the unitary decomposition logic to be disabled + * during compilation by substituting a mocked implementation, reducing + * compilation times immensely, because Eigen is absolutely massive. + */ +class Unitary { +private: + + /** + * Whether this unitary gate has been decomposed yet. + */ + utils::Bool decomposed; + + /** + * The list of decomposed instructions. + * + * TODO: document and refactor, using Reals to communicate enum information + * is not okay... + */ + utils::Vec instruction_list; + +public: + + /** + * The name of the unitary gate. + */ + const utils::Str name; + + /** + * The unitary matrix in row-major form. + */ + const utils::Vec array; + + /** + * Creates a unitary gate with the given name and row-major unitary matrix. + */ + Unitary(const utils::Str &name, const utils::Vec &array); + + /** + * Returns the number of elements in the incoming matrix. + */ + utils::UInt size() const; + + /** + * Explicitly runs the matrix decomposition algorithm. Used to be required, + * nowadays is called implicitly by get_decomposition() if not done explicitly. + */ + void decompose(); + + /** + * Returns whether unitary decomposition support was enabled in this build + * of OpenQL. + */ + static utils::Bool is_decompose_support_enabled(); + + /** + * Returns the decomposed circuit. + */ + ir::GateRefs get_decomposition(const utils::Vec &qubits); + +}; + +} // namespace com +} // namespace ql diff --git a/include/ql/ir/bundle.h b/include/ql/ir/bundle.h new file mode 100644 index 000000000..fa5c7ebc3 --- /dev/null +++ b/include/ql/ir/bundle.h @@ -0,0 +1,74 @@ +/** \file + * Common IR implementation. + */ + +#pragma once + +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/list.h" +#include "ql/ir/gate.h" +#include "ql/ir/kernel.h" + +namespace ql { +namespace ir { + +/** + * Cycle numbers in OpenQL, for some historic reason, start at 1 (see discussion + * in PR #398). + */ +static const utils::UInt FIRST_CYCLE = 1; + +/** + * A bundle of gates that start in the same cycle. + */ +struct Bundle { + + /** + * The start cycle for all gates in this bundle. + */ + utils::UInt start_cycle = FIRST_CYCLE; + + /** + * The maximum gate duration of the gates in this bundle. + */ + utils::UInt duration_in_cycles = 0; + + /** + * The list of parallel gates in this bundle. + */ + utils::List gates = {}; + +}; + +/** +* A list of bundles. Note that subsequent bundles can overlap in time. + */ +using Bundles = utils::List; + +/** + * Create a circuit with valid cycle values from the bundled internal + * representation. The bundles are assumed to be ordered by cycle number. + */ +GateRefs circuiter(const Bundles &bundles); + +/** + * Create a bundled-qasm external representation from the bundled internal + * representation. + */ +utils::Str qasm(const Bundles &bundles); + +/** + * Create a bundled internal representation from the given kernel with valid + * cycle information. + */ +Bundles bundler(const KernelRef &kernel); + +/** + * Print the bundles with an indication (taken from 'at') from where this + * function was called. + */ +void debug_bundles(const utils::Str &at, const Bundles &bundles); + +} // namespace ir +} // namespace ql diff --git a/include/ql/ir/classical.h b/include/ql/ir/classical.h new file mode 100644 index 000000000..a2bbf7230 --- /dev/null +++ b/include/ql/ir/classical.h @@ -0,0 +1,89 @@ +/** \file + * Classical operation implementation. + */ + +#pragma once + +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/vec.h" +#include "ql/utils/tree.h" + +#include "ql/ir/gate.h" + +namespace ql { +namespace ir { + +enum class ClassicalOperationType { + ARITHMETIC, RELATIONAL, BITWISE +}; + +enum class ClassicalOperandType { + REGISTER, VALUE +}; + +class ClassicalValue; +class ClassicalRegister; + +class ClassicalOperand : public utils::Node { +public: + virtual ClassicalOperandType type() const = 0; + virtual void print() const = 0; + ClassicalValue &as_value(); + const ClassicalValue &as_value() const; + ClassicalRegister &as_register(); + const ClassicalRegister &as_register() const; +}; + +class ClassicalValue : public ClassicalOperand { +public: + utils::Int value; + ClassicalValue(utils::Int val); + ClassicalValue(const ClassicalValue &cv); + ClassicalOperandType type() const override; + void print() const override; +}; + +class ClassicalRegister : public ClassicalOperand { +public: + utils::UInt id; + ClassicalRegister(utils::UInt id); + ClassicalRegister(const ClassicalRegister &c); + ClassicalOperandType type() const override; + void print() const override; +}; + +class ClassicalOperation : public utils::Node { +public: + utils::Str operation_name; + utils::Str inv_operation_name; + ClassicalOperationType operation_type; + utils::Any operands; + + ClassicalOperation(const ClassicalRegister &l, const utils::Str &op, const ClassicalRegister &r); + + // used for assign + ClassicalOperation(const ClassicalRegister &l); + + // used for initializing with an imm + ClassicalOperation(const ClassicalValue &v); + + // used for initializing with an imm + ClassicalOperation(utils::Int val); + + ClassicalOperation(const utils::Str &op, const ClassicalRegister &r); +}; + +namespace gate_types { + +class Classical : public Gate { +public: + Classical(const ClassicalRegister &dest, const ClassicalOperation &oper); + Classical(const utils::Str &operation); + Instruction qasm() const override; + GateType type() const override; +}; + +} // namespace gates +} // namespace ir +} // namespace ql diff --git a/include/ql/ir/gate.h b/include/ql/ir/gate.h new file mode 100644 index 000000000..a9cdc3add --- /dev/null +++ b/include/ql/ir/gate.h @@ -0,0 +1,352 @@ +/** \file + * Quantum gate abstraction implementation. + */ + +#pragma once + +#include "ql/utils/str.h" +#include "ql/utils/vec.h" +#include "ql/utils/json.h" +#include "ql/utils/misc.h" +#include "ql/utils/tree.h" + +namespace ql { +namespace ir { + +typedef utils::Str Instruction; + +// gate types +enum class GateType { + IDENTITY, + HADAMARD, + PAULI_X, + PAULI_Y, + PAULI_Z, + PHASE, + PHASE_DAG, + T, + T_DAG, + RX90, + MRX90, + RX180, + RY90, + MRY90, + RY180, + RX, + RY, + RZ, + PREP_Z, + CNOT, + CPHASE, + TOFFOLI, + CUSTOM, + COMPOSITE, + MEASURE, + DISPLAY, + DISPLAY_BINARY, + NOP, + DUMMY, + SWAP, + WAIT, + CLASSICAL +}; + +std::ostream &operator<<(std::ostream &os, GateType gate_type); + +/* + * additional definitions for describing conditional gates + */ +enum class ConditionType { + // 0 operands: + ALWAYS, NEVER, + // 1 operand: + UNARY, NOT, + // 2 operands + AND, NAND, OR, NOR, XOR, NXOR +}; + +std::ostream &operator<<(std::ostream &os, ConditionType condition_type); + +const utils::UInt MAX_CYCLE = utils::MAX; + +struct SwapParamaters { + utils::Bool part_of_swap = false; + // at the end of the swap r0 stores v0 and r1 stores v1 + utils::Int r0 = -1; + utils::Int r1 = -1; + utils::Int v0 = -1; + utils::Int v1 = -1; + + // default constructor + SwapParamaters() {} + + // initializer list + SwapParamaters(utils::Bool _part_of_swap, utils::Int _r0, utils::Int _r1, utils::Int _v0, utils::Int _v1) + : part_of_swap(_part_of_swap), r0(_r0), r1(_r1), v0(_v0), v1(_v1) + {} +}; + +/** + * gate interface + */ +class Gate : public utils::Node { +public: + utils::Str name; + utils::Vec operands; // qubit operands + utils::Vec creg_operands; + utils::Vec breg_operands; // bit operands e.g. assigned to by measure; cond_operands are separate + utils::Vec cond_operands; // 0, 1 or 2 bit operands of condition + ConditionType condition = ConditionType::ALWAYS; // defines condition and by that number of bit operands of condition + SwapParamaters swap_params; // if the gate is part of a swap/move, this will contain the real and virtual qubits involved + utils::Int int_operand = 0; // FIXME: move to class 'classical' + utils::UInt duration = 0; + utils::Real angle = 0.0; // for arbitrary rotations + utils::UInt cycle = MAX_CYCLE; // cycle after scheduling; MAX_CYCLE indicates undefined + virtual ~Gate() = default; + virtual Instruction qasm() const = 0; + virtual GateType type() const = 0; + utils::Bool is_conditional() const; // whether gate has condition that is NOT cond_always + Instruction cond_qasm() const; // returns the condition expression in qasm layout + static utils::Bool is_valid_cond(ConditionType condition, const utils::Vec &cond_operands); +}; + +using GateRef = utils::One; +using GateRefs = utils::Any; + +/****************************************************************************\ +| Standard gates +\****************************************************************************/ + +namespace gate_types { + +class Identity : public Gate { +public: + explicit Identity(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class Hadamard : public Gate { +public: + explicit Hadamard(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class Phase : public Gate { +public: + explicit Phase(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class PhaseDag : public Gate { +public: + explicit PhaseDag(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class RX : public Gate { +public: + RX(utils::UInt q, utils::Real theta); + Instruction qasm() const override; + GateType type() const override; +}; + +class RY : public Gate { +public: + RY(utils::UInt q, utils::Real theta); + Instruction qasm() const override; + GateType type() const override; +}; + +class RZ : public Gate { +public: + RZ(utils::UInt q, utils::Real theta); + Instruction qasm() const override; + GateType type() const override; +}; + +class T : public Gate { +public: + explicit T(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class TDag : public Gate { +public: + explicit TDag(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class PauliX : public Gate { +public: + explicit PauliX(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class PauliY : public Gate { +public: + explicit PauliY(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class PauliZ : public Gate { +public: + explicit PauliZ(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class RX90 : public Gate { +public: + explicit RX90(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class MRX90 : public Gate { +public: + explicit MRX90(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class RX180 : public Gate { +public: + explicit RX180(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class RY90 : public Gate { +public: + explicit RY90(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class MRY90 : public Gate { +public: + explicit MRY90(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class RY180 : public Gate { +public: + explicit RY180(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class Measure : public Gate { +public: + explicit Measure(utils::UInt q); + Measure(utils::UInt q, utils::UInt c); + Instruction qasm() const override; + GateType type() const override; +}; + +class PrepZ : public Gate { +public: + explicit PrepZ(utils::UInt q); + Instruction qasm() const override; + GateType type() const override; +}; + +class CNot : public Gate { +public: + CNot(utils::UInt q1, utils::UInt q2); + Instruction qasm() const override; + GateType type() const override; +}; + +class CPhase : public Gate { +public: + CPhase(utils::UInt q1, utils::UInt q2); + Instruction qasm() const override; + GateType type() const override; +}; + +class Toffoli : public Gate { +public: + Toffoli(utils::UInt q1, utils::UInt q2, utils::UInt q3); + Instruction qasm() const override; + GateType type() const override; +}; + +class Nop : public Gate { +public: + Nop(); + Instruction qasm() const override; + GateType type() const override; +}; + +class Swap : public Gate { +public: + Swap(utils::UInt q1, utils::UInt q2); + Instruction qasm() const override; + GateType type() const override; +}; + +/****************************************************************************\ +| Special gates +\****************************************************************************/ + +class Wait : public Gate { +public: + utils::UInt duration_in_cycles; + + Wait(utils::Vec qubits, utils::UInt d, utils::UInt dc); + Instruction qasm() const override; + GateType type() const override; +}; + +class Source : public Gate { +public: + Source(); + Instruction qasm() const override; + GateType type() const override; +}; + +class Sink : public Gate { +public: + Sink(); + Instruction qasm() const override; + GateType type() const override; +}; + +class Display : public Gate { +public: + Display(); + Instruction qasm() const override; + GateType type() const override; +}; + +class Custom : public Gate { +public: + explicit Custom(const utils::Str &name); + void load(const utils::Json &instr, utils::UInt num_qubits, utils::UInt cycle_time); + void print_info() const; + Instruction qasm() const override; + GateType type() const override; +}; + +class Composite : public Custom { +public: + GateRefs gs; + explicit Composite(const utils::Str &name); + Composite(const utils::Str &name, const GateRefs &seq); + Instruction qasm() const override; + GateType type() const override; +}; + +} // namespace gates +} // namespace ir +} // namespace ql diff --git a/include/ql/ir/ir.h b/include/ql/ir/ir.h new file mode 100644 index 000000000..c3204f035 --- /dev/null +++ b/include/ql/ir/ir.h @@ -0,0 +1,11 @@ +/** \file + * Utility file for including all IR classes. + */ + +#pragma once + +#include "ql/ir/gate.h" +#include "ql/ir/classical.h" +#include "ql/ir/bundle.h" +#include "ql/ir/kernel.h" +#include "ql/ir/program.h" diff --git a/src/kernel.h b/include/ql/ir/kernel.h similarity index 65% rename from src/kernel.h rename to include/ql/ir/kernel.h index fee700d6d..71dc73fb3 100644 --- a/src/kernel.h +++ b/include/ql/ir/kernel.h @@ -4,20 +4,38 @@ #pragma once -#include "utils/num.h" -#include "utils/str.h" -#include "utils/vec.h" -#include "utils/opt.h" -#include "gate.h" -#include "circuit.h" -#include "classical.h" -#include "hardware_configuration.h" -#include "unitary.h" -#include "platform.h" +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/vec.h" +#include "ql/utils/opt.h" +#include "ql/plat/platform.h" +#include "ql/ir/gate.h" +#include "ql/ir/classical.h" namespace ql { -enum class kernel_type_t { +// FIXME: this should not be here. IR headers should not have to include +// anything not from ql::ir, ql::plat, or ql::utils; they should be dumb +// containers. But decomposition etc. is still part of Kernel. +namespace com { +class Unitary; +} // namespace com + +namespace ir { + +/** + * Generates cQASM for a given circuit. + */ +utils::Str qasm(const GateRefs &c); + +/** + * The role of a kernel in control-flow representation. + * + * FIXME: this representation of control-flow, while complete, is very poorly + * engineered. The recursive structure is flattened and thus difficult to + * deduce, there is redundant data everywhere, etc. + */ +enum class KernelType { STATIC, FOR_START, FOR_END, DO_WHILE_START, DO_WHILE_END, @@ -25,41 +43,118 @@ enum class kernel_type_t { ELSE_START, ELSE_END }; -class quantum_kernel { +/** + * A single kernel of a program, a.k.a. a basic block. + */ +class Kernel : public utils::Node { public: // FIXME: should be private - utils::Str name; - utils::UInt iterations; - utils::UInt qubit_count; - utils::UInt creg_count; - utils::UInt breg_count; - kernel_type_t type; - circuit c; - utils::Bool cycles_valid; // used in bundler to check if kernel has been scheduled - utils::Opt br_condition; - utils::UInt cycle_time; // FIXME HvS just a copy of platform.cycle_time - instruction_map_t instruction_map; - utils::Vec cond_operands; // see gate interface: condition mode to make new gates conditional - cond_type_t condition; // kernel condition mode is set by gate_preset_condition() + /** + * Name given to the kernel by the user. + */ + utils::Str name; + + /** + * The platform associated with the kernel. + * + * TODO: this doesn't really belong here, but is currently necessary because + * the gate constructors are part of the kernel. Rather, gates should be + * constructed by the platform and then added to the kernel, in much the + * same way that kernels are created using the platform and then added to + * a program. + */ + plat::PlatformRef platform; + + /** + * Number of (virtual) qubits used by this kernel. Must be less than or + * equal to the number of qubits in the platform. When the qubits represent + * physical qubits (post-mapping), this must equal the number of qubits in + * the platform. + */ + utils::UInt qubit_count; + + /** + * Number of (virtual) 32-bit general-purpose classical registers used by + * this kernel. Must be less than or equal to the number of registers in the + * platform. + */ + utils::UInt creg_count; + + /** + * Number of (virtual) single-bit condition registers used by this kernel. + * Must be less than or equal to the number of registers in the platform. + * + * FIXME: code is not consistent about what a breg means. I (JvS) thought we + * were using the first num_qubits bregs as registers that always exist and + * implicitly receive measurement results when no breg is manually + * specified, and use num_qubits..num_qubits+breg_count for user-specified + * state variables. But that's not how it works at all; bregs are still + * usually implicit, code all over the place assumes that bregs only range + * up to breg_count (exclusive), and breg_count defaults to zero. I don't + * get it. + */ + utils::UInt breg_count; + + /** + * The list of gates that forms the body of the kernel. + */ + GateRefs gates; + + /** + * The classical control-flow behavior of this kernel. + */ + KernelType type; + + /** + * The number of iterations that this kernel must be run for. Exact usage + * (if any) depends on type. + */ + utils::UInt iteration_count; + + /** + * The branch condition for this kernel. Exact usage (if any) depends on + * type. + */ + utils::Opt br_condition; + + /** + * Whether the cycle numbers attached to the gates in the circuit are + * considered to be valid. Used by the bundler to see if the kernel has been + * scheduled. + */ + utils::Bool cycles_valid; + + /** + * A conditional gate type used when adding gates to the kernel. + * + * FIXME: does NOT exactly serve as a condition for the kernel itself unless + * it's only at construction, not changed after that, and all kernels are + * added via the kernel.gate()-like functions (rather than being added to + * the circuit directly, as done by unitary decomposition, for example. + */ + ConditionType condition; + + /** + * Operands for the above condition. + */ + utils::Vec cond_operands; public: - quantum_kernel(const utils::Str &name); - quantum_kernel( + + Kernel( const utils::Str &name, - const quantum_platform &platform, - utils::UInt qcount, - utils::UInt ccount=0, - utils::UInt bcount=0 + const plat::PlatformRef &platform, + utils::UInt qubit_count, + utils::UInt creg_count=0, + utils::UInt breg_count=0 ); // FIXME: add constructor which allows setting iterations and type, and use that in program.h::add_for(), etc - void set_condition(const operation &oper); - void set_kernel_type(kernel_type_t typ); + void set_condition(const ClassicalOperation &oper); + void set_kernel_type(KernelType typ); utils::Str get_gates_definition() const; utils::Str get_name() const; - circuit &get_circuit(); - const circuit &get_circuit() const; void identity(utils::UInt qubit); void i(utils::UInt qubit); @@ -91,7 +186,7 @@ class quantum_kernel { void swap(utils::UInt qubit1, utils::UInt qubit2); void wait(const utils::Vec &qubits, utils::UInt duration); void display(); - void clifford(utils::Int id, utils::UInt qubit=0); + void clifford(utils::Int id, utils::UInt qubit); private: // a default gate is the last resort of user gate resolution and is of a build-in form, as below in the code; @@ -106,7 +201,7 @@ class quantum_kernel { utils::UInt duration = 0, utils::Real angle = 0.0, const utils::Vec &bregs = {}, - cond_type_t gcond = cond_always, + ConditionType gcond = ConditionType::ALWAYS, const utils::Vec &gcondregs = {} ); @@ -121,7 +216,7 @@ class quantum_kernel { utils::UInt duration = 0, utils::Real angle = 0.0, const utils::Vec &bregs = {}, - cond_type_t gcond = cond_always, + ConditionType gcond = ConditionType::ALWAYS, const utils::Vec &gcondregs = {} ); @@ -129,7 +224,7 @@ class quantum_kernel { // return the subinstructions of a composite gate // while doing, test whether the subinstructions have a definition (so they cannot be specialized or default ones!) void get_decomposed_ins( - const composite_gate *gptr, + const gate_types::Composite &gate, utils::Vec &sub_instructons ) const; @@ -144,7 +239,7 @@ class quantum_kernel { const utils::Vec &all_qubits, const utils::Vec &cregs = {}, const utils::Vec &bregs = {}, - cond_type_t gcond = cond_always, + ConditionType gcond = ConditionType::ALWAYS, const utils::Vec &gcondregs = {} ); @@ -159,7 +254,7 @@ class quantum_kernel { const utils::Vec &all_qubits, const utils::Vec &cregs = {}, const utils::Vec &bregs = {}, - cond_type_t gcond = cond_always, + ConditionType gcond = ConditionType::ALWAYS, const utils::Vec &gcondregs = {} ); @@ -174,22 +269,22 @@ class quantum_kernel { utils::UInt duration = 0, utils::Real angle = 0.0, const utils::Vec &bregs = {}, - cond_type_t gcond = cond_always, + ConditionType gcond = ConditionType::ALWAYS, const utils::Vec &gcondregs = {} ); void gate_preset_condition( - cond_type_t gcond, + ConditionType gcond, const utils::Vec &gcondregs ); void gate_clear_condition(); void condgate( const utils::Str &gname, const utils::Vec &qubits, - cond_type_t gcond, + ConditionType gcond, const utils::Vec &gcondregs ); // to add unitary to kernel - void gate(const unitary &u, const utils::Vec &qubits); + void gate(com::Unitary &u, const utils::Vec &qubits); // terminology: // - composite/custom/default (in decreasing order of priority during lookup in the gate definition): @@ -225,14 +320,14 @@ class quantum_kernel { utils::UInt duration = 0, utils::Real angle = 0.0, const utils::Vec &bregs = {}, - cond_type_t gcond = cond_always, + ConditionType gcond = ConditionType::ALWAYS, const utils::Vec &gcondregs = {} ); /** * support function for Python conditional execution interfaces to pass condition */ - ql::cond_type_t condstr2condvalue(const std::string &condstring); + ConditionType condstr2condvalue(const std::string &condstring); private: void gate_add_implicits( @@ -242,36 +337,10 @@ class quantum_kernel { utils::UInt &duration, utils::Real &angle, utils::Vec &bregs, - cond_type_t &gcond, + ConditionType &gcond, const utils::Vec &gcondregs ); - //recursive gate count function - //n is number of qubits - //i is the start point for the instructionlist - utils::Int recursiveRelationsForUnitaryDecomposition( - const unitary &u, - const utils::Vec &qubits, - utils::UInt n, - utils::UInt i - ); - - //controlled qubit is the first in the list. - void multicontrolled_rz( - const utils::Vec &instruction_list, - utils::UInt start_index, - utils::UInt end_index, - const utils::Vec &qubits - ); - - //controlled qubit is the first in the list. - void multicontrolled_ry( - const utils::Vec &instruction_list, - utils::UInt start_index, - utils::UInt end_index, - const utils::Vec &qubits - ); - public: /** @@ -282,7 +351,7 @@ class quantum_kernel { utils::Str get_epilogue() const; utils::Str qasm() const; - void classical(const creg &destination, const operation &oper); + void classical(const ClassicalRegister &destination, const ClassicalOperation &oper); void classical(const utils::Str &operation); // Controlled gates @@ -308,17 +377,28 @@ class quantum_kernel { \************************************************************************/ void controlled_single( - const quantum_kernel *k, + const Kernel &k, utils::UInt control_qubit, utils::UInt ancilla_qubit ); void controlled( - const quantum_kernel *k, + const Kernel &k, const utils::Vec &control_qubits, const utils::Vec &ancilla_qubits ); - void conjugate(const quantum_kernel *k); + void conjugate(const Kernel &k); }; +/** + * A "reference" (actually a smart pointer) to a single kernel node. + */ +using KernelRef = utils::One; + +/** + * A vector of "references" (actually smart pointers) to kernel nodes. + */ +using KernelRefs = utils::Any; + +} // namespace ir } // namespace ql diff --git a/include/ql/ir/program.h b/include/ql/ir/program.h new file mode 100644 index 000000000..e90d034ae --- /dev/null +++ b/include/ql/ir/program.h @@ -0,0 +1,153 @@ +/** \file + * Quantum program abstraction implementation. + */ + +#pragma once + +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/vec.h" +#include "ql/utils/tree.h" +#include "ql/ir/kernel.h" +#include "ql/plat/platform.h" + +namespace ql { +namespace ir { + +class Program; + +/** + * A "reference" (actually a smart pointer) to a single program node. + */ +using ProgramRef = utils::One; + +/** + * Toplevel IR node, representing a complete program. + */ +class Program : public utils::Node { +public: + + /** + * Program name given by the user. + */ + utils::Str name; + + /** + * Program name as used for output file generation. If the global + * `unique_output` option is set, this may receive a numerical suffix w.r.t. + * name to prevent overwriting files from previous runs. Otherwise, it's + * just a copy of name. + */ + utils::Str unique_name; + + /** + * The platform that this program is intended for. + */ + plat::PlatformRef platform; + + /** + * Number of (virtual) qubits used by this program. Must be less than or + * equal to the number of qubits in the platform. When the qubits represent + * physical qubits (post-mapping), this must equal the number of qubits in + * the platform. + */ + utils::UInt qubit_count; + + /** + * Number of (virtual) 32-bit general-purpose classical registers used by + * this program. Must be less than or equal to the number of registers in + * the platform. + */ + utils::UInt creg_count; + + /** + * Number of (virtual) single-bit condition registers used by this program. + * Must be less than or equal to the number of registers in the platform. + * + * FIXME: code is not consistent about what a breg means. I (JvS) thought we + * were using the first num_qubits bregs as registers that always exist and + * implicitly receive measurement results when no breg is manually + * specified, and use num_qubits..num_qubits+breg_count for user-specified + * state variables. But that's not how it works at all; bregs are still + * usually implicit, code all over the place assumes that bregs only range + * up to breg_count (exclusive), and breg_count defaults to zero. I don't + * get it. + */ + utils::UInt breg_count; + + /** + * The kernels that comprise the program. + */ + KernelRefs kernels; + + /** + * Constructs a new program. + */ + Program( + const utils::Str &name, + const plat::PlatformRef &platform, + utils::UInt qubit_count, + utils::UInt creg_count = 0, + utils::UInt breg_count = 0 + ); + + /** + * Adds the given kernel to the end of the program, after checking that it's + * safe to add. + */ + void add(const KernelRef &k); + + /** + * Adds the kernels in the given (sub)program to the end of this program, + * checking for each kernel whether it's safe to add. + */ + void add_program(const ProgramRef &p); + + /** + * Adds a conditional kernel, conditioned by a classical operation via + * classical flow control. + */ + void add_if(const KernelRef &k, const ClassicalOperation &cond); + + /** + * Adds a conditional program, conditioned by a classical operation via + * classical flow control. + */ + void add_if(const ProgramRef &p, const ClassicalOperation &cond); + + /** + * Adds two conditional kernels, conditioned by a classical operation and + * its complement respectively via classical flow control. + */ + void add_if_else(const KernelRef &k_if, const KernelRef &k_else, const ClassicalOperation &cond); + + /** + * Adds two conditional programs, conditioned by a classical operation and + * its complement respectively via classical flow control. + */ + void add_if_else(const ProgramRef &p_if, const ProgramRef &p_else, const ClassicalOperation &cond); + + /** + * Adds a do-while loop with the given kernel as the body. + */ + void add_do_while(const KernelRef &k, const ClassicalOperation &cond); + + /** + * Adds a do-while loop with the given program as the body. + */ + void add_do_while(const ProgramRef &p, const ClassicalOperation &cond); + + /** + * Adds a static for loop with the given kernel as the body. + */ + void add_for(const KernelRef &k, utils::UInt iterations); + + /** + * Adds a static for loop with the given program as the body. + */ + void add_for(const ProgramRef &p, utils::UInt iterations); + +}; + +} // namespace ir +} // namespace ql diff --git a/include/ql/pass/ana/statistics/annotations.h b/include/ql/pass/ana/statistics/annotations.h new file mode 100644 index 000000000..b841af32b --- /dev/null +++ b/include/ql/pass/ana/statistics/annotations.h @@ -0,0 +1,54 @@ +/** \file + * Defines common types and utility functions related to the statistics passes. + */ + +#pragma once + +#include "ql/utils/str.h" +#include "ql/utils/list.h" +#include "ql/ir/ir.h" + +namespace ql { +namespace pass { +namespace ana { +namespace statistics { + +/** + * Annotation object that may be used by other passes to attach additional + * statistics to the program and/or kernel nodes. These will then be printed + * and removed by the next statistics reporting pass, or discarded by a + * statistics cleaning pass. + */ +struct AdditionalStats { + + /** + * Additional lines for the statistics report. + */ + utils::List stats; + + /** + * Attaches a statistic to the given kernel node. + */ + static void push(const ir::KernelRef &kernel, const utils::Str &line); + + /** + * Attaches a statistic to the given program node. + */ + static void push(const ir::ProgramRef &program, const utils::Str &line); + + /** + * Pops all statistics annotations from the given kernel. + */ + static utils::List pop(const ir::KernelRef &kernel); + + /** + * Pops all statistics annotations from the given program. + */ + static utils::List pop(const ir::ProgramRef &program); + +}; + +} // namespace statistics +} // namespace ana +} // namespace pass +} // namespace ql diff --git a/include/ql/pass/ana/statistics/clean.h b/include/ql/pass/ana/statistics/clean.h new file mode 100644 index 000000000..3169974cc --- /dev/null +++ b/include/ql/pass/ana/statistics/clean.h @@ -0,0 +1,65 @@ +/** \file + * Defines the statistics cleaning pass. + */ + +#pragma once + +#include "ql/pmgr/pass_types/specializations.h" +#include "ql/pass/ana/statistics/annotations.h" + +namespace ql { +namespace pass { +namespace ana { +namespace statistics { +namespace clean { + +/** + * Statistics cleaning pass. + */ +class CleanStatisticsPass : public pmgr::pass_types::ProgramAnalysis { +protected: + + /** + * Dumps docs for the statistics cleaner. + */ + void dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Returns a user-friendly type name for this pass. + */ + utils::Str get_friendly_type() const override; + + /** + * Constructs a statistics cleaner. + */ + CleanStatisticsPass( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Runs the statistics cleaner. + */ + utils::Int run( + const ir::ProgramRef &program, + const pmgr::pass_types::Context &context + ) const override; + +}; + +/** + * Shorthand for referring to the pass using namespace notation. + */ +using Pass = CleanStatisticsPass; + +} // namespace clean +} // namespace statistics +} // namespace ana +} // namespace pass +} // namespace ql diff --git a/include/ql/pass/ana/statistics/report.h b/include/ql/pass/ana/statistics/report.h new file mode 100644 index 000000000..066ff5ae4 --- /dev/null +++ b/include/ql/pass/ana/statistics/report.h @@ -0,0 +1,95 @@ +/** \file + * Defines the statistics reporting pass. + */ + +#pragma once + +#include "ql/pmgr/pass_types/specializations.h" +#include "ql/pass/ana/statistics/annotations.h" + +namespace ql { +namespace pass { +namespace ana { +namespace statistics { +namespace report { + +/** + * Dumps basic statistics for the given kernel to the given output stream. + */ +void dump( + const ir::KernelRef &kernel, + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" +); + +/** + * Dumps basic statistics for the given program to the given output stream. This + * only dumps the global statistics, not the statistics for each individual + * kernel. + */ +void dump( + const ir::ProgramRef &program, + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" +); + +/** + * Dumps statistics for the given program and its kernels to the given output + * stream. + */ +void dump_all( + const ir::ProgramRef &program, + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" +); + +/** + * Statistics reporting pass. + */ +class ReportStatisticsPass : public pmgr::pass_types::ProgramAnalysis { +protected: + + /** + * Dumps docs for the statistics reporter. + */ + void dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Returns a user-friendly type name for this pass. + */ + utils::Str get_friendly_type() const override; + + /** + * Constructs a statistics reporter. + */ + ReportStatisticsPass( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Runs the statistics reporter. + */ + utils::Int run( + const ir::ProgramRef &program, + const pmgr::pass_types::Context &context + ) const override; + +}; + +/** + * Shorthand for referring to the pass using namespace notation. + */ +using Pass = ReportStatisticsPass; + +} // namespace report +} // namespace statistics +} // namespace ana +} // namespace pass +} // namespace ql diff --git a/include/ql/pass/ana/visualize/circuit.h b/include/ql/pass/ana/visualize/circuit.h new file mode 100644 index 000000000..e9ee16a1e --- /dev/null +++ b/include/ql/pass/ana/visualize/circuit.h @@ -0,0 +1,64 @@ +/** \file + * Defines the circuit visualization pass. + */ + +#pragma once + +#include "ql/pmgr/pass_types/specializations.h" + +namespace ql { +namespace pass { +namespace ana { +namespace visualize { +namespace circuit { + +/** + * Circuit visualizer pass. + */ +class VisualizeCircuitPass : public pmgr::pass_types::ProgramAnalysis { +protected: + + /** + * Dumps docs for the circuit visualizer. + */ + void dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Returns a user-friendly type name for this pass. + */ + utils::Str get_friendly_type() const override; + + /** + * Constructs a circuit visualizer pass. + */ + VisualizeCircuitPass( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Runs the circuit visualizer. + */ + utils::Int run( + const ir::ProgramRef &program, + const pmgr::pass_types::Context &context + ) const override; + +}; + +/** + * Shorthand for referring to the pass using namespace notation. + */ +using Pass = VisualizeCircuitPass; + +} // namespace circuit +} // namespace visualize +} // namespace ana +} // namespace pass +} // namespace ql diff --git a/include/ql/pass/ana/visualize/interaction.h b/include/ql/pass/ana/visualize/interaction.h new file mode 100644 index 000000000..8a48a1067 --- /dev/null +++ b/include/ql/pass/ana/visualize/interaction.h @@ -0,0 +1,64 @@ +/** \file + * Defines the interaction graph visualization pass. + */ + +#pragma once + +#include "ql/pmgr/pass_types/specializations.h" + +namespace ql { +namespace pass { +namespace ana { +namespace visualize { +namespace interaction { + +/** + * Interaction graph visualizer pass. + */ +class VisualizeInteractionPass : public pmgr::pass_types::ProgramAnalysis { +protected: + + /** + * Dumps docs for the interaction graph visualizer. + */ + void dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Returns a user-friendly type name for this pass. + */ + utils::Str get_friendly_type() const override; + + /** + * Constructs a interaction graph visualizer pass. + */ + VisualizeInteractionPass( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Runs the interaction graph visualizer. + */ + utils::Int run( + const ir::ProgramRef &program, + const pmgr::pass_types::Context &context + ) const override; + +}; + +/** + * Shorthand for referring to the pass using namespace notation. + */ +using Pass = VisualizeInteractionPass; + +} // namespace interaction +} // namespace visualize +} // namespace ana +} // namespace pass +} // namespace ql diff --git a/include/ql/pass/ana/visualize/mapping.h b/include/ql/pass/ana/visualize/mapping.h new file mode 100644 index 000000000..567ed4038 --- /dev/null +++ b/include/ql/pass/ana/visualize/mapping.h @@ -0,0 +1,64 @@ +/** \file + * Defines the mapping graph visualization pass. + */ + +#pragma once + +#include "ql/pmgr/pass_types/specializations.h" + +namespace ql { +namespace pass { +namespace ana { +namespace visualize { +namespace mapping { + +/** + * Mapping graph visualizer pass. + */ +class VisualizeMappingPass : public pmgr::pass_types::ProgramAnalysis { +protected: + + /** + * Dumps docs for the mapping graph visualizer. + */ + void dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Returns a user-friendly type name for this pass. + */ + utils::Str get_friendly_type() const override; + + /** + * Constructs a mapping graph visualizer pass. + */ + VisualizeMappingPass( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Runs the mapping graph visualizer. + */ + utils::Int run( + const ir::ProgramRef &program, + const pmgr::pass_types::Context &context + ) const override; + +}; + +/** + * Shorthand for referring to the pass using namespace notation. + */ +using Pass = VisualizeMappingPass; + +} // namespace mapping +} // namespace visualize +} // namespace ana +} // namespace pass +} // namespace ql diff --git a/include/ql/pass/io/cqasm/read.h b/include/ql/pass/io/cqasm/read.h new file mode 100644 index 000000000..ffd7cf832 --- /dev/null +++ b/include/ql/pass/io/cqasm/read.h @@ -0,0 +1,112 @@ +/** \file + * Defines the cQASM reader pass. + */ + +#pragma once + +#include "ql/utils/str.h" +#include "ql/utils/json.h" +#include "ql/utils/opt.h" +#include "ql/pmgr/pass_types/specializations.h" + +namespace ql { +namespace pass { +namespace io { +namespace cqasm { +namespace read { + +// Opaque forward declaration for the actual implementation of the reader, to +// keep the header file clean. +namespace detail { +class ReaderImpl; +} + +/** + * Class for converting cQASM files to OpenQL circuits. + */ +class Reader { +private: + utils::Opt impl; +public: + Reader(const plat::PlatformRef &platform, const ir::ProgramRef &program); + Reader(const plat::PlatformRef &platform, const ir::ProgramRef &program, const utils::Json &gateset); + Reader(const plat::PlatformRef &platform, const ir::ProgramRef &program, const utils::Str &gateset_fname); + void string2circuit(const utils::Str &cqasm_str); + void file2circuit(const utils::Str &cqasm_fname); +}; + +/** + * Reads a cQASM file. Its content are added to program. The number of qubits, + * cregs, and/or bregs allocated in the program are increased as needed (if + * possible for the current platform). The gateset parameter should be loaded + * from a gateset configuration file or be alternatively initialized. If empty + * or unspecified, a default set is used, that mimics the behavior of the reader + * before it became configurable. + */ +void from_file( + const ir::ProgramRef &program, + const utils::Str &cqasm_fname, + const utils::Json &gateset={} +); + +/** + * Same as file(), be reads from a string instead. + * + * \see file() + */ +void from_string( + const ir::ProgramRef &program, + const utils::Str &cqasm_body, + const utils::Json &gateset={} +); + +/** + * cQASM reader pass. + */ +class ReadCQasmPass : public pmgr::pass_types::ProgramTransformation { +protected: + + /** + * Dumps docs for the cQASM reader. + */ + void dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Returns a user-friendly type name for this pass. + */ + utils::Str get_friendly_type() const override; + + /** + * Constructs a cQASM reader. + */ + ReadCQasmPass( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Runs the cQASM reader. + */ + utils::Int run( + const ir::ProgramRef &program, + const pmgr::pass_types::Context &context + ) const override; + +}; + +/** + * Shorthand for referring to the pass using namespace notation. + */ +using Pass = ReadCQasmPass; + +} // namespace reader +} // namespace cqasm +} // namespace io +} // namespace pass +} // namespace ql diff --git a/include/ql/pass/io/cqasm/report.h b/include/ql/pass/io/cqasm/report.h new file mode 100644 index 000000000..0e589798a --- /dev/null +++ b/include/ql/pass/io/cqasm/report.h @@ -0,0 +1,88 @@ +/** \file + * Defines the cQASM writer pass. + */ + +#pragma once + +#include "ql/pmgr/pass_types/specializations.h" + +#include + +namespace ql { +namespace pass { +namespace io { +namespace cqasm { +namespace report { + +/** + * Dumps cQASM code for the given program to the given output stream. + * Optionally, the after_kernel callback function may be used to dump additional + * information at the end of each kernel. The third argument specifies the + * appropriate line prefix for correctly-indented comments. + */ +void dump( + const ir::ProgramRef &program, + std::ostream &os = std::cout, + std::function after_kernel + = [](const ir::KernelRef&, std::ostream&, const utils::Str&){} +); + +/** + * Specialization of dump() that includes statistics per kernel and program in + * comments. + */ +void dump_with_statistics( + const ir::ProgramRef &program, + std::ostream &os = std::cout +); + +/** + * cQASM writer pass. + */ +class ReportCQasmPass : public pmgr::pass_types::ProgramAnalysis { +protected: + + /** + * Dumps docs for the cQASM writer. + */ + void dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Returns a user-friendly type name for this pass. + */ + utils::Str get_friendly_type() const override; + + /** + * Constructs a cQASM writer. + */ + ReportCQasmPass( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Runs the cQASM writer. + */ + utils::Int run( + const ir::ProgramRef &program, + const pmgr::pass_types::Context &context + ) const override; + +}; + +/** + * Shorthand for referring to the pass using namespace notation. + */ +using Pass = ReportCQasmPass; + +} // namespace report +} // namespace cqasm +} // namespace io +} // namespace pass +} // namespace ql diff --git a/include/ql/pass/io/sweep_points/annotation.h b/include/ql/pass/io/sweep_points/annotation.h new file mode 100644 index 000000000..c8abb82d2 --- /dev/null +++ b/include/ql/pass/io/sweep_points/annotation.h @@ -0,0 +1,33 @@ +/** \file + * Defines the program annotation used to record sweep point data. + */ + +#pragma once + +namespace ql { +namespace pass { +namespace io { +namespace sweep_points { + +/** + * Program annotation used to record sweep point data. + */ +struct Annotation { + + /** + * TODO JvS: still not sure what this is. + */ + utils::Vec data = {}; + + /** + * Configuration file name for the sweep points pass. Leave empty to use the + * default generated filename. + */ + utils::Str config_file_name = ""; + +}; + +} // namespace sweep_points +} // namespace io +} // namespace pass +} // namespace ql diff --git a/include/ql/pass/io/sweep_points/write.h b/include/ql/pass/io/sweep_points/write.h new file mode 100644 index 000000000..70453af6d --- /dev/null +++ b/include/ql/pass/io/sweep_points/write.h @@ -0,0 +1,64 @@ +/** \file + * Defines the sweep point writer pass. + */ + +#pragma once + +#include "ql/pmgr/pass_types/specializations.h" + +namespace ql { +namespace pass { +namespace io { +namespace sweep_points { +namespace write { + +/** + * Sweep point writer pass. + */ +class WriteSweepPointsPass : public pmgr::pass_types::ProgramAnalysis { +protected: + + /** + * Dumps docs for the sweep point writer. + */ + void dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Returns a user-friendly type name for this pass. + */ + utils::Str get_friendly_type() const override; + + /** + * Constructs a sweep point writer. + */ + WriteSweepPointsPass( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Runs the sweep point writer. + */ + utils::Int run( + const ir::ProgramRef &program, + const pmgr::pass_types::Context &context + ) const override; + +}; + +/** + * Shorthand for referring to the pass using namespace notation. + */ +using Pass = WriteSweepPointsPass; + +} // namespace write +} // namespace sweep_points +} // namespace io +} // namespace pass +} // namespace ql diff --git a/include/ql/pass/map/qubits/map/map.h b/include/ql/pass/map/qubits/map/map.h new file mode 100644 index 000000000..917aa64a1 --- /dev/null +++ b/include/ql/pass/map/qubits/map/map.h @@ -0,0 +1,86 @@ +/** \file + * Defines the mapper pass. + */ + +#pragma once + +#include "ql/utils/ptr.h" +#include "ql/com/options.h" +#include "ql/pmgr/pass_types/specializations.h" + +namespace ql { +namespace pass { +namespace map { +namespace qubits { +namespace map { + +namespace detail { +struct Options; +} // namespace detail + +/** + * Qubit mapper pass. + */ +class MapQubitsPass : public pmgr::pass_types::ProgramTransformation { +private: + + /** + * Parsed options structure. + */ + utils::Ptr parsed_options; + +protected: + + /** + * Dumps docs for the qubit mapper. + */ + void dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Returns a user-friendly type name for this pass. + */ + utils::Str get_friendly_type() const override; + + /** + * Constructs a qubit mapper. + */ + MapQubitsPass( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Builds the options structure for the mapper. + */ + pmgr::pass_types::NodeType on_construct( + const utils::Ptr &factory, + utils::List &passes, + pmgr::condition::Ref &condition + ) override; + + /** + * Runs the qubit mapper. + */ + utils::Int run( + const ir::ProgramRef &program, + const pmgr::pass_types::Context &context + ) const override; + +}; + +/** + * Shorthand for referring to the pass using namespace notation. + */ +using Pass = MapQubitsPass; + +} // namespace map +} // namespace qubits +} // namespace map +} // namespace pass +} // namespace ql diff --git a/include/ql/pass/map/qubits/place_mip/place_mip.h b/include/ql/pass/map/qubits/place_mip/place_mip.h new file mode 100644 index 000000000..946efffc6 --- /dev/null +++ b/include/ql/pass/map/qubits/place_mip/place_mip.h @@ -0,0 +1,66 @@ +/** \file + * Defines the initial placement pass. + */ + +#pragma once + +#include "ql/com/options.h" +#include "ql/pmgr/pass_types/specializations.h" + +namespace ql { +namespace pass { +namespace map { +namespace qubits { +namespace place_mip { + +/** + * Initial qubit placer pass. + */ +class PlaceQubitsPass : public pmgr::pass_types::KernelTransformation { +protected: + + /** + * Dumps docs for the initial qubit placer. + */ + void dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Returns a user-friendly type name for this pass. + */ + utils::Str get_friendly_type() const override; + + /** + * Constructs an initial qubit placer. + */ + PlaceQubitsPass( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Runs initial qubit placement. + */ + utils::Int run( + const ir::ProgramRef &program, + const ir::KernelRef &kernel, + const pmgr::pass_types::Context &context + ) const override; + +}; + +/** + * Shorthand for referring to the pass using namespace notation. + */ +using Pass = PlaceQubitsPass; + +} // namespace map +} // namespace qubits +} // namespace map +} // namespace pass +} // namespace ql diff --git a/include/ql/pass/opt/clifford/optimize.h b/include/ql/pass/opt/clifford/optimize.h new file mode 100644 index 000000000..3a95fb030 --- /dev/null +++ b/include/ql/pass/opt/clifford/optimize.h @@ -0,0 +1,75 @@ +/** \file + * Defines the Clifford optimizer pass. + */ + +#pragma once + +#include "ql/pmgr/pass_types/specializations.h" + +namespace ql { +namespace pass { +namespace opt { +namespace clifford { +namespace optimize { + +/** + * Clifford sequence optimizer. + * FIXME JvS: remove; only used by old pass manager + */ +void clifford_optimize( + const ir::ProgramRef &programp, + const plat::PlatformRef &platform, + const utils::Str &passname +); + +/** + * Clifford optimizer pass. + */ +class CliffordOptimizePass : public pmgr::pass_types::KernelTransformation { +protected: + + /** + * Dumps docs for the Clifford optimizer. + */ + void dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Returns a user-friendly type name for this pass. + */ + utils::Str get_friendly_type() const override; + + /** + * Constructs a Clifford optimizer. + */ + CliffordOptimizePass( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Runs the Clifford optimizer. + */ + utils::Int run( + const ir::ProgramRef &program, + const ir::KernelRef &kernel, + const pmgr::pass_types::Context &context + ) const override; + +}; + +/** + * Shorthand for referring to the pass using namespace notation. + */ +using Pass = CliffordOptimizePass; + +} // namespace optimize +} // namespace clifford +} // namespace opt +} // namespace pass +} // namespace ql diff --git a/include/ql/pass/sch/schedule/schedule.h b/include/ql/pass/sch/schedule/schedule.h new file mode 100644 index 000000000..1d4b12fe5 --- /dev/null +++ b/include/ql/pass/sch/schedule/schedule.h @@ -0,0 +1,64 @@ +/** \file + * Defines the scheduler pass. + */ + +#pragma once + +#include "ql/com/options.h" +#include "ql/pmgr/pass_types/specializations.h" + +namespace ql { +namespace pass { +namespace sch { +namespace schedule { + +/** + * Scheduler pass. + */ +class SchedulePass : public pmgr::pass_types::KernelTransformation { +protected: + + /** + * Dumps docs for the scheduler. + */ + void dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Returns a user-friendly type name for this pass. + */ + utils::Str get_friendly_type() const override; + + /** + * Constructs a scheduler. + */ + SchedulePass( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Runs the scheduler. + */ + utils::Int run( + const ir::ProgramRef &program, + const ir::KernelRef &kernel, + const pmgr::pass_types::Context &context + ) const override; + +}; + +/** + * Shorthand for referring to the pass using namespace notation. + */ +using Pass = SchedulePass; + +} // namespace schedule +} // namespace sch +} // namespace pass +} // namespace ql diff --git a/include/ql/plat/platform.h b/include/ql/plat/platform.h new file mode 100644 index 000000000..e23ca02c5 --- /dev/null +++ b/include/ql/plat/platform.h @@ -0,0 +1,208 @@ +/** \file + * Platform header for target-specific compilation. + */ + +#pragma once + +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/opt.h" +#include "ql/utils/json.h" +#include "ql/utils/tree.h" +#include "ql/ir/gate.h" +#include "ql/plat/topology.h" +#include "ql/arch/declarations.h" + +namespace ql { +namespace plat { + +using CustomGateRef = utils::One; + +using InstructionMap = utils::Map; + +/** + * Platform configuration structure. Represents everything we know about the + * target qubit chip, simulator, control architecture, etc. + * + * TODO: this still needs a lot of work. As much JSON parsing as possible should + * be done while loading, and we need different classes for gate instances and + * types. + */ +class Platform : public utils::Node { +public: + + /** + * Dumps the documentation for the platform configuration file structure. + */ + static void dump_docs(std::ostream &os = std::cout, const utils::Str &line_prefix = ""); + +private: + + /** + * Loads the platform members from the given JSON data and optional + * auxiliary compiler configuration file. + */ + void load( + utils::Json &platform_config, + const utils::Str &platform_config_fname = "", + const utils::Str &compiler_config = "" + ); + + /** + * Raw instruction setting data for use by the eqasm backend, corresponding + * to the `"instructions"` key in the root JSON object. + */ + utils::Json instruction_settings; + +public: + + /** + * User-specified name for the platform. + */ + utils::Str name; + + /** + * The total number of physical qubits supported by the platform. + */ + utils::UInt qubit_count; + + /** + * The total number of 32-bit general-purpose classical registers supported + * by the platform. + */ + utils::UInt creg_count; + + /** + * Historically, creg count was not specified in the platform description + * file, and was instead implicitly taken from the amount allocated for the + * program constructed from it. Setting this models this old behavior to + * some extent: creg_count will then be increased whenever a program is + * created with more than creg_count creg declarations. + */ + utils::Bool compat_implicit_creg_count; + + /** + * The total number of single-bit condition/measurement result registers + * supported by the platform. + */ + utils::UInt breg_count; + + /** + * Same as compat_implicit_creg_count, but for bregs. + */ + utils::Bool compat_implicit_breg_count; + + /** + * Cycle time in nanoseconds. + * + * FIXME: why is this a UInt? Non-integer-nanosecond cycle times are not + * supported...? At least use picoseconds or femtoseconds as a unit if it + * needs to be fixed-point, 64-bit is plenty for that. + */ + utils::UInt cycle_time; + + /** + * The gate/instruction set supported by this platform. + */ + InstructionMap instruction_map; + + /** + * Architecture information object. + */ + arch::CArchitectureRef architecture; + + /** + * Settings for the compiler. This can be: + * - an empty string, if no eqasm_compiler key is specified; + * - a recognized string (none, qx, cc_light_compiler, or + * eqasm_backend_cc); + * - a JSON object representing the compiler configuration structure, + * which may or may not have a strategy.architecture key set to cc or + * cc_light. + * + * NOTE: while it's nasty that this is here as a raw JSON object, we can't + * construct it into a pass manager until program.compile, because + * construction may use global options in compatibility mode... + */ + utils::Json compiler_settings; + + /** + * Additional hardware settings (to use by the eqasm backend), corresponding + * to the `"hardware_settings"` key in the root JSON object. + */ + utils::Json hardware_settings; + + /** + * Scheduling resource description (representing e.g. instrument/control + * constraints), corresponding to the `"resources"` key in the root JSON + * object. + * + * FIXME: this shouldn't be here as a raw JSON object. Right now the + * resource manager does the parsing, but it's much better if the platform + * does it, so problems are caught earlier. (People need to stop writing + * broken config files just because they're not using parts of it! Either + * there should be a sane default for something, which there is in this + * case (namely nothing) or they need to specify a valid structure. + * Period.) + */ + utils::Json resources; + + /** + * Parsed topology/qubit grid information. + */ + utils::Opt topology; + + /** + * Constructs a platform from the given configuration filename. + */ + Platform( + const utils::Str &name, + const utils::Str &platform_config, + const utils::Str &compiler_config = "" + ); + + /** + * Constructs a platform from the given configuration *data*. + */ + Platform( + const utils::Str &name, + const utils::Json &platform_config, + const utils::Str &compiler_config = "" + ); + + /** + * Dumps some basic info about the platform to the given stream. + */ + void dump_info(std::ostream &os = std::cout, utils::Str line_prefix = "") const; + + /** + * Returns the JSON data for a custom gate, throwing a semi-useful + * exception if the instruction is not found. + * + * FIXME: this shouldn't be here. Extra data should be part of the gate + * types (but there are no gate types yet, of course). + */ + const utils::Json &find_instruction(const utils::Str &iname) const; + + /** + * Returns the JSON data for all instructions as a JSON map. + * + * FIXME: something like this is needed, but the structure should already + * have been parsed rather than be in JSON form. + */ + const utils::Json &get_instructions() const; + + /** + * Converts the given time in nanoseconds to cycles. + */ + utils::UInt time_to_cycles(utils::Real time_ns) const; + +}; + +/** + * Smart pointer reference to a platform. + */ +using PlatformRef = utils::One; + +} // namespace plat +} // namespace ql diff --git a/include/ql/plat/topology.h b/include/ql/plat/topology.h new file mode 100644 index 000000000..b32bbdd90 --- /dev/null +++ b/include/ql/plat/topology.h @@ -0,0 +1,301 @@ +/** \file + * Definition and access functions to the grid of qubits that supports the real + * qubits. + */ + +#pragma once + +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/pair.h" +#include "ql/utils/list.h" +#include "ql/utils/vec.h" +#include "ql/utils/map.h" +#include "ql/utils/json.h" + +namespace ql { +namespace plat { + +/** + * Qubit grid form/shape. + */ +enum class GridForm { + + /** + * Qubits have integer X/Y coordinates associated with them. + */ + XY, + + /** + * Qubits do not have any kind of coordinates associated with them. + */ + IRREGULAR + +}; + +/** + * String representation for GridForm. + */ +std::ostream &operator<<(std::ostream &os, GridForm gf); + +/** + * A coordinate as used by GridForm::XY. + */ +struct XYCoordinate { + utils::Int x; + utils::Int y; +}; + +/** + * String representation for XYCoordinate. + */ +std::ostream &operator<<(std::ostream &os, XYCoordinate xy); + +/** + * Qubit connectivity mode. + */ +enum class GridConnectivity { + + /** + * Qubit connectivity is specified in the platform configuration file via + * the "edges" section. + */ + SPECIFIED, + + /** + * Qubit connectivity is not specified in the platform configuration file. + * Full connectivity is assumed. + */ + FULL + +}; + +/** + * String representation for GridConnectivity. + */ +std::ostream &operator<<(std::ostream &os, GridConnectivity gc); + +/** + * Qubit grid abstraction layer. + */ +class Topology { +public: + + /** + * Dumps the documentation for the topology JSON structure. + */ + static void dump_docs(std::ostream &os = std::cout, const utils::Str &line_prefix = ""); + + /** + * Shorthand for a qubit index. + */ + using Qubit = utils::UInt; + + /** + * Shorthand for a pair of qubits. + */ + using QubitPair = utils::Pair; + + /** + * Shorthand for an edge index. + */ + using Edge = utils::Int; + + /** + * A list of neighboring qubits. + */ + using Neighbors = utils::List; + +private: + + /** + * Shorthand for a map from a qubit number to something else. + */ + template + using QubitMap = utils::Map; + + /** + * The total number of qubits in the platform. + */ + utils::UInt num_qubits; + + /** + * The number of quantum cores. If greater than 1, each core is assumed to + * have the same number of qubits, being num_qubits/num_cores. + */ + utils::UInt num_cores; + + /** + * Number of communication qubits per core. The first num_comm_qubits qubits + * associated with each core is considered to be a communication qubit. + */ + utils::UInt num_comm_qubits; + + /** + * The grid form/shape. + */ + GridForm form; + + /** + * If this is an XY grid, this is the size of the grid; all X coordinates + * must be between 0 and xy_size.x-1, and all Y coordinates must be between + * 0 and xy_size.y-1. + */ + XYCoordinate xy_size; + + /** + * If this is an XY grid, this contains the coordinates for each qubit. + */ + QubitMap xy_coord; + + /** + * Connectivity of the grid. + */ + GridConnectivity connectivity; + + /** + * The list of neighboring qubits for each qubit. + */ + QubitMap neighbors; + + /** + * Edge to qubit pair map. Only used for specified connectivity. + */ + utils::Map edge_to_qubits; + + /** + * Qubit pair to edge index. Only used for specified connectivity. + */ + utils::Map qubits_to_edge; + + /** + * The highest used edge index plus one. For specified connectivity, this + * is computed from the user-specified edge indices. For unspecified, it is + * set to num_qubits**2. + */ + Edge max_edge; + + /** + * The distance (number of edges) between a pair of qubits. + */ + utils::Vec> distance; + +public: + + /** + * Constructs the grid for the given number of qubits from the given JSON + * object. Refer to dump_docs() for details. + */ + Topology(utils::UInt num_qubits, const utils::Json &topology); + + /** + * Returns the size of the qubit grid, if coordinates have been specified. + * If not, this returns (0, 0). + */ + XYCoordinate get_grid_size() const; + + /** + * Returns the coordinate of the given qubit, if coordinates have been + * specified. If not, or if the qubit index is out of range, this returns + * (0, 0). + */ + XYCoordinate get_qubit_coordinate(Qubit q) const; + + /** + * Returns the edge index for the given qubit pair, or returns -1 when there + * is no defined edge index for the given qubit pair. + */ + Edge get_edge_index(QubitPair qs) const; + + /** + * Returns the qubit pair corresponding with the given edge, or returns 0,0 + * when there is no edge with the given index. + */ + QubitPair get_edge_qubits(Edge edge) const; + + /** + * Returns the highest used edge index plus one. Note that not all edge + * indices between 0 and max-1 actually need to be in use, so this is not + * necessarily the total number of edges. + */ + Edge get_max_edge() const; + + /** + * Returns the indices of the neighboring qubits for the given qubit. + */ + const Neighbors &get_neighbors(Qubit qubit) const; + + /** + * Returns the number of cores. + */ + utils::UInt get_num_cores() const; + + /** + * Returns whether the given qubit is a communication qubit of a core. + */ + utils::Bool is_comm_qubit(Qubit qubit) const; + + /** + * Returns the core index for the given qubit in a multi-core environment. + */ + utils::UInt get_core_index(Qubit qubit) const; + + /** + * Returns whether communication between the given two qubits involves + * inter-core communication. + */ + utils::Bool is_inter_core_hop(Qubit source, Qubit target) const; + + /** + * Returns the distance between the two given qubits in number of hops. + * Returns 0 iff source == target. + */ + utils::UInt get_distance(Qubit source, Qubit target) const; + + /** + * Returns the distance between the given two qubits in terms of cores. + */ + utils::UInt get_core_distance(Qubit source, Qubit target) const; + + /** + * Minimum number of hops between two qubits is always >= distance(from, to) + * and inside one core (or without multi-core) the minimum number of + * hops == distance. + * + * However, in multi-core with inter-core hops, an inter-core hop cannot + * execute a 2qgate so when the minimum number of hops are all inter-core + * hops (so distance(from,to) == coredistance(from,to)) and no 2qgate has + * been placed yet, then at least one additional inter-core hop is needed + * for the 2qgate, the number of hops required being at least distance+1. + * + * We assume below that a valid path exists with distance+1 hops; this fails + * when not all qubits in a core support connections to all other cores. + * See the check in initialization of neighbors. + */ + utils::UInt get_min_hops(Qubit source, Qubit target) const; + + /** + * Returns whether qubits have coordinates associated with them. + */ + utils::Bool has_coordinates() const; + + /** + * Rotate neighbors list such that largest angle difference between adjacent + * elements is behind back. This is needed when a given subset of variations + * from a node is wanted (mappathselect==borders). This can only be computed + * when there is an underlying x/y grid (so not for form==gf_irregular). + * + * TODO/FIXME: see + * https://github.com/QE-Lab/OpenQL/pull/405#issuecomment-831247204 + */ + void sort_neighbors_by_angle(Qubit src, Neighbors &nbl) const; + + /** + * Dumps the grid configuration to the given stream. + */ + void dump(std::ostream &os=std::cout, const utils::Str &line_prefix="") const; + +}; + +} // namespace plat +} // namespace ql diff --git a/include/ql/pmgr/condition.h b/include/ql/pmgr/condition.h new file mode 100644 index 000000000..e71dd18cc --- /dev/null +++ b/include/ql/pmgr/condition.h @@ -0,0 +1,142 @@ +/** \file + * Contains the condition classes that the pass management logic uses for + * conditional pass execution. + */ + +#pragma once + +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/ptr.h" + +namespace ql { +namespace pmgr { +namespace condition { + +/** + * Base class for the conditions used by GROUP_IF, GROUP_WHILE, and + * GROUP_REPEAT_UNTIL_NOT pass nodes. + */ +class Base { +public: + + /** + * Default virtual destructor. + */ + virtual ~Base() = default; + + /** + * Evaluates the condition, given the pass return code. + */ + virtual bool evaluate(utils::Int pass_return_value) const = 0; + + /** + * Returns a string representation of the condition for debugging. + */ + virtual utils::Str to_string() const = 0; + +}; + +/** + * Reference to a pass condition. + */ +using Ref = utils::Ptr; + +/** + * Immutable reference to a pass condition. + */ +using CRef = utils::Ptr; + +/** + * Comparison relation for the Compare class. + */ +enum class Relation { + EQ, NE, GT, GE, LT, LE +}; + +/** + * Class for conditions based on how the value in question compares to a + * reference value. + */ +class Compare : public Base { +private: + + /** + * Reference value to compare to. + */ + utils::Int value; + + /** + * The relation to use. + */ + Relation relation; + +public: + + /** + * Constructs a condition that bases its result upon how the the pass return + * code compares to the given value. If invert is false or unspecified, true is + * returned when the value is in range. If invert is true, false is returned + * when the value is in range. + */ + Compare(utils::Int value, Relation relation = Relation::EQ); + + /** + * Evaluates the condition, given the pass return code. + */ + bool evaluate(utils::Int pass_return_value) const override; + + /** + * Returns a string representation of the condition for debugging. + */ + utils::Str to_string() const override; + +}; + +/** + * Class for conditions based on a range of values. + */ +class Range : public Base { +private: + + /** + * Minimum value in the range. + */ + utils::Int min; + + /** + * Maximum value in the range. + */ + utils::Int max; + + /** + * Whether to invert the result. + */ + utils::Bool invert; + +public: + + /** + * Constructs a condition that bases its result upon whether the pass return + * code is within a certain range. min and max represent the inclusive + * range. If invert is false or unspecified, true is returned when the + * value is in range. If invert is true, false is returned when the value is + * in range. + */ + Range(utils::Int min, utils::Int max, utils::Bool invert = false); + + /** + * Evaluates the condition, given the pass return code. + */ + bool evaluate(utils::Int pass_return_value) const override; + + /** + * Returns a string representation of the condition for debugging. + */ + utils::Str to_string() const override; + +}; + +} // namespace condition +} // namespace pmgr +} // namespace ql diff --git a/include/ql/pmgr/declarations.h b/include/ql/pmgr/declarations.h new file mode 100644 index 000000000..41a9de555 --- /dev/null +++ b/include/ql/pmgr/declarations.h @@ -0,0 +1,15 @@ +/** \file + * Forward declarations for the pass factory and manager classes. + */ + +#pragma once + +namespace ql { +namespace pmgr { + +// Forward declaration for the pass factory and pass manager. +class Factory; +class Manager; + +} // namespace pmgr +} // namespace ql diff --git a/include/ql/pmgr/factory.h b/include/ql/pmgr/factory.h new file mode 100644 index 000000000..e480084d4 --- /dev/null +++ b/include/ql/pmgr/factory.h @@ -0,0 +1,119 @@ +/** \file + * Pass factory. + */ + +#pragma once + +#include +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/ptr.h" +#include "ql/utils/map.h" +#include "ql/ir/ir.h" +#include "ql/pmgr/pass_types/base.h" + +namespace ql { +namespace pmgr { + +// Forward declaration. +class Factory; + +/** + * Mutable reference to a pass factory. + */ +using FactoryRef = utils::Ptr; + +/** + * Immutable reference to a pass factory. + */ +using CFactoryRef = utils::Ptr; + +/** + * Factory class for constructing passes. + */ +class Factory { +private: + + /** + * Function pointer object type that is used to construct pass class + * instances. + */ + using ConstructorFn = utils::Ptr< + std::function< + PassRef( + const CFactoryRef &pass_factory, + const utils::Str &instance_name + ) + > + >; + + /** + * Map from (desugared) pass type name to a constructor function for that + * particular pass type. + */ + utils::Map pass_types; + +public: + + /** + * Constructs a default pass factory for OpenQL. + */ + Factory(); + + /** + * Registers a pass class with the given type name. + */ + template + void register_pass(const utils::Str &type_name) { + ConstructorFn fn; + fn.emplace([type_name]( + const CFactoryRef &pass_factory, + const utils::Str &instance_name + ) { + PassRef pass; + pass.emplace(pass_factory, type_name, instance_name); + return pass; + }); + pass_types.set(type_name) = fn; + } + + /** + * Returns a copy of this pass factory with the following modifications made + * to the map. + * + * - Entries with a `dnu` path component in them are removed. If the type + * of the removed entry exists in dnu however, it will be reinserted with + * the `dnu` path component removed. + * - A copy is made of entries that include an `arch.` + * component pair, with that pair stripped. + * + * The original factory is not modified. + */ + CFactoryRef configure( + const utils::Str &architecture, + const utils::Set &dnu + ) const; + + /** + * Builds a pass instance. + */ + static PassRef build_pass( + const CFactoryRef &pass_factory, + const utils::Str &type_name, + const utils::Str &instance_name + ); + + /** + * Dumps documentation for all pass types known by this factory, as well as + * the option documentation for each pass. + */ + static void dump_pass_types( + const CFactoryRef &pass_factory, + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" + ); + +}; + +} // namespace pmgr +} // namespace ql diff --git a/include/ql/pmgr/group.h b/include/ql/pmgr/group.h new file mode 100644 index 000000000..be9578a4e --- /dev/null +++ b/include/ql/pmgr/group.h @@ -0,0 +1,65 @@ +/** \file + * Basic pass group implementation. + */ + +#pragma once + +#include +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/ptr.h" +#include "ql/ir/ir.h" +#include "ql/pmgr/pass_types/specializations.h" + +namespace ql { +namespace pmgr { + +/** + * A generic group of passes, with no special functionality or default set of + * passes. + */ +class Group : public pass_types::Group { +public: + + /** + * Constructs the pass group. No error checking here; this is up to the + * parent pass group. Note that the type name is missing, and that + * instance_name defaults to the empty string; generic passes always have + * an empty type name, and the root group has an empty instance name as + * well. + */ + Group( + const utils::Ptr &pass_factory, + const utils::Str &instance_name + ); + +protected: + + /** + * Implementation for the initial pass list. This is no-op for a generic + * pass group. + */ + void get_passes( + const utils::Ptr &factory, + utils::List &passes + ) final; + + /** + * Writes the documentation for a basic pass group to the given stream. + */ + void dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Returns a user-friendly type name for this pass. + */ + utils::Str get_friendly_type() const override; + +}; + +} // namespace pmgr +} // namespace ql diff --git a/include/ql/pmgr/manager.h b/include/ql/pmgr/manager.h new file mode 100644 index 000000000..1ee7072da --- /dev/null +++ b/include/ql/pmgr/manager.h @@ -0,0 +1,322 @@ +/** \file + * Pass management. + */ + +#pragma once + +#include +#include "ql/config.h" +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/ptr.h" +#include "ql/utils/list.h" +#include "ql/utils/vec.h" +#include "ql/utils/set.h" +#include "ql/utils/pair.h" +#include "ql/utils/options.h" +#include "ql/utils/compat.h" +#include "ql/ir/ir.h" +#include "ql/pmgr/pass_types/base.h" +#include "ql/pmgr/factory.h" +#include "ql/pmgr/declarations.h" + +namespace ql { +namespace pmgr { + +/** + * The top-level pass manager class that drives compilation. + * + * Internally, this contains a tree structure with compiler passes at the nodes. + * This structure represents a compilation strategy. Usually, the strategy is + * just "run the following passes in sequence," but it's also possible to run + * groups of passes conditionally of in a loop, for instance based on some + * analysis pass that tries to estimate how much potential for optimization + * remains in a given program. + * + * Passes are configured based on a pass type and pass options. The available + * pass type names depend on the PassFactory that the PassManager is + * constructed with, the selected target architecture, and the list of + * "do-not-use" passes that are explicitly enabled. As for the options; some + * options exist for all passes, while others only exist for a particular pass + * type. Pass options can be (re)configured until construct() is called, at + * which point the pass may expand into a number of sub-passes based on its + * configuration, which then become configurable. The complete list of passes + * and their options + * + * Ultimately, the compile() method applies the configured compilation strategy + * to a program and platform, reducing the abstraction level of the program and + * constraining it to the platform as per the strategy. + * + * Constructed passes are usually referred to by instance names. You're free to + * choose these names, as long as they don't contain any special symbols + * (the names must match `[a-zA-Z0-9_\-]+`); the pass should not do anything + * with this name other than use it to name log files and such. Periods are used + * for hierarchy separation, so `a.b` refers to sub-pass `b` of pass `a`. + */ +class Manager { +public: + + /** + * Dumps the documentation for the pass JSON configuration structure. + */ + static void dump_docs(std::ostream &os = std::cout, const utils::Str &line_prefix = ""); + +private: + + /** + * The pass factory we're using. + */ + CFactoryRef pass_factory; + + /** + * The root pass group. + */ + PassRef root; + +public: + + /** + * Constructs a new pass manager. + */ + explicit Manager( + const utils::Str &architecture = "", + const utils::Set &dnu = {}, + const Factory &factory = {} + ); + + /** + * Constructs a pass manager based on the given JSON configuration. Refer + * to dump_docs() for details. + */ + static Manager from_json( + const utils::Json &json, + const Factory &factory = {} + ); + + /** + * Generate a pass manager with a strategy that aims to mimic the flow of + * the OpenQL compiler as it was before pass management as closely as + * possible. The actual pass list is derived from the eqasm_compiler key + * in the configuration file and from the global options (similar to the + * "compatibility-mode" key in the JSON strategy definition format). + */ + static Manager from_defaults(const plat::PlatformRef &platform); + + /** + * Returns a reference to the root pass group. + */ + const PassRef &get_root(); + + /** + * Returns a reference to the root pass group. + */ + CPassRef get_root() const; + + /** + * Dumps documentation for all available pass types, as well as the option + * documentation for the passes. + */ + void dump_pass_types( + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" + ) const; + + /** + * Dumps the currently configured compilation strategy to the given stream. + */ + void dump_strategy( + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" + ) const; + + /** + * Sets a pass option. Periods are used as hierarchy separators; the last + * element will be the option name, and the preceding elements represent + * pass instance names. Furthermore, wildcards may be used for the pass name + * elements (asterisks for zero or more characters and a question mark for a + * single character) to select multiple or all immediate sub-passes of that + * group, and a double asterisk may be used for the element before the + * option name to chain to set_option_recursively() instead. The return + * value is the number of passes that were affected; passes are only + * affected when they are selected by the option path AND have an option + * with the specified name. If must_exist is set an exception will be thrown + * if none of the passes were affected, otherwise 0 will be returned. + */ + utils::UInt set_option( + const utils::Str &path, + const utils::Str &value, + utils::Bool must_exist = true + ); + + /** + * Sets an option for all passes recursively. The return value is the number + * of passes that were affected; passes are only affected when they have an + * option with the specified name. If must_exist is set an exception will be + * thrown if none of the passes were affected, otherwise 0 will be returned. + */ + utils::UInt set_option_recursively( + const utils::Str &option, + const utils::Str &value, + utils::Bool must_exist = true + ); + + /** + * Returns the current value of an option. Periods are used as hierarchy + * separators; the last element will be the option name, and the preceding + * elements represent pass instance names. + */ + const utils::Option &get_option(const utils::Str &path) const; + + /** + * Appends a pass to the end of the pass list. If type_name is empty + * or unspecified, a generic subgroup is added. Returns a reference to the + * constructed pass. + */ + PassRef append_pass( + const utils::Str &type_name = "", + const utils::Str &instance_name = "", + const utils::Map &options = {} + ); + + /** + * Appends a pass to the beginning of the pass list. If type_name is empty + * or unspecified, a generic subgroup is added. Returns a reference to the + * constructed pass. + */ + PassRef prefix_pass( + const utils::Str &type_name = "", + const utils::Str &instance_name = "", + const utils::Map &options = {} + ); + + /** + * Inserts a pass immediately after the target pass (named by instance). If + * target does not exist, an exception is thrown. If type_name is empty or + * unspecified, a generic subgroup is added. Returns a reference to the + * constructed pass. Periods may be used in target to traverse deeper into + * the pass hierarchy. + */ + PassRef insert_pass_after( + const utils::Str &target, + const utils::Str &type_name = "", + const utils::Str &instance_name = "", + const utils::Map &options = {} + ); + + /** + * Inserts a pass immediately before the target pass (named by instance). If + * target does not exist, an exception is thrown. If type_name is empty or + * unspecified, a generic subgroup is added. Returns a reference to the + * constructed pass. Periods may be used in target to traverse deeper into + * the pass hierarchy. + */ + PassRef insert_pass_before( + const utils::Str &target, + const utils::Str &type_name = "", + const utils::Str &instance_name = "", + const utils::Map &options = {} + ); + + /** + * Looks for the pass with the target instance name, and embeds it into a + * newly generated group. The group will assume the name of the original + * pass, while the original pass will be renamed as specified by sub_name. + * Note that this ultimately does not modify the pass order. If target does + * not exist or this pass is not a group of sub-passes, an exception is + * thrown. Returns a reference to the constructed group. Periods may be used + * in target to traverse deeper into the pass hierarchy. + */ + PassRef group_pass( + const utils::Str &target, + const utils::Str &sub_name = "main" + ); + + /** + * Like group_pass(), but groups an inclusive range of passes into a + * group with the given name, leaving the original pass names unchanged. + * Periods may be used in from/to to traverse deeper into the pass + * hierarchy, but the hierarchy prefix must be the same for from and to. + */ + PassRef group_passes( + const utils::Str &from, + const utils::Str &to, + const utils::Str &group_name + ); + + /** + * Looks for an unconditional pass group with the target instance name and + * flattens its contained passes into its parent group. The names of the + * passes found in the collapsed group are prefixed with name_prefix before + * they are added to the parent group. Note that this ultimately does not + * modify the pass order. If the target instance name does not exist or is + * not an unconditional group, an exception is thrown. Periods may be used + * in target to traverse deeper into the pass hierarchy. + */ + void flatten_subgroup( + const utils::Str &target, + const utils::Str &name_prefix = "" + ); + + /** + * Returns a reference to the pass with the given instance name. If no such + * pass exists, an exception is thrown. Periods may be used as hierarchy + * separators to get nested sub-passes. + */ + PassRef get_pass(const utils::Str &target) const; + + /** + * Returns whether a pass with the target instance name exists. Periods may + * be used in target to traverse deeper into the pass hierarchy. + */ + utils::Bool does_pass_exist(const utils::Str &target) const; + + /** + * Returns the total number of passes in the root hierarchy. + */ + utils::UInt get_num_passes() const; + + /** + * If this pass constructed into a group of passes, returns a reference to + * the list containing all the sub-passes. Otherwise, an exception is + * thrown. + */ + const utils::List &get_passes() const; + + /** + * Returns an indexable list of references to all passes with the given + * type within the root hierarchy. + */ + utils::Vec get_sub_passes_by_type(const utils::Str &target) const; + + /** + * Removes the pass with the given target instance name, or throws an + * exception if no such pass exists. + */ + void remove_pass(const utils::Str &target); + + /** + * Clears the entire pass list. + */ + void clear_passes(); + + /** + * Constructs all passes recursively. This freezes the pass options, but + * allows subtrees to be modified. + */ + void construct(); + + /** + * Ensures that all passes have been constructed, and then runs the passes + * on the given program. + */ + void compile(const ir::ProgramRef &program); + +}; + +/** + * A shared pointer reference to a pass manager. + */ +using Ref = utils::Ptr; + +} // namespace pmgr +} // namespace ql diff --git a/include/ql/pmgr/pass_types/base.h b/include/ql/pmgr/pass_types/base.h new file mode 100644 index 000000000..a1458b836 --- /dev/null +++ b/include/ql/pmgr/pass_types/base.h @@ -0,0 +1,624 @@ +/** \file + * Defines the base classes for all passes. + */ + +#pragma once + +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/ptr.h" +#include "ql/utils/list.h" +#include "ql/utils/vec.h" +#include "ql/utils/set.h" +#include "ql/utils/options.h" +#include "ql/ir/ir.h" +#include "ql/pmgr/declarations.h" +#include "ql/pmgr/condition.h" + +namespace ql { +namespace pmgr { +namespace pass_types { + +/** + * Context information provided to the run function of a pass by the pass + * management system. + */ +struct Context { + + /** + * The fully-qualified pass name, using periods for hierarchy separation. + */ + utils::Str full_pass_name; + + /** + * The directory and filename prefix that should be used for all output + * products of the pass. + */ + utils::Str output_prefix; + + /** + * Reference to the pass options. + */ + const utils::Options &options; + +}; + +// Forward declaration for the base type. +class Base; + +/** + * A reference to any pass type. + */ +using Ref = utils::Ptr; + +/** + * An immutable reference to any pass type. + */ +using CRef = utils::Ptr; + +/** + * Type of a node within the pass instance tree that represents the compilation + * strategy. + */ +enum class NodeType { + + /** + * construct() has not been called yet, so the node type is still + * undetermined. + */ + UNKNOWN, + + /** + * A normal pass that semantically doesn't contain a group of sub-passes. + * The group is ignored by compile() and calls to functions that modify it + * throw an exception. compile() only calls run_internal()/run(). + */ + NORMAL, + + /** + * An unconditional group of passes. Such a group serves only as a + * hierarchical level for logging/profiling and as an abstraction layer to + * users; compile() will simply run the passes in sequence with no further + * logic; run_internal()/run() is not called. Such groups can be collapsed + * or created by the user at will. + */ + GROUP, + + /** + * A conditional group of passes. compile() will first call + * run_internal()/run(), and then use its status return value and the + * condition to determine whether to run the pass group as well. + */ + GROUP_IF, + + /** + * Like GROUP_IF, but loops back and re-evaluates the condition by calling + * run_internal()/run() again after the group of passes finishes executing. + */ + GROUP_WHILE, + + /** + * Like GROUP_WHILE, but the condition is evaluated at the end of the loop + * rather than at the beginning, so the group is evaluated at list once. + */ + GROUP_REPEAT_UNTIL_NOT + +}; + +/** + * Base class for all passes. + */ +class Base { +private: + + /** + * Reference to the pass factory that was used to construct this pass, + * allowing this pass to construct sub-passes. + */ + utils::Ptr pass_factory; + + /** + * The full type name for this pass. This is the full name that was used + * when the pass was registered with the pass factory. The same pass class + * may be registered with multiple type names, in which case the pass + * implementation may use this to differentiate. An empty type name is used + * for generic groups. + */ + const utils::Str type_name; + + /** + * The instance name for this pass, i.e. the name that the user assigned to + * it or the name that was assigned to it automatically. Must match + * `[a-zA-Z0-9_\-]+` for normal passes or groups, and must be unique within + * the group of passes it resides in. The root group uses an empty name. + * Instance names should NOT have a semantic meaning besides possibly + * uniquely naming output files; use options for any other functional + * configuration. + */ + utils::Str instance_name; + + /** + * The type of node that this pass represents in the pass tree. Configured + * by construct(). + */ + NodeType node_type = NodeType::UNKNOWN; + + /** + * List of sub-passes, used only by group nodes. + */ + utils::List sub_pass_order; + + /** + * Mapping from instance name to sub-pass, used only by group nodes. + */ + utils::Map sub_pass_names; + + /** + * The condition used to turn the pass return value into a boolean, used + * only for conditional group nodes. + */ + condition::Ref condition; + + /** + * Throws an exception if the given pass instance name is invalid. + */ + static void check_pass_name( + const utils::Str &instance_name, + const utils::Map &existing_pass_names + ); + + /** + * Returns a unique name generated from the given type name. + */ + utils::Str generate_valid_pass_name(const utils::Str &type_name) const; + + /** + * Makes a new pass. Used by the various functions that add passes. + */ + Ref make_pass( + const utils::Str &type_name, + const utils::Str &instance_name, + const utils::Map &options + ) const; + + /** + * Returns an iterator for the sub_pass_order list corresponding with the + * given target instance name, or throws an exception if no such pass is + * found. + */ + utils::List::iterator find_pass(const utils::Str &target); + + /** + * Checks whether access to the sub-pass list is allowed. Throws an + * exception if not. + */ + void check_group_access_allowed() const; + + /** + * Checks whether access to the condition is allowed. Throws an exception + * if not. + */ + void check_condition_access_allowed() const; + +protected: + + /** + * The option set for this pass. The available options should be registered + * in the constructor of the derived pass types. It becomes illegal to + * change options once construct() is called. + */ + utils::Options options; + + /** + * Constructs the abstract pass. No error checking here; this is up to the + * parent pass group. + */ + Base( + const utils::Ptr &pass_factory, + const utils::Str &type_name, + const utils::Str &instance_name + ); + + /** + * Writes the documentation for this pass to the given output stream. May + * depend on type_name, but should not depend on anything else. The + * automatically-generated documentation for the options should not be + * added here; it is added by dump_help(). The help should end in a newline, + * and every line printed should start with line_prefix. + */ + virtual void dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const = 0; + + /** + * Overridable implementation of construct(). Must return a non-unknown node + * type for this pass to construct into, based on its options. If a group + * type is returned, passes must be populated (it may be assumed to be empty + * initially). If a conditional group type is returned, condition must also + * be populated. + */ + virtual NodeType on_construct( + const utils::Ptr &factory, + utils::List &passes, + condition::Ref &condition + ) = 0; + + /** + * Overridable implementation for calling the implementation of the pass. + */ + virtual utils::Int run_internal( + const ir::ProgramRef &program, + const Context &context + ) const = 0; + + /** + * Returns `pass ""` for normal passes and `root` for the root pass. + * Used for error messages. + */ + utils::Str describe() const; + +public: + + /** + * Default virtual destructor. + */ + virtual ~Base() = default; + + /** + * Returns a user-friendly type name for this pass. Used for documentation + * generation. + */ + virtual utils::Str get_friendly_type() const = 0; + + /** + * Returns the full, desugared type name that this pass was constructed + * with. + */ + const utils::Str &get_type() const; + + /** + * Returns the instance name of the pass within the surrounding group. + */ + const utils::Str &get_name() const; + + /** + * Dumps the documentation for this pass to the given stream. + */ + void dump_help( + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" + ) const; + + /** + * Dumps the current state of the options to the given stream. If only_set + * is set to true, only the options that were explicitly configured are + * dumped. + */ + void dump_options( + utils::Bool only_set = false, + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" + ) const; + + /** + * Dumps the entire compilation strategy including configured options of + * this pass and all sub-passes. + */ + void dump_strategy( + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" + ) const; + + /** + * Sets an option. Periods may be used as hierarchy separators to set + * options for sub-passes; the last element will be the option name, and the + * preceding elements represent pass instance names. Furthermore, wildcards + * may be used for the pass name elements (asterisks for zero or more + * characters and a question mark for a single character) to select multiple + * or all immediate sub-passes of that group, and a double asterisk may be + * used for the element before the option name to chain to + * set_option_recursively() instead. The return value is the number of + * passes that were affected; passes are only affected when they are + * selected by the option path AND have an option with the specified name. + * If must_exist is set an exception will be thrown if none of the passes + * were affected, otherwise 0 will be returned. + */ + utils::UInt set_option( + const utils::Str &option, + const utils::Str &value, + utils::Bool must_exist = true + ); + + /** + * Sets an option for all sub-passes recursively. The return value is the + * number of passes that were affected; passes are only affected when they + * have an option with the specified name. If must_exist is set an exception + * will be thrown if none of the passes were affected, otherwise 0 will be + * returned. + */ + utils::UInt set_option_recursively( + const utils::Str &option, + const utils::Str &value, + utils::Bool must_exist = true + ); + + /** + * Returns the current value of an option. Periods may be used as hierarchy + * separators to get options from sub-passes (if any). + */ + const utils::Option &get_option(const utils::Str &option) const; + + /** + * Returns mutable access to the embedded options object. This is allowed + * only until construct() is called. + */ + utils::Options &get_options(); + + /** + * Returns read access to the embedded options object. + */ + const utils::Options &get_options() const; + + /** + * Constructs this pass. During construction, the pass implementation may + * decide, based on its options, to become a group of passes or a normal + * pass. If it decides to become a group, the group may be introspected or + * modified by the user. The options are frozen after this, so set_option() + * will start throwing exceptions when called. construct() may be called any + * number of times, but becomes no-op after the first call. + */ + void construct(); + +private: + friend class ::ql::pmgr::Manager; + + /** + * Recursively constructs this pass and all its sub-passes (if it constructs + * or previously constructed into a group). + */ + void construct_recursive(const utils::Str &pass_name_prefix = ""); + +public: + + /** + * Returns whether this pass has been constructed yet. + */ + utils::Bool is_constructed() const; + + /** + * Returns whether this pass has configurable sub-passes. + */ + utils::Bool is_group() const; + + /** + * Returns whether this pass is a simple group of which the sub-passes can + * be collapsed into the parent pass group without affecting the strategy. + */ + utils::Bool is_collapsible() const; + + /** + * Returns whether this is the root pass group in a pass manager. + */ + utils::Bool is_root() const; + + /** + * Returns whether this pass contains a conditionally-executed group. + */ + utils::Bool is_conditional() const; + + /** + * If this pass constructed into a group of passes, appends a pass to the + * end of its pass list. Otherwise, an exception is thrown. If type_name is + * empty or unspecified, a generic subgroup is added. Returns a reference to + * the constructed pass. + */ + Ref append_sub_pass( + const utils::Str &type_name = "", + const utils::Str &instance_name = "", + const utils::Map &options = {} + ); + + /** + * If this pass constructed into a group of passes, appends a pass to the + * beginning of its pass list. Otherwise, an exception is thrown. If + * type_name is empty or unspecified, a generic subgroup is added. Returns a + * reference to the constructed pass. + */ + Ref prefix_sub_pass( + const utils::Str &type_name = "", + const utils::Str &instance_name = "", + const utils::Map &options = {} + ); + + /** + * If this pass constructed into a group of passes, inserts a pass + * immediately after the target pass (named by instance). If target does not + * exist or this pass is not a group of sub-passes, an exception is thrown. + * If type_name is empty or unspecified, a generic subgroup is added. + * Returns a reference to the constructed pass. Periods may be used in + * target to traverse deeper into the pass hierarchy. + */ + Ref insert_sub_pass_after( + const utils::Str &target, + const utils::Str &type_name = "", + const utils::Str &instance_name = "", + const utils::Map &options = {} + ); + + /** + * If this pass constructed into a group of passes, inserts a pass + * immediately before the target pass (named by instance). If target does + * not exist or this pass is not a group of sub-passes, an exception is + * thrown. If type_name is empty or unspecified, a generic subgroup is + * added. Returns a reference to the constructed pass. Periods may be used + * in target to traverse deeper into the pass hierarchy. + */ + Ref insert_sub_pass_before( + const utils::Str &target, + const utils::Str &type_name = "", + const utils::Str &instance_name = "", + const utils::Map &options = {} + ); + + /** + * If this pass constructed into a group of passes, looks for the pass with + * the target instance name, and embeds it into a newly generated group. The + * group will assume the name of the original pass, while the original pass + * will be renamed as specified by sub_name. Note that this ultimately does + * not modify the pass order. If target does not exist or this pass is not a + * group of sub-passes, an exception is thrown. Returns a reference to the + * constructed group. Periods may be used in target to traverse deeper into + * the pass hierarchy. + */ + Ref group_sub_pass( + const utils::Str &target, + const utils::Str &sub_name = "main" + ); + + /** + * Like group_sub_pass(), but groups an inclusive range of passes into a + * group with the given name, leaving the original pass names unchanged. + * Periods may be used in from/to to traverse deeper into the pass + * hierarchy, but the hierarchy prefix must be the same for from and to. + */ + Ref group_sub_passes( + const utils::Str &from, + const utils::Str &to, + const utils::Str &group_name + ); + + /** + * If this pass constructed into a group of passes, looks for the pass with + * the target instance name, treats it as a generic group, and flattens its + * contained passes into the list of sub-passes of its parent. The names of + * the passes found in the collapsed subgroup are prefixed with name_prefix + * before they are added to the parent group. Note that this ultimately does + * not modify the pass order. If target does not exist, does not construct + * into a group of passes (construct() is called automatically), or this + * pass is not a group of sub-passes, an exception is thrown. Periods may be + * used in target to traverse deeper into the pass hierarchy. + */ + void flatten_subgroup( + const utils::Str &target, + const utils::Str &name_prefix = "" + ); + + /** + * If this pass constructed into a group of passes, returns a reference to + * the pass with the given instance name. If target does not exist or this + * pass is not a group of sub-passes, an exception is thrown. Periods may be + * used as hierarchy separators to get nested sub-passes. + */ + Ref get_sub_pass(const utils::Str &target) const; + + /** + * If this pass constructed into a group of passes, returns whether a + * sub-pass with the target instance name exists. Otherwise, an exception is + * thrown. Periods may be used in target to traverse deeper into the pass + * hierarchy. + */ + utils::Bool does_sub_pass_exist(const utils::Str &target) const; + + /** + * If this pass constructed into a group of passes, returns the total number + * of immediate sub-passes. Otherwise, an exception is thrown. + */ + utils::UInt get_num_sub_passes() const; + + /** + * If this pass constructed into a group of passes, returns a reference to + * the list containing all the sub-passes. Otherwise, an exception is + * thrown. + */ + const utils::List &get_sub_passes() const; + + /** + * If this pass constructed into a group of passes, returns an indexable + * list of references to all immediate sub-passes with the given type. + * Otherwise, an exception is thrown. + */ + utils::Vec get_sub_passes_by_type(const utils::Str &target) const; + + /** + * If this pass constructed into a group of passes, removes the sub-pass + * with the target instance name. If target does not exist or this pass is + * not a group of sub-passes, an exception is thrown. Periods may be used in + * target to traverse deeper into the pass hierarchy. + */ + void remove_sub_pass(const utils::Str &target); + + /** + * If this pass constructed into a group of passes, removes all sub-passes. + * Otherwise, an exception is thrown. + */ + void clear_sub_passes(); + + /** + * If this pass constructed into a conditional pass group, returns a const + * reference to the configured condition. Otherwise, an exception is thrown. + */ + condition::CRef get_condition() const; + + /** + * If this pass constructed into a conditional pass group, returns a mutable + * reference to the configured condition. Otherwise, an exception is thrown. + */ + condition::Ref get_condition(); + +private: + + /** + * Handles the debug option. Called once before and once after compile(). + * after_pass is false when run before, and true when run after. + */ + void handle_debugging( + const ir::ProgramRef &program, + const Context &context, + utils::Bool after_pass + ); + + /** + * Wrapper around running the main pass implementation for this pass, taking + * care of logging, profiling, etc. + */ + utils::Int run_main_pass( + const ir::ProgramRef &program, + const Context &context + ) const; + + /** + * Wrapper around running the sub-passes for this pass, taking care of logging, + * profiling, etc. + */ + void run_sub_passes( + const ir::ProgramRef &program, + const Context &context + ) const; + +public: + + /** + * Executes this pass or pass group on the given program. + */ + void compile( + const ir::ProgramRef &program, + const utils::Str &pass_name_prefix = "" + ); + +}; + +} // namespace pass_types + +/** + * Shorthand for a reference to any pass type. + */ +using PassRef = pass_types::Ref; + +/** + * Shorthand for an immutable reference to any pass type. + */ +using CPassRef = pass_types::CRef; + +} // namespace pmgr +} // namespace ql diff --git a/include/ql/pmgr/pass_types/specializations.h b/include/ql/pmgr/pass_types/specializations.h new file mode 100644 index 000000000..f2dd8a6a0 --- /dev/null +++ b/include/ql/pmgr/pass_types/specializations.h @@ -0,0 +1,278 @@ +/** \file + * Defines specialized abstract classes for passes. These are all abstract, to + * be implemented by actual passes; only common functionality is provided. + */ + +#pragma once + +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/ptr.h" +#include "ql/ir/ir.h" +#include "ql/pmgr/condition.h" +#include "ql/pmgr/pass_types/base.h" + +namespace ql { +namespace pmgr { +namespace pass_types { + +/** + * A pass type for passes that always construct into a simple group. For + * example, a generic optimizer pass with an option-configured set of + * optimization passes would derive from this. + */ +class Group : public Base { +protected: + + /** + * Constructs the abstract pass group. No error checking here; this is up to + * the parent pass group. + */ + Group( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Simple implementation for on_construct() that always returns true and + * defers to get_passes() for the initial pass list. + */ + NodeType on_construct( + const utils::Ptr &factory, + utils::List &passes, + condition::Ref &condition + ) final; + + /** + * Dummy implementation for compilation. Should never be called, as this + * pass always behaves as an unconditional group. Thus, it just throws an + * exception. + */ + utils::Int run_internal( + const ir::ProgramRef &program, + const Context &context + ) const final; + + /** + * Overridable implementation that returns the initial pass list for this + * pass group. The default implementation is no-op. + */ + virtual void get_passes( + const utils::Ptr &factory, + utils::List &passes + ) = 0; + +}; + +/** + * A pass type for regular passes that normally don't construct into a group + * (although this is still possible). Just provides a default implementation for + * on_construct(). + */ +class Normal : public Base { +protected: + + /** + * Constructs the normal pass. No error checking here; this is up to the + * parent pass group. + */ + Normal( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Default implementation for on_construct() that makes this a normal pass. + * May be overridden to allow the pass to generate into a group as well, + * based on its options. + */ + NodeType on_construct( + const utils::Ptr &factory, + utils::List &passes, + condition::Ref &condition + ) override; + +}; + +/** + * A pass type for passes that apply a program-wide transformation. The platform + * may not be modified. + * + * TODO: the tree structures currently do not have an immutable variant that + * protects against accidental modification. + */ +class ProgramTransformation : public Normal { +protected: + + /** + * Constructs the pass. No error checking here; this is up to the parent + * pass group. + */ + ProgramTransformation( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Implementation for on_compile() that calls run() appropriately. + */ + utils::Int run_internal( + const ir::ProgramRef &program, + const Context &context + ) const final; + + /** + * The virtual implementation for this pass. + */ + virtual utils::Int run( + const ir::ProgramRef &program, + const Context &context + ) const = 0; + +}; + +/** + * A pass type for passes that apply a transformation per kernel/basic block. + * The platform may not be modified. The return value for such a pass is always + * 0. + * + * TODO: the tree structures currently do not have an immutable variant that + * protects against accidental modification. + */ +class KernelTransformation : public Normal { +protected: + + /** + * Constructs the pass. No error checking here; this is up to the parent + * pass group. + */ + KernelTransformation( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Implementation for on_compile() that calls run() appropriately. + */ + utils::Int run_internal( + const ir::ProgramRef &program, + const Context &context + ) const final; + + /** + * Initial accumulator value for the return value. Defaults to zero. + */ + virtual utils::Int retval_initialize() const; + + /** + * Return value reduction operator. Defaults to addition. + */ + virtual utils::Int retval_accumulate(utils::Int state, utils::Int kernel) const; + + /** + * The virtual implementation for this pass. + */ + virtual utils::Int run( + const ir::ProgramRef &program, + const ir::KernelRef &kernel, + const Context &context + ) const = 0; + +}; + +/** + * A pass type for passes that analyze the complete program without modifying + * it. + * + * TODO: the tree structures currently do not have an immutable variant that + * protects against accidental modification. + */ +class ProgramAnalysis : public Normal { +protected: + + /** + * Constructs the pass. No error checking here; this is up to the parent + * pass group. + */ + ProgramAnalysis( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Implementation for on_compile() that calls run() appropriately. + */ + utils::Int run_internal( + const ir::ProgramRef &program, + const Context &context + ) const final; + + /** + * The virtual implementation for this pass. The contents of platform and + * program must not be modified. + */ + virtual utils::Int run( + const ir::ProgramRef &program, + const Context &context + ) const = 0; + +}; + +/** + * A pass type for passes that analyze individual kernels. The return value for + * such a pass is always 0. + * + * TODO: the tree structures currently do not have an immutable variant that + * protects against accidental modification. + */ +class KernelAnalysis : public Normal { +protected: + + /** + * Constructs the pass. No error checking here; this is up to the parent + * pass group. + */ + KernelAnalysis( + const utils::Ptr &pass_factory, + const utils::Str &instance_name, + const utils::Str &type_name + ); + + /** + * Implementation for on_compile() that calls run() appropriately. + */ + utils::Int run_internal( + const ir::ProgramRef &program, + const Context &context + ) const final; + + /** + * Initial accumulator value for the return value. Defaults to zero. + */ + virtual utils::Int retval_initialize() const; + + /** + * Return value reduction operator. Defaults to addition. + */ + virtual utils::Int retval_accumulate(utils::Int state, utils::Int kernel) const; + + /** + * The virtual implementation for this pass. The contents of program and + * kernel must not be modified. + */ + virtual utils::Int run( + const ir::ProgramRef &program, + const ir::KernelRef &kernel, + const Context &context + ) const = 0; + +}; + +} // namespace pass_types +} // namespace pmgr +} // namespace ql diff --git a/include/ql/resource/instrument.h b/include/ql/resource/instrument.h new file mode 100644 index 000000000..1f3d0f780 --- /dev/null +++ b/include/ql/resource/instrument.h @@ -0,0 +1,104 @@ +/** \file + * Defines an instrument resource, used to model mutually-exlusive gates due + * to instrument resource sharing. + */ + +#pragma once + +#include "ql/utils/set.h" +#include "ql/utils/rangemap.h" +#include "ql/rmgr/resource_types/base.h" + +namespace ql { +namespace resource { +namespace instrument { + +/** + * State per instrument. + */ +using State = utils::RangeMap; + +/** + * Forward-declaration for the configuration structure, defined in the CC file. + */ +struct Config; + +/** + * Instrument resource. + */ +class InstrumentResource : public rmgr::resource_types::Base { +private: + + /** + * The reservations made for each instrument. + */ + utils::Vec state; + + /** + * Shared pointer to the configuration structure. + */ + utils::Ptr config; + +protected: + + /** + * Initializes this resource. + */ + void on_initialize(rmgr::Direction direction) override; + + /** + * Checks availability of and/or reserves a gate. + */ + utils::Bool on_gate( + utils::UInt cycle, + const ir::GateRef &gate, + utils::Bool commit + ) override; + + /** + * Dumps documentation for this resource. + */ + void on_dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + + /** + * Dumps the configuration of this resource. + */ + void on_dump_config( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + + /** + * Dumps the state of this resource. + */ + void on_dump_state( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Constructs the resource. No error checking here; this is up to the + * resource manager. + */ + explicit InstrumentResource(const rmgr::Context &context); + + /** + * Returns a user-friendly type name for this resource. + */ + utils::Str get_friendly_type() const override; + +}; + +/** + * Shorthand for namespace-based notation. + */ +using Resource = InstrumentResource; + +} // namespace instrument +} // namespace resource +} // namespace ql diff --git a/include/ql/resource/inter_core_channel.h b/include/ql/resource/inter_core_channel.h new file mode 100644 index 000000000..152cc5caf --- /dev/null +++ b/include/ql/resource/inter_core_channel.h @@ -0,0 +1,104 @@ +/** \file + * Defines the channel resource. This limits the amount of parallel + * communication between cores in a multi-core system. + */ + +#pragma once + +#include "ql/utils/rangemap.h" +#include "ql/rmgr/resource_types/base.h" + +namespace ql { +namespace resource { +namespace inter_core_channel { + +/** + * State per qubit. + */ +using State = utils::RangeSet; + +/** + * Forward-declaration for the configuration structure, defined in the CC file. + */ +struct Config; + +/** + * Communication channel resource. This limits the amount of parallel + * communication between cores in a multi-core system. + */ +class InterCoreChannelResource : public rmgr::resource_types::Base { +private: + + /** + * The reservations for each [core][channel]. + */ + utils::Vec> state; + + /** + * Shared pointer to the configuration structure. + */ + utils::Ptr config; + +protected: + + /** + * Initializes this resource. + */ + void on_initialize(rmgr::Direction direction) override; + + /** + * Checks availability of and/or reserves a gate. + */ + utils::Bool on_gate( + utils::UInt cycle, + const ir::GateRef &gate, + utils::Bool commit + ) override; + + /** + * Dumps documentation for this resource. + */ + void on_dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + + /** + * Dumps the configuration of this resource. + */ + void on_dump_config( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + + /** + * Dumps the state of this resource. + */ + void on_dump_state( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Constructs the resource. No error checking here; this is up to the + * resource manager. + */ + explicit InterCoreChannelResource(const rmgr::Context &context); + + /** + * Returns a user-friendly type name for this resource. + */ + utils::Str get_friendly_type() const override; + +}; + +/** + * Shorthand for namespace-based notation. + */ +using Resource = InterCoreChannelResource; + +} // namespace inter_core_channel +} // namespace resource +} // namespace ql diff --git a/include/ql/resource/qubit.h b/include/ql/resource/qubit.h new file mode 100644 index 000000000..5cffed7ac --- /dev/null +++ b/include/ql/resource/qubit.h @@ -0,0 +1,105 @@ +/** \file + * Defines the qubit resource. This resource prevents a qubit from being used + * more than once in each cycle. + */ + +#pragma once + +#include "ql/utils/rangemap.h" +#include "ql/rmgr/resource_types/base.h" + +namespace ql { +namespace resource { +namespace qubit { + +/** + * State per qubit. + */ +using State = utils::RangeSet; + +/** + * Qubit resource. This resource prevents a qubit from being used more than once + * in each cycle. + */ +class QubitResource : public rmgr::resource_types::Base { +private: + + /** + * The reservations for each qubit. + */ + utils::Vec state; + + /** + * When set, there is a defined scheduling direction, which means it's + * sufficient to only track the latest reservation for each qubit. + */ + utils::Bool optimize; + + /** + * Cycle time of the platform. + */ + utils::UInt cycle_time; + +protected: + + /** + * Initializes this resource. + */ + void on_initialize(rmgr::Direction direction) override; + + /** + * Checks availability of and/or reserves a gate. + */ + utils::Bool on_gate( + utils::UInt cycle, + const ir::GateRef &gate, + utils::Bool commit + ) override; + + /** + * Dumps documentation for this resource. + */ + void on_dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + + /** + * Dumps the configuration of this resource. + */ + void on_dump_config( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + + /** + * Dumps the state of this resource. + */ + void on_dump_state( + std::ostream &os, + const utils::Str &line_prefix + ) const override; + +public: + + /** + * Constructs the resource. No error checking here; this is up to the + * resource manager. + */ + explicit QubitResource(const rmgr::Context &context); + + /** + * Returns a user-friendly type name for this resource. + */ + utils::Str get_friendly_type() const override; + +}; + +/** + * Shorthand for namespace-based notation. + */ +using Resource = QubitResource; + +} // namespace qubit +} // namespace resource +} // namespace ql diff --git a/include/ql/rmgr/factory.h b/include/ql/rmgr/factory.h new file mode 100644 index 000000000..9060b4c3d --- /dev/null +++ b/include/ql/rmgr/factory.h @@ -0,0 +1,112 @@ +/** \file + * Resource factory implementation. + */ + +#pragma once + +#include +#include "ql/utils/ptr.h" +#include "ql/utils/str.h" +#include "ql/utils/map.h" +#include "ql/utils/json.h" +#include "ql/plat/platform.h" +#include "ql/rmgr/resource_types/base.h" + +namespace ql { +namespace rmgr { + +/** + * Factory class for constructing resources. + */ +class Factory { +private: + + /** + * Function pointer object type that is used to construct resource class + * instances. + */ + using ConstructorFn = utils::Ptr< + std::function< + ResourceRef( + const utils::Str &instance_name, + const plat::PlatformRef &platform, + const utils::Json &configuration + ) + > + >; + + /** + * Map from (desugared) resource type name to a constructor function for + * that particular resource type. + */ + utils::Map resource_types; + +public: + + /** + * Constructs a default resource factory for OpenQL. + */ + Factory(); + + /** + * Registers a resource class with the given type name. + */ + template + void register_resource(const utils::Str &type_name) { + ConstructorFn fn; + fn.emplace([type_name]( + const utils::Str &instance_name, + const plat::PlatformRef &platform, + const utils::Json &configuration + ) { + ResourceRef resource; + resource.emplace(Context({ + type_name, + instance_name, + platform, + configuration + })); + return resource; + }); + resource_types.set(type_name) = fn; + } + + /** + * Returns a copy of this resource factory with the following modifications + * made to the map. + * + * - Entries with a `dnu` path component in them are removed. If the type + * of the removed entry exists in dnu however, it will be reinserted with + * the `dnu` path component removed. + * - A copy is made of entries that include an `arch.` + * component pair, with that pair stripped. + * + * The original factory is not modified. + */ + Factory configure( + const utils::Str &architecture, + const utils::Set &dnu + ) const; + + /** + * Builds a resource instance. + */ + ResourceRef build_resource( + const utils::Str &type_name, + const utils::Str &instance_name, + const plat::PlatformRef &platform, + const utils::Json &configuration + ) const; + + /** + * Dumps documentation for all resource types known by this factory. + */ + void dump_resource_types( + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" + ) const; + +}; + +} // namespace rmgr +} // namespacq ql diff --git a/include/ql/rmgr/manager.h b/include/ql/rmgr/manager.h new file mode 100644 index 000000000..4129d7299 --- /dev/null +++ b/include/ql/rmgr/manager.h @@ -0,0 +1,138 @@ +/** \file + * Defines the resource manager. + */ + +#pragma once + +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/utils/list.h" +#include "ql/utils/map.h" +#include "ql/rmgr/resource_types/base.h" +#include "ql/rmgr/factory.h" +#include "ql/rmgr/state.h" + +namespace ql { +namespace rmgr { + +/** + * A collection of resources corresponding to a particular platform. + */ +class Manager { +public: + + /** + * Dumps the documentation for the resource JSON configuration structure. + */ + static void dump_docs(std::ostream &os = std::cout, const utils::Str &line_prefix = ""); + +private: + + /** + * Factory for constructing resources. + */ + const Factory factory; + + /** + * The platform that this resource manager is built for. + */ + const plat::PlatformRef &platform; + + /** + * The list of resources. + */ + utils::Map resources; + + /** + * Returns whether the given user-specified name is a valid resource name. + */ + void check_resource_name(const utils::Str &name) const; + + /** + * Returns a unique name generated from the given type name. + */ + utils::Str generate_valid_resource_name(const utils::Str &type_name) const; + +public: + + /** + * Constructs a new, empty resource manager. + */ + explicit Manager( + const plat::PlatformRef &platform, + const utils::Str &architecture = "", + const utils::Set &dnu = {}, + const Factory &factory = {} + ); + + /** + * Constructs a resource manager based on the given JSON configuration. + * Refer to dump_docs() for more information. + */ + static Manager from_json( + const plat::PlatformRef &platform, + const utils::Json &json, + const Factory &factory = {} + ); + + /** + * Builds the default resource manager for the platform. The JSON data is + * taken from platform.resources. + */ + static Manager from_defaults( + const plat::PlatformRef &platform, + const Factory &factory = {} + ); + + /** + * Writes documentation for the available resource types to the given output + * stream. + */ + void dump_resource_types( + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" + ) const; + + /** + * Writes information about the current configuration of this set of + * resources to the given output stream. + */ + void dump_config( + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" + ) const; + + /** + * Adds a resource. If no instance name is provided, a unique name is + * generated. + */ + void add_resource( + const utils::Str &type_name, + const utils::Str &instance_name = "", + const utils::Json &configuration = {} + ); + + /** + * Returns whether a resource with the target instance name exists. + */ + utils::Bool does_resource_exist( + const utils::Str &target + ); + + /** + * Removes the resource with the given target instance name, or throws an + * exception if no such resource exists. + */ + void remove_resource( + const utils::Str &target + ); + + /** + * Builds a state tracker from the configured list of resources. + */ + State build(Direction direction = Direction::UNDEFINED) const; + +}; + +} // namespace rmgr +} // namespacq ql diff --git a/include/ql/rmgr/resource_types/base.h b/include/ql/rmgr/resource_types/base.h new file mode 100644 index 000000000..9709662d8 --- /dev/null +++ b/include/ql/rmgr/resource_types/base.h @@ -0,0 +1,191 @@ +/** \file + * Defines the base class for scheduler resources. + */ + +#pragma once + +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/ir/ir.h" +#include "ql/rmgr/types.h" + +namespace ql { +namespace rmgr { +namespace resource_types { + +/** + * Base class for scheduling resources. Scheduling resources are used to + * represent constraints on when gates can be executed in a schedule, within + * context of other gates. + */ +class Base { +protected: + + /** + * The context information that the resource was constructed with. This is + * wrapped in a Ptr so it doesn't need to be cloned every time the resource + * state is cloned. + */ + const utils::Ptr context; + +private: + + /** + * Whether our state has been initialized yet. + */ + utils::Bool initialized; + + /** + * The scheduling direction. + */ + Direction direction; + + /** + * Used to verify that gates are added in the order specified by direction. + */ + utils::UInt prev_cycle; + +protected: + + /** + * Constructs the abstract resource. No error checking here; this is up to + * the resource manager. + */ + explicit Base(const Context &context); + + /** + * Abstract implementation for initialize(). This is where the JSON + * structure should be parsed and the resource state should be initialized. + * This will only be called once during the lifetime of this resource. The + * default implementation is no-op. + */ + virtual void on_initialize(Direction direction); + + /** + * Abstract implementation for gate(). + */ + virtual utils::Bool on_gate( + utils::UInt cycle, + const ir::GateRef &gate, + utils::Bool commit + ) = 0; + + /** + * Abstract implementation for dump_docs(). + */ + virtual void on_dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const = 0; + + /** + * Abstract implementation for dump_config(). + */ + virtual void on_dump_config( + std::ostream &os, + const utils::Str &line_prefix + ) const = 0; + + /** + * Abstract implementation for dump_state(). + */ + virtual void on_dump_state( + std::ostream &os, + const utils::Str &line_prefix + ) const = 0; + +public: + + /** + * Virtual destructor for proper polymorphism. + */ + virtual ~Base() = default; + + /** + * Returns a user-friendly type name for this resource. + */ + virtual utils::Str get_friendly_type() const = 0; + + /** + * Returns the type name for this resource. + */ + const utils::Str &get_type() const; + + /** + * Returns the user-specified or generated unique instance name for this + * resource. + */ + const utils::Str &get_name() const; + + /** + * Writes the documentation for this resource to the given output stream. + * May depend on type_name, but should not depend on anything else. The help + * information should end in a newline, and every line printed should start + * with line_prefix. + */ + void dump_docs( + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" + ) const; + + /** + * Writes information about the configuration of this resource. This is + * called before initialize(). The printed information should end in a + * newline, and every line printed should start with line_prefix. + */ + void dump_config( + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" + ) const; + + /** + * Initializes the state for this resource for a particular scheduling + * direction. + */ + void initialize(Direction direction); + + /** + * Checks and optionally updates the resource manager state for the given + * gate and (start) cycle number. The state is only updated if the gate is + * schedulable for the given cycle and commit is set. + */ + utils::Bool gate( + utils::UInt cycle, + const ir::GateRef &gate, + utils::Bool commit + ); + + /** + * Dumps a debug representation of the current resource state. + */ + void dump_state( + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" + ) const; + +}; + +/** + * A mutable reference to a resource. + */ +using Ref = utils::CloneablePtr; + +/** + * An immutable reference to a resource. + */ +using CRef = utils::CloneablePtr; + +} // namespace resource_types + +/** + * Shorthand for a reference to any resource type. + */ +using ResourceRef = resource_types::Ref; + +/** + * Shorthand for an immutable reference to any resource type. + */ +using CResourceRef = resource_types::CRef; + +} // namespace rmgr +} // namespacq ql diff --git a/include/ql/rmgr/resource_types/compat.h b/include/ql/rmgr/resource_types/compat.h new file mode 100644 index 000000000..33271da7a --- /dev/null +++ b/include/ql/rmgr/resource_types/compat.h @@ -0,0 +1,119 @@ +/** \file + * Temporary compatibility layer for resources. + */ + +#pragma once + +#include "ql/rmgr/resource_types/base.h" + +namespace ql { +namespace rmgr { +namespace resource_types { + +// FIXME JvS: replace all old-style resources with new ones, then delete this +// whole file. +class OldResource { +public: + utils::Str name; + utils::UInt count; + Direction direction; + + OldResource(const utils::Str &n, Direction dir) : name(n), direction(dir) {} + virtual ~OldResource() = default; + + virtual utils::Bool available(utils::UInt op_start_cycle, const ir::GateRef &ins, const plat::PlatformRef &platform) const = 0; + virtual void reserve(utils::UInt op_start_cycle, const ir::GateRef &ins, const plat::PlatformRef &platform) = 0; +}; + + +template +class Compat : public Base { +private: + + /** + * The old-style resource being wrapped. + */ + utils::Opt resource; + +public: + + /** + * Constructs the resource. + */ + explicit Compat(const Context &context) : Base(context) {} + + /** + * Abstract implementation for initialize(). This is where the JSON + * structure should be parsed and the resource state should be initialized. + * This will only be called once during the lifetime of this resource. The + * default implementation is no-op. + */ + void on_initialize(Direction direction) override { + if (direction == Direction::UNDEFINED) { + throw utils::Exception( + "direction must be forward or backward for old-style resources" + ); + } + resource.emplace(context->platform, direction); + } + + /** + * Abstract implementation for gate(). + */ + utils::Bool on_gate( + utils::UInt cycle, + const ir::GateRef &gate, + utils::Bool commit + ) override { + auto result = resource->available(cycle, gate, context->platform); + if (result && commit) { + resource->reserve(cycle, gate, context->platform); + } + return result; + } + + /** + * Writes the documentation for this resource to the given output stream. + * May depend on type_name, but should not depend on anything else. The help + * information should end in a newline, and every line printed should start + * with line_prefix. + */ + void on_dump_docs( + std::ostream &os, + const utils::Str &line_prefix + ) const override { + os << line_prefix << "Compatibility wrapper for " << typeid(T).name() << "\n"; + } + + /** + * Returns a user-friendly type name for this resource. + */ + utils::Str get_friendly_type() const override { + return utils::Str("Compatibility wrapper for ") + typeid(T).name(); + } + + /** + * Abstract implementation for dump_config(). + */ + void on_dump_config( + std::ostream &os, + const utils::Str &line_prefix + ) const override { + os << line_prefix << "Config dump is not implemented for compatibility wrapper\n"; + } + + /** + * Dumps a debug representation of the current resource state. + */ + void on_dump_state( + std::ostream &os, + const utils::Str &line_prefix + ) const override { + os << line_prefix << "State dump is not implemented for compatibility wrapper\n"; + } + +}; + +} // namespace resource_types +} // namespace rmgr +} // namespacq ql diff --git a/include/ql/rmgr/state.h b/include/ql/rmgr/state.h new file mode 100644 index 000000000..e763cf605 --- /dev/null +++ b/include/ql/rmgr/state.h @@ -0,0 +1,96 @@ +/** \file + * Defines a class for tracking the state of a collection of initialized + * resources. + */ + +#pragma once + +#include "ql/utils/num.h" +#include "ql/utils/vec.h" +#include "ql/ir/ir.h" +#include "ql/rmgr/resource_types/base.h" +#include "ql/rmgr/factory.h" + +namespace ql { +namespace rmgr { + +// Forward declaration for the manager, so we can declare it as a friend. +class Manager; + +/** + * Maintains the state of a collection of scheduling resources. + */ +class State { +private: + friend class Manager; + + /** + * The list of resources and their state. + */ + utils::Vec resources; + + /** + * Set when reserve() returned an error, implying that the resources are in + * an inconsistent state. When set, further calls to available() and + * reserve() will immediately throw an exception. + */ + utils::Bool is_broken; + + /** + * Constructor for the initial state, called from Manager::build(). + */ + State(); + +public: + + /** + * Copy constructor that clones the resource states. + */ + State(const State &src); + + /** + * Move constructor. + */ + State(State &&src) = default; + + /** + * Copy assignment operator that clones the resource states. + */ + State &operator=(const State &src); + + /** + * Move assignment operator. + */ + State &operator=(State &&src) = default; + + /** + * Checks whether the given gate can be scheduled at the given (start) + * cycle. + */ + utils::Bool available( + utils::UInt cycle, + const ir::GateRef &gate + ) const; + + /** + * Schedules the given gate at the given (start) cycle. Throws an exception + * if this is not possible. When an exception is thrown, the resulting state + * of the resources is undefined. + */ + void reserve( + utils::UInt cycle, + const ir::GateRef &gate + ); + + /** + * Dumps a debug representation of the current resource state. + */ + void dump( + std::ostream &os = std::cout, + const utils::Str &line_prefix = "" + ) const; + +}; + +} // namespace rmgr +} // namespacq ql diff --git a/include/ql/rmgr/types.h b/include/ql/rmgr/types.h new file mode 100644 index 000000000..a983b59bc --- /dev/null +++ b/include/ql/rmgr/types.h @@ -0,0 +1,78 @@ +/** \file + * Defines some basic types used by all resources. + */ + +#pragma once + +#include "ql/utils/num.h" +#include "ql/utils/str.h" +#include "ql/plat/platform.h" +#include "ql/ir/ir.h" + +namespace ql { +namespace rmgr { + +/** + * The direction in which gates are presented to a resource, allowing the + * resource to optimize its state. + */ +enum class Direction { + + /** + * Gates are only reserved with non-decreasing cycle numbers. + */ + FORWARD, + + /** + * Gates are only reserved with non-increasing cycle numbers. + */ + BACKWARD, + + /** + * available() and reserve() may be called with any cycle number. + */ + UNDEFINED + +}; + +/** + * Stream operator for Direction. + */ +std::ostream &operator<<(std::ostream &os, Direction dir); + +/** + * Context for constructing resource instances. + */ +struct Context { + + /** + * The full type name for the resource. This is the full name that was used + * when the resource was registered with the resource factory. The same + * resource class may be registered with multiple type names, in which case + * the pass implementation may use this to differentiate. + */ + utils::Str type_name; + + /** + * The instance name for the resource, i.e. the name that the user assigned + * to it or the name that was assigned to it automatically. Must match + * `[a-zA-Z0-9_\-]+`, and must be unique within a resource manager. + * Instance names should NOT have a semantic meaning; they are only intended + * for logging. + */ + utils::Str instance_name; + + /** + * The platform being compiled for. + */ + plat::PlatformRef platform; + + /** + * Unparsed JSON configuration data for the resource. + */ + utils::Json configuration; + +}; + +} // namespace rmgr +} // namespacq ql diff --git a/include/ql/utils/compat.h b/include/ql/utils/compat.h new file mode 100644 index 000000000..18ed66181 --- /dev/null +++ b/include/ql/utils/compat.h @@ -0,0 +1,55 @@ +/** \file + * Contains utilities for cross-platform compatibility. + */ + +#pragma once + +#include "ql/config.h" + +/** + * Prefix for global variable declarations. + * + * For public globals (declared in a header file), use it like this in the + * header file: + * + * QL_GLOBAL extern ; + * + * The definition in the accompanying CC file doesn't need the prefix in this + * case. Private globals that are *only* defined in a CC file do however need + * the prefix, still: + * + * QL_GLOBAL ; + * + * As for the definition itself... there are three options: + * + * - if OpenQL is linked dynamically (*.dll) and OpenQL is being built, the + * dllexport prefix is needed; + * - if OpenQL is linked dynamically (*.dll) and something depending on it is + * being built, the dllimport prefix is needed; and + * - if OpenQL is linked statically (*.lib), there must be no prefix in either + * case. + * + * All of these are contextual in a way that cannot normally be detected from + * within the C preprocessor; we need to know if and how OpenQL is/was built + * from within a public header file. Whether it's being built now or not is + * determined using BUILDING_OPENQL, which is defined on the command line only + * when OpenQL itself is being compiled. Whether it is being built or was built + * as a static or dynamic library can however only be done using the generated + * config.h file. + * + * This nonsense is unfortunately necessary for Windows library support. + */ +#ifdef _MSC_VER +#ifndef QL_SHARED_LIB +#define QL_GLOBAL +#else +#ifdef BUILDING_OPENQL +#define QL_GLOBAL __declspec(dllexport) +#define QL_GLOBAL +#else +#define QL_GLOBAL __declspec(dllimport) +#endif +#endif +#else +#define QL_GLOBAL +#endif diff --git a/src/utils/container_base.h b/include/ql/utils/container_base.h similarity index 98% rename from src/utils/container_base.h rename to include/ql/utils/container_base.h index a36eb82c9..04fc3b22e 100644 --- a/src/utils/container_base.h +++ b/include/ql/utils/container_base.h @@ -6,7 +6,7 @@ #include #include -#include "utils/exception.h" +#include "ql/utils/exception.h" namespace ql { namespace utils { @@ -148,6 +148,13 @@ template < typename Allocator = std::allocator> > class UncheckedMap; +template < + typename Key, + typename T, + T DEFAULT = 0, + typename Compare = std::less +> +class SparseMap; /** * Wrapper for standard iterators to detect undefined behavior and throw an diff --git a/src/utils/exception.h b/include/ql/utils/exception.h similarity index 98% rename from src/utils/exception.h rename to include/ql/utils/exception.h index d21db4408..b8f051837 100644 --- a/src/utils/exception.h +++ b/include/ql/utils/exception.h @@ -5,7 +5,7 @@ #pragma once #include -#include "utils/str.h" +#include "ql/utils/str.h" namespace ql { namespace utils { diff --git a/src/utils/filesystem.h b/include/ql/utils/filesystem.h similarity index 69% rename from src/utils/filesystem.h rename to include/ql/utils/filesystem.h index ea3dc8503..43bd7db1a 100644 --- a/src/utils/filesystem.h +++ b/include/ql/utils/filesystem.h @@ -5,16 +5,44 @@ #pragma once #include -#include "utils/str.h" -#include "utils/exception.h" +#include "ql/utils/str.h" +#include "ql/utils/exception.h" namespace ql { namespace utils { +/** + * Returns whether the given path exists and is a directory. + */ bool is_dir(const Str &path); + +/** + * Returns whether the given path exists and is a regular file. + */ bool is_file(const Str &path); + +/** + * Returns whether the given path exists. + */ bool path_exists(const Str &path); + +/** + * If path looks like it's a relative path, make it relative to base instead. + * If path looks like it's absolute, return it unchanged. + */ +Str path_relative_to(const Str &base, const Str &path); + +/** + * Returns the directory of the given path. On Linux and MacOS, this just maps + * to dirname() from libgen.h. On Windows, the string is stripped from the last + * backslash or slash onward, if any. + */ Str dir_name(const Str &path); + +/** + * (Recursively) creates a new directory if it does not already exist. Throws + * an Exception if creation of the directory fails. + */ void make_dirs(const Str &path); /** @@ -32,7 +60,7 @@ class OutFile { std::ofstream ofs; Str path; public: - OutFile(const Str &path); + explicit OutFile(const Str &path); void write(const Str &content); void close(); void check(); diff --git a/include/ql/utils/json.h b/include/ql/utils/json.h new file mode 100644 index 000000000..0504b6bb0 --- /dev/null +++ b/include/ql/utils/json.h @@ -0,0 +1,191 @@ +/** \file + * Provides utilities for handling JSON files, and wraps nlohmann::json in + * OpenQL's code style. + */ + +#pragma once + +#include +#include +#include "ql/utils/logger.h" +#include "ql/utils/exception.h" +#include "ql/utils/str.h" +#include "ql/utils/list.h" +#include "ql/utils/set.h" +#include "ql/utils/ptr.h" + +// check existence of JSON key within node, see PR #194 +#define QL_JSON_EXISTS(node, key) ((node).count(key) > 0) + +#define QL_JSON_FATAL(s) QL_FATAL("Error in JSON definition: " << s) // NB: FATAL prepends "Error : " + +#define QL_JSON_ASSERT(node, key, nodePath) \ + do { \ + if (!QL_JSON_EXISTS(node, key)) { \ + QL_JSON_FATAL("key '" << key << "' not found on path '" << nodePath << "', actual node contents '" << node << "'"); \ + } \ + } while (false) + +namespace ql { +namespace utils { + +#if 0 + +using RawJson = nlohmann::json; + +class JsonConfigurable { +protected: + + + + +}; + + +class JsonObjectReader { + + /** + * Registers an action for the given key, treating they key as required. The + * action function will be called by run() exactly once, receiving a value + * reader for the value. + */ + JsonObjectReader &&require( + const Str &key, + const Str &doc, + std::function action + ) &&; + + /** + * Registers an action for the given key, treating they key as required. The + * action function will be called by run() exactly once, receiving a value + * reader for the value. + */ + JsonObjectReader &&optional( + const Str &key, + const Str &doc, + std::function action + ) &&; + + JsonObjectReader &&otherwise(std::function action) &&; + +}; + +class JsonObj { +private: + + /** + * Shared pointer to the root node, to prevent it from being deallocated. + */ + Ptr root; + + /** + * Reference to the current JSON object. Empty when this is the root, + * otherwise it consists of a period-terminated list of period-separated + * elements, with object indices surrounded in "" and array indices + * surrounded in []. + */ + RawJson ¤t; + + /** + * The path leading up to the current JSON object. + */ + Str path; + + /** + * The set of keys that have been referenced thus far. + */ + Set used_keys; + +public: + + /** + * Reads a JSON configuration file. + */ + static JsonObj from_file(const Str &file_name); + + /** + * Parses JSON data from a string. + */ + static JsonObj from_string(const Str &data); + + /** + * Wraps data from nlohmann_json. + */ + static JsonObj from_nlohmann(nlohmann::json &&json); + + /** + * Returns the path leading up to this object from the root. Empty when this + * is the root, otherwise it consists of a period-terminated list of + * period-separated elements, with object indices surrounded in "" and array + * indices surrounded in []. + */ + const Str &get_path() const; + + /** + * Returns the raw nlohmann::json object that we're wrapping. + */ + const RawJson &unwrap() const; + + /** + * Returns the raw nlohmann::json object that we're wrapping. + */ + RawJson &unwrap(); + + bool is_unspecified + + /** + * Returns true if the + */ + void check_object(const List &keys); + +}; + +#endif + +using Json = nlohmann::json; + +/** + * Parses JSON data that may include // comments. + */ +Json parse_json(std::istream &is); + +/** + * Parses JSON data that may include // comments. + */ +Json parse_json(const Str &data); + +/** + * Loads a JSON file that may include // comments. + */ +Json load_json(const Str &file_name); + +// get json value with error notification +// based on: https://github.com/nlohmann/json/issues/932 +template +T json_get(const Json &j, const Str &key, const Str &nodePath = "") { + // first check existence of key + auto it = j.find(key); + if (it == j.end()) { + QL_JSON_FATAL("Key '" << key + << "' not found on path '" << nodePath + << "', actual node contents '" << j << "'"); + } + + // then try to get key + try { + return it->get(); + } catch (const std::exception &e) { + QL_JSON_FATAL("Could not get value of key '" << key + << "' on path '" << nodePath + << "', exception message '" + << e.what() + << "', actual node contents '" << j + << "'"); + } +} + +template<> +const Json &json_get(const Json &j, const Str &key, const Str &nodePath); + +} // namespace utils +} // namespace ql diff --git a/src/utils/list.h b/include/ql/utils/list.h similarity index 98% rename from src/utils/list.h rename to include/ql/utils/list.h index 6ea5a3ebb..f96c860c6 100644 --- a/src/utils/list.h +++ b/include/ql/utils/list.h @@ -6,9 +6,9 @@ #pragma once #include -#include "ql_config.h" -#include "utils/str.h" -#include "utils/container_base.h" +#include "ql/config.h" +#include "ql/utils/str.h" +#include "ql/utils/container_base.h" namespace ql { namespace utils { @@ -28,6 +28,26 @@ class UncheckedList : public std::list { */ using Stl = std::list; + /** + * Forward iterator with mutable access to the values. + */ + using Iter = typename Stl::iterator; + + /** + * Forward iterator with const access to the values. + */ + using ConstIter = typename Stl::const_iterator; + + /** + * Backward iterator with mutable access to the values. + */ + using ReverseIter = typename Stl::reverse_iterator; + + /** + * Backward iterator with const access to the values. + */ + using ConstReverseIter = typename Stl::const_reverse_iterator; + // Member types expected by the standard library. using value_type = T; using allocator_type = Allocator; diff --git a/src/utils/logger.h b/include/ql/utils/logger.h similarity index 62% rename from src/utils/logger.h rename to include/ql/utils/logger.h index 90677a187..d9f0e5a6f 100644 --- a/src/utils/logger.h +++ b/include/ql/utils/logger.h @@ -5,9 +5,9 @@ #pragma once #include -#include "utils/exception.h" -#include "utils/compat.h" -#include "utils/str.h" +#include "ql/utils/exception.h" +#include "ql/utils/compat.h" +#include "ql/utils/str.h" // helper macro: stringstream to string // based on https://stackoverflow.com/questions/21924156/how-to-initialize-a-stdstringstream @@ -67,6 +67,35 @@ } \ } while (false) +#define QL_ASSERT_EQ(a, b) \ + do { \ + auto _a = (a); \ + auto _b = (b); \ + if (_a != _b) { \ + QL_FATAL( \ + "assert \"" << try_to_string(_a) << "\" (" #a ") == " \ + "\"" << try_to_string(_b) << "\" (" #b ") " \ + "failed in file " __FILE__ " at line " << __LINE__ \ + ); \ + } \ + } while (false) + +#define QL_ASSERT_RAISES(code) \ + do { \ + auto ok = false; \ + try { \ + code; \ + } catch (const std::exception &e) { \ + ok = true; \ + } \ + if (!ok) { \ + QL_FATAL("no exception thrown in file " __FILE__ " at line " << __LINE__); \ + } \ + } while (false) + +#define QL_IF_LOG_DEBUG \ + if (::ql::utils::logger::log_level >= ::ql::utils::logger::LogLevel::LOG_DEBUG) + namespace ql { namespace utils { namespace logger { diff --git a/src/utils/map.h b/include/ql/utils/map.h similarity index 88% rename from src/utils/map.h rename to include/ql/utils/map.h index 94e656946..a3922a1b1 100644 --- a/src/utils/map.h +++ b/include/ql/utils/map.h @@ -6,9 +6,9 @@ #pragma once #include -#include "ql_config.h" -#include "utils/str.h" -#include "utils/container_base.h" +#include "ql/config.h" +#include "ql/utils/str.h" +#include "ql/utils/container_base.h" namespace ql { namespace utils { @@ -25,10 +25,10 @@ namespace utils { * is equivalent to what `map[key]` normally does. There is no `const` * version of this method. * - If you want to access an existing key, use `map.at(key)` in place of - * `map[key]`. This will throw a Exception with context information if - * the key does not exist yet. This is equivalent to what `map[at]` normally - * does. There is both a `const` and non-`const` version, the latter giving - * you a mutable reference to the value, the former being immutable. + * `map[key]`. This will throw an Exception with context information if + * the key does not exist yet. This is equivalent to what `map.at(key)` + * normally does. There is both a `const` and non-`const` version, the latter + * giving you a mutable reference to the value, the former being immutable. * - If you want to read a key if it exists but get some default value instead * if it doesn't, for instance when your value type is another container and * empty containers may or may not actually be in the map, use `map.get(key)` @@ -49,6 +49,26 @@ class UncheckedMap : public std::map { */ using Stl = std::map; + /** + * Forward iterator with mutable access to the values. + */ + using Iter = typename Stl::iterator; + + /** + * Forward iterator with const access to the values. + */ + using ConstIter = typename Stl::const_iterator; + + /** + * Backward iterator with mutable access to the values. + */ + using ReverseIter = typename Stl::reverse_iterator; + + /** + * Backward iterator with const access to the values. + */ + using ConstReverseIter = typename Stl::const_reverse_iterator; + /** * Default constructor. Constructs an empty container with a * default-constructed allocator. @@ -63,6 +83,16 @@ class UncheckedMap : public std::map { explicit UncheckedMap(Args&&... args) : Stl(std::forward(args)...) { } + /** + * Copy constructor from Stl variant. + */ + UncheckedMap(const Stl &stl) : Stl(stl) {} + + /** + * Move constructor from Stl variant. + */ + UncheckedMap(Stl &&stl) : Stl(stl) {} + /** * Implicit conversion for initializer lists. */ @@ -886,10 +916,13 @@ class CheckedMap { * No iterators or references are invalidated. */ template - std::pair emplace_hint(const ConstIter &pos, Args&&... args) { + iterator emplace_hint(const ConstIter &pos, Args&&... args) { pos.check(data_ptr); - auto p = get_data().get_mut_element_only().emplace_hint(std::forward(args)...); - return std::make_pair(iterator(std::move(p.first), data_ptr), std::move(p.second)); + auto p = get_data().get_mut_element_only().emplace_hint( + pos.iter, + std::forward(args)... + ); + return iterator(std::move(p), data_ptr); } /** @@ -1108,5 +1141,138 @@ using Map = CheckedMap; using Map = UncheckedMap; #endif +/** + * Specialization of Map that adds operator[] back into it, with the following + * semantics: + * + * - if the key exists, act normally; + * - if the key doesn't exist and const access is sufficient, return DEFAULT; + * - if the key doesn't exist and mutable access is required, add a key to the + * map with DEFAULT as initial value. + */ +template +class SparseMap : public Map { +public: + + /** + * Default constructor. Constructs an empty container with a + * default-constructed allocator. + */ + SparseMap() : Map() {} + + /** + * Constructor arguments are forwarded to the STL container constructor, so + * all constructors of the STL container can be used. + */ + template + explicit SparseMap(Args&&... args) : Map(std::forward(args)...) { + } + + /** + * Implicit conversion for initializer lists. + */ + SparseMap( + std::initializer_list::value_type> init + ) : + Map(init) + {} + + /** + * Default copy constructor. + */ + SparseMap(const SparseMap &map) = default; + + /** + * Default move constructor. + */ + SparseMap(SparseMap &&map) noexcept = default; + + /** + * Default copy assignment. + */ + SparseMap &operator=(const SparseMap &other) = default; + + /** + * Default move assignment. + */ + SparseMap &operator=(SparseMap &&other) noexcept = default; + + /** + * Immutable element access. + */ + const T &operator[](const Key &key) const { + auto it = this->find(key); + if (it == Map::end()) { + static const T DEFLT = DEFAULT; + return &DEFLT; + } else { + return it.second; + } + } + + /** + * Mutable element access. + */ + T &operator[](const Key &key) { + auto it = this->find(key); + if (it == Map::end()) { + return this->set(key) = DEFAULT; + } else { + return it->second; + } + } + + /** + * Returns the number of keys with non-default values. + */ + UInt sparse_size() const { + UInt size = 0; + for (const auto &it : *this) { + if (it.second != DEFAULT) { + size++; + } + } + return size; + } + + /** + * Returns a string representation of the sparse contents of the map. Stream + * << overloads must exist for both the key and value type. + */ + Str to_string( + const Str &prefix = "{", + const Str &key_value_separator = ": ", + const Str &element_separator = ", ", + const Str &suffix = "}" + ) const { + StrStrm ss{}; + ss << prefix; + bool first = true; + for (const auto &kv : *this) { + if (kv.second == DEFAULT) { + continue; + } + if (first) { + first = false; + } else { + ss << element_separator; + } + ss << kv.first << key_value_separator << kv.second; + } + ss << suffix; + return ss.str(); + } + +}; + +/** + * Stream << overload for SparseMap<>. + */ +template +std::ostream &operator<<(std::ostream &os, const ::ql::utils::SparseMap &map) { + os << map.to_string(); + return os; +} + } // namespace utils } // namespace ql diff --git a/src/utils/misc.h b/include/ql/utils/misc.h similarity index 100% rename from src/utils/misc.h rename to include/ql/utils/misc.h diff --git a/src/utils/num.h b/include/ql/utils/num.h similarity index 94% rename from src/utils/num.h rename to include/ql/utils/num.h index 7d17ccf98..c19978348 100644 --- a/src/utils/num.h +++ b/include/ql/utils/num.h @@ -47,6 +47,11 @@ using Real = double; */ using Complex = std::complex; +/** + * Maximum value for a UInt. + */ +const Int UMAX = std::numeric_limits::max(); + /** * Maximum value for an Int. */ @@ -85,6 +90,13 @@ inline int sign_of(T val) noexcept { return (T(0) < val) - (val < T(0)); } +/** + * Divides the given two unsigned integers, rounding up. + */ +inline UInt div_ceil(UInt a, UInt b) { + return (a + b - 1) / b; +} + /** * Rounds the given double toward positive infinity. */ diff --git a/src/utils/opt.h b/include/ql/utils/opt.h similarity index 88% rename from src/utils/opt.h rename to include/ql/utils/opt.h index 0996a722a..2579da2d0 100644 --- a/src/utils/opt.h +++ b/include/ql/utils/opt.h @@ -6,7 +6,7 @@ #pragma once #include -#include "utils/exception.h" +#include "ql/utils/exception.h" namespace ql { namespace utils { @@ -22,9 +22,10 @@ class Opt { private: /** - * The contained value, wrapped in a unique_ptr. + * The contained value, wrapped in a shared_ptr for simplicity. A unique_ptr + * would do, but its interface is a bit more annoying. */ - std::unique_ptr v{}; + std::shared_ptr v{}; public: @@ -37,7 +38,7 @@ class Opt { if (v) { throw Exception("Opt has already been initialized", false); } - v = std::unique_ptr(new S(std::forward(args)...)); + v = std::shared_ptr(new S(std::forward(args)...)); } /** @@ -86,7 +87,7 @@ class Opt { * move constructor of the contained object type. */ Opt &operator=(T &&rhs) { - v = std::unique_ptr(new T(std::forward(rhs))); + v = std::shared_ptr(new T(std::forward(rhs))); return *this; } @@ -125,16 +126,16 @@ class Opt { } /** - * Returns the raw unique_ptr. + * Returns the raw shared_ptr. */ - const std::unique_ptr &unwrap() const { + const std::shared_ptr &unwrap() const { return v; } /** - * Returns the raw unique_ptr. + * Returns the raw shared_ptr. */ - std::unique_ptr &unwrap() { + std::shared_ptr &unwrap() { return v; } @@ -183,7 +184,7 @@ class Opt { */ friend std::ostream &operator<<(std::ostream &os, const Opt &opt) { if (opt.v) { - os << *opt.v; + os << *(opt.v); } else { os << ""; } diff --git a/src/options.h b/include/ql/utils/options.h similarity index 93% rename from src/options.h rename to include/ql/utils/options.h index 0a90647eb..69a4db2e1 100644 --- a/src/options.h +++ b/include/ql/utils/options.h @@ -6,14 +6,13 @@ #include #include -#include "utils/ptr.h" -#include "utils/str.h" -#include "utils/list.h" -#include "utils/map.h" -#include "utils/compat.h" +#include "ql/utils/ptr.h" +#include "ql/utils/str.h" +#include "ql/utils/list.h" +#include "ql/utils/map.h" namespace ql { -namespace options { +namespace utils { /** * Represents an option. That is, some user-configurable value with some @@ -160,7 +159,7 @@ class Option { /** * Writes a help message for this option to the given stream (or stdout). */ - void help(std::ostream &os = std::cout) const; + void dump_help(std::ostream &os = std::cout, const utils::Str &line_prefix = "") const; /** * Registers a callback, to be called when the option changes. @@ -374,6 +373,11 @@ class Options { */ utils::Map> options; + /** + * Order in which the options were added. Used for documentation output. + */ + utils::List option_order; + public: /** @@ -384,6 +388,7 @@ class Options { auto option = utils::Ptr